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..171e8b5 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,4588 ---- * 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; ! ! found = false; ! children = NIL; ! ! 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, ©_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 *** 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) { --- 4590,4596 ---- 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 --- 4600,4684 ---- */ 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); } /* --- 4699,4911 ---- 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; ! 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; ! ! 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, ©_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 **** --- 6483,6492 ---- 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; --- 6497,6505 ---- 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; --- 6570,6579 ---- 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 **** --- 7300,7306 ---- 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 **** --- 8062,8068 ---- 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. --- 8149,8159 ---- 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; --- 8229,8236 ---- * 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 **** --- 8277,8287 ---- 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; --- 8320,8327 ---- * 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 */ --- 8348,8355 ---- 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 --- 8365,8411 ---- 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) --- 8448,8455 ---- * 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; /* --- 8462,8468 ---- attributeTuple, constraintTuple, depTuple; ! List *constraints; bool found = false; /* *************** ATExecDropInherit(Relation rel, RangeVar *** 7874,7879 **** --- 8512,8519 ---- RelationGetRelationName(parent_rel), RelationGetRelationName(rel)))); + constraints = NIL; + /* * Search through child columns looking for ones matching parent rel */ *************** ATExecDropInherit(Relation rel, RangeVar *** 7887,7892 **** --- 8527,8533 ---- 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, ©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); --- 8535,8584 ---- 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 *** 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); --- 8589,8607 ---- 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) --- 8619,8667 ---- 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