notnull_constraint.patch
application/octet-stream
Filename: notnull_constraint.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| src/backend/catalog/heap.c | 0 | 0 |
| src/backend/commands/tablecmds.c | 0 | 0 |
| src/backend/parser/parse_utilcmd.c | 0 | 0 |
| src/backend/utils/adt/ruleutils.c | 0 | 0 |
| src/include/catalog/heap.h | 0 | 0 |
| src/include/catalog/pg_constraint.h | 0 | 0 |
| src/include/nodes/parsenodes.h | 0 | 0 |
| src/test/regress/expected/alter_table.out | 0 | 0 |
| src/test/regress/expected/cluster.out | 0 | 0 |
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 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