Skip to content

Commit f5069f0

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 3c0fe75 commit f5069f0

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
@@ -121,8 +121,9 @@ CreateConstraintEntry(const char *constraintName,
121121
if (foreignNKeys > 0)
122122
{
123123
Datum *fkdatums;
124+
int nkeys = Max(foreignNKeys, numFkDeleteSetCols);
124125

125-
fkdatums = (Datum *) palloc(foreignNKeys * sizeof(Datum));
126+
fkdatums = (Datum *) palloc(nkeys * sizeof(Datum));
126127
for (i = 0; i < foreignNKeys; i++)
127128
fkdatums[i] = Int16GetDatum(foreignKey[i]);
128129
confkeyArray = construct_array(fkdatums, foreignNKeys,

src/backend/commands/tablecmds.c

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -497,8 +497,8 @@ static ObjectAddress ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *
497497
Relation rel, Constraint *fkconstraint,
498498
bool recurse, bool recursing,
499499
LOCKMODE lockmode);
500-
static void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
501-
int numfksetcols, const int16 *fksetcolsattnums,
500+
static int validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
501+
int numfksetcols, int16 *fksetcolsattnums,
502502
List *fksetcols);
503503
static ObjectAddress addFkConstraint(addFkConstraintSides fkside,
504504
char *constraintname,
@@ -9309,9 +9309,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
93099309
numfkdelsetcols = transformColumnNameList(RelationGetRelid(rel),
93109310
fkconstraint->fk_del_set_cols,
93119311
fkdelsetcols, NULL);
9312-
validateFkOnDeleteSetColumns(numfks, fkattnum,
9313-
numfkdelsetcols, fkdelsetcols,
9314-
fkconstraint->fk_del_set_cols);
9312+
numfkdelsetcols = validateFkOnDeleteSetColumns(numfks, fkattnum,
9313+
numfkdelsetcols,
9314+
fkdelsetcols,
9315+
fkconstraint->fk_del_set_cols);
93159316

93169317
/*
93179318
* If the attribute list for the referenced table was omitted, lookup the
@@ -9632,17 +9633,23 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
96329633
* validateFkOnDeleteSetColumns
96339634
* Verifies that columns used in ON DELETE SET NULL/DEFAULT (...)
96349635
* column lists are valid.
9636+
*
9637+
* If there are duplicates in the fksetcolsattnums[] array, this silently
9638+
* removes the dups. The new count of numfksetcols is returned.
96359639
*/
9636-
void
9640+
static int
96379641
validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
9638-
int numfksetcols, const int16 *fksetcolsattnums,
9642+
int numfksetcols, int16 *fksetcolsattnums,
96399643
List *fksetcols)
96409644
{
9645+
int numcolsout = 0;
9646+
96419647
for (int i = 0; i < numfksetcols; i++)
96429648
{
96439649
int16 setcol_attnum = fksetcolsattnums[i];
96449650
bool seen = false;
96459651

9652+
/* Make sure it's in fkattnums[] */
96469653
for (int j = 0; j < numfks; j++)
96479654
{
96489655
if (fkattnums[j] == setcol_attnum)
@@ -9660,7 +9667,21 @@ validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
96609667
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
96619668
errmsg("column \"%s\" referenced in ON DELETE SET action must be part of foreign key", col)));
96629669
}
9670+
9671+
/* Now check for dups */
9672+
seen = false;
9673+
for (int j = 0; j < numcolsout; j++)
9674+
{
9675+
if (fksetcolsattnums[j] == setcol_attnum)
9676+
{
9677+
seen = true;
9678+
break;
9679+
}
9680+
}
9681+
if (!seen)
9682+
fksetcolsattnums[numcolsout++] = setcol_attnum;
96639683
}
9684+
return numcolsout;
96649685
}
96659686

96669687
/*

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)