From 61d81bd28dbec65a6b144e0cd3d0bfe25913c3ac Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Mon, 5 Dec 2011 15:10:18 -0300 Subject: [PATCH] Allow CHECK constraints to be declared ONLY 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 --- doc/src/sgml/catalogs.sgml | 10 +++++ doc/src/sgml/ref/alter_table.sgml | 8 ++++ src/backend/catalog/heap.c | 28 +++++++++---- src/backend/catalog/index.c | 3 +- src/backend/catalog/pg_constraint.c | 4 +- src/backend/commands/tablecmds.c | 50 ++++++++++++++++------- src/backend/commands/trigger.c | 3 +- src/backend/commands/typecmds.c | 3 +- src/backend/utils/cache/relcache.c | 1 + src/bin/pg_dump/pg_dump.c | 44 +++++++++++++------- src/bin/pg_dump/pg_dump.h | 1 + src/bin/psql/describe.c | 19 ++++++--- src/include/access/tupdesc.h | 1 + src/include/catalog/heap.h | 4 +- src/include/catalog/pg_constraint.h | 25 +++++++----- src/test/regress/expected/alter_table.out | 21 +++++----- src/test/regress/expected/inherit.out | 35 ++++++++++++++++ src/test/regress/sql/alter_table.sql | 15 ++++--- src/test/regress/sql/inherit.sql | 14 +++++++ 19 files changed, 211 insertions(+), 78 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index d948ed487c..be4bbc736c 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -2036,6 +2036,16 @@ + + conisonly + bool + + + This constraint is defined locally for the relation. It is a + non-inheritable constraint. + + + conkey int2[] diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 00a477ef88..3b111a4c2b 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -983,6 +983,14 @@ ALTER TABLE distributors ADD CONSTRAINT zipchk CHECK (char_length(zipcode) = 5); + + To add a check constraint only to a table and not to its children: + +ALTER TABLE ONLY distributors ADD CONSTRAINT zipchk CHECK (char_length(zipcode) = 5); + + (The check constraint will not be inherited by future children, either.) + + To remove a check constraint from a table and all its children: diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index e11d896ec8..2f6a6ffba1 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -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; diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 758872ff4e..f9075c4752 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -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. diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 69979942af..cfe82ea3a8 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -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); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 135736a8dc..00b6cb9d50 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -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; diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index f4c93e5b25..eb5114079a 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -449,7 +449,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, NULL, NULL, true, /* islocal */ - 0); /* inhcount */ + 0, /* inhcount */ + false); /* isonly */ } /* diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 2b8f9aec38..eda43d826f 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -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 diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 603e4c1b62..f9ad75e7f8 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -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)); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 7dfa1dd8f8..5deb9d658d 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -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); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 31442f1622..3bfeb317d6 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -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; diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index dcafdd2c1a..b6aeae22e5 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -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); } diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h index 8b99cb849d..d5e1333d03 100644 --- a/src/include/access/tupdesc.h +++ b/src/include/access/tupdesc.h @@ -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 */ diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index aee2d88ebe..07938137de 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -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); diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h index 1566af2973..dae42e8e5f 100644 --- a/src/include/catalog/pg_constraint.h +++ b/src/include/catalog/pg_constraint.h @@ -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); diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 87432a8538..57096f230e 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -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 diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index d958da2652..309c1db425 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -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); diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index a477f0401b..faafb224ef 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -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; diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql index deda5a519e..6914404023 100644 --- a/src/test/regress/sql/inherit.sql +++ b/src/test/regress/sql/inherit.sql @@ -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); -- 2.40.0