mirror of
https://github.com/postgres/postgres.git
synced 2025-06-27 23:21:58 +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,
|
static ObjectAddress ATExecValidateConstraint(List **wqueue,
|
||||||
Relation rel, char *constrName,
|
Relation rel, char *constrName,
|
||||||
bool recurse, bool recursing, LOCKMODE lockmode);
|
bool recurse, bool recursing, LOCKMODE lockmode);
|
||||||
static void QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation rel,
|
static void QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation fkrel,
|
||||||
HeapTuple contuple, LOCKMODE lockmode);
|
Oid pkrelid, HeapTuple contuple, LOCKMODE lockmode);
|
||||||
static void QueueCheckConstraintValidation(List **wqueue, Relation conrel, Relation rel,
|
static void QueueCheckConstraintValidation(List **wqueue, Relation conrel, Relation rel,
|
||||||
char *constrName, HeapTuple contuple,
|
char *constrName, HeapTuple contuple,
|
||||||
bool recurse, bool recursing, LOCKMODE lockmode);
|
bool recurse, bool recursing, LOCKMODE lockmode);
|
||||||
@ -11858,6 +11858,7 @@ AttachPartitionForeignKey(List **wqueue,
|
|||||||
if (queueValidation)
|
if (queueValidation)
|
||||||
{
|
{
|
||||||
Relation conrel;
|
Relation conrel;
|
||||||
|
Oid confrelid;
|
||||||
|
|
||||||
conrel = table_open(ConstraintRelationId, RowExclusiveLock);
|
conrel = table_open(ConstraintRelationId, RowExclusiveLock);
|
||||||
|
|
||||||
@ -11865,9 +11866,11 @@ AttachPartitionForeignKey(List **wqueue,
|
|||||||
if (!HeapTupleIsValid(partcontup))
|
if (!HeapTupleIsValid(partcontup))
|
||||||
elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
|
elog(ERROR, "cache lookup failed for constraint %u", partConstrOid);
|
||||||
|
|
||||||
|
confrelid = ((Form_pg_constraint) GETSTRUCT(partcontup))->confrelid;
|
||||||
|
|
||||||
/* Use the same lock as for AT_ValidateConstraint */
|
/* Use the same lock as for AT_ValidateConstraint */
|
||||||
QueueFKConstraintValidation(wqueue, conrel, partition, partcontup,
|
QueueFKConstraintValidation(wqueue, conrel, partition, confrelid,
|
||||||
ShareUpdateExclusiveLock);
|
partcontup, ShareUpdateExclusiveLock);
|
||||||
ReleaseSysCache(partcontup);
|
ReleaseSysCache(partcontup);
|
||||||
table_close(conrel, RowExclusiveLock);
|
table_close(conrel, RowExclusiveLock);
|
||||||
}
|
}
|
||||||
@ -12919,7 +12922,8 @@ ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName,
|
|||||||
{
|
{
|
||||||
if (con->contype == CONSTRAINT_FOREIGN)
|
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)
|
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.
|
* for the specified relation and all its children.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation rel,
|
QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation fkrel,
|
||||||
HeapTuple contuple, LOCKMODE lockmode)
|
Oid pkrelid, HeapTuple contuple, LOCKMODE lockmode)
|
||||||
{
|
{
|
||||||
Form_pg_constraint con;
|
Form_pg_constraint con;
|
||||||
AlteredTableInfo *tab;
|
AlteredTableInfo *tab;
|
||||||
@ -12964,7 +12968,17 @@ QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation rel,
|
|||||||
Assert(con->contype == CONSTRAINT_FOREIGN);
|
Assert(con->contype == CONSTRAINT_FOREIGN);
|
||||||
Assert(!con->convalidated);
|
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;
|
NewConstraint *newcon;
|
||||||
Constraint *fkconstraint;
|
Constraint *fkconstraint;
|
||||||
@ -12983,15 +12997,16 @@ QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation rel,
|
|||||||
newcon->qual = (Node *) fkconstraint;
|
newcon->qual = (Node *) fkconstraint;
|
||||||
|
|
||||||
/* Find or create work queue entry for this table */
|
/* Find or create work queue entry for this table */
|
||||||
tab = ATGetQueueEntry(wqueue, rel);
|
tab = ATGetQueueEntry(wqueue, fkrel);
|
||||||
tab->constraints = lappend(tab->constraints, newcon);
|
tab->constraints = lappend(tab->constraints, newcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the table at either end of the constraint is partitioned, we need to
|
* 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)
|
get_rel_relkind(con->confrelid) == RELKIND_PARTITIONED_TABLE)
|
||||||
{
|
{
|
||||||
ScanKeyData pkey;
|
ScanKeyData pkey;
|
||||||
@ -13023,8 +13038,12 @@ QueueFKConstraintValidation(List **wqueue, Relation conrel, Relation rel,
|
|||||||
|
|
||||||
childrel = table_open(childcon->conrelid, lockmode);
|
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);
|
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);
|
copyTuple = heap_copytuple(contuple);
|
||||||
copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
|
copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
|
||||||
|
@ -1895,29 +1895,67 @@ WHERE conrelid::regclass::text like 'fk_partitioned_fk%' ORDER BY oid::regclass:
|
|||||||
(5 rows)
|
(5 rows)
|
||||||
|
|
||||||
DROP TABLE fk_partitioned_fk, fk_notpartitioned_pk;
|
DROP TABLE fk_partitioned_fk, fk_notpartitioned_pk;
|
||||||
-- NOT VALID foreign key on a non-partitioned table referencing a partitioned table
|
-- NOT VALID foreign key on a non-partitioned table referencing a partitioned
|
||||||
|
-- table
|
||||||
CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b);
|
CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b);
|
||||||
CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000);
|
CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000);
|
||||||
|
CREATE TABLE fk_partitioned_pk_2 PARTITION OF fk_partitioned_pk FOR VALUES FROM (1000,1000) TO (2000,2000);
|
||||||
CREATE TABLE fk_notpartitioned_fk (b int, a int);
|
CREATE TABLE fk_notpartitioned_fk (b int, a int);
|
||||||
ALTER TABLE fk_notpartitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT VALID;
|
INSERT INTO fk_partitioned_pk VALUES(100,100), (1000,1000);
|
||||||
-- Constraint will be invalid.
|
INSERT INTO fk_notpartitioned_fk VALUES(100,100), (1000,1000);
|
||||||
SELECT conname, convalidated FROM pg_constraint
|
ALTER TABLE fk_notpartitioned_fk ADD CONSTRAINT fk_notpartitioned_fk_a_b_fkey
|
||||||
|
FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT VALID;
|
||||||
|
-- All constraints will be invalid.
|
||||||
|
SELECT conname, conenforced, convalidated FROM pg_constraint
|
||||||
WHERE conrelid = 'fk_notpartitioned_fk'::regclass ORDER BY oid::regclass::text;
|
WHERE conrelid = 'fk_notpartitioned_fk'::regclass ORDER BY oid::regclass::text;
|
||||||
conname | convalidated
|
conname | conenforced | convalidated
|
||||||
---------------------------------+--------------
|
---------------------------------+-------------+--------------
|
||||||
fk_notpartitioned_fk_a_b_fkey | f
|
fk_notpartitioned_fk_a_b_fkey | t | f
|
||||||
fk_notpartitioned_fk_a_b_fkey_1 | f
|
fk_notpartitioned_fk_a_b_fkey_1 | t | f
|
||||||
(2 rows)
|
fk_notpartitioned_fk_a_b_fkey_2 | t | f
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
ALTER TABLE fk_notpartitioned_fk VALIDATE CONSTRAINT fk_notpartitioned_fk_a_b_fkey;
|
ALTER TABLE fk_notpartitioned_fk VALIDATE CONSTRAINT fk_notpartitioned_fk_a_b_fkey;
|
||||||
-- All constraints are now valid.
|
-- All constraints are now valid.
|
||||||
SELECT conname, convalidated FROM pg_constraint
|
SELECT conname, conenforced, convalidated FROM pg_constraint
|
||||||
WHERE conrelid = 'fk_notpartitioned_fk'::regclass ORDER BY oid::regclass::text;
|
WHERE conrelid = 'fk_notpartitioned_fk'::regclass ORDER BY oid::regclass::text;
|
||||||
conname | convalidated
|
conname | conenforced | convalidated
|
||||||
---------------------------------+--------------
|
---------------------------------+-------------+--------------
|
||||||
fk_notpartitioned_fk_a_b_fkey | t
|
fk_notpartitioned_fk_a_b_fkey | t | t
|
||||||
fk_notpartitioned_fk_a_b_fkey_1 | t
|
fk_notpartitioned_fk_a_b_fkey_1 | t | t
|
||||||
(2 rows)
|
fk_notpartitioned_fk_a_b_fkey_2 | t | t
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
-- test a self-referential FK
|
||||||
|
ALTER TABLE fk_partitioned_pk ADD CONSTRAINT selffk FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT VALID;
|
||||||
|
CREATE TABLE fk_partitioned_pk_3 PARTITION OF fk_partitioned_pk FOR VALUES FROM (2000,2000) TO (3000,3000)
|
||||||
|
PARTITION BY RANGE (a);
|
||||||
|
CREATE TABLE fk_partitioned_pk_3_1 PARTITION OF fk_partitioned_pk_3 FOR VALUES FROM (2000) TO (2100);
|
||||||
|
SELECT conname, conenforced, convalidated FROM pg_constraint
|
||||||
|
WHERE conrelid = 'fk_partitioned_pk'::regclass AND contype = 'f'
|
||||||
|
ORDER BY oid::regclass::text;
|
||||||
|
conname | conenforced | convalidated
|
||||||
|
------------+-------------+--------------
|
||||||
|
selffk | t | f
|
||||||
|
selffk_1 | t | f
|
||||||
|
selffk_2 | t | f
|
||||||
|
selffk_3 | t | f
|
||||||
|
selffk_3_1 | t | f
|
||||||
|
(5 rows)
|
||||||
|
|
||||||
|
ALTER TABLE fk_partitioned_pk_2 VALIDATE CONSTRAINT selffk;
|
||||||
|
ALTER TABLE fk_partitioned_pk VALIDATE CONSTRAINT selffk;
|
||||||
|
SELECT conname, conenforced, convalidated FROM pg_constraint
|
||||||
|
WHERE conrelid = 'fk_partitioned_pk'::regclass AND contype = 'f'
|
||||||
|
ORDER BY oid::regclass::text;
|
||||||
|
conname | conenforced | convalidated
|
||||||
|
------------+-------------+--------------
|
||||||
|
selffk | t | t
|
||||||
|
selffk_1 | t | t
|
||||||
|
selffk_2 | t | t
|
||||||
|
selffk_3 | t | t
|
||||||
|
selffk_3_1 | t | t
|
||||||
|
(5 rows)
|
||||||
|
|
||||||
DROP TABLE fk_notpartitioned_fk, fk_partitioned_pk;
|
DROP TABLE fk_notpartitioned_fk, fk_partitioned_pk;
|
||||||
-- Test some other exotic foreign key features: MATCH SIMPLE, ON UPDATE/DELETE
|
-- Test some other exotic foreign key features: MATCH SIMPLE, ON UPDATE/DELETE
|
||||||
|
@ -1389,22 +1389,41 @@ WHERE conrelid::regclass::text like 'fk_partitioned_fk%' ORDER BY oid::regclass:
|
|||||||
|
|
||||||
DROP TABLE fk_partitioned_fk, fk_notpartitioned_pk;
|
DROP TABLE fk_partitioned_fk, fk_notpartitioned_pk;
|
||||||
|
|
||||||
-- NOT VALID foreign key on a non-partitioned table referencing a partitioned table
|
-- NOT VALID foreign key on a non-partitioned table referencing a partitioned
|
||||||
|
-- table
|
||||||
CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b);
|
CREATE TABLE fk_partitioned_pk (a int, b int, PRIMARY KEY (a, b)) PARTITION BY RANGE (a, b);
|
||||||
CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000);
|
CREATE TABLE fk_partitioned_pk_1 PARTITION OF fk_partitioned_pk FOR VALUES FROM (0,0) TO (1000,1000);
|
||||||
|
CREATE TABLE fk_partitioned_pk_2 PARTITION OF fk_partitioned_pk FOR VALUES FROM (1000,1000) TO (2000,2000);
|
||||||
CREATE TABLE fk_notpartitioned_fk (b int, a int);
|
CREATE TABLE fk_notpartitioned_fk (b int, a int);
|
||||||
ALTER TABLE fk_notpartitioned_fk ADD FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT VALID;
|
INSERT INTO fk_partitioned_pk VALUES(100,100), (1000,1000);
|
||||||
|
INSERT INTO fk_notpartitioned_fk VALUES(100,100), (1000,1000);
|
||||||
|
ALTER TABLE fk_notpartitioned_fk ADD CONSTRAINT fk_notpartitioned_fk_a_b_fkey
|
||||||
|
FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT VALID;
|
||||||
|
|
||||||
-- Constraint will be invalid.
|
-- All constraints will be invalid.
|
||||||
SELECT conname, convalidated FROM pg_constraint
|
SELECT conname, conenforced, convalidated FROM pg_constraint
|
||||||
WHERE conrelid = 'fk_notpartitioned_fk'::regclass ORDER BY oid::regclass::text;
|
WHERE conrelid = 'fk_notpartitioned_fk'::regclass ORDER BY oid::regclass::text;
|
||||||
|
|
||||||
ALTER TABLE fk_notpartitioned_fk VALIDATE CONSTRAINT fk_notpartitioned_fk_a_b_fkey;
|
ALTER TABLE fk_notpartitioned_fk VALIDATE CONSTRAINT fk_notpartitioned_fk_a_b_fkey;
|
||||||
|
|
||||||
-- All constraints are now valid.
|
-- All constraints are now valid.
|
||||||
SELECT conname, convalidated FROM pg_constraint
|
SELECT conname, conenforced, convalidated FROM pg_constraint
|
||||||
WHERE conrelid = 'fk_notpartitioned_fk'::regclass ORDER BY oid::regclass::text;
|
WHERE conrelid = 'fk_notpartitioned_fk'::regclass ORDER BY oid::regclass::text;
|
||||||
|
|
||||||
|
-- test a self-referential FK
|
||||||
|
ALTER TABLE fk_partitioned_pk ADD CONSTRAINT selffk FOREIGN KEY (a, b) REFERENCES fk_partitioned_pk NOT VALID;
|
||||||
|
CREATE TABLE fk_partitioned_pk_3 PARTITION OF fk_partitioned_pk FOR VALUES FROM (2000,2000) TO (3000,3000)
|
||||||
|
PARTITION BY RANGE (a);
|
||||||
|
CREATE TABLE fk_partitioned_pk_3_1 PARTITION OF fk_partitioned_pk_3 FOR VALUES FROM (2000) TO (2100);
|
||||||
|
SELECT conname, conenforced, convalidated FROM pg_constraint
|
||||||
|
WHERE conrelid = 'fk_partitioned_pk'::regclass AND contype = 'f'
|
||||||
|
ORDER BY oid::regclass::text;
|
||||||
|
ALTER TABLE fk_partitioned_pk_2 VALIDATE CONSTRAINT selffk;
|
||||||
|
ALTER TABLE fk_partitioned_pk VALIDATE CONSTRAINT selffk;
|
||||||
|
SELECT conname, conenforced, convalidated FROM pg_constraint
|
||||||
|
WHERE conrelid = 'fk_partitioned_pk'::regclass AND contype = 'f'
|
||||||
|
ORDER BY oid::regclass::text;
|
||||||
|
|
||||||
DROP TABLE fk_notpartitioned_fk, fk_partitioned_pk;
|
DROP TABLE fk_notpartitioned_fk, fk_partitioned_pk;
|
||||||
|
|
||||||
-- Test some other exotic foreign key features: MATCH SIMPLE, ON UPDATE/DELETE
|
-- Test some other exotic foreign key features: MATCH SIMPLE, ON UPDATE/DELETE
|
||||||
|
Reference in New Issue
Block a user