Avoid name collision with NOT NULL constraints

If a CREATE TABLE statement defined a constraint whose name is identical
to the name generated for a NOT NULL constraint, we'd throw an
(unnecessary) unique key violation error on
pg_constraint_conrelid_contypid_conname_index: this can easily be
avoided by choosing a different name for the NOT NULL constraint.

Fix by passing the constraint names already created by
AddRelationNewConstraints() to AddRelationNotNullConstraints(), so that
the latter can avoid name collisions with them.

Bug: #19393
Author: Laurenz Albe <laurenz.albe@cybertec.at>
Reported-by: Hüseyin Demir <huseyin.d3r@gmail.com>
Backpatch-through: 18
Discussion: https://postgr.es/m/19393-6a82427485a744cf@postgresql.org
This commit is contained in:
Álvaro Herrera 2026-02-21 12:22:08 +01:00
parent c5edc6c8ff
commit 8d9a97e0bb
No known key found for this signature in database
GPG key ID: 1C20ACB9D5C564AE
5 changed files with 34 additions and 8 deletions

View file

@ -2886,14 +2886,16 @@ MergeWithExistingConstraint(Relation rel, const char *ccname, Node *expr,
* for each column, giving priority to user-specified ones, and setting * for each column, giving priority to user-specified ones, and setting
* inhcount according to how many parents cause each column to get a * inhcount according to how many parents cause each column to get a
* not-null constraint. If a user-specified name clashes with another * not-null constraint. If a user-specified name clashes with another
* user-specified name, an error is raised. * user-specified name, an error is raised. 'existing_constraints'
* is a list of already defined constraint names, which should be avoided
* when generating further ones.
* *
* Returns a list of AttrNumber for columns that need to have the attnotnull * Returns a list of AttrNumber for columns that need to have the attnotnull
* flag set. * flag set.
*/ */
List * List *
AddRelationNotNullConstraints(Relation rel, List *constraints, AddRelationNotNullConstraints(Relation rel, List *constraints,
List *old_notnulls) List *old_notnulls, List *existing_constraints)
{ {
List *givennames; List *givennames;
List *nnnames; List *nnnames;
@ -2905,7 +2907,7 @@ AddRelationNotNullConstraints(Relation rel, List *constraints,
* because we must raise error for user-generated name conflicts, but for * because we must raise error for user-generated name conflicts, but for
* system-generated name conflicts we just generate another. * system-generated name conflicts we just generate another.
*/ */
nnnames = NIL; nnnames = list_copy(existing_constraints); /* don't scribble on input */
givennames = NIL; givennames = NIL;
/* /*

View file

@ -776,6 +776,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
List *rawDefaults; List *rawDefaults;
List *cookedDefaults; List *cookedDefaults;
List *nncols; List *nncols;
List *connames = NIL;
Datum reloptions; Datum reloptions;
ListCell *listptr; ListCell *listptr;
AttrNumber attnum; AttrNumber attnum;
@ -1329,11 +1330,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/* /*
* Now add any newly specified CHECK constraints to the new relation. Same * Now add any newly specified CHECK constraints to the new relation. Same
* as for defaults above, but these need to come after partitioning is set * as for defaults above, but these need to come after partitioning is set
* up. * up. We save the constraint names that were used, to avoid dupes below.
*/ */
if (stmt->constraints) if (stmt->constraints)
AddRelationNewConstraints(rel, NIL, stmt->constraints, {
true, true, false, queryString); List *conlist;
conlist = AddRelationNewConstraints(rel, NIL, stmt->constraints,
true, true, false, queryString);
foreach_ptr(CookedConstraint, cons, conlist)
{
if (cons->name != NULL)
connames = lappend(connames, cons->name);
}
}
/* /*
* Finally, merge the not-null constraints that are declared directly with * Finally, merge the not-null constraints that are declared directly with
@ -1342,7 +1352,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* columns that don't yet have it. * columns that don't yet have it.
*/ */
nncols = AddRelationNotNullConstraints(rel, stmt->nnconstraints, nncols = AddRelationNotNullConstraints(rel, stmt->nnconstraints,
old_notnulls); old_notnulls, connames);
foreach_int(attrnum, nncols) foreach_int(attrnum, nncols)
set_attnotnull(NULL, rel, attrnum, true, false); set_attnotnull(NULL, rel, attrnum, true, false);

View file

@ -117,7 +117,8 @@ extern List *AddRelationNewConstraints(Relation rel,
const char *queryString); const char *queryString);
extern List *AddRelationNotNullConstraints(Relation rel, extern List *AddRelationNotNullConstraints(Relation rel,
List *constraints, List *constraints,
List *old_notnulls); List *old_notnulls,
List *existing_constraints);
extern void RelationClearMissing(Relation rel); extern void RelationClearMissing(Relation rel);

View file

@ -161,6 +161,12 @@ ALTER TABLE remember_node_subid ALTER c TYPE bigint;
SAVEPOINT q; DROP TABLE remember_node_subid; ROLLBACK TO q; SAVEPOINT q; DROP TABLE remember_node_subid; ROLLBACK TO q;
COMMIT; COMMIT;
DROP TABLE remember_node_subid; DROP TABLE remember_node_subid;
-- generated NOT NULL constraint names must not collide with explicitly named constraints
CREATE TABLE two_not_null_constraints (
col integer NOT NULL,
CONSTRAINT two_not_null_constraints_col_not_null CHECK (col IS NOT NULL)
);
DROP TABLE two_not_null_constraints;
-- --
-- Partitioned tables -- Partitioned tables
-- --

View file

@ -105,6 +105,13 @@ SAVEPOINT q; DROP TABLE remember_node_subid; ROLLBACK TO q;
COMMIT; COMMIT;
DROP TABLE remember_node_subid; DROP TABLE remember_node_subid;
-- generated NOT NULL constraint names must not collide with explicitly named constraints
CREATE TABLE two_not_null_constraints (
col integer NOT NULL,
CONSTRAINT two_not_null_constraints_col_not_null CHECK (col IS NOT NULL)
);
DROP TABLE two_not_null_constraints;
-- --
-- Partitioned tables -- Partitioned tables
-- --