]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Add ALTER TABLE ... ALTER CONSTRAINT ... SET [NO] INHERIT
authorÁlvaro Herrera <alvherre@alvh.no-ip.org>
Wed, 5 Mar 2025 12:50:22 +0000 (13:50 +0100)
committerÁlvaro Herrera <alvherre@alvh.no-ip.org>
Wed, 5 Mar 2025 12:50:22 +0000 (13:50 +0100)
This allows to redefine an existing non-inheritable constraint to be
inheritable, which allows to straighten up situations with NO INHERIT
constraints so that thay can become normal constraints without having to
re-verify existing data.  For existing inheritance children this may
require creating additional constraints, if they don't exist already.

It also allows to do the opposite, if only for symmetry.

Author: Suraj Kharage <suraj.kharage@enterprisedb.com>
Reviewed-by: jian he <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CAF1DzPVfOW6Kk=7SSh7LbneQDJWh=PbJrEC_Wkzc24tHOyQWGg@mail.gmail.com

doc/src/sgml/ref/alter_table.sgml
src/backend/commands/tablecmds.c
src/backend/parser/gram.y
src/include/nodes/parsenodes.h
src/test/regress/expected/inherit.out
src/test/regress/sql/inherit.sql

index 8e56b8e59b01d75945e923516fd57d27e228df8c..dceb7a7593c7bbe5a951e94aa5d4625ed3628b41 100644 (file)
@@ -59,6 +59,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
     ADD <replaceable class="parameter">table_constraint</replaceable> [ NOT VALID ]
     ADD <replaceable class="parameter">table_constraint_using_index</replaceable>
     ALTER CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+    ALTER CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> SET [ INHERIT | NO INHERIT ]
     VALIDATE CONSTRAINT <replaceable class="parameter">constraint_name</replaceable>
     DROP CONSTRAINT [ IF EXISTS ]  <replaceable class="parameter">constraint_name</replaceable> [ RESTRICT | CASCADE ]
     DISABLE TRIGGER [ <replaceable class="parameter">trigger_name</replaceable> | ALL | USER ]
@@ -556,11 +557,31 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     <listitem>
      <para>
       This form alters the attributes of a constraint that was previously
-      created. Currently only foreign key constraints may be altered.
+      created. Currently only foreign key constraints may be altered in
+      this fashion, but see below.
      </para>
     </listitem>
    </varlistentry>
 
+   <varlistentry id="sql-altertable-desc-alter-constraint-inherit">
+    <term><literal>ALTER CONSTRAINT ... SET INHERIT</literal></term>
+    <term><literal>ALTER CONSTRAINT ... SET NO INHERIT</literal></term>
+    <listitem>
+     <para>
+      These forms modify a inheritable constraint so that it becomes not
+      inheritable, or vice-versa. Only not-null constraints may be altered
+      in this fashion at present.
+      In addition to changing the inheritability status of the constraint,
+      in the case where a non-inheritable constraint is being marked
+      inheritable, if the table has children, an equivalent constraint
+      will be added to them. If marking an inheritable constraint as
+      non-inheritable on a table with children, then the corresponding
+      constraint on children will be marked as no longer inherited,
+      but not removed.
+      </para>
+     </listitem>
+    </varlistentry>
+
    <varlistentry id="sql-altertable-desc-validate-constraint">
     <term><literal>VALIDATE CONSTRAINT</literal></term>
     <listitem>
index 8826ca5c32c39fd2fbc65293cfa7a79f045f0b00..59156a1c1f6be932394ed85edd0227dca329eb6c 100644 (file)
@@ -389,9 +389,10 @@ static void AlterIndexNamespaces(Relation classRel, Relation rel,
 static void AlterSeqNamespaces(Relation classRel, Relation rel,
                                                           Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved,
                                                           LOCKMODE lockmode);
-static ObjectAddress ATExecAlterConstraint(Relation rel, ATAlterConstraint *cmdcon,
+static ObjectAddress ATExecAlterConstraint(List **wqueue, Relation rel,
+                                                                                  ATAlterConstraint *cmdcon,
                                                                                   bool recurse, LOCKMODE lockmode);
-static bool ATExecAlterConstraintInternal(ATAlterConstraint *cmdcon, Relation conrel,
+static bool ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon, Relation conrel,
                                                                                  Relation tgrel, Relation rel, HeapTuple contuple,
                                                                                  bool recurse, List **otherrelids, LOCKMODE lockmode);
 static void AlterConstrTriggerDeferrability(Oid conoid, Relation tgrel, Relation rel,
@@ -5437,8 +5438,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
                                                                                           lockmode);
                        break;
                case AT_AlterConstraint:        /* ALTER CONSTRAINT */
-                       address = ATExecAlterConstraint(rel, castNode(ATAlterConstraint,
-                                                                                                                 cmd->def),
+                       address = ATExecAlterConstraint(wqueue, rel,
+                                                                                       castNode(ATAlterConstraint, cmd->def),
                                                                                        cmd->recurse, lockmode);
                        break;
                case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
@@ -11813,14 +11814,14 @@ GetForeignKeyCheckTriggers(Relation trigrel,
  *
  * Update the attributes of a constraint.
  *
- * Currently only works for Foreign Key constraints.
+ * Currently only works for Foreign Key and not null constraints.
  *
  * If the constraint is modified, returns its address; otherwise, return
  * InvalidObjectAddress.
  */
 static ObjectAddress
-ATExecAlterConstraint(Relation rel, ATAlterConstraint *cmdcon, bool recurse,
-                                         LOCKMODE lockmode)
+ATExecAlterConstraint(List **wqueue, Relation rel, ATAlterConstraint *cmdcon,
+                                         bool recurse, LOCKMODE lockmode)
 {
        Relation        conrel;
        Relation        tgrel;
@@ -11871,11 +11872,26 @@ ATExecAlterConstraint(Relation rel, ATAlterConstraint *cmdcon, bool recurse,
                                                cmdcon->conname, RelationGetRelationName(rel))));
 
        currcon = (Form_pg_constraint) GETSTRUCT(contuple);
-       if (currcon->contype != CONSTRAINT_FOREIGN)
+       if (cmdcon->alterDeferrability && currcon->contype != CONSTRAINT_FOREIGN)
                ereport(ERROR,
                                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                                 errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
                                                cmdcon->conname, RelationGetRelationName(rel))));
+       if (cmdcon->alterInheritability &&
+               currcon->contype != CONSTRAINT_NOTNULL)
+               ereport(ERROR,
+                               errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                               errmsg("constraint \"%s\" of relation \"%s\" is not a not-null constraint",
+                                          cmdcon->conname, RelationGetRelationName(rel)));
+
+       /* Refuse to modify inheritability of inherited constraints */
+       if (cmdcon->alterInheritability &&
+               cmdcon->noinherit && currcon->coninhcount > 0)
+               ereport(ERROR,
+                               errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                               errmsg("cannot alter inherited constraint \"%s\" on relation \"%s\"",
+                                          NameStr(currcon->conname),
+                                          RelationGetRelationName(rel)));
 
        /*
         * If it's not the topmost constraint, raise an error.
@@ -11926,8 +11942,8 @@ ATExecAlterConstraint(Relation rel, ATAlterConstraint *cmdcon, bool recurse,
        /*
         * Do the actual catalog work, and recurse if necessary.
         */
-       if (ATExecAlterConstraintInternal(cmdcon, conrel, tgrel, rel, contuple,
-                                                                         recurse, &otherrelids, lockmode))
+       if (ATExecAlterConstraintInternal(wqueue, cmdcon, conrel, tgrel, rel,
+                                                                         contuple, recurse, &otherrelids, lockmode))
                ObjectAddressSet(address, ConstraintRelationId, currcon->oid);
 
        /*
@@ -11958,9 +11974,10 @@ ATExecAlterConstraint(Relation rel, ATAlterConstraint *cmdcon, bool recurse,
  * but existing releases don't do that.)
  */
 static bool
-ATExecAlterConstraintInternal(ATAlterConstraint *cmdcon, Relation conrel,
-                                                         Relation tgrel, Relation rel, HeapTuple contuple,
-                                                         bool recurse, List **otherrelids, LOCKMODE lockmode)
+ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
+                                                         Relation conrel, Relation tgrel, Relation rel,
+                                                         HeapTuple contuple, bool recurse,
+                                                         List **otherrelids, LOCKMODE lockmode)
 {
        Form_pg_constraint currcon;
        Oid                     refrelid = InvalidOid;
@@ -12040,14 +12057,82 @@ ATExecAlterConstraintInternal(ATAlterConstraint *cmdcon, Relation conrel,
                        Relation        childrel;
 
                        childrel = table_open(childcon->conrelid, lockmode);
-                       ATExecAlterConstraintInternal(cmdcon, conrel, tgrel, childrel, childtup,
-                                                                                 recurse, otherrelids, lockmode);
+                       ATExecAlterConstraintInternal(wqueue, cmdcon, conrel, tgrel, childrel,
+                                                                                 childtup, recurse, otherrelids, lockmode);
                        table_close(childrel, NoLock);
                }
 
                systable_endscan(pscan);
        }
 
+       /*
+        * Update the catalog for inheritability.  No work if the constraint is
+        * already in the requested state.
+        */
+       if (cmdcon->alterInheritability &&
+               (cmdcon->noinherit != currcon->connoinherit))
+       {
+               AttrNumber      colNum;
+               char       *colName;
+               List       *children;
+               HeapTuple       copyTuple;
+               Form_pg_constraint copy_con;
+
+               /* The current implementation only works for NOT NULL constraints */
+               Assert(currcon->contype == CONSTRAINT_NOTNULL);
+
+               copyTuple = heap_copytuple(contuple);
+               copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
+               copy_con->connoinherit = cmdcon->noinherit;
+
+               CatalogTupleUpdate(conrel, &copyTuple->t_self, copyTuple);
+               CommandCounterIncrement();
+               heap_freetuple(copyTuple);
+               changed = true;
+
+               /* Fetch the column number and name */
+               colNum = extractNotNullColumn(contuple);
+               colName = get_attname(currcon->conrelid, colNum, false);
+
+               /*
+                * Propagate the change to children.  For SET NO INHERIT, we don't
+                * recursively affect children, just the immediate level.
+                */
+               children = find_inheritance_children(RelationGetRelid(rel),
+                                                                                        lockmode);
+               foreach_oid(childoid, children)
+               {
+                       ObjectAddress addr;
+
+                       if (cmdcon->noinherit)
+                       {
+                               HeapTuple       childtup;
+                               Form_pg_constraint childcon;
+
+                               childtup = findNotNullConstraint(childoid, colName);
+                               if (!childtup)
+                                       elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation %u",
+                                                colName, childoid);
+                               childcon = (Form_pg_constraint) GETSTRUCT(childtup);
+                               Assert(childcon->coninhcount > 0);
+                               childcon->coninhcount--;
+                               childcon->conislocal = true;
+                               CatalogTupleUpdate(conrel, &childtup->t_self, childtup);
+                               heap_freetuple(childtup);
+                       }
+                       else
+                       {
+                               Relation        childrel = table_open(childoid, NoLock);
+
+                               addr = ATExecSetNotNull(wqueue, childrel, NameStr(currcon->conname),
+                                                                               colName, true, true, lockmode);
+                               if (OidIsValid(addr.objectId))
+                                       CommandCounterIncrement();
+                               table_close(childrel, NoLock);
+                       }
+               }
+       }
+
        return changed;
 }
 
index c11a3beff06d129b4dfeaa1b932308616d9c1548..271ae26cbaf93b873ce74b327d85cd46ccfd4907 100644 (file)
@@ -2669,6 +2669,34 @@ alter_table_cmd:
                                                                        NULL, NULL, NULL, yyscanner);
                                        $$ = (Node *) n;
                                }
+                       /* ALTER TABLE <name> ALTER CONSTRAINT SET INHERIT */
+                       | ALTER CONSTRAINT name SET INHERIT
+                               {
+                                       AlterTableCmd *n = makeNode(AlterTableCmd);
+                                       ATAlterConstraint *c = makeNode(ATAlterConstraint);
+
+                                       n->subtype = AT_AlterConstraint;
+                                       n->def = (Node *) c;
+                                       c->conname = $3;
+                                       c->alterInheritability = true;
+                                       c->noinherit = false;
+
+                                       $$ = (Node *) n;
+                               }
+                       /* ALTER TABLE <name> ALTER CONSTRAINT SET NO INHERIT */
+                       | ALTER CONSTRAINT name SET NO INHERIT
+                               {
+                                       AlterTableCmd *n = makeNode(AlterTableCmd);
+                                       ATAlterConstraint *c = makeNode(ATAlterConstraint);
+
+                                       n->subtype = AT_AlterConstraint;
+                                       n->def = (Node *) c;
+                                       c->conname = $3;
+                                       c->alterInheritability = true;
+                                       c->noinherit = true;
+
+                                       $$ = (Node *) n;
+                               }
                        /* ALTER TABLE <name> VALIDATE CONSTRAINT ... */
                        | VALIDATE CONSTRAINT name
                                {
index 0b208f51bdd0c8cc4bd9a2decec0f4e81200c958..23c9e3c5abf2e119ec29ae5c4abe69604b35a8e1 100644 (file)
@@ -2493,6 +2493,8 @@ typedef struct ATAlterConstraint
        bool            alterDeferrability; /* changing deferrability properties? */
        bool            deferrable;             /* DEFERRABLE? */
        bool            initdeferred;   /* INITIALLY DEFERRED? */
+       bool            alterInheritability;    /* changing inheritability properties */
+       bool            noinherit;
 } ATAlterConstraint;
 
 /* Ad-hoc node for AT_ReplicaIdentity */
index 420b6ae59963a768b54dc5a11761a9afe530d6db..e671975a281d29ad326d09bb4008e4a5996fd3ee 100644 (file)
@@ -2505,8 +2505,10 @@ CREATE TABLE inh_nn3 (a int not null, b int,  not null a no inherit);
 ERROR:  conflicting NO INHERIT declaration for not-null constraint on column "a"
 CREATE TABLE inh_nn4 (a int not null no inherit, b int,  not null a);
 ERROR:  conflicting NO INHERIT declaration for not-null constraint on column "a"
-DROP TABLE inh_nn1, inh_nn2, inh_nn3, inh_nn4;
-ERROR:  table "inh_nn2" does not exist
+DROP TABLE IF EXISTS inh_nn1, inh_nn2, inh_nn3, inh_nn4;
+NOTICE:  table "inh_nn2" does not exist, skipping
+NOTICE:  table "inh_nn3" does not exist, skipping
+NOTICE:  table "inh_nn4" does not exist, skipping
 --
 -- test inherit/deinherit
 --
@@ -2744,6 +2746,131 @@ NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to table inh_multiparent
 drop cascades to table inh_multiparent2
 --
+-- Test ALTER CONSTRAINT SET [NO] INHERIT
+--
+create table inh_nn1 (f1 int not null no inherit);
+create table inh_nn2 (f2 text, f3 int, f1 int);
+alter table inh_nn2 inherit inh_nn1;
+create table inh_nn3 (f4 float) inherits (inh_nn2);
+create table inh_nn4 (f5 int, f4 float, f2 text, f3 int, f1 int);
+alter table inh_nn4 inherit inh_nn2, inherit inh_nn1, inherit inh_nn3;
+alter table inh_nn1 alter constraint inh_nn1_f1_not_null set inherit;
+select conrelid::regclass, conname, conkey, coninhcount, conislocal, connoinherit
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass::text in ('inh_nn1', 'inh_nn2', 'inh_nn3', 'inh_nn4')
+ order by 2, 1;
+ conrelid |       conname       | conkey | coninhcount | conislocal | connoinherit 
+----------+---------------------+--------+-------------+------------+--------------
+ inh_nn1  | inh_nn1_f1_not_null | {1}    |           0 | t          | f
+ inh_nn2  | inh_nn1_f1_not_null | {3}    |           1 | f          | f
+ inh_nn3  | inh_nn1_f1_not_null | {3}    |           1 | f          | f
+ inh_nn4  | inh_nn1_f1_not_null | {5}    |           3 | f          | f
+(4 rows)
+
+-- ALTER CONSTRAINT SET NO INHERIT should work on top-level constraints
+alter table inh_nn1 alter constraint inh_nn1_f1_not_null set no inherit;
+select conrelid::regclass, conname, conkey, coninhcount, conislocal, connoinherit
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass::text in ('inh_nn1', 'inh_nn2', 'inh_nn3', 'inh_nn4')
+ order by 2, 1;
+ conrelid |       conname       | conkey | coninhcount | conislocal | connoinherit 
+----------+---------------------+--------+-------------+------------+--------------
+ inh_nn1  | inh_nn1_f1_not_null | {1}    |           0 | t          | t
+ inh_nn2  | inh_nn1_f1_not_null | {3}    |           0 | t          | f
+ inh_nn3  | inh_nn1_f1_not_null | {3}    |           1 | f          | f
+ inh_nn4  | inh_nn1_f1_not_null | {5}    |           2 | t          | f
+(4 rows)
+
+-- A constraint that's NO INHERIT can be dropped without damaging children
+alter table inh_nn1 drop constraint inh_nn1_f1_not_null;
+select conrelid::regclass, conname, coninhcount, conislocal, connoinherit
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass::text in ('inh_nn1', 'inh_nn2', 'inh_nn3', 'inh_nn4')
+ order by 2, 1;
+ conrelid |       conname       | coninhcount | conislocal | connoinherit 
+----------+---------------------+-------------+------------+--------------
+ inh_nn2  | inh_nn1_f1_not_null |           0 | t          | f
+ inh_nn3  | inh_nn1_f1_not_null |           1 | f          | f
+ inh_nn4  | inh_nn1_f1_not_null |           2 | t          | f
+(3 rows)
+
+drop table inh_nn1, inh_nn2, inh_nn3, inh_nn4;
+-- Test inherit constraint and make sure it validates.
+create table inh_nn1 (f1 int not null no inherit);
+create table inh_nn2 (f2 text, f3 int) inherits (inh_nn1);
+insert into inh_nn2 values(NULL, 'sample', 1);
+alter table inh_nn1 alter constraint inh_nn1_f1_not_null set inherit;
+ERROR:  column "f1" of relation "inh_nn2" contains null values
+delete from inh_nn2;
+create table inh_nn3 () inherits (inh_nn2);
+create table inh_nn4 () inherits (inh_nn1, inh_nn2);
+NOTICE:  merging multiple inherited definitions of column "f1"
+alter table inh_nn1    -- test multicommand alter table while at it
+   alter constraint inh_nn1_f1_not_null set inherit,
+   alter constraint inh_nn1_f1_not_null set no inherit;
+select conrelid::regclass, conname, coninhcount, conislocal, connoinherit
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass::text in ('inh_nn1', 'inh_nn2', 'inh_nn3', 'inh_nn4')
+ order by 2, 1;
+ conrelid |       conname       | coninhcount | conislocal | connoinherit 
+----------+---------------------+-------------+------------+--------------
+ inh_nn1  | inh_nn1_f1_not_null |           0 | t          | t
+ inh_nn2  | inh_nn1_f1_not_null |           0 | t          | f
+ inh_nn3  | inh_nn1_f1_not_null |           1 | f          | f
+ inh_nn4  | inh_nn1_f1_not_null |           1 | t          | f
+(4 rows)
+
+drop table inh_nn1, inh_nn2, inh_nn3, inh_nn4;
+-- Test not null inherit constraint which already exists on child table.
+create table inh_nn1 (f1 int not null no inherit);
+create table inh_nn2 (f2 text, f3 int) inherits (inh_nn1);
+create table inh_nn3 (f4 float, constraint nn3_f1 not null f1 no inherit) inherits (inh_nn1, inh_nn2);
+NOTICE:  merging multiple inherited definitions of column "f1"
+select conrelid::regclass, conname, conkey, coninhcount, conislocal, connoinherit
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass::text in ('inh_nn1', 'inh_nn2', 'inh_nn3')
+ order by 2, 1;
+ conrelid |       conname       | conkey | coninhcount | conislocal | connoinherit 
+----------+---------------------+--------+-------------+------------+--------------
+ inh_nn1  | inh_nn1_f1_not_null | {1}    |           0 | t          | t
+ inh_nn3  | nn3_f1              | {1}    |           0 | t          | t
+(2 rows)
+
+-- error: inh_nn3 has an incompatible NO INHERIT constraint
+alter table inh_nn1 alter constraint inh_nn1_f1_not_null set inherit;
+ERROR:  cannot change NO INHERIT status of NOT NULL constraint "nn3_f1" on relation "inh_nn3"
+alter table inh_nn3 alter constraint nn3_f1 set inherit;
+alter table inh_nn1 alter constraint inh_nn1_f1_not_null set inherit; -- now it works
+select conrelid::regclass, conname, conkey, coninhcount, conislocal, connoinherit
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass::text in ('inh_nn1', 'inh_nn2', 'inh_nn3')
+ order by 2, 1;
+ conrelid |       conname       | conkey | coninhcount | conislocal | connoinherit 
+----------+---------------------+--------+-------------+------------+--------------
+ inh_nn1  | inh_nn1_f1_not_null | {1}    |           0 | t          | f
+ inh_nn2  | inh_nn1_f1_not_null | {1}    |           1 | f          | f
+ inh_nn3  | nn3_f1              | {1}    |           2 | t          | f
+(3 rows)
+
+drop table inh_nn1, inh_nn2, inh_nn3;
+-- Negative scenarios for alter constraint .. set inherit.
+create table inh_nn1 (f1 int check(f1 > 5) primary key references inh_nn1, f2 int not null);
+-- constraints other than not-null are not supported
+alter table inh_nn1 alter constraint inh_nn1_f1_check set inherit;
+ERROR:  constraint "inh_nn1_f1_check" of relation "inh_nn1" is not a not-null constraint
+alter table inh_nn1 alter constraint inh_nn1_pkey set inherit;
+ERROR:  constraint "inh_nn1_pkey" of relation "inh_nn1" is not a not-null constraint
+alter table inh_nn1 alter constraint inh_nn1_f1_fkey set inherit;
+ERROR:  constraint "inh_nn1_f1_fkey" of relation "inh_nn1" is not a not-null constraint
+-- try to drop a nonexistant constraint
+alter table inh_nn1 alter constraint foo set inherit;
+ERROR:  constraint "foo" of relation "inh_nn1" does not exist
+-- Can't modify inheritability of inherited constraints
+create table inh_nn2 () inherits (inh_nn1);
+alter table inh_nn2 alter constraint inh_nn1_f2_not_null set no inherit;
+ERROR:  cannot alter inherited constraint "inh_nn1_f2_not_null" on relation "inh_nn2"
+drop table inh_nn1, inh_nn2;
+--
 -- Mixed ownership inheritance tree
 --
 create role regress_alice;
index 30fba16231c0262e1dc77ecf32b350102d8c5f08..4e73c70495c7841bb9e65adbfaa29ebdc376747d 100644 (file)
@@ -957,7 +957,7 @@ CREATE TABLE inh_nn1 (a int not null);
 CREATE TABLE inh_nn2 (a int not null no inherit) INHERITS (inh_nn1);
 CREATE TABLE inh_nn3 (a int not null, b int,  not null a no inherit);
 CREATE TABLE inh_nn4 (a int not null no inherit, b int,  not null a);
-DROP TABLE inh_nn1, inh_nn2, inh_nn3, inh_nn4;
+DROP TABLE IF EXISTS inh_nn1, inh_nn2, inh_nn3, inh_nn4;
 
 --
 -- test inherit/deinherit
@@ -1090,6 +1090,83 @@ select conrelid::regclass, contype, conname,
 
 drop table inh_p1, inh_p2, inh_p3, inh_p4 cascade;
 
+--
+-- Test ALTER CONSTRAINT SET [NO] INHERIT
+--
+create table inh_nn1 (f1 int not null no inherit);
+create table inh_nn2 (f2 text, f3 int, f1 int);
+alter table inh_nn2 inherit inh_nn1;
+create table inh_nn3 (f4 float) inherits (inh_nn2);
+create table inh_nn4 (f5 int, f4 float, f2 text, f3 int, f1 int);
+alter table inh_nn4 inherit inh_nn2, inherit inh_nn1, inherit inh_nn3;
+alter table inh_nn1 alter constraint inh_nn1_f1_not_null set inherit;
+select conrelid::regclass, conname, conkey, coninhcount, conislocal, connoinherit
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass::text in ('inh_nn1', 'inh_nn2', 'inh_nn3', 'inh_nn4')
+ order by 2, 1;
+-- ALTER CONSTRAINT SET NO INHERIT should work on top-level constraints
+alter table inh_nn1 alter constraint inh_nn1_f1_not_null set no inherit;
+select conrelid::regclass, conname, conkey, coninhcount, conislocal, connoinherit
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass::text in ('inh_nn1', 'inh_nn2', 'inh_nn3', 'inh_nn4')
+ order by 2, 1;
+-- A constraint that's NO INHERIT can be dropped without damaging children
+alter table inh_nn1 drop constraint inh_nn1_f1_not_null;
+select conrelid::regclass, conname, coninhcount, conislocal, connoinherit
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass::text in ('inh_nn1', 'inh_nn2', 'inh_nn3', 'inh_nn4')
+ order by 2, 1;
+drop table inh_nn1, inh_nn2, inh_nn3, inh_nn4;
+
+-- Test inherit constraint and make sure it validates.
+create table inh_nn1 (f1 int not null no inherit);
+create table inh_nn2 (f2 text, f3 int) inherits (inh_nn1);
+insert into inh_nn2 values(NULL, 'sample', 1);
+alter table inh_nn1 alter constraint inh_nn1_f1_not_null set inherit;
+delete from inh_nn2;
+create table inh_nn3 () inherits (inh_nn2);
+create table inh_nn4 () inherits (inh_nn1, inh_nn2);
+alter table inh_nn1    -- test multicommand alter table while at it
+   alter constraint inh_nn1_f1_not_null set inherit,
+   alter constraint inh_nn1_f1_not_null set no inherit;
+select conrelid::regclass, conname, coninhcount, conislocal, connoinherit
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass::text in ('inh_nn1', 'inh_nn2', 'inh_nn3', 'inh_nn4')
+ order by 2, 1;
+drop table inh_nn1, inh_nn2, inh_nn3, inh_nn4;
+
+-- Test not null inherit constraint which already exists on child table.
+create table inh_nn1 (f1 int not null no inherit);
+create table inh_nn2 (f2 text, f3 int) inherits (inh_nn1);
+create table inh_nn3 (f4 float, constraint nn3_f1 not null f1 no inherit) inherits (inh_nn1, inh_nn2);
+select conrelid::regclass, conname, conkey, coninhcount, conislocal, connoinherit
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass::text in ('inh_nn1', 'inh_nn2', 'inh_nn3')
+ order by 2, 1;
+-- error: inh_nn3 has an incompatible NO INHERIT constraint
+alter table inh_nn1 alter constraint inh_nn1_f1_not_null set inherit;
+alter table inh_nn3 alter constraint nn3_f1 set inherit;
+alter table inh_nn1 alter constraint inh_nn1_f1_not_null set inherit; -- now it works
+select conrelid::regclass, conname, conkey, coninhcount, conislocal, connoinherit
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass::text in ('inh_nn1', 'inh_nn2', 'inh_nn3')
+ order by 2, 1;
+drop table inh_nn1, inh_nn2, inh_nn3;
+
+-- Negative scenarios for alter constraint .. set inherit.
+create table inh_nn1 (f1 int check(f1 > 5) primary key references inh_nn1, f2 int not null);
+-- constraints other than not-null are not supported
+alter table inh_nn1 alter constraint inh_nn1_f1_check set inherit;
+alter table inh_nn1 alter constraint inh_nn1_pkey set inherit;
+alter table inh_nn1 alter constraint inh_nn1_f1_fkey set inherit;
+-- try to drop a nonexistant constraint
+alter table inh_nn1 alter constraint foo set inherit;
+-- Can't modify inheritability of inherited constraints
+create table inh_nn2 () inherits (inh_nn1);
+alter table inh_nn2 alter constraint inh_nn1_f2_not_null set no inherit;
+
+drop table inh_nn1, inh_nn2;
+
 --
 -- Mixed ownership inheritance tree
 --