diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index c28cf9c..2878ff3 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -1778,7 +1778,7 @@ ExecRelCheck(ResultRelInfo *resultRelInfo, * * Note: This is called *iff* resultRelInfo is the main target table. */ -static bool +bool ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate) { diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 29c6a6e..287af13 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -62,7 +62,7 @@ static bool ExecOnConflictUpdate(ModifyTableState *mtstate, EState *estate, bool canSetTag, TupleTableSlot **returning); - +static void ExecInitPartitionReturningProjection(ModifyTableState *mtstate, Relation root_rel); /* * Verify that the tuples to be produced by INSERT or UPDATE match the * target relation's rowtype @@ -625,6 +625,7 @@ ExecDelete(ItemPointer tupleid, TupleTableSlot *planSlot, EPQState *epqstate, EState *estate, + bool *concurrently_deleted, bool canSetTag) { ResultRelInfo *resultRelInfo; @@ -633,6 +634,9 @@ ExecDelete(ItemPointer tupleid, HeapUpdateFailureData hufd; TupleTableSlot *slot = NULL; + if (concurrently_deleted) + *concurrently_deleted = false; + /* * get information on the (current) result relation */ @@ -776,6 +780,8 @@ ldelete:; } } /* tuple already deleted; nothing to do */ + if (concurrently_deleted) + *concurrently_deleted = true; return NULL; default: @@ -878,7 +884,8 @@ ldelete:; * ---------------------------------------------------------------- */ static TupleTableSlot * -ExecUpdate(ItemPointer tupleid, +ExecUpdate(ModifyTableState *mtstate, + ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot, TupleTableSlot *planSlot, @@ -987,6 +994,69 @@ lreplace:; ExecWithCheckOptions(WCO_RLS_UPDATE_CHECK, resultRelInfo, slot, estate); + if (resultRelInfo->ri_PartitionCheck && + !ExecPartitionCheck(resultRelInfo, slot, estate)) + { + bool is_partitioned_table = true; + + if (!mtstate->mt_partition_dispatch_info) + { + ModifyTable *node = (ModifyTable *) mtstate->ps.plan; + Relation root_rel; + + /* root table RT index is at the head of partitioned_rels */ + if (node->partitioned_rels) + { + Index root_rti; + Oid root_oid; + + root_rti = linitial_int(node->partitioned_rels); + root_oid = getrelid(root_rti, estate->es_range_table); + root_rel = heap_open(root_oid, NoLock); /* locked by InitPlan */ + } + else + root_rel = mtstate->resultRelInfo->ri_RelationDesc; + + is_partitioned_table = + root_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE; + + if (is_partitioned_table) + ExecSetupPartitionTupleRouting( + root_rel, + &mtstate->mt_partition_dispatch_info, + &mtstate->mt_partitions, + &mtstate->mt_partition_tupconv_maps, + &mtstate->mt_partition_tuple_slot, + &mtstate->mt_num_dispatch, + &mtstate->mt_num_partitions); + + /* Build a projection for each leaf partition rel. */ + ExecInitPartitionReturningProjection(mtstate, root_rel); + } + + /* + * If it's not a partitioned table after all, let it fall through + * the usual error handling. + */ + if (is_partitioned_table) + { + bool concurrently_deleted; + + ExecDelete(tupleid, oldtuple, planSlot, epqstate, estate, + &concurrently_deleted, canSetTag); + + if (concurrently_deleted) + return NULL; + + /* + * Don't update estate.es_processed updated again. ExecDelete() + * has already done it above. So use canSetTag=false. + */ + return ExecInsert(mtstate, slot, planSlot, NULL, + ONCONFLICT_NONE, estate, false); + } + } + /* * Check the constraints of the tuple. Note that we pass the same * slot for the orig_slot argument, because unlike ExecInsert(), no @@ -1313,7 +1383,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); @@ -1583,12 +1653,12 @@ 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, - &node->mt_epqstate, estate, node->canSetTag); + &node->mt_epqstate, estate, NULL, node->canSetTag); break; default: elog(ERROR, "unknown operation"); @@ -1837,7 +1907,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) { TupleTableSlot *slot; ExprContext *econtext; - List *returningList; /* * Initialize result tuple slot and assign its rowtype using the first @@ -1872,30 +1941,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) } /* - * Build a projection for each leaf partition rel. Note that we - * didn't build the returningList for each partition within the - * planner, but simple translation of the varattnos for each partition - * will suffice. This only occurs for the INSERT case; UPDATE/DELETE - * are handled above. + * Build a projection for each leaf partition rel. This only occurs for + * the INSERT case; UPDATE/DELETE are handled above. */ - resultRelInfo = mtstate->mt_partitions; - returningList = linitial(node->returningLists); - for (i = 0; i < mtstate->mt_num_partitions; i++) - { - Relation partrel = resultRelInfo->ri_RelationDesc; - List *rlist, - *rliststate; - - /* varno = node->nominalRelation */ - rlist = map_partition_varattnos(returningList, - node->nominalRelation, - partrel, rel); - rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps); - resultRelInfo->ri_projectReturning = - ExecBuildProjectionInfo(rliststate, econtext, slot, - resultRelInfo->ri_RelationDesc->rd_att); - resultRelInfo++; - } + ExecInitPartitionReturningProjection(mtstate, rel); } else { @@ -2124,6 +2173,56 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) } /* ---------------------------------------------------------------- + * ExecInitPartitionReturningProjection + * + * Initialize stuff required to handle RETURNING for leaf partitions. + * We don't build the returningList for each partition within the planner, but + * simple translation of the varattnos for each partition suffices. This + * actually is helpful only for INSERT case; UPDATE/DELETE are handled + * differently. + * ---------------------------------------------------------------- + */ +static void +ExecInitPartitionReturningProjection(ModifyTableState *mtstate, Relation root_rel) +{ + ResultRelInfo *resultRelInfo = mtstate->mt_partitions; + ModifyTable *node = (ModifyTable *) mtstate->ps.plan; + TupleTableSlot *returning_slot = mtstate->ps.ps_ResultTupleSlot; + List *returningList; + int i; + + /* + * If there is no returning clause, or if we have already initialized the + * returning projection info, there is nothing to be done. + */ + if (node->returningLists == NIL || + (resultRelInfo && resultRelInfo->ri_projectReturning != NULL) || + mtstate->mt_num_partitions == 0) + return; + + returningList = linitial(node->returningLists); + for (i = 0; i < mtstate->mt_num_partitions; i++) + { + Relation partrel = resultRelInfo->ri_RelationDesc; + List *rlist, + *rliststate; + + /* varno = node->nominalRelation */ + rlist = map_partition_varattnos(returningList, + node->nominalRelation, + partrel, root_rel); + rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps); + resultRelInfo->ri_projectReturning = + ExecBuildProjectionInfo(rliststate, + mtstate->ps.ps_ExprContext, + returning_slot, + resultRelInfo->ri_RelationDesc->rd_att); + resultRelInfo++; + } +} + + +/* ---------------------------------------------------------------- * ExecEndModifyTable * * Shuts down the plan. @@ -2154,10 +2253,19 @@ ExecEndModifyTable(ModifyTableState *node) * Close all the partitioned tables, leaf partitions, and their indices * * Remember node->mt_partition_dispatch_info[0] corresponds to the root - * partitioned table, which we must not try to close, because it is the - * main target table of the query that will be closed by ExecEndPlan(). - * Also, tupslot is NULL for the root partitioned table. + * partitioned table, which should not be closed if it is the main target + * table of the query, which will be closed by ExecEndPlan(). Also, tupslot + * is NULL for the root partitioned table. */ + if (node->mt_num_dispatch > 0) + { + Relation root_partition; + + root_partition = node->mt_partition_dispatch_info[0]->reldesc; + if (root_partition != node->resultRelInfo->ri_RelationDesc) + heap_close(root_partition, NoLock); + } + for (i = 1; i < node->mt_num_dispatch; i++) { PartitionDispatch pd = node->mt_partition_dispatch_info[i]; @@ -2165,6 +2273,7 @@ ExecEndModifyTable(ModifyTableState *node) heap_close(pd->reldesc, NoLock); ExecDropSingleTupleTableSlot(pd->tupslot); } + for (i = 0; i < node->mt_num_partitions; i++) { ResultRelInfo *resultRelInfo = node->mt_partitions + i; diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index a5c75e7..1fc7cb2 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -225,6 +225,9 @@ extern int ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd, TupleTableSlot *slot, EState *estate); +extern bool ExecPartitionCheck(ResultRelInfo *resultRelInfo, + TupleTableSlot *slot, + EState *estate); #define EvalPlanQualSetSlot(epqstate, slot) ((epqstate)->origslot = (slot)) extern void EvalPlanQualFetchRowMarks(EPQState *epqstate); diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out index 9366f04..99c8046 100644 --- a/src/test/regress/expected/update.out +++ b/src/test/regress/expected/update.out @@ -209,13 +209,12 @@ create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20); insert into part_a_1_a_10 values ('a', 1); insert into part_b_10_b_20 values ('b', 10); --- fail +-- fail (row movement happens only within the partition subtree) update part_a_1_a_10 set a = 'b' where a = 'a'; ERROR: new row for relation "part_a_1_a_10" violates partition constraint DETAIL: Failing row contains (b, 1). +-- ok (row movement) update range_parted set b = b - 1 where b = 10; -ERROR: new row for relation "part_b_10_b_20" violates partition constraint -DETAIL: Failing row contains (b, 9). -- ok update range_parted set b = b + 1 where b = 10; -- cleanup diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql index 6637119..7667793 100644 --- a/src/test/regress/sql/update.sql +++ b/src/test/regress/sql/update.sql @@ -119,8 +119,9 @@ create table part_b_10_b_20 partition of range_parted for values from ('b', 10) insert into part_a_1_a_10 values ('a', 1); insert into part_b_10_b_20 values ('b', 10); --- fail +-- fail (row movement happens only within the partition subtree) update part_a_1_a_10 set a = 'b' where a = 'a'; +-- ok (row movement) update range_parted set b = b - 1 where b = 10; -- ok update range_parted set b = b + 1 where b = 10;