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 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