Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 43b8e6c

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 0f43083 commit 43b8e6c

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
@@ -129,8 +129,9 @@ CreateConstraintEntry(const char *constraintName,
129129
if (foreignNKeys > 0)
130130
{
131131
Datum *fkdatums;
132+
int nkeys = Max(foreignNKeys, numFkDeleteSetCols);
132133

133-
fkdatums = (Datum *) palloc(foreignNKeys * sizeof(Datum));
134+
fkdatums = (Datum *) palloc(nkeys * sizeof(Datum));
134135
for (i = 0; i < foreignNKeys; i++)
135136
fkdatums[i] = Int16GetDatum(foreignKey[i]);
136137
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
@@ -557,8 +557,8 @@ static ObjectAddress ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *
557557
Relation rel, Constraint *fkconstraint,
558558
bool recurse, bool recursing,
559559
LOCKMODE lockmode);
560-
static void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
561-
int numfksetcols, const int16 *fksetcolsattnums,
560+
static int validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
561+
int numfksetcols, int16 *fksetcolsattnums,
562562
List *fksetcols);
563563
static ObjectAddress addFkConstraint(addFkConstraintSides fkside,
564564
char *constraintname,
@@ -10037,9 +10037,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
1003710037
numfkdelsetcols = transformColumnNameList(RelationGetRelid(rel),
1003810038
fkconstraint->fk_del_set_cols,
1003910039
fkdelsetcols, NULL, NULL);
10040-
validateFkOnDeleteSetColumns(numfks, fkattnum,
10041-
numfkdelsetcols, fkdelsetcols,
10042-
fkconstraint->fk_del_set_cols);
10040+
numfkdelsetcols = validateFkOnDeleteSetColumns(numfks, fkattnum,
10041+
numfkdelsetcols,
10042+
fkdelsetcols,
10043+
fkconstraint->fk_del_set_cols);
1004310044

1004410045
/*
1004510046
* If the attribute list for the referenced table was omitted, lookup the
@@ -10499,17 +10500,23 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
1049910500
* validateFkOnDeleteSetColumns
1050010501
* Verifies that columns used in ON DELETE SET NULL/DEFAULT (...)
1050110502
* column lists are valid.
10503+
*
10504+
* If there are duplicates in the fksetcolsattnums[] array, this silently
10505+
* removes the dups. The new count of numfksetcols is returned.
1050210506
*/
10503-
void
10507+
static int
1050410508
validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
10505-
int numfksetcols, const int16 *fksetcolsattnums,
10509+
int numfksetcols, int16 *fksetcolsattnums,
1050610510
List *fksetcols)
1050710511
{
10512+
int numcolsout = 0;
10513+
1050810514
for (int i = 0; i < numfksetcols; i++)
1050910515
{
1051010516
int16 setcol_attnum = fksetcolsattnums[i];
1051110517
bool seen = false;
1051210518

10519+
/* Make sure it's in fkattnums[] */
1051310520
for (int j = 0; j < numfks; j++)
1051410521
{
1051510522
if (fkattnums[j] == setcol_attnum)
@@ -10527,7 +10534,21 @@ validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
1052710534
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
1052810535
errmsg("column \"%s\" referenced in ON DELETE SET action must be part of foreign key", col)));
1052910536
}
10537+
10538+
/* Now check for dups */
10539+
seen = false;
10540+
for (int j = 0; j < numcolsout; j++)
10541+
{
10542+
if (fksetcolsattnums[j] == setcol_attnum)
10543+
{
10544+
seen = true;
10545+
break;
10546+
}
10547+
}
10548+
if (!seen)
10549+
fksetcolsattnums[numcolsout++] = setcol_attnum;
1053010550
}
10551+
return numcolsout;
1053110552
}
1053210553

1053310554
/*

src/test/regress/expected/foreign_key.out

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -837,7 +837,8 @@ CREATE TABLE FKTABLE (
837837
fk_id_del_set_null int,
838838
fk_id_del_set_default int DEFAULT 0,
839839
FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES PKTABLE ON DELETE SET NULL (fk_id_del_set_null),
840-
FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (fk_id_del_set_default)
840+
-- this tests handling of duplicate entries in SET DEFAULT column list
841+
FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (fk_id_del_set_default, fk_id_del_set_default)
841842
);
842843
SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass::oid ORDER BY oid;
843844
pg_get_constraintdef

src/test/regress/sql/foreign_key.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,8 @@ CREATE TABLE FKTABLE (
517517
fk_id_del_set_null int,
518518
fk_id_del_set_default int DEFAULT 0,
519519
FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES PKTABLE ON DELETE SET NULL (fk_id_del_set_null),
520-
FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (fk_id_del_set_default)
520+
-- this tests handling of duplicate entries in SET DEFAULT column list
521+
FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (fk_id_del_set_default, fk_id_del_set_default)
521522
);
522523

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

0 commit comments

Comments
 (0)