Skip to content

Commit 5e6e97f

Browse files
committed
Repair misbehavior with duplicate entries in FK SET column lists.
Since v15 we've had an option to apply a foreign key constraint's ON DELETE SET DEFAULT or SET NULL action to just some of the referencing columns. There was not a check for duplicate entries in the list of columns-to-set, though. That caused a potential memory stomp in CreateConstraintEntry(), which incautiously assumed that the list of columns-to-set couldn't be longer than the number of key columns. Even after fixing that, the case doesn't work because you get an error like "multiple assignments to same column" from the SQL command that is generated to do the update. We could either raise an error for duplicate columns or silently suppress the dups, and after a bit of thought I chose to do the latter. This is motivated by the fact that duplicates in the FK column list are legal, so it's not real clear why duplicates in the columns-to-set list shouldn't be. Of course there's no need to actually set the column more than once. I left in the fix in CreateConstraintEntry() too, just because it didn't seem like such low-level code ought to be making assumptions about what it's handed. Bug: #18879 Reported-by: Yu Liang <luy70@psu.edu> Author: Tom Lane <tgl@sss.pgh.pa.us> Discussion: https://postgr.es/m/18879-259fc59d072bd4d7@postgresql.org Backpatch-through: 15
1 parent 6526d07 commit 5e6e97f

File tree

4 files changed

+34
-10
lines changed

4 files changed

+34
-10
lines changed

src/backend/catalog/pg_constraint.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,9 @@ CreateConstraintEntry(const char *constraintName,
118118
if (foreignNKeys > 0)
119119
{
120120
Datum *fkdatums;
121+
int nkeys = Max(foreignNKeys, numFkDeleteSetCols);
121122

122-
fkdatums = (Datum *) palloc(foreignNKeys * sizeof(Datum));
123+
fkdatums = (Datum *) palloc(nkeys * sizeof(Datum));
123124
for (i = 0; i < foreignNKeys; i++)
124125
fkdatums[i] = Int16GetDatum(foreignKey[i]);
125126
confkeyArray = construct_array_builtin(fkdatums, foreignNKeys, INT2OID);

src/backend/commands/tablecmds.c

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -512,8 +512,8 @@ static ObjectAddress ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *
512512
Relation rel, Constraint *fkconstraint,
513513
bool recurse, bool recursing,
514514
LOCKMODE lockmode);
515-
static void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
516-
int numfksetcols, const int16 *fksetcolsattnums,
515+
static int validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
516+
int numfksetcols, int16 *fksetcolsattnums,
517517
List *fksetcols);
518518
static ObjectAddress addFkConstraint(addFkConstraintSides fkside,
519519
char *constraintname,
@@ -9716,9 +9716,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
97169716
numfkdelsetcols = transformColumnNameList(RelationGetRelid(rel),
97179717
fkconstraint->fk_del_set_cols,
97189718
fkdelsetcols, NULL);
9719-
validateFkOnDeleteSetColumns(numfks, fkattnum,
9720-
numfkdelsetcols, fkdelsetcols,
9721-
fkconstraint->fk_del_set_cols);
9719+
numfkdelsetcols = validateFkOnDeleteSetColumns(numfks, fkattnum,
9720+
numfkdelsetcols,
9721+
fkdelsetcols,
9722+
fkconstraint->fk_del_set_cols);
97229723

97239724
/*
97249725
* If the attribute list for the referenced table was omitted, lookup the
@@ -10039,17 +10040,23 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
1003910040
* validateFkOnDeleteSetColumns
1004010041
* Verifies that columns used in ON DELETE SET NULL/DEFAULT (...)
1004110042
* column lists are valid.
10043+
*
10044+
* If there are duplicates in the fksetcolsattnums[] array, this silently
10045+
* removes the dups. The new count of numfksetcols is returned.
1004210046
*/
10043-
void
10047+
static int
1004410048
validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
10045-
int numfksetcols, const int16 *fksetcolsattnums,
10049+
int numfksetcols, int16 *fksetcolsattnums,
1004610050
List *fksetcols)
1004710051
{
10052+
int numcolsout = 0;
10053+
1004810054
for (int i = 0; i < numfksetcols; i++)
1004910055
{
1005010056
int16 setcol_attnum = fksetcolsattnums[i];
1005110057
bool seen = false;
1005210058

10059+
/* Make sure it's in fkattnums[] */
1005310060
for (int j = 0; j < numfks; j++)
1005410061
{
1005510062
if (fkattnums[j] == setcol_attnum)
@@ -10067,7 +10074,21 @@ validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
1006710074
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
1006810075
errmsg("column \"%s\" referenced in ON DELETE SET action must be part of foreign key", col)));
1006910076
}
10077+
10078+
/* Now check for dups */
10079+
seen = false;
10080+
for (int j = 0; j < numcolsout; j++)
10081+
{
10082+
if (fksetcolsattnums[j] == setcol_attnum)
10083+
{
10084+
seen = true;
10085+
break;
10086+
}
10087+
}
10088+
if (!seen)
10089+
fksetcolsattnums[numcolsout++] = setcol_attnum;
1007010090
}
10091+
return numcolsout;
1007110092
}
1007210093

1007310094
/*

src/test/regress/expected/foreign_key.out

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,8 @@ CREATE TABLE FKTABLE (
772772
fk_id_del_set_null int,
773773
fk_id_del_set_default int DEFAULT 0,
774774
FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES PKTABLE ON DELETE SET NULL (fk_id_del_set_null),
775-
FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (fk_id_del_set_default)
775+
-- this tests handling of duplicate entries in SET DEFAULT column list
776+
FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (fk_id_del_set_default, fk_id_del_set_default)
776777
);
777778
SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass::oid ORDER BY oid;
778779
pg_get_constraintdef

src/test/regress/sql/foreign_key.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,8 @@ CREATE TABLE FKTABLE (
473473
fk_id_del_set_null int,
474474
fk_id_del_set_default int DEFAULT 0,
475475
FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES PKTABLE ON DELETE SET NULL (fk_id_del_set_null),
476-
FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (fk_id_del_set_default)
476+
-- this tests handling of duplicate entries in SET DEFAULT column list
477+
FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (fk_id_del_set_default, fk_id_del_set_default)
477478
);
478479

479480
SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass::oid ORDER BY oid;

0 commit comments

Comments
 (0)