summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/src/sgml/catalogs.sgml21
-rw-r--r--src/backend/catalog/heap.c44
-rw-r--r--src/backend/commands/tablecmds.c1321
-rw-r--r--src/backend/commands/typecmds.c175
-rw-r--r--src/backend/parser/parse_utilcmd.c28
-rw-r--r--src/backend/utils/adt/ruleutils.c44
-rw-r--r--src/include/catalog/heap.h4
-rw-r--r--src/include/catalog/pg_constraint.h1
-rw-r--r--src/include/nodes/parsenodes.h2
-rw-r--r--src/test/regress/expected/alter_table.out16
-rw-r--r--src/test/regress/expected/cluster.out7
-rw-r--r--src/test/regress/expected/domain.out17
-rw-r--r--src/test/regress/expected/inherit.out303
-rw-r--r--src/test/regress/sql/domain.sql10
-rw-r--r--src/test/regress/sql/inherit.sql137
15 files changed, 2016 insertions, 114 deletions
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 8504555bac..d6c28c8de3 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1820,11 +1820,13 @@
<para>
The catalog <structname>pg_constraint</structname> stores check, primary
- key, unique, foreign key, and exclusion constraints on tables.
+ key, unique, foreign key, exclusion constraints and additional information
+ of not-null constraints on tables.
(Column constraints are not treated specially. Every column constraint is
equivalent to some table constraint.)
- Not-null constraints are represented in the <structname>pg_attribute</>
- catalog, not here.
+ Not-null constraints are primarily represented in the <structname>pg_attribute</>
+ catalog, but each attribute which has a not-null constraint in <structname>pg_attribute</>
+ has additional inheritance information stored in <structname>pg_constraint</>.
</para>
<para>
@@ -1873,6 +1875,7 @@
<entry>
<literal>c</> = check constraint,
<literal>f</> = foreign key constraint,
+ <literal>n</> = not-null constraint
<literal>p</> = primary key constraint,
<literal>u</> = unique constraint,
<literal>t</> = constraint trigger,
@@ -1993,7 +1996,8 @@
<entry><type>int2[]</type></entry>
<entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</></entry>
<entry>If a table constraint (including foreign keys, but not constraint
- triggers), list of the constrained columns</entry>
+ triggers), list of the constrained columns. This column is used to store
+ the referenced attribute number of a not-null constraint, too</entry>
</row>
<row>
@@ -2058,6 +2062,15 @@
index.)
</para>
+ <para>
+ Not-null constraints are used to store inheritance information for each not-null
+ constraint attached to a column. They shouldn't be confused with check
+ constraints, since they are handled differently. If a column in <structname>pg_attribute</>
+ has its <structfield>attnotnull</> set, there must be a matching not-null constraint
+ in <structname>pg_constraint</> with the attribute number of the column stored in
+ <structfield>conkey</>. There is always only one attribute number stored at the same time.
+ </para>
+
<note>
<para>
<structfield>consrc</structfield> is not updated when referenced objects
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a6e541d858..281887b586 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1838,6 +1838,47 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, Node *expr)
}
/*
+ * Stores a NOT NULL constraint of a column into pg_constraint.
+ */
+void
+StoreColumnNotNullConstraint(Relation rel, CookedConstraint *cooked)
+{
+
+ /*
+ * Store the constraint. Reflect conislocal and coninhcount to
+ * match the same values as the attached column.
+ */
+ CreateConstraintEntry(cooked->name,
+ RelationGetNamespace(rel),
+ CONSTRAINT_NOTNULL,
+ false,
+ false,
+ true,
+ RelationGetRelid(rel),
+ &(cooked->attnum),
+ 1,
+ InvalidOid,
+ InvalidOid,
+ InvalidOid,
+ NULL,
+ InvalidOid,
+ InvalidOid,
+ InvalidOid,
+ 0,
+ ' ',
+ ' ',
+ ' ',
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ cooked->is_local,
+ cooked->inhcount
+ );
+
+}
+
+/*
* Store a check-constraint expression for the given relation.
*
* Caller is responsible for updating the count of constraints
@@ -1962,6 +2003,9 @@ StoreConstraints(Relation rel, List *cooked_constraints)
switch (con->contype)
{
+ case CONSTR_NOTNULL:
+ StoreColumnNotNullConstraint(rel, con);
+ break;
case CONSTR_DEFAULT:
StoreAttrDefault(rel, con->attnum, con->expr);
break;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2c9f855f53..12baca1e23 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -182,6 +182,27 @@ typedef struct NewColumnValue
} NewColumnValue;
/*
+ * Struct describing the constraint information
+ * to de-inherit. Currently CHECK and NOT NULL constraints
+ * are carried by ATExecDropInherit() within these struct.
+ */
+typedef struct DeinheritConstraintInfo
+{
+ char contype; /* constraint type */
+ AttrNumber attnum; /* if NOT NULL constraint, the attnum */
+ char *conname; /* if CHECK constraint, the constraint name */
+} DeinheritConstraintInfo;
+
+/*
+ * Struct carrying information about inherited NOT NULL constraint.
+ */
+typedef struct InheritedColumnNotNull
+{
+ AttrNumber attnum;
+ bool attnotnull;
+} InheritedColumnNotNull;
+
+/*
* Error-reporting support for RemoveRelations
*/
struct dropmsgstrings
@@ -243,11 +264,13 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ List **supOids, List **supconstr, int *supOidCount);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static bool change_varattnos_walker(Node *node, const AttrNumber *newattno);
-static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
-static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
+static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel,
+ List **child_attnums);
+static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel,
+ List *child_attnums);
static void StoreCatalogInheritance(Oid relationId, List *supers);
static void StoreCatalogInheritance1(Oid relationId, Oid parentOid,
int16 seqNumber, Relation inhRelation);
@@ -299,10 +322,20 @@ static void ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
- AlterTableCmd *cmd, LOCKMODE lockmode);
-static void ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
-static void ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
- const char *colName, LOCKMODE lockmode);
+ AlterTableCmd *cmd, LOCKMODE lockmode);
+static void ATExecDropNotNull(Relation rel, const char *colName, DropBehavior behavior,
+ bool recurse, bool recursing, LOCKMODE lockmode);
+static void ATExecSetNotNull(List **wqueue, AlteredTableInfo *tab, Relation rel,
+ const char *colName, bool recurse, bool recursing,
+ bool is_new_constraint, LOCKMODE lockmode);
+static void ATExecSetNotNullInternal(Relation rel, Relation attr_rel,
+ HeapTuple atttup, bool *is_new_constraint,
+ bool allow_new_constraint, bool is_local);
+static void
+ATExecDropNotNullInternal(Relation rel, Relation attr_rel,
+ DropBehavior behavior,
+ HeapTuple atttup, bool recurse,
+ bool recursing);
static void ATExecColumnDefault(Relation rel, const char *colName,
Node *newDefault, LOCKMODE lockmode);
static void ATPrepSetStatistics(Relation rel, const char *colName,
@@ -368,7 +401,16 @@ static void ATExecGenericOptions(Relation rel, List *options);
static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
ForkNumber forkNum, char relpersistence);
static const char *storage_name(char c);
-
+static bool
+CheckNotNullOnAttributeName(Relation rel, const char *colname,
+ AttrNumber *attnum);
+static void
+DropNotNullOnAttributeNum(Relation rel, AttrNumber attnum,
+ DropBehavior behavior, bool lock);
+static bool
+MergeNotNullConstraint(List **constraints, List *nnconstraints, AttrNumber attno);
+static void
+GetRelationNNConstraintList(List **nnconstraints, Oid relOid);
/* ----------------------------------------------------------------
* DefineRelation
@@ -399,6 +441,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
TupleDesc descriptor;
List *inheritOids;
List *old_constraints;
+ List *oldDefaultsConstraints;
+ List *nnconstraints;
bool localHasOids;
int parentOidCount;
List *rawDefaults;
@@ -526,14 +570,67 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
*/
rawDefaults = NIL;
cookedDefaults = NIL;
+ nnconstraints = NIL;
attnum = 0;
foreach(listptr, schema)
{
ColumnDef *colDef = lfirst(listptr);
-
attnum++;
+ if (colDef->is_not_null)
+ {
+ /*
+ * Check list of explicit defined NOT NULL constraints for the
+ * current column name. There should be only one Constraint node,
+ * so we give up immediately when we've found a constraint.
+ */
+
+ ListCell *nnconstrptr;
+ foreach(nnconstrptr, colDef->constraints)
+ {
+ CookedConstraint *cooked;
+ Constraint *nnconstr;
+
+ nnconstr = (Constraint *) lfirst(nnconstrptr);
+ cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
+
+ if (nnconstr->contype != CONSTR_NOTNULL)
+ continue;
+
+ /*
+ * Cook the constraint
+ */
+ cooked->contype = CONSTR_NOTNULL;
+ cooked->attnum = attnum;
+ cooked->expr = NULL;
+ cooked->is_local = true;
+ cooked->inhcount = 0;
+ nnconstraints = lappend(nnconstraints, cooked);
+
+ /*
+ * Keep user defined constraint identifier
+ */
+ if (nnconstr->conname != NULL)
+ {
+ cooked->name = pstrdup(nnconstr->conname);
+ }
+ else
+ {
+ cooked->name = ChooseConstraintName(relname,
+ NameStr(descriptor->attrs[attnum - 1]->attname),
+ "not_null", namespaceId, NIL);
+ }
+
+ /*
+ * We expect only one constraint to be created, so abort the
+ * loop as soon we've done.
+ */
+ break;
+ }
+
+ }
+
if (colDef->raw_default != NULL)
{
RawColumnDefault *rawEnt;
@@ -568,6 +665,15 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
}
/*
+ * Concat raw defaults, cooked defaults and NOT NULL constraints,
+ * so that we can pass 'em over to heap_create_with_catalog() together.
+ */
+ oldDefaultsConstraints = list_concat(cookedDefaults,
+ old_constraints);
+ oldDefaultsConstraints = list_concat(oldDefaultsConstraints,
+ nnconstraints);
+
+ /*
* Create the relation. Inherited defaults and constraints are passed in
* for immediate handling --- since they don't need parsing, they can be
* stored immediately.
@@ -580,8 +686,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
ofTypeId,
ownerId,
descriptor,
- list_concat(cookedDefaults,
- old_constraints),
+ oldDefaultsConstraints,
relkind,
stmt->relation->relpersistence,
false,
@@ -1192,7 +1297,8 @@ storage_name(char c)
* Output arguments:
* 'supOids' receives a list of the OIDs of the parent relations.
* 'supconstr' receives a list of constraints belonging to the parents,
- * updated as necessary to be valid for the child.
+ * updated as necessary to be valid for the child. This includes
+ * inherited CHECK and NOT NULL constraints.
* 'supOidCount' is set to the number of parents that have OID columns.
*
* Return value:
@@ -1335,8 +1441,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
Relation relation;
TupleDesc tupleDesc;
TupleConstr *constr;
- AttrNumber *newattno;
+ AttrNumber *newattno;
+ InheritedColumnNotNull *inhAttrNotNull;
AttrNumber parent_attno;
+ List *nnconstraints;
+
+ nnconstraints = NIL;
/*
* A self-exclusive lock is needed here. If two backends attempt to
@@ -1393,6 +1503,10 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
newattno = (AttrNumber *)
palloc(tupleDesc->natts * sizeof(AttrNumber));
+ inhAttrNotNull =
+ (InheritedColumnNotNull *)palloc(tupleDesc->natts
+ * sizeof(InheritedColumnNotNull));
+
for (parent_attno = 1; parent_attno <= tupleDesc->natts;
parent_attno++)
{
@@ -1412,6 +1526,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* out.
*/
newattno[parent_attno - 1] = 0;
+ inhAttrNotNull[parent_attno - 1].attnum = 0;
+ inhAttrNotNull[parent_attno - 1].attnotnull = false;
continue;
}
@@ -1470,6 +1586,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
def->is_not_null |= attribute->attnotnull;
/* Default and other constraints are handled below */
newattno[parent_attno - 1] = exist_attno;
+
+ inhAttrNotNull[parent_attno - 1].attnum = exist_attno;
+ inhAttrNotNull[parent_attno - 1].attnotnull = def->is_not_null;
}
else
{
@@ -1492,6 +1611,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
def->constraints = NIL;
inhSchema = lappend(inhSchema, def);
newattno[parent_attno - 1] = ++child_attno;
+
+ /*
+ * Also record the new inherited column in the
+ * not null constraint list.
+ */
+ inhAttrNotNull[parent_attno - 1].attnum = newattno[parent_attno - 1];
+ inhAttrNotNull[parent_attno - 1].attnotnull = def->is_not_null;
}
/*
@@ -1574,7 +1700,52 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
}
}
+ /*
+ * Build a list of all not null constraints of the
+ * given parent relation. We need them to identify and record
+ * any inheritance on not null constraints later.
+ */
+ GetRelationNNConstraintList(&nnconstraints, RelationGetRelid(relation));
+
+ /*
+ * We need to adjust any existing not null constraint in the list
+ * to match the new attribute number in the child table.
+ * Then loop through the not null constraint list and check for
+ * any constraint to be created or inherited.
+ */
+ if ((nnconstraints != NIL) && list_length(nnconstraints) > 0)
+ {
+ ListCell *nncon;
+ CookedConstraint *constr;
+ int i;
+
+ foreach(nncon, nnconstraints)
+ {
+ constr = (CookedConstraint *) lfirst(nncon);
+ constr->attnum = newattno[constr->attnum - 1];
+ constr->is_local = false;
+ constr->inhcount = 1;
+ }
+
+ for (i = 1; i <= tupleDesc->natts; i++)
+ {
+ /*
+ * Skip any column marked nullable.
+ */
+ if (!inhAttrNotNull[i - 1].attnotnull)
+ continue;
+
+ /*
+ * MergeNotNullConstraint() keeps track of the inheritance
+ * counter if any inherited not null constraint exists
+ */
+ MergeNotNullConstraint(&constraints, nnconstraints, newattno[i - 1]);
+
+ }
+ }
+
pfree(newattno);
+ pfree(inhAttrNotNull);
/*
* Close the parent rel, but keep our AccessShareLock on it until xact
@@ -1654,7 +1825,34 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/* Mark the column as locally defined */
def->is_local = true;
+
/* Merge of NOT NULL constraints = OR 'em together */
+ if (!def->is_not_null && newdef->is_not_null)
+ {
+ ListCell *constrCell;
+
+ /*
+ * There must be a locally defined Constraint node attached
+ * to this column definition. We need to inject it to the
+ * list of local constraints, at least to keep a possible
+ * assigned constraint name. We do this by adding the
+ * Constraint node to the column's own list of constraints.
+ * Such constraints are treated as locally defined and will
+ * be handled later when the target table get's created in
+ * DefineRelation().
+ */
+ foreach (constrCell, newdef->constraints)
+ {
+ Constraint *newnnconstr = lfirst(constrCell);
+ if (newnnconstr->contype == CONSTR_NOTNULL)
+ {
+ def->constraints = lappend(def->constraints,
+ newnnconstr);
+ break;
+ }
+ }
+ }
+
def->is_not_null |= newdef->is_not_null;
/* If new def has a default, override previous default */
if (newdef->raw_default != NULL)
@@ -1754,6 +1952,145 @@ MergeCheckConstraint(List *constraints, char *name, Node *expr)
return false;
}
+/*
+ * MergeNotNullConstraint
+ *
+ * Merge existing not null constraints identified by the given
+ * attribute number. This will adjust the inheritance counter of
+ * an existing not null constraint given in the nnconstraints list
+ * if the specified attribute number matches a constraint found in
+ * nnconstraints.
+ */
+static bool
+MergeNotNullConstraint(List **constraints, List *nnconstraints, AttrNumber attno)
+{
+ ListCell *lc;
+ bool found;
+
+ found = false;
+
+ foreach(lc, nnconstraints)
+ {
+ CookedConstraint *child_nncon;
+ ListCell *merge_lc;
+
+ child_nncon = (CookedConstraint *) lfirst(lc);
+
+ /*
+ * Paranoid check, we don't expect any other constraint
+ * in the nnconstraint list here.
+ */
+ Assert(child_nncon->contype == CONSTR_NOTNULL);
+
+ /*
+ * Scan the list for the given attribute number. If found, check
+ * wether its already assigned to the final constraint list. If true,
+ * just bump up its inheritance counter, otherwise we need to
+ * save the new cooked constraint.
+ */
+ if (child_nncon->attnum != attno)
+ continue;
+
+ foreach(merge_lc, *constraints)
+ {
+ CookedConstraint *parent_nncon;
+
+ parent_nncon = (CookedConstraint *) lfirst(merge_lc);
+
+ if (parent_nncon->contype != CONSTR_NOTNULL)
+ continue;
+
+ if (parent_nncon->attnum != attno)
+ continue;
+
+ /*
+ * This not null constraint was already assigned from another parent.
+ * Bump its inheritance counter.
+ */
+ parent_nncon->inhcount++;
+ found = true;
+ }
+
+ if (!found)
+ {
+ /*
+ * Constraint wasn't already merged. Assign it to the
+ * constraints list.
+ */
+ *constraints = lappend(*constraints, child_nncon);
+ }
+
+ break;
+ }
+
+ return found;
+}
+
+/*
+ * GetRelationNNConstraintList
+ */
+static void
+GetRelationNNConstraintList(List **nnconstraints, Oid relOid)
+{
+ HeapTuple constr_tup;
+ Relation constr_rel;
+ SysScanDesc scan;
+ ScanKeyData key;
+
+ /*
+ * Scan through the pg_constraint tuples belonging to relOid,
+ * looking for CONSTRAINT_NOTNULL constraints. We build then a list
+ * of CookedConstraints for all matching not null constraints.
+ */
+
+ constr_rel = heap_open(ConstraintRelationId, RowExclusiveLock);
+ ScanKeyInit(&key,
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relOid));
+ scan = systable_beginscan(constr_rel, ConstraintRelidIndexId,
+ true, SnapshotNow, 1, &key);
+
+ while (HeapTupleIsValid(constr_tup = systable_getnext(scan)))
+ {
+ Form_pg_constraint constr;
+ CookedConstraint *cooked;
+ Datum arrayp;
+ Datum *keyvals;
+ bool isnull;
+ int nelems;
+
+ constr = (Form_pg_constraint) GETSTRUCT(constr_tup);
+
+ if (constr->contype != CONSTRAINT_NOTNULL)
+ continue;
+
+ cooked = (CookedConstraint *)palloc(sizeof(CookedConstraint));
+ cooked->contype = CONSTR_NOTNULL;
+ cooked->name = pstrdup(NameStr(constr->conname));
+
+ /*
+ * A not null constraint tuple is expected to have one
+ * and only one assigned attribute number
+ */
+ arrayp = SysCacheGetAttr(CONSTROID, constr_tup,
+ Anum_pg_constraint_conkey,
+ &isnull);
+ Assert(!isnull);
+
+ deconstruct_array(DatumGetArrayTypeP(arrayp), INT2OID, 2, true,
+ 's', &keyvals, NULL, &nelems);
+ Assert(nelems == 1);
+ cooked->attnum = keyvals[0];
+ cooked->is_local = constr->conislocal;
+ cooked->inhcount = constr->coninhcount;
+
+ *nnconstraints = lappend(*nnconstraints, cooked);
+ }
+
+ systable_endscan(scan);
+ heap_close(constr_rel, NoLock);
+}
/*
* Replace varattno values in an expression tree according to the given
@@ -2809,14 +3146,22 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
+
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
- ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+
+ if (recurse)
+ cmd->subtype = AT_DropNotNullRecurse;
+
/* No command-specific prep needed */
pass = AT_PASS_DROP;
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
+
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
- ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+
+ if (recurse)
+ cmd->subtype = AT_SetNotNullRecurse;
+
/* No command-specific prep needed */
pass = AT_PASS_ADD_CONSTR;
break;
@@ -3055,10 +3400,20 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
- ATExecDropNotNull(rel, cmd->name, lockmode);
+ ATExecDropNotNull(rel, cmd->name, cmd->behavior,
+ false, false, lockmode);
+ break;
+ case AT_DropNotNullRecurse: /* ALTER COLUMN DROP NOT NULL with recursion */
+ ATExecDropNotNull(rel, cmd->name, cmd->behavior,
+ true, false, lockmode);
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
- ATExecSetNotNull(tab, rel, cmd->name, lockmode);
+ ATExecSetNotNull(wqueue, tab, rel, cmd->name, false,
+ false, false, lockmode);
+ break;
+ case AT_SetNotNullRecurse: /* ALTER COLUMN SET NOT NULL recursing */
+ ATExecSetNotNull(wqueue, tab, rel, cmd->name, true,
+ false, false, lockmode);
break;
case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
@@ -3643,8 +3998,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
if (heap_attisnull(tuple, attn + 1))
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
- errmsg("column \"%s\" contains null values",
- NameStr(newTupDesc->attrs[attn]->attname))));
+ errmsg("column \"%s\" of relation \"%s\" contains null values",
+ NameStr(newTupDesc->attrs[attn]->attname),
+ RelationGetRelationName(oldrel))));
}
foreach(l, tab->constraints)
@@ -4148,6 +4504,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
Oid collOid;
Form_pg_type tform;
Expr *defval;
+ bool processDefaults;
+ bool processnnConstr;
List *children;
ListCell *child;
@@ -4174,6 +4532,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple);
Oid ctypeId;
int32 ctypmod;
+ bool new_constraint;
Oid ccollid;
/* Child column must match on type, typmod, and collation */
@@ -4206,6 +4565,22 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
simple_heap_update(attrdesc, &tuple->t_self, tuple);
CatalogUpdateIndexes(attrdesc, tuple);
+ /*
+ * We need a CCI here
+ */
+ CommandCounterIncrement();
+
+ /*
+ * We shouldn't forget to adjust any NOT NULL constraints. Take care wether
+ * we have to create a new NOT NULL constraint (the existing attribute
+ * doesn't have a NOT NULL constraint attached already) or to adjust an
+ * existing with new inheritance information. Since we merge with
+ * an existing attribute, we need to supress the creation of an extra
+ * constraint tuple.
+ */
+ ATExecSetNotNullInternal(rel, attrdesc, tuple, &new_constraint,
+ colDef->is_not_null, false);
+
heap_freetuple(tuple);
/* Inform the user about the merge */
@@ -4311,6 +4686,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
/* Make the attribute's catalog entry visible */
CommandCounterIncrement();
+ processDefaults = false;
+ processnnConstr = false;
+
/*
* Store the DEFAULT, if any, in the catalogs
*/
@@ -4333,11 +4711,70 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
*/
AddRelationNewConstraints(rel, list_make1(rawEnt), NIL, false, true);
- /* Make the additional catalog changes visible */
- CommandCounterIncrement();
+ /*
+ * We need to make changes visible, but defer it until
+ * we have processed NOT NULL constraints below.
+ */
+ processDefaults = true;
+ }
+
+ /*
+ * Add NOT NULL constraint to the list of constraint
+ * nodes. We need to get the transformed Constraint expression
+ * from the current node, if we want to keep a user defined
+ * constraint identifier. We can't use ATExecSetNotNullInternal()
+ * directly, since it generates *always* a constraint name.
+ */
+ if (colDef->is_not_null)
+ {
+
+ ListCell *nnconstrptr;
+ foreach(nnconstrptr, colDef->constraints)
+ {
+ CookedConstraint *cooked;
+ Constraint *nnconstr;
+
+ nnconstr = (Constraint *) lfirst(nnconstrptr);
+ if (nnconstr->contype != CONSTR_NOTNULL)
+ continue;
+
+ cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
+
+ cooked->contype = CONSTR_NOTNULL;
+ cooked->attnum = attribute.attnum;
+ cooked->expr = NULL;
+ cooked->is_local = colDef->is_local;
+ cooked->inhcount = colDef->inhcount;
+
+ /*
+ * Keep user defined constraint identifier
+ */
+ if (nnconstr->conname != NULL)
+ {
+ cooked->name = pstrdup(nnconstr->conname);
+ }
+ else
+ {
+ cooked->name = ChooseConstraintName(RelationGetRelationName(rel),
+ NameStr(attribute.attname),
+ "not_null",
+ RelationGetNamespace(rel),
+ NIL);
+ }
+
+ StoreColumnNotNullConstraint(rel, cooked);
+ processnnConstr = true;
+ break;
+ }
}
/*
+ * Make new defaults and NOT NULL constraints visible.
+ */
+ if (processDefaults || processnnConstr)
+ CommandCounterIncrement();
+
+ /*
* Tell Phase 3 to fill in the default expression, if there is one.
*
* If there is no default, Phase 3 doesn't have to do anything, because
@@ -4543,13 +4980,30 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
* ALTER TABLE ALTER COLUMN DROP NOT NULL
*/
static void
-ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
+ATExecDropNotNull(Relation rel, const char *colName, DropBehavior behavior,
+ bool recurse, bool recursing, LOCKMODE lockmode)
{
- HeapTuple tuple;
+ List *children;
+ ListCell *child;
+ HeapTuple tuple;
AttrNumber attnum;
- Relation attr_rel;
- List *indexoidlist;
- ListCell *indexoidscan;
+ Relation attr_rel;
+
+ children = NIL;
+
+ if (recursing)
+ ATSimplePermissions(rel, ATT_TABLE|ATT_FOREIGN_TABLE);
+
+ children = find_inheritance_children(RelationGetRelid(rel),
+ AccessExclusiveLock);
+
+ /*
+ * Lookup the attribute. This also checks for a dropped column
+ * or any attempts to alter a system column. Returns false in case
+ * no further work needs to be done (e.g. no NOT NULL present).
+ */
+ if (!CheckNotNullOnAttributeName(rel, colName, &attnum))
+ return;
/*
* lookup the attribute
@@ -4570,8 +5024,288 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot alter system column \"%s\"",
- colName)));
+ errmsg("cannot alter system column \"%s\" of relation \"%s\"",
+ colName, RelationGetRelationName(rel))));
+
+ /*
+ * Treat it an error if there's an attempt to remove
+ * a NOT NULL constraint from a table but not from
+ * existing child tables (thus, ALTER TABLE ONLY...DROP NOT NULL
+ * was specified to a table inherited from others).
+ */
+ if (!recurse)
+ {
+ if (children)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("NOT NULL constraint must be removed from child tables too")));
+ }
+
+ /*
+ * Everything looks okay, so perform the deletion on the parent.
+ */
+ ATExecDropNotNullInternal(rel, attr_rel, behavior, tuple,
+ recurse, recursing);
+ //DropNotNullOnAttributeNum(rel, attnum, behavior, false);
+
+ heap_freetuple(tuple);
+ heap_close(attr_rel, NoLock);
+
+ /*
+ * Perform work on all children, if any
+ */
+ foreach(child, children)
+ {
+ Oid childrelid = lfirst_oid(child);
+ Relation childrel;
+
+ attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
+ childrel = heap_open(childrelid, NoLock);
+ CheckTableNotInUse(childrel, "ALTER TABLE");
+
+ /*
+ * Look for inherited column
+ */
+ tuple = SearchSysCacheCopyAttName(childrelid, colName);
+
+ /* not expected */
+ Assert(HeapTupleIsValid(tuple));
+
+ /* Perform deletion */
+ ATExecDropNotNull(childrel, colName, behavior,
+ recurse, true, lockmode);
+
+ heap_freetuple(tuple);
+ heap_close(attr_rel, NoLock);
+ heap_close(childrel, NoLock);
+ }
+}
+
+/*
+ * Checks if given attribute number is specified within
+ * the key array of the given pg_constraint tuple. This function
+ * is primarily used to check for matching constraints of
+ * type CONSTRAINT_NOTNULL, but can be used for other constraints types
+ * as well.
+ */
+static bool
+constraint_is_for_single_col(HeapTuple constrtup, int attnum)
+{
+ Form_pg_constraint constrStruct;
+ Datum arrayp;
+ Datum *keyvals;
+ int nelems;
+ bool isnull;
+ bool found;
+ int i;
+
+ constrStruct = (Form_pg_constraint) GETSTRUCT(constrtup);
+ arrayp = SysCacheGetAttr(CONSTROID, constrtup,
+ Anum_pg_constraint_conkey,
+ &isnull);
+
+ if (isnull)
+ return false;
+
+ found = false;
+ deconstruct_array(DatumGetArrayTypeP(arrayp), INT2OID, 2, true,
+ 's', &keyvals, NULL, &nelems);
+ for (i = 0; i < nelems; i++)
+ {
+ if (DatumGetInt16(keyvals[0]) != attnum)
+ found = false;
+ else
+ found = true;
+ }
+
+ return found;
+}
+
+/*
+ * Does the leg work on dropping a NOT NULL constraint
+ * identified by the given constrtup tuple.
+ *
+ * Note: The caller is responsible to pass a valid
+ * CONSTRAINT_NOTNULL tuple.
+ */
+static void
+ATExecDropNotNullInternal(Relation rel, Relation attr_rel,
+ DropBehavior behavior,
+ HeapTuple atttup, bool recurse,
+ bool recursing)
+{
+ Form_pg_attribute attr;
+ HeapTuple constr_tup;
+ Relation constr_rel;
+ SysScanDesc scan;
+ ScanKeyData key;
+ bool found;
+
+ found = false;
+ attr = (Form_pg_attribute) GETSTRUCT(atttup);
+
+ /* exit immediately if nothing to do */
+ if (!attr->attnotnull)
+ return;
+
+ /*
+ * Lookup CONSTRAINT_NOTNULL tuple
+ */
+ constr_rel = heap_open(ConstraintRelationId, RowExclusiveLock);
+ ScanKeyInit(&key,
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ scan = systable_beginscan(constr_rel, ConstraintRelidIndexId,
+ true, SnapshotNow, 1, &key);
+
+ while (HeapTupleIsValid(constr_tup = systable_getnext(scan)))
+ {
+ Form_pg_constraint constr_struct;
+
+ constr_struct = (Form_pg_constraint) GETSTRUCT(constr_tup);
+
+ /* We need to look for NOT NULL constraint only */
+ if (constr_struct->contype != CONSTRAINT_NOTNULL)
+ continue;
+
+ if (constraint_is_for_single_col(constr_tup, attr->attnum))
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ /* not expected */
+ Assert(found);
+
+ if (found)
+ {
+ HeapTuple copy_tuple = heap_copytuple(constr_tup);
+ Form_pg_constraint constrStruct = (Form_pg_constraint) GETSTRUCT(copy_tuple);
+
+ /*
+ * Be paranoid, caller is responsible to pass a valid HeapTuple.
+ */
+ Assert(constrStruct->contype == CONSTRAINT_NOTNULL);
+
+ /* Preliminary check, it is okay to drop this constraint when recursing */
+ if ((constrStruct->coninhcount > 0) && (!recursing))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop inherited NOT NULL constraint \"%s\", relation \"%s\"",
+ NameStr(constrStruct->conname), RelationGetRelationName(rel))));
+ }
+
+ if (recurse)
+ {
+ /*
+ * If the child constraint has other definition sources, just
+ * decrement its inheritance count; if not, recurse to delete
+ * it.
+ */
+ if ((constrStruct->coninhcount == 0)
+ || (constrStruct->coninhcount == 1 && !constrStruct->conislocal))
+ {
+ DropNotNullOnAttributeNum(rel, attr->attnum, behavior, false);
+
+ /*
+ * Make sure changes are visible
+ */
+ CommandCounterIncrement();
+ }
+ else
+ {
+ /* Child constraint must survive my deletion */
+ constrStruct->coninhcount--;
+ simple_heap_update(constr_rel, &copy_tuple->t_self, copy_tuple);
+ CatalogUpdateIndexes(constr_rel, copy_tuple);
+
+ /* Make update visible */
+ CommandCounterIncrement();
+ }
+ }
+ else
+ {
+ /*
+ * If we were told to drop ONLY in this table (no recursion),
+ * we need to mark the inheritors' constraints as locally
+ * defined rather than inherited.
+ */
+ constrStruct->coninhcount--;
+ constrStruct->conislocal = true;
+
+ simple_heap_update(constr_rel, &copy_tuple->t_self, copy_tuple);
+ CatalogUpdateIndexes(constr_rel, copy_tuple);
+
+ /* Make update visible */
+ CommandCounterIncrement();
+ }
+
+ heap_freetuple(copy_tuple);
+ }
+
+ systable_endscan(scan);
+ heap_close(constr_rel, NoLock);
+}
+
+/*
+ * Checks wether the given attribute name has a NOT NULL constraint attached
+ * which can be dropped.
+ *
+ * Any attempts to drop a NOT NULL constraint on a system
+ * column or non-existing column will throw an error. Returns true
+ * in case the caller is allowed to proceed, false if no NOT NULL flag
+ * was set on the attribute.
+ */
+static bool
+CheckNotNullOnAttributeName(Relation rel, const char *colname, AttrNumber *attnum)
+{
+ Relation attr_rel;
+ HeapTuple attr_tuple;
+ Form_pg_attribute attr_struct;
+ bool has_not_null;
+ List *indexoidlist;
+ ListCell *indexoidscan;
+
+ has_not_null = false;
+
+ attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
+ attr_tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colname);
+
+ if (!HeapTupleIsValid(attr_tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ colname,
+ RelationGetRelationName(rel))));
+
+ /* attribute exists */
+ attr_struct = (Form_pg_attribute) GETSTRUCT(attr_tuple);
+
+ /* Prevent them from altering a system attribute */
+ if (attr_struct->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot alter system column \"%s\" of relation \"%s\"",
+ colname,
+ RelationGetRelationName(rel))));
+
+ /*
+ * Check for dropped attributes
+ */
+ if (attr_struct->attisdropped)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("attempt to drop NOT NULL constraint on dropped column \"%s\" of relation \"%s\"",
+ colname,
+ RelationGetRelationName(rel))));
+ }
+
+ if (attnum)
+ *attnum = attr_struct->attnum;
/*
* Check that the attribute is not in a primary key
@@ -4582,8 +5316,8 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
foreach(indexoidscan, indexoidlist)
{
- Oid indexoid = lfirst_oid(indexoidscan);
- HeapTuple indexTuple;
+ Oid indexoid = lfirst_oid(indexoidscan);
+ HeapTuple indexTuple;
Form_pg_index indexStruct;
int i;
@@ -4601,45 +5335,136 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
*/
for (i = 0; i < indexStruct->indnatts; i++)
{
- if (indexStruct->indkey.values[i] == attnum)
+ if (indexStruct->indkey.values[i] == attr_struct->attnum)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("column \"%s\" is in a primary key",
- colName)));
+ colname)));
}
}
ReleaseSysCache(indexTuple);
}
- list_free(indexoidlist);
+ /*
+ * In case no NOT NULL constraint was set on the
+ * column, give up and tell the caller we haven't done
+ * anything.
+ */
+ has_not_null = ((!attr_struct->attnotnull) ? false : true);
+
+ heap_freetuple(attr_tuple);
+ heap_close(attr_rel, NoLock);
+ return has_not_null;
+}
+
+/*
+ * Drops a NOT NULL constraint from the given attribute number of the
+ * specified relation. lock gives the caller the possibility to
+ * specify wether pg_attribute was already locked or not.
+ */
+static void
+DropNotNullOnAttributeNum(Relation rel, AttrNumber attnum,
+ DropBehavior behavior, bool lock)
+{
+ Relation att_rel;
+ HeapTuple att_tuple;
+ Form_pg_attribute att_struct;
+ SysScanDesc scan;
+ ScanKeyData key;
+ bool found_constraint;
+
+ if (lock)
+ att_rel = heap_open(AttributeRelationId, RowExclusiveLock);
+ else
+ att_rel = heap_open(AttributeRelationId, NoLock);
+
+ att_tuple = SearchSysCacheCopy(ATTNUM,
+ ObjectIdGetDatum(RelationGetRelid(rel)),
+ Int16GetDatum(attnum),
+ 0, 0);
+
+ att_struct = (Form_pg_attribute) GETSTRUCT(att_tuple);
+
+ /* not expected, so be paranoid */
+ Assert(att_struct->attnotnull);
+
+ ((Form_pg_attribute) GETSTRUCT(att_tuple))->attnotnull = FALSE;
+ simple_heap_update(att_rel, &att_tuple->t_self, att_tuple);
+
+ /* keep the system catalog indexes current */
+ CatalogUpdateIndexes(att_rel, att_tuple);
+
+ heap_close(att_rel, NoLock);
/*
- * Okay, actually perform the catalog change ... if needed
+ * We shouldn't forget any pg_constraint tuple belonging
+ * to this attributes NOT NULL constraint.
*/
- if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
+ found_constraint = false;
+ att_rel = heap_open(ConstraintRelationId, RowExclusiveLock);
+ ScanKeyInit(&key,
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ scan = systable_beginscan(att_rel, ConstraintRelidIndexId,
+ true, SnapshotNow, 1, &key);
+
+ while (HeapTupleIsValid(att_tuple = systable_getnext(scan)))
{
- ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = FALSE;
+ Form_pg_constraint constr_struct;
- simple_heap_update(attr_rel, &tuple->t_self, tuple);
+ constr_struct = (Form_pg_constraint) GETSTRUCT(att_tuple);
- /* keep the system catalog indexes current */
- CatalogUpdateIndexes(attr_rel, tuple);
+ if (constr_struct->contype != CONSTRAINT_NOTNULL)
+ continue;
+
+ if (constraint_is_for_single_col(att_tuple, attnum))
+ {
+ ObjectAddress conobj;
+
+ found_constraint = true;
+ conobj.classId = ConstraintRelationId;
+ conobj.objectId = HeapTupleGetOid(att_tuple);
+ conobj.objectSubId = 0;
+
+ performDeletion(&conobj, behavior);
+ break;
+ }
}
- heap_close(attr_rel, RowExclusiveLock);
+ systable_endscan(scan);
+ heap_close(att_rel, NoLock);
+
+ Assert(found_constraint);
}
/*
* ALTER TABLE ALTER COLUMN SET NOT NULL
+ *
+ * Recursively add not null constraints to the given relation and
+ * column name. ATExecSetNotNull() also works on existing inheritance
+ * trees to ensure the inheritance counter for each not null constraint
+ * is adjusted accordingly. is_new_constraint carries wether the parent has
+ * created a new constraint on its own.
*/
static void
-ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
- const char *colName, LOCKMODE lockmode)
+ATExecSetNotNull(List **wqueue, AlteredTableInfo *tab, Relation rel,
+ const char *colName, bool recurse, bool recursing,
+ bool is_new_constraint, LOCKMODE lockmode)
{
HeapTuple tuple;
AttrNumber attnum;
Relation attr_rel;
+ List *children;
+ ListCell *child;
+ /* bool is_new_constraint; */
+
+ if (recursing)
+ ATSimplePermissions(rel, ATT_TABLE|ATT_FOREIGN_TABLE);
+
+ children = find_inheritance_children(RelationGetRelid(rel),
+ AccessExclusiveLock);
/*
* lookup the attribute
@@ -4660,26 +5485,244 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot alter system column \"%s\"",
- colName)));
+ errmsg("cannot alter system column \"%s\" of relation \"%s\"",
+ colName, RelationGetRelationName(rel))));
/*
- * Okay, actually perform the catalog change ... if needed
+ * Treat it an error if there's an attempt to add
+ * a NOT NULL constraint to a table but not to
+ * existing child tables (thus, ALTER TABLE ONLY
+ * was specified to a table inherited from others).
*/
- if (!((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
+ if (!recurse && !recursing)
{
- ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE;
+ if (children)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("NOT NULL constraint must be added to child tables too")));
+ }
- simple_heap_update(attr_rel, &tuple->t_self, tuple);
+ /*
+ * Actually recursing? If not, check wether this attribute is already
+ * NOT NULL. If not, exit, since there's nothing to do and we would
+ * adjust the inheritance counter accidently.
+ */
+ if (!recursing && ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
+ {
+ heap_freetuple(tuple);
+ heap_close(attr_rel, RowExclusiveLock);
+ return;
+ }
- /* keep the system catalog indexes current */
- CatalogUpdateIndexes(attr_rel, tuple);
+ /*
+ * Okay, actually perform the catalog changes ...
+ *
+ * Setting attnotnull to TRUE requires to create a
+ * constraint tuple in pg_constraint as well. We do this
+ * to record wether this constraint was added locally or
+ * inherited by some other attribute. We also record the
+ * attribute number to determine which column this
+ * constraint belongs to. We need to take care wether
+ * the constraint was added locally to a child table or
+ * we're currently already recursing through an inheritance
+ * tree and wether we already created a new constraint during
+ * recursion.
+ */
+ ATExecSetNotNullInternal(rel, attr_rel, tuple,
+ &is_new_constraint, true, !recursing);
+
+ /* Tell Phase 3 it needs to test the constraint */
+ tab->new_notnull = true;
+
+ heap_freetuple(tuple);
+ heap_close(attr_rel, NoLock);
+
+ /*
+ * Loop through all child relations
+ */
+ foreach(child, children)
+ {
+ Oid childrelid = lfirst_oid(child);
+ Relation childrel;
+ AlteredTableInfo *childtab;
+
+ attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
+ childrel = heap_open(childrelid, NoLock);
+ CheckTableNotInUse(childrel, "ALTER TABLE");
+
+ /* get work queue entry for child relation */
+ childtab = ATGetQueueEntry(wqueue, childrel);
+
+ /*
+ * Look for inherited column
+ */
+ tuple = SearchSysCacheCopyAttName(childrelid, colName);
+
+ /* shouldn't happen */
+ Assert(HeapTupleIsValid(tuple));
+
+ /* perform the actual work */
+ ATExecSetNotNull(wqueue, childtab, childrel,
+ colName, false, true,
+ is_new_constraint, lockmode);
+
+ heap_freetuple(tuple);
+ heap_close(attr_rel, NoLock);
+ heap_close(childrel, NoLock);
+ }
+}
+
+/*
+ * Internal function to ATExecSetNotNull()
+ *
+ * Takes a relation and the pg_attribute relation and tuple of the
+ * target column to create a NOT NULL constraint.
+ * The caller is responsible to pass a valid HeapTuple.
+ */
+static void
+ATExecSetNotNullInternal(Relation rel, Relation attr_rel,
+ HeapTuple atttup, bool *is_new_constraint,
+ bool allow_new_constraint, bool is_local)
+{
+ Form_pg_attribute attr;
+
+ attr = (Form_pg_attribute) GETSTRUCT(atttup);
+
+ if (attr->attnotnull)
+ {
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple constr_tup;
+ Relation constr_rel;
+ bool found;
+
+ found = false;
+
+ /*
+ * Column has already a NOT NULL constraint attached,
+ * fetch its pg_constraint tuple and adjust the inheritance
+ * information according to its pg_attribute tuple.
+ */
+ constr_rel = heap_open(ConstraintRelationId, RowExclusiveLock);
+ ScanKeyInit(&key,
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ scan = systable_beginscan(constr_rel, ConstraintRelidIndexId,
+ true, SnapshotNow, 1, &key);
+
+ while (HeapTupleIsValid(constr_tup = systable_getnext(scan)))
+ {
+ Form_pg_constraint constr_struct;
+
+ constr_struct = (Form_pg_constraint) GETSTRUCT(constr_tup);
+
+ /* We need to look for NOT NULL constraint only */
+ if (constr_struct->contype != CONSTRAINT_NOTNULL)
+ continue;
+
+ if (constraint_is_for_single_col(constr_tup, attr->attnum))
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ /* there should be a NOT NULL constraint tuple */
+ if (found)
+ {
+ HeapTuple copy_tuple = heap_copytuple(constr_tup);
+ Form_pg_constraint constr = (Form_pg_constraint) GETSTRUCT(copy_tuple);
+
+ elog(DEBUG1, "visit constraint %s of relation %d with inhcount %d",
+ NameStr(constr->conname), constr->conrelid, constr->coninhcount);
+
+ /*
+ * If we are forced to create a new not null constraint, we
+ * have is_new_constraint set to true. This means during recursion a
+ * parent has got a new not null constraint on this column. We
+ * have found an existing not null constraint now, which means either
+ * this one was locally created or inherited from another parent.
+ *
+ * We must adjust its inhcount only, when a parent has become a new
+ * constraint on its column.
+ */
+ if (*is_new_constraint)
+ {
+ /* actual recursing */
+ constr->coninhcount++;
+ }
+ else if (is_local)
+ {
+ constr->conislocal = true;
+ }
+
+ /*
+ * Inform caller that we have found an existing constraint.
+ */
+ *is_new_constraint = FALSE;
+
+ /*
+ * Apply the changes...
+ */
+ simple_heap_update(constr_rel, &copy_tuple->t_self, copy_tuple);
+
+ /*
+ * Keep system catalog indexes current and make
+ * changes visible
+ */
+ CatalogUpdateIndexes(constr_rel, copy_tuple);
+ CommandCounterIncrement();
+
+ heap_freetuple(copy_tuple);
+ }
+
+ systable_endscan(scan);
+ heap_close(constr_rel, NoLock);
- /* Tell Phase 3 it needs to test the constraint */
- tab->new_notnull = true;
}
+ else if (!attr->attnotnull && allow_new_constraint)
+ {
+ CookedConstraint *cooked;
+
+ /*
+ * New NOT NULL constraint
+ */
+ cooked = palloc(sizeof(CookedConstraint));
+ cooked->contype = CONSTR_NOTNULL;
+ cooked->expr = NULL;
+ cooked->name = ChooseConstraintName(RelationGetRelationName(rel),
+ NameStr(attr->attname),
+ "not_null", RelationGetNamespace(rel),
+ NIL);
+ cooked->attnum = attr->attnum;
+ cooked->is_local = is_local;
+ cooked->inhcount = is_local ? 0 : 1;
+
+ StoreColumnNotNullConstraint(rel, cooked);
+
+ /*
+ * Adjust inherited attnotnull attribute accordingly.
+ * XXX: Unfortunately, we need a CCI here, since the
+ * attribute might be already updated before. Room
+ * for further optimization...
+ */
+ CommandCounterIncrement();
- heap_close(attr_rel, RowExclusiveLock);
+ attr->attnotnull = TRUE;
+ simple_heap_update(attr_rel, &atttup->t_self, atttup);
+
+ /* keep the system catalog indexes current */
+ CatalogUpdateIndexes(attr_rel, atttup);
+
+ /*
+ * Inform caller that we have created a new constraint
+ */
+ *is_new_constraint = TRUE;
+
+ /* Make changes visible */
+ CommandCounterIncrement();
+ }
}
/*
@@ -6420,6 +7463,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
con = (Form_pg_constraint) GETSTRUCT(tuple);
+ /* NOT NULL is handled by ALTER TABLE ... DROP/SET NOT NULL */
+ if (con->contype == CONSTRAINT_NOTNULL)
+ continue;
+
if (strcmp(NameStr(con->conname), constrName) != 0)
continue;
@@ -6430,7 +7477,9 @@ ATExecDropConstraint(Relation rel, const char *constrName,
errmsg("cannot drop inherited constraint \"%s\" of relation \"%s\"",
constrName, RelationGetRelationName(rel))));
- /* Right now only CHECK constraints can be inherited */
+ /* This is a CHECK constraint, remember that we found one
+ * and proceed to drop it
+ */
if (con->contype == CONSTRAINT_CHECK)
is_check_constraint = true;
@@ -6501,7 +7550,10 @@ ATExecDropConstraint(Relation rel, const char *constrName,
con = (Form_pg_constraint) GETSTRUCT(tuple);
- /* Right now only CHECK constraints can be inherited */
+ /* Besides CHECK constraint we support inheritance of
+ * NOT NULL constraints, too. Ignore them here, since they
+ * are handled by ALTER TABLE...DROP/SET NOT NULL
+ */
if (con->contype != CONSTRAINT_CHECK)
continue;
@@ -7292,6 +8344,7 @@ ATPostAlterTypeParse(char *cmd, List **wqueue, LOCKMODE lockmode)
tab->subcmds[AT_PASS_OLD_INDEX] =
lappend(tab->subcmds[AT_PASS_OLD_INDEX], cmd);
break;
+ case AT_SetNotNull:
case AT_AddConstraint:
tab->subcmds[AT_PASS_OLD_CONSTR] =
lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
@@ -8057,6 +9110,7 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
HeapTuple inheritsTuple;
int32 inhseqno;
List *children;
+ List *child_attnums;
/*
* A self-exclusive lock is needed here. See the similar case in
@@ -8144,10 +9198,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
RelationGetRelationName(parent_rel))));
/* Match up the columns and bump attinhcount as needed */
- MergeAttributesIntoExisting(child_rel, parent_rel);
+ child_attnums = NIL;
+ MergeAttributesIntoExisting(child_rel, parent_rel, &child_attnums);
/* Match up the constraints and bump coninhcount as needed */
- MergeConstraintsIntoExisting(child_rel, parent_rel);
+ MergeConstraintsIntoExisting(child_rel, parent_rel, child_attnums);
/*
* OK, it looks valid. Make the catalog entries that show inheritance.
@@ -8223,7 +9278,8 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
* the child must be as well. Defaults are not compared, however.
*/
static void
-MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
+MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel,
+ List **attnums_list)
{
Relation attrrel;
AttrNumber parent_attno;
@@ -8279,6 +9335,11 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attributeName)));
/*
+ * Record the child's inherited attnum in attnums_list.
+ */
+ *attnums_list = lappend_int(*attnums_list, childatt->attnum);
+
+ /*
* OK, bump the child column's inheritance count. (If we fail
* later on, this change will just roll back.)
*/
@@ -8317,7 +9378,8 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
* a problem though. Even 100 constraints ought not be the end of the world.
*/
static void
-MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
+MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel,
+ List *child_attnums)
{
Relation catalog_relation;
TupleDesc tuple_desc;
@@ -8344,7 +9406,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
HeapTuple child_tuple;
bool found = false;
- if (parent_con->contype != CONSTRAINT_CHECK)
+ if (parent_con->contype != CONSTRAINT_CHECK
+ && parent_con->contype != CONSTRAINT_NOTNULL)
continue;
/* Search for a child constraint matching this one */
@@ -8360,19 +9423,47 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
Form_pg_constraint child_con = (Form_pg_constraint) GETSTRUCT(child_tuple);
HeapTuple child_copy;
- if (child_con->contype != CONSTRAINT_CHECK)
- continue;
-
- if (strcmp(NameStr(parent_con->conname),
- NameStr(child_con->conname)) != 0)
- continue;
-
- if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
- RelationGetRelationName(child_rel),
- NameStr(parent_con->conname))));
+ switch(child_con->contype)
+ {
+ case CONSTRAINT_CHECK:
+ {
+ if (strcmp(NameStr(parent_con->conname),
+ NameStr(child_con->conname)) != 0)
+ continue;
+
+ if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+ RelationGetRelationName(child_rel),
+ NameStr(parent_con->conname))));
+ break;
+ }
+ case CONSTRAINT_NOTNULL:
+ {
+ Datum arrayp;
+ Datum *vals;
+ int nelems;
+ bool isnull;
+
+ if (child_attnums == NIL)
+ continue;
+
+ arrayp = SysCacheGetAttr(CONSTROID, child_tuple,
+ Anum_pg_constraint_conkey, &isnull);
+ /* should not happen */
+ Assert(!isnull);
+ deconstruct_array(DatumGetArrayTypeP(arrayp), INT2OID, 2, true,
+ 's', &vals, NULL, &nelems);
+ Assert(nelems > 0);
+
+ if (!list_member_int(child_attnums, DatumGetInt16(vals[0])))
+ continue;
+ break;
+ }
+ default:
+ continue;
+ }
/*
* OK, bump the child constraint's inheritance count. (If we fail
@@ -8415,8 +9506,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
* surprise. But at least we'll never surprise by dropping columns someone
* isn't expecting to be dropped which would actually mean data loss.
*
- * coninhcount and conislocal for inherited constraints are adjusted in
- * exactly the same way.
+ * coninhcount and conislocal for inherited CHECK and NOT NULL constraints
+ * are adjusted in exactly the same way.
*/
static void
ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
@@ -8427,8 +9518,8 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyData key[3];
HeapTuple inheritsTuple,
attributeTuple,
- constraintTuple;
- List *connames;
+ constraintTuple;
+ List *constraints;
bool found = false;
/*
@@ -8478,6 +9569,8 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
RelationGetRelationName(parent_rel),
RelationGetRelationName(rel))));
+ constraints = NIL;
+
/*
* Search through child columns looking for ones matching parent rel
*/
@@ -8491,6 +9584,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
{
Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple);
+ HeapTuple parentattr_tuple;
/* Ignore if dropped or not inherited */
if (att->attisdropped)
@@ -8498,30 +9592,51 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
if (att->attinhcount <= 0)
continue;
- if (SearchSysCacheExistsAttName(RelationGetRelid(parent_rel),
- NameStr(att->attname)))
+ parentattr_tuple = SearchSysCacheCopyAttName(RelationGetRelid(parent_rel),
+ NameStr(att->attname));
+
+ if (HeapTupleIsValid(parentattr_tuple))
{
/* Decrement inhcount and possibly set islocal to true */
HeapTuple copyTuple = heap_copytuple(attributeTuple);
Form_pg_attribute copy_att = (Form_pg_attribute) GETSTRUCT(copyTuple);
+ Form_pg_attribute parent_att = (Form_pg_attribute) GETSTRUCT(parentattr_tuple);
copy_att->attinhcount--;
if (copy_att->attinhcount == 0)
copy_att->attislocal = true;
+ /*
+ * If attnotnull is set, record the inherited
+ * not null constraint. We save its attnum to deinherit
+ * its representation in pg_constraint below, but only when
+ * its ancestor has a NOT NULL constraint, too.
+ */
+ if (copy_att->attnotnull
+ && parent_att->attnotnull)
+ {
+ DeinheritConstraintInfo *inhInfo
+ = (DeinheritConstraintInfo *)palloc(sizeof(DeinheritConstraintInfo));
+ inhInfo->contype = CONSTRAINT_NOTNULL;
+ inhInfo->conname = NULL;
+ inhInfo->attnum = copy_att->attnum;
+ constraints = lappend(constraints, inhInfo);
+ }
+
simple_heap_update(catalogRelation, &copyTuple->t_self, copyTuple);
CatalogUpdateIndexes(catalogRelation, copyTuple);
heap_freetuple(copyTuple);
+ heap_freetuple(parentattr_tuple);
}
}
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
/*
- * Likewise, find inherited check constraints and disinherit them. To do
- * this, we first need a list of the names of the parent's check
- * constraints. (We cheat a bit by only checking for name matches,
- * assuming that the expressions will match.)
+ * Likewise, find inherited check and NOT NULL constraints and disinherit
+ * them. To do this, we first need a list of the names of the parent's check
+ * and NOT NULL constraints. (For check constraints, we cheat a bit by
+ * only checking for name matches, assuming that the expressions will match.)
*/
catalogRelation = heap_open(ConstraintRelationId, RowExclusiveLock);
ScanKeyInit(&key[0],
@@ -8531,14 +9646,19 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
true, SnapshotNow, 1, key);
- connames = NIL;
-
while (HeapTupleIsValid(constraintTuple = systable_getnext(scan)))
{
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
if (con->contype == CONSTRAINT_CHECK)
- connames = lappend(connames, pstrdup(NameStr(con->conname)));
+ {
+ DeinheritConstraintInfo *inhInfo
+ = (DeinheritConstraintInfo *)palloc(sizeof(DeinheritConstraintInfo));
+ inhInfo->contype = CONSTRAINT_CHECK;
+ inhInfo->conname = pstrdup(NameStr(con->conname));
+ inhInfo->attnum = 0;
+ constraints = lappend(constraints, inhInfo);
+ }
}
systable_endscan(scan);
@@ -8557,13 +9677,33 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
bool match;
ListCell *lc;
- if (con->contype != CONSTRAINT_CHECK)
+ if (con->contype != CONSTRAINT_CHECK
+ && con->contype != CONSTRAINT_NOTNULL)
continue;
+ /*
+ * We scan through all constraints of the current child relation,
+ * checking for CONSTRAINT_CHECK and CONSTRAINT_NOTNULL occurrences.
+ *
+ * CHECK constraints are assumed to be named equally to in the
+ * relation's ancestor, while NOT NULL constraints always are attached
+ * to a specific column. Check for attnum and adjust the inheritance
+ * information accordingly.
+ */
+
match = false;
- foreach(lc, connames)
+ foreach(lc, constraints)
{
- if (strcmp(NameStr(con->conname), (char *) lfirst(lc)) == 0)
+ DeinheritConstraintInfo *inhInfo = (DeinheritConstraintInfo *) lfirst(lc);
+
+ if ((inhInfo->contype == CONSTRAINT_CHECK)
+ && (strcmp(NameStr(con->conname), inhInfo->conname) == 0))
+ {
+ match = true;
+ break;
+ }
+ else if ((inhInfo->contype == CONSTRAINT_NOTNULL)
+ && (constraint_is_for_single_col(constraintTuple, inhInfo->attnum)))
{
match = true;
break;
@@ -8580,6 +9720,9 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
RelationGetRelid(rel), NameStr(copy_con->conname));
+ elog(DEBUG1, "deinherit not null constraint \"%s\", relation \"%s\"",
+ NameStr(copy_con->conname), RelationGetRelationName(rel));
+
copy_con->coninhcount--;
if (copy_con->coninhcount == 0)
copy_con->conislocal = true;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 66c11de672..c680553e6b 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -93,7 +93,11 @@ static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
Oid baseTypeOid,
int typMod, Constraint *constr,
char *domainName);
-
+static void domainAddNotNull(Oid domainOid, Oid domainNamespace,
+ Constraint *constr, char *domainName);
+static void
+addOrRemoveDomainNotNull(Oid domainOid, Oid domainNamespace,
+ char * domainName, bool is_new);
/*
* DefineType
@@ -779,6 +783,7 @@ DefineDomain(CreateDomainStmt *stmt)
Form_pg_type baseType;
int32 basetypeMod;
Oid baseColl;
+ bool saw_notnull = false;
/* Convert list of names to a name and namespace */
domainNamespace = QualifiedNameGetCreationNamespace(stmt->domainname,
@@ -971,8 +976,22 @@ DefineDomain(CreateDomainStmt *stmt)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting NULL/NOT NULL constraints")));
+
+ if (saw_notnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("multiple definition for NOT NULL constraint")));
+
+ /*
+ * We are going to create a pg_constraint entry for this
+ * constraint to record an explicit named NOT NULL constraint.
+ * We do this even in case it wasn't named explicitly. Since
+ * we need the domains' OID, this is done later.
+ */
+
typNotNull = true;
nullDefined = true;
+ saw_notnull = true;
break;
case CONSTR_NULL:
@@ -1078,10 +1097,14 @@ DefineDomain(CreateDomainStmt *stmt)
{
Constraint *constr = lfirst(listptr);
- /* it must be a Constraint, per check above */
+ /* it must be a CHECK or NOT NULL Constraint, per check above */
switch (constr->contype)
{
+ case CONSTR_NOTNULL:
+ domainAddNotNull(domainoid, domainNamespace,
+ constr, domainName);
+ break;
case CONSTR_CHECK:
domainAddConstraint(domainoid, domainNamespace,
basetypeoid, basetypeMod,
@@ -1854,12 +1877,99 @@ AlterDomainNotNull(List *names, bool notNull)
CatalogUpdateIndexes(typrel, tup);
+ /*
+ * Depending on wether we are setting the domain to NOT NULL or NULL, we
+ * need to update pg_constraint as well.
+ */
+ addOrRemoveDomainNotNull(domainoid, typTup->typnamespace,
+ NameStr(typTup->typname), typTup->typnotnull);
+
/* Clean up */
heap_freetuple(tup);
heap_close(typrel, RowExclusiveLock);
}
/*
+ * Internal magic for pg_constraint and ALTER DOMAIN ... SET|DROP NOT NULL.
+ */
+static void
+addOrRemoveDomainNotNull(Oid domainOid, Oid domainNamespace,
+ char * domainName, bool is_new)
+{
+ if (is_new)
+ {
+ /*
+ * Caller wants a new CONSTRAINT_NOTNULL tuple
+ * for the given domain OID.
+ */
+ Constraint *constr;
+ constr = (Constraint *) palloc(sizeof(Constraint));
+
+ /*
+ * ALTER DOMAIN ... SET NOT NULL doesn't have an infrastructure
+ * to specify a name for the new constraint, so always generate
+ * a new one.
+ */
+ constr->conname = NULL;
+ constr->contype = CONSTRAINT_NOTNULL;
+
+ /*
+ * Finally do the creation.
+ */
+ domainAddNotNull(domainOid, domainNamespace,
+ constr, domainName);
+ }
+ else
+ {
+ /* Existing constraint tuple should be dropped */
+
+ Relation conrel;
+ HeapTuple contup;
+ SysScanDesc conscan;
+ ScanKeyData key[1];
+
+ /* Grab an appropriate lock on the pg_constraint relation */
+ conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
+
+ /* Use the index to scan only constraints of the target relation */
+ ScanKeyInit(&key[0],
+ Anum_pg_constraint_contypid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(domainOid));
+
+ conscan = systable_beginscan(conrel, ConstraintTypidIndexId, true,
+ SnapshotNow, 1, key);
+
+ while ((contup = systable_getnext(conscan)) != NULL)
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(contup);
+
+ /*
+ * Perform deletion over pg_depend
+ */
+ if (con->contype == CONSTRAINT_NOTNULL)
+ {
+ ObjectAddress conobj;
+
+ conobj.classId = ConstraintRelationId;
+ conobj.objectId = HeapTupleGetOid(contup);
+ conobj.objectSubId = 0;
+
+ performDeletion(&conobj, DEPENDENCY_AUTO);
+
+ /*
+ * Only one tuple expected, break.
+ */
+ break;
+ }
+ }
+
+ systable_endscan(conscan);
+ heap_close(conrel, NoLock);
+ }
+}
+
+/*
* AlterDomainDropConstraint
*
* Implements the ALTER DOMAIN DROP CONSTRAINT statement
@@ -2293,6 +2403,67 @@ checkDomainOwner(HeapTuple tup)
}
/*
+ * domainAddNotNull - add a NOT NULL constraint to
+ * pg_constraint.
+ */
+void
+domainAddNotNull(Oid domainOid, Oid domainNamespace,
+ Constraint *constr, char *domainName)
+{
+ /*
+ * Validate constraint name or assign a new one
+ * if not specified already.
+ */
+ if (constr->conname)
+ {
+ if (ConstraintNameIsUsed(CONSTRAINT_DOMAIN,
+ domainOid,
+ domainNamespace,
+ constr->conname))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("not null constraint \"%s\" for domain \"%s\" already exists",
+ constr->conname, domainName)));
+ }
+ else
+ constr->conname = ChooseConstraintName(domainName,
+ NULL,
+ "not_null",
+ domainNamespace,
+ NIL);
+
+ /*
+ * Store the not null constraint in pg_constraint
+ */
+ CreateConstraintEntry(constr->conname, /* Constraint Name */
+ domainNamespace, /* namespace */
+ CONSTRAINT_NOTNULL, /* Constraint Type */
+ false, /* Is Deferrable */
+ false, /* Is Deferred */
+ true, /* Is Validated */
+ InvalidOid, /* not a relation constraint */
+ NULL,
+ 0,
+ domainOid, /* domain constraint */
+ InvalidOid, /* no associated index */
+ InvalidOid, /* Foreign key fields */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ ' ',
+ ' ',
+ ' ',
+ NULL, /* not an exclusion constraint */
+ NULL, /* Tree form of check constraint */
+ NULL, /* Binary form of check constraint */
+ NULL, /* Source form of check constraint */
+ true, /* is local */
+ 0); /* inhcount */
+}
+
+/*
* domainAddConstraint - code shared between CREATE and ALTER DOMAIN
*/
static char *
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 485bf4b8e7..a6a626f576 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -78,6 +78,7 @@ typedef struct
List *ckconstraints; /* CHECK constraints */
List *fkconstraints; /* FOREIGN KEY constraints */
List *ixconstraints; /* index-creating constraints */
+ List *nnconstraints; /* NOT NULL column constraints */
List *inh_indexes; /* cloned indexes from INCLUDING INDEXES */
List *blist; /* "before list" of things to do before
* creating the table */
@@ -211,6 +212,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.ckconstraints = NIL;
cxt.fkconstraints = NIL;
cxt.ixconstraints = NIL;
+ cxt.nnconstraints = NIL;
cxt.inh_indexes = NIL;
cxt.blist = NIL;
cxt.alist = NIL;
@@ -493,6 +495,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
parser_errposition(cxt->pstate,
constraint->location)));
column->is_not_null = TRUE;
+
+ cxt->nnconstraints = lappend(cxt->nnconstraints, constraint);
saw_nullable = true;
break;
@@ -2268,6 +2272,7 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
cxt.ckconstraints = NIL;
cxt.fkconstraints = NIL;
cxt.ixconstraints = NIL;
+ cxt.nnconstraints = NIL;
cxt.inh_indexes = NIL;
cxt.blist = NIL;
cxt.alist = NIL;
@@ -2288,6 +2293,8 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
case AT_AddColumnToView:
{
ColumnDef *def = (ColumnDef *) cmd->def;
+ List *nnconstr;
+ ListCell *constrptr;
Assert(IsA(def, ColumnDef));
transformColumnDefinition(&cxt, def);
@@ -2300,11 +2307,26 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
skipValidation = false;
/*
- * All constraints are processed in other ways. Remove the
- * original list
+ * All constraints except CONSTR_NOTNULL nodes
+ * are processed in other ways. Remove the
+ * original list, but keep all NOT NULL constraints.
+ * We need the additional information to ease the
+ * creation of corresponding pg_constraint tuple in
+ * ATExecAddColumn().
*/
- def->constraints = NIL;
+ nnconstr = NIL;
+
+ foreach(constrptr, def->constraints)
+ {
+ Constraint *constraint = (Constraint *) lfirst(constrptr);
+
+ if (constraint->contype != CONSTR_NOTNULL)
+ continue;
+
+ nnconstr = lappend(nnconstr, constraint);
+ }
+ def->constraints = nnconstr;
newcmds = lappend(newcmds, cmd);
break;
}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 06cf6fa4f7..cb3f2d8b46 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1106,13 +1106,51 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
if (fullCommand && OidIsValid(conForm->conrelid))
{
- appendStringInfo(&buf, "ALTER TABLE ONLY %s ADD CONSTRAINT %s ",
- generate_relation_name(conForm->conrelid, NIL),
- quote_identifier(NameStr(conForm->conname)));
+ /* XXX: TODO, not sure how to handle this right now? */
+ if (conForm->contype == CONSTRAINT_NOTNULL)
+ {
+ appendStringInfo(&buf, "ALTER TABLE ONLY %s ALTER COLUMN ",
+ generate_relation_name(conForm->conrelid, NIL));
+ }
+ else
+ {
+ appendStringInfo(&buf, "ALTER TABLE ONLY %s ADD CONSTRAINT %s ",
+ generate_relation_name(conForm->conrelid, NIL),
+ quote_identifier(NameStr(conForm->conname)));
+ }
}
switch (conForm->contype)
{
+ case CONSTRAINT_NOTNULL:
+ {
+ Datum val;
+ bool isnull;
+
+ /* XXX: TODO, not sure how to handle this right now? */
+
+ /* Fetch referenced column OID */
+ val = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_conkey, &isnull);
+
+ if (conForm->conrelid != InvalidOid)
+ {
+ /* Should not happen */
+ if (isnull)
+ elog(ERROR, "null conkey for constraint %u",
+ constraintId);
+
+ /* relation constraint */
+ decompile_column_index_array(val, conForm->conrelid, &buf);
+ appendStringInfo(&buf, " SET NOT NULL");
+ }
+ else
+ {
+ /* Domain constraint */
+ }
+
+ break;
+ }
case CONSTRAINT_FOREIGN:
{
Datum val;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index c95e91303b..456587b653 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -26,7 +26,7 @@ typedef struct RawColumnDefault
typedef struct CookedConstraint
{
- ConstrType contype; /* CONSTR_DEFAULT or CONSTR_CHECK */
+ ConstrType contype; /* CONSTR_DEFAULT, CONSTR_CHECK or CONSTR_NOTNULL */
char *name; /* name, or NULL if none */
AttrNumber attnum; /* which attr (only for DEFAULT) */
Node *expr; /* transformed default or check expr */
@@ -92,7 +92,7 @@ extern List *AddRelationNewConstraints(Relation rel,
bool is_local);
extern void StoreAttrDefault(Relation rel, AttrNumber attnum, Node *expr);
-
+extern void StoreColumnNotNullConstraint(Relation rel, CookedConstraint *cooked);
extern Node *cookDefault(ParseState *pstate,
Node *raw_default,
Oid atttypid,
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 1566af2973..3088480437 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -177,6 +177,7 @@ typedef FormData_pg_constraint *Form_pg_constraint;
/* Valid values for contype */
#define CONSTRAINT_CHECK 'c'
+#define CONSTRAINT_NOTNULL 'n'
#define CONSTRAINT_FOREIGN 'f'
#define CONSTRAINT_PRIMARY 'p'
#define CONSTRAINT_UNIQUE 'u'
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 14937d4363..cffe47930f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1178,7 +1178,9 @@ typedef enum AlterTableType
AT_AddColumnToView, /* implicitly via CREATE OR REPLACE VIEW */
AT_ColumnDefault, /* alter column default */
AT_DropNotNull, /* alter column drop not null */
+ AT_DropNotNullRecurse, /* internal to commands/tablecmds.c */
AT_SetNotNull, /* alter column set not null */
+ AT_SetNotNullRecurse, /* internal to commands/tablecmds.c */
AT_SetStatistics, /* alter column set statistics */
AT_SetOptions, /* alter column set ( options ) */
AT_ResetOptions, /* alter column reset ( options ) */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index f84da45de6..8f11b238f7 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -519,7 +519,7 @@ create table atacc1 ( test int );
insert into atacc1 (test) values (NULL);
-- add a primary key (fails)
alter table atacc1 add constraint atacc_test1 primary key (test);
-ERROR: column "test" contains null values
+ERROR: column "test" of relation "atacc1" contains null values
insert into atacc1 (test) values (3);
drop table atacc1;
-- let's do one where the primary key constraint fails
@@ -536,7 +536,7 @@ insert into atacc1 (test) values (0);
-- add a primary key column without a default (fails).
alter table atacc1 add column test2 int primary key;
NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index "atacc1_pkey" for table "atacc1"
-ERROR: column "test2" contains null values
+ERROR: column "test2" of relation "atacc1" contains null values
-- now add a primary key column with a default (succeeds).
alter table atacc1 add column test2 int default 0 primary key;
NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index "atacc1_pkey" for table "atacc1"
@@ -599,7 +599,7 @@ alter table atacc1 drop constraint "atacc1_pkey";
alter table atacc1 alter column test drop not null;
insert into atacc1 values (null);
alter table atacc1 alter test set not null;
-ERROR: column "test" contains null values
+ERROR: column "test" of relation "atacc1" contains null values
delete from atacc1;
alter table atacc1 alter test set not null;
-- try altering a non-existent column, should fail
@@ -609,9 +609,9 @@ alter table atacc1 alter bar drop not null;
ERROR: column "bar" of relation "atacc1" does not exist
-- try altering the oid column, should fail
alter table atacc1 alter oid set not null;
-ERROR: cannot alter system column "oid"
+ERROR: cannot alter system column "oid" of relation "atacc1"
alter table atacc1 alter oid drop not null;
-ERROR: cannot alter system column "oid"
+ERROR: cannot alter system column "oid" of relation "atacc1"
-- try creating a view and altering that, should fail
create view myview as select * from atacc1;
alter table myview alter column test drop not null;
@@ -632,13 +632,13 @@ alter table parent alter a drop not null;
insert into parent values (NULL);
insert into child (a, b) values (NULL, 'foo');
alter table only parent alter a set not null;
-ERROR: column "a" contains null values
+ERROR: NOT NULL constraint must be added to child tables too
alter table child alter a set not null;
-ERROR: column "a" contains null values
+ERROR: column "a" of relation "child" contains null values
delete from parent;
alter table only parent alter a set not null;
+ERROR: NOT NULL constraint must be added to child tables too
insert into parent values (NULL);
-ERROR: null value in column "a" violates not-null constraint
alter table child alter a set not null;
insert into child (a, b) values (NULL, 'foo');
ERROR: null value in column "a" violates not-null constraint
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 979057d26f..1273d01657 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -251,11 +251,12 @@ ERROR: insert or update on table "clstr_tst" violates foreign key constraint "c
DETAIL: Key (b)=(1111) is not present in table "clstr_tst_s".
SELECT conname FROM pg_constraint WHERE conrelid = 'clstr_tst'::regclass
ORDER BY 1;
- conname
-----------------
+ conname
+----------------------
+ clstr_tst_a_not_null
clstr_tst_con
clstr_tst_pkey
-(2 rows)
+(3 rows)
SELECT relname, relkind,
EXISTS(SELECT 1 FROM pg_class WHERE oid = c.reltoastrelid) AS hastoast
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 2586b07241..7484d65b32 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -305,6 +305,23 @@ drop domain dnotnulltest cascade;
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to table domnotnull column col1
drop cascades to table domnotnull column col2
+create domain dnotnulltest integer constraint dnn not null;
+select conname, contype, contypid::regtype from pg_constraint c
+ where contype = 'n' and contypid <> 0 order by 1;
+ conname | contype | contypid
+----------------+---------+--------------
+ ddef5_not_null | n | ddef5
+ dnn | n | dnotnulltest
+(2 rows)
+
+drop domain dnotnulltest;
+select conname, contype, contypid::regtype from pg_constraint
+ where contype = 'n' and contypid <> 0 order by 1;
+ conname | contype | contypid
+----------------+---------+----------
+ ddef5_not_null | n | ddef5
+(1 row)
+
-- Test ALTER DOMAIN .. DEFAULT ..
create table domdeftest (col1 ddef1);
insert into domdeftest default values;
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index d59ca449dc..621346f0aa 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1242,3 +1242,306 @@ NOTICE: drop cascades to 3 other objects
DETAIL: drop cascades to table matest1
drop cascades to table matest2
drop cascades to table matest3
+--
+-- Test inheritance of NOT NULL constraints
+--
+create table pp1 (f1 int);
+create table cc1 (f2 text, f3 int) inherits (pp1);
+\d cc1
+ Table "public.cc1"
+ Column | Type | Modifiers
+--------+---------+-----------
+ f1 | integer |
+ f2 | text |
+ f3 | integer |
+Inherits: pp1
+
+create table cc2(f4 float) inherits(pp1,cc1);
+NOTICE: merging multiple inherited definitions of column "f1"
+\d cc2
+ Table "public.cc2"
+ Column | Type | Modifiers
+--------+------------------+-----------
+ f1 | integer |
+ f2 | text |
+ f3 | integer |
+ f4 | double precision |
+Inherits: pp1,
+ cc1
+
+-- named NOT NULL constraint
+alter table cc1 add column a2 int constraint nn not null;
+\d cc1
+ Table "public.cc1"
+ Column | Type | Modifiers
+--------+---------+-----------
+ f1 | integer |
+ f2 | text |
+ f3 | integer |
+ a2 | integer | not null
+Inherits: pp1
+Number of child tables: 1 (Use \d+ to list them.)
+
+\d cc2
+ Table "public.cc2"
+ Column | Type | Modifiers
+--------+------------------+-----------
+ f1 | integer |
+ f2 | text |
+ f3 | integer |
+ f4 | double precision |
+ a2 | integer | not null
+Inherits: pp1,
+ cc1
+
+alter table pp1 alter column f1 set not null;
+\d pp1
+ Table "public.pp1"
+ Column | Type | Modifiers
+--------+---------+-----------
+ f1 | integer | not null
+Number of child tables: 2 (Use \d+ to list them.)
+
+\d cc1
+ Table "public.cc1"
+ Column | Type | Modifiers
+--------+---------+-----------
+ f1 | integer | not null
+ f2 | text |
+ f3 | integer |
+ a2 | integer | not null
+Inherits: pp1
+Number of child tables: 1 (Use \d+ to list them.)
+
+\d cc2
+ Table "public.cc2"
+ Column | Type | Modifiers
+--------+------------------+-----------
+ f1 | integer | not null
+ f2 | text |
+ f3 | integer |
+ f4 | double precision |
+ a2 | integer | not null
+Inherits: pp1,
+ cc1
+
+-- have a look at pg_constraint
+select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' order by 2;
+ conrelid | conname | contype | coninhcount | conislocal
+----------+------------------+---------+-------------+------------
+ cc1 | cc1_f1_not_null | n | 1 | f
+ cc2 | cc2_f1_not_null | n | 2 | f
+ inhx | inhx_xx_not_null | n | 0 | t
+ cc1 | nn | n | 0 | t
+ cc2 | nn | n | 1 | f
+ pp1 | pp1_f1_not_null | n | 0 | t
+(6 rows)
+
+-- remove constraint from cc2, should fail
+alter table cc2 alter column a2 drop not null;
+ERROR: cannot drop inherited NOT NULL constraint "nn", relation "cc2"
+-- remove constraint cc1, should succeed
+alter table cc1 alter column a2 drop not null;
+-- have a look at pg_constraint
+select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' order by 2;
+ conrelid | conname | contype | coninhcount | conislocal
+----------+------------------+---------+-------------+------------
+ cc1 | cc1_f1_not_null | n | 1 | f
+ cc2 | cc2_f1_not_null | n | 2 | f
+ inhx | inhx_xx_not_null | n | 0 | t
+ pp1 | pp1_f1_not_null | n | 0 | t
+(4 rows)
+
+-- same for cc2
+alter table cc2 alter column f1 drop not null;
+ERROR: cannot drop inherited NOT NULL constraint "cc2_f1_not_null", relation "cc2"
+-- remove from cc1, should fail again
+alter table cc1 alter column f1 drop not null;
+ERROR: cannot drop inherited NOT NULL constraint "cc1_f1_not_null", relation "cc1"
+-- remove from pp1, should succeed
+alter table pp1 alter column f1 drop not null;
+-- have a look at pg_constraint
+select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' order by 2;
+ conrelid | conname | contype | coninhcount | conislocal
+----------+------------------+---------+-------------+------------
+ inhx | inhx_xx_not_null | n | 0 | t
+(1 row)
+
+drop table pp1 cascade;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table cc1
+drop cascades to table cc2
+-- have a look at pg_constraint, everything should be clean now
+select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' order by 2;
+ conrelid | conname | contype | coninhcount | conislocal
+----------+------------------+---------+-------------+------------
+ inhx | inhx_xx_not_null | n | 0 | t
+(1 row)
+
+--
+-- test inherit/deinherit
+--
+create table parent(f1 int);
+create table child1(f1 int not null);
+create table child2(f1 int);
+-- child1 should have not null constraint
+alter table child1 inherit parent;
+-- should fail, missing NOT NULL constraint
+alter table child2 inherit child1;
+ERROR: column "f1" in child table must be marked NOT NULL
+alter table child2 alter column f1 set not null;
+alter table child2 inherit child1;
+-- add NOT NULL constraint recursively
+alter table parent alter column f1 set not null;
+\d parent
+ Table "public.parent"
+ Column | Type | Modifiers
+--------+---------+-----------
+ f1 | integer | not null
+Number of child tables: 1 (Use \d+ to list them.)
+
+\d child1
+ Table "public.child1"
+ Column | Type | Modifiers
+--------+---------+-----------
+ f1 | integer | not null
+Inherits: parent
+Number of child tables: 1 (Use \d+ to list them.)
+
+\d child2
+ Table "public.child2"
+ Column | Type | Modifiers
+--------+---------+-----------
+ f1 | integer | not null
+Inherits: child1
+
+select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' order by 2;
+ conrelid | conname | contype | coninhcount | conislocal
+----------+--------------------+---------+-------------+------------
+ child1 | child1_f1_not_null | n | 1 | t
+ child2 | child2_f1_not_null | n | 1 | t
+ inhx | inhx_xx_not_null | n | 0 | t
+ parent | parent_f1_not_null | n | 0 | t
+(4 rows)
+
+--
+-- test deinherit procedure
+--
+-- deinherit child1
+alter table child1 no inherit parent;
+\d parent
+ Table "public.parent"
+ Column | Type | Modifiers
+--------+---------+-----------
+ f1 | integer | not null
+
+\d child1
+ Table "public.child1"
+ Column | Type | Modifiers
+--------+---------+-----------
+ f1 | integer | not null
+Number of child tables: 1 (Use \d+ to list them.)
+
+\d child2
+ Table "public.child2"
+ Column | Type | Modifiers
+--------+---------+-----------
+ f1 | integer | not null
+Inherits: child1
+
+select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' order by 2;
+ conrelid | conname | contype | coninhcount | conislocal
+----------+--------------------+---------+-------------+------------
+ child1 | child1_f1_not_null | n | 0 | t
+ child2 | child2_f1_not_null | n | 1 | t
+ inhx | inhx_xx_not_null | n | 0 | t
+ parent | parent_f1_not_null | n | 0 | t
+(4 rows)
+
+-- test inhcount of child2, should fail
+alter table child2 alter f1 drop not null;
+ERROR: cannot drop inherited NOT NULL constraint "child2_f1_not_null", relation "child2"
+-- should succeed
+drop table parent;
+drop table child1 cascade;
+NOTICE: drop cascades to table child2
+--
+-- test multi inheritance tree
+--
+create table parent(f1 int not null);
+create table c1() inherits(parent);
+create table c2() inherits(parent);
+create table d1() inherits(c1, c2);
+NOTICE: merging multiple inherited definitions of column "f1"
+-- show constraint info
+select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' order by 2;
+ conrelid | conname | contype | coninhcount | conislocal
+----------+--------------------+---------+-------------+------------
+ inhx | inhx_xx_not_null | n | 0 | t
+ parent | parent_f1_not_null | n | 0 | t
+ c1 | parent_f1_not_null | n | 1 | f
+ c2 | parent_f1_not_null | n | 1 | f
+ d1 | parent_f1_not_null | n | 2 | f
+(5 rows)
+
+drop table parent cascade;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table c1
+drop cascades to table c2
+drop cascades to table d1
+-- test child table with inherited columns and
+-- with explicitely specified not null constraints
+create table parent1(f1 int);
+create table parent2(f2 text);
+create table child(f1 int not null, f2 text not null) inherits(parent1, parent2);
+NOTICE: merging column "f1" with inherited definition
+NOTICE: merging column "f2" with inherited definition
+-- show constraint info
+select conrelid::regclass, conname, contype, coninhcount, conislocal, conkey from pg_constraint where contype = 'n' order by 2;
+ conrelid | conname | contype | coninhcount | conislocal | conkey
+----------+-------------------+---------+-------------+------------+--------
+ child | child_f1_not_null | n | 0 | t | {1}
+ child | child_f2_not_null | n | 0 | t | {2}
+ inhx | inhx_xx_not_null | n | 0 | t | {1}
+(3 rows)
+
+-- also drops child table
+drop table parent1 cascade;
+NOTICE: drop cascades to table child
+drop table parent2;
+-- test multi layer inheritance tree
+create table p1(f1 int not null);
+create table p2(f1 int not null);
+create table p3(f2 int);
+create table p4(f1 int not null, f3 text not null);
+create table c() inherits(p1, p2, p3, p4);
+NOTICE: merging multiple inherited definitions of column "f1"
+NOTICE: merging multiple inherited definitions of column "f1"
+ERROR: relation "c" already exists
+-- constraint on f1 should have three parents
+select conrelid::regclass, conname, contype, coninhcount, conislocal, conkey from pg_constraint where contype = 'n' order by 2;
+ conrelid | conname | contype | coninhcount | conislocal | conkey
+----------+------------------+---------+-------------+------------+--------
+ inhx | inhx_xx_not_null | n | 0 | t | {1}
+ p1 | p1_f1_not_null | n | 0 | t | {1}
+ p2 | p2_f1_not_null | n | 0 | t | {1}
+ p4 | p4_f1_not_null | n | 0 | t | {1}
+ p4 | p4_f3_not_null | n | 0 | t | {2}
+(5 rows)
+
+create table d(a int not null, f1 int) inherits(p3, c);
+ERROR: relation "d" already exists
+select conrelid::regclass, conname, contype, coninhcount, conislocal, conkey from pg_constraint where contype = 'n' order by 2;
+ conrelid | conname | contype | coninhcount | conislocal | conkey
+----------+------------------+---------+-------------+------------+--------
+ inhx | inhx_xx_not_null | n | 0 | t | {1}
+ p1 | p1_f1_not_null | n | 0 | t | {1}
+ p2 | p2_f1_not_null | n | 0 | t | {1}
+ p4 | p4_f1_not_null | n | 0 | t | {1}
+ p4 | p4_f3_not_null | n | 0 | t | {2}
+(5 rows)
+
+drop table p1 cascade;
+drop table p2;
+drop table p3;
+drop table p4;
diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql
index 1cc447b8a2..c3ad7ebca3 100644
--- a/src/test/regress/sql/domain.sql
+++ b/src/test/regress/sql/domain.sql
@@ -224,6 +224,16 @@ update domnotnull set col1 = null;
drop domain dnotnulltest cascade;
+create domain dnotnulltest integer constraint dnn not null;
+
+select conname, contype, contypid::regtype from pg_constraint c
+ where contype = 'n' and contypid <> 0 order by 1;
+
+drop domain dnotnulltest;
+
+select conname, contype, contypid::regtype from pg_constraint
+ where contype = 'n' and contypid <> 0 order by 1;
+
-- Test ALTER DOMAIN .. DEFAULT ..
create table domdeftest (col1 ddef1);
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index 3087a14b72..8edc4d3784 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -406,3 +406,140 @@ select * from matest0 order by 1-id;
reset enable_seqscan;
drop table matest0 cascade;
+
+--
+-- Test inheritance of NOT NULL constraints
+--
+create table pp1 (f1 int);
+create table cc1 (f2 text, f3 int) inherits (pp1);
+\d cc1
+create table cc2(f4 float) inherits(pp1,cc1);
+\d cc2
+
+-- named NOT NULL constraint
+alter table cc1 add column a2 int constraint nn not null;
+\d cc1
+\d cc2
+alter table pp1 alter column f1 set not null;
+\d pp1
+\d cc1
+\d cc2
+
+-- have a look at pg_constraint
+select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' order by 2;
+
+-- remove constraint from cc2, should fail
+alter table cc2 alter column a2 drop not null;
+
+-- remove constraint cc1, should succeed
+alter table cc1 alter column a2 drop not null;
+
+-- have a look at pg_constraint
+select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' order by 2;
+
+-- same for cc2
+alter table cc2 alter column f1 drop not null;
+
+-- remove from cc1, should fail again
+alter table cc1 alter column f1 drop not null;
+
+-- remove from pp1, should succeed
+alter table pp1 alter column f1 drop not null;
+
+-- have a look at pg_constraint
+select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' order by 2;
+
+drop table pp1 cascade;
+
+-- have a look at pg_constraint, everything should be clean now
+select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' order by 2;
+
+--
+-- test inherit/deinherit
+--
+create table parent(f1 int);
+create table child1(f1 int not null);
+create table child2(f1 int);
+
+-- child1 should have not null constraint
+alter table child1 inherit parent;
+
+-- should fail, missing NOT NULL constraint
+alter table child2 inherit child1;
+
+alter table child2 alter column f1 set not null;
+alter table child2 inherit child1;
+
+-- add NOT NULL constraint recursively
+alter table parent alter column f1 set not null;
+
+\d parent
+\d child1
+\d child2
+
+select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' order by 2;
+
+--
+-- test deinherit procedure
+--
+
+-- deinherit child1
+alter table child1 no inherit parent;
+\d parent
+\d child1
+\d child2
+select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' order by 2;
+
+-- test inhcount of child2, should fail
+alter table child2 alter f1 drop not null;
+
+-- should succeed
+
+drop table parent;
+drop table child1 cascade;
+
+--
+-- test multi inheritance tree
+--
+create table parent(f1 int not null);
+create table c1() inherits(parent);
+create table c2() inherits(parent);
+create table d1() inherits(c1, c2);
+
+-- show constraint info
+select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' order by 2;
+
+drop table parent cascade;
+
+-- test child table with inherited columns and
+-- with explicitely specified not null constraints
+create table parent1(f1 int);
+create table parent2(f2 text);
+create table child(f1 int not null, f2 text not null) inherits(parent1, parent2);
+
+-- show constraint info
+select conrelid::regclass, conname, contype, coninhcount, conislocal, conkey from pg_constraint where contype = 'n' order by 2;
+
+-- also drops child table
+drop table parent1 cascade;
+drop table parent2;
+
+-- test multi layer inheritance tree
+create table p1(f1 int not null);
+create table p2(f1 int not null);
+create table p3(f2 int);
+create table p4(f1 int not null, f3 text not null);
+
+create table c() inherits(p1, p2, p3, p4);
+
+-- constraint on f1 should have three parents
+select conrelid::regclass, conname, contype, coninhcount, conislocal, conkey from pg_constraint where contype = 'n' order by 2;
+
+create table d(a int not null, f1 int) inherits(p3, c);
+
+select conrelid::regclass, conname, contype, coninhcount, conislocal, conkey from pg_constraint where contype = 'n' order by 2;
+
+drop table p1 cascade;
+drop table p2;
+drop table p3;
+drop table p4;