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 -insert into atacc1 (test) values (-3); -insert into atacc1 (test) values (3); --- check constraint is there on child +alter table only atacc1 add constraint foo check (test>0); +-- check constraint is not 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); +-- 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); +-- 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);