mirror of
https://github.com/postgres/postgres.git
synced 2025-07-03 20:02:46 +03:00
Avoid bogus scans of partitions when validating FKs to partitioned tables
Validating an unvalidated foreign key that references a partitioned
table would try to queue validations for each individual partition of
the referenced table, but this is wrong: each individual partition would
not necessarily have all the referenced rows, so errors would be raised.
Avoid doing that. The pg_constraint rows that cause this to happen are
only there to support the action triggers that implement the DELETE/
UPDATE actions of the FK, so no validating scan is necessary.
This was an oversight in commit b663b9436e
.
An equivalent oversight exists for NOT ENFORCED constraints, which is
not fixed in this commit.
Author: Amul Sul <sulamul@gmail.com>
Reported-by: Antonin Houska <ah@cybertec.at>
Reviewed-by: jian he <jian.universality@gmail.com>
Reviewed-by: Tender Wang <tndrwang@gmail.com>
Discussion: https://postgr.es/m/26983.1748418675@localhost
This commit is contained in:
@ -430,8 +430,8 @@ static void AlterConstrUpdateConstraintEntry(ATAlterConstraint *cmdcon, Relation
|
||||
static ObjectAddress ATExecValidateConstraint(List **wqueue,
|
||||
Relation rel, char *constrName,
|
||||
bool recurse, bool recursing, LOCKMODE lockmode);
|
||||
static void QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation rel,
|
||||
HeapTuple contuple, LOCKMODE lockmode);
|
||||
static void QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation fkrel,
|
||||
Oid pkrelid, HeapTuple contuple, LOCKMODE lockmode);
|
||||
static void QueueCheckConstraintValidation(List **wqueue, Relation conrel, Relation rel,
|
||||
char *constrName, HeapTuple contuple,
|
||||
bool recurse, bool recursing, LOCKMODE lockmode);
|
||||
@ -11858,6 +11858,7 @@ AttachPartitionForeignKey(List **wqueue,
|
||||
if (queueValidation)
|
||||
{
|
||||
Relation conrel;
|
||||
Oid confrelid;
|
||||
|
||||
conrel = table_open(ConstraintRelationId, RowExclusiveLock);
|
||||
|
||||
@ -11865,9 +11866,11 @@ AttachPartitionForeignKey(List **wqueue,
|
||||
if (!HeapTupleIsValid(partcontup))
|
||||
elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
|
||||
|
||||
confrelid = ((Form_pg_constraint) GETSTRUCT(partcontup))->confrelid;
|
||||
|
||||
/* Use the same lock as for AT_ValidateConstraint */
|
||||
QueueFKConstraintValidation(wqueue, conrel, partition, partcontup,
|
||||
ShareUpdateExclusiveLock);
|
||||
QueueFKConstraintValidation(wqueue, conrel, partition, confrelid,
|
||||
partcontup, ShareUpdateExclusiveLock);
|
||||
ReleaseSysCache(partcontup);
|
||||
table_close(conrel, RowExclusiveLock);
|
||||
}
|
||||
@ -12919,7 +12922,8 @@ ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName,
|
||||
{
|
||||
if (con->contype == CONSTRAINT_FOREIGN)
|
||||
{
|
||||
QueueFKConstraintValidation(wqueue, conrel, rel, tuple, lockmode);
|
||||
QueueFKConstraintValidation(wqueue, conrel, rel, con->confrelid,
|
||||
tuple, lockmode);
|
||||
}
|
||||
else if (con->contype == CONSTRAINT_CHECK)
|
||||
{
|
||||
@ -12952,8 +12956,8 @@ ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName,
|
||||
* for the specified relation and all its children.
|
||||
*/
|
||||
static void
|
||||
QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation rel,
|
||||
HeapTuple contuple, LOCKMODE lockmode)
|
||||
QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation fkrel,
|
||||
Oid pkrelid, HeapTuple contuple, LOCKMODE lockmode)
|
||||
{
|
||||
Form_pg_constraint con;
|
||||
AlteredTableInfo *tab;
|
||||
@ -12964,7 +12968,17 @@ QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation rel,
|
||||
Assert(con->contype == CONSTRAINT_FOREIGN);
|
||||
Assert(!con->convalidated);
|
||||
|
||||
if (rel->rd_rel->relkind == RELKIND_RELATION)
|
||||
/*
|
||||
* Add the validation to phase 3's queue; not needed for partitioned
|
||||
* tables themselves, only for their partitions.
|
||||
*
|
||||
* When the referenced table (pkrelid) is partitioned, the referencing
|
||||
* table (fkrel) has one pg_constraint row pointing to each partition
|
||||
* thereof. These rows are there only to support action triggers and no
|
||||
* table scan is needed, therefore skip this for them as well.
|
||||
*/
|
||||
if (fkrel->rd_rel->relkind == RELKIND_RELATION &&
|
||||
con->confrelid == pkrelid)
|
||||
{
|
||||
NewConstraint *newcon;
|
||||
Constraint *fkconstraint;
|
||||
@ -12983,15 +12997,16 @@ QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation rel,
|
||||
newcon->qual = (Node *) fkconstraint;
|
||||
|
||||
/* Find or create work queue entry for this table */
|
||||
tab = ATGetQueueEntry(wqueue, rel);
|
||||
tab = ATGetQueueEntry(wqueue, fkrel);
|
||||
tab->constraints = lappend(tab->constraints, newcon);
|
||||
}
|
||||
|
||||
/*
|
||||
* If the table at either end of the constraint is partitioned, we need to
|
||||
* recurse and handle every constraint that is a child of this constraint.
|
||||
* recurse and handle every unvalidate constraint that is a child of this
|
||||
* constraint.
|
||||
*/
|
||||
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
|
||||
if (fkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
|
||||
get_rel_relkind(con->confrelid) == RELKIND_PARTITIONED_TABLE)
|
||||
{
|
||||
ScanKeyData pkey;
|
||||
@ -13023,8 +13038,12 @@ QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation rel,
|
||||
|
||||
childrel = table_open(childcon->conrelid, lockmode);
|
||||
|
||||
QueueFKConstraintValidation(wqueue, conrel, childrel, childtup,
|
||||
lockmode);
|
||||
/*
|
||||
* NB: Note that pkrelid should be passed as-is during recursion,
|
||||
* as it is required to identify the root referenced table.
|
||||
*/
|
||||
QueueFKConstraintValidation(wqueue, conrel, childrel, pkrelid,
|
||||
childtup, lockmode);
|
||||
table_close(childrel, NoLock);
|
||||
}
|
||||
|
||||
@ -13032,7 +13051,11 @@ QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation rel,
|
||||
}
|
||||
|
||||
/*
|
||||
* Now update the catalog, while we have the door open.
|
||||
* Now mark the pg_constraint row as validated (even if we didn't check,
|
||||
* notably the ones for partitions on the referenced side).
|
||||
*
|
||||
* We rely on transaction abort to roll back this change if phase 3
|
||||
* ultimately finds violating rows. This is a bit ugly.
|
||||
*/
|
||||
copyTuple = heap_copytuple(contuple);
|
||||
copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
|
||||
|
Reference in New Issue
Block a user