</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>
</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>
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,
*/
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;
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);
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:
List *newColDefaults,
List *newConstraints,
bool allow_merge,
- bool is_local)
+ bool is_local,
+ bool is_only)
{
List *cookedConstraints = NIL;
TupleDesc tupleDesc;
cooked->skip_validation = false;
cooked->is_local = is_local;
cooked->inhcount = is_local ? 0 : 1;
+ cooked->is_only = is_only;
cookedConstraints = lappend(cookedConstraints, cooked);
}
* what ATAddCheckConstraint wants.)
*/
if (MergeWithExistingConstraint(rel, ccname, expr,
- allow_merge, is_local))
+ allow_merge, is_local, is_only))
continue;
}
else
* OK, store it.
*/
StoreRelCheck(rel, ccname, expr, !cdef->skip_validation, is_local,
- is_local ? 0 : 1);
+ is_local ? 0 : 1, is_only);
numchecks++;
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);
}
*/
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;
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;
NULL,
NULL,
true, /* islocal */
- 0); /* inhcount */
+ 0, /* inhcount */
+ false); /* isonly */
/*
* Register the index as internally dependent on the constraint.
const char *conBin,
const char *conSrc,
bool conIsLocal,
- int conInhCount)
+ int conInhCount,
+ bool conIsOnly)
{
Relation conDesc;
Oid conOid;
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);
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;
}
*/
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
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);
cooked->skip_validation = false;
cooked->is_local = false;
cooked->inhcount = 1;
+ cooked->is_only = false;
constraints = lappend(constraints, cooked);
}
}
* 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();
* 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);
}
}
* 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)
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
*/
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);
NULL,
NULL,
true, /* islocal */
- 0); /* inhcount */
+ 0, /* inhcount */
+ false); /* isonly */
/*
* Create the triggers that will enforce the constraint.
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)
/* 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
performDeletion(&conobj, behavior);
found = true;
+
+ /* constraint found and dropped -- no need to keep looping */
+ break;
}
systable_endscan(scan);
* 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;
NULL,
NULL,
true, /* islocal */
- 0); /* inhcount */
+ 0, /* inhcount */
+ false); /* isonly */
}
/*
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
RelationGetRelationName(relation));
check[found].ccvalid = conform->convalidated;
+ check[found].cconly = conform->conisonly;
check[found].ccname = MemoryContextStrdup(CacheMemoryContext,
NameStr(conform->conname));
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' "
{
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' "
{
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' "
/* 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' "
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",
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",
"(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",
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));
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;
* 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);
/* 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);
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;
/* 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;
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);
}
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 */
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,
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);
/* 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.
*/
* 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
#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 */
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);
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
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);
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;
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);