Allow CHECK constraints to be declared ONLY
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Mon, 5 Dec 2011 18:10:18 +0000 (15:10 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Mon, 19 Dec 2011 20:30:23 +0000 (17:30 -0300)
This makes them enforceable only on the parent table, not on children
tables.  This is useful in various situations, per discussion involving
people bitten by the restrictive behavior introduced in 8.4.

Message-Id:
8762mp93iw.fsf@comcast.net
CAFaPBrSMMpubkGf4zcRL_YL-AERUbYF_-ZNNYfb3CVwwEqc9TQ@mail.gmail.com

Authors: Nikhil Sontakke, Alex Hunsaker
Reviewed by Robert Haas and myself

19 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/ref/alter_table.sgml
src/backend/catalog/heap.c
src/backend/catalog/index.c
src/backend/catalog/pg_constraint.c
src/backend/commands/tablecmds.c
src/backend/commands/trigger.c
src/backend/commands/typecmds.c
src/backend/utils/cache/relcache.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dump.h
src/bin/psql/describe.c
src/include/access/tupdesc.h
src/include/catalog/heap.h
src/include/catalog/pg_constraint.h
src/test/regress/expected/alter_table.out
src/test/regress/expected/inherit.out
src/test/regress/sql/alter_table.sql
src/test/regress/sql/inherit.sql

index d948ed487c6961ee36613839bb4fdb465b51710e..be4bbc736c745aefb0c6a3b757c91aadc6b7e8e9 100644 (file)
       </entry>
      </row>
 
+     <row>
+      <entry><structfield>conisonly</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>
+       This constraint is defined locally for the relation.  It is a
+       non-inheritable constraint.
+      </entry>
+     </row>
+
      <row>
       <entry><structfield>conkey</structfield></entry>
       <entry><type>int2[]</type></entry>
index 00a477ef885d9338d23830aa057776f1c1830019..3b111a4c2b90a843a7411f1e7d0dddaf06e677cc 100644 (file)
@@ -983,6 +983,14 @@ ALTER TABLE distributors ADD CONSTRAINT zipchk CHECK (char_length(zipcode) = 5);
 </programlisting>
   </para>
 
+  <para>
+   To add a check constraint only to a table and not to its children:
+<programlisting>
+ALTER TABLE ONLY distributors ADD CONSTRAINT zipchk CHECK (char_length(zipcode) = 5);
+</programlisting>
+   (The check constraint will not be inherited by future children, either.)
+  </para>
+
   <para>
    To remove a check constraint from a table and all its children:
 <programlisting>
index e11d896ec8cd3a15f33393cf7f2b517520860890..2f6a6ffba170f864cd1ae05833ffd3117cc02a24 100644 (file)
@@ -92,10 +92,10 @@ static Oid AddNewRelationType(const char *typeName,
                   Oid new_array_type);
 static void RelationRemoveInheritance(Oid relid);
 static void StoreRelCheck(Relation rel, char *ccname, Node *expr,
-             bool is_validated, bool is_local, int inhcount);
+             bool is_validated, bool is_local, int inhcount, bool is_only);
 static void StoreConstraints(Relation rel, List *cooked_constraints);
 static bool MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
-                           bool allow_merge, bool is_local);
+                           bool allow_merge, bool is_local, bool is_only);
 static void SetRelationNumChecks(Relation rel, int numchecks);
 static Node *cookConstraint(ParseState *pstate,
               Node *raw_constraint,
@@ -1859,7 +1859,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, Node *expr)
  */
 static void
 StoreRelCheck(Relation rel, char *ccname, Node *expr,
-             bool is_validated, bool is_local, int inhcount)
+             bool is_validated, bool is_local, int inhcount, bool is_only)
 {
    char       *ccbin;
    char       *ccsrc;
@@ -1942,7 +1942,8 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
                          ccbin,    /* Binary form of check constraint */
                          ccsrc,    /* Source form of check constraint */
                          is_local,     /* conislocal */
-                         inhcount);    /* coninhcount */
+                         inhcount,     /* coninhcount */
+                         is_only);     /* conisonly */
 
    pfree(ccbin);
    pfree(ccsrc);
@@ -1983,7 +1984,7 @@ StoreConstraints(Relation rel, List *cooked_constraints)
                break;
            case CONSTR_CHECK:
                StoreRelCheck(rel, con->name, con->expr, !con->skip_validation,
-                             con->is_local, con->inhcount);
+                             con->is_local, con->inhcount, con->is_only);
                numchecks++;
                break;
            default:
@@ -2026,7 +2027,8 @@ AddRelationNewConstraints(Relation rel,
                          List *newColDefaults,
                          List *newConstraints,
                          bool allow_merge,
-                         bool is_local)
+                         bool is_local,
+                         bool is_only)
 {
    List       *cookedConstraints = NIL;
    TupleDesc   tupleDesc;
@@ -2099,6 +2101,7 @@ AddRelationNewConstraints(Relation rel,
        cooked->skip_validation = false;
        cooked->is_local = is_local;
        cooked->inhcount = is_local ? 0 : 1;
+       cooked->is_only = is_only;
        cookedConstraints = lappend(cookedConstraints, cooked);
    }
 
@@ -2166,7 +2169,7 @@ AddRelationNewConstraints(Relation rel,
             * what ATAddCheckConstraint wants.)
             */
            if (MergeWithExistingConstraint(rel, ccname, expr,
-                                           allow_merge, is_local))
+                               allow_merge, is_local, is_only))
                continue;
        }
        else
@@ -2213,7 +2216,7 @@ AddRelationNewConstraints(Relation rel,
         * OK, store it.
         */
        StoreRelCheck(rel, ccname, expr, !cdef->skip_validation, is_local,
-                     is_local ? 0 : 1);
+                     is_local ? 0 : 1, is_only);
 
        numchecks++;
 
@@ -2225,6 +2228,7 @@ AddRelationNewConstraints(Relation rel,
        cooked->skip_validation = cdef->skip_validation;
        cooked->is_local = is_local;
        cooked->inhcount = is_local ? 0 : 1;
+       cooked->is_only = is_only;
        cookedConstraints = lappend(cookedConstraints, cooked);
    }
 
@@ -2250,7 +2254,8 @@ AddRelationNewConstraints(Relation rel,
  */
 static bool
 MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
-                           bool allow_merge, bool is_local)
+                           bool allow_merge, bool is_local,
+                           bool is_only)
 {
    bool        found;
    Relation    conDesc;
@@ -2312,6 +2317,11 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
                con->conislocal = true;
            else
                con->coninhcount++;
+           if (is_only)
+           {
+               Assert(is_local);
+               con->conisonly = true;
+           }
            simple_heap_update(conDesc, &tup->t_self, tup);
            CatalogUpdateIndexes(conDesc, tup);
            break;
index 758872ff4eda584ab4f269c768226e10079ddf08..f9075c4752dfa1ea734524277be8d5bf78a01b4e 100644 (file)
@@ -1155,7 +1155,8 @@ index_constraint_create(Relation heapRelation,
                                   NULL,
                                   NULL,
                                   true,        /* islocal */
-                                  0);  /* inhcount */
+                                  0,           /* inhcount */
+                                  false);      /* isonly */
 
    /*
     * Register the index as internally dependent on the constraint.
index 69979942af42a00922fb4bce3ada1fa622dd2212..cfe82ea3a8c86df948c61bb328d8cfc6a3734ebf 100644 (file)
@@ -66,7 +66,8 @@ CreateConstraintEntry(const char *constraintName,
                      const char *conBin,
                      const char *conSrc,
                      bool conIsLocal,
-                     int conInhCount)
+                     int conInhCount,
+                     bool conIsOnly)
 {
    Relation    conDesc;
    Oid         conOid;
@@ -169,6 +170,7 @@ CreateConstraintEntry(const char *constraintName,
    values[Anum_pg_constraint_confmatchtype - 1] = CharGetDatum(foreignMatchType);
    values[Anum_pg_constraint_conislocal - 1] = BoolGetDatum(conIsLocal);
    values[Anum_pg_constraint_coninhcount - 1] = Int32GetDatum(conInhCount);
+   values[Anum_pg_constraint_conisonly - 1] = BoolGetDatum(conIsOnly);
 
    if (conkeyArray)
        values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray);
index 135736a8dc64c8404060a9d6e225e6e9f6463800..00b6cb9d50dda8277dd4291648a2579a4f361a98 100644 (file)
@@ -579,6 +579,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
            cooked->skip_validation = false;
            cooked->is_local = true;    /* not used for defaults */
            cooked->inhcount = 0;       /* ditto */
+           cooked->is_only = false;
            cookedDefaults = lappend(cookedDefaults, cooked);
            descriptor->attrs[attnum - 1]->atthasdef = true;
        }
@@ -638,7 +639,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
     */
    if (rawDefaults || stmt->constraints)
        AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
-                                 true, true);
+                                 true, true, false);
 
    /*
     * Clean up.  We keep lock on new relation (although it shouldn't be
@@ -1599,6 +1600,10 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
                char       *name = check[i].ccname;
                Node       *expr;
 
+               /* ignore if the constraint is non-inheritable */
+               if (check[i].cconly)
+                   continue;
+
                /* adjust varattnos of ccbin here */
                expr = stringToNode(check[i].ccbin);
                change_varattnos_of_a_node(expr, newattno);
@@ -1617,6 +1622,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
                    cooked->skip_validation = false;
                    cooked->is_local = false;
                    cooked->inhcount = 1;
+                   cooked->is_only = false;
                    constraints = lappend(constraints, cooked);
                }
            }
@@ -4501,7 +4507,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
         * This function is intended for CREATE TABLE, so it processes a
         * _list_ of defaults, but we just do one.
         */
-       AddRelationNewConstraints(rel, list_make1(rawEnt), NIL, false, true);
+       AddRelationNewConstraints(rel, list_make1(rawEnt), NIL, false, true, false);
 
        /* Make the additional catalog changes visible */
        CommandCounterIncrement();
@@ -4898,7 +4904,7 @@ ATExecColumnDefault(Relation rel, const char *colName,
         * This function is intended for CREATE TABLE, so it processes a
         * _list_ of defaults, but we just do one.
         */
-       AddRelationNewConstraints(rel, list_make1(rawEnt), NIL, false, true);
+       AddRelationNewConstraints(rel, list_make1(rawEnt), NIL, false, true, false);
    }
 }
 
@@ -5562,10 +5568,16 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
     * omitted from the returned list, which is what we want: we do not need
     * to do any validation work.  That can only happen at child tables,
     * though, since we disallow merging at the top level.
+    *
+    * Note: we set is_only based on the recurse flag which is false when
+    * interpretInhOption() of our statement returns false all the way up
+    * in AlterTable and gets passed all the way down to here.
     */
    newcons = AddRelationNewConstraints(rel, NIL,
                                        list_make1(copyObject(constr)),
-                                       recursing, !recursing);
+                                       recursing, /* allow_merge */
+                                       !recursing, /* is_local */
+                                       !recurse && !recursing); /* is_only */
 
    /* Add each to-be-validated constraint to Phase 3's queue */
    foreach(lcon, newcons)
@@ -5605,6 +5617,12 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
    if (newcons == NIL)
        return;
 
+   /*
+    * Adding an ONLY constraint? No need to find our children
+    */
+   if (!recurse && !recursing)
+       return;
+
    /*
     * Propagate to children as appropriate.  Unlike most other ALTER
     * routines, we have to do this one level of recursion at a time; we can't
@@ -5612,15 +5630,6 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
     */
    children = find_inheritance_children(RelationGetRelid(rel), lockmode);
 
-   /*
-    * If we are told not to recurse, there had better not be any child
-    * tables; else the addition would put them out of step.
-    */
-   if (children && !recurse)
-       ereport(ERROR,
-               (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-                errmsg("constraint must be added to child tables too")));
-
    foreach(child, children)
    {
        Oid         childrelid = lfirst_oid(child);
@@ -5914,7 +5923,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
                                      NULL,
                                      NULL,
                                      true,     /* islocal */
-                                     0);       /* inhcount */
+                                     0,        /* inhcount */
+                                     false);   /* isonly */
 
    /*
     * Create the triggers that will enforce the constraint.
@@ -6755,6 +6765,7 @@ ATExecDropConstraint(Relation rel, const char *constrName,
    HeapTuple   tuple;
    bool        found = false;
    bool        is_check_constraint = false;
+   bool        is_only_constraint = false;
 
    /* At top level, permission check was done in ATPrepCmd, else do it */
    if (recursing)
@@ -6791,6 +6802,12 @@ ATExecDropConstraint(Relation rel, const char *constrName,
        /* Right now only CHECK constraints can be inherited */
        if (con->contype == CONSTRAINT_CHECK)
            is_check_constraint = true;
+       
+       if (con->conisonly)
+       {
+           Assert(is_check_constraint);
+           is_only_constraint = true;
+       }
 
        /*
         * Perform the actual constraint deletion
@@ -6802,6 +6819,9 @@ ATExecDropConstraint(Relation rel, const char *constrName,
        performDeletion(&conobj, behavior);
 
        found = true;
+
+       /* constraint found and dropped -- no need to keep looping */
+       break;
    }
 
    systable_endscan(scan);
@@ -6830,7 +6850,7 @@ ATExecDropConstraint(Relation rel, const char *constrName,
     * routines, we have to do this one level of recursion at a time; we can't
     * use find_all_inheritors to do it in one pass.
     */
-   if (is_check_constraint)
+   if (is_check_constraint && !is_only_constraint)
        children = find_inheritance_children(RelationGetRelid(rel), lockmode);
    else
        children = NIL;
index f4c93e5b25460be7c06543e2ecb754deed753eeb..eb5114079ad7248542066eff4a32ec2a88799ed8 100644 (file)
@@ -449,7 +449,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
                                              NULL,
                                              NULL,
                                              true,     /* islocal */
-                                             0);       /* inhcount */
+                                             0,        /* inhcount */
+                                             false);   /* isonly */
    }
 
    /*
index 2b8f9aec38400b3c7525b09313a7bd9f0e6de332..eda43d826fa29ce783487a7d0d1c568a5d8245cc 100644 (file)
@@ -2934,7 +2934,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
                          ccbin,    /* Binary form of check constraint */
                          ccsrc,    /* Source form of check constraint */
                          true, /* is local */
-                         0);   /* inhcount */
+                         0,    /* inhcount */
+                         false);   /* is only */
 
    /*
     * Return the compiled constraint expression so the calling routine can
index 603e4c1b621ba672a1f1b1088e78f83cf18f4bd5..f9ad75e7f8901261fa9b1d988ba3c57dc81dd1e5 100644 (file)
@@ -3261,6 +3261,7 @@ CheckConstraintFetch(Relation relation)
                 RelationGetRelationName(relation));
 
        check[found].ccvalid = conform->convalidated;
+       check[found].cconly = conform->conisonly;
        check[found].ccname = MemoryContextStrdup(CacheMemoryContext,
                                                  NameStr(conform->conname));
 
index 7dfa1dd8f8a2dcc60cbf500c080c259d53776f7a..5deb9d658d8ece96f069846e06835afaeb38b14a 100644 (file)
@@ -6019,11 +6019,16 @@ getTableAttrs(TableInfo *tblinfo, int numTables)
                          tbinfo->dobj.name);
 
            resetPQExpBuffer(q);
-           if (g_fout->remoteVersion >= 90100)
+           if (g_fout->remoteVersion >= 90200)
            {
+               /*
+                * conisonly and convalidated are new in 9.2 (actually, the latter
+                * is there in 9.1, but it wasn't ever false for check constraints
+                * until 9.2).
+                */
                appendPQExpBuffer(q, "SELECT tableoid, oid, conname, "
                           "pg_catalog.pg_get_constraintdef(oid) AS consrc, "
-                                 "conislocal, convalidated "
+                                 "conislocal, convalidated, conisonly "
                                  "FROM pg_catalog.pg_constraint "
                                  "WHERE conrelid = '%u'::pg_catalog.oid "
                                  "   AND contype = 'c' "
@@ -6034,7 +6039,8 @@ getTableAttrs(TableInfo *tblinfo, int numTables)
            {
                appendPQExpBuffer(q, "SELECT tableoid, oid, conname, "
                           "pg_catalog.pg_get_constraintdef(oid) AS consrc, "
-                                 "conislocal, true AS convalidated "
+                                 "conislocal, true AS convalidated, "
+                                 "false as conisonly "
                                  "FROM pg_catalog.pg_constraint "
                                  "WHERE conrelid = '%u'::pg_catalog.oid "
                                  "   AND contype = 'c' "
@@ -6045,7 +6051,8 @@ getTableAttrs(TableInfo *tblinfo, int numTables)
            {
                appendPQExpBuffer(q, "SELECT tableoid, oid, conname, "
                           "pg_catalog.pg_get_constraintdef(oid) AS consrc, "
-                                 "true AS conislocal, true AS convalidated "
+                                 "true AS conislocal, true AS convalidated, "
+                                 "false as conisonly "
                                  "FROM pg_catalog.pg_constraint "
                                  "WHERE conrelid = '%u'::pg_catalog.oid "
                                  "   AND contype = 'c' "
@@ -6057,7 +6064,8 @@ getTableAttrs(TableInfo *tblinfo, int numTables)
                /* no pg_get_constraintdef, must use consrc */
                appendPQExpBuffer(q, "SELECT tableoid, oid, conname, "
                                  "'CHECK (' || consrc || ')' AS consrc, "
-                                 "true AS conislocal, true AS convalidated "
+                                 "true AS conislocal, true AS convalidated, "
+                                 "false as conisonly "
                                  "FROM pg_catalog.pg_constraint "
                                  "WHERE conrelid = '%u'::pg_catalog.oid "
                                  "   AND contype = 'c' "
@@ -6070,7 +6078,8 @@ getTableAttrs(TableInfo *tblinfo, int numTables)
                appendPQExpBuffer(q, "SELECT tableoid, 0 AS oid, "
                                  "rcname AS conname, "
                                  "'CHECK (' || rcsrc || ')' AS consrc, "
-                                 "true AS conislocal, true AS convalidated "
+                                 "true AS conislocal, true AS convalidated, "
+                                 "false as conisonly "
                                  "FROM pg_relcheck "
                                  "WHERE rcrelid = '%u'::oid "
                                  "ORDER BY rcname",
@@ -6081,7 +6090,8 @@ getTableAttrs(TableInfo *tblinfo, int numTables)
                appendPQExpBuffer(q, "SELECT tableoid, oid, "
                                  "rcname AS conname, "
                                  "'CHECK (' || rcsrc || ')' AS consrc, "
-                                 "true AS conislocal, true AS convalidated "
+                                 "true AS conislocal, true AS convalidated, "
+                                 "false as conisonly "
                                  "FROM pg_relcheck "
                                  "WHERE rcrelid = '%u'::oid "
                                  "ORDER BY rcname",
@@ -6094,7 +6104,8 @@ getTableAttrs(TableInfo *tblinfo, int numTables)
                                  "(SELECT oid FROM pg_class WHERE relname = 'pg_relcheck') AS tableoid, "
                                  "oid, rcname AS conname, "
                                  "'CHECK (' || rcsrc || ')' AS consrc, "
-                                 "true AS conislocal, true AS convalidated "
+                                 "true AS conislocal, true AS convalidated, "
+                                 "false as conisonly "
                                  "FROM pg_relcheck "
                                  "WHERE rcrelid = '%u'::oid "
                                  "ORDER BY rcname",
@@ -6120,6 +6131,7 @@ getTableAttrs(TableInfo *tblinfo, int numTables)
            for (j = 0; j < numConstrs; j++)
            {
                bool    validated = PQgetvalue(res, j, 5)[0] == 't';
+               bool    isonly = PQgetvalue(res, j, 6)[0] == 't';
 
                constrs[j].dobj.objType = DO_CONSTRAINT;
                constrs[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, 0));
@@ -6136,12 +6148,14 @@ getTableAttrs(TableInfo *tblinfo, int numTables)
                constrs[j].condeferrable = false;
                constrs[j].condeferred = false;
                constrs[j].conislocal = (PQgetvalue(res, j, 4)[0] == 't');
+               constrs[j].conisonly = isonly;
                /*
                 * An unvalidated constraint needs to be dumped separately, so
                 * that potentially-violating existing data is loaded before
-                * the constraint.
+                * the constraint.  An ONLY constraint needs to be dumped
+                * separately too.
                 */
-               constrs[j].separate = !validated;
+               constrs[j].separate = !validated || isonly;
 
                constrs[j].dobj.dump = tbinfo->dobj.dump;
 
@@ -6149,12 +6163,12 @@ getTableAttrs(TableInfo *tblinfo, int numTables)
                 * Mark the constraint as needing to appear before the table
                 * --- this is so that any other dependencies of the
                 * constraint will be emitted before we try to create the
-                * table.  If the constraint is not validated, it will be
+                * table.  If the constraint is to be dumped separately, it will be
                 * dumped after data is loaded anyway, so don't do it.  (There's
                 * an automatic dependency in the opposite direction anyway, so
                 * don't need to add one manually here.)
                 */
-               if (validated)
+               if (!constrs[j].separate)
                    addObjectDependency(&tbinfo->dobj,
                                        constrs[j].dobj.dumpId);
 
@@ -13193,9 +13207,9 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
        /* Ignore if not to be dumped separately */
        if (coninfo->separate)
        {
-           /* not ONLY since we want it to propagate to children */
-           appendPQExpBuffer(q, "ALTER TABLE %s\n",
-                             fmtId(tbinfo->dobj.name));
+           /* add ONLY if we do not want it to propagate to children */
+           appendPQExpBuffer(q, "ALTER TABLE %s %s\n",
+                            coninfo->conisonly ? "ONLY" : "", fmtId(tbinfo->dobj.name));
            appendPQExpBuffer(q, "    ADD CONSTRAINT %s %s;\n",
                              fmtId(coninfo->dobj.name),
                              coninfo->condef);
index 31442f1622d76442fdee07d7ece7adb7a0469075..3bfeb317d6e05a4163d8efdefa82dd833ba8d6b0 100644 (file)
@@ -378,6 +378,7 @@ typedef struct _constraintInfo
    bool        condeferrable;  /* TRUE if constraint is DEFERRABLE */
    bool        condeferred;    /* TRUE if constraint is INITIALLY DEFERRED */
    bool        conislocal;     /* TRUE if constraint has local definition */
+   bool        conisonly;      /* TRUE if constraint is non-inheritable */
    bool        separate;       /* TRUE if must dump as separate item */
 } ConstraintInfo;
 
index dcafdd2c1aed1b18097f85a112b7414071f138ad..b6aeae22e5d1a5781e0578b35e136df0660c976b 100644 (file)
@@ -1781,12 +1781,20 @@ describeOneTableDetails(const char *schemaname,
        /* print table (and column) check constraints */
        if (tableinfo.checks)
        {
+           char *is_only;
+
+           if (pset.sversion >= 90200)
+               is_only = "r.conisonly";
+           else
+               is_only = "false AS conisonly";
+
            printfPQExpBuffer(&buf,
-                             "SELECT r.conname, "
+                             "SELECT r.conname, %s, "
                              "pg_catalog.pg_get_constraintdef(r.oid, true)\n"
                              "FROM pg_catalog.pg_constraint r\n"
-                  "WHERE r.conrelid = '%s' AND r.contype = 'c'\nORDER BY 1;",
-                             oid);
+                  "WHERE r.conrelid = '%s' AND r.contype = 'c'\n"
+                             "ORDER BY 2 DESC, 1;",
+                             is_only, oid);
            result = PSQLexec(buf.data, false);
            if (!result)
                goto error_return;
@@ -1799,9 +1807,10 @@ describeOneTableDetails(const char *schemaname,
                for (i = 0; i < tuples; i++)
                {
                    /* untranslated contraint name and def */
-                   printfPQExpBuffer(&buf, "    \"%s\" %s",
+                   printfPQExpBuffer(&buf, "    \"%s\"%s%s",
                                      PQgetvalue(result, i, 0),
-                                     PQgetvalue(result, i, 1));
+                                     (strcmp(PQgetvalue(result, i, 1), "t") == 0) ? " (ONLY) ":" ",
+                                     PQgetvalue(result, i, 2));
 
                    printTableAddFooter(&cont, buf.data);
                }
index 8b99cb849d8e201eef69de535607c485ed4e39e5..d5e1333d03933931ba6a3bbf45b6b4cde8130a2a 100644 (file)
@@ -30,6 +30,7 @@ typedef struct constrCheck
    char       *ccname;
    char       *ccbin;          /* nodeToString representation of expr */
    bool        ccvalid;
+   bool        cconly;         /* this is a non-inheritable constraint */
 } ConstrCheck;
 
 /* This structure contains constraints of a tuple */
index aee2d88ebe233609d914d1e6adc070543c6830b5..07938137dece713cd168991f39e861c4e44d1421 100644 (file)
@@ -33,6 +33,7 @@ typedef struct CookedConstraint
    bool        skip_validation;    /* skip validation? (only for CHECK) */
    bool        is_local;       /* constraint has local (non-inherited) def */
    int         inhcount;       /* number of times constraint is inherited */
+   bool        is_only;        /* constraint has local def and cannot be inherited */
 } CookedConstraint;
 
 extern Relation heap_create(const char *relname,
@@ -91,7 +92,8 @@ extern List *AddRelationNewConstraints(Relation rel,
                          List *newColDefaults,
                          List *newConstraints,
                          bool allow_merge,
-                         bool is_local);
+                         bool is_local,
+                         bool is_only);
 
 extern void StoreAttrDefault(Relation rel, AttrNumber attnum, Node *expr);
 
index 1566af2973549f7a9b2c9fd8de34b004ca41b105..dae42e8e5fb64e00c0979983bbe8ea7639f6e5f9 100644 (file)
@@ -88,6 +88,9 @@ CATALOG(pg_constraint,2606)
    /* Number of times inherited from direct parent relation(s) */
    int4        coninhcount;
 
+   /* Has a local definition and cannot be inherited */
+   bool        conisonly;
+
    /*
     * VARIABLE LENGTH FIELDS start here.  These fields may be NULL, too.
     */
@@ -149,7 +152,7 @@ typedef FormData_pg_constraint *Form_pg_constraint;
  *     compiler constants for pg_constraint
  * ----------------
  */
-#define Natts_pg_constraint                    23
+#define Natts_pg_constraint                    24
 #define Anum_pg_constraint_conname         1
 #define Anum_pg_constraint_connamespace        2
 #define Anum_pg_constraint_contype         3
@@ -165,14 +168,15 @@ typedef FormData_pg_constraint *Form_pg_constraint;
 #define Anum_pg_constraint_confmatchtype   13
 #define Anum_pg_constraint_conislocal      14
 #define Anum_pg_constraint_coninhcount     15
-#define Anum_pg_constraint_conkey          16
-#define Anum_pg_constraint_confkey         17
-#define Anum_pg_constraint_conpfeqop       18
-#define Anum_pg_constraint_conppeqop       19
-#define Anum_pg_constraint_conffeqop       20
-#define Anum_pg_constraint_conexclop       21
-#define Anum_pg_constraint_conbin          22
-#define Anum_pg_constraint_consrc          23
+#define Anum_pg_constraint_conisonly       16
+#define Anum_pg_constraint_conkey          17
+#define Anum_pg_constraint_confkey         18
+#define Anum_pg_constraint_conpfeqop       19
+#define Anum_pg_constraint_conppeqop       20
+#define Anum_pg_constraint_conffeqop       21
+#define Anum_pg_constraint_conexclop       22
+#define Anum_pg_constraint_conbin          23
+#define Anum_pg_constraint_consrc          24
 
 
 /* Valid values for contype */
@@ -227,7 +231,8 @@ extern Oid CreateConstraintEntry(const char *constraintName,
                      const char *conBin,
                      const char *conSrc,
                      bool conIsLocal,
-                     int conInhCount);
+                     int conInhCount,
+                     bool conIsOnly);
 
 extern void RemoveConstraintById(Oid conId);
 extern void RenameConstraintById(Oid conId, const char *newname);
index 87432a8538108dd3571c4267fc04e79fbe0a5ad5..57096f230ebadac358a9d5058ff452b04d8b4431 100644 (file)
@@ -498,22 +498,21 @@ select test2 from atacc2;
 drop table atacc2 cascade;
 NOTICE:  drop cascades to table atacc3
 drop table atacc1;
--- adding only to a parent is disallowed as of 8.4
+-- adding only to a parent is allowed as of 9.2
 create table atacc1 (test int);
 create table atacc2 (test2 int) inherits (atacc1);
--- fail:
-alter table only atacc1 add constraint foo check (test>0);
-ERROR:  constraint must be added to child tables too
 -- ok:
-alter table only atacc2 add constraint foo check (test>0);
--- check constraint not there on parent
+alter table only atacc1 add constraint foo check (test>0);
+-- check constraint is not there on child
+insert into atacc2 (test) values (-3);
+-- check constraint is there on parent
 insert into atacc1 (test) values (-3);
+ERROR:  new row for relation "atacc1" violates check constraint "foo"
+DETAIL:  Failing row contains (-3).
 insert into atacc1 (test) values (3);
--- check constraint is there on child
-insert into atacc2 (test) values (-3);
-ERROR:  new row for relation "atacc2" violates check constraint "foo"
-DETAIL:  Failing row contains (-3, null).
-insert into atacc2 (test) values (3);
+-- fail, violating row:
+alter table only atacc2 add constraint foo check (test>0);
+ERROR:  check constraint "foo" is violated by some row
 drop table atacc2;
 drop table atacc1;
 -- test unique constraint adding
index d958da2652682b8bc9a5dc087b2a4ebcef9337a4..309c1db425eb183ccc6b660e81ac98eb84bf4317 100644 (file)
@@ -683,6 +683,41 @@ select * from d;
  32 | one | two | three
 (1 row)
 
+-- Test non-inheritable parent constraints
+create table p1(ff1 int);
+alter table only p1 add constraint p1chk check (ff1 > 0);
+alter table p1 add constraint p2chk check (ff1 > 10);
+-- conisonly should be true for ONLY constraint
+select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.conisonly from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname = 'p1';
+ relname | conname | contype | conislocal | coninhcount | conisonly 
+---------+---------+---------+------------+-------------+-----------
+ p1      | p1chk   | c       | t          |           0 | t
+ p1      | p2chk   | c       | t          |           0 | f
+(2 rows)
+
+-- Test that child does not inherit ONLY constraints
+create table c1 () inherits (p1);
+\d p1
+      Table "public.p1"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ ff1    | integer | 
+Check constraints:
+    "p1chk" (ONLY) CHECK (ff1 > 0)
+    "p2chk" CHECK (ff1 > 10)
+Number of child tables: 1 (Use \d+ to list them.)
+
+\d c1
+      Table "public.c1"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ ff1    | integer | 
+Check constraints:
+    "p2chk" CHECK (ff1 > 10)
+Inherits: p1
+
+drop table p1 cascade;
+NOTICE:  drop cascades to table c1
 -- Tests for casting between the rowtypes of parent and child
 -- tables. See the pgsql-hackers thread beginning Dec. 4/04
 create table base (i integer);
index a477f0401b4651de7fdad6cef10c5bd55abf2830..faafb224ef43f62e5e61fcfafe1f2af24ffbe04b 100644 (file)
@@ -450,20 +450,19 @@ select test2 from atacc2;
 drop table atacc2 cascade;
 drop table atacc1;
 
--- adding only to a parent is disallowed as of 8.4
+-- adding only to a parent is allowed as of 9.2
 
 create table atacc1 (test int);
 create table atacc2 (test2 int) inherits (atacc1);
--- fail:
-alter table only atacc1 add constraint foo check (test>0);
 -- ok:
-alter table only atacc2 add constraint foo check (test>0);
--- check constraint not there on parent
+alter table only atacc1 add constraint foo check (test>0);
+-- check constraint is not there on child
+insert into atacc2 (test) values (-3);
+-- check constraint is there on parent
 insert into atacc1 (test) values (-3);
 insert into atacc1 (test) values (3);
--- check constraint is there on child
-insert into atacc2 (test) values (-3);
-insert into atacc2 (test) values (3);
+-- fail, violating row:
+alter table only atacc2 add constraint foo check (test>0);
 drop table atacc2;
 drop table atacc1;
 
index deda5a519ef6d7b6f678bb7b952bfbd6478b2c96..6914404023667476aaa96a65b61d448aa4bb401e 100644 (file)
@@ -188,6 +188,20 @@ insert into d values('test','one','two','three');
 alter table a alter column aa type integer using bit_length(aa);
 select * from d;
 
+-- Test non-inheritable parent constraints
+create table p1(ff1 int);
+alter table only p1 add constraint p1chk check (ff1 > 0);
+alter table p1 add constraint p2chk check (ff1 > 10);
+-- conisonly should be true for ONLY constraint
+select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pgc.conisonly from pg_class as pc inner join pg_constraint as pgc on (pgc.conrelid = pc.oid) where pc.relname = 'p1';
+
+-- Test that child does not inherit ONLY constraints
+create table c1 () inherits (p1);
+\d p1
+\d c1
+
+drop table p1 cascade;
+
 -- Tests for casting between the rowtypes of parent and child
 -- tables. See the pgsql-hackers thread beginning Dec. 4/04
 create table base (i integer);