diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 9403dc1..5b82dd8 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 1e5ac13..af87b58 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 *** 275,282 **** 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, --- 289,297 ---- 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(AlteredTableInfo *tab, Relation rel, const char *colName, LOCKMODE lockmode); static void ATExecColumnDefault(Relation rel, const char *colName, *************** static void ATExecDropInherit(Relation r *** 335,341 **** static void copy_relation_data(SMgrRelation rel, SMgrRelation dst, ForkNumber forkNum, bool istemp); static const char *storage_name(char c); ! /* ---------------------------------------------------------------- * DefineRelation --- 350,363 ---- 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 *** 507,512 **** --- 529,555 ---- 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 *** 2666,2672 **** break; case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ ATSimplePermissions(rel, false); ! ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); /* No command-specific prep needed */ pass = AT_PASS_DROP; break; --- 2709,2718 ---- break; case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ ATSimplePermissions(rel, false); ! ! if (recurse) ! cmd->subtype = AT_DropNotNullRecurse; ! /* No command-specific prep needed */ pass = AT_PASS_DROP; break; *************** ATExecCmd(List **wqueue, AlteredTableInf *** 2896,2902 **** 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); --- 2942,2953 ---- 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(tab, rel, cmd->name, lockmode); *************** ATPrepAddOids(List **wqueue, Relation re *** 4111,4157 **** * 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; --- 4162,4507 ---- * 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; ! ListCell *child; ! HeapTuple constr_tuple; AttrNumber attnum; ! Relation constr_rel; ! SysScanDesc scan; ! ScanKeyData key; ! bool found; ! ! if (recursing) ! ATSimplePermissions(rel, 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, ©_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, ©_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 *** 4159,4165 **** 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) { --- 4509,4515 ---- 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 *** 4169,4201 **** */ 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); } /* --- 4519,4581 ---- */ 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); } /* *************** ATExecSetNotNull(AlteredTableInfo *tab, *** 4228,4241 **** 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); --- 4608,4649 ---- if (attnum <= 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("cannot alter system column \"%s\" of relation \"%s\"", ! colName, RelationGetRelationName(rel)))); /* * Okay, actually perform the catalog change ... if needed */ if (!((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull) { + /* + * 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. + */ + CookedConstraint *cooked = palloc(sizeof(CookedConstraint)); + cooked->contype = CONSTR_NOTNULL; + cooked->expr = NULL; + cooked->name = ChooseConstraintName(RelationGetRelationName(rel), + NameStr(((Form_pg_attribute) GETSTRUCT(tuple))->attname), + "not_null", RelationGetNamespace(rel), NIL); + cooked->attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum; + cooked->is_local = ((Form_pg_attribute) GETSTRUCT(tuple))->attislocal; + cooked->inhcount = ((Form_pg_attribute) GETSTRUCT(tuple))->attinhcount; + StoreColumnNotNullConstraint(rel, cooked); + + /* + * Make changes visible + */ + CommandCounterIncrement(); + + /* + * Finally record in pg_attribute that there is a NOT NULL + * constraint. + */ ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE; simple_heap_update(attr_rel, &tuple->t_self, tuple); *************** ATExecDropConstraint(Relation rel, const *** 5821,5826 **** --- 6229,6238 ---- 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 *** 5831,5837 **** 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; --- 6243,6251 ---- 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 *** 5902,5908 **** con = (Form_pg_constraint) GETSTRUCT(tuple); ! /* Right now only CHECK constraints can be inherited */ if (con->contype != CONSTRAINT_CHECK) continue; --- 6316,6325 ---- 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 *** 6595,6600 **** --- 7012,7018 ---- 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 *** 7356,7361 **** --- 7774,7780 ---- 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 *** 7442,7451 **** 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. --- 7861,7871 ---- 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 *** 7521,7527 **** * 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; --- 7941,7948 ---- * 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 *** 7568,7573 **** --- 7989,7999 ---- 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 *** 7606,7612 **** * 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; --- 8032,8039 ---- * 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 *** 7633,7639 **** HeapTuple child_tuple; bool found = false; ! if (parent_con->contype != CONSTRAINT_CHECK) continue; /* Search for a child constraint matching this one */ --- 8060,8067 ---- 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 *** 7649,7667 **** 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 --- 8077,8123 ---- 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 *** 7704,7711 **** * 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) --- 8160,8167 ---- * 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 *** 7718,7724 **** attributeTuple, constraintTuple, depTuple; ! List *connames; bool found = false; /* --- 8174,8180 ---- attributeTuple, constraintTuple, depTuple; ! List *constraints; bool found = false; /* *************** ATExecDropInherit(Relation rel, RangeVar *** 7768,7773 **** --- 8224,8231 ---- RelationGetRelationName(parent_rel), RelationGetRelationName(rel)))); + constraints = NIL; + /* * Search through child columns looking for ones matching parent rel */ *************** ATExecDropInherit(Relation rel, RangeVar *** 7781,7786 **** --- 8239,8245 ---- 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 *** 7788,7816 **** 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, ©Tuple->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); --- 8247,8296 ---- 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, ©Tuple->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 *** 7821,7834 **** 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); --- 8301,8319 ---- 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 *** 7846,7863 **** 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) --- 8331,8379 ---- 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 af25c21..3ab752c 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/port/pg_latch.c b/src/backend/port/pg_latch.c index ...002f2f4 . *** a/src/backend/port/pg_latch.c --- b/src/backend/port/pg_latch.c *************** *** 0 **** --- 1 ---- + ../../../src/backend/port/unix_latch.c \ No newline at end of file diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index eece757..a6e8619 100644 *** a/src/backend/utils/adt/ruleutils.c --- b/src/backend/utils/adt/ruleutils.c *************** pg_get_constraintdef_worker(Oid constrai *** 1071,1083 **** 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; --- 1071,1112 ---- 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 07db4a3..dda3dd5 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 ff87b6f..cd95391 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 e896dc7..8364b27 100644 *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** typedef enum AlterTableType *** 1116,1122 **** --- 1116,1124 ---- 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 83e24fd..9afd2bd 100644 *** a/src/test/regress/expected/alter_table.out --- b/src/test/regress/expected/alter_table.out *************** 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; 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