Fix not-null constraint test
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Fri, 1 Sep 2023 17:49:20 +0000 (19:49 +0200)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Fri, 1 Sep 2023 17:49:20 +0000 (19:49 +0200)
When a partitioned table has a primary key, trying to find the
corresponding not-null constraint for that column would come up empty,
causing code that's trying to check said not-null constraint to crash.
Fix by only running the check when the not-null constraint exists.

Reported-by: Alexander Lakhin <exclusion@gmail.com>
Discussion: https://postgr.es/m/d57b4a69-7394-3146-5976-9a1ef27e7972@gmail.com

src/backend/commands/tablecmds.c
src/test/regress/expected/constraints.out
src/test/regress/sql/constraints.sql

index 03397746724d1d6cccce73679556755ac44fe6c7..47c556669f33e73dd1a6e01505886b1cb0872f01 100644 (file)
@@ -15746,9 +15746,10 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
                                                                attributeName)));
 
                        /*
-                        * Check child doesn't discard NOT NULL property.  (Other
-                        * constraints are checked elsewhere.)  However, if the constraint
-                        * is NO INHERIT in the parent, this is allowed.
+                        * If the parent has a not-null constraint that's not NO INHERIT,
+                        * make sure the child has one too.
+                        *
+                        * Other constraints are checked elsewhere.
                         */
                        if (attribute->attnotnull && !childatt->attnotnull)
                        {
@@ -15756,11 +15757,12 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 
                                contup = findNotNullConstraintAttnum(RelationGetRelid(parent_rel),
                                                                                                         attribute->attnum);
-                               if (!((Form_pg_constraint) GETSTRUCT(contup))->connoinherit)
+                               if (HeapTupleIsValid(contup) &&
+                                       !((Form_pg_constraint) GETSTRUCT(contup))->connoinherit)
                                        ereport(ERROR,
-                                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                        errmsg("column \"%s\" in child table must be marked NOT NULL",
-                                                                       attributeName)));
+                                                       errcode(ERRCODE_DATATYPE_MISMATCH),
+                                                       errmsg("column \"%s\" in child table must be marked NOT NULL",
+                                                                  attributeName));
                        }
 
                        /*
@@ -15981,10 +15983,20 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
                systable_endscan(child_scan);
 
                if (!found)
+               {
+                       if (parent_con->contype == CONSTRAINT_NOTNULL)
+                               ereport(ERROR,
+                                               errcode(ERRCODE_DATATYPE_MISMATCH),
+                                               errmsg("column \"%s\" in child table must be marked NOT NULL",
+                                                          get_attname(parent_relid,
+                                                                                  extractNotNullColumn(parent_tuple),
+                                                                                  false)));
+
                        ereport(ERROR,
                                        (errcode(ERRCODE_DATATYPE_MISMATCH),
                                         errmsg("child table is missing constraint \"%s\"",
                                                        NameStr(parent_con->conname))));
+               }
        }
 
        systable_endscan(parent_scan);
index b7de50ad6a6077406eeee32118e55c7ba45ffc1a..5b068477bf8f1ca98c798f47837d581f0718a894 100644 (file)
@@ -1006,6 +1006,17 @@ Inherits: cnn_grandchild,
 ALTER TABLE cnn_parent DROP CONSTRAINT cnn_parent_pkey;
 ERROR:  constraint "cnn_parent_pkey" of relation "cnn_parent" does not exist
 -- keeps these tables around, for pg_upgrade testing
+-- ensure columns in partitions are marked not-null
+create table cnn2_parted(a int primary key) partition by list (a);
+create table cnn2_part1(a int);
+alter table cnn2_parted attach partition cnn2_part1 for values in (1);
+ERROR:  primary key column "a" is not marked NOT NULL
+drop table cnn2_parted, cnn2_part1;
+create table cnn2_parted(a int not null) partition by list (a);
+create table cnn2_part1(a int primary key);
+alter table cnn2_parted attach partition cnn2_part1 for values in (1);
+ERROR:  column "a" in child table must be marked NOT NULL
+drop table cnn2_parted, cnn2_part1;
 -- Comments
 -- Setup a low-level role to enforce non-superuser checks.
 CREATE ROLE regress_constraint_comments;
index 782699a43775bf7a9059155dbf66f90e869c8c06..a7d96e98f5889d9e8235ca07a1789bae5b78a0ff 100644 (file)
@@ -657,6 +657,17 @@ ALTER TABLE cnn_parent ADD PRIMARY KEY USING INDEX b_uq;
 ALTER TABLE cnn_parent DROP CONSTRAINT cnn_parent_pkey;
 -- keeps these tables around, for pg_upgrade testing
 
+-- ensure columns in partitions are marked not-null
+create table cnn2_parted(a int primary key) partition by list (a);
+create table cnn2_part1(a int);
+alter table cnn2_parted attach partition cnn2_part1 for values in (1);
+drop table cnn2_parted, cnn2_part1;
+
+create table cnn2_parted(a int not null) partition by list (a);
+create table cnn2_part1(a int primary key);
+alter table cnn2_parted attach partition cnn2_part1 for values in (1);
+drop table cnn2_parted, cnn2_part1;
+
 
 -- Comments
 -- Setup a low-level role to enforce non-superuser checks.