Skip to content

Commit d45597f

Browse files
committed
Disallow direct change of NO INHERIT of not-null constraints
We support changing NO INHERIT constraint to INHERIT for constraints in child relations when adding a constraint to some ancestor relation, and also during pg_upgrade's schema restore; but other than those special cases, command ALTER TABLE ADD CONSTRAINT should not be allowed to change an existing constraint from NO INHERIT to INHERIT, as that would require to process child relations so that they also acquire an appropriate constraint, which we may not be in a position to do. (It'd also be surprising behavior.) It is conceivable that we want to allow ALTER TABLE SET NOT NULL to make such a change; but in that case some more code is needed to implement it correctly, so for now I've made that throw the same error message. Also, during the prep phase of ALTER TABLE ADD CONSTRAINT, acquire locks on all descendant tables; otherwise we might operate on child tables on which no locks are held, particularly in the mode where a primary key causes not-null constraints to be created on children. Reported-by: Alexander Lakhin <[email protected]> Discussion: https://postgr.es/m/[email protected]
1 parent 42510c0 commit d45597f

File tree

7 files changed

+92
-14
lines changed

7 files changed

+92
-14
lines changed

doc/src/sgml/ref/alter_table.sgml

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
105105
<phrase>and <replaceable class="parameter">column_constraint</replaceable> is:</phrase>
106106

107107
[ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
108-
{ NOT NULL |
108+
{ NOT NULL [ NO INHERIT ] |
109109
NULL |
110110
CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
111111
DEFAULT <replaceable>default_expr</replaceable> |

src/backend/catalog/heap.c

+15-5
Original file line numberDiff line numberDiff line change
@@ -2549,13 +2549,23 @@ AddRelationNewConstraints(Relation rel,
25492549

25502550
/*
25512551
* If the column already has an inheritable not-null constraint,
2552-
* we need only adjust its inheritance status and we're done. If
2553-
* the constraint is there but marked NO INHERIT, then it is
2554-
* updated in the same way, but we also recurse to the children
2555-
* (if any) to add the constraint there as well.
2552+
* we need only adjust its coninhcount and we're done. In certain
2553+
* cases (see below), if the constraint is there but marked NO
2554+
* INHERIT, then we mark it as no longer such and coninhcount
2555+
* updated, plus we must also recurse to the children (if any) to
2556+
* add the constraint there.
2557+
*
2558+
* We only allow the inheritability status to change during binary
2559+
* upgrade (where it's used to add the not-null constraints for
2560+
* children of tables with primary keys), or when we're recursing
2561+
* processing a table down an inheritance hierarchy; directly
2562+
* allowing a constraint to change from NO INHERIT to INHERIT
2563+
* during ALTER TABLE ADD CONSTRAINT would be far too surprising
2564+
* behavior.
25562565
*/
25572566
existing = AdjustNotNullInheritance1(RelationGetRelid(rel), colnum,
2558-
cdef->inhcount, cdef->is_no_inherit);
2567+
cdef->inhcount, cdef->is_no_inherit,
2568+
IsBinaryUpgrade || allow_merge);
25592569
if (existing == 1)
25602570
continue; /* all done! */
25612571
else if (existing == -1)

src/backend/catalog/pg_constraint.c

+11-4
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,7 @@ extractNotNullColumn(HeapTuple constrTup)
721721
*/
722722
int
723723
AdjustNotNullInheritance1(Oid relid, AttrNumber attnum, int count,
724-
bool is_no_inherit)
724+
bool is_no_inherit, bool allow_noinherit_change)
725725
{
726726
HeapTuple tup;
727727

@@ -744,16 +744,23 @@ AdjustNotNullInheritance1(Oid relid, AttrNumber attnum, int count,
744744
if (is_no_inherit && !conform->connoinherit)
745745
ereport(ERROR,
746746
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
747-
errmsg("cannot change NO INHERIT status of inherited NOT NULL constraint \"%s\" on relation \"%s\"",
747+
errmsg("cannot change NO INHERIT status of NOT NULL constraint \"%s\" on relation \"%s\"",
748748
NameStr(conform->conname), get_rel_name(relid)));
749749

750750
/*
751751
* If the constraint already exists in this relation but it's marked
752-
* NO INHERIT, we can just remove that flag, and instruct caller to
753-
* recurse to add the constraint to children.
752+
* NO INHERIT, we can just remove that flag (provided caller allows
753+
* such a change), and instruct caller to recurse to add the
754+
* constraint to children.
754755
*/
755756
if (!is_no_inherit && conform->connoinherit)
756757
{
758+
if (!allow_noinherit_change)
759+
ereport(ERROR,
760+
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
761+
errmsg("cannot change NO INHERIT status of NOT NULL constraint \"%s\" in relation \"%s\"",
762+
NameStr(conform->conname), get_rel_name(relid)));
763+
757764
conform->connoinherit = false;
758765
retval = -1; /* caller must add constraint on child rels */
759766
}

src/backend/commands/tablecmds.c

+15-2
Original file line numberDiff line numberDiff line change
@@ -4980,10 +4980,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
49804980
break;
49814981
case AT_AddConstraint: /* ADD CONSTRAINT */
49824982
ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
4983-
/* Recursion occurs during execution phase */
4984-
/* No command-specific prep needed except saving recurse flag */
49854983
if (recurse)
4984+
{
4985+
/* recurses at exec time; lock descendants and set flag */
4986+
(void) find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);
49864987
cmd->recurse = true;
4988+
}
49874989
pass = AT_PASS_ADD_CONSTR;
49884990
break;
49894991
case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
@@ -7892,6 +7894,17 @@ ATExecSetNotNull(List **wqueue, Relation rel, char *conName, char *colName,
78927894
copytup = heap_copytuple(tuple);
78937895
conForm = (Form_pg_constraint) GETSTRUCT(copytup);
78947896

7897+
/*
7898+
* Don't let a NO INHERIT constraint be changed into inherit.
7899+
*/
7900+
if (conForm->connoinherit && recurse)
7901+
ereport(ERROR,
7902+
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
7903+
errmsg("cannot change NO INHERIT status of NOT NULL constraint \"%s\" in relation \"%s\"",
7904+
NameStr(conForm->conname),
7905+
RelationGetRelationName(rel)));
7906+
7907+
78957908
/*
78967909
* If we find an appropriate constraint, we're almost done, but just
78977910
* need to change some properties on it: if we're recursing, increment

src/include/catalog/pg_constraint.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ extern HeapTuple findNotNullConstraint(Oid relid, const char *colname);
262262
extern HeapTuple findDomainNotNullConstraint(Oid typid);
263263
extern AttrNumber extractNotNullColumn(HeapTuple constrTup);
264264
extern int AdjustNotNullInheritance1(Oid relid, AttrNumber attnum, int count,
265-
bool is_no_inherit);
265+
bool is_no_inherit, bool allow_noinherit_change);
266266
extern void AdjustNotNullInheritance(Oid relid, Bitmapset *columns, int count);
267267
extern List *RelationGetNotNullConstraints(Oid relid, bool cooked);
268268

src/test/regress/expected/inherit.out

+29-1
Original file line numberDiff line numberDiff line change
@@ -2126,7 +2126,7 @@ ERROR: cannot define not-null constraint on column "a2" with NO INHERIT
21262126
DETAIL: The column has an inherited not-null constraint.
21272127
-- change NO INHERIT status of inherited constraint: no dice, it's inherited
21282128
alter table cc2 add not null a2 no inherit;
2129-
ERROR: cannot change NO INHERIT status of inherited NOT NULL constraint "nn" on relation "cc2"
2129+
ERROR: cannot change NO INHERIT status of NOT NULL constraint "nn" on relation "cc2"
21302130
-- remove constraint from cc2: no dice, it's inherited
21312131
alter table cc2 alter column a2 drop not null;
21322132
ERROR: cannot drop inherited constraint "nn" of relation "cc2"
@@ -2277,6 +2277,34 @@ Child tables: inh_nn_child,
22772277
inh_nn_child2
22782278

22792279
drop table inh_nn_parent, inh_nn_child, inh_nn_child2;
2280+
CREATE TABLE inh_nn_parent (a int, NOT NULL a NO INHERIT);
2281+
CREATE TABLE inh_nn_child() INHERITS (inh_nn_parent);
2282+
ALTER TABLE inh_nn_parent ADD CONSTRAINT nna NOT NULL a;
2283+
ERROR: cannot change NO INHERIT status of NOT NULL constraint "inh_nn_parent_a_not_null" in relation "inh_nn_parent"
2284+
ALTER TABLE inh_nn_parent ALTER a SET NOT NULL;
2285+
ERROR: cannot change NO INHERIT status of NOT NULL constraint "inh_nn_parent_a_not_null" in relation "inh_nn_parent"
2286+
DROP TABLE inh_nn_parent cascade;
2287+
NOTICE: drop cascades to table inh_nn_child
2288+
-- Adding a PK at the top level of a hierarchy should cause all descendants
2289+
-- to be checked for nulls, even past a no-inherit constraint
2290+
CREATE TABLE inh_nn_lvl1 (a int);
2291+
CREATE TABLE inh_nn_lvl2 () INHERITS (inh_nn_lvl1);
2292+
CREATE TABLE inh_nn_lvl3 (CONSTRAINT foo NOT NULL a NO INHERIT) INHERITS (inh_nn_lvl2);
2293+
CREATE TABLE inh_nn_lvl4 () INHERITS (inh_nn_lvl3);
2294+
CREATE TABLE inh_nn_lvl5 () INHERITS (inh_nn_lvl4);
2295+
INSERT INTO inh_nn_lvl2 VALUES (NULL);
2296+
ALTER TABLE inh_nn_lvl1 ADD PRIMARY KEY (a);
2297+
ERROR: column "a" of relation "inh_nn_lvl2" contains null values
2298+
DELETE FROM inh_nn_lvl2;
2299+
INSERT INTO inh_nn_lvl5 VALUES (NULL);
2300+
ALTER TABLE inh_nn_lvl1 ADD PRIMARY KEY (a);
2301+
ERROR: column "a" of relation "inh_nn_lvl5" contains null values
2302+
DROP TABLE inh_nn_lvl1 CASCADE;
2303+
NOTICE: drop cascades to 4 other objects
2304+
DETAIL: drop cascades to table inh_nn_lvl2
2305+
drop cascades to table inh_nn_lvl3
2306+
drop cascades to table inh_nn_lvl4
2307+
drop cascades to table inh_nn_lvl5
22802308
--
22812309
-- test inherit/deinherit
22822310
--

src/test/regress/sql/inherit.sql

+20
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,26 @@ select conrelid::regclass, conname, contype, conkey,
844844
\d+ inh_nn*
845845
drop table inh_nn_parent, inh_nn_child, inh_nn_child2;
846846

847+
CREATE TABLE inh_nn_parent (a int, NOT NULL a NO INHERIT);
848+
CREATE TABLE inh_nn_child() INHERITS (inh_nn_parent);
849+
ALTER TABLE inh_nn_parent ADD CONSTRAINT nna NOT NULL a;
850+
ALTER TABLE inh_nn_parent ALTER a SET NOT NULL;
851+
DROP TABLE inh_nn_parent cascade;
852+
853+
-- Adding a PK at the top level of a hierarchy should cause all descendants
854+
-- to be checked for nulls, even past a no-inherit constraint
855+
CREATE TABLE inh_nn_lvl1 (a int);
856+
CREATE TABLE inh_nn_lvl2 () INHERITS (inh_nn_lvl1);
857+
CREATE TABLE inh_nn_lvl3 (CONSTRAINT foo NOT NULL a NO INHERIT) INHERITS (inh_nn_lvl2);
858+
CREATE TABLE inh_nn_lvl4 () INHERITS (inh_nn_lvl3);
859+
CREATE TABLE inh_nn_lvl5 () INHERITS (inh_nn_lvl4);
860+
INSERT INTO inh_nn_lvl2 VALUES (NULL);
861+
ALTER TABLE inh_nn_lvl1 ADD PRIMARY KEY (a);
862+
DELETE FROM inh_nn_lvl2;
863+
INSERT INTO inh_nn_lvl5 VALUES (NULL);
864+
ALTER TABLE inh_nn_lvl1 ADD PRIMARY KEY (a);
865+
DROP TABLE inh_nn_lvl1 CASCADE;
866+
847867
--
848868
-- test inherit/deinherit
849869
--

0 commit comments

Comments
 (0)