diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index d848ef0..d55fa59 100644 *** a/src/backend/catalog/heap.c --- b/src/backend/catalog/heap.c *************** StoreAttrDefault(Relation rel, AttrNumbe *** 1692,1697 **** --- 1692,1737 ---- } /* + * 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 *** 1815,1820 **** --- 1855,1863 ---- 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 9b5ce65..62defe2 100644 *** a/src/backend/commands/tablecmds.c --- b/src/backend/commands/tablecmds.c *************** static void ATExecAddColumn(AlteredTable *** 275,281 **** static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid); static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd); ! static void ATExecDropNotNull(Relation rel, const char *colName); static void ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, const char *colName); static void ATExecColumnDefault(Relation rel, const char *colName, --- 275,282 ---- static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid); static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd); ! static void ATExecDropNotNull(Relation rel, const char *colName, DropBehavior behavior, ! bool recurse, bool recursing); static void ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, const char *colName); static void ATExecColumnDefault(Relation rel, const char *colName, *************** static void ATExecDropInherit(Relation r *** 332,338 **** static void copy_relation_data(SMgrRelation rel, SMgrRelation dst, ForkNumber forkNum, bool istemp); static const char *storage_name(char c); ! /* ---------------------------------------------------------------- * DefineRelation --- 333,346 ---- 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 *** 495,500 **** --- 503,526 ---- attnum++; + if (colDef->is_not_null) { + /* + * Adjust NOT NULL constraint of this column + * to hold new attnum and inheritance information + */ + 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 *** 2469,2475 **** break; case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ ATSimplePermissions(rel, false); ! ATSimpleRecursion(wqueue, rel, cmd, recurse); /* No command-specific prep needed */ pass = AT_PASS_DROP; break; --- 2495,2502 ---- 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 *** 2696,2702 **** ATExecColumnDefault(rel, cmd->name, cmd->def); break; case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ ! ATExecDropNotNull(rel, cmd->name); break; case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */ ATExecSetNotNull(tab, rel, cmd->name); --- 2723,2734 ---- ATExecColumnDefault(rel, cmd->name, cmd->def); break; case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ ! ATExecDropNotNull(rel, cmd->name, cmd->behavior, ! false, false); ! break; ! case AT_DropNotNullRecurse: /* ALTER COLUMN DROP NOT NULL with recursion */ ! ATExecDropNotNull(rel, cmd->name, cmd->behavior, ! true, false); break; case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */ ATExecSetNotNull(tab, rel, cmd->name); *************** ATPrepAddOids(List **wqueue, Relation re *** 3909,3955 **** * ALTER TABLE ALTER COLUMN DROP NOT NULL */ static void ! ATExecDropNotNull(Relation rel, const char *colName) { ! 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; --- 3941,4288 ---- * ALTER TABLE ALTER COLUMN DROP NOT NULL */ static void ! ATExecDropNotNull(Relation rel, const char *colName, DropBehavior behavior, ! bool recurse, bool recursing) { ! 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. ! * 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); ! } ! 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); ! ! elog(DEBUG1, "ATExecDropNotNullInternal, relation %s", ! RelationGetRelationName(rel)); ! ! /* 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 *** 3957,3963 **** 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) { --- 4290,4296 ---- 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 *** 3967,3999 **** */ 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); } /* --- 4300,4362 ---- */ 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, *** 4026,4039 **** 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); --- 4389,4430 ---- 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 *** 5610,5615 **** --- 6001,6010 ---- 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 *** 5620,5626 **** 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; --- 6015,6023 ---- 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 *** 5692,5698 **** con = (Form_pg_constraint) GETSTRUCT(tuple); ! /* Right now only CHECK constraints can be inherited */ if (con->contype != CONSTRAINT_CHECK) continue; --- 6089,6098 ---- 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 *** 6380,6385 **** --- 6780,6786 ---- 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); diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 90d5c76..b9410ee 100644 *** a/src/backend/parser/parse_utilcmd.c --- b/src/backend/parser/parse_utilcmd.c *************** transformColumnDefinition(ParseState *ps *** 453,458 **** --- 453,459 ---- 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 d16f1c4..58e1884 100644 *** a/src/backend/utils/adt/ruleutils.c --- b/src/backend/utils/adt/ruleutils.c *************** pg_get_constraintdef_worker(Oid constrai *** 1051,1063 **** 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; --- 1051,1092 ---- 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 8292273..8eb04e3 100644 *** a/src/include/catalog/heap.h --- b/src/include/catalog/heap.h *************** extern List *AddRelationNewConstraints(R *** 90,96 **** bool is_local); extern void StoreAttrDefault(Relation rel, AttrNumber attnum, Node *expr); ! extern Node *cookDefault(ParseState *pstate, Node *raw_default, Oid atttypid, --- 90,96 ---- 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 c3dfcb0..99d672f 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 b591073..a98ba53 100644 *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** typedef enum AlterTableType *** 1113,1119 **** --- 1113,1121 ---- 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 5aff44f..9d31275 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