ALTER TABLE...ALTER COLUMN vs inheritance
I just run across an issue with ALTER TABLE and inheritance (i don't know
wether this is of the same kind of issue KaiGai reported today, so i keep
it on a separate thread).
Consider the following workflow:
CREATE TABLE foo(id integer NOT NULL, val text NOT NULL);
CREATE TABLE foo2(another_id integer NOT NULL) INHERITS(foo);
Now someone decides he doesn't want the NOT NULL constraint on the
inherited column "val" anymore:
ALTER TABLE foo2 ALTER COLUMN val DROP NOT NULL;
This breaks at least pg_dump, which will produce unrestorable dumps at
least when using NULL-values as intended on foo2.
I havent thought about that very deep, but we already force ALTER TABLE
... INHERIT that columns have to be NOT NULL when the new parent already
has a constraint on it, so it seems to me that the best way to repair this
deficiency is to introduce the same rules in ALTER TABLE...ALTER COLUMN,
too.
The described workflow is currently used in OpenERP, which employs such an
inheritance structure on some of its tables (however, making ALTER TABLE
here more strict will surely break OpenERP, but that is another story).
Opinions?
--
Thanks
Bernd
Bernd Helmle <mailings@oopsware.de> writes:
Consider the following workflow:
CREATE TABLE foo(id integer NOT NULL, val text NOT NULL);
CREATE TABLE foo2(another_id integer NOT NULL) INHERITS(foo);
Now someone decides he doesn't want the NOT NULL constraint on the
inherited column "val" anymore:
ALTER TABLE foo2 ALTER COLUMN val DROP NOT NULL;
Yeah, this is a known issue. The ALTER should be rejected, but it is
not, because we don't have enough infrastructure to notice that the
constraint is inherited and logically can't be dropped. I think the
consensus was that the way to fix this (along with some other problems)
is to start representing NOT NULL constraints in pg_constraint, turning
attnotnull into just a bit of denormalization for performance.
regards, tom lane
--On 4. November 2009 09:57:27 -0500 Tom Lane <tgl@sss.pgh.pa.us> wrote:
I think the
consensus was that the way to fix this (along with some other problems)
is to start representing NOT NULL constraints in pg_constraint, turning
attnotnull into just a bit of denormalization for performance.
Ah okay, should have checked the archive more carefully. I think i give it
a try...
--
Thanks
Bernd
--On 4. November 2009 09:57:27 -0500 Tom Lane <tgl@sss.pgh.pa.us> wrote:
I think the
consensus was that the way to fix this (along with some other problems)
is to start representing NOT NULL constraints in pg_constraint, turning
attnotnull into just a bit of denormalization for performance.
I've just started looking into this and wonder how this should look like.
My first idea is to just introduce a special contype in pg_constraint
representing a NOT NULL constraint on a column, which holds all required
information to do the mentioned maintenance stuff on them and to keep most
of the current infrastructure. Utility commands need to track all changes
in pg_constraint and keep pg_attribute.attnotnull up to date.
Another possibility is to change the representation of NOT NULL to be a
CHECK constraint (e.g. CHECK(col IS NOT NULL)) internally and leave all the
responsibility up to the current existing check constraint infrastructure
(which already does the right thing for inheritance, e.g. it's not possible
to drop such a constraint if it was inherited).
ALTER TABLE ... SET NOT NULL and DROP NOT NULL will be just syntactic sugar
then.
I don't know the original design decisions for the current representation,
but it seems it wasn't essential?
Of course, there's still the requirement to special case those check
constraints in various places, since pg_dump and psql have to do the right
thing.
--
Thanks
Bernd
Bernd Helmle <mailings@oopsware.de> writes:
My first idea is to just introduce a special contype in pg_constraint
representing a NOT NULL constraint on a column, which holds all required
information to do the mentioned maintenance stuff on them and to keep most
of the current infrastructure. Utility commands need to track all changes
in pg_constraint and keep pg_attribute.attnotnull up to date.
Another possibility is to change the representation of NOT NULL to be a
CHECK constraint (e.g. CHECK(col IS NOT NULL)) internally and leave all the
responsibility up to the current existing check constraint infrastructure
(which already does the right thing for inheritance, e.g. it's not possible
to drop such a constraint if it was inherited).
I'd go for the first of those, for sure. Testing attnotnull is
significantly cheaper than enforcing a generic constraint expression,
and NOT NULL is a sufficiently common case to be worth worrying about
optimizing it. Furthermore, removing attnotnull would break an unknown
but probably not-negligible amount of client-side code that cares
whether columns are known not null (I think both JDBC and ODBC track
that). And it would significantly complicate code in the backend that
wants to determine whether a column is known not null --- there are
several proposals for planner optimizations that would depend on knowing
that, for example.
You will find yourself copying-and-pasting a fair amount of the
check-constraint code if you implement this as a completely separate
code path, and so it might be worthwhile to be creative about exactly
what the pg_constraint representations look like --- maybe you can
design it to share some of that code. But I recommend strongly that
attnotnull stay there.
regards, tom lane
On Thu, Nov 12, 2009 at 13:55, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I'd go for the first of those, for sure. Testing attnotnull is
significantly cheaper than enforcing a generic constraint expression,
and NOT NULL is a sufficiently common case to be worth worrying about
optimizing it.
When I looked at doing this, I thought about just using check
constraints just for the book keeping and leaving attnotnull as it is.
If would be easier, but it seemed quite ugly.
Alex Hunsaker <badalex@gmail.com> writes:
On Thu, Nov 12, 2009 at 13:55, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I'd go for the first of those, for sure. Testing attnotnull is
significantly cheaper than enforcing a generic constraint expression,
and NOT NULL is a sufficiently common case to be worth worrying about
optimizing it.
When I looked at doing this, I thought about just using check
constraints just for the book keeping and leaving attnotnull as it is.
Yeah, you could definitely attack it like that. The code that fixes up
attnotnull would have to look for check constraints that look like "foo
NOT NULL" rather than something more instantly recognizable, but
presumably ALTER TABLE is not a performance-critical path.
regards, tom lane
On Thu, Nov 12, 2009 at 11:56, Bernd Helmle <mailings@oopsware.de> wrote:
I've just started looking into this and wonder how this should look like.
IIRC another motivation for moving them into pg_constraint was we
could then give them names as required by the spec (unless I got mixed
up with defaults). Looking at the 2003 spec I don't see any grammar
for that, so either I cant find it (likely) or its not there. Either
way I see something like the below options:
ALTER TABLE ALTER COLUMN ADD CONSTRAINT my_not_null NOT NULL;
[ we dont currently support add constraint on ALTER COLUMN AFAICT...
but it might be nice? ]
-or-
ALTER TABLE ADD CONSTRAINT my_not_null NOT NULL (column);
-or-
ALTER TABLE ALTER COLUMN column SET NOT NULL 'name';
Comments?
Anyway Bernd if you are working on this great! If not lemme know, Ill
plan on having something for the next commit feast. Though I still
may never get around to it :(.
FYI defaults have the same problem. Would it be awkward would it be
to use pg_constraint for the book keeping as well? [ and by that I
really mean ALTER TABLE ADD CONSTRAINT my_default DEFAULT .... so you
can give them a name ]
Alex Hunsaker <badalex@gmail.com> writes:
FYI defaults have the same problem. Would it be awkward would it be
to use pg_constraint for the book keeping as well? [ and by that I
really mean ALTER TABLE ADD CONSTRAINT my_default DEFAULT .... so you
can give them a name ]
That sounds moderately insane to me. Why would you need a name?
What would it mean to have more than one default attached to a column?
regards, tom lane
On Mon, Nov 16, 2009 at 11:45, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Alex Hunsaker <badalex@gmail.com> writes:
FYI defaults have the same problem. Would it be awkward would it be
to use pg_constraint for the book keeping as well? [ and by that I
really mean ALTER TABLE ADD CONSTRAINT my_default DEFAULT .... so you
can give them a name ]That sounds moderately insane to me. Why would you need a name?
I don't care strongly enough to argue for them. I just thought if it
was something the spec said or someone wanted it would be easy to add
while in the area :) Sorry for the insane hand waving.
We already have pg_attrdef, all we really need is the inhcount and
islocal columns on that. No reason to bring pg_constraint into it all
at.
What would it mean to have more than one default attached to a column?
"It would be like so far out dude"
Ok so my hippie impression needs work...
--On 16. November 2009 11:00:33 -0700 Alex Hunsaker <badalex@gmail.com>
wrote:
Anyway Bernd if you are working on this great! If not lemme know, Ill
plan on having something for the next commit feast. Though I still
may never get around to it :(.
I'm just working on it.
The current patch assigns <tablename>_<col>_not_null (by using
ChooseConstraintName()) as the constraint name to NOT NULL, i record the
attnum this NOT NULL belongs to in conkey. So far so good, creating the
constraints already works, i'm going to adjust the utility commands now.
One thing i just stumpled across: I guess we want the same behavior for
dropping NOT NULL constraints recursively like we already do for CHECK
constraints.
I thought i can reuse some of the infrastructure of ATExecDropConstraint(),
but this seems somekind awful, since it requires a constraint name and we
already did the scanning of pg_constraint up to this point. Since i don't
like duplicating too much code i'm thinking about splitting
ATExecDropConstraint() in an additional function
ATExecDropConstraintInternal(), which does the real work for a given
constraint OID.
--
Thanks
Bernd
--On 4. November 2009 09:57:27 -0500 Tom Lane <tgl@sss.pgh.pa.us> wrote:
Yeah, this is a known issue. The ALTER should be rejected, but it is
not, because we don't have enough infrastructure to notice that the
constraint is inherited and logically can't be dropped. I think the
consensus was that the way to fix this (along with some other problems)
is to start representing NOT NULL constraints in pg_constraint, turning
attnotnull into just a bit of denormalization for performance.
Lost it a little from my radar, but here's is a first shot on this issue
now. The patch creates a new CONSTRAINT_NOTNULL contype and assigns all
required information for the NOT NULL constraint to it. Currently the
constraint records the attribute number it belongs to and manages the
inheritance properties. Passes regression tests with some adjustments to
pg_constraint output.
The patch as it stands employs a dedicated code path for
ATExecDropNotNull(), thus duplicates the behavior of
ATExecDropConstraint(). I'm not really satisfied with this, but i did it
this way to prevent some heavy conditional rearrangement in
ATExecDropConstraint(). Maybe its worth to move the code to adjust
constraint inheritance properties into a separate function.
There's also a remaining issue which needs to be addressed: currently
pg_get_constraintdef_worker() is special case'd to dump the correct syntax
for the NOT NULL constraint, which is totally redundant. I'm not sure how
to do it correctly, since for example ATExecAlterColumnType() actually uses
it to restore dependent constraints on a column. We might want to just move
the special case there.
I understand that people are busy with the remaining open items list for
9.0, so it's okay to discuss this during the upcoming reviewfest.
Bernd
Attachments:
notnull_constraint.patchapplication/octet-stream; name=notnull_constraint.patchDownload
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
On Wed, May 26, 2010 at 2:37 PM, Bernd Helmle <mailings@oopsware.de> wrote:
Lost it a little from my radar, but here's is a first shot on this issue
now. The patch creates a new CONSTRAINT_NOTNULL contype and assigns all
required information for the NOT NULL constraint to it. Currently the
constraint records the attribute number it belongs to and manages the
inheritance properties. Passes regression tests with some adjustments to
pg_constraint output.
Confirmed that this tests fine against commit id
0dca7d2f70872a242d4430c4c3aa01ba8dbd4a8c
I also wrote a little test script and verified that it throws an error
when I try to remove a constraint from the parent table.
Should an explicit test be added for this?
There are some spelling and grammar errors in comments that I can help
fix if you want the help.
-selena
--
http://chesnok.com/daily - me
--On 15. Juni 2010 20:51:21 -0700 Selena Deckelmann <selenamarie@gmail.com>
wrote:
Confirmed that this tests fine against commit id
0dca7d2f70872a242d4430c4c3aa01ba8dbd4a8cI also wrote a little test script and verified that it throws an error
when I try to remove a constraint from the parent table.
Thanks for looking at this.
Please note that the main purpose of this patch is to protect *child*
tables against dropping NOT NULL constraints inherited from a parent table.
This could lead to unrestorable dumps formerly.
Should an explicit test be added for this?
I think so, yes.
There are some spelling and grammar errors in comments that I can help
fix if you want the help.
You're welcome. I have pushed my branch to my git repos as well, if you
like to start from there:
<http://git.postgresql.org/gitweb?p=bernd_pg.git;a=shortlog;h=refs/heads/notnull_constraint>
--
Thanks
Bernd
On Wed, Jun 16, 2010 at 5:31 AM, Bernd Helmle <mailings@oopsware.de> wrote:
--On 15. Juni 2010 20:51:21 -0700 Selena Deckelmann <selenamarie@gmail.com>
wrote:Confirmed that this tests fine against commit id
0dca7d2f70872a242d4430c4c3aa01ba8dbd4a8cI also wrote a little test script and verified that it throws an error
when I try to remove a constraint from the parent table.Thanks for looking at this.
Please note that the main purpose of this patch is to protect *child* tables
against dropping NOT NULL constraints inherited from a parent table. This
could lead to unrestorable dumps formerly.
Yes! I didn't say that right earlier -- sorry I should have attached
the test. I'll just try to add it and send it to you in patch form.
Should an explicit test be added for this?
I think so, yes.
There are some spelling and grammar errors in comments that I can help
fix if you want the help.You're welcome. I have pushed my branch to my git repos as well, if you like
to start from there:<http://git.postgresql.org/gitweb?p=bernd_pg.git;a=shortlog;h=refs/heads/notnull_constraint>
Awesome! I'll have a look this afternoon.
And, I didn't really know what to say about the rest of the issues you
brought up around structuring the code, and the couple TODOs still
left in the patch.
-selena
--
http://chesnok.com/daily - me
--On 16. Juni 2010 14:31:00 +0200 Bernd Helmle <mailings@oopsware.de> wrote:
There are some spelling and grammar errors in comments that I can help
fix if you want the help.You're welcome. I have pushed my branch to my git repos as well, if you
like to start from there:<http://git.postgresql.org/gitweb?p=bernd_pg.git;a=shortlog;h=refs/heads/
notnull_constraint>
I'm going to delay this patch until the next commitfest. I'm able to work
on it only sporadically during the next two weeks, and some remaining
issues need my attention on this patch:
- ALTER TABLE SET NOT NULL works not properly
- ALTER TABLE child NO INHERIT parent leaves inconistent state in
pg_constraint
- Special case in pg_get_constraintdef_worker() needs more thinking
--
Thanks
Bernd
--On 23. Juli 2010 09:23:56 +0200 Bernd Helmle <mailings@oopsware.de> wrote:
I'm going to delay this patch until the next commitfest. I'm able to work
on it only sporadically during the next two weeks, and some remaining
issues need my attention on this patch:- ALTER TABLE SET NOT NULL works not properly
- ALTER TABLE child NO INHERIT parent leaves inconistent state in
pg_constraint
- Special case in pg_get_constraintdef_worker() needs more thinking
Attached is my current progress on this work. It handles ALTER TABLE ...
[NO] INHERIT and NOT NULL constraints the same way we do with CHECK
constraints now. ALTER TABLE SET NOT NULL still lags support for this (ran
out of time to this commitfest deadline), but if nobody complains i would
like to add this to the current commitfest as WIP to hear other opinions
about the current approach again.
--
Thanks
Bernd
Attachments:
notnull_constraint.patchapplication/octet-stream; name=notnull_constraint.patchDownload
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9403dc1..5b82dd8 100644
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
*************** StoreAttrDefault(Relation rel, AttrNumbe
*** 1714,1719 ****
--- 1714,1759 ----
}
/*
+ * Stores a NOT NULL constraint of a column into pg_constraint.
+ */
+ void
+ StoreColumnNotNullConstraint(Relation rel, CookedConstraint *cooked)
+ {
+
+ /*
+ * Store the constraint. Reflect conislocal and coninhcount to
+ * match the same values as the attached column.
+ */
+ CreateConstraintEntry(cooked->name,
+ RelationGetNamespace(rel),
+ CONSTRAINT_NOTNULL,
+ false,
+ false,
+ RelationGetRelid(rel),
+ &(cooked->attnum),
+ 1,
+ InvalidOid,
+ InvalidOid,
+ InvalidOid,
+ NULL,
+ InvalidOid,
+ InvalidOid,
+ InvalidOid,
+ 0,
+ ' ',
+ ' ',
+ ' ',
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ cooked->is_local,
+ cooked->inhcount
+ );
+
+ }
+
+ /*
* Store a check-constraint expression for the given relation.
*
* Caller is responsible for updating the count of constraints
*************** StoreConstraints(Relation rel, List *coo
*** 1837,1842 ****
--- 1877,1885 ----
switch (con->contype)
{
+ case CONSTR_NOTNULL:
+ StoreColumnNotNullConstraint(rel, con);
+ break;
case CONSTR_DEFAULT:
StoreAttrDefault(rel, con->attnum, con->expr);
break;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1e5ac13..af87b58 100644
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
*************** typedef struct NewColumnValue
*** 175,180 ****
--- 175,192 ----
} NewColumnValue;
/*
+ * Struct describing the constraint information
+ * to de-inherit. Currently CHECK and NOT NULL constraints
+ * are carried by ATExecDropInherit() within these struct.
+ */
+ typedef struct DeinheritConstraintInfo
+ {
+ char contype; /* constraint type */
+ AttrNumber attnum; /* if NOT NULL constraint, the attnum */
+ char *conname; /* if CHECK constraint, the constraint name */
+ } DeinheritConstraintInfo;
+
+ /*
* Error-reporting support for RemoveRelations
*/
struct dropmsgstrings
*************** static List *MergeAttributes(List *schem
*** 227,234 ****
List **supOids, List **supconstr, int *supOidCount);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static bool change_varattnos_walker(Node *node, const AttrNumber *newattno);
! static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
! static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
static void StoreCatalogInheritance(Oid relationId, List *supers);
static void StoreCatalogInheritance1(Oid relationId, Oid parentOid,
int16 seqNumber, Relation inhRelation);
--- 239,248 ----
List **supOids, List **supconstr, int *supOidCount);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static bool change_varattnos_walker(Node *node, const AttrNumber *newattno);
! static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel,
! List **child_attnums);
! static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel,
! List *child_attnums);
static void StoreCatalogInheritance(Oid relationId, List *supers);
static void StoreCatalogInheritance1(Oid relationId, Oid parentOid,
int16 seqNumber, Relation inhRelation);
*************** static void ATExecAddColumn(AlteredTable
*** 275,282 ****
ColumnDef *colDef, bool isOid, LOCKMODE lockmode);
static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
! AlterTableCmd *cmd, LOCKMODE lockmode);
! static void ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
static void ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode);
static void ATExecColumnDefault(Relation rel, const char *colName,
--- 289,297 ----
ColumnDef *colDef, bool isOid, LOCKMODE lockmode);
static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
! AlterTableCmd *cmd, LOCKMODE lockmode);
! static void ATExecDropNotNull(Relation rel, const char *colName, DropBehavior behavior,
! bool recurse, bool recursing, LOCKMODE lockmode);
static void ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode);
static void ATExecColumnDefault(Relation rel, const char *colName,
*************** static void ATExecDropInherit(Relation r
*** 335,341 ****
static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
ForkNumber forkNum, bool istemp);
static const char *storage_name(char c);
!
/* ----------------------------------------------------------------
* DefineRelation
--- 350,363 ----
static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
ForkNumber forkNum, bool istemp);
static const char *storage_name(char c);
! static bool ATExecDropNotNullInternal(Relation rel, HeapTuple constrtup,
! DropBehavior behavior,
! bool recurse, bool recursing);
! static bool
! CheckNotNullOnAttributeName(Relation rel, const char *colname,
! AttrNumber *attnum);
! static void
! DropNotNullOnAttributeNum(Relation rel, AttrNumber attnum, bool lock);
/* ----------------------------------------------------------------
* DefineRelation
*************** DefineRelation(CreateStmt *stmt, char re
*** 507,512 ****
--- 529,555 ----
attnum++;
+ if (colDef->is_not_null) {
+ /*
+ * Adjust NOT NULL constraint of this column
+ * to hold new attnum and inheritance information
+ *
+ * XXX: Currently the constraint is cheated into cookedDefaults
+ * list, maybe we need to invent a different machinerie/naming.
+ */
+ CookedConstraint *cooked;
+
+ cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
+ cooked->contype = CONSTR_NOTNULL;
+ cooked->name = ChooseConstraintName(relname, NameStr(descriptor->attrs[attnum - 1]->attname),
+ "not_null", namespaceId, NIL);
+ cooked->attnum = attnum;
+ cooked->expr = NULL;
+ cooked->is_local = colDef->is_local;
+ cooked->inhcount = colDef->inhcount;
+ cookedDefaults = lappend(cookedDefaults, cooked);
+ }
+
if (colDef->raw_default != NULL)
{
RawColumnDefault *rawEnt;
*************** ATPrepCmd(List **wqueue, Relation rel, A
*** 2666,2672 ****
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
ATSimplePermissions(rel, false);
! ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_DROP;
break;
--- 2709,2718 ----
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
ATSimplePermissions(rel, false);
!
! if (recurse)
! cmd->subtype = AT_DropNotNullRecurse;
!
/* No command-specific prep needed */
pass = AT_PASS_DROP;
break;
*************** ATExecCmd(List **wqueue, AlteredTableInf
*** 2896,2902 ****
ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
! ATExecDropNotNull(rel, cmd->name, lockmode);
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
ATExecSetNotNull(tab, rel, cmd->name, lockmode);
--- 2942,2953 ----
ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
! ATExecDropNotNull(rel, cmd->name, cmd->behavior,
! false, false, lockmode);
! break;
! case AT_DropNotNullRecurse: /* ALTER COLUMN DROP NOT NULL with recursion */
! ATExecDropNotNull(rel, cmd->name, cmd->behavior,
! true, false, lockmode);
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
ATExecSetNotNull(tab, rel, cmd->name, lockmode);
*************** ATPrepAddOids(List **wqueue, Relation re
*** 4111,4157 ****
* ALTER TABLE ALTER COLUMN DROP NOT NULL
*/
static void
! ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
{
! HeapTuple tuple;
AttrNumber attnum;
! Relation attr_rel;
! List *indexoidlist;
! ListCell *indexoidscan;
/*
! * lookup the attribute
*/
! attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
! tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
! if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
! colName, RelationGetRelationName(rel))));
! attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
/* Prevent them from altering a system attribute */
! if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot alter system column \"%s\"",
! colName)));
/*
! * Check that the attribute is not in a primary key
*/
/* Loop over all indexes on the relation */
indexoidlist = RelationGetIndexList(rel);
!
foreach(indexoidscan, indexoidlist)
{
! Oid indexoid = lfirst_oid(indexoidscan);
! HeapTuple indexTuple;
Form_pg_index indexStruct;
int i;
--- 4162,4507 ----
* ALTER TABLE ALTER COLUMN DROP NOT NULL
*/
static void
! ATExecDropNotNull(Relation rel, const char *colName, DropBehavior behavior,
! bool recurse, bool recursing, LOCKMODE lockmode)
{
! List *children;
! ListCell *child;
! HeapTuple constr_tuple;
AttrNumber attnum;
! Relation constr_rel;
! SysScanDesc scan;
! ScanKeyData key;
! bool found;
!
! if (recursing)
! ATSimplePermissions(rel, false);
/*
! * Lookup the attribute. This also checks for a dropped column
! * or any attempts to alter a system column. Returns false in case
! * no further work needs to be done (e.g. no NOT NULL present).
*/
! if (!CheckNotNullOnAttributeName(rel, colName, &attnum))
! return;
! /*
! * Scan through our constraint records, looking for the matching
! * NOT NULL constraint.
! */
! constr_rel = heap_open(ConstraintRelationId, RowExclusiveLock);
! ScanKeyInit(&key,
! Anum_pg_constraint_conrelid,
! BTEqualStrategyNumber, F_OIDEQ,
! ObjectIdGetDatum(RelationGetRelid(rel)));
! scan = systable_beginscan(constr_rel, ConstraintRelidIndexId,
! true, SnapshotNow, 1, &key);
!
! while(HeapTupleIsValid(constr_tuple = systable_getnext(scan)))
! {
! Form_pg_constraint constrStruct;
! Datum arrayp;
! Datum *keyvals;
! int nelems;
! bool isnull;
! constrStruct = (Form_pg_constraint) GETSTRUCT(constr_tuple);
!
! if (constrStruct->contype != CONSTRAINT_NOTNULL)
! continue;
!
! /*
! * Check wether this constraint is attached to the given column.
! * ATExecSetNotNull() ensures that only one NOT NULL constraint tuple
! * lives per attribute.
! */
! arrayp = SysCacheGetAttr(CONSTROID, constr_tuple, Anum_pg_constraint_conkey, &isnull);
!
! /* should not happen */
! Assert(!isnull);
!
! deconstruct_array(DatumGetArrayTypeP(arrayp), INT2OID, 2, true,
! 's', &keyvals, NULL, &nelems);
!
! /* Is this NOT NULL constraint attached to the current tuple? */
! if (DatumGetInt16(keyvals[0]) != attnum)
! continue;
!
! /*
! * Check if the attribute is inherited from another relation or
! * part of a primary key.
! * If true, error out, otherwise perform the deletion of the constraint.
! *
! * Note: ATExecDropNotNullInternal() performs a CCI, if required.
! */
! if ((found = ATExecDropNotNullInternal(rel,
! constr_tuple,
! behavior,
! recurse, recursing)))
! {
! /*
! * Okay, actually remove the NOT NULL from pg_attribute.
! * CheckNotNullOnAttributeName() already holds a lock on pg_attribute, so
! * override an additional lock here.
! */
! DropNotNullOnAttributeNum(rel, attnum, false);
! break;
! }
! }
!
! systable_endscan(scan);
!
! if (found)
! {
! /*
! * Propagate to children as appropriate.
! */
! children = find_inheritance_children(RelationGetRelid(rel),
! AccessExclusiveLock);
! }
! else
! {
! ereport(ERROR,
! (errcode(ERRCODE_UNDEFINED_OBJECT),
! errmsg("column \"%s\" of relation \"%s\" has no NOT NULL constraint",
! colName,
! RelationGetRelationName(rel))));
! }
!
! foreach(child, children)
! {
! Oid childrelid = lfirst_oid(child);
! Relation childrel;
!
! childrel = heap_open(childrelid, NoLock);
! CheckTableNotInUse(childrel, "ALTER TABLE");
!
! ScanKeyInit(&key,
! Anum_pg_constraint_conrelid,
! BTEqualStrategyNumber, F_OIDEQ,
! ObjectIdGetDatum(childrelid));
! scan = systable_beginscan(constr_rel, ConstraintRelidIndexId,
! true, SnapshotNow, 1, &key);
!
! while (HeapTupleIsValid(constr_tuple = systable_getnext(scan)))
! {
! HeapTuple copy_tuple;
! Form_pg_constraint constrStruct;
! Datum arrayp;
! Datum *keyvals;
! int nelems;
! bool isnull;
!
! constrStruct = (Form_pg_constraint) GETSTRUCT(constr_tuple);
!
! if (constrStruct->contype != CONSTRAINT_NOTNULL)
! continue;
!
! /*
! * Check wether this constraint is attached to the given column.
! * ATExecSetNotNull() ensures that only one NOT NULL constraint tuple
! * lives per attribute.
! */
! arrayp = SysCacheGetAttr(CONSTROID, constr_tuple, Anum_pg_constraint_conkey, &isnull);
!
! /* should not happen */
! Assert(!isnull);
!
! deconstruct_array(DatumGetArrayTypeP(arrayp), INT2OID, 2, true,
! 's', &keyvals, NULL, &nelems);
!
! /* Is this NOT NULL constraint attached to the current tuple? */
! if (DatumGetInt16(keyvals[0]) != attnum)
! continue;
!
! found = true;
!
! if (constrStruct->coninhcount <= 0) /* shouldn't happen */
! elog(ERROR, "relation %u has non-inherited NOT NULL constraint \"%s\"",
! childrelid, NameStr(constrStruct->conname));
!
! copy_tuple = heap_copytuple(constr_tuple);
! constrStruct = (Form_pg_constraint) GETSTRUCT(copy_tuple);
!
! if (recurse)
! {
! /*
! * If the child constraint has other definition sources, just
! * decrement its inheritance count; if not, recurse to delete
! * it.
! */
! if (constrStruct->coninhcount == 1 && !constrStruct->conislocal)
! {
! /* Time to delete this child constraint, too */
! ATExecDropNotNull(childrel, colName, behavior,
! true, true, lockmode);
! }
! else
! {
! /* Child constraint must survive my deletion */
! constrStruct->coninhcount--;
! simple_heap_update(constr_rel, ©_tuple->t_self, copy_tuple);
! CatalogUpdateIndexes(constr_rel, copy_tuple);
!
! /* Make update visible */
! CommandCounterIncrement();
! }
! }
! else
! {
! /*
! * If we were told to drop ONLY in this table (no recursion),
! * we need to mark the inheritors' constraints as locally
! * defined rather than inherited.
! */
! constrStruct->coninhcount--;
! constrStruct->conislocal = true;
!
! simple_heap_update(constr_rel, ©_tuple->t_self, copy_tuple);
! CatalogUpdateIndexes(constr_rel, copy_tuple);
!
! /* Make update visible */
! CommandCounterIncrement();
! }
!
! heap_freetuple(copy_tuple);
!
! }
!
! systable_endscan(scan);
! heap_close(childrel, NoLock);
!
! }
!
! heap_close(constr_rel, NoLock);
!
! }
!
! /*
! * Does the leg work on dropping a NOT NULL constraint
! * identified by the given constrtup tuple.
! *
! * Note: The caller is responsible to pass a valid
! * CONSTRAINT_NOTNULL tuple.
! */
! static bool
! ATExecDropNotNullInternal(Relation rel, HeapTuple constrtup,
! DropBehavior behavior,
! bool recurse, bool recursing)
! {
! bool found;
! Form_pg_constraint constrStruct;
! ObjectAddress conobj;
!
! found = false;
!
! if (HeapTupleIsValid(constrtup))
! {
!
! constrStruct = (Form_pg_constraint) GETSTRUCT(constrtup);
!
! /*
! * Be paranoid, caller is responsible to pass a valid HeapTuple.
! */
! Assert(constrStruct->contype == CONSTRAINT_NOTNULL);
!
! /* It is okay to drop this constraint when recursing */
! if ((constrStruct->coninhcount > 0) && (!recursing))
! {
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
! errmsg("cannot drop inherited NOT NULL constraint \"%s\", relation \"%s\"",
! NameStr(constrStruct->conname), RelationGetRelationName(rel))));
! }
!
! /*
! * Time to drop the constraint now.
! */
! conobj.classId = ConstraintRelationId;
! conobj.objectId = HeapTupleGetOid(constrtup);
! conobj.objectSubId = 0;
!
! performDeletion(&conobj, behavior);
!
! /*
! * Make sure changes are visible
! */
! CommandCounterIncrement();
! found = true;
!
! }
!
! return found;
! }
!
! /*
! * Checks wether the given attribute name has a NOT NULL constraint attached
! * which can be dropped.
! *
! * Any attempts to drop a NOT NULL constraint on a system
! * column or non-existing column will throw an error. Returns true
! * in case the caller is allowed to proceed, false if no NOT NULL flag
! * was set on the attribute.
! */
! static bool
! CheckNotNullOnAttributeName(Relation rel, const char *colname, AttrNumber *attnum)
! {
! Relation attr_rel;
! HeapTuple attr_tuple;
! Form_pg_attribute attr_struct;
! bool has_not_null;
! List *indexoidlist;
! ListCell *indexoidscan;
!
! has_not_null = false;
!
! attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
! attr_tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colname);
!
! if (!HeapTupleIsValid(attr_tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
! colname,
! RelationGetRelationName(rel))));
! /* attribute exists */
! attr_struct = (Form_pg_attribute) GETSTRUCT(attr_tuple);
/* Prevent them from altering a system attribute */
! if (attr_struct->attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot alter system column \"%s\" of relation \"%s\"",
! colname,
! RelationGetRelationName(rel))));
/*
! * Check for dropped attributes
*/
+ if (attr_struct->attisdropped)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("attempt to drop NOT NULL constraint on dropped column \"%s\" of relation \"%s\"",
+ colname,
+ RelationGetRelationName(rel))));
+ }
+ if (attnum)
+ *attnum = attr_struct->attnum;
+
+ /*
+ * Check that the attribute is not in a primary key
+ */
+
/* Loop over all indexes on the relation */
indexoidlist = RelationGetIndexList(rel);
!
foreach(indexoidscan, indexoidlist)
{
! Oid indexoid = lfirst_oid(indexoidscan);
! HeapTuple indexTuple;
Form_pg_index indexStruct;
int i;
*************** ATExecDropNotNull(Relation rel, const ch
*** 4159,4165 ****
if (!HeapTupleIsValid(indexTuple))
elog(ERROR, "cache lookup failed for index %u", indexoid);
indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
!
/* If the index is not a primary key, skip the check */
if (indexStruct->indisprimary)
{
--- 4509,4515 ----
if (!HeapTupleIsValid(indexTuple))
elog(ERROR, "cache lookup failed for index %u", indexoid);
indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
!
/* If the index is not a primary key, skip the check */
if (indexStruct->indisprimary)
{
*************** ATExecDropNotNull(Relation rel, const ch
*** 4169,4201 ****
*/
for (i = 0; i < indexStruct->indnatts; i++)
{
! if (indexStruct->indkey.values[i] == attnum)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("column \"%s\" is in a primary key",
! colName)));
}
}
!
ReleaseSysCache(indexTuple);
}
! list_free(indexoidlist);
!
! /*
! * Okay, actually perform the catalog change ... if needed
*/
! if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
! {
! ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = FALSE;
! simple_heap_update(attr_rel, &tuple->t_self, tuple);
! /* keep the system catalog indexes current */
! CatalogUpdateIndexes(attr_rel, tuple);
! }
- heap_close(attr_rel, RowExclusiveLock);
}
/*
--- 4519,4581 ----
*/
for (i = 0; i < indexStruct->indnatts; i++)
{
! if (indexStruct->indkey.values[i] == attr_struct->attnum)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("column \"%s\" is in a primary key",
! colname)));
}
}
!
ReleaseSysCache(indexTuple);
}
! /*
! * In case no NOT NULL constraint was set on the
! * column, give up and tell the caller we haven't done
! * anything.
*/
! has_not_null = ((!attr_struct->attnotnull) ? false : true);
! heap_freetuple(attr_tuple);
! heap_close(attr_rel, NoLock);
! return has_not_null;
! }
! /*
! * Drops a NOT NULL constraint from the given attribute number of the
! * specified relation.
! */
! static void
! DropNotNullOnAttributeNum(Relation rel, AttrNumber attnum, bool lock)
! {
! Relation attr_rel;
! HeapTuple attr_tuple;
! Form_pg_attribute attr_struct;
!
! if (lock)
! attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
! else
! attr_rel = heap_open(AttributeRelationId, NoLock);
!
! attr_tuple = SearchSysCacheCopy(ATTNUM,
! ObjectIdGetDatum(RelationGetRelid(rel)),
! Int16GetDatum(attnum),
! 0, 0);
!
! attr_struct = (Form_pg_attribute) GETSTRUCT(attr_tuple);
!
! /* not expected, so be paranoid */
! Assert(attr_struct->attnotnull);
!
! ((Form_pg_attribute) GETSTRUCT(attr_tuple))->attnotnull = FALSE;
! simple_heap_update(attr_rel, &attr_tuple->t_self, attr_tuple);
!
! /* keep the system catalog indexes current */
! CatalogUpdateIndexes(attr_rel, attr_tuple);
!
! heap_close(attr_rel, NoLock);
}
/*
*************** ATExecSetNotNull(AlteredTableInfo *tab,
*** 4228,4241 ****
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot alter system column \"%s\"",
! colName)));
/*
* Okay, actually perform the catalog change ... if needed
*/
if (!((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
{
((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE;
simple_heap_update(attr_rel, &tuple->t_self, tuple);
--- 4608,4649 ----
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot alter system column \"%s\" of relation \"%s\"",
! colName, RelationGetRelationName(rel))));
/*
* Okay, actually perform the catalog change ... if needed
*/
if (!((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
{
+ /*
+ * Setting attnotnull to TRUE requires to create a
+ * constraint tuple in pg_constraint as well. We do this
+ * to record wether this constraint was added locally or
+ * inherited by some other attribute. We also record the
+ * attribute number to determine which column this
+ * constraint belongs to.
+ */
+ CookedConstraint *cooked = palloc(sizeof(CookedConstraint));
+ cooked->contype = CONSTR_NOTNULL;
+ cooked->expr = NULL;
+ cooked->name = ChooseConstraintName(RelationGetRelationName(rel),
+ NameStr(((Form_pg_attribute) GETSTRUCT(tuple))->attname),
+ "not_null", RelationGetNamespace(rel), NIL);
+ cooked->attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
+ cooked->is_local = ((Form_pg_attribute) GETSTRUCT(tuple))->attislocal;
+ cooked->inhcount = ((Form_pg_attribute) GETSTRUCT(tuple))->attinhcount;
+ StoreColumnNotNullConstraint(rel, cooked);
+
+ /*
+ * Make changes visible
+ */
+ CommandCounterIncrement();
+
+ /*
+ * Finally record in pg_attribute that there is a NOT NULL
+ * constraint.
+ */
((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE;
simple_heap_update(attr_rel, &tuple->t_self, tuple);
*************** ATExecDropConstraint(Relation rel, const
*** 5821,5826 ****
--- 6229,6238 ----
con = (Form_pg_constraint) GETSTRUCT(tuple);
+ /* NOT NULL is handled by ALTER TABLE ... DROP/SET NOT NULL */
+ if (con->contype == CONSTRAINT_NOTNULL)
+ continue;
+
if (strcmp(NameStr(con->conname), constrName) != 0)
continue;
*************** ATExecDropConstraint(Relation rel, const
*** 5831,5837 ****
errmsg("cannot drop inherited constraint \"%s\" of relation \"%s\"",
constrName, RelationGetRelationName(rel))));
! /* Right now only CHECK constraints can be inherited */
if (con->contype == CONSTRAINT_CHECK)
is_check_constraint = true;
--- 6243,6251 ----
errmsg("cannot drop inherited constraint \"%s\" of relation \"%s\"",
constrName, RelationGetRelationName(rel))));
! /* This is a CHECK constraint, remember that we found one
! * and proceed to drop it
! */
if (con->contype == CONSTRAINT_CHECK)
is_check_constraint = true;
*************** ATExecDropConstraint(Relation rel, const
*** 5902,5908 ****
con = (Form_pg_constraint) GETSTRUCT(tuple);
! /* Right now only CHECK constraints can be inherited */
if (con->contype != CONSTRAINT_CHECK)
continue;
--- 6316,6325 ----
con = (Form_pg_constraint) GETSTRUCT(tuple);
! /* Besides CHECK constraint we support inheritance of
! * NOT NULL constraints, too. Ignore them here, since they
! * are handled by ALTER TABLE...DROP/SET NOT NULL
! */
if (con->contype != CONSTRAINT_CHECK)
continue;
*************** ATPostAlterTypeParse(char *cmd, List **w
*** 6595,6600 ****
--- 7012,7018 ----
tab->subcmds[AT_PASS_OLD_INDEX] =
lappend(tab->subcmds[AT_PASS_OLD_INDEX], cmd);
break;
+ case AT_SetNotNull:
case AT_AddConstraint:
tab->subcmds[AT_PASS_OLD_CONSTR] =
lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
*************** ATExecAddInherit(Relation child_rel, Ran
*** 7356,7361 ****
--- 7774,7780 ----
HeapTuple inheritsTuple;
int32 inhseqno;
List *children;
+ List *child_attnums;
/*
* AccessShareLock on the parent is what's obtained during normal CREATE
*************** ATExecAddInherit(Relation child_rel, Ran
*** 7442,7451 ****
RelationGetRelationName(parent_rel))));
/* Match up the columns and bump attinhcount as needed */
! MergeAttributesIntoExisting(child_rel, parent_rel);
/* Match up the constraints and bump coninhcount as needed */
! MergeConstraintsIntoExisting(child_rel, parent_rel);
/*
* OK, it looks valid. Make the catalog entries that show inheritance.
--- 7861,7871 ----
RelationGetRelationName(parent_rel))));
/* Match up the columns and bump attinhcount as needed */
! child_attnums = NIL;
! MergeAttributesIntoExisting(child_rel, parent_rel, &child_attnums);
/* Match up the constraints and bump coninhcount as needed */
! MergeConstraintsIntoExisting(child_rel, parent_rel, child_attnums);
/*
* OK, it looks valid. Make the catalog entries that show inheritance.
*************** constraints_equivalent(HeapTuple a, Heap
*** 7521,7527 ****
* the child must be as well. Defaults are not compared, however.
*/
static void
! MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
Relation attrrel;
AttrNumber parent_attno;
--- 7941,7948 ----
* the child must be as well. Defaults are not compared, however.
*/
static void
! MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel,
! List **attnums_list)
{
Relation attrrel;
AttrNumber parent_attno;
*************** MergeAttributesIntoExisting(Relation chi
*** 7568,7573 ****
--- 7989,7999 ----
attributeName)));
/*
+ * Record the child's inherited attnum in attnums_list.
+ */
+ *attnums_list = lappend_int(*attnums_list, childatt->attnum);
+
+ /*
* OK, bump the child column's inheritance count. (If we fail
* later on, this change will just roll back.)
*/
*************** MergeAttributesIntoExisting(Relation chi
*** 7606,7612 ****
* a problem though. Even 100 constraints ought not be the end of the world.
*/
static void
! MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
{
Relation catalog_relation;
TupleDesc tuple_desc;
--- 8032,8039 ----
* a problem though. Even 100 constraints ought not be the end of the world.
*/
static void
! MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel,
! List *child_attnums)
{
Relation catalog_relation;
TupleDesc tuple_desc;
*************** MergeConstraintsIntoExisting(Relation ch
*** 7633,7639 ****
HeapTuple child_tuple;
bool found = false;
! if (parent_con->contype != CONSTRAINT_CHECK)
continue;
/* Search for a child constraint matching this one */
--- 8060,8067 ----
HeapTuple child_tuple;
bool found = false;
! if (parent_con->contype != CONSTRAINT_CHECK
! && parent_con->contype != CONSTRAINT_NOTNULL)
continue;
/* Search for a child constraint matching this one */
*************** MergeConstraintsIntoExisting(Relation ch
*** 7649,7667 ****
Form_pg_constraint child_con = (Form_pg_constraint) GETSTRUCT(child_tuple);
HeapTuple child_copy;
! if (child_con->contype != CONSTRAINT_CHECK)
! continue;
! if (strcmp(NameStr(parent_con->conname),
! NameStr(child_con->conname)) != 0)
! continue;
! if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
! ereport(ERROR,
! (errcode(ERRCODE_DATATYPE_MISMATCH),
! errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
! RelationGetRelationName(child_rel),
! NameStr(parent_con->conname))));
/*
* OK, bump the child constraint's inheritance count. (If we fail
--- 8077,8123 ----
Form_pg_constraint child_con = (Form_pg_constraint) GETSTRUCT(child_tuple);
HeapTuple child_copy;
! switch(child_con->contype)
! {
! case CONSTRAINT_CHECK:
! {
! if (strcmp(NameStr(parent_con->conname),
! NameStr(child_con->conname)) != 0)
! continue;
!
! if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
! ereport(ERROR,
! (errcode(ERRCODE_DATATYPE_MISMATCH),
! errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
! RelationGetRelationName(child_rel),
! NameStr(parent_con->conname))));
! break;
! }
! case CONSTRAINT_NOTNULL:
! {
! Datum arrayp;
! Datum *vals;
! int nelems;
! bool isnull;
! if (child_attnums == NIL)
! continue;
! arrayp = SysCacheGetAttr(CONSTROID, child_tuple,
! Anum_pg_constraint_conkey, &isnull);
! /* should not happen */
! Assert(!isnull);
! deconstruct_array(DatumGetArrayTypeP(arrayp), INT2OID, 2, true,
! 's', &vals, NULL, &nelems);
! Assert(nelems > 0);
!
! if (!list_member_int(child_attnums, DatumGetInt16(vals[0])))
! continue;
! break;
! }
! default:
! continue;
! }
/*
* OK, bump the child constraint's inheritance count. (If we fail
*************** MergeConstraintsIntoExisting(Relation ch
*** 7704,7711 ****
* surprise. But at least we'll never surprise by dropping columns someone
* isn't expecting to be dropped which would actually mean data loss.
*
! * coninhcount and conislocal for inherited constraints are adjusted in
! * exactly the same way.
*/
static void
ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
--- 8160,8167 ----
* surprise. But at least we'll never surprise by dropping columns someone
* isn't expecting to be dropped which would actually mean data loss.
*
! * coninhcount and conislocal for inherited CHECK and NOT NULL constraints
! * are adjusted in exactly the same way.
*/
static void
ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
*************** ATExecDropInherit(Relation rel, RangeVar
*** 7718,7724 ****
attributeTuple,
constraintTuple,
depTuple;
! List *connames;
bool found = false;
/*
--- 8174,8180 ----
attributeTuple,
constraintTuple,
depTuple;
! List *constraints;
bool found = false;
/*
*************** ATExecDropInherit(Relation rel, RangeVar
*** 7768,7773 ****
--- 8224,8231 ----
RelationGetRelationName(parent_rel),
RelationGetRelationName(rel))));
+ constraints = NIL;
+
/*
* Search through child columns looking for ones matching parent rel
*/
*************** ATExecDropInherit(Relation rel, RangeVar
*** 7781,7786 ****
--- 8239,8245 ----
while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
{
Form_pg_attribute att = (Form_pg_attribute) GETSTRUCT(attributeTuple);
+ HeapTuple parentattr_tuple;
/* Ignore if dropped or not inherited */
if (att->attisdropped)
*************** ATExecDropInherit(Relation rel, RangeVar
*** 7788,7816 ****
if (att->attinhcount <= 0)
continue;
! if (SearchSysCacheExistsAttName(RelationGetRelid(parent_rel),
! NameStr(att->attname)))
{
/* Decrement inhcount and possibly set islocal to true */
HeapTuple copyTuple = heap_copytuple(attributeTuple);
Form_pg_attribute copy_att = (Form_pg_attribute) GETSTRUCT(copyTuple);
!
copy_att->attinhcount--;
if (copy_att->attinhcount == 0)
copy_att->attislocal = true;
simple_heap_update(catalogRelation, ©Tuple->t_self, copyTuple);
CatalogUpdateIndexes(catalogRelation, copyTuple);
heap_freetuple(copyTuple);
}
}
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
/*
! * Likewise, find inherited check constraints and disinherit them. To do
! * this, we first need a list of the names of the parent's check
! * constraints. (We cheat a bit by only checking for name matches,
* assuming that the expressions will match.)
*/
catalogRelation = heap_open(ConstraintRelationId, RowExclusiveLock);
--- 8247,8296 ----
if (att->attinhcount <= 0)
continue;
! parentattr_tuple = SearchSysCacheCopyAttName(RelationGetRelid(parent_rel),
! NameStr(att->attname));
!
! if (HeapTupleIsValid(parentattr_tuple))
{
/* Decrement inhcount and possibly set islocal to true */
HeapTuple copyTuple = heap_copytuple(attributeTuple);
Form_pg_attribute copy_att = (Form_pg_attribute) GETSTRUCT(copyTuple);
! Form_pg_attribute parent_att = (Form_pg_attribute) GETSTRUCT(parentattr_tuple);
!
copy_att->attinhcount--;
if (copy_att->attinhcount == 0)
copy_att->attislocal = true;
+
+ /*
+ * If attnotnull is set, record the inherited
+ * not null constraint. We save its attnum to deinherit
+ * its representation in pg_constraint below, but only when
+ * its ancestor has a NOT NULL constraint, too.
+ */
+ if (copy_att->attnotnull
+ && parent_att->attnotnull)
+ {
+ DeinheritConstraintInfo *inhInfo
+ = (DeinheritConstraintInfo *)palloc(sizeof(DeinheritConstraintInfo));
+ inhInfo->contype = CONSTRAINT_NOTNULL;
+ inhInfo->conname = NULL;
+ inhInfo->attnum = copy_att->attnum;
+ constraints = lappend(constraints, inhInfo);
+ }
simple_heap_update(catalogRelation, ©Tuple->t_self, copyTuple);
CatalogUpdateIndexes(catalogRelation, copyTuple);
heap_freetuple(copyTuple);
+ heap_freetuple(parentattr_tuple);
}
}
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
/*
! * Likewise, find inherited check and NOT NULL constraints and disinherit
! * them. To do this, we first need a list of the names of the parent's check
! * and NOT NULL constraints. (We cheat a bit by only checking for name matches,
* assuming that the expressions will match.)
*/
catalogRelation = heap_open(ConstraintRelationId, RowExclusiveLock);
*************** ATExecDropInherit(Relation rel, RangeVar
*** 7821,7834 ****
scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
true, SnapshotNow, 1, key);
- connames = NIL;
-
while (HeapTupleIsValid(constraintTuple = systable_getnext(scan)))
{
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
if (con->contype == CONSTRAINT_CHECK)
! connames = lappend(connames, pstrdup(NameStr(con->conname)));
}
systable_endscan(scan);
--- 8301,8319 ----
scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
true, SnapshotNow, 1, key);
while (HeapTupleIsValid(constraintTuple = systable_getnext(scan)))
{
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
if (con->contype == CONSTRAINT_CHECK)
! {
! DeinheritConstraintInfo *inhInfo
! = (DeinheritConstraintInfo *)palloc(sizeof(DeinheritConstraintInfo));
! inhInfo->contype = CONSTRAINT_CHECK;
! inhInfo->conname = pstrdup(NameStr(con->conname));
! inhInfo->attnum = 0;
! constraints = lappend(constraints, inhInfo);
! }
}
systable_endscan(scan);
*************** ATExecDropInherit(Relation rel, RangeVar
*** 7846,7863 ****
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
bool match;
ListCell *lc;
! if (con->contype != CONSTRAINT_CHECK)
continue;
match = false;
! foreach(lc, connames)
{
! if (strcmp(NameStr(con->conname), (char *) lfirst(lc)) == 0)
{
match = true;
break;
}
}
if (match)
--- 8331,8379 ----
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
bool match;
ListCell *lc;
+ Datum arrayp;
+ Datum *keyvals;
+ int nelems;
+ bool isnull;
! if (con->contype != CONSTRAINT_CHECK
! && con->contype != CONSTRAINT_NOTNULL)
continue;
+ /*
+ * We scan through all constraints of the current child relation,
+ * checking for CONSTRAINT_CHECK and CONSTRAINT_NOTNULL occurrences.
+ *
+ * CHECK constraints are assumed to be named equally to in the
+ * relation's ancestor, while NOT NULL constraints always are attached
+ * to a specific column. Check for attnum and adjust the inheritance
+ * information accordingly.
+ */
+
match = false;
! foreach(lc, constraints)
{
! DeinheritConstraintInfo *inhInfo = (DeinheritConstraintInfo *) lfirst(lc);
!
! if ((inhInfo->contype == CONSTRAINT_CHECK)
! && (strcmp(NameStr(con->conname), inhInfo->conname) == 0))
{
match = true;
break;
}
+ else if (inhInfo->contype == CONSTRAINT_NOTNULL)
+ {
+ arrayp = SysCacheGetAttr(CONSTROID, constraintTuple,
+ Anum_pg_constraint_conkey, &isnull);
+
+ deconstruct_array(DatumGetArrayTypeP(arrayp), INT2OID, 2, true,
+ 's', &keyvals, NULL, &nelems);
+ if (keyvals[0] == inhInfo->attnum)
+ {
+ match = true;
+ break;
+ }
+ }
}
if (match)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index af25c21..3ab752c 100644
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
*************** transformColumnDefinition(ParseState *ps
*** 466,471 ****
--- 466,472 ----
parser_errposition(pstate,
constraint->location)));
column->is_not_null = TRUE;
+ cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
saw_nullable = true;
break;
diff --git a/src/backend/port/pg_latch.c b/src/backend/port/pg_latch.c
index ...002f2f4 .
*** a/src/backend/port/pg_latch.c
--- b/src/backend/port/pg_latch.c
***************
*** 0 ****
--- 1 ----
+ ../../../src/backend/port/unix_latch.c
\ No newline at end of file
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index eece757..a6e8619 100644
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** pg_get_constraintdef_worker(Oid constrai
*** 1071,1083 ****
if (fullCommand && OidIsValid(conForm->conrelid))
{
! appendStringInfo(&buf, "ALTER TABLE ONLY %s ADD CONSTRAINT %s ",
! generate_relation_name(conForm->conrelid, NIL),
! quote_identifier(NameStr(conForm->conname)));
}
switch (conForm->contype)
{
case CONSTRAINT_FOREIGN:
{
Datum val;
--- 1071,1112 ----
if (fullCommand && OidIsValid(conForm->conrelid))
{
! /* XXX: TODO, not sure how to handle this right now? */
! if (conForm->contype == CONSTRAINT_NOTNULL)
! {
! appendStringInfo(&buf, "ALTER TABLE ONLY %s ALTER COLUMN ",
! generate_relation_name(conForm->conrelid, NIL));
! }
! else
! {
! appendStringInfo(&buf, "ALTER TABLE ONLY %s ADD CONSTRAINT %s ",
! generate_relation_name(conForm->conrelid, NIL),
! quote_identifier(NameStr(conForm->conname)));
! }
}
switch (conForm->contype)
{
+ case CONSTRAINT_NOTNULL:
+ {
+ Datum val;
+ bool isnull;
+
+ /* XXX: TODO, not sure how to handle this right now? */
+
+ /* Fetch referenced column OID */
+ val = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_conkey, &isnull);
+
+ /* Should not happen */
+ if (isnull)
+ elog(ERROR, "null conkey for constraint %u",
+ constraintId);
+
+ decompile_column_index_array(val, conForm->conrelid, &buf);
+ appendStringInfo(&buf, " SET NOT NULL");
+ break;
+ }
case CONSTRAINT_FOREIGN:
{
Datum val;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 07db4a3..dda3dd5 100644
*** a/src/include/catalog/heap.h
--- b/src/include/catalog/heap.h
*************** extern List *AddRelationNewConstraints(R
*** 91,97 ****
bool is_local);
extern void StoreAttrDefault(Relation rel, AttrNumber attnum, Node *expr);
!
extern Node *cookDefault(ParseState *pstate,
Node *raw_default,
Oid atttypid,
--- 91,97 ----
bool is_local);
extern void StoreAttrDefault(Relation rel, AttrNumber attnum, Node *expr);
! extern void StoreColumnNotNullConstraint(Relation rel, CookedConstraint *cooked);
extern Node *cookDefault(ParseState *pstate,
Node *raw_default,
Oid atttypid,
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index ff87b6f..cd95391 100644
*** a/src/include/catalog/pg_constraint.h
--- b/src/include/catalog/pg_constraint.h
*************** typedef FormData_pg_constraint *Form_pg_
*** 175,180 ****
--- 175,181 ----
/* Valid values for contype */
#define CONSTRAINT_CHECK 'c'
+ #define CONSTRAINT_NOTNULL 'n'
#define CONSTRAINT_FOREIGN 'f'
#define CONSTRAINT_PRIMARY 'p'
#define CONSTRAINT_UNIQUE 'u'
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e896dc7..8364b27 100644
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef enum AlterTableType
*** 1116,1122 ****
--- 1116,1124 ----
AT_AddColumnToView, /* implicitly via CREATE OR REPLACE VIEW */
AT_ColumnDefault, /* alter column default */
AT_DropNotNull, /* alter column drop not null */
+ AT_DropNotNullRecurse, /* internal to commands/tablecmds.c */
AT_SetNotNull, /* alter column set not null */
+ AT_SetNotNullRecurse, /* internal to commands/tablecmds.c */
AT_SetStatistics, /* alter column set statistics */
AT_SetOptions, /* alter column set ( options ) */
AT_ResetOptions, /* alter column reset ( options ) */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 83e24fd..9afd2bd 100644
*** a/src/test/regress/expected/alter_table.out
--- b/src/test/regress/expected/alter_table.out
*************** alter table atacc1 alter bar drop not nu
*** 593,601 ****
ERROR: column "bar" of relation "atacc1" does not exist
-- try altering the oid column, should fail
alter table atacc1 alter oid set not null;
! ERROR: cannot alter system column "oid"
alter table atacc1 alter oid drop not null;
! ERROR: cannot alter system column "oid"
-- try creating a view and altering that, should fail
create view myview as select * from atacc1;
alter table myview alter column test drop not null;
--- 593,601 ----
ERROR: column "bar" of relation "atacc1" does not exist
-- try altering the oid column, should fail
alter table atacc1 alter oid set not null;
! ERROR: cannot alter system column "oid" of relation "atacc1"
alter table atacc1 alter oid drop not null;
! ERROR: cannot alter system column "oid" of relation "atacc1"
-- try creating a view and altering that, should fail
create view myview as select * from atacc1;
alter table myview alter column test drop not null;
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 96bd816..6d0e3e1 100644
*** a/src/test/regress/expected/cluster.out
--- b/src/test/regress/expected/cluster.out
*************** ERROR: insert or update on table "clstr
*** 251,261 ****
DETAIL: Key (b)=(1111) is not present in table "clstr_tst_s".
SELECT conname FROM pg_constraint WHERE conrelid = 'clstr_tst'::regclass
ORDER BY 1;
! conname
! ----------------
clstr_tst_con
clstr_tst_pkey
! (2 rows)
SELECT relname, relkind,
EXISTS(SELECT 1 FROM pg_class WHERE oid = c.reltoastrelid) AS hastoast
--- 251,262 ----
DETAIL: Key (b)=(1111) is not present in table "clstr_tst_s".
SELECT conname FROM pg_constraint WHERE conrelid = 'clstr_tst'::regclass
ORDER BY 1;
! conname
! ----------------------
! clstr_tst_a_not_null
clstr_tst_con
clstr_tst_pkey
! (3 rows)
SELECT relname, relkind,
EXISTS(SELECT 1 FROM pg_class WHERE oid = c.reltoastrelid) AS hastoast