diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 340c961..43f5081 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -2986,6 +2986,11 @@ VALUES ('Albany', NULL, NULL, 'NY');
foreign table partitions.
+
+ Updating the partition key of a row might cause it to be moved into a
+ different partition where this row satisfies its partition constraint.
+
+
Example
@@ -3278,9 +3283,20 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02
- An UPDATE> that causes a row to move from one partition to
- another fails, because the new value of the row fails to satisfy the
- implicit partition constraint of the original partition.
+ When an UPDATE> causes a row to move from one partition to
+ another, there is a chance that another concurrent UPDATE> or
+ DELETE> misses this row. Suppose, during the row movement,
+ the row is still visible for the concurrent session, and it is about to
+ do an UPDATE> or DELETE> operation on the same
+ row. This DML operation can silently miss this row if the row now gets
+ deleted from the partition by the first session as part of its
+ UPDATE> row movement. In such case, the concurrent
+ UPDATE>/DELETE>, being unaware of the row
+ movement, interprets that the row has just been deleted so there is
+ nothing to be done for this row. Whereas, in the usual case where the
+ table is not partitioned, or where there is no row movement, the second
+ session would have identified the newly updated row and carried
+ UPDATE>/DELETE> on this new row version.
diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml
index 8a1619f..28cfc1a 100644
--- a/doc/src/sgml/ref/update.sgml
+++ b/doc/src/sgml/ref/update.sgml
@@ -282,10 +282,17 @@ UPDATE count
In the case of a partitioned table, updating a row might cause it to no
- longer satisfy the partition constraint. Since there is no provision to
- move the row to the partition appropriate to the new value of its
- partitioning key, an error will occur in this case. This can also happen
- when updating a partition directly.
+ longer satisfy the partition constraint of the containing partition. In that
+ case, if there is some other partition in the partition tree for which this
+ row satisfies its partition constraint, then the row is moved to that
+ partition. If there isn't such a partition, an error will occur. The error
+ will also occur when updating a partition directly. Behind the scenes, the
+ row movement is actually a DELETE> and
+ INSERT> operation. However, there is a possibility that a
+ concurrent UPDATE> or DELETE> on the same row may miss
+ this row. For details see the section
+ .
+
diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml
index 8f724c8..b0ed167 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -151,6 +151,29 @@
+ If an UPDATE on a partitioned table causes a row to
+ move to another partition, it will be performed as a
+ DELETE from the original partition followed by
+ INSERT into the new partition. In this case, all
+ row-level BEFORE> UPDATE triggers and all
+ row-level BEFORE> DELETE triggers are fired
+ on the original partition. Then all row-level BEFORE>
+ INSERT triggers are fired on the destination partition.
+ The possibility of surprising outcomes should be considered when all these
+ triggers affect the row being moved. As far as AFTER ROW>
+ triggers are concerned, AFTER> DELETE and
+ AFTER> INSERT triggers are applied; but
+ AFTER> UPDATE triggers are not applied
+ because the UPDATE has been converted to a
+ DELETE and INSERT. As far as
+ statement-level triggers are concerned, none of the
+ DELETE or INSERT triggers are fired,
+ even if row movement occurs; only the UPDATE triggers
+ defined on the target table used in the UPDATE statement
+ will be fired.
+
+
+
Trigger functions invoked by per-statement triggers should always
return NULL. Trigger functions invoked by per-row
triggers can return a table row (a value of
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 8c58808..c1ccdc5 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2651,7 +2651,7 @@ CopyFrom(CopyState cstate)
/* Check the constraints of the tuple */
if (cstate->rel->rd_att->constr ||
resultRelInfo->ri_PartitionCheck)
- ExecConstraints(resultRelInfo, slot, oldslot, estate);
+ ExecConstraints(resultRelInfo, slot, oldslot, estate, true);
if (useHeapMultiInsert)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 920b120..d4ba965 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1783,7 +1783,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)
{
@@ -1820,8 +1820,8 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
/*
* ExecConstraints - check constraints of the tuple in 'slot'
*
- * This checks the traditional NOT NULL and check constraints, as well as
- * the partition constraint, if any.
+ * This checks the traditional NOT NULL and check constraints, and if requested,
+ * checks the partition constraint.
*
* Note: 'slot' contains the tuple to check the constraints of, which may
* have been converted from the original input tuple after tuple routing,
@@ -1831,7 +1831,7 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
void
ExecConstraints(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, TupleTableSlot *orig_slot,
- EState *estate)
+ EState *estate, bool check_partition_constraint)
{
Relation rel = resultRelInfo->ri_RelationDesc;
TupleDesc tupdesc = RelationGetDescr(rel);
@@ -1918,33 +1918,51 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
}
}
- if (resultRelInfo->ri_PartitionCheck &&
+ if (check_partition_constraint && resultRelInfo->ri_PartitionCheck &&
!ExecPartitionCheck(resultRelInfo, slot, estate))
- {
- char *val_desc;
- Relation orig_rel = rel;
+ ExecPartitionCheckEmitError(resultRelInfo, orig_slot, estate);
+}
- /* See the comment above. */
- if (resultRelInfo->ri_PartitionRoot)
- {
- rel = resultRelInfo->ri_PartitionRoot;
- tupdesc = RelationGetDescr(rel);
- }
+/*
+ * ExecPartitionCheckEmitError - Form and emit an error message after a failed
+ * partition constraint check.
+ *
+ * 'orig_slot' contains the original tuple to be shown in the error message.
+ */
+void
+ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
+ TupleTableSlot *orig_slot,
+ EState *estate)
+{
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ Relation orig_rel = rel;
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ char *val_desc;
+ Bitmapset *modifiedCols;
+ Bitmapset *insertedCols;
+ Bitmapset *updatedCols;
- insertedCols = GetInsertedColumns(resultRelInfo, estate);
- updatedCols = GetUpdatedColumns(resultRelInfo, estate);
- modifiedCols = bms_union(insertedCols, updatedCols);
- val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
- orig_slot,
- tupdesc,
- modifiedCols,
- 64);
- ereport(ERROR,
- (errcode(ERRCODE_CHECK_VIOLATION),
- errmsg("new row for relation \"%s\" violates partition constraint",
- RelationGetRelationName(orig_rel)),
- val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+
+ /* See the comments in ExecConstraints. */
+ if (resultRelInfo->ri_PartitionRoot)
+ {
+ rel = resultRelInfo->ri_PartitionRoot;
+ tupdesc = RelationGetDescr(rel);
}
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ orig_slot,
+ tupdesc,
+ modifiedCols,
+ 64);
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("new row for relation \"%s\" violates partition constraint",
+ RelationGetRelationName(orig_rel)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
}
/*
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index f20d728..2f76140 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -389,7 +389,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
/* Check the constraints of the tuple */
if (rel->rd_att->constr)
- ExecConstraints(resultRelInfo, slot, slot, estate);
+ ExecConstraints(resultRelInfo, slot, slot, estate, true);
/* Store the slot into tuple that we can inspect. */
tuple = ExecMaterializeSlot(slot);
@@ -448,7 +448,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
/* Check the constraints of the tuple */
if (rel->rd_att->constr)
- ExecConstraints(resultRelInfo, slot, slot, estate);
+ ExecConstraints(resultRelInfo, slot, slot, estate, true);
/* Store the slot into tuple that we can write. */
tuple = ExecMaterializeSlot(slot);
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 0b524e0..64e40fe 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -62,7 +62,10 @@ static bool ExecOnConflictUpdate(ModifyTableState *mtstate,
EState *estate,
bool canSetTag,
TupleTableSlot **returning);
-
+static void ExecInitPartitionWithCheckOptions(ModifyTableState *mtstate,
+ Relation root_rel);
+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
@@ -435,7 +438,7 @@ ExecInsert(ModifyTableState *mtstate,
* Check the constraints of the tuple
*/
if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
- ExecConstraints(resultRelInfo, slot, oldslot, estate);
+ ExecConstraints(resultRelInfo, slot, oldslot, estate, true);
if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
{
@@ -625,6 +628,8 @@ ExecDelete(ItemPointer tupleid,
TupleTableSlot *planSlot,
EPQState *epqstate,
EState *estate,
+ bool *concurrently_deleted,
+ bool process_returning,
bool canSetTag)
{
ResultRelInfo *resultRelInfo;
@@ -633,6 +638,9 @@ ExecDelete(ItemPointer tupleid,
HeapUpdateFailureData hufd;
TupleTableSlot *slot = NULL;
+ if (concurrently_deleted)
+ *concurrently_deleted = false;
+
/*
* get information on the (current) result relation
*/
@@ -776,6 +784,8 @@ ldelete:;
}
}
/* tuple already deleted; nothing to do */
+ if (concurrently_deleted)
+ *concurrently_deleted = true;
return NULL;
default:
@@ -799,8 +809,8 @@ ldelete:;
/* AFTER ROW DELETE Triggers */
ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple);
- /* Process RETURNING if present */
- if (resultRelInfo->ri_projectReturning)
+ /* Process RETURNING if present and if requested */
+ if (process_returning && resultRelInfo->ri_projectReturning)
{
/*
* We have to put the target tuple into a slot, which means first we
@@ -878,7 +888,8 @@ ldelete:;
* ----------------------------------------------------------------
*/
static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(ModifyTableState *mtstate,
+ ItemPointer tupleid,
HeapTuple oldtuple,
TupleTableSlot *slot,
TupleTableSlot *planSlot,
@@ -988,12 +999,90 @@ lreplace:;
resultRelInfo, slot, estate);
/*
+ * If a partition check fails, try to move the row into the right
+ * partition.
+ */
+ if (resultRelInfo->ri_PartitionCheck &&
+ !ExecPartitionCheck(resultRelInfo, slot, estate))
+ {
+ bool is_partitioned_table = true;
+
+ if (mtstate->mt_partition_dispatch_info == NULL)
+ {
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ Relation root_rel;
+
+ /*
+ * If this is a partitioned table, we need to open the root
+ * table RT index which 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 /* this may be a leaf partition */
+ 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 WITH CHECK OPTION constraints for leaf partitions */
+ ExecInitPartitionWithCheckOptions(mtstate, root_rel);
+
+ /* Build a projection for each leaf partition rel. */
+ ExecInitPartitionReturningProjection(mtstate, root_rel);
+
+ /* Close the root partitioned rel if we opened it above. */
+ if (root_rel != mtstate->resultRelInfo->ri_RelationDesc)
+ heap_close(root_rel, NoLock);
+ }
+
+ if (is_partitioned_table)
+ {
+ bool concurrently_deleted;
+
+ /*
+ * Skip RETURNING processing for DELETE. We want to return rows
+ * from INSERT.
+ */
+ ExecDelete(tupleid, oldtuple, planSlot, epqstate, estate,
+ &concurrently_deleted, false, false);
+
+ if (concurrently_deleted)
+ return NULL;
+
+ return ExecInsert(mtstate, slot, planSlot, NULL,
+ ONCONFLICT_NONE, estate, canSetTag);
+ }
+
+ /* It's not a partitioned table after all; error out. */
+ ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
+ }
+
+ /*
* Check the constraints of the tuple. Note that we pass the same
* slot for the orig_slot argument, because unlike ExecInsert(), no
* tuple-routing is performed here, hence the slot remains unchanged.
+ * We have already checked partition constraints above, so skip them
+ * below.
*/
- if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
- ExecConstraints(resultRelInfo, slot, slot, estate);
+ if (resultRelationDesc->rd_att->constr)
+ ExecConstraints(resultRelInfo, slot, slot, estate, false);
/*
* replace the heap tuple
@@ -1313,7 +1402,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 +1672,13 @@ 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, true, node->canSetTag);
break;
default:
elog(ERROR, "unknown operation");
@@ -1790,44 +1880,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
}
/*
- * Build WITH CHECK OPTION constraints for each leaf partition rel.
- * Note that we didn't build the withCheckOptionList 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 cases are handled above.
+ * Build WITH CHECK OPTION constraints for each leaf partition rel. This
+ * only occurs for INSERT case; UPDATE/DELETE are handled above.
*/
- if (node->withCheckOptionLists != NIL && mtstate->mt_num_partitions > 0)
- {
- List *wcoList;
-
- Assert(operation == CMD_INSERT);
- resultRelInfo = mtstate->mt_partitions;
- wcoList = linitial(node->withCheckOptionLists);
- for (i = 0; i < mtstate->mt_num_partitions; i++)
- {
- Relation partrel = resultRelInfo->ri_RelationDesc;
- List *mapped_wcoList;
- List *wcoExprs = NIL;
- ListCell *ll;
-
- /* varno = node->nominalRelation */
- mapped_wcoList = map_partition_varattnos(wcoList,
- node->nominalRelation,
- partrel, rel);
- foreach(ll, mapped_wcoList)
- {
- WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
- mtstate->mt_plans[i]);
-
- wcoExprs = lappend(wcoExprs, wcoExpr);
- }
-
- resultRelInfo->ri_WithCheckOptions = mapped_wcoList;
- resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
- resultRelInfo++;
- }
- }
+ ExecInitPartitionWithCheckOptions(mtstate, rel);
/*
* Initialize RETURNING projections if needed.
@@ -1836,7 +1892,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
@@ -1870,28 +1925,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;
-
- /* varno = node->nominalRelation */
- rlist = map_partition_varattnos(returningList,
- node->nominalRelation,
- partrel, rel);
- resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
- resultRelInfo->ri_RelationDesc->rd_att);
- resultRelInfo++;
- }
+ ExecInitPartitionReturningProjection(mtstate, rel);
}
else
{
@@ -2118,6 +2155,104 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
}
/* ----------------------------------------------------------------
+ * ExecInitPartitionWithCheckOptions
+ *
+ * Build WITH CHECK OPTION constraints for each leaf partition rel.
+ * Note that we don't build the withCheckOptionList for each partition
+ * within the planner, but simple translation of the varattnos for each
+ * partition suffices. This only occurs for the INSERT case; UPDATE/DELETE
+ * cases are handled separately.
+ * ----------------------------------------------------------------
+ */
+
+static void
+ExecInitPartitionWithCheckOptions(ModifyTableState *mtstate, Relation root_rel)
+{
+ ResultRelInfo *resultRelInfo = mtstate->mt_partitions;
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ List *wcoList;
+ int i;
+
+ if (node->withCheckOptionLists == NIL || mtstate->mt_num_partitions == 0)
+ return;
+
+ wcoList = linitial(node->withCheckOptionLists);
+ for (i = 0; i < mtstate->mt_num_partitions; i++)
+ {
+ Relation partrel = resultRelInfo->ri_RelationDesc;
+ List *mapped_wcoList;
+ List *wcoExprs = NIL;
+ ListCell *ll;
+
+ /* varno = node->nominalRelation */
+ mapped_wcoList = map_partition_varattnos(wcoList,
+ node->nominalRelation,
+ partrel, root_rel);
+ foreach(ll, mapped_wcoList)
+ {
+ WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ mtstate->mt_plans[i]);
+
+ wcoExprs = lappend(wcoExprs, wcoExpr);
+ }
+
+ resultRelInfo->ri_WithCheckOptions = mapped_wcoList;
+ resultRelInfo->ri_WithCheckOptionExprs = wcoExprs;
+ resultRelInfo++;
+ }
+}
+
+/* ----------------------------------------------------------------
+ * 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;
+
+ /* varno = node->nominalRelation */
+ rlist = map_partition_varattnos(returningList,
+ node->nominalRelation,
+ partrel, root_rel);
+ resultRelInfo->ri_projectReturning =
+ ExecBuildProjectionInfo(rlist,
+ mtstate->ps.ps_ExprContext,
+ returning_slot,
+ &mtstate->ps,
+ resultRelInfo->ri_RelationDesc->rd_att);
+ resultRelInfo++;
+ }
+}
+
+
+/* ----------------------------------------------------------------
* ExecEndModifyTable
*
* Shuts down the plan.
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index d3849b9..102fc97 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -187,7 +187,9 @@ extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
extern void ExecConstraints(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, TupleTableSlot *orig_slot,
- EState *estate);
+ EState *estate, bool check_partition_constraint);
+extern void ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
+ TupleTableSlot *orig_slot, EState *estate);
extern void ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate);
extern LockTupleMode ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo);
@@ -216,6 +218,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..a56afab 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -198,25 +198,121 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
DROP TABLE update_test;
DROP TABLE upsert_test;
--- update to a partition should check partition bound constraint for the new tuple
-create table range_parted (
+-- update to a partition should check partition bound constraint for the new tuple.
+-- If partition key is updated, the row should be moved to the appropriate
+-- partition. updatable views using partitions should enforce the check options
+-- for the rows that have been moved.
+CREATE TABLE range_parted (
a text,
- b int
+ b int,
+ c int
) partition by range (a, b);
+CREATE VIEW upview AS SELECT * FROM range_parted WHERE c > 120 WITH CHECK OPTION;
create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
-create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20) partition by range (c);
+create table part_c_1_100 partition of part_b_10_b_20 for values from (1) to (100);
+create table part_c_100_200 partition of part_b_10_b_20 for values from (100) to (200);
insert into part_a_1_a_10 values ('a', 1);
-insert into part_b_10_b_20 values ('b', 10);
--- fail
-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).
-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;
+insert into part_a_10_a_20 values ('a', 10, 200);
+insert into part_c_1_100 values ('b', 12, 96);
+insert into part_c_1_100 values ('b', 13, 97);
+insert into part_c_100_200 values ('b', 15, 105);
+insert into part_c_100_200 values ('b', 17, 105);
+-- fail (row movement happens only within the partition subtree) :
+update part_c_1_100 set c = c + 20 where c = 96; -- No row found :
+ERROR: new row for relation "part_c_1_100" violates partition constraint
+DETAIL: Failing row contains (b, 12, 116).
+update part_c_1_100 set c = c + 20 where c = 98;
+-- ok (row movement)
+update part_b_10_b_20 set c = c + 20 ;
+select * from part_c_1_100 order by 1, 2, 3;
+ a | b | c
+---+---+---
+(0 rows)
+
+select * from part_c_100_200 order by 1, 2, 3;
+ a | b | c
+---+----+-----
+ b | 12 | 116
+ b | 13 | 117
+ b | 15 | 125
+ b | 17 | 125
+(4 rows)
+
+-- fail (row movement happens only within the partition subtree) :
+update part_b_10_b_20 set b = b - 6 where c > 116 returning *;
+ERROR: new row for relation "part_c_100_200" violates partition constraint
+DETAIL: Failing row contains (b, 7, 117).
+-- ok (row movement, with subset of rows moved into different partition)
+update range_parted set b = b - 6 where c > 116 returning a, b + c;
+ a | ?column?
+---+----------
+ a | 204
+ b | 124
+ b | 134
+ b | 136
+(4 rows)
+
+select * from part_a_1_a_10 order by 1, 2, 3;
+ a | b | c
+---+---+-----
+ a | 1 |
+ a | 4 | 200
+(2 rows)
+
+select * from part_a_10_a_20 order by 1, 2, 3;
+ a | b | c
+---+---+---
+(0 rows)
+
+select * from part_b_1_b_10 order by 1, 2, 3;
+ a | b | c
+---+---+-----
+ b | 7 | 117
+ b | 9 | 125
+(2 rows)
+
+select * from part_c_1_100 order by 1, 2, 3;
+ a | b | c
+---+---+---
+(0 rows)
+
+select * from part_c_100_200 order by 1, 2, 3;
+ a | b | c
+---+----+-----
+ b | 11 | 125
+ b | 12 | 116
+(2 rows)
+
+-- update partition key using updatable view.
+-- succeeds
+update upview set c = 199 where b = 4;
+-- fail, check option violation
+update upview set c = 120 where b = 4;
+ERROR: new row violates check option for view "upview"
+DETAIL: Failing row contains (a, 4, 120).
+-- fail, row movement with check option violation
+update upview set a = 'b', b = 15, c = 120 where b = 4;
+ERROR: new row violates check option for view "upview"
+DETAIL: Failing row contains (b, 15, 120).
+-- succeeds, row movement , check option passes
+update upview set a = 'b', b = 15 where b = 4;
+select * from part_a_1_a_10 order by 1, 2, 3;
+ a | b | c
+---+---+---
+ a | 1 |
+(1 row)
+
+select * from part_c_100_200 order by 1, 2, 3;
+ a | b | c
+---+----+-----
+ b | 11 | 125
+ b | 12 | 116
+ b | 15 | 199
+(3 rows)
+
-- cleanup
+drop view upview;
drop table range_parted;
diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql
index 6637119..cda9906 100644
--- a/src/test/regress/sql/update.sql
+++ b/src/test/regress/sql/update.sql
@@ -107,23 +107,61 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a)
DROP TABLE update_test;
DROP TABLE upsert_test;
--- update to a partition should check partition bound constraint for the new tuple
-create table range_parted (
+-- update to a partition should check partition bound constraint for the new tuple.
+-- If partition key is updated, the row should be moved to the appropriate
+-- partition. updatable views using partitions should enforce the check options
+-- for the rows that have been moved.
+CREATE TABLE range_parted (
a text,
- b int
+ b int,
+ c int
) partition by range (a, b);
+CREATE VIEW upview AS SELECT * FROM range_parted WHERE c > 120 WITH CHECK OPTION;
+
create table part_a_1_a_10 partition of range_parted for values from ('a', 1) to ('a', 10);
create table part_a_10_a_20 partition of range_parted for values from ('a', 10) to ('a', 20);
create table part_b_1_b_10 partition of range_parted for values from ('b', 1) to ('b', 10);
-create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20);
+create table part_b_10_b_20 partition of range_parted for values from ('b', 10) to ('b', 20) partition by range (c);
+create table part_c_1_100 partition of part_b_10_b_20 for values from (1) to (100);
+create table part_c_100_200 partition of part_b_10_b_20 for values from (100) to (200);
insert into part_a_1_a_10 values ('a', 1);
-insert into part_b_10_b_20 values ('b', 10);
-
--- fail
-update part_a_1_a_10 set a = 'b' where a = 'a';
-update range_parted set b = b - 1 where b = 10;
--- ok
-update range_parted set b = b + 1 where b = 10;
-
+insert into part_a_10_a_20 values ('a', 10, 200);
+insert into part_c_1_100 values ('b', 12, 96);
+insert into part_c_1_100 values ('b', 13, 97);
+insert into part_c_100_200 values ('b', 15, 105);
+insert into part_c_100_200 values ('b', 17, 105);
+
+-- fail (row movement happens only within the partition subtree) :
+update part_c_1_100 set c = c + 20 where c = 96; -- No row found :
+update part_c_1_100 set c = c + 20 where c = 98;
+-- ok (row movement)
+update part_b_10_b_20 set c = c + 20 ;
+select * from part_c_1_100 order by 1, 2, 3;
+select * from part_c_100_200 order by 1, 2, 3;
+
+-- fail (row movement happens only within the partition subtree) :
+update part_b_10_b_20 set b = b - 6 where c > 116 returning *;
+-- ok (row movement, with subset of rows moved into different partition)
+update range_parted set b = b - 6 where c > 116 returning a, b + c;
+
+select * from part_a_1_a_10 order by 1, 2, 3;
+select * from part_a_10_a_20 order by 1, 2, 3;
+select * from part_b_1_b_10 order by 1, 2, 3;
+select * from part_c_1_100 order by 1, 2, 3;
+select * from part_c_100_200 order by 1, 2, 3;
+
+-- update partition key using updatable view.
+
+-- succeeds
+update upview set c = 199 where b = 4;
+-- fail, check option violation
+update upview set c = 120 where b = 4;
+-- fail, row movement with check option violation
+update upview set a = 'b', b = 15, c = 120 where b = 4;
+-- succeeds, row movement , check option passes
+update upview set a = 'b', b = 15 where b = 4;
+select * from part_a_1_a_10 order by 1, 2, 3;
+select * from part_c_100_200 order by 1, 2, 3;
-- cleanup
+drop view upview;
drop table range_parted;