Fix handling of inherited check constraints in ALTER COLUMN TYPE.
authorTom Lane
Mon, 5 Nov 2012 18:36:21 +0000 (13:36 -0500)
committerTom Lane
Mon, 5 Nov 2012 18:36:21 +0000 (13:36 -0500)
This case got broken in 8.4 by the addition of an error check that
complains if ALTER TABLE ONLY is used on a table that has children.
We do use ONLY for this situation, but it's okay because the necessary
recursion occurs at a higher level.  So we need to have a separate
flag to suppress recursion without making the error check.

Reported and patched by Pavan Deolasee, with some editorial adjustments by
me.  Back-patch to 8.4, since this is a regression of functionality that
worked in earlier branches.

src/backend/commands/tablecmds.c
src/include/nodes/parsenodes.h
src/test/regress/expected/alter_table.out
src/test/regress/sql/alter_table.sql

index d963f6985b8557f70241c58e7e3aba000bd836e0..d88e3dee3faa905b899e67dc4782691582f14772 100644 (file)
@@ -335,13 +335,15 @@ static void ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
               IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
 static void ATExecAddConstraint(List **wqueue,
                    AlteredTableInfo *tab, Relation rel,
-                Constraint *newConstraint, bool recurse, LOCKMODE lockmode);
+                   Constraint *newConstraint, bool recurse, bool is_readd,
+                   LOCKMODE lockmode);
 static void ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
                         IndexStmt *stmt, LOCKMODE lockmode);
 static void ATAddCheckConstraint(List **wqueue,
                     AlteredTableInfo *tab, Relation rel,
                     Constraint *constr,
-                    bool recurse, bool recursing, LOCKMODE lockmode);
+                    bool recurse, bool recursing, bool is_readd,
+                    LOCKMODE lockmode);
 static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
                          Constraint *fkconstraint, LOCKMODE lockmode);
 static void ATExecDropConstraint(Relation rel, const char *constrName,
@@ -2757,6 +2759,7 @@ AlterTableGetLockLevel(List *cmds)
            case AT_ColumnDefault:
            case AT_ProcessedConstraint:        /* becomes AT_AddConstraint */
            case AT_AddConstraintRecurse:       /* becomes AT_AddConstraint */
+           case AT_ReAddConstraint:            /* becomes AT_AddConstraint */
            case AT_EnableTrig:
            case AT_EnableAlwaysTrig:
            case AT_EnableReplicaTrig:
@@ -3248,11 +3251,15 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
            break;
        case AT_AddConstraint:  /* ADD CONSTRAINT */
            ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-                               false, lockmode);
+                               false, false, lockmode);
            break;
        case AT_AddConstraintRecurse:   /* ADD CONSTRAINT with recursion */
            ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-                               true, lockmode);
+                               true, false, lockmode);
+           break;
+       case AT_ReAddConstraint:    /* Re-add pre-existing check constraint */
+           ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
+                               false, true, lockmode);
            break;
        case AT_AddIndexConstraint:     /* ADD CONSTRAINT USING INDEX */
            ATExecAddIndexConstraint(tab, rel, (IndexStmt *) cmd->def, lockmode);
@@ -5499,7 +5506,8 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
  */
 static void
 ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
-                 Constraint *newConstraint, bool recurse, LOCKMODE lockmode)
+                   Constraint *newConstraint, bool recurse, bool is_readd,
+                   LOCKMODE lockmode)
 {
    Assert(IsA(newConstraint, Constraint));
 
@@ -5512,7 +5520,8 @@ ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
    {
        case CONSTR_CHECK:
            ATAddCheckConstraint(wqueue, tab, rel,
-                                newConstraint, recurse, false, lockmode);
+                                newConstraint, recurse, false, is_readd,
+                                lockmode);
            break;
 
        case CONSTR_FOREIGN:
@@ -5564,11 +5573,18 @@ ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
  * AddRelationNewConstraints would normally assign different names to the
  * child constraints.  To fix that, we must capture the name assigned at
  * the parent table and pass that down.
+ *
+ * When re-adding a previously existing constraint (during ALTER COLUMN TYPE),
+ * we don't need to recurse here, because recursion will be carried out at a
+ * higher level; the constraint name issue doesn't apply because the names
+ * have already been assigned and are just being re-used.  We need a separate
+ * "is_readd" flag for that; just setting recurse=false would result in an
+ * error if there are child tables.
  */
 static void
 ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
                     Constraint *constr, bool recurse, bool recursing,
-                    LOCKMODE lockmode)
+                    bool is_readd, LOCKMODE lockmode)
 {
    List       *newcons;
    ListCell   *lcon;
@@ -5633,9 +5649,11 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
        return;
 
    /*
-    * Adding a NO INHERIT constraint? No need to find our children
+    * If adding a NO INHERIT constraint, no need to find our children.
+    * Likewise, in a re-add operation, we don't need to recurse (that will be
+    * handled at higher levels).
     */
-   if (constr->is_no_inherit)
+   if (constr->is_no_inherit || is_readd)
        return;
 
    /*
@@ -5670,7 +5688,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
        /* Recurse to child */
        ATAddCheckConstraint(wqueue, childtab, childrel,
-                            constr, recurse, true, lockmode);
+                            constr, recurse, true, is_readd, lockmode);
 
        heap_close(childrel, NoLock);
    }
@@ -7884,6 +7902,10 @@ ATPostAlterTypeParse(Oid oldId, char *cmd,
    /*
     * Attach each generated command to the proper place in the work queue.
     * Note this could result in creation of entirely new work-queue entries.
+    *
+    * Also note that we have to tweak the command subtypes, because it turns
+    * out that re-creation of indexes and constraints has to act a bit
+    * differently from initial creation.
     */
    foreach(list_item, querytree_list)
    {
@@ -7941,6 +7963,7 @@ ATPostAlterTypeParse(Oid oldId, char *cmd,
                                if (con->contype == CONSTR_FOREIGN &&
                                    !rewrite && !tab->rewrite)
                                    TryReuseForeignKey(oldId, con);
+                               cmd->subtype = AT_ReAddConstraint;
                                tab->subcmds[AT_PASS_OLD_CONSTR] =
                                    lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
                                break;
index e6f1df7feb0f12759a94616de90a5375798e6ee5..2997a2ab69e844d1cf3615291587dd847e42a747 100644 (file)
@@ -1227,7 +1227,9 @@ typedef enum AlterTableType
    AT_DropInherit,             /* NO INHERIT parent */
    AT_AddOf,                   /* OF  */
    AT_DropOf,                  /* NOT OF */
-   AT_GenericOptions           /* OPTIONS (...) */
+   AT_GenericOptions,          /* OPTIONS (...) */
+   /* this will be in a more natural position in 9.3: */
+   AT_ReAddConstraint          /* internal to commands/tablecmds.c */
 } AlterTableType;
 
 typedef struct AlterTableCmd   /* one subcommand of an ALTER TABLE */
index 60ced86f490767dc7842987fa9375b37bce085ee..8e9d22836ac63eabcfba91a01c7254747d50127d 100644 (file)
@@ -1804,6 +1804,28 @@ where oid = 'test_storage'::regclass;
  t
 (1 row)
 
+-- ALTER TYPE with a check constraint and a child table (bug before Nov 2012)
+CREATE TABLE test_inh_check (a float check (a > 10.2));
+CREATE TABLE test_inh_check_child() INHERITS(test_inh_check);
+ALTER TABLE test_inh_check ALTER COLUMN a TYPE numeric;
+\d test_inh_check
+Table "public.test_inh_check"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | numeric | 
+Check constraints:
+    "test_inh_check_a_check" CHECK (a::double precision > 10.2::double precision)
+Number of child tables: 1 (Use \d+ to list them.)
+
+\d test_inh_check_child
+Table "public.test_inh_check_child"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | numeric | 
+Check constraints:
+    "test_inh_check_a_check" CHECK (a::double precision > 10.2::double precision)
+Inherits: test_inh_check
+
 --
 -- lock levels
 --
index 0f9cb380e1662749295cc2fca959346a8d1b84c6..dcf8121d70c1dd7ac2db2ffaf58ce20bca2ec742 100644 (file)
@@ -1239,6 +1239,13 @@ select reltoastrelid <> 0 as has_toast_table
 from pg_class
 where oid = 'test_storage'::regclass;
 
+-- ALTER TYPE with a check constraint and a child table (bug before Nov 2012)
+CREATE TABLE test_inh_check (a float check (a > 10.2));
+CREATE TABLE test_inh_check_child() INHERITS(test_inh_check);
+ALTER TABLE test_inh_check ALTER COLUMN a TYPE numeric;
+\d test_inh_check
+\d test_inh_check_child
+
 --
 -- lock levels
 --