notnull_constraint2.patch

application/octet-stream

Filename: notnull_constraint2.patch
Type: application/octet-stream
Part: 0
Message: Re: Re: starting to review the Extend NOT NULL representation to pg_constraint patch

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
File+
src/backend/catalog/heap.c 0 0
src/backend/commands/tablecmds.c 0 0
src/backend/parser/parse_utilcmd.c 0 0
src/backend/utils/adt/ruleutils.c 0 0
src/include/catalog/heap.h 0 0
src/include/catalog/pg_constraint.h 0 0
src/include/nodes/parsenodes.h 0 0
src/test/regress/expected/alter_table.out 0 0
src/test/regress/expected/cluster.out 0 0
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index dcc53e1..c976e46 100644
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
*************** StoreAttrDefault(Relation rel, AttrNumbe
*** 1714,1719 ****
--- 1714,1759 ----
  }
  
  /*
+  * 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,
+ 						  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
*************** StoreConstraints(Relation rel, List *coo
*** 1837,1842 ****
--- 1877,1885 ----
  
  		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 c009711..88d574b 100644
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
*************** typedef struct NewColumnValue
*** 175,180 ****
--- 175,192 ----
  } 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;
+ 
+ /*
   * Error-reporting support for RemoveRelations
   */
  struct dropmsgstrings
*************** static List *MergeAttributes(List *schem
*** 227,234 ****
  				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 StoreCatalogInheritance(Oid relationId, List *supers);
  static void StoreCatalogInheritance1(Oid relationId, Oid parentOid,
  						 int16 seqNumber, Relation inhRelation);
--- 239,248 ----
  				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, 
! 										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);
*************** static void ATExecAddColumn(AlteredTable
*** 276,285 ****
  				ColumnDef *colDef, bool isOid, LOCKMODE lockmode);
  static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
  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);
  static void ATExecColumnDefault(Relation rel, const char *colName,
  					Node *newDefault, LOCKMODE lockmode);
  static void ATPrepSetStatistics(Relation rel, const char *colName,
--- 290,304 ----
  				ColumnDef *colDef, bool isOid, LOCKMODE lockmode);
  static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
  static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
! 						  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, 
! 							 LOCKMODE lockmode);
! static void ATExecSetNotNullInternal(Relation rel, Relation attr_rel,
! 									 HeapTuple atttup, bool *is_new_constraint,
! 									 bool is_local);
  static void ATExecColumnDefault(Relation rel, const char *colName,
  					Node *newDefault, LOCKMODE lockmode);
  static void ATPrepSetStatistics(Relation rel, const char *colName,
*************** static void ATExecDropInherit(Relation r
*** 336,342 ****
  static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
  				   ForkNumber forkNum, bool istemp);
  static const char *storage_name(char c);
! 
  
  /* ----------------------------------------------------------------
   *		DefineRelation
--- 355,368 ----
  static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
  				   ForkNumber forkNum, bool istemp);
  static const char *storage_name(char c);
! static bool ATExecDropNotNullInternal(Relation rel, HeapTuple constrtup,
! 									  DropBehavior behavior,
! 									  bool recurse, bool recursing);
! static bool
! CheckNotNullOnAttributeName(Relation rel, const char *colname, 
! 							AttrNumber *attnum);
! static void
! DropNotNullOnAttributeNum(Relation rel, AttrNumber attnum, bool lock);
  
  /* ----------------------------------------------------------------
   *		DefineRelation
*************** DefineRelation(CreateStmt *stmt, char re
*** 508,513 ****
--- 534,560 ----
  
  		attnum++;
  
+ 		if (colDef->is_not_null) {
+ 			/*
+ 			 * Adjust NOT NULL constraint of this column
+ 			 * to hold new attnum and inheritance information
+ 			 *
+ 			 * XXX: Currently the constraint is cheated into cookedDefaults
+ 			 * list, maybe we need to invent a different machinerie/naming.
+ 			 */
+ 			CookedConstraint *cooked;
+ 
+ 			cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
+ 			cooked->contype = CONSTR_NOTNULL;
+ 			cooked->name = ChooseConstraintName(relname, NameStr(descriptor->attrs[attnum - 1]->attname),
+ 												"not_null", namespaceId, NIL);
+ 			cooked->attnum = attnum;
+ 			cooked->expr = NULL;
+ 			cooked->is_local = colDef->is_local;
+ 			cooked->inhcount = colDef->inhcount;
+ 			cookedDefaults = lappend(cookedDefaults, cooked);
+ 		}
+ 
  		if (colDef->raw_default != NULL)
  		{
  			RawColumnDefault *rawEnt;
*************** ATPrepCmd(List **wqueue, Relation rel, A
*** 2682,2696 ****
  			/* No command-specific prep needed */
  			pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
  			break;
! 		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
  			ATSimplePermissions(rel, false, false);
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
  			/* No command-specific prep needed */
  			pass = AT_PASS_DROP;
  			break;
  		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
  			ATSimplePermissions(rel, false, false);
! 			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
  			/* No command-specific prep needed */
  			pass = AT_PASS_ADD_CONSTR;
  			break;
--- 2729,2748 ----
  			/* No command-specific prep needed */
  			pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
  			break;
! 		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */			
  			ATSimplePermissions(rel, false, false);
! 
! 			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, false, false);
! 
! 			if (recurse)
! 				cmd->subtype = AT_SetNotNullRecurse;
! 
  			/* No command-specific prep needed */
  			pass = AT_PASS_ADD_CONSTR;
  			break;
*************** ATExecCmd(List **wqueue, AlteredTableInf
*** 2914,2923 ****
  			ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
  			break;
  		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
! 			ATExecDropNotNull(rel, cmd->name, lockmode);
  			break;
  		case AT_SetNotNull:		/* ALTER COLUMN SET NOT NULL */
! 			ATExecSetNotNull(tab, rel, cmd->name, lockmode);
  			break;
  		case AT_SetStatistics:	/* ALTER COLUMN SET STATISTICS */
  			ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
--- 2966,2983 ----
  			ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
  			break;
  		case AT_DropNotNull:	/* ALTER COLUMN DROP NOT NULL */
! 			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(wqueue, tab, rel, cmd->name, false, false, lockmode);
! 			break;
! 		case AT_SetNotNullRecurse: /* ALTER COLUMN SET NOT NULL recursing */
! 			ATExecSetNotNull(wqueue, tab, rel, cmd->name, true, false, lockmode);
  			break;
  		case AT_SetStatistics:	/* ALTER COLUMN SET STATISTICS */
  			ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
*************** ATRewriteTable(AlteredTableInfo *tab, Oi
*** 3441,3448 ****
  				if (heap_attisnull(tuple, attn + 1))
  					ereport(ERROR,
  							(errcode(ERRCODE_NOT_NULL_VIOLATION),
! 							 errmsg("column \"%s\" contains null values",
! 								NameStr(newTupDesc->attrs[attn]->attname))));
  			}
  
  			foreach(l, tab->constraints)
--- 3501,3509 ----
  				if (heap_attisnull(tuple, attn + 1))
  					ereport(ERROR,
  							(errcode(ERRCODE_NOT_NULL_VIOLATION),
! 							 errmsg("column \"%s\" of relation \"%s\" contains null values",
! 									NameStr(newTupDesc->attrs[attn]->attname),
! 									RelationGetRelationName(oldrel))));
  			}
  
  			foreach(l, tab->constraints)
*************** ATPrepAddOids(List **wqueue, Relation re
*** 4179,4225 ****
   * ALTER TABLE ALTER COLUMN DROP NOT NULL
   */
  static void
! ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
  {
! 	HeapTuple	tuple;
  	AttrNumber	attnum;
! 	Relation	attr_rel;
! 	List	   *indexoidlist;
! 	ListCell   *indexoidscan;
  
  	/*
! 	 * lookup the attribute
  	 */
! 	attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
  
! 	tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
  
! 	if (!HeapTupleIsValid(tuple))
  		ereport(ERROR,
  				(errcode(ERRCODE_UNDEFINED_COLUMN),
  				 errmsg("column \"%s\" of relation \"%s\" does not exist",
! 						colName, RelationGetRelationName(rel))));
  
! 	attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
  
  	/* Prevent them from altering a system attribute */
! 	if (attnum <= 0)
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("cannot alter system column \"%s\"",
! 						colName)));
  
  	/*
! 	 * Check that the attribute is not in a primary key
  	 */
  
  	/* Loop over all indexes on the relation */
  	indexoidlist = RelationGetIndexList(rel);
! 
  	foreach(indexoidscan, indexoidlist)
  	{
! 		Oid			indexoid = lfirst_oid(indexoidscan);
! 		HeapTuple	indexTuple;
  		Form_pg_index indexStruct;
  		int			i;
  
--- 4240,4585 ----
   * ALTER TABLE ALTER COLUMN DROP NOT NULL
   */
  static void
! ATExecDropNotNull(Relation rel, const char *colName, DropBehavior behavior,
! 				  bool recurse, bool recursing, LOCKMODE lockmode)
  {
! 	List	   *children = NULL;
! 	ListCell   *child;
! 	HeapTuple   constr_tuple;
  	AttrNumber	attnum;
! 	Relation    constr_rel;
! 	SysScanDesc scan;
! 	ScanKeyData key;
! 	bool        found = false;
! 
! 	if (recursing)
! 		ATSimplePermissions(rel, false, false);
  
  	/*
! 	 * 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;
  
! 	/*
! 	 * Scan through our constraint records, looking for the matching
! 	 * NOT NULL constraint.
! 	 */
! 	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_tuple = systable_getnext(scan)))
! 	{
! 		Form_pg_constraint constrStruct;
! 		Datum              arrayp;
! 		Datum             *keyvals;
! 		int                nelems;
! 		bool               isnull;
  
! 		constrStruct = (Form_pg_constraint) GETSTRUCT(constr_tuple);
! 
! 		if (constrStruct->contype != CONSTRAINT_NOTNULL)
! 			continue;
! 
! 		/*
! 		 * Check wether this constraint is attached to the given column.
! 		 * ATExecSetNotNull() ensures that only one NOT NULL constraint tuple
! 		 * lives per attribute.
! 		 */
! 		arrayp = SysCacheGetAttr(CONSTROID, constr_tuple, Anum_pg_constraint_conkey, &isnull);
! 
! 		/* should not happen */
! 		Assert(!isnull);
! 
! 		deconstruct_array(DatumGetArrayTypeP(arrayp), INT2OID, 2, true, 
! 						  's', &keyvals, NULL, &nelems);
! 
! 		/* Is this NOT NULL constraint attached to the current tuple? */
! 		if (DatumGetInt16(keyvals[0]) != attnum) 
! 			continue;
! 
! 		/*
! 		 * Check if the attribute is inherited from another relation or
! 		 * part of a primary key.
! 		 * If true, error out, otherwise perform the deletion of the constraint.
! 		 *
! 		 * Note: ATExecDropNotNullInternal() performs a CCI, if required.
! 		 */
! 		if ((found = ATExecDropNotNullInternal(rel,
! 											   constr_tuple,
! 											   behavior,
! 											   recurse, recursing)))
! 		{			
! 			/*
! 			 * Okay, actually remove the NOT NULL from pg_attribute. 
! 			 * CheckNotNullOnAttributeName() already holds a lock on pg_attribute, so
! 			 * override an additional lock here.
! 			 */
! 			DropNotNullOnAttributeNum(rel, attnum, false);
! 			break;
! 		}
! 	}
! 
! 	systable_endscan(scan);
! 
! 	if (found)
! 	{
! 		/*
! 		 * Propagate to children as appropriate.
! 		 */
! 		children = find_inheritance_children(RelationGetRelid(rel),
! 											 AccessExclusiveLock);
! 	}
! 	else
! 	{
! 		ereport(ERROR,
! 				(errcode(ERRCODE_UNDEFINED_OBJECT),
! 				 errmsg("column \"%s\" of relation \"%s\" has no NOT NULL constraint",
! 						colName,
! 						RelationGetRelationName(rel))));
! 	}
! 
! 	foreach(child, children)
! 	{
! 		Oid childrelid = lfirst_oid(child);
! 		Relation childrel;
! 
! 		childrel = heap_open(childrelid, NoLock);
! 		CheckTableNotInUse(childrel, "ALTER TABLE");
! 		
! 		ScanKeyInit(&key,
! 					Anum_pg_constraint_conrelid,
! 					BTEqualStrategyNumber, F_OIDEQ,
! 					ObjectIdGetDatum(childrelid));
! 		scan = systable_beginscan(constr_rel, ConstraintRelidIndexId,
! 								  true, SnapshotNow, 1, &key);
! 
! 		while (HeapTupleIsValid(constr_tuple = systable_getnext(scan)))
! 		{
! 			HeapTuple copy_tuple;
! 			Form_pg_constraint constrStruct;
! 			Datum              arrayp;
! 			Datum             *keyvals;
! 			int                nelems;
! 			bool               isnull;
! 			
! 			constrStruct = (Form_pg_constraint) GETSTRUCT(constr_tuple);
! 			
! 			if (constrStruct->contype != CONSTRAINT_NOTNULL)
! 				continue;
! 			
! 			/*
! 			 * Check wether this constraint is attached to the given column.
! 			 * ATExecSetNotNull() ensures that only one NOT NULL constraint tuple
! 			 * lives per attribute.
! 			 */
! 			arrayp = SysCacheGetAttr(CONSTROID, constr_tuple, Anum_pg_constraint_conkey, &isnull);
! 			
! 			/* should not happen */
! 			Assert(!isnull);
! 			
! 			deconstruct_array(DatumGetArrayTypeP(arrayp), INT2OID, 2, true, 
! 							  's', &keyvals, NULL, &nelems);
! 			
! 			/* Is this NOT NULL constraint attached to the current tuple? */
! 			if (DatumGetInt16(keyvals[0]) != attnum) 
! 				continue;
! 
! 			found = true;
! 
! 			if (constrStruct->coninhcount <= 0) /* shouldn't happen */
! 				elog(ERROR, "relation %u has non-inherited NOT NULL constraint \"%s\"",
! 					 childrelid, NameStr(constrStruct->conname));
! 			
! 			copy_tuple = heap_copytuple(constr_tuple);
! 			constrStruct = (Form_pg_constraint) GETSTRUCT(copy_tuple);
! 
! 			if (recurse)
! 			{
! 				/*
! 				 * If the child constraint has other definition sources, just
! 				 * decrement its inheritance count; if not, recurse to delete
! 				 * it.
! 				 */
! 				if (constrStruct->coninhcount == 1 && !constrStruct->conislocal)
! 				{
! 					/* Time to delete this child constraint, too */
! 					ATExecDropNotNull(childrel, colName, behavior,
! 									  true, true, lockmode);
! 				}
! 				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(childrel, NoLock);
! 
! 	}
! 
! 	heap_close(constr_rel, NoLock);
! 	
! }
! 
! /*
!  * 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 bool
! ATExecDropNotNullInternal(Relation rel, HeapTuple constrtup,
! 						  DropBehavior behavior,
! 						  bool recurse, bool recursing)
! {
! 	bool               found;
! 	Form_pg_constraint constrStruct;
! 	ObjectAddress      conobj;
! 
! 	found = false;
! 
! 	if (HeapTupleIsValid(constrtup))
! 	{
! 		
! 		constrStruct = (Form_pg_constraint) GETSTRUCT(constrtup);
! 
! 		/*
! 		 * Be paranoid, caller is responsible to pass a valid HeapTuple.
! 		 */
! 		Assert(constrStruct->contype == CONSTRAINT_NOTNULL);
! 		
! 		/* 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))));
! 		} 
! 
! 		/*
! 		 * Time to drop the constraint now.
! 		 */
! 		conobj.classId = ConstraintRelationId;
! 		conobj.objectId = HeapTupleGetOid(constrtup);
! 		conobj.objectSubId = 0;
! 
! 		performDeletion(&conobj, behavior);
! 
! 		/*
! 		 * Make sure changes are visible
! 		 */
! 		CommandCounterIncrement();
! 		found = true;
! 			
! 	}
! 
! 	return found;
! }
! 
! /*
!  * 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
+ 	 */
+ 	
  	/* Loop over all indexes on the relation */
  	indexoidlist = RelationGetIndexList(rel);
! 	
  	foreach(indexoidscan, indexoidlist)
  	{
! 		Oid			  indexoid = lfirst_oid(indexoidscan);
! 		HeapTuple	  indexTuple;
  		Form_pg_index indexStruct;
  		int			i;
  
*************** ATExecDropNotNull(Relation rel, const ch
*** 4227,4233 ****
  		if (!HeapTupleIsValid(indexTuple))
  			elog(ERROR, "cache lookup failed for index %u", indexoid);
  		indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
! 
  		/* If the index is not a primary key, skip the check */
  		if (indexStruct->indisprimary)
  		{
--- 4587,4593 ----
  		if (!HeapTupleIsValid(indexTuple))
  			elog(ERROR, "cache lookup failed for index %u", indexoid);
  		indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
! 		
  		/* If the index is not a primary key, skip the check */
  		if (indexStruct->indisprimary)
  		{
*************** ATExecDropNotNull(Relation rel, const ch
*** 4237,4281 ****
  			 */
  			for (i = 0; i < indexStruct->indnatts; i++)
  			{
! 				if (indexStruct->indkey.values[i] == attnum)
  					ereport(ERROR,
  							(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
  							 errmsg("column \"%s\" is in a primary key",
! 									colName)));
  			}
  		}
! 
  		ReleaseSysCache(indexTuple);
  	}
  
! 	list_free(indexoidlist);
! 
! 	/*
! 	 * Okay, actually perform the catalog change ... if needed
  	 */
! 	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
! 	{
! 		((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = FALSE;
  
! 		simple_heap_update(attr_rel, &tuple->t_self, tuple);
  
! 		/* keep the system catalog indexes current */
! 		CatalogUpdateIndexes(attr_rel, tuple);
! 	}
  
- 	heap_close(attr_rel, RowExclusiveLock);
  }
  
  /*
   * ALTER TABLE ALTER COLUMN SET NOT NULL
   */
  static void
! ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
! 				 const char *colName, LOCKMODE lockmode)
  {
  	HeapTuple	tuple;
  	AttrNumber	attnum;
  	Relation	attr_rel;
  
  	/*
  	 * lookup the attribute
--- 4597,4681 ----
  			 */
  			for (i = 0; i < indexStruct->indnatts; i++)
  			{
! 				if (indexStruct->indkey.values[i] == attr_struct->attnum)
  					ereport(ERROR,
  							(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
  							 errmsg("column \"%s\" is in a primary key",
! 									colname)));
  			}
  		}
! 		
  		ReleaseSysCache(indexTuple);
  	}
  
! 	/* 
! 	 * 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.
!  */
! static void
! DropNotNullOnAttributeNum(Relation rel, AttrNumber attnum, bool lock)
! {
! 	Relation attr_rel;
! 	HeapTuple attr_tuple;
! 	Form_pg_attribute attr_struct;
! 
! 	if (lock)
! 		attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
! 	else
! 		attr_rel = heap_open(AttributeRelationId, NoLock);
! 
! 	attr_tuple = SearchSysCacheCopy(ATTNUM,
! 									ObjectIdGetDatum(RelationGetRelid(rel)),
! 									Int16GetDatum(attnum),
! 									0, 0);
! 
! 	attr_struct = (Form_pg_attribute) GETSTRUCT(attr_tuple);
! 
! 	/* not expected, so be paranoid */
! 	Assert(attr_struct->attnotnull);
! 
! 	((Form_pg_attribute) GETSTRUCT(attr_tuple))->attnotnull = FALSE;
! 	simple_heap_update(attr_rel, &attr_tuple->t_self, attr_tuple);
! 
! 	/* keep the system catalog indexes current */
! 	CatalogUpdateIndexes(attr_rel, attr_tuple);
! 
! 	heap_close(attr_rel, NoLock);
  
  }
  
  /*
   * ALTER TABLE ALTER COLUMN SET NOT NULL
   */
  static void
! ATExecSetNotNull(List **wqueue, AlteredTableInfo *tab, Relation rel,
! 				 const char *colName, bool recurse, bool recursing, 
! 				 LOCKMODE lockmode)
  {
  	HeapTuple	tuple;
  	AttrNumber	attnum;
  	Relation	attr_rel;
+ 	List       *children;
+ 	ListCell   *child;
+ 	bool		is_new_constraint;
+ 
+ 	if (recursing)
+ 		ATSimplePermissions(rel, false, false);
+ 
+ 	children = find_inheritance_children(RelationGetRelid(rel),
+ 										 AccessExclusiveLock);
  
  	/*
  	 * lookup the attribute
*************** ATExecSetNotNull(AlteredTableInfo *tab,
*** 4296,4321 ****
  	if (attnum <= 0)
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("cannot alter system column \"%s\"",
! 						colName)));
  
  	/*
! 	 * Okay, actually perform the catalog change ... if needed
  	 */
! 	if (!((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
  	{
! 		((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE;
  
! 		simple_heap_update(attr_rel, &tuple->t_self, tuple);
  
! 		/* keep the system catalog indexes current */
! 		CatalogUpdateIndexes(attr_rel, tuple);
  
! 		/* Tell Phase 3 it needs to test the constraint */
! 		tab->new_notnull = true;
  	}
  
! 	heap_close(attr_rel, RowExclusiveLock);
  }
  
  /*
--- 4696,4907 ----
  	if (attnum <= 0)
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("cannot alter system column \"%s\" of relation \"%s\"",
! 						colName, RelationGetRelationName(rel))));
  
  	/*
! 	 * 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).
! 	 * Maybe we should treat this as a no op, but it seems
! 	 * better to inform the user that he's doing something
! 	 * wrong.
  	 */
! 	if (!recurse)
! 	{		
! 		if (children)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
! 					 errmsg("NOT NULL constraint must be added to child tables too")));
! 	}
! 
! 	/*
! 	 * 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.
! 	 */
! 	ATExecSetNotNullInternal(rel, attr_rel, tuple, &is_new_constraint, !recursing);
! 	
! 	/* Tell Phase 3 it needs to test the constraint */
! 	tab->new_notnull = true;
! 	
! 	heap_freetuple(tuple);
! 	heap_close(attr_rel, RowExclusiveLock);
! 	
! 	/*
! 	 * Loop through all child relations
! 	 */
! 	foreach(child, children)
  	{
! 		Oid      childrelid = lfirst_oid(child);
! 		Relation childrel;
! 		bool     is_new_constraint;
! 		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, recurse, true, 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 is_local)
! {
! 	Form_pg_attribute attr;
! 
! 	attr = (Form_pg_attribute) GETSTRUCT(atttup);
! 	*is_new_constraint = FALSE;
! 
! 	if (attr->attnotnull)
! 	{
! 		SysScanDesc scan;
! 		ScanKeyData key;
! 		HeapTuple   constr_tup;
! 		Relation    constr_rel;
! 		bool        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;
! 			Datum   arrayp;
! 			Datum  *keyvals;
! 			int     nelems;
! 			bool    isnull;
! 
! 			constr_struct = (Form_pg_constraint) GETSTRUCT(constr_tup);
! 
! 			/* We need to look for NOT NULL constraint only */
! 			if (constr_struct->contype != CONSTRAINT_NOTNULL)
! 				continue;
! 
! 			/* constraint tuple attached to our column? */
! 			arrayp = SysCacheGetAttr(CONSTROID, constr_tup, 
! 									 Anum_pg_constraint_conkey,
! 									 &isnull);
! 			/* not expected here */
! 			Assert(!isnull);
! 			
! 			deconstruct_array(DatumGetArrayTypeP(arrayp), INT2OID, 2, true,
! 							  's', &keyvals, NULL, &nelems);
! 			if (DatumGetInt16(keyvals[0]) != attr->attnum)
! 				continue;
! 
! 			/* only reached in case we have found the required constraint tuple */
! 			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);
! 
! 			/*
! 			 * We don't need to update the constraint tuple when
! 			 * the inheritance counter is already up to date
! 			 */
! 			if (constr->coninhcount != attr->attinhcount)
! 			{
! 				constr->coninhcount = is_local ? 0 : attr->attinhcount;
! 				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();
! 				*is_new_constraint = TRUE;
! 			}
! 
! 			heap_freetuple(copy_tuple);
! 		}
! 		
! 		systable_endscan(scan);
! 		heap_close(constr_rel, NoLock);		
! 
! 	}
! 	else
! 	{
! 		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);
! 		
! 		/* Make changes visible */
! 		CommandCounterIncrement();
! 		
! 		attr->attnotnull = TRUE;
! 		simple_heap_update(attr_rel, &atttup->t_self, atttup);
! 		
! 		/* keep the system catalog indexes current */
! 		CatalogUpdateIndexes(attr_rel, atttup);
! 
! 		*is_new_constraint = TRUE;
! 	}
  }
  
  /*
*************** ATExecDropConstraint(Relation rel, const
*** 5893,5898 ****
--- 6479,6488 ----
  
  		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;
  
*************** ATExecDropConstraint(Relation rel, const
*** 5903,5909 ****
  					 errmsg("cannot drop inherited constraint \"%s\" of relation \"%s\"",
  							constrName, RelationGetRelationName(rel))));
  
! 		/* Right now only CHECK constraints can be inherited */
  		if (con->contype == CONSTRAINT_CHECK)
  			is_check_constraint = true;
  
--- 6493,6501 ----
  					 errmsg("cannot drop inherited constraint \"%s\" of relation \"%s\"",
  							constrName, RelationGetRelationName(rel))));
  
! 		/* This is a CHECK constraint, remember that we found one
! 		 * and proceed to drop it
! 		 */
  		if (con->contype == CONSTRAINT_CHECK)
  			is_check_constraint = true;
  
*************** ATExecDropConstraint(Relation rel, const
*** 5974,5980 ****
  
  			con = (Form_pg_constraint) GETSTRUCT(tuple);
  
! 			/* Right now only CHECK constraints can be inherited */
  			if (con->contype != CONSTRAINT_CHECK)
  				continue;
  
--- 6566,6575 ----
  
  			con = (Form_pg_constraint) GETSTRUCT(tuple);
  
! 			/* 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;
  
*************** ATPostAlterTypeParse(char *cmd, List **w
*** 6701,6706 ****
--- 7296,7302 ----
  								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);
*************** ATExecAddInherit(Relation child_rel, Ran
*** 7462,7467 ****
--- 8058,8064 ----
  	HeapTuple	inheritsTuple;
  	int32		inhseqno;
  	List	   *children;
+ 	List       *child_attnums;
  
  	/*
  	 * AccessShareLock on the parent is what's obtained during normal CREATE
*************** ATExecAddInherit(Relation child_rel, Ran
*** 7548,7557 ****
  						RelationGetRelationName(parent_rel))));
  
  	/* Match up the columns and bump attinhcount as needed */
! 	MergeAttributesIntoExisting(child_rel, parent_rel);
  
  	/* Match up the constraints and bump coninhcount as needed */
! 	MergeConstraintsIntoExisting(child_rel, parent_rel);
  
  	/*
  	 * OK, it looks valid.	Make the catalog entries that show inheritance.
--- 8145,8155 ----
  						RelationGetRelationName(parent_rel))));
  
  	/* Match up the columns and bump attinhcount as needed */
! 	child_attnums = NIL;
! 	MergeAttributesIntoExisting(child_rel, parent_rel, &child_attnums);
  
  	/* Match up the constraints and bump coninhcount as needed */
! 	MergeConstraintsIntoExisting(child_rel, parent_rel, child_attnums);
  
  	/*
  	 * OK, it looks valid.	Make the catalog entries that show inheritance.
*************** constraints_equivalent(HeapTuple a, Heap
*** 7627,7633 ****
   * the child must be as well. Defaults are not compared, however.
   */
  static void
! MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
  {
  	Relation	attrrel;
  	AttrNumber	parent_attno;
--- 8225,8232 ----
   * the child must be as well. Defaults are not compared, however.
   */
  static void
! MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel,
! 							List **attnums_list)
  {
  	Relation	attrrel;
  	AttrNumber	parent_attno;
*************** MergeAttributesIntoExisting(Relation chi
*** 7674,7679 ****
--- 8273,8283 ----
  					   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.)
  			 */
*************** MergeAttributesIntoExisting(Relation chi
*** 7712,7718 ****
   * a problem though. Even 100 constraints ought not be the end of the world.
   */
  static void
! MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
  {
  	Relation	catalog_relation;
  	TupleDesc	tuple_desc;
--- 8316,8323 ----
   * a problem though. Even 100 constraints ought not be the end of the world.
   */
  static void
! MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel,
! 							 List *child_attnums)
  {
  	Relation	catalog_relation;
  	TupleDesc	tuple_desc;
*************** MergeConstraintsIntoExisting(Relation ch
*** 7739,7745 ****
  		HeapTuple	child_tuple;
  		bool		found = false;
  
! 		if (parent_con->contype != CONSTRAINT_CHECK)
  			continue;
  
  		/* Search for a child constraint matching this one */
--- 8344,8351 ----
  		HeapTuple	child_tuple;
  		bool		found = false;
  
! 		if (parent_con->contype != CONSTRAINT_CHECK
! 			&& parent_con->contype != CONSTRAINT_NOTNULL)
  			continue;
  
  		/* Search for a child constraint matching this one */
*************** MergeConstraintsIntoExisting(Relation ch
*** 7755,7773 ****
  			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))));
  
  			/*
  			 * OK, bump the child constraint's inheritance count.  (If we fail
--- 8361,8407 ----
  			Form_pg_constraint child_con = (Form_pg_constraint) GETSTRUCT(child_tuple);
  			HeapTuple	child_copy;
  
! 			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
*************** MergeConstraintsIntoExisting(Relation ch
*** 7810,7817 ****
   * 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.
   */
  static void
  ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
--- 8444,8451 ----
   * 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 CHECK and NOT NULL constraints 
!  * are adjusted in exactly the same way.
   */
  static void
  ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
*************** ATExecDropInherit(Relation rel, RangeVar
*** 7824,7830 ****
  				attributeTuple,
  				constraintTuple,
  				depTuple;
! 	List	   *connames;
  	bool		found = false;
  
  	/*
--- 8458,8464 ----
  				attributeTuple,
  				constraintTuple,
  				depTuple;
! 	List	   *constraints;
  	bool		found = false;
  
  	/*
*************** ATExecDropInherit(Relation rel, RangeVar
*** 7874,7879 ****
--- 8508,8515 ----
  						RelationGetRelationName(parent_rel),
  						RelationGetRelationName(rel))));
  
+ 	constraints = NIL;
+ 
  	/*
  	 * Search through child columns looking for ones matching parent rel
  	 */
*************** ATExecDropInherit(Relation rel, RangeVar
*** 7887,7892 ****
--- 8523,8529 ----
  	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)
*************** ATExecDropInherit(Relation rel, RangeVar
*** 7894,7922 ****
  		if (att->attinhcount <= 0)
  			continue;
  
! 		if (SearchSysCacheExistsAttName(RelationGetRelid(parent_rel),
! 										NameStr(att->attname)))
  		{
  			/* Decrement inhcount and possibly set islocal to true */
  			HeapTuple	copyTuple = heap_copytuple(attributeTuple);
  			Form_pg_attribute copy_att = (Form_pg_attribute) GETSTRUCT(copyTuple);
! 
  			copy_att->attinhcount--;
  			if (copy_att->attinhcount == 0)
  				copy_att->attislocal = true;
  
  			simple_heap_update(catalogRelation, &copyTuple->t_self, copyTuple);
  			CatalogUpdateIndexes(catalogRelation, copyTuple);
  			heap_freetuple(copyTuple);
  		}
  	}
  	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.)
  	 */
  	catalogRelation = heap_open(ConstraintRelationId, RowExclusiveLock);
--- 8531,8580 ----
  		if (att->attinhcount <= 0)
  			continue;
  
! 		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 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.  (We cheat a bit by only checking for name matches,
  	 * assuming that the expressions will match.)
  	 */
  	catalogRelation = heap_open(ConstraintRelationId, RowExclusiveLock);
*************** ATExecDropInherit(Relation rel, RangeVar
*** 7927,7940 ****
  	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)));
  	}
  
  	systable_endscan(scan);
--- 8585,8603 ----
  	scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
  							  true, SnapshotNow, 1, key);
  
  	while (HeapTupleIsValid(constraintTuple = systable_getnext(scan)))
  	{
  		Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
  
  		if (con->contype == CONSTRAINT_CHECK)
! 		{
! 			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);
*************** ATExecDropInherit(Relation rel, RangeVar
*** 7952,7969 ****
  		Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
  		bool		match;
  		ListCell   *lc;
  
! 		if (con->contype != CONSTRAINT_CHECK)
  			continue;
  
  		match = false;
! 		foreach(lc, connames)
  		{
! 			if (strcmp(NameStr(con->conname), (char *) lfirst(lc)) == 0)
  			{
  				match = true;
  				break;
  			}
  		}
  
  		if (match)
--- 8615,8663 ----
  		Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
  		bool		match;
  		ListCell   *lc;
+ 		Datum       arrayp;
+ 		Datum      *keyvals;
+ 		int         nelems;
+ 		bool        isnull;
  
! 		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, constraints)
  		{
! 			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)
+ 			{
+ 				arrayp = SysCacheGetAttr(CONSTROID, constraintTuple, 
+ 										 Anum_pg_constraint_conkey, &isnull);
+ 				
+ 				deconstruct_array(DatumGetArrayTypeP(arrayp), INT2OID, 2, true,
+ 								  's', &keyvals, NULL, &nelems);
+ 				if (keyvals[0] == inhInfo->attnum)
+ 				{
+ 					match = true;
+ 					break;
+ 				}
+ 			}
  		}
  
  		if (match)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 37ca331..b4d8d3d 100644
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
*************** transformColumnDefinition(ParseState *ps
*** 466,471 ****
--- 466,472 ----
  							 parser_errposition(pstate,
  												constraint->location)));
  				column->is_not_null = TRUE;
+ 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
  				saw_nullable = true;
  				break;
  
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f1c1d04..7913cf5 100644
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** pg_get_constraintdef_worker(Oid constrai
*** 1076,1088 ****
  
  	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)));
  	}
  
  	switch (conForm->contype)
  	{
  		case CONSTRAINT_FOREIGN:
  			{
  				Datum		val;
--- 1076,1117 ----
  
  	if (fullCommand && OidIsValid(conForm->conrelid))
  	{
! 		/* 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);
+ 				
+ 				/* Should not happen */
+ 				if (isnull)
+ 					elog(ERROR, "null conkey for constraint %u",
+ 						 constraintId);
+ 
+ 				decompile_column_index_array(val, conForm->conrelid, &buf);
+ 				appendStringInfo(&buf, " SET NOT NULL");
+ 				break;
+ 		    }
  		case CONSTRAINT_FOREIGN:
  			{
  				Datum		val;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 7795bda..4f55a57 100644
*** a/src/include/catalog/heap.h
--- b/src/include/catalog/heap.h
*************** extern List *AddRelationNewConstraints(R
*** 91,97 ****
  						  bool is_local);
  
  extern void StoreAttrDefault(Relation rel, AttrNumber attnum, Node *expr);
! 
  extern Node *cookDefault(ParseState *pstate,
  			Node *raw_default,
  			Oid atttypid,
--- 91,97 ----
  						  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 49e7c31..a0abc65 100644
*** a/src/include/catalog/pg_constraint.h
--- b/src/include/catalog/pg_constraint.h
*************** typedef FormData_pg_constraint *Form_pg_
*** 175,180 ****
--- 175,181 ----
  
  /* 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 ca225d0..7e5b81f 100644
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef enum AlterTableType
*** 1117,1123 ****
--- 1117,1125 ----
  	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 ab19a8e..f1585e1 100644
*** a/src/test/regress/expected/alter_table.out
--- b/src/test/regress/expected/alter_table.out
*************** create table atacc1 ( test int );
*** 503,509 ****
  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
  insert into atacc1 (test) values (3);
  drop table atacc1;
  -- let's do one where the primary key constraint fails
--- 503,509 ----
  insert into atacc1 (test) values (NULL);
  -- add a primary key (fails)
  alter table atacc1 add constraint atacc_test1 primary key (test);
! 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
*************** insert into atacc1 (test) values (0);
*** 520,526 ****
  -- 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
  -- 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"
--- 520,526 ----
  -- 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" 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"
*************** alter table atacc1 drop constraint "atac
*** 583,589 ****
  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
  delete from atacc1;
  alter table atacc1 alter test set not null;
  -- try altering a non-existent column, should fail
--- 583,589 ----
  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" 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
*************** alter table atacc1 alter bar drop not nu
*** 593,601 ****
  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"
  alter table atacc1 alter oid drop not null;
! ERROR:  cannot alter system column "oid"
  -- 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;
--- 593,601 ----
  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" of relation "atacc1"
  alter table atacc1 alter oid drop not null;
! 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;
*************** alter table parent alter a drop not null
*** 616,628 ****
  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
  alter table child alter a set not null;
! ERROR:  column "a" contains null values
  delete from parent;
  alter table only parent alter a set not null;
  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
--- 616,628 ----
  insert into parent values (NULL);
  insert into child (a, b) values (NULL, 'foo');
  alter table only parent alter a set not null;
! ERROR:  NOT NULL constraint must be added to child tables too
  alter table child alter a set not null;
! 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);
  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 96bd816..6d0e3e1 100644
*** a/src/test/regress/expected/cluster.out
--- b/src/test/regress/expected/cluster.out
*************** ERROR:  insert or update on table "clstr
*** 251,261 ****
  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     
! ----------------
   clstr_tst_con
   clstr_tst_pkey
! (2 rows)
  
  SELECT relname, relkind,
      EXISTS(SELECT 1 FROM pg_class WHERE oid = c.reltoastrelid) AS hastoast
--- 251,262 ----
  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        
! ----------------------
!  clstr_tst_a_not_null
   clstr_tst_con
   clstr_tst_pkey
! (3 rows)
  
  SELECT relname, relkind,
      EXISTS(SELECT 1 FROM pg_class WHERE oid = c.reltoastrelid) AS hastoast