mirror of
https://github.com/postgres/postgres.git
synced 2025-06-27 23:21:58 +03:00
Prevent BEFORE triggers from violating partitioning constraints.
Since tuple-routing implicitly checks the partitioning constraints
at least for the levels of the partitioning hierarchy it traverses,
there's normally no need to revalidate the partitioning constraint
after performing tuple routing. However, if there's a BEFORE trigger
on the target partition, it could modify the tuple, causing the
partitioning constraint to be violated. Catch that case.
Also, instead of checking the root table's partition constraint after
tuple-routing, check it beforehand. Otherwise, the rules for when
the partitioning constraint gets checked get too complicated, because
you sometimes have to check part of the constraint but not all of it.
This effectively reverts commit 39162b2030
in favor of a different approach altogether.
Report by me. Initial debugging by Jeevan Ladhe. Patch by Amit
Langote, reviewed by me.
Discussion: http://postgr.es/m/CA+Tgmoa9DTgeVOqopieV8d1QRpddmP65aCdxyjdYDoEO5pS5KA@mail.gmail.com
This commit is contained in:
@ -103,6 +103,8 @@ static char *ExecBuildSlotPartitionKeyDescription(Relation rel,
|
||||
int maxfieldlen);
|
||||
static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
|
||||
Plan *planTree);
|
||||
static void ExecPartitionCheck(ResultRelInfo *resultRelInfo,
|
||||
TupleTableSlot *slot, EState *estate);
|
||||
|
||||
/*
|
||||
* Note that GetUpdatedColumns() also exists in commands/trigger.c. There does
|
||||
@ -1339,34 +1341,19 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
|
||||
resultRelInfo->ri_projectReturning = NULL;
|
||||
|
||||
/*
|
||||
* If partition_root has been specified, that means we are building the
|
||||
* ResultRelInfo for one of its leaf partitions. In that case, we need
|
||||
* *not* initialize the leaf partition's constraint, but rather the
|
||||
* partition_root's (if any). We must do that explicitly like this,
|
||||
* because implicit partition constraints are not inherited like user-
|
||||
* defined constraints and would fail to be enforced by ExecConstraints()
|
||||
* after a tuple is routed to a leaf partition.
|
||||
* Partition constraint, which also includes the partition constraint of
|
||||
* all the ancestors that are partitions. Note that it will be checked
|
||||
* even in the case of tuple-routing where this table is the target leaf
|
||||
* partition, if there any BR triggers defined on the table. Although
|
||||
* tuple-routing implicitly preserves the partition constraint of the
|
||||
* target partition for a given row, the BR triggers may change the row
|
||||
* such that the constraint is no longer satisfied, which we must fail
|
||||
* for by checking it explicitly.
|
||||
*
|
||||
* If this is a partitioned table, the partition constraint (if any) of a
|
||||
* given row will be checked just before performing tuple-routing.
|
||||
*/
|
||||
if (partition_root)
|
||||
{
|
||||
/*
|
||||
* Root table itself may or may not be a partition; partition_check
|
||||
* would be NIL in the latter case.
|
||||
*/
|
||||
partition_check = RelationGetPartitionQual(partition_root);
|
||||
|
||||
/*
|
||||
* This is not our own partition constraint, but rather an ancestor's.
|
||||
* So any Vars in it bear the ancestor's attribute numbers. We must
|
||||
* switch them to our own. (dummy varno = 1)
|
||||
*/
|
||||
if (partition_check != NIL)
|
||||
partition_check = map_partition_varattnos(partition_check, 1,
|
||||
resultRelationDesc,
|
||||
partition_root);
|
||||
}
|
||||
else
|
||||
partition_check = RelationGetPartitionQual(resultRelationDesc);
|
||||
partition_check = RelationGetPartitionQual(resultRelationDesc);
|
||||
|
||||
resultRelInfo->ri_PartitionCheck = partition_check;
|
||||
resultRelInfo->ri_PartitionRoot = partition_root;
|
||||
@ -1835,13 +1822,16 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
|
||||
|
||||
/*
|
||||
* ExecPartitionCheck --- check that tuple meets the partition constraint.
|
||||
*
|
||||
* Note: This is called *iff* resultRelInfo is the main target table.
|
||||
*/
|
||||
static bool
|
||||
static void
|
||||
ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
|
||||
EState *estate)
|
||||
{
|
||||
Relation rel = resultRelInfo->ri_RelationDesc;
|
||||
TupleDesc tupdesc = RelationGetDescr(rel);
|
||||
Bitmapset *modifiedCols;
|
||||
Bitmapset *insertedCols;
|
||||
Bitmapset *updatedCols;
|
||||
ExprContext *econtext;
|
||||
|
||||
/*
|
||||
@ -1869,7 +1859,44 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
|
||||
* As in case of the catalogued constraints, we treat a NULL result as
|
||||
* success here, not a failure.
|
||||
*/
|
||||
return ExecCheck(resultRelInfo->ri_PartitionCheckExpr, econtext);
|
||||
if (!ExecCheck(resultRelInfo->ri_PartitionCheckExpr, econtext))
|
||||
{
|
||||
char *val_desc;
|
||||
Relation orig_rel = rel;
|
||||
|
||||
/* See the comment above. */
|
||||
if (resultRelInfo->ri_PartitionRoot)
|
||||
{
|
||||
HeapTuple tuple = ExecFetchSlotTuple(slot);
|
||||
TupleDesc old_tupdesc = RelationGetDescr(rel);
|
||||
TupleConversionMap *map;
|
||||
|
||||
rel = resultRelInfo->ri_PartitionRoot;
|
||||
tupdesc = RelationGetDescr(rel);
|
||||
/* a reverse map */
|
||||
map = convert_tuples_by_name(old_tupdesc, tupdesc,
|
||||
gettext_noop("could not convert row type"));
|
||||
if (map != NULL)
|
||||
{
|
||||
tuple = do_convert_tuple(tuple, map);
|
||||
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
|
||||
}
|
||||
}
|
||||
|
||||
insertedCols = GetInsertedColumns(resultRelInfo, estate);
|
||||
updatedCols = GetUpdatedColumns(resultRelInfo, estate);
|
||||
modifiedCols = bms_union(insertedCols, updatedCols);
|
||||
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1997,47 +2024,11 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
|
||||
}
|
||||
}
|
||||
|
||||
if (resultRelInfo->ri_PartitionCheck &&
|
||||
!ExecPartitionCheck(resultRelInfo, slot, estate))
|
||||
{
|
||||
char *val_desc;
|
||||
Relation orig_rel = rel;
|
||||
|
||||
/* See the comment above. */
|
||||
if (resultRelInfo->ri_PartitionRoot)
|
||||
{
|
||||
HeapTuple tuple = ExecFetchSlotTuple(slot);
|
||||
TupleDesc old_tupdesc = RelationGetDescr(rel);
|
||||
TupleConversionMap *map;
|
||||
|
||||
rel = resultRelInfo->ri_PartitionRoot;
|
||||
tupdesc = RelationGetDescr(rel);
|
||||
/* a reverse map */
|
||||
map = convert_tuples_by_name(old_tupdesc, tupdesc,
|
||||
gettext_noop("could not convert row type"));
|
||||
if (map != NULL)
|
||||
{
|
||||
tuple = do_convert_tuple(tuple, map);
|
||||
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
|
||||
}
|
||||
}
|
||||
|
||||
insertedCols = GetInsertedColumns(resultRelInfo, estate);
|
||||
updatedCols = GetUpdatedColumns(resultRelInfo, estate);
|
||||
modifiedCols = bms_union(insertedCols, updatedCols);
|
||||
val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
|
||||
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));
|
||||
}
|
||||
if (resultRelInfo->ri_PartitionCheck)
|
||||
ExecPartitionCheck(resultRelInfo, slot, estate);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ExecWithCheckOptions -- check that tuple satisfies any WITH CHECK OPTIONs
|
||||
* of the specified kind.
|
||||
@ -3317,6 +3308,13 @@ ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
|
||||
PartitionDispatchData *failed_at;
|
||||
TupleTableSlot *failed_slot;
|
||||
|
||||
/*
|
||||
* First check the root table's partition constraint, if any. No point in
|
||||
* routing the tuple it if it doesn't belong in the root table itself.
|
||||
*/
|
||||
if (resultRelInfo->ri_PartitionCheck)
|
||||
ExecPartitionCheck(resultRelInfo, slot, estate);
|
||||
|
||||
result = get_partition_for_tuple(pd, slot, estate,
|
||||
&failed_at, &failed_slot);
|
||||
if (result < 0)
|
||||
|
Reference in New Issue
Block a user