mirror of
https://github.com/postgres/postgres.git
synced 2025-07-07 00:36:50 +03:00
Fix TRUNCATE .. CASCADE on partitions
When running TRUNCATE CASCADE on a child of a partitioned table referenced by another partitioned table, the truncate was not applied to partitions of the referencing table; this could leave rows violating the constraint in the referencing partitioned table. Repair by walking the pg_constraint chain all the way up to the topmost referencing table. Note: any partitioned tables containing FKs that reference other partitioned tables should be checked for possible violating rows, if TRUNCATE has occurred in partitions of the referenced table. Reported-by: Christophe Courtois Author: Jehan-Guillaume de Rorthais Discussion: https://postgr.es/m/20200204183906.115f693e@firost
This commit is contained in:
@ -3399,9 +3399,16 @@ List *
|
||||
heap_truncate_find_FKs(List *relationIds)
|
||||
{
|
||||
List *result = NIL;
|
||||
List *oids = list_copy(relationIds);
|
||||
List *parent_cons;
|
||||
ListCell *cell;
|
||||
ScanKeyData key;
|
||||
Relation fkeyRel;
|
||||
SysScanDesc fkeyScan;
|
||||
HeapTuple tuple;
|
||||
bool restart;
|
||||
|
||||
oids = list_copy(relationIds);
|
||||
|
||||
/*
|
||||
* Must scan pg_constraint. Right now, it is a seqscan because there is
|
||||
@ -3409,6 +3416,10 @@ heap_truncate_find_FKs(List *relationIds)
|
||||
*/
|
||||
fkeyRel = table_open(ConstraintRelationId, AccessShareLock);
|
||||
|
||||
restart:
|
||||
restart = false;
|
||||
parent_cons = NIL;
|
||||
|
||||
fkeyScan = systable_beginscan(fkeyRel, InvalidOid, false,
|
||||
NULL, 0, NULL);
|
||||
|
||||
@ -3421,16 +3432,85 @@ heap_truncate_find_FKs(List *relationIds)
|
||||
continue;
|
||||
|
||||
/* Not referencing one of our list of tables */
|
||||
if (!list_member_oid(relationIds, con->confrelid))
|
||||
if (!list_member_oid(oids, con->confrelid))
|
||||
continue;
|
||||
|
||||
/* Add referencer unless already in input or result list */
|
||||
/*
|
||||
* If this constraint has a parent constraint which we have not seen
|
||||
* yet, keep track of it for the second loop, below. Tracking parent
|
||||
* constraints allows us to climb up to the top-level level constraint
|
||||
* and look for all possible relations referencing the partitioned
|
||||
* table.
|
||||
*/
|
||||
if (OidIsValid(con->conparentid) &&
|
||||
!list_member_oid(parent_cons, con->conparentid))
|
||||
parent_cons = lappend_oid(parent_cons, con->conparentid);
|
||||
|
||||
/*
|
||||
* Add referencer to result, unless already present in input or result
|
||||
* list.
|
||||
*/
|
||||
if (!list_member_oid(relationIds, con->conrelid))
|
||||
result = insert_ordered_unique_oid(result, con->conrelid);
|
||||
}
|
||||
|
||||
systable_endscan(fkeyScan);
|
||||
|
||||
/*
|
||||
* Process each parent constraint we found to add the list of referenced
|
||||
* relations by them to the oids list. If we do add any new such
|
||||
* relations, redo the first loop above. Also, if we see that the parent
|
||||
* constraint in turn has a parent, add that so that we process all
|
||||
* relations in a single additional pass.
|
||||
*/
|
||||
foreach(cell, parent_cons)
|
||||
{
|
||||
Oid parent = lfirst_oid(cell);
|
||||
|
||||
ScanKeyInit(&key,
|
||||
Anum_pg_constraint_oid,
|
||||
BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(parent));
|
||||
|
||||
fkeyScan = systable_beginscan(fkeyRel, ConstraintOidIndexId,
|
||||
true, NULL, 1, &key);
|
||||
|
||||
tuple = systable_getnext(fkeyScan);
|
||||
if (HeapTupleIsValid(tuple))
|
||||
{
|
||||
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
|
||||
|
||||
/*
|
||||
* pg_constraint rows always appear for partitioned hierarchies
|
||||
* this way: on the each side of the constraint, one row appears
|
||||
* for each partition that points to the top-most table on the
|
||||
* other side.
|
||||
*
|
||||
* Because of this arrangement, we can correctly catch all
|
||||
* relevant relations by adding to 'parent_cons' all rows with
|
||||
* valid conparentid, and to the 'oids' list all rows with a
|
||||
* zero conparentid. If any oids are added to 'oids', redo the
|
||||
* first loop above by setting 'restart'.
|
||||
*/
|
||||
if (OidIsValid(con->conparentid))
|
||||
parent_cons = list_append_unique_oid(parent_cons,
|
||||
con->conparentid);
|
||||
else if (!list_member_oid(oids, con->confrelid))
|
||||
{
|
||||
oids = lappend_oid(oids, con->confrelid);
|
||||
restart = true;
|
||||
}
|
||||
}
|
||||
|
||||
systable_endscan(fkeyScan);
|
||||
}
|
||||
|
||||
list_free(parent_cons);
|
||||
if (restart)
|
||||
goto restart;
|
||||
|
||||
table_close(fkeyRel, AccessShareLock);
|
||||
list_free(oids);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
Reference in New Issue
Block a user