*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 1081,1088 ****
bool
! This represents a not-null constraint. It is possible to
! change this column to enable or disable the constraint.
--- 1081,1090 ----
bool
! This represents a non nullable column. This may be either because
! of a not null constraint, or because of a primary key.
! The pg_constraint catalog contains the complete
! information; this column should be considered just a cache.
***************
*** 1825,1835 ****
The catalog pg_constraint stores check, primary
! key, unique, foreign key, and exclusion constraints on tables.
(Column constraints are not treated specially. Every column constraint is
equivalent to some table constraint.)
- Not-null constraints are represented in the pg_attribute>
- catalog, not here.
--- 1827,1836 ----
The catalog pg_constraint stores check, primary
! key, unique, foreign key, exclusion, and not null constraints
! on tables.
(Column constraints are not treated specially. Every column constraint is
equivalent to some table constraint.)
***************
*** 1838,1844 ****
! Check constraints on domains are stored here, too.
--- 1839,1845 ----
! Check and not null constraints on domains are stored here, too.
***************
*** 1878,1883 ****
--- 1879,1885 ----
c> = check constraint,
f> = foreign key constraint,
+ n> = not-null constraint,
p> = primary key constraint,
u> = unique constraint,
t> = constraint trigger,
***************
*** 1999,2005 ****
int2[]
pg_attribute .attnum>
If a table constraint (including foreign keys, but not constraint
! triggers), list of the constrained columns
--- 2001,2008 ----
int2[]
pg_attribute .attnum>
If a table constraint (including foreign keys, but not constraint
! triggers), list of the constrained columns. This column is used to store
! the referenced attribute number of a not-null constraint, too
*** a/src/backend/access/common/tupdesc.c
--- b/src/backend/access/common/tupdesc.c
***************
*** 557,562 **** BuildDescForRelation(List *schema)
--- 557,563 ----
foreach(l, schema)
{
ColumnDef *entry = lfirst(l);
+ bool notnull;
/*
* for each entry in the list, get the name and type information from
***************
*** 585,592 **** BuildDescForRelation(List *schema)
desc->attrs[attnum - 1]->attstorage = entry->storage;
/* Fill in additional stuff not handled by TupleDescInitEntry */
! desc->attrs[attnum - 1]->attnotnull = entry->is_not_null;
! has_not_null |= entry->is_not_null;
desc->attrs[attnum - 1]->attislocal = entry->is_local;
desc->attrs[attnum - 1]->attinhcount = entry->inhcount;
}
--- 586,594 ----
desc->attrs[attnum - 1]->attstorage = entry->storage;
/* Fill in additional stuff not handled by TupleDescInitEntry */
! notnull = entry->is_not_null | entry->is_primary_key;
! has_not_null |= notnull;
! desc->attrs[attnum - 1]->attnotnull = notnull;
desc->attrs[attnum - 1]->attislocal = entry->is_local;
desc->attrs[attnum - 1]->attinhcount = entry->inhcount;
}
*** a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c
***************
*** 174,181 **** relationHasPrimaryKey(Relation rel)
* are simple column references (not expressions), and that all those
* columns are marked NOT NULL. If they aren't (which can only happen during
* ALTER TABLE ADD CONSTRAINT, since the parser forces such columns to be
! * created NOT NULL during CREATE TABLE), do an ALTER SET NOT NULL to mark
! * them so --- or fail if they are not in fact nonnull.
*
* Caller had better have at least ShareLock on the table, else the not-null
* checking isn't trustworthy.
--- 174,182 ----
* are simple column references (not expressions), and that all those
* columns are marked NOT NULL. If they aren't (which can only happen during
* ALTER TABLE ADD CONSTRAINT, since the parser forces such columns to be
! * created NOT NULL during CREATE TABLE; or if they come from an inherited
! * relation), do an ALTER SET NOT NULL to mark them so --- or fail if they are
! * not in fact nonnull.
*
* Caller had better have at least ShareLock on the table, else the not-null
* checking isn't trustworthy.
***************
*** 236,242 **** index_check_primary_key(Relation heapRel,
/* Add a subcommand to make this one NOT NULL */
AlterTableCmd *cmd = makeNode(AlterTableCmd);
! cmd->subtype = AT_SetNotNull;
cmd->name = pstrdup(NameStr(attform->attname));
cmds = lappend(cmds, cmd);
}
--- 237,243 ----
/* Add a subcommand to make this one NOT NULL */
AlterTableCmd *cmd = makeNode(AlterTableCmd);
! cmd->subtype = AT_SetAttNotNull;
cmd->name = pstrdup(NameStr(attform->attname));
cmds = lappend(cmds, cmd);
}
*** a/src/backend/catalog/information_schema.sql
--- b/src/backend/catalog/information_schema.sql
***************
*** 427,450 **** CREATE VIEW check_constraints AS
LEFT OUTER JOIN pg_class c ON (c.oid = con.conrelid)
LEFT OUTER JOIN pg_type t ON (t.oid = con.contypid)
WHERE pg_has_role(coalesce(c.relowner, t.typowner), 'USAGE')
! AND con.contype = 'c'
!
! UNION
! -- not-null constraints
!
! SELECT CAST(current_database() AS sql_identifier) AS constraint_catalog,
! CAST(n.nspname AS sql_identifier) AS constraint_schema,
! CAST(CAST(n.oid AS text) || '_' || CAST(r.oid AS text) || '_' || CAST(a.attnum AS text) || '_not_null' AS sql_identifier) AS constraint_name, -- XXX
! CAST(a.attname || ' IS NOT NULL' AS character_data)
! AS check_clause
! FROM pg_namespace n, pg_class r, pg_attribute a
! WHERE n.oid = r.relnamespace
! AND r.oid = a.attrelid
! AND a.attnum > 0
! AND NOT a.attisdropped
! AND a.attnotnull
! AND r.relkind = 'r'
! AND pg_has_role(r.relowner, 'USAGE');
GRANT SELECT ON check_constraints TO PUBLIC;
--- 427,433 ----
LEFT OUTER JOIN pg_class c ON (c.oid = con.conrelid)
LEFT OUTER JOIN pg_type t ON (t.oid = con.contypid)
WHERE pg_has_role(coalesce(c.relowner, t.typowner), 'USAGE')
! AND con.contype = 'c';
GRANT SELECT ON check_constraints TO PUBLIC;
***************
*** 1704,1710 **** CREATE VIEW table_constraints AS
CASE c.contype WHEN 'c' THEN 'CHECK'
WHEN 'f' THEN 'FOREIGN KEY'
WHEN 'p' THEN 'PRIMARY KEY'
! WHEN 'u' THEN 'UNIQUE' END
AS character_data) AS constraint_type,
CAST(CASE WHEN c.condeferrable THEN 'YES' ELSE 'NO' END AS yes_or_no)
AS is_deferrable,
--- 1687,1694 ----
CASE c.contype WHEN 'c' THEN 'CHECK'
WHEN 'f' THEN 'FOREIGN KEY'
WHEN 'p' THEN 'PRIMARY KEY'
! WHEN 'u' THEN 'UNIQUE'
! WHEN 'n' THEN 'NOT NULL' END
AS character_data) AS constraint_type,
CAST(CASE WHEN c.condeferrable THEN 'YES' ELSE 'NO' END AS yes_or_no)
AS is_deferrable,
***************
*** 1724,1760 **** CREATE VIEW table_constraints AS
AND (pg_has_role(r.relowner, 'USAGE')
-- SELECT privilege omitted, per SQL standard
OR has_table_privilege(r.oid, 'INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER')
! OR has_any_column_privilege(r.oid, 'INSERT, UPDATE, REFERENCES') )
!
! UNION ALL
!
! -- not-null constraints
!
! SELECT CAST(current_database() AS sql_identifier) AS constraint_catalog,
! CAST(nr.nspname AS sql_identifier) AS constraint_schema,
! CAST(CAST(nr.oid AS text) || '_' || CAST(r.oid AS text) || '_' || CAST(a.attnum AS text) || '_not_null' AS sql_identifier) AS constraint_name, -- XXX
! CAST(current_database() AS sql_identifier) AS table_catalog,
! CAST(nr.nspname AS sql_identifier) AS table_schema,
! CAST(r.relname AS sql_identifier) AS table_name,
! CAST('CHECK' AS character_data) AS constraint_type,
! CAST('NO' AS yes_or_no) AS is_deferrable,
! CAST('NO' AS yes_or_no) AS initially_deferred
!
! FROM pg_namespace nr,
! pg_class r,
! pg_attribute a
!
! WHERE nr.oid = r.relnamespace
! AND r.oid = a.attrelid
! AND a.attnotnull
! AND a.attnum > 0
! AND NOT a.attisdropped
! AND r.relkind = 'r'
! AND (NOT pg_is_other_temp_schema(nr.oid))
! AND (pg_has_role(r.relowner, 'USAGE')
! -- SELECT privilege omitted, per SQL standard
! OR has_table_privilege(r.oid, 'INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER')
! OR has_any_column_privilege(r.oid, 'INSERT, UPDATE, REFERENCES') );
GRANT SELECT ON table_constraints TO PUBLIC;
--- 1708,1714 ----
AND (pg_has_role(r.relowner, 'USAGE')
-- SELECT privilege omitted, per SQL standard
OR has_table_privilege(r.oid, 'INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER')
! OR has_any_column_privilege(r.oid, 'INSERT, UPDATE, REFERENCES') ) ;
GRANT SELECT ON table_constraints TO PUBLIC;
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 145,151 **** typedef struct AlteredTableInfo
/* Information saved by Phases 1/2 for Phase 3: */
List *constraints; /* List of NewConstraint */
List *newvals; /* List of NewColumnValue */
! bool new_notnull; /* T if we added new NOT NULL constraints */
bool rewrite; /* T if a rewrite is forced */
Oid newTableSpace; /* new tablespace; 0 means no change */
/* Objects to rebuild after completing ALTER TYPE operations */
--- 145,151 ----
/* Information saved by Phases 1/2 for Phase 3: */
List *constraints; /* List of NewConstraint */
List *newvals; /* List of NewColumnValue */
! bool new_notnull; /* T if we added new NOT NULL or PKs */
bool rewrite; /* T if a rewrite is forced */
Oid newTableSpace; /* new tablespace; 0 means no change */
/* Objects to rebuild after completing ALTER TYPE operations */
***************
*** 169,174 **** typedef struct NewConstraint
--- 169,186 ----
} NewConstraint;
/*
+ * Struct used to describe add/drop operations of NOT NULL constraints
+ */
+ typedef struct NotNullConstraint
+ {
+ char *conname;
+ char *attname;
+ AttrNumber attnum;
+ bool is_local;
+ int inhcount;
+ } NotNullConstraint;
+
+ /*
* Struct describing one new column value that needs to be computed during
* Phase 3 copy (this could be either a new column with a non-null default, or
* a column that we're changing the type of). Columns without such an entry
***************
*** 183,188 **** typedef struct NewColumnValue
--- 195,211 ----
} NewColumnValue;
/*
+ * Struct describing the constraint information to disinherit. Currently only
+ * CHECK and NOT NULL constraints use this struct.
+ */
+ typedef struct DisinheritConstraintInfo
+ {
+ ConstrType contype; /* constraint type */
+ AttrNumber attnum; /* if NOT NULL constraint, the attnum */
+ char *conname; /* if CHECK constraint, the constraint name */
+ } DisinheritConstraintInfo;
+
+ /*
* Error-reporting support for RemoveRelations
*/
struct dropmsgstrings
***************
*** 244,254 **** static const struct dropmsgstrings dropmsgstringarray[] = {
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
! 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);
--- 267,282 ----
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
! char relkind, List **supOids, List **supconstr,
! List **nullconstr, int *supOidCount);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
+ static void StoreNotNullConstraints(Oid relid, Oid nspid, List *constraints,
+ char *relname, TupleDesc tupdesc);
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);
***************
*** 301,311 **** static void ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
bool recurse, bool recursing, LOCKMODE lockmode);
static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
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,
--- 329,354 ----
bool recurse, bool recursing, LOCKMODE lockmode);
static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
+ static void StoreNotNullConstraint(Oid relid, Oid nspid,
+ NotNullConstraint *constraint);
+ static HeapTuple RelationFetchNotNullConstraint(Relation pg_constraint,
+ Oid relid, AttrNumber attnum);
+ static List *GetRelationNotNullConstraints(Relation rel);
+ static void CreateOrAdjustNotNullConstraint(Relation rel, AttrNumber attnum,
+ char *attname, int addinhcount,
+ int tweak_islocal);
+ static AttrNumber get_constraint_singlecol_attno(HeapTuple tup);
+ static void CreateNotNullConstraint(Relation rel, AttrNumber attnum,
+ char *constr_name, int inhcount, bool islocal);
static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
AlterTableCmd *cmd, LOCKMODE lockmode);
! static bool ATExecDropNotNull(Relation rel, const char *colName,
! bool recurse, bool recursing, LOCKMODE lockmode);
! static bool ATExecSetNotNull(List **wqueue, AlteredTableInfo *tab,
! Relation rel, const char *colName, bool recurse,
! bool recursing, LOCKMODE lockmode);
! static void ATExecSetAttNotNull(List **wqueue, 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,
***************
*** 373,379 **** static void ATExecGenericOptions(Relation rel, List *options);
static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
ForkNumber forkNum, char relpersistence);
static const char *storage_name(char c);
!
/* ----------------------------------------------------------------
* DefineRelation
--- 416,422 ----
static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
ForkNumber forkNum, char relpersistence);
static const char *storage_name(char c);
! static Constraint *locate_notnull_constraint(ColumnDef *def);
/* ----------------------------------------------------------------
* DefineRelation
***************
*** 404,409 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 447,453 ----
TupleDesc descriptor;
List *inheritOids;
List *old_constraints;
+ List *null_constraints;
bool localHasOids;
int parentOidCount;
List *rawDefaults;
***************
*** 505,512 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
* inherited attributes.
*/
schema = MergeAttributes(schema, stmt->inhRelations,
! stmt->relation->relpersistence,
! &inheritOids, &old_constraints, &parentOidCount);
/*
* Create a tuple descriptor from the relation schema. Note that this
--- 549,557 ----
* inherited attributes.
*/
schema = MergeAttributes(schema, stmt->inhRelations,
! stmt->relation->relpersistence, relkind,
! &inheritOids, &old_constraints, &null_constraints,
! &parentOidCount);
/*
* Create a tuple descriptor from the relation schema. Note that this
***************
*** 603,608 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 648,657 ----
/* Store inheritance information for new rel. */
StoreCatalogInheritance(relationId, inheritOids);
+ /* Store NOT NULL constraints */
+ StoreNotNullConstraints(relationId, namespaceId, null_constraints,
+ relname, descriptor);
+
/*
* We must bump the command counter to make the newly-created relation
* tuple visible for opening.
***************
*** 640,645 **** DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
--- 689,741 ----
}
/*
+ * StoreNotNullConstraints
+ * Subroutine for DefineRelation
+ *
+ * Store a bunch of NOT NULL constraints into pg_constraint, automatically
+ * generating names for those that don't have them. Also, take care to resolve
+ * attnums from attnames using the passed tupledesc for those attribute whose
+ * numbers could not be resolved earlier.
+ */
+ static void
+ StoreNotNullConstraints(Oid relid, Oid nspid, List *constraints, char *relname,
+ TupleDesc tupdesc)
+ {
+ ListCell *cell;
+
+ foreach(cell, constraints)
+ {
+ NotNullConstraint *constraint = lfirst(cell);
+
+ if (constraint->attnum == InvalidAttrNumber)
+ {
+ int i;
+
+ /*
+ * XXX how can we avoid looping over the tupdesc for each
+ * constraint? Is this worth optimizing?
+ */
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ if (strcmp(NameStr(tupdesc->attrs[i]->attname),
+ constraint->attname) == 0)
+ {
+ constraint->attnum = i + 1;
+ break;
+ }
+ }
+ }
+
+ if (constraint->conname == NULL)
+ constraint->conname =
+ ChooseConstraintName(relname, constraint->attname,
+ "not_null", nspid, NIL);
+
+ StoreNotNullConstraint(relid, nspid, constraint);
+ }
+ }
+
+ /*
* Emit the right error or warning message for a "DROP" command issued on a
* non-existent relation
*/
***************
*** 1202,1212 **** storage_name(char c)
* of ColumnDef's.) It is destructively changed.
* 'supers' is a list of names (as RangeVar nodes) of parent relations.
* 'relpersistence' is a persistence type of the table.
*
* Output arguments:
* 'supOids' receives a list of the OIDs of the parent relations.
! * 'supconstr' receives a list of constraints belonging to the parents,
* updated as necessary to be valid for the child.
* 'supOidCount' is set to the number of parents that have OID columns.
*
* Return value:
--- 1298,1313 ----
* of ColumnDef's.) It is destructively changed.
* 'supers' is a list of names (as RangeVar nodes) of parent relations.
* 'relpersistence' is a persistence type of the table.
+ * 'relkind' is the relkind of the table.
*
* Output arguments:
* 'supOids' receives a list of the OIDs of the parent relations.
! * 'supconstr' receives a list of CHECK constraints belonging to the parents,
* updated as necessary to be valid for the child.
+ * 'nullconstr' receives a list of NOT NULL constraints, both the parents'
+ * and locally defined by the child definition. NB - only filled if
+ * it's a RELKIND_RELATION (i.e. plain table). Otherwise, not nulls
+ * are only recorded in the is_not_null flag of each column
* 'supOidCount' is set to the number of parents that have OID columns.
*
* Return value:
***************
*** 1252,1268 **** storage_name(char c)
*----------
*/
static List *
! MergeAttributes(List *schema, List *supers, char relpersistence,
! List **supOids, List **supconstr, int *supOidCount)
{
ListCell *entry;
List *inhSchema = NIL;
List *parentOids = NIL;
List *constraints = NIL;
int parentsWithOids = 0;
bool have_bogus_defaults = false;
int child_attno;
static Node bogus_marker = {0}; /* marks conflicting defaults */
/*
* Check for and reject tables with too many columns. We perform this
--- 1353,1372 ----
*----------
*/
static List *
! MergeAttributes(List *schema, List *supers, char relpersistence, char relkind,
! List **supOids, List **supconstr, List **nullconstr,
! int *supOidCount)
{
ListCell *entry;
List *inhSchema = NIL;
List *parentOids = NIL;
List *constraints = NIL;
+ List *notnullconstrs = NIL;
int parentsWithOids = 0;
bool have_bogus_defaults = false;
int child_attno;
static Node bogus_marker = {0}; /* marks conflicting defaults */
+ bool collect_notnulls = relkind == RELKIND_RELATION;
/*
* Check for and reject tables with too many columns. We perform this
***************
*** 1320,1325 **** MergeAttributes(List *schema, List *supers, char relpersistence,
--- 1424,1430 ----
* merge the column options into the column from the type
*/
coldef->is_not_null = restdef->is_not_null;
+ coldef->is_primary_key = restdef->is_primary_key;
coldef->raw_default = restdef->raw_default;
coldef->cooked_default = restdef->cooked_default;
coldef->constraints = restdef->constraints;
***************
*** 1351,1356 **** MergeAttributes(List *schema, List *supers, char relpersistence,
--- 1456,1463 ----
TupleConstr *constr;
AttrNumber *newattno;
AttrNumber parent_attno;
+ List *parent_nns;
+ ListCell *cell;
/*
* A self-exclusive lock is needed here. If two backends attempt to
***************
*** 1367,1374 **** MergeAttributes(List *schema, List *supers, char relpersistence,
errmsg("inherited relation \"%s\" is not a table",
parent->relname)));
/* Permanent rels cannot inherit from temporary ones */
! if (relpersistence != RELPERSISTENCE_TEMP
! && RelationUsesTempNamespace(relation))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from temporary relation \"%s\"",
--- 1474,1481 ----
errmsg("inherited relation \"%s\" is not a table",
parent->relname)));
/* Permanent rels cannot inherit from temporary ones */
! if (relpersistence != RELPERSISTENCE_TEMP &&
! RelationUsesTempNamespace(relation))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from temporary relation \"%s\"",
***************
*** 1396,1404 **** MergeAttributes(List *schema, List *supers, char relpersistence,
if (relation->rd_rel->relhasoids)
parentsWithOids++;
! tupleDesc = RelationGetDescr(relation);
constr = tupleDesc->constr;
/*
* newattno[] will contain the child-table attribute numbers for the
* attributes of this parent table. (They are not the same for
--- 1503,1539 ----
if (relation->rd_rel->relhasoids)
parentsWithOids++;
! /*
! * We want to inherit NOT NULL constraints, but not primary keys.
! * Since attnotnull flags in pg_attribute stores both, we want to keep
! * the attnotnull flag only on those columns that have it from NOT NULL
! * constraints. To do this, we create a copy of the table's descriptor
! * and scribble on it by resetting all the attnotnull bits to false,
! * and the setting them true for columns that appear in a NOT NULL
! * constraint.
! *
! * Note: we cannot use CreateTupleDescCopy here, because it'd lose the
! * atthasdef bits, as well as constraints, both of which we need below.
! */
! tupleDesc = CreateTupleDescCopyConstr(RelationGetDescr(relation));
constr = tupleDesc->constr;
+ if (collect_notnulls)
+ {
+ parent_nns = GetRelationNotNullConstraints(relation);
+
+ for (parent_attno = 1; parent_attno <= tupleDesc->natts;
+ parent_attno++)
+ tupleDesc->attrs[parent_attno - 1]->attnotnull = false;
+
+ foreach (cell, parent_nns)
+ {
+ NotNullConstraint *constr = lfirst(cell);
+
+ tupleDesc->attrs[constr->attnum - 1]->attnotnull = true;
+ }
+ }
+
/*
* newattno[] will contain the child-table attribute numbers for the
* attributes of this parent table. (They are not the same for
***************
*** 1438,1443 **** MergeAttributes(List *schema, List *supers, char relpersistence,
--- 1573,1579 ----
Oid defTypeId;
int32 deftypmod;
Oid defCollId;
+ NotNullConstraint *nnconstr;
/*
* Yes, try to merge the two column definitions. They must
***************
*** 1480,1486 **** MergeAttributes(List *schema, List *supers, char relpersistence,
storage_name(attribute->attstorage))));
def->inhcount++;
! /* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= attribute->attnotnull;
/* Default and other constraints are handled below */
newattno[parent_attno - 1] = exist_attno;
--- 1616,1665 ----
storage_name(attribute->attstorage))));
def->inhcount++;
!
! /*
! * Merge NOT NULL constraints: create a new NotNullConstraint
! * if it wasn't NOT NULL in the previous parents; bump inhcount
! * if it was.
! */
! if (collect_notnulls)
! {
! if (def->is_not_null && attribute->attnotnull)
! {
! bool found = false;
! ListCell *cell;
!
! foreach (cell, notnullconstrs)
! {
! nnconstr = lfirst(cell);
! if (strcmp(nnconstr->attname, def->colname) == 0)
! {
! found = true;
! break;
! }
! }
!
! nnconstr->inhcount++;
!
! if (!found)
! elog(ERROR, "missing Constraint for NOT NULL");
! }
! else if (attribute->attnotnull)
! {
! NotNullConstraint *nnconstr;
!
! nnconstr = (NotNullConstraint *)
! palloc(sizeof(NotNullConstraint));
! nnconstr->conname = NULL; /* figure out later */
! nnconstr->attname = pstrdup(def->colname);
! nnconstr->attnum = exist_attno;
! nnconstr->is_local = false;
! nnconstr->inhcount = 1;
! notnullconstrs = lappend(notnullconstrs, nnconstr);
! }
! }
!
! /* also OR the NOT NULL bits in the ColDef */
def->is_not_null |= attribute->attnotnull;
/* Default and other constraints are handled below */
newattno[parent_attno - 1] = exist_attno;
***************
*** 1504,1511 **** MergeAttributes(List *schema, List *supers, char relpersistence,
def->collClause = NULL;
def->collOid = attribute->attcollation;
def->constraints = NIL;
- inhSchema = lappend(inhSchema, def);
newattno[parent_attno - 1] = ++child_attno;
}
/*
--- 1683,1704 ----
def->collClause = NULL;
def->collOid = attribute->attcollation;
def->constraints = NIL;
newattno[parent_attno - 1] = ++child_attno;
+
+ if (def->is_not_null && collect_notnulls)
+ {
+ NotNullConstraint *nnconstr;
+
+ nnconstr = (NotNullConstraint *) palloc(sizeof(NotNullConstraint));
+ nnconstr->conname = NULL; /* figure out later */
+ nnconstr->attname = pstrdup(def->colname);
+ nnconstr->attnum = child_attno;
+ nnconstr->is_local = false;
+ nnconstr->inhcount = 1;
+ notnullconstrs = lappend(notnullconstrs, nnconstr);
+ }
+
+ inhSchema = lappend(inhSchema, def);
}
/*
***************
*** 1589,1594 **** MergeAttributes(List *schema, List *supers, char relpersistence,
--- 1782,1822 ----
}
}
+ /*
+ * We also have to adjust the NOT NULL constraints that we added to the
+ * child's list, by filling in the parent's name for the constraint if
+ * appropriate.
+ */
+ if (list_length(parent_nns) > 0)
+ {
+ ListCell *childcell;
+
+ foreach (childcell, notnullconstrs)
+ {
+ NotNullConstraint *childconstr = lfirst(childcell);
+ ListCell *cell;
+
+ /* name already claimed by a previous parent */
+ if (childconstr->conname != NULL)
+ continue;
+
+ foreach (cell, parent_nns)
+ {
+ NotNullConstraint *constr = lfirst(cell);
+
+ if (strcmp(childconstr->attname, constr->attname) == 0)
+ {
+ childconstr->conname = pstrdup(constr->conname);
+ break;
+ }
+ }
+
+ Assert(childconstr->conname != NULL);
+ }
+
+ /* XXX free the parent's list memory */
+ }
+
pfree(newattno);
/*
***************
*** 1601,1610 **** MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* If we had no inherited attributes, the result schema is just the
! * explicitly declared columns. Otherwise, we need to merge the declared
! * columns into the inherited schema list.
*/
! if (inhSchema != NIL)
{
foreach(entry, schema)
{
--- 1829,1867 ----
/*
* If we had no inherited attributes, the result schema is just the
! * explicitly declared columns; we only need to walk it to generate NOT
! * NULL constraint nodes. If there are inherited attributes, we need to
! * merge the declared columns into the inherited schema list.
*/
! if (inhSchema == NIL)
! {
! if (collect_notnulls)
! {
! foreach(entry, schema)
! {
! ColumnDef *newdef = lfirst(entry);
!
! if (newdef->is_not_null)
! {
! NotNullConstraint *constr;
! Constraint *constraint;
!
! constraint = locate_notnull_constraint(newdef);
!
! constr = (NotNullConstraint *)
! palloc(sizeof(NotNullConstraint));
! constr->conname = constraint->conname ?
! pstrdup(constraint->conname) : NULL;
! constr->attname = pstrdup(newdef->colname);
! constr->attnum = InvalidAttrNumber;
! constr->is_local = true;
! constr->inhcount = 0;
! notnullconstrs = lappend(notnullconstrs, constr);
! }
! }
! }
! }
! else
{
foreach(entry, schema)
{
***************
*** 1669,1676 **** MergeAttributes(List *schema, List *supers, char relpersistence,
/* Mark the column as locally defined */
def->is_local = true;
! /* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= newdef->is_not_null;
/* If new def has a default, override previous default */
if (newdef->raw_default != NULL)
{
--- 1926,2004 ----
/* Mark the column as locally defined */
def->is_local = true;
!
! /*
! * Handle NOT NULL constraints. We have two cases on which
! * we need to do something: (1) if parents and child define the
! * column as not null, there's already a NotNullConstraint node
! * and we only need to flip the is_local bit. We keep the
! * child's name for the constraint, if defined; otherwise we
! * use whatever was already there. Finally, (2) if the child
! * defines the column as not null but the parents didn't, then
! * create a new NotNull node.
! *
! * The other two possibilities are (a) the parents define the
! * column as not null, but the child doesn't; we don't need to
! * do anything here because the constraint node is already in
! * the list, and there's no need to tweak it. Finally, (b)
! * neither parents nor child define the column as not null.
! */
! if (collect_notnulls)
! {
! if (def->is_not_null && newdef->is_not_null) /* case 1 */
! {
! ListCell *cell;
! NotNullConstraint *nnconstr;
! Constraint *constraint;
! bool found;
!
! constraint = locate_notnull_constraint(newdef);
!
! /* locate the existing node */
! found = false;
! foreach (cell, notnullconstrs)
! {
! nnconstr = lfirst(cell);
!
! if (strcmp(nnconstr->attname, newdef->colname) == 0)
! {
! found = true;
! break;
! }
! }
! if (!found)
! elog(ERROR, "missing NotNullConstraint node");
!
! if (constraint->conname != NULL)
! {
! pfree(nnconstr->conname);
! nnconstr->conname = pstrdup(constraint->conname);
! }
! nnconstr->is_local = true;
!
! /* This node is already in the NOT NULL list */
! }
! else if (!def->is_not_null && newdef->is_not_null)
! {
! Constraint *constraint;
! NotNullConstraint *constr;
!
! constraint = locate_notnull_constraint(newdef);
! constr = (NotNullConstraint *)
! palloc(sizeof(NotNullConstraint));
! constr->conname = constraint->conname ?
! pstrdup(constraint->conname) : NULL;
! constr->attname = pstrdup(newdef->colname);
! constr->attnum = exist_attno;
! constr->is_local = true;
! constr->inhcount = 0;
! notnullconstrs = lappend(notnullconstrs, constr);
! }
! }
!
! /* ... as for the columnDef list, just OR the bits together */
def->is_not_null |= newdef->is_not_null;
+
/* If new def has a default, override previous default */
if (newdef->raw_default != NULL)
{
***************
*** 1681,1689 **** MergeAttributes(List *schema, List *supers, char relpersistence,
else
{
/*
! * No, attach new column to result schema
*/
inhSchema = lappend(inhSchema, newdef);
}
}
--- 2009,2035 ----
else
{
/*
! * No, attach new column to result schema and handle NOT NULL
*/
inhSchema = lappend(inhSchema, newdef);
+
+ if (newdef->is_not_null && collect_notnulls)
+ {
+ NotNullConstraint *constr;
+ Constraint *constraint;
+
+ constraint = locate_notnull_constraint(newdef);
+
+ constr = (NotNullConstraint *)
+ palloc(sizeof(NotNullConstraint));
+ constr->conname = constraint->conname ?
+ pstrdup(constraint->conname) : NULL;
+ constr->attname = pstrdup(newdef->colname);
+ constr->attnum = InvalidAttrNumber;
+ constr->is_local = true;
+ constr->inhcount = 0;
+ notnullconstrs = lappend(notnullconstrs, constr);
+ }
}
}
***************
*** 1721,1726 **** MergeAttributes(List *schema, List *supers, char relpersistence,
--- 2067,2073 ----
*supOids = parentOids;
*supconstr = constraints;
+ *nullconstr = notnullconstrs;
*supOidCount = parentsWithOids;
return schema;
}
***************
*** 1769,1774 **** MergeCheckConstraint(List *constraints, char *name, Node *expr)
--- 2116,2150 ----
return false;
}
+ /*
+ * Find the Constraint node related to the NOT NULL marking for a column
+ */
+ static Constraint *
+ locate_notnull_constraint(ColumnDef *def)
+ {
+ Constraint *constraint;
+ bool found = false;
+ ListCell *cell;
+
+ foreach (cell, def->constraints)
+ {
+ constraint = lfirst(cell);
+
+ if (constraint->contype == CONSTR_NOTNULL)
+ {
+ found = true;
+ break;
+ }
+ }
+ /* sanity check */
+ if (!found)
+ {
+ constraint = NULL; /* keep compiler quiet */
+ elog(ERROR, "missing Constraint node for NOT NULL constraint");
+ }
+
+ return constraint;
+ }
/*
* Replace varattno values in an expression tree according to the given
***************
*** 2647,2652 **** AlterTableGetLockLevel(List *cmds)
--- 3023,3029 ----
case AT_SetTableSpace: /* must rewrite heap */
case AT_DropNotNull: /* may change some SQL plans */
case AT_SetNotNull:
+ case AT_SetAttNotNull:
case AT_GenericOptions:
cmd_lockmode = AccessExclusiveLock;
break;
***************
*** 2855,2867 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
! 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, ATT_TABLE | ATT_FOREIGN_TABLE);
! ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_ADD_CONSTR;
break;
--- 3232,3252 ----
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
! if (recurse)
! cmd->subtype = AT_DropNotNullRecurse;
/* No command-specific prep needed */
pass = AT_PASS_DROP;
break;
+ case AT_SetAttNotNull: /* used by DefineIndex in PRIMARY KEY case */
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* This command never recurses */
+ /* No command-specific prep needed */
+ pass = AT_PASS_ADD_CONSTR;
+ break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
! if (recurse)
! cmd->subtype = AT_SetNotNullRecurse;
/* No command-specific prep needed */
pass = AT_PASS_ADD_CONSTR;
break;
***************
*** 3107,3116 **** ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
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);
--- 3492,3514 ----
ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
! ATExecDropNotNull(rel, cmd->name, false, false, lockmode);
! break;
! case AT_DropNotNullRecurse: /* ALTER COLUMN DROP NOT NULL with
! * recursion */
! ATExecDropNotNull(rel, cmd->name, 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 with
! * recursion */
! ATExecSetNotNull(wqueue, tab, rel, cmd->name, true,
! false, lockmode);
! break;
! case AT_SetAttNotNull:
! ATExecSetAttNotNull(wqueue, tab, rel, cmd->name, lockmode);
break;
case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
***************
*** 3558,3566 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
{
/*
* If we are rebuilding the tuples OR if we added any new NOT NULL
! * constraints, check all not-null constraints. This is a bit of
! * overkill but it minimizes risk of bugs, and heap_attisnull is a
! * pretty cheap test anyway.
*/
for (i = 0; i < newTupDesc->natts; i++)
{
--- 3956,3964 ----
{
/*
* If we are rebuilding the tuples OR if we added any new NOT NULL
! * or primary key constraints, check all not-null constraints. This is
! * a bit of overkill but it minimizes risk of bugs, and heap_attisnull
! * is a pretty cheap test anyway.
*/
for (i = 0; i < newTupDesc->natts; i++)
{
***************
*** 3698,3705 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
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)
--- 4096,4104 ----
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)
***************
*** 4203,4208 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4602,4608 ----
Oid collOid;
Form_pg_type tform;
Expr *defval;
+ bool need_final_cci;
List *children;
ListCell *child;
***************
*** 4261,4266 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4661,4677 ----
simple_heap_update(attrdesc, &tuple->t_self, tuple);
CatalogUpdateIndexes(attrdesc, tuple);
+ /*
+ * If the new column is marked NOT NULL, we need to increment
+ * inhcount to an existing NOT NULL constraint in the child, or
+ * create a new one if it doesn't exist. Skip this for the OID
+ * column.
+ */
+ if (colDef->is_not_null && !isOid)
+ CreateOrAdjustNotNullConstraint(rel, childatt->attnum,
+ NameStr(childatt->attname),
+ 1, 0);
+
heap_freetuple(tuple);
/* Inform the user about the merge */
***************
*** 4328,4334 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
attribute.attndims = list_length(colDef->typeName->arrayBounds);
attribute.attstorage = tform->typstorage;
attribute.attalign = tform->typalign;
! attribute.attnotnull = colDef->is_not_null;
attribute.atthasdef = false;
attribute.attisdropped = false;
attribute.attislocal = colDef->is_local;
--- 4739,4745 ----
attribute.attndims = list_length(colDef->typeName->arrayBounds);
attribute.attstorage = tform->typstorage;
attribute.attalign = tform->typalign;
! attribute.attnotnull = colDef->is_not_null | colDef->is_primary_key;
attribute.atthasdef = false;
attribute.attisdropped = false;
attribute.attislocal = colDef->is_local;
***************
*** 4366,4371 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 4777,4784 ----
/* Make the attribute's catalog entry visible */
CommandCounterIncrement();
+ need_final_cci = false;
+
/*
* Store the DEFAULT, if any, in the catalogs
*/
***************
*** 4388,4398 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
*/
AddRelationNewConstraints(rel, list_make1(rawEnt), NIL, false, true);
! /* Make the additional catalog changes visible */
! CommandCounterIncrement();
}
/*
* Tell Phase 3 to fill in the default expression, if there is one.
*
* If there is no default, Phase 3 doesn't have to do anything, because
--- 4801,4826 ----
*/
AddRelationNewConstraints(rel, list_make1(rawEnt), NIL, false, true);
! need_final_cci = true;
! }
!
! /* Finally, store the NOT NULL constraint, if necessary, except for OID */
! if (colDef->is_not_null && !isOid)
! {
! Constraint *constraint = locate_notnull_constraint(colDef);
!
! CreateNotNullConstraint(rel, attribute.attnum, constraint->conname,
! colDef->inhcount, colDef->is_local);
! need_final_cci = true;
}
/*
+ * Make new defaults and NOT NULL constraints visible.
+ */
+ if (need_final_cci)
+ CommandCounterIncrement();
+
+ /*
* Tell Phase 3 to fill in the default expression, if there is one.
*
* If there is no default, Phase 3 doesn't have to do anything, because
***************
*** 4460,4471 **** ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
}
/*
! * If the new column is NOT NULL, tell Phase 3 it needs to test that.
! * (Note we don't do this for an OID column. OID will be marked not
! * null, but since it's filled specially, there's no need to test
! * anything.)
*/
! tab->new_notnull |= colDef->is_not_null;
}
/*
--- 4888,4899 ----
}
/*
! * If the new column is either NOT NULL or primary key, tell Phase 3 it
! * needs to test that. (Note we don't do this for an OID column. OID
! * will be marked not null, but since it's filled specially, there's no
! * need to test anything.)
*/
! tab->new_notnull |= (colDef->is_not_null | colDef->is_primary_key);
}
/*
***************
*** 4567,4707 **** add_column_collation_dependency(Oid relid, int32 attnum, Oid collid)
}
/*
! * ALTER TABLE SET WITH OIDS
! *
! * Basically this is an ADD COLUMN for the special OID column. We have
! * to cons up a ColumnDef node because the ADD COLUMN code needs one.
*/
! static void
! ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOCKMODE lockmode)
{
! /* If we're recursing to a child table, the ColumnDef is already set up */
! if (cmd->def == NULL)
{
! ColumnDef *cdef = makeNode(ColumnDef);
! cdef->colname = pstrdup("oid");
! cdef->typeName = makeTypeNameFromOid(OIDOID, -1);
! cdef->inhcount = 0;
! cdef->is_local = true;
! cdef->is_not_null = true;
! cdef->storage = 0;
! cmd->def = (Node *) cdef;
}
! ATPrepAddColumn(wqueue, rel, recurse, false, cmd, lockmode);
! if (recurse)
! cmd->subtype = AT_AddOidsRecurse;
}
/*
! * 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;
! indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
! 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)
! {
! /*
! * Loop over each attribute in the primary key and see if it
! * matches the to-be-altered attribute
! */
! 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
*/
attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
! tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
--- 4995,5536 ----
}
/*
! * Create a NOT NULL pg_constraint entry for the given column of the
! * given name. If the name is NULL, a new one is generated.
*/
! void
! CreateNotNullConstraint(Relation rel, AttrNumber attnum, char *constr_name,
! int inhcount, bool islocal)
{
! NotNullConstraint constraint;
!
! Assert(AttrNumberIsForUserDefinedAttr(attnum));
!
! if (constr_name)
! constraint.conname = pstrdup(constr_name);
! else
{
! TupleDesc tupdesc = RelationGetDescr(rel);
! char *attname = NameStr(tupdesc->attrs[attnum - 1]->attname);
! constraint.conname =
! ChooseConstraintName(RelationGetRelationName(rel),
! attname,
! "not_null", RelationGetNamespace(rel),
! NIL);
}
! /* attname is not used here */
! constraint.attnum = attnum;
! constraint.is_local = islocal;
! constraint.inhcount = islocal ? 0 : 1;
! StoreNotNullConstraint(RelationGetRelid(rel), RelationGetNamespace(rel),
! &constraint);
! pfree(constraint.conname);
}
/*
! * Stores a NOT NULL constraint of a column into pg_constraint.
*/
! void
! StoreNotNullConstraint(Oid relid, Oid nspid, NotNullConstraint *constraint)
{
/*
! * Store the constraint. Reflect conislocal and coninhcount to
! * match the same values as the attached column.
! */
! CreateConstraintEntry(constraint->conname,
! nspid,
! CONSTRAINT_NOTNULL,
! false,
! false,
! true,
! relid,
! &(constraint->attnum),
! 1,
! InvalidOid,
! InvalidOid,
! InvalidOid,
! NULL,
! InvalidOid,
! InvalidOid,
! InvalidOid,
! 0,
! ' ',
! ' ',
! ' ',
! NULL,
! NULL,
! NULL,
! NULL,
! constraint->is_local,
! constraint->inhcount);
! }
! /*
! * get_constraint_singlecol_attno
! *
! * Get the column number that this pg_constraint tuple is for. Note that
! * this can only be useful for NOT NULL constraints, because other types
! * might have more than one key column, and this only returns the first one.
! */
! AttrNumber
! get_constraint_singlecol_attno(HeapTuple tup)
! {
! Datum arrayp;
! bool isnull;
! Datum *keyvals;
! int nelems;
!
! arrayp = SysCacheGetAttr(CONSTROID, tup,
! Anum_pg_constraint_conkey,
! &isnull);
! if (isnull)
! elog(ERROR, "null conkey found");
! deconstruct_array(DatumGetArrayTypeP(arrayp), INT2OID, 2, true,
! 's', &keyvals, NULL, &nelems);
! if (nelems != 1)
! elog(ERROR, "constraint tuple has more than one key column");
! return DatumGetInt16(keyvals[0]);
! }
! /*
! * RelationFetchNotNullConstraint
! *
! * Get (a copy of) the HeapTuple representing a NOT NULL constraint for the
! * given relation and column, or NULL if there's no such constraint.
! */
! HeapTuple
! RelationFetchNotNullConstraint(Relation pg_constraint, Oid relid,
! AttrNumber attnum)
! {
! SysScanDesc scan;
! ScanKeyData key[1];
! HeapTuple tup;
! bool found;
! ScanKeyInit(&key[0],
! Anum_pg_constraint_conrelid,
! BTEqualStrategyNumber, F_OIDEQ,
! ObjectIdGetDatum(relid));
! scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true,
! SnapshotNow, 1, key);
! while (HeapTupleIsValid((tup = systable_getnext(scan))))
{
! Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(tup);
! AttrNumber con_attno;
! if (conform->contype != CONSTRAINT_NOTNULL)
! continue;
! con_attno = get_constraint_singlecol_attno(tup);
! if (con_attno != attnum)
! continue;
!
! tup = heap_copytuple(tup);
! found = true;
! break;
}
+ if (!found)
+ tup = NULL;
! systable_endscan(scan);
! return tup;
! }
!
! /*
! * GetRelationNotNullConstraints
! *
! * Return a list of not null constraints in a table, as a list of struct
! * NotNullConstraint.
! */
! List *
! GetRelationNotNullConstraints(Relation rel)
! {
! Relation constr_rel;
! ScanKeyData key;
! SysScanDesc scan;
! HeapTuple constr_tup;
! List *constraints = NIL;
! TupleDesc tupdesc = RelationGetDescr(rel);
!
! constr_rel = heap_open(ConstraintRelationId, AccessShareLock);
! 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;
! int attnum;
! Form_pg_attribute attr;
! NotNullConstraint *constraint;
!
! constr = (Form_pg_constraint) GETSTRUCT(constr_tup);
!
! if (constr->contype != CONSTRAINT_NOTNULL)
! continue;
!
! attnum = get_constraint_singlecol_attno(constr_tup);
! attr = tupdesc->attrs[attnum - 1];
! constraint = (NotNullConstraint *) palloc(sizeof(NotNullConstraint));
! constraint->conname = pstrdup(NameStr(constr->conname));
! constraint->attname = pstrdup(NameStr(attr->attname));
! constraint->attnum = attnum;
! constraint->is_local = constr->conislocal;
! constraint->inhcount = constr->coninhcount;
! constraints = lappend(constraints, constraint);
}
! systable_endscan(scan);
! heap_close(constr_rel, AccessShareLock);
!
! return constraints;
}
/*
! * CreateOrAdjustNotNullConstraint
! *
! * Create a new NOT NULL constraint for the given relation and attnum, if
! * none exists already; otherwise, adjust its properties by adding the given
! * quantity to coninhcount, and tweak conislocal as follows: set to true if
! * tweak_islocal is positive, to false if negative, or not at all if 0.
! */
! void
! CreateOrAdjustNotNullConstraint(Relation rel, AttrNumber attnum, char *attname,
! int addinhcount, int tweak_islocal)
! {
! HeapTuple constrtup;
! Relation pg_con;
!
! pg_con = heap_open(ConstraintRelationId, RowExclusiveLock);
!
! constrtup = RelationFetchNotNullConstraint(pg_con, RelationGetRelid(rel),
! attnum);
! if (constrtup)
! {
! HeapTuple copy_con = heap_copytuple(constrtup);
! Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(copy_con);
!
! conform->coninhcount += addinhcount;
! if (tweak_islocal > 0)
! conform->conislocal = true;
! else if (tweak_islocal < 0)
! conform->conislocal = false;
!
! simple_heap_update(pg_con, &(copy_con->t_self), copy_con);
! CatalogUpdateIndexes(pg_con, copy_con);
! }
! else
! {
! CreateNotNullConstraint(rel, attnum, NULL, 1, false);
! }
!
! heap_close(pg_con, RowExclusiveLock);
! }
!
!
! /*
! * ALTER TABLE SET WITH OIDS
! *
! * Basically this is an ADD COLUMN for the special OID column. We have
! * to cons up a ColumnDef node because the ADD COLUMN code needs one.
*/
static void
! ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOCKMODE lockmode)
! {
! /* If we're recursing to a child table, the ColumnDef is already set up */
! if (cmd->def == NULL)
! {
! ColumnDef *cdef = makeNode(ColumnDef);
!
! cdef->colname = pstrdup("oid");
! cdef->typeName = makeTypeNameFromOid(OIDOID, -1);
! cdef->inhcount = 0;
! cdef->is_local = true;
! cdef->is_not_null = true;
! cdef->storage = 0;
! cmd->def = (Node *) cdef;
! }
! ATPrepAddColumn(wqueue, rel, recurse, false, cmd, lockmode);
!
! if (recurse)
! cmd->subtype = AT_AddOidsRecurse;
! }
!
! /*
! * ALTER TABLE ALTER COLUMN DROP NOT NULL
! *
! * For inheritance children, the consequences are nonobvious: if we're asked
! * to recurse, we have to recurse to delete the constraint from them too. If
! * we're asked not to recurse, we have to recurse anyway, but instead of
! * deleting we need to decrement the coninhcount for the constraint, and set
! * conislocal if inhcount goes to zero.
! */
! static bool
! ATExecDropNotNull(Relation rel, const char *colName,
! bool recurse, bool recursing, LOCKMODE lockmode)
! {
! List *children;
! ListCell *child;
! Oid relid;
! HeapTuple attrtup;
! HeapTuple constrtup;
! Relation pg_attr;
! Relation pg_con;
! Form_pg_attribute attrform;
! Form_pg_constraint constrform;
! bool do_drop;
! bool retval = false;
!
! /* At top level, permission check was done in ATPrepCmd, else do it */
! if (recursing)
! ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
!
! relid = RelationGetRelid(rel);
! children = find_inheritance_children(relid, lockmode);
!
! /* lookup the pg_attribute row */
! pg_attr = heap_open(AttributeRelationId, RowExclusiveLock);
! attrtup = SearchSysCacheCopyAttName(relid, colName);
!
! if (!HeapTupleIsValid(attrtup))
! ereport(ERROR,
! (errcode(ERRCODE_UNDEFINED_COLUMN),
! errmsg("column \"%s\" of relation \"%s\" does not exist",
! colName, RelationGetRelationName(rel))));
!
! attrform = (Form_pg_attribute) GETSTRUCT(attrtup);
!
! /* Prevent them from altering a system attribute */
! if (attrform->attnum <= 0)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot alter system column \"%s\"", colName)));
!
! /* Easy case: if attnotnull is not set, there's nothing to do */
! if (!attrform->attnotnull)
! {
! heap_freetuple(attrtup);
! heap_close(pg_attr, RowExclusiveLock);
! return false;
! }
!
! /*
! * Look up the pg_constraint row. If there's none, then the attnotnull
! * mark is due to a primary key, and we must not touch it.
! *
! * XXX We could, but do not, attempt to locate the pg_constraint row for
! * the primary key, to ensure that what our message says is true.
! */
! pg_con = heap_open(ConstraintRelationId, RowExclusiveLock);
! constrtup = RelationFetchNotNullConstraint(pg_con, relid, attrform->attnum);
! if (!constrtup)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
! errmsg("column \"%s\" is in a primary key", colName)));
!
! constrform = (Form_pg_constraint) GETSTRUCT(constrtup);
!
! /*
! * Finally, figure out if we're to actually drop the constraint or not. If
! * this is the relation for which we were directly called, we need to
! * forbid dropping the constraint if it's inherited; they need to drop it
! * from the parent instead.
! *
! * On the other hand, if we're recursing, we actually drop the constraint
! * only if it's not locally defined and there are no other parents forcing
! * this constraint, *and* we're asked to recurse. Otherwise, just update
! * the pg_constraint tuple: decrement coninhcount in all cases, and if not
! * asked to recurse we need to set conislocal to true in case it goes to
! * zero. Otherwise (i.e. inhcount is zero and we're asked to recurse), do
! * drop the contraint.
! */
! do_drop = false;
! if (!recursing)
! {
! if (constrform->coninhcount != 0)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
! errmsg("cannot drop inherited NOT NULL constraint \"%s\", relation \"%s\"",
! NameStr(constrform->conname), RelationGetRelationName(rel))));
! do_drop = true;
! }
! else
! {
! bool do_update = false;
!
! if (--constrform->coninhcount == 0 && !constrform->conislocal)
! {
! if (!recurse && recursing)
! {
! constrform->conislocal = true;
! do_update = true;
! }
! else
! do_drop = true;
! }
! else
! do_update = true;
!
! if (do_update)
! {
! Assert(!do_drop);
! simple_heap_update(pg_con, &(constrtup->t_self), constrtup);
! CatalogUpdateIndexes(pg_con, constrtup);
!
! retval = true;
! }
! }
!
! /*
! * Okay, delete the constraint. Note that we mustn't update pg_attribute
! * if there's a PK involving the column.
! */
! if (do_drop)
! {
! ObjectAddress conobj;
! HeapTuple pktup;
! bool ispk;
! SysScanDesc scan;
! ScanKeyData key[1];
!
! conobj.classId = ConstraintRelationId;
! conobj.objectId = HeapTupleGetOid(constrtup);
! conobj.objectSubId = 0;
!
! performDeletion(&conobj, DROP_CASCADE);
!
! ScanKeyInit(&key[0],
! Anum_pg_constraint_conrelid,
! BTEqualStrategyNumber, F_OIDEQ,
! ObjectIdGetDatum(relid));
!
! scan = systable_beginscan(pg_con, ConstraintRelidIndexId,
! true, SnapshotNow, 1, key);
! ispk = false;
! while (HeapTupleIsValid(pktup = systable_getnext(scan)))
! {
! Form_pg_constraint pkform;
! Datum arrayp;
! Datum *keyvals;
! int nelems;
! bool isnull;
! int i;
!
! pkform = (Form_pg_constraint) GETSTRUCT(pktup);
! if (pkform->contype != CONSTRAINT_PRIMARY)
! continue;
!
! arrayp = SysCacheGetAttr(CONSTROID, pktup,
! Anum_pg_constraint_conkey,
! &isnull);
! if (isnull)
! elog(ERROR, "null conkey found");
!
! deconstruct_array(DatumGetArrayTypeP(arrayp), INT2OID, 2, true,
! 's', &keyvals, NULL, &nelems);
! for (i = 0; i < nelems; i++)
! {
! if (DatumGetInt16(keyvals[i]) == attrform->attnum)
! {
! ispk = true;
! break;
! }
! }
! if (ispk)
! break;
! }
! systable_endscan(scan);
!
! /* it's not PK -- reset attnotnull */
! if (!ispk)
! {
! attrform->attnotnull = false;
! simple_heap_update(pg_attr, &(attrtup->t_self), attrtup);
! CatalogUpdateIndexes(pg_attr, attrtup);
! }
!
! /*
! * Perform work on all children, if any. Note that if a children is
! * altered during recursion, we need a CommandCounterIncrement, as in
! * ATExecSetNotNull; see comments therein. (This is only necessary in
! * case we update pg_constraint, though; if we do a delete there, or in
! * case we update pg_attribute, there's no need for this.)
! */
! foreach(child, children)
! {
! Oid childrelid = lfirst_oid(child);
! Relation childrel;
!
! Assert(!recursing || recurse);
!
! /* find_inheritance_children already got lock */
! childrel = heap_open(childrelid, NoLock);
! CheckTableNotInUse(childrel, "ALTER TABLE");
!
! if (ATExecDropNotNull(childrel, colName, recurse, true, lockmode))
! CommandCounterIncrement();
!
! heap_close(childrel, NoLock);
! }
! }
!
! heap_close(pg_con, RowExclusiveLock);
! heap_close(pg_attr, RowExclusiveLock);
!
! return retval;
! }
!
! /*
! * ALTER TABLE ALTER COLUMN SET NOT NULL
! *
! * Recursively add not null constraints to the given relation and column name.
! * If the constraint is inherited, walk down the inheritance tree to ensure the
! * inheritance counter for each not null constraint is adjusted accordingly.
! */
! static bool
! ATExecSetNotNull(List **wqueue, AlteredTableInfo *tab, Relation rel,
! const char *colName, bool recurse, bool recursing,
! LOCKMODE lockmode)
{
HeapTuple tuple;
AttrNumber attnum;
Relation attr_rel;
+ Oid relid;
+ List *children;
+ ListCell *cell;
+ Relation pg_con;
+ HeapTuple constrtup;
+ Form_pg_attribute attrtup;
+
+ /* At top level, permission check was done in ATPrepCmd, else do it */
+ if (recursing)
+ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/*
! * We only recurse one level at a time, because if we merge a constraint
! * with an existing one in a child, we have to stop recursing down that
! * hierarchy path.
*/
+ children = find_inheritance_children(RelationGetRelid(rel),
+ lockmode);
+
+ /* lookup the attribute */
attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
! relid = RelationGetRelid(rel);
! tuple = SearchSysCacheCopyAttName(relid, colName);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
***************
*** 4709,4742 **** ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
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)));
/*
! * 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);
}
/*
* ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
*/
--- 5538,5732 ----
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel))));
! attrtup = (Form_pg_attribute) GETSTRUCT(tuple);
! attnum = attrtup->attnum;
/* Prevent them from altering a system attribute */
if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot alter system column \"%s\" of relation \"%s\"",
! colName, RelationGetRelationName(rel))));
!
! /*
! * If we are told not to recurse, there had better not be any child
! * tables; else the addition would put them out of step.
! */
! if (children && !recurse)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
! errmsg("NOT NULL constraint must be added to child tables too")));
!
! pg_con = heap_open(ConstraintRelationId, RowExclusiveLock);
! constrtup = RelationFetchNotNullConstraint(pg_con, relid, attnum);
!
! /*
! * At the top level of recursion, an existing constraint means we only need
! * to set conislocal (if not set already). If we're already recursing,
! * however, we need to adjust attinhcount. In both cases we stop
! * recursing, because the children must already have this constraint.
! */
! if (constrtup)
! {
! Form_pg_constraint constrform;
! bool touched = false;
!
! constrform = (Form_pg_constraint) GETSTRUCT(constrtup);
!
! if (!recursing)
! {
! if (!constrform->conislocal)
! {
! constrform->conislocal = true;
! touched = true;
! }
! }
! else
! {
! constrform->coninhcount++;
! touched = true;
! }
!
! if (touched)
! {
! simple_heap_update(pg_con, &(constrtup->t_self), constrtup);
! CatalogUpdateIndexes(pg_con, constrtup);
! }
!
! heap_close(pg_con, RowExclusiveLock);
! heap_close(attr_rel, RowExclusiveLock);
!
! return touched;
! }
!
! /*
! * By here, we've established that the column does not already have a not
! * null constraint, so we need to add one and recurse to ensure that our
! * children have it too.
! */
! CreateNotNullConstraint(rel, attnum, NULL,
! recursing ? 1 : 0,
! recursing ? false : true);
!
! /*
! * And set attnotnull in pg_attribute. Note that it might already be set,
! * if the column belongs to a primary key.
! */
! if (!attrtup->attnotnull)
! {
! HeapTuple copy_attr = heap_copytuple(tuple);
! Form_pg_attribute attrform = (Form_pg_attribute) GETSTRUCT(copy_attr);
!
! attrform->attnotnull = true;
!
! simple_heap_update(attr_rel, &(copy_attr->t_self), copy_attr);
! CatalogUpdateIndexes(attr_rel, copy_attr);
! }
!
! /* Tell Phase 3 it needs to test the constraint */
! tab->new_notnull = true;
!
! heap_close(attr_rel, RowExclusiveLock);
! heap_close(pg_con, RowExclusiveLock);
/*
! * Recurse. Note that if a child is altered during recursion, we need
! * a CommandCounterIncrement in case that table is also a direct child
! * of our parent. (Skipping that would mean that we would try to update
! * its pg_constraint and pg_attribute rows twice without an intervening
! * CCI, leading to "tuple updated by self"). Maybe there's a way to avoid
! * this overhead by building a table of coninhcount increments we need to
! * apply to each relation before actually applying them, but it doesn't
! * seem worth the trouble (and it doesn't help with pg_attribute updates
! * anyway.)
*/
! foreach(cell, children)
{
! Oid childrelid = lfirst_oid(cell);
! Relation childrel;
! AlteredTableInfo *childtab;
!
! /* find_inheritance_children already got lock */
! childrel = heap_open(childrelid, NoLock);
! CheckTableNotInUse(childrel, "ALTER TABLE");
!
! /* get work queue entry for child relation */
! childtab = ATGetQueueEntry(wqueue, childrel);
!
! /* perform the actual work */
! if (ATExecSetNotNull(wqueue, childtab, childrel,
! colName, recurse, true,
! lockmode))
! CommandCounterIncrement();
!
! heap_close(childrel, NoLock);
! }
!
! return true;
! }
!
! /*
! * ATExecSetAttNotNull
! *
! * This is not SQL-accessible directly -- it's only used by DefineIndex to
! * ensure that pg_attribute.attnotnull is set for PRIMARY KEY columns. The
! * differences with plain SET NOT NULL is that we don't create pg_constraint
! * entries, and we do not recurse either.
! */
! static void
! ATExecSetAttNotNull(List **wqueue, AlteredTableInfo *tab, Relation rel,
! const char *colName, LOCKMODE lockmode)
! {
! Relation attr_rel;
! Oid relid;
! HeapTuple tuple;
! Form_pg_attribute attrtup;
! AttrNumber attnum;
!
! /* lookup the attribute */
! attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
!
! relid = RelationGetRelid(rel);
! tuple = SearchSysCacheCopyAttName(relid, colName);
!
! if (!HeapTupleIsValid(tuple))
! ereport(ERROR,
! (errcode(ERRCODE_UNDEFINED_COLUMN),
! errmsg("column \"%s\" of relation \"%s\" does not exist",
! colName, RelationGetRelationName(rel))));
! attrtup = (Form_pg_attribute) GETSTRUCT(tuple);
! attnum = attrtup->attnum;
!
! /* Prevent them from altering a system attribute */
! if (attnum <= 0)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("cannot alter system column \"%s\" of relation \"%s\"",
! colName, RelationGetRelationName(rel))));
!
! /*
! * And set attnotnull in pg_attribute. Note that it might already be set,
! * if the column has a NOT NULL constraint already.
! */
! if (!attrtup->attnotnull)
! {
! HeapTuple copy_attr = heap_copytuple(tuple);
! Form_pg_attribute attrform = (Form_pg_attribute) GETSTRUCT(copy_attr);
! attrform->attnotnull = true;
! simple_heap_update(attr_rel, &(copy_attr->t_self), copy_attr);
! CatalogUpdateIndexes(attr_rel, copy_attr);
}
+ /* Tell Phase 3 it needs to test the constraint */
+ tab->new_notnull = true;
+
heap_close(attr_rel, RowExclusiveLock);
}
+
/*
* ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
*/
***************
*** 6609,6614 **** createForeignKeyTriggers(Relation rel, Constraint *fkconstraint,
--- 7599,7686 ----
}
/*
+ * After dropping a primary key, we need to adjust the attnotnull flags for all
+ * columns that do not have a NOT NULL constraint.
+ *
+ * rel is the involved relation, "tuple" is the pg_constraint tuple for the
+ * dropped primary key. pg_con is pg_constraint, suitably locked.
+ */
+ static void
+ fixup_attnotnull(Relation rel, Relation pg_con, HeapTuple pktuple)
+ {
+ Relation pg_attr;
+ HeapTuple contuple;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ List *notnulls = NIL;
+ List *nullables = NIL;
+ ListCell *cell;
+ Datum arrayp;
+ bool isnull;
+ Datum *keyvals;
+ int nelems;
+ int i;
+ HeapTuple attrtup;
+
+ pg_attr = heap_open(AttributeRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber,
+ F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+
+ scan = systable_beginscan(pg_con, ConstraintRelidIndexId, true,
+ SnapshotNow, 1, key);
+ while (HeapTupleIsValid(contuple = systable_getnext(scan)))
+ {
+ Form_pg_constraint conForm = (Form_pg_constraint) GETSTRUCT(contuple);
+ AttrNumber attno;
+
+ if (conForm->contype != CONSTRAINT_NOTNULL)
+ continue;
+
+ attno = get_constraint_singlecol_attno(contuple);
+ notnulls = lappend_int(notnulls, attno);
+ }
+ systable_endscan(scan);
+
+ arrayp = SysCacheGetAttr(CONSTROID, pktuple,
+ Anum_pg_constraint_conkey,
+ &isnull);
+ if (isnull)
+ elog(ERROR, "null conkey found");
+
+ deconstruct_array(DatumGetArrayTypeP(arrayp), INT2OID, 2, true,
+ 's', &keyvals, NULL, &nelems);
+
+ /* this could be made a lot faster if necessary ... */
+ for (i = 0; i < nelems; i++)
+ if (!list_member_int(notnulls, DatumGetInt16(keyvals[i])))
+ nullables = lappend_int(nullables, DatumGetInt16(keyvals[i]));
+
+ foreach(cell, nullables)
+ {
+ AttrNumber attr = (AttrNumber) lfirst_int(cell);
+ Form_pg_attribute attrform;
+
+ attrtup = SearchSysCacheCopy2(ATTNUM,
+ ObjectIdGetDatum(RelationGetRelid(rel)),
+ Int16GetDatum(attr));
+ if (!HeapTupleIsValid(attrtup))
+ elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+ attr, RelationGetRelid(rel));
+ attrform = (Form_pg_attribute) GETSTRUCT(attrtup);
+ Assert(attrform->attnotnull == true);
+ attrform->attnotnull = false;
+ simple_heap_update(pg_attr, &attrtup->t_self, attrtup);
+ CatalogUpdateIndexes(pg_attr, attrtup);
+ }
+
+ heap_close(pg_attr, RowExclusiveLock);
+ }
+
+ /*
* ALTER TABLE DROP CONSTRAINT
*
* Like DROP COLUMN, we can't use the normal ALTER TABLE recursion mechanism.
***************
*** 6627,6633 **** ATExecDropConstraint(Relation rel, const char *constrName,
ScanKeyData key;
HeapTuple tuple;
bool found = false;
! bool is_check_constraint = false;
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
--- 7699,7705 ----
ScanKeyData key;
HeapTuple tuple;
bool found = false;
! bool is_inheritable_constraint = false;
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
***************
*** 6651,6656 **** ATExecDropConstraint(Relation rel, const char *constrName,
--- 7723,7732 ----
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;
***************
*** 6661,6669 **** ATExecDropConstraint(Relation rel, const char *constrName,
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;
/*
* Perform the actual constraint deletion
--- 7737,7748 ----
errmsg("cannot drop inherited constraint \"%s\" of relation \"%s\"",
constrName, RelationGetRelationName(rel))));
! /*
! * Remember if we see an inheritable constraint. We only need to
! * handle CHECK constraints, because we rejected NOT NULL ones above.
! */
if (con->contype == CONSTRAINT_CHECK)
! is_inheritable_constraint = true;
/*
* Perform the actual constraint deletion
***************
*** 6674,6679 **** ATExecDropConstraint(Relation rel, const char *constrName,
--- 7753,7765 ----
performDeletion(&conobj, behavior);
+ /*
+ * If we dropped the primary key, we need to reset the attnotnull
+ * flag for all columns that don't have a NOT NULL constraint.
+ */
+ if (con->contype == CONSTRAINT_PRIMARY)
+ fixup_attnotnull(rel, conrel, tuple);
+
found = true;
}
***************
*** 6703,6709 **** ATExecDropConstraint(Relation rel, const char *constrName,
* routines, we have to do this one level of recursion at a time; we can't
* use find_all_inheritors to do it in one pass.
*/
! if (is_check_constraint)
children = find_inheritance_children(RelationGetRelid(rel), lockmode);
else
children = NIL;
--- 7789,7795 ----
* routines, we have to do this one level of recursion at a time; we can't
* use find_all_inheritors to do it in one pass.
*/
! if (is_inheritable_constraint)
children = find_inheritance_children(RelationGetRelid(rel), lockmode);
else
children = NIL;
***************
*** 6732,6738 **** ATExecDropConstraint(Relation rel, const char *constrName,
con = (Form_pg_constraint) GETSTRUCT(tuple);
! /* Right now only CHECK constraints can be inherited */
if (con->contype != CONSTRAINT_CHECK)
continue;
--- 7818,7828 ----
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;
***************
*** 7540,7545 **** ATPostAlterTypeParse(Oid oldId, char *cmd,
--- 8630,8636 ----
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);
***************
*** 8325,8330 **** ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
--- 9416,9422 ----
HeapTuple inheritsTuple;
int32 inhseqno;
List *children;
+ List *child_attnums;
/*
* A self-exclusive lock is needed here. See the similar case in
***************
*** 8412,8421 **** ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
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.
--- 9504,9514 ----
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.
***************
*** 8491,8497 **** constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
* 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;
--- 9584,9591 ----
* 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;
***************
*** 8547,8552 **** MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
--- 9641,9651 ----
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.)
*/
***************
*** 8585,8591 **** MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
* 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;
--- 9684,9691 ----
* 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;
***************
*** 8612,8618 **** MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
HeapTuple child_tuple;
bool found = false;
! if (parent_con->contype != CONSTRAINT_CHECK)
continue;
/* Search for a child constraint matching this one */
--- 9712,9719 ----
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 */
***************
*** 8628,8646 **** MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
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
--- 9729,9775 ----
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
***************
*** 8683,8690 **** MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
* 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)
--- 9812,9819 ----
* 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)
***************
*** 8695,8702 **** ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyData key[3];
HeapTuple inheritsTuple,
attributeTuple,
! constraintTuple;
! List *connames;
bool found = false;
/*
--- 9824,9831 ----
ScanKeyData key[3];
HeapTuple inheritsTuple,
attributeTuple,
! constraintTuple;
! List *constraints;
bool found = false;
/*
***************
*** 8746,8751 **** ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
--- 9875,9882 ----
RelationGetRelationName(parent_rel),
RelationGetRelationName(rel))));
+ constraints = NIL;
+
/*
* Search through child columns looking for ones matching parent rel
*/
***************
*** 8759,8764 **** ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
--- 9890,9896 ----
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)
***************
*** 8766,8773 **** ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
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);
--- 9898,9907 ----
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);
***************
*** 8780,8795 **** ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
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);
ScanKeyInit(&key[0],
--- 9914,9931 ----
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. (For check constraints, we cheat a bit
! * by only checking for name matches, assuming that the expressions will
! * match.)
*/
catalogRelation = heap_open(ConstraintRelationId, RowExclusiveLock);
ScanKeyInit(&key[0],
***************
*** 8799,8812 **** ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
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);
--- 9935,9963 ----
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)
! {
! DisinheritConstraintInfo *inhInfo = (DisinheritConstraintInfo *)
! palloc(sizeof(DisinheritConstraintInfo));
! inhInfo->contype = CONSTRAINT_CHECK;
! inhInfo->conname = pstrdup(NameStr(con->conname));
! inhInfo->attnum = 0;
! constraints = lappend(constraints, inhInfo);
! }
! else if (con->contype == CONSTRAINT_NOTNULL)
! {
! DisinheritConstraintInfo *inhInfo = (DisinheritConstraintInfo *)
! palloc(sizeof(DisinheritConstraintInfo));
! inhInfo->contype = CONSTRAINT_NOTNULL;
! inhInfo->conname = NULL;
! inhInfo->attnum = get_constraint_singlecol_attno(constraintTuple);
! constraints = lappend(constraints, inhInfo);
! }
}
systable_endscan(scan);
***************
*** 8821,8837 **** ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
while (HeapTupleIsValid(constraintTuple = systable_getnext(scan)))
{
! 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;
--- 9972,10012 ----
while (HeapTupleIsValid(constraintTuple = systable_getnext(scan)))
{
! Form_pg_constraint con;
bool match;
ListCell *lc;
! con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
!
! 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)
{
! DisinheritConstraintInfo *inhInfo = (DisinheritConstraintInfo *) lfirst(lc);
!
! if (inhInfo->contype != con->contype)
! continue;
! if (con->contype == CONSTRAINT_CHECK &&
! strcmp(NameStr(con->conname), inhInfo->conname) == 0)
! {
! match = true;
! break;
! }
! if (con->contype == CONSTRAINT_NOTNULL &&
! (get_constraint_singlecol_attno(constraintTuple) == inhInfo->attnum))
{
match = true;
break;
***************
*** 8842,8853 **** ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
{
/* Decrement inhcount and possibly set islocal to true */
HeapTuple copyTuple = heap_copytuple(constraintTuple);
! Form_pg_constraint copy_con = (Form_pg_constraint) GETSTRUCT(copyTuple);
if (copy_con->coninhcount <= 0) /* shouldn't happen */
elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
RelationGetRelid(rel), NameStr(copy_con->conname));
copy_con->coninhcount--;
if (copy_con->coninhcount == 0)
copy_con->conislocal = true;
--- 10017,10032 ----
{
/* Decrement inhcount and possibly set islocal to true */
HeapTuple copyTuple = heap_copytuple(constraintTuple);
! Form_pg_constraint copy_con = (Form_pg_constraint)
! GETSTRUCT(copyTuple);
if (copy_con->coninhcount <= 0) /* shouldn't happen */
elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
RelationGetRelid(rel), NameStr(copy_con->conname));
+ elog(DEBUG1, "disinherit not null constraint \"%s\" relation \"%s\"",
+ NameStr(copy_con->conname), RelationGetRelationName(rel));
+
copy_con->coninhcount--;
if (copy_con->coninhcount == 0)
copy_con->conislocal = true;
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 95,101 **** static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
Oid baseTypeOid,
int typMod, Constraint *constr,
char *domainName);
!
/*
* DefineType
--- 95,103 ----
Oid baseTypeOid,
int typMod, Constraint *constr,
char *domainName);
! static void domainAddNotNull(Oid domainOid, Oid domainNamespace,
! Constraint *constr, char *domainName);
! static void domainDropNotNull(Oid domainOid);
/*
* DefineType
***************
*** 969,978 **** DefineDomain(CreateDomainStmt *stmt)
break;
case CONSTR_NOTNULL:
! if (nullDefined && !typNotNull)
! ereport(ERROR,
! (errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("conflicting NULL/NOT NULL constraints")));
typNotNull = true;
nullDefined = true;
break;
--- 971,995 ----
break;
case CONSTR_NOTNULL:
! if (nullDefined)
! {
! if (!typNotNull)
! ereport(ERROR,
! (errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("conflicting NULL/NOT NULL constraints")));
! else
! ereport(ERROR,
! (errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("multiple definitions for NOT NULL constraint")));
! }
!
! /*
! * We are going to create a pg_constraint entry for this
! * constraint to record an explicit named NOT NULL constraint.
! * We do this even in case it wasn't named explicitly. Since we
! * need the domain's OID, this is done later.
! */
!
typNotNull = true;
nullDefined = true;
break;
***************
*** 1080,1089 **** DefineDomain(CreateDomainStmt *stmt)
{
Constraint *constr = lfirst(listptr);
! /* it must be a Constraint, per check above */
switch (constr->contype)
{
case CONSTR_CHECK:
domainAddConstraint(domainoid, domainNamespace,
basetypeoid, basetypeMod,
--- 1097,1110 ----
{
Constraint *constr = lfirst(listptr);
! /* it must be a CHECK or NOT NULL Constraint, per check above */
switch (constr->contype)
{
+ case CONSTR_NOTNULL:
+ domainAddNotNull(domainoid, domainNamespace,
+ constr, domainName);
+ break;
case CONSTR_CHECK:
domainAddConstraint(domainoid, domainNamespace,
basetypeoid, basetypeMod,
***************
*** 1857,1868 **** AlterDomainNotNull(List *names, bool notNull)
--- 1878,1959 ----
CatalogUpdateIndexes(typrel, tup);
+ /* also update pg_constraint */
+ if (typTup->typnotnull)
+ {
+ Constraint constr;
+
+ /*
+ * ALTER DOMAIN ... SET NOT NULL doesn't have an infrastructure
+ * to specify a name for the new constraint, so always generate
+ * a new one.
+ */
+ constr.contype = CONSTR_NOTNULL;
+ constr.conname = NULL;
+
+ domainAddNotNull(domainoid, typTup->typnamespace,
+ &constr, NameStr(typTup->typname));
+ }
+ else
+ domainDropNotNull(domainoid);
+
/* Clean up */
heap_freetuple(tup);
heap_close(typrel, RowExclusiveLock);
}
/*
+ * Subroutine for AlterDomainNotNull
+ * Delete the pg_constraint entry for a domain's NOT NULL constraint
+ */
+ static void
+ domainDropNotNull(Oid domainOid)
+ {
+ Relation conrel;
+ HeapTuple contup;
+ SysScanDesc conscan;
+ ScanKeyData key[1];
+
+ /* Grab an appropriate lock on the pg_constraint relation */
+ conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
+
+ /* Use the index to scan only constraints of the target relation */
+ ScanKeyInit(&key[0],
+ Anum_pg_constraint_contypid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(domainOid));
+
+ conscan = systable_beginscan(conrel, ConstraintTypidIndexId, true,
+ SnapshotNow, 1, key);
+
+ while (HeapTupleIsValid(contup = systable_getnext(conscan)))
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(contup);
+
+ /*
+ * Perform deletion over pg_depend
+ */
+ if (con->contype == CONSTRAINT_NOTNULL)
+ {
+ ObjectAddress conobj;
+
+ conobj.classId = ConstraintRelationId;
+ conobj.objectId = HeapTupleGetOid(contup);
+ conobj.objectSubId = 0;
+
+ performDeletion(&conobj, DROP_RESTRICT);
+
+ /*
+ * we only expect a single tuple, but keep looping just in case.
+ */
+ }
+ }
+
+ systable_endscan(conscan);
+ heap_close(conrel, RowExclusiveLock);
+ }
+
+ /*
* AlterDomainDropConstraint
*
* Implements the ALTER DOMAIN DROP CONSTRAINT statement
***************
*** 2400,2405 **** checkDomainOwner(HeapTuple tup)
--- 2491,2558 ----
}
/*
+ * domainAddNotNull
+ * add a NOT NULL constraint to pg_constraint for a domain.
+ */
+ void
+ domainAddNotNull(Oid domainOid, Oid domainNamespace, Constraint *constr,
+ char *domainName)
+ {
+ Assert(constr->contype == CONSTR_NOTNULL);
+
+ /*
+ * Validate constraint name or assign a new one if not specified already.
+ */
+ if (constr->conname)
+ {
+ if (ConstraintNameIsUsed(CONSTRAINT_DOMAIN,
+ domainOid,
+ domainNamespace,
+ constr->conname))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("not null constraint \"%s\" for domain \"%s\" already exists",
+ constr->conname, domainName)));
+ }
+ else
+ constr->conname = ChooseConstraintName(domainName,
+ NULL,
+ "not_null",
+ domainNamespace,
+ NIL);
+
+ /*
+ * Store the not null constraint in pg_constraint
+ */
+ CreateConstraintEntry(constr->conname,
+ domainNamespace,
+ CONSTRAINT_NOTNULL,
+ false, /* Is Deferrable */
+ false, /* Is Deferred */
+ true, /* Is Validated */
+ InvalidOid, /* not a relation constraint */
+ NULL,
+ 0,
+ domainOid, /* domain constraint */
+ InvalidOid, /* no associated index */
+ InvalidOid, /* Foreign key fields */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ ' ',
+ ' ',
+ ' ',
+ NULL, /* not an exclusion constraint */
+ NULL, /* Tree form of check constraint */
+ NULL, /* Binary form of check constraint */
+ NULL, /* Source form of check constraint */
+ true, /* is local */
+ 0); /* inhcount */
+ }
+
+ /*
* domainAddConstraint - code shared between CREATE and ALTER DOMAIN
*/
static char *
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2305,2310 **** _copyColumnDef(ColumnDef *from)
--- 2305,2311 ----
COPY_SCALAR_FIELD(inhcount);
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
+ COPY_SCALAR_FIELD(is_primary_key);
COPY_SCALAR_FIELD(is_from_type);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 2236,2241 **** _equalColumnDef(ColumnDef *a, ColumnDef *b)
--- 2236,2242 ----
COMPARE_SCALAR_FIELD(inhcount);
COMPARE_SCALAR_FIELD(is_local);
COMPARE_SCALAR_FIELD(is_not_null);
+ COMPARE_SCALAR_FIELD(is_primary_key);
COMPARE_SCALAR_FIELD(is_from_type);
COMPARE_SCALAR_FIELD(storage);
COMPARE_NODE_FIELD(raw_default);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2095,2100 **** _outColumnDef(StringInfo str, ColumnDef *node)
--- 2095,2101 ----
WRITE_INT_FIELD(inhcount);
WRITE_BOOL_FIELD(is_local);
WRITE_BOOL_FIELD(is_not_null);
+ WRITE_BOOL_FIELD(is_primary_key);
WRITE_BOOL_FIELD(is_from_type);
WRITE_INT_FIELD(storage);
WRITE_NODE_FIELD(raw_default);
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 2505,2510 **** columnDef: ColId Typename ColQualList
--- 2505,2511 ----
n->inhcount = 0;
n->is_local = true;
n->is_not_null = false;
+ n->is_primary_key = false;
n->is_from_type = false;
n->storage = 0;
n->raw_default = NULL;
***************
*** 2524,2529 **** columnOptions: ColId WITH OPTIONS ColQualList
--- 2525,2531 ----
n->inhcount = 0;
n->is_local = true;
n->is_not_null = false;
+ n->is_primary_key = false;
n->is_from_type = false;
n->storage = 0;
n->raw_default = NULL;
***************
*** 3045,3050 **** CreateAsElement:
--- 3047,3053 ----
n->inhcount = 0;
n->is_local = true;
n->is_not_null = false;
+ n->is_primary_key = false;
n->is_from_type = false;
n->storage = 0;
n->raw_default = NULL;
***************
*** 9171,9176 **** TableFuncElement: ColId Typename opt_collate_clause
--- 9174,9180 ----
n->inhcount = 0;
n->is_local = true;
n->is_not_null = false;
+ n->is_primary_key = false;
n->is_from_type = false;
n->storage = 0;
n->raw_default = NULL;
*** a/src/backend/parser/parse_utilcmd.c
--- b/src/backend/parser/parse_utilcmd.c
***************
*** 65,71 ****
/* State shared by transformCreateStmt and its subroutines */
! typedef struct
{
ParseState *pstate; /* overall parser state */
const char *stmtType; /* "CREATE [FOREIGN] TABLE" or "ALTER TABLE" */
--- 65,71 ----
/* State shared by transformCreateStmt and its subroutines */
! typedef struct CreateStmtContext
{
ParseState *pstate; /* overall parser state */
const char *stmtType; /* "CREATE [FOREIGN] TABLE" or "ALTER TABLE" */
***************
*** 87,93 **** typedef struct
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
! typedef struct
{
const char *stmtType; /* "CREATE SCHEMA" or "ALTER SCHEMA" */
char *schemaname; /* name of schema */
--- 87,93 ----
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
! typedef struct CreateSchemaStmtContext
{
const char *stmtType; /* "CREATE SCHEMA" or "ALTER SCHEMA" */
char *schemaname; /* name of schema */
***************
*** 491,497 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
column->colname, cxt->relation->relname),
parser_errposition(cxt->pstate,
constraint->location)));
! column->is_not_null = FALSE;
saw_nullable = true;
break;
--- 491,497 ----
column->colname, cxt->relation->relname),
parser_errposition(cxt->pstate,
constraint->location)));
! column->is_not_null = false;
saw_nullable = true;
break;
***************
*** 503,509 **** transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
column->colname, cxt->relation->relname),
parser_errposition(cxt->pstate,
constraint->location)));
! column->is_not_null = TRUE;
saw_nullable = true;
break;
--- 503,509 ----
column->colname, cxt->relation->relname),
parser_errposition(cxt->pstate,
constraint->location)));
! column->is_not_null = true;
saw_nullable = true;
break;
***************
*** 678,683 **** transformInhRelation(CreateStmtContext *cxt, InhRelation *inhRelation)
--- 678,697 ----
def->constraints = NIL;
/*
+ * FIXME strictly speaking, we need to fetch pg_constraint rows so that
+ * we don't create spurious NOT NULL constraints for the primary key
+ * columns.
+ */
+ if (attribute->attnotnull)
+ {
+ Constraint *constraint = makeNode(Constraint);
+
+ constraint->contype = CONSTR_NOTNULL;
+ constraint->location = -1;
+ def->constraints = lappend(def->constraints, constraint);
+ }
+
+ /*
* Add to column list
*/
cxt->columns = lappend(cxt->columns, def);
***************
*** 1644,1650 **** transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
{
/* found column in the new table; force it to be NOT NULL */
if (constraint->contype == CONSTR_PRIMARY)
! column->is_not_null = TRUE;
}
else if (SystemAttributeByName(key, cxt->hasoids) != NULL)
{
--- 1658,1664 ----
{
/* found column in the new table; force it to be NOT NULL */
if (constraint->contype == CONSTR_PRIMARY)
! column->is_primary_key = true;
}
else if (SystemAttributeByName(key, cxt->hasoids) != NULL)
{
***************
*** 2300,2305 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
--- 2314,2321 ----
case AT_AddColumnToView:
{
ColumnDef *def = (ColumnDef *) cmd->def;
+ List *constraints = NIL;
+ ListCell *cell;
Assert(IsA(def, ColumnDef));
transformColumnDefinition(&cxt, def);
***************
*** 2312,2322 **** transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
skipValidation = false;
/*
! * All constraints are processed in other ways. Remove the
! * original list
*/
! def->constraints = NIL;
newcmds = lappend(newcmds, cmd);
break;
}
--- 2328,2346 ----
skipValidation = false;
/*
! * All constraints are processed in other ways, except NOT
! * NULL. Filter down the list so that only CONSTR_NOTNULL
! * nodes remain.
*/
! foreach(cell, def->constraints)
! {
! Constraint *constraint = (Constraint *) lfirst(cell);
!
! if (constraint->contype == CONSTR_NOTNULL)
! constraints = lappend(constraints, constraint);
! }
+ def->constraints = constraints;
newcmds = lappend(newcmds, cmd);
break;
}
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 1110,1122 **** pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
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;
--- 1110,1160 ----
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);
+
+ if (conForm->conrelid != InvalidOid)
+ {
+ /* Should not happen */
+ if (isnull)
+ elog(ERROR, "null conkey for constraint %u",
+ constraintId);
+
+ /* relation constraint */
+ decompile_column_index_array(val, conForm->conrelid, &buf);
+ appendStringInfo(&buf, " SET NOT NULL");
+ }
+ else
+ {
+ /* Domain constraint */
+ }
+
+ break;
+ }
case CONSTRAINT_FOREIGN:
{
Datum val;
*** a/src/bin/initdb/initdb.c
--- b/src/bin/initdb/initdb.c
***************
*** 165,170 **** static void setup_config(void);
--- 165,171 ----
static void bootstrap_template1(void);
static void setup_auth(void);
static void get_set_pwd(void);
+ static void setup_notnull_constraints(void);
static void setup_depend(void);
static void setup_sysviews(void);
static void setup_description(void);
***************
*** 1326,1331 **** get_set_pwd(void)
--- 1327,1372 ----
check_ok();
}
+ static void
+ setup_notnull_constraints(void)
+ {
+ PG_CMD_DECL;
+ const char **line;
+ static const char *notnull_constraints_setup[] = {
+ /*
+ * Create pg_constraint entries for all NOT NULL columns created so
+ * far. The name chosen should resemble the rules in
+ * ChooseConstraintName.
+ */
+ "INSERT INTO pg_constraint"
+ " SELECT format('%s_%s_notnull', relname, attname), relnamespace,"
+ " 'n', 'f', 'f', 't', pg_class.oid, 0, 0, 0, ' ', ' ', ' ',"
+ " 't', 0, ARRAY[attnum], null, null, null, null, null,"
+ " null, null"
+ " FROM pg_attribute att JOIN pg_class"
+ " ON (att.attrelid = pg_class.oid)"
+ " WHERE attnotnull AND attnum > 0;\n",
+ NULL
+ };
+
+ fputs(_("initializing constraints ... "), stdout);
+ fflush(stdout);
+
+ snprintf(cmd, sizeof(cmd),
+ "\"%s\" %s template1 >%s",
+ backend_exec, backend_options,
+ DEVNULL);
+
+ PG_CMD_OPEN;
+
+ for (line = notnull_constraints_setup; *line != NULL; line++)
+ PG_CMD_PUTS(*line);
+
+ PG_CMD_CLOSE;
+
+ check_ok();
+ }
+
/*
* set up pg_depend
*/
***************
*** 3269,3274 **** main(int argc, char *argv[])
--- 3310,3317 ----
if (pwprompt || pwfilename)
get_set_pwd();
+ setup_notnull_constraints();
+
setup_depend();
setup_sysviews();
*** a/src/bin/pg_dump/common.c
--- b/src/bin/pg_dump/common.c
***************
*** 439,444 **** flagInhAttrs(TableInfo *tblinfo, int numTables)
--- 439,449 ----
tbinfo->inhNotNull[j] = false;
}
+ /*
+ * Clear it if NOT NULL is known to have a local definition */
+ if (tbinfo->localnotnull[j])
+ tbinfo->inhNotNull[j] = false;
+
/* Clear it if attr has local definition */
if (tbinfo->attislocal[j])
tbinfo->inhAttrs[j] = false;
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 1767,1773 **** dumpDatabase(Archive *AH)
"datcollate, datctype, datfrozenxid, "
"(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, "
"shobj_description(oid, 'pg_database') AS description "
-
"FROM pg_database "
"WHERE datname = ",
username_subquery);
--- 1767,1772 ----
***************
*** 1781,1787 **** dumpDatabase(Archive *AH)
"NULL AS datcollate, NULL AS datctype, datfrozenxid, "
"(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, "
"shobj_description(oid, 'pg_database') AS description "
-
"FROM pg_database "
"WHERE datname = ",
username_subquery);
--- 1780,1785 ----
***************
*** 4827,4832 **** getDomainConstraints(TypeInfo *tyinfo)
--- 4825,4831 ----
int i_tableoid,
i_oid,
i_conname,
+ i_contype,
i_consrc;
int ntups;
***************
*** 4843,4856 **** getDomainConstraints(TypeInfo *tyinfo)
query = createPQExpBuffer();
if (g_fout->remoteVersion >= 70400)
! appendPQExpBuffer(query, "SELECT tableoid, oid, conname, "
"pg_catalog.pg_get_constraintdef(oid) AS consrc "
"FROM pg_catalog.pg_constraint "
"WHERE contypid = '%u'::pg_catalog.oid "
"ORDER BY conname",
tyinfo->dobj.catId.oid);
else
! appendPQExpBuffer(query, "SELECT tableoid, oid, conname, "
"'CHECK (' || consrc || ')' AS consrc "
"FROM pg_catalog.pg_constraint "
"WHERE contypid = '%u'::pg_catalog.oid "
--- 4842,4855 ----
query = createPQExpBuffer();
if (g_fout->remoteVersion >= 70400)
! appendPQExpBuffer(query, "SELECT tableoid, oid, conname, contype, "
"pg_catalog.pg_get_constraintdef(oid) AS consrc "
"FROM pg_catalog.pg_constraint "
"WHERE contypid = '%u'::pg_catalog.oid "
"ORDER BY conname",
tyinfo->dobj.catId.oid);
else
! appendPQExpBuffer(query, "SELECT tableoid, oid, conname, 'c' as contype, "
"'CHECK (' || consrc || ')' AS consrc "
"FROM pg_catalog.pg_constraint "
"WHERE contypid = '%u'::pg_catalog.oid "
***************
*** 4865,4870 **** getDomainConstraints(TypeInfo *tyinfo)
--- 4864,4870 ----
i_tableoid = PQfnumber(res, "tableoid");
i_oid = PQfnumber(res, "oid");
i_conname = PQfnumber(res, "conname");
+ i_contype = PQfnumber(res, "contype");
i_consrc = PQfnumber(res, "consrc");
constrinfo = (ConstraintInfo *) malloc(ntups * sizeof(ConstraintInfo));
***************
*** 4882,4888 **** getDomainConstraints(TypeInfo *tyinfo)
constrinfo[i].dobj.namespace = tyinfo->dobj.namespace;
constrinfo[i].contable = NULL;
constrinfo[i].condomain = tyinfo;
! constrinfo[i].contype = 'c';
constrinfo[i].condef = strdup(PQgetvalue(res, i, i_consrc));
constrinfo[i].confrelid = InvalidOid;
constrinfo[i].conindex = 0;
--- 4882,4888 ----
constrinfo[i].dobj.namespace = tyinfo->dobj.namespace;
constrinfo[i].contable = NULL;
constrinfo[i].condomain = tyinfo;
! constrinfo[i].contype = *PQgetvalue(res, i, i_contype);
constrinfo[i].condef = strdup(PQgetvalue(res, i, i_consrc));
constrinfo[i].confrelid = InvalidOid;
constrinfo[i].conindex = 0;
***************
*** 5581,5586 **** getTableAttrs(TableInfo *tblinfo, int numTables)
--- 5581,5587 ----
for (i = 0; i < numTables; i++)
{
TableInfo *tbinfo = &tblinfo[i];
+ int notnulls;
/* Don't bother to collect info for sequences */
if (tbinfo->relkind == RELKIND_SEQUENCE)
***************
*** 5739,5744 **** getTableAttrs(TableInfo *tblinfo, int numTables)
--- 5740,5747 ----
tbinfo->attalign = (char *) malloc(ntups * sizeof(char));
tbinfo->attislocal = (bool *) malloc(ntups * sizeof(bool));
tbinfo->notnull = (bool *) malloc(ntups * sizeof(bool));
+ tbinfo->namenotnull = (char **) malloc(ntups * sizeof(char *));
+ tbinfo->localnotnull = (bool *) malloc(ntups * sizeof(bool));
tbinfo->attrdefs = (AttrDefInfo **) malloc(ntups * sizeof(AttrDefInfo *));
tbinfo->attoptions = (char **) malloc(ntups * sizeof(char *));
tbinfo->attcollation = (Oid *) malloc(ntups * sizeof(Oid));
***************
*** 5746,5751 **** getTableAttrs(TableInfo *tblinfo, int numTables)
--- 5749,5755 ----
tbinfo->inhAttrDef = (bool *) malloc(ntups * sizeof(bool));
tbinfo->inhNotNull = (bool *) malloc(ntups * sizeof(bool));
hasdefaults = false;
+ notnulls = 0;
for (j = 0; j < ntups; j++)
{
***************
*** 5766,5771 **** getTableAttrs(TableInfo *tblinfo, int numTables)
--- 5770,5780 ----
tbinfo->attalign[j] = *(PQgetvalue(res, j, i_attalign));
tbinfo->attislocal[j] = (PQgetvalue(res, j, i_attislocal)[0] == 't');
tbinfo->notnull[j] = (PQgetvalue(res, j, i_attnotnull)[0] == 't');
+ tbinfo->namenotnull[j] = NULL; /* fix below */
+ tbinfo->localnotnull[j] = false; /* fix below */
+ /* not null info is dumped separately only in 9.2 up */
+ if (tbinfo->notnull[j] && g_fout->remoteVersion >= 90200)
+ notnulls++;
tbinfo->attoptions[j] = strdup(PQgetvalue(res, j, i_attoptions));
tbinfo->attcollation[j] = atooid(PQgetvalue(res, j, i_attcollation));
tbinfo->attrdefs[j] = NULL; /* fix below */
***************
*** 6020,6025 **** getTableAttrs(TableInfo *tblinfo, int numTables)
--- 6029,6074 ----
}
PQclear(res);
}
+
+ /*
+ * Get info about not null constraints
+ */
+ if (notnulls > 0)
+ {
+ if (g_verbose)
+ write_msg(NULL, "finding NOT NULL constraint names for table \"%s\"\n",
+ tbinfo->dobj.name);
+
+ resetPQExpBuffer(q);
+ appendPQExpBuffer(q, "SELECT conkey[1], conname, conislocal "
+ "FROM pg_catalog.pg_constraint "
+ "WHERE conrelid = '%u'::pg_catalog.oid "
+ " AND contype = 'n' "
+ "ORDER BY 1",
+ tbinfo->dobj.catId.oid);
+ res = PQexec(g_conn, q->data);
+ check_sql_result(res, g_conn, q->data, PGRES_TUPLES_OK);
+
+ if (PQntuples(res) != notnulls)
+ {
+ write_msg(NULL, ngettext("expected %d not null constraint on table \"%s\" but found %d\n",
+ "expected %d not null constraints on table \"%s\" but found %d\n",
+ notnulls),
+ notnulls, tbinfo->dobj.name, PQntuples(res));
+ write_msg(NULL, "(The system catalogs might be corrupted.)\n");
+ exit_nicely();
+ }
+
+ for (j = 0; j < notnulls; j++)
+ {
+ int colnum = atoi(PQgetvalue(res, j, 0));
+
+ tbinfo->namenotnull[colnum - 1] = strdup(PQgetvalue(res, j, 1));
+ tbinfo->localnotnull[colnum - 1] = PQgetvalue(res, j, 2)[0] == 't';
+ }
+
+ PQclear(res);
+ }
}
destroyPQExpBuffer(q);
***************
*** 7805,7811 **** dumpDomain(Archive *fout, TypeInfo *tyinfo)
selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
/* Fetch domain specific details */
! if (g_fout->remoteVersion >= 90100)
{
/* typcollation is new in 9.1 */
appendPQExpBuffer(query, "SELECT t.typnotnull, "
--- 7854,7875 ----
selectSourceSchema(tyinfo->dobj.namespace->dobj.name);
/* Fetch domain specific details */
! if (g_fout->remoteVersion >= 90200)
! {
! /* NOT NULL is handled as a constraint in 9.2 */
! appendPQExpBuffer(query, "SELECT 'f' AS typnotnull, "
! "pg_catalog.format_type(t.typbasetype, t.typtypmod) AS typdefn, "
! "pg_catalog.pg_get_expr(t.typdefaultbin, 'pg_catalog.pg_type'::pg_catalog.regclass) AS typdefaultbin, "
! "t.typdefault, "
! "CASE WHEN t.typcollation <> u.typcollation "
! "THEN t.typcollation ELSE 0 END AS typcollation "
! "FROM pg_catalog.pg_type t "
! "LEFT JOIN pg_catalog.pg_type u ON (t.typbasetype = u.oid) "
! "WHERE t.oid = '%u'::pg_catalog.oid",
! tyinfo->dobj.catId.oid);
!
! }
! else if (g_fout->remoteVersion >= 90100)
{
/* typcollation is new in 9.1 */
appendPQExpBuffer(query, "SELECT t.typnotnull, "
***************
*** 7904,7911 **** dumpDomain(Archive *fout, TypeInfo *tyinfo)
ConstraintInfo *domcheck = &(tyinfo->domChecks[i]);
if (!domcheck->separate)
! appendPQExpBuffer(q, "\n\tCONSTRAINT %s %s",
! fmtId(domcheck->dobj.name), domcheck->condef);
}
appendPQExpBuffer(q, ";\n");
--- 7968,7981 ----
ConstraintInfo *domcheck = &(tyinfo->domChecks[i]);
if (!domcheck->separate)
! {
! if (domcheck->contype == 'c')
! appendPQExpBuffer(q, "\n\tCONSTRAINT %s %s",
! fmtId(domcheck->dobj.name), domcheck->condef);
! else
! appendPQExpBuffer(q, "\n\tCONSTRAINT %s NOT NULL",
! fmtId(domcheck->dobj.name));
! }
}
appendPQExpBuffer(q, ";\n");
***************
*** 12202,12208 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
--- 12272,12282 ----
tbinfo->attrdefs[j]->adef_expr);
if (has_notnull)
+ {
+ if (tbinfo->namenotnull[j])
+ appendPQExpBuffer(q, " CONSTRAINT %s", tbinfo->namenotnull[j]);
appendPQExpBuffer(q, " NOT NULL");
+ }
}
}
***************
*** 12888,12893 **** dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
--- 12962,13005 ----
NULL, NULL);
}
}
+ else if (coninfo->contype == 'n' && tbinfo == NULL)
+ {
+ /* NOT NULL constraint on a domain */
+ TypeInfo *tyinfo = coninfo->condomain;
+
+ /* Ignore if not to be dumped separately */
+ if (coninfo->separate)
+ {
+ /*
+ * FIXME this code path is apparently unused. Is there any use
+ * for separate NOT NULL constraints?
+ */
+ appendPQExpBuffer(q, "ALTER DOMAIN %s\n",
+ fmtId(tyinfo->dobj.name));
+ /* XXX we lose the constraint name */
+ appendPQExpBuffer(q, " SET NOT NULL;\n");
+
+ /*
+ * DROP must be fully qualified in case same name appears in
+ * pg_catalog
+ */
+ appendPQExpBuffer(delq, "ALTER DOMAIN %s.",
+ fmtId(tyinfo->dobj.namespace->dobj.name));
+ appendPQExpBuffer(delq, "%s ",
+ fmtId(tyinfo->dobj.name));
+ appendPQExpBuffer(delq, "DROP NOT NULL;\n");
+
+ ArchiveEntry(fout, coninfo->dobj.catId, coninfo->dobj.dumpId,
+ coninfo->dobj.name,
+ tyinfo->dobj.namespace->dobj.name,
+ NULL,
+ tyinfo->rolname, false,
+ "NOT NULL CONSTRAINT", SECTION_POST_DATA,
+ q->data, delq->data, NULL,
+ coninfo->dobj.dependencies, coninfo->dobj.nDeps,
+ NULL, NULL);
+ }
+ }
else
{
write_msg(NULL, "unrecognized constraint type: %c\n", coninfo->contype);
*** a/src/bin/pg_dump/pg_dump.h
--- b/src/bin/pg_dump/pg_dump.h
***************
*** 282,287 **** typedef struct _tableInfo
--- 282,289 ----
* were inherited.
*/
bool *notnull; /* Not null constraints on attributes */
+ bool *localnotnull; /* whether NOT NULL constrs have local defs */
+ char **namenotnull; /* names of the NOT NULL constraints */
struct _attrDefInfo **attrdefs; /* DEFAULT expressions */
bool *inhAttrs; /* true if each attribute is inherited */
bool *inhAttrDef; /* true if attr's default is inherited */
*** a/src/bin/pg_dump/pg_dump_sort.c
--- b/src/bin/pg_dump/pg_dump_sort.c
***************
*** 753,759 **** repairTableAttrDefMultiLoop(DumpableObject *tableobj,
}
/*
! * CHECK constraints on domains work just like those on tables ...
*/
static void
repairDomainConstraintLoop(DumpableObject *domainobj,
--- 753,759 ----
}
/*
! * CHECK and NOT NULL constraints on domains work just like those on tables ...
*/
static void
repairDomainConstraintLoop(DumpableObject *domainobj,
***************
*** 928,938 **** repairDependencyLoop(DumpableObject **loop,
}
}
! /* Domain and CHECK constraint */
if (nLoop == 2 &&
loop[0]->objType == DO_TYPE &&
loop[1]->objType == DO_CONSTRAINT &&
! ((ConstraintInfo *) loop[1])->contype == 'c' &&
((ConstraintInfo *) loop[1])->condomain == (TypeInfo *) loop[0])
{
repairDomainConstraintLoop(loop[0], loop[1]);
--- 928,939 ----
}
}
! /* Domain and CHECK or NOT NULL constraint */
if (nLoop == 2 &&
loop[0]->objType == DO_TYPE &&
loop[1]->objType == DO_CONSTRAINT &&
! (((ConstraintInfo *) loop[1])->contype == 'c' ||
! ((ConstraintInfo *) loop[1])->contype == 'n') &&
((ConstraintInfo *) loop[1])->condomain == (TypeInfo *) loop[0])
{
repairDomainConstraintLoop(loop[0], loop[1]);
***************
*** 941,954 **** repairDependencyLoop(DumpableObject **loop,
if (nLoop == 2 &&
loop[1]->objType == DO_TYPE &&
loop[0]->objType == DO_CONSTRAINT &&
! ((ConstraintInfo *) loop[0])->contype == 'c' &&
((ConstraintInfo *) loop[0])->condomain == (TypeInfo *) loop[1])
{
repairDomainConstraintLoop(loop[1], loop[0]);
return;
}
! /* Indirect loop involving domain and CHECK constraint */
if (nLoop > 2)
{
for (i = 0; i < nLoop; i++)
--- 942,956 ----
if (nLoop == 2 &&
loop[1]->objType == DO_TYPE &&
loop[0]->objType == DO_CONSTRAINT &&
! (((ConstraintInfo *) loop[0])->contype == 'c' ||
! ((ConstraintInfo *) loop[0])->contype == 'n') &&
((ConstraintInfo *) loop[0])->condomain == (TypeInfo *) loop[1])
{
repairDomainConstraintLoop(loop[1], loop[0]);
return;
}
! /* Indirect loop involving domain and CHECK or NOT NULL constraint */
if (nLoop > 2)
{
for (i = 0; i < nLoop; i++)
***************
*** 958,964 **** repairDependencyLoop(DumpableObject **loop,
for (j = 0; j < nLoop; j++)
{
if (loop[j]->objType == DO_CONSTRAINT &&
! ((ConstraintInfo *) loop[j])->contype == 'c' &&
((ConstraintInfo *) loop[j])->condomain == (TypeInfo *) loop[i])
{
repairDomainConstraintMultiLoop(loop[i], loop[j]);
--- 960,967 ----
for (j = 0; j < nLoop; j++)
{
if (loop[j]->objType == DO_CONSTRAINT &&
! (((ConstraintInfo *) loop[j])->contype == 'c' ||
! ((ConstraintInfo *) loop[j])->contype == 'n') &&
((ConstraintInfo *) loop[j])->condomain == (TypeInfo *) loop[i])
{
repairDomainConstraintMultiLoop(loop[i], loop[j]);
*** a/src/include/catalog/pg_attribute.h
--- b/src/include/catalog/pg_attribute.h
***************
*** 127,133 **** CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK
*/
char attalign;
! /* This flag represents the "NOT NULL" constraint */
bool attnotnull;
/* Has DEFAULT value or not */
--- 127,136 ----
*/
char attalign;
! /*
! * Whether the column is nullable. This might be from a NOT NULL
! * constraint or a primary key.
! */
bool attnotnull;
/* Has DEFAULT value or not */
*** a/src/include/catalog/pg_constraint.h
--- b/src/include/catalog/pg_constraint.h
***************
*** 177,182 **** typedef FormData_pg_constraint *Form_pg_constraint;
--- 177,183 ----
/* Valid values for contype */
#define CONSTRAINT_CHECK 'c'
+ #define CONSTRAINT_NOTNULL 'n'
#define CONSTRAINT_FOREIGN 'f'
#define CONSTRAINT_PRIMARY 'p'
#define CONSTRAINT_UNIQUE 'u'
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 493,498 **** typedef struct ColumnDef
--- 493,499 ----
int inhcount; /* number of times column is inherited */
bool is_local; /* column has local (non-inherited) def'n */
bool is_not_null; /* NOT NULL constraint specified? */
+ bool is_primary_key; /* Does it belong to a PK? */
bool is_from_type; /* column definition came from table type */
char storage; /* attstorage setting, or 0 for default */
Node *raw_default; /* default value (untransformed parse tree) */
***************
*** 1178,1184 **** typedef enum AlterTableType
--- 1179,1188 ----
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_SetAttNotNull, /* internal (used by DefineIndex) */
AT_SetStatistics, /* alter column set statistics */
AT_SetOptions, /* alter column set ( options ) */
AT_ResetOptions, /* alter column reset ( options ) */
*** a/src/test/regress/expected/alter_table.out
--- b/src/test/regress/expected/alter_table.out
***************
*** 620,626 **** create table atacc1 ( test int );
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
--- 620,626 ----
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
***************
*** 637,643 **** insert into atacc1 (test) values (0);
-- 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"
--- 637,643 ----
-- 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"
***************
*** 695,706 **** create table atacc1 (test int not null) with oids;
alter table atacc1 add constraint "atacc1_pkey" primary key (test);
NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index "atacc1_pkey" for table "atacc1"
alter table atacc1 alter column test drop not null;
- ERROR: column "test" is in a primary key
alter table atacc1 drop constraint "atacc1_pkey";
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
--- 695,705 ----
alter table atacc1 add constraint "atacc1_pkey" primary key (test);
NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index "atacc1_pkey" for table "atacc1"
alter table atacc1 alter column test drop not null;
alter table atacc1 drop constraint "atacc1_pkey";
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
***************
*** 710,716 **** alter table atacc1 alter bar drop not null;
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
--- 709,715 ----
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"
-- try creating a view and altering that, should fail
***************
*** 733,745 **** alter table parent alter a drop not null;
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
--- 732,744 ----
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
*** a/src/test/regress/expected/cluster.out
--- b/src/test/regress/expected/cluster.out
***************
*** 251,261 **** ERROR: insert or update on table "clstr_tst" violates foreign key constraint "c
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
*** a/src/test/regress/expected/domain.out
--- b/src/test/regress/expected/domain.out
***************
*** 305,310 **** drop domain dnotnulltest cascade;
--- 305,327 ----
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to table domnotnull column col1
drop cascades to table domnotnull column col2
+ create domain dnotnulltest integer constraint dnn not null;
+ select conname, contype, contypid::regtype from pg_constraint c
+ where contype = 'n' and contypid <> 0 order by 1;
+ conname | contype | contypid
+ ----------------+---------+--------------
+ ddef5_not_null | n | ddef5
+ dnn | n | dnotnulltest
+ (2 rows)
+
+ drop domain dnotnulltest;
+ select conname, contype, contypid::regtype from pg_constraint
+ where contype = 'n' and contypid <> 0 order by 1;
+ conname | contype | contypid
+ ----------------+---------+----------
+ ddef5_not_null | n | ddef5
+ (1 row)
+
-- Test ALTER DOMAIN .. DEFAULT ..
create table domdeftest (col1 ddef1);
insert into domdeftest default values;
*** a/src/test/regress/expected/inherit.out
--- b/src/test/regress/expected/inherit.out
***************
*** 990,996 **** NOTICE: merging multiple inherited definitions of column "a"
Table "public.t13_inh"
Column | Type | Modifiers | Storage | Description
--------+------+-----------+----------+-------------
! a | text | not null | main |
b | text | | extended |
c | text | | external |
Check constraints:
--- 990,996 ----
Table "public.t13_inh"
Column | Type | Modifiers | Storage | Description
--------+------+-----------+----------+-------------
! a | text | | main |
b | text | | extended |
c | text | | external |
Check constraints:
***************
*** 1006,1012 **** NOTICE: merging column "a" with inherited definition
Table "public.t13_like"
Column | Type | Modifiers | Storage | Description
--------+------+-----------+----------+-------------
! a | text | not null | main | A3
b | text | | extended |
c | text | | external | C
Check constraints:
--- 1006,1012 ----
Table "public.t13_like"
Column | Type | Modifiers | Storage | Description
--------+------+-----------+----------+-------------
! a | text | | main | A3
b | text | | extended |
c | text | | external | C
Check constraints:
***************
*** 1242,1244 **** NOTICE: drop cascades to 3 other objects
--- 1242,1546 ----
DETAIL: drop cascades to table matest1
drop cascades to table matest2
drop cascades to table matest3
+ --
+ -- Test inheritance of NOT NULL constraints
+ --
+ create table pp1 (f1 int);
+ create table cc1 (f2 text, f3 int) inherits (pp1);
+ \d cc1
+ Table "public.cc1"
+ Column | Type | Modifiers
+ --------+---------+-----------
+ f1 | integer |
+ f2 | text |
+ f3 | integer |
+ Inherits: pp1
+
+ create table cc2(f4 float) inherits(pp1,cc1);
+ NOTICE: merging multiple inherited definitions of column "f1"
+ \d cc2
+ Table "public.cc2"
+ Column | Type | Modifiers
+ --------+------------------+-----------
+ f1 | integer |
+ f2 | text |
+ f3 | integer |
+ f4 | double precision |
+ Inherits: pp1,
+ cc1
+
+ -- named NOT NULL constraint
+ alter table cc1 add column a2 int constraint nn not null;
+ \d cc1
+ Table "public.cc1"
+ Column | Type | Modifiers
+ --------+---------+-----------
+ f1 | integer |
+ f2 | text |
+ f3 | integer |
+ a2 | integer | not null
+ Inherits: pp1
+ Number of child tables: 1 (Use \d+ to list them.)
+
+ \d cc2
+ Table "public.cc2"
+ Column | Type | Modifiers
+ --------+------------------+-----------
+ f1 | integer |
+ f2 | text |
+ f3 | integer |
+ f4 | double precision |
+ a2 | integer | not null
+ Inherits: pp1,
+ cc1
+
+ alter table pp1 alter column f1 set not null;
+ \d pp1
+ Table "public.pp1"
+ Column | Type | Modifiers
+ --------+---------+-----------
+ f1 | integer | not null
+ Number of child tables: 2 (Use \d+ to list them.)
+
+ \d cc1
+ Table "public.cc1"
+ Column | Type | Modifiers
+ --------+---------+-----------
+ f1 | integer | not null
+ f2 | text |
+ f3 | integer |
+ a2 | integer | not null
+ Inherits: pp1
+ Number of child tables: 1 (Use \d+ to list them.)
+
+ \d cc2
+ Table "public.cc2"
+ Column | Type | Modifiers
+ --------+------------------+-----------
+ f1 | integer | not null
+ f2 | text |
+ f3 | integer |
+ f4 | double precision |
+ a2 | integer | not null
+ Inherits: pp1,
+ cc1
+
+ -- have a look at pg_constraint
+ select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' and conrelid::regclass in ('pp1', 'cc1', 'cc2') order by 2;
+ conrelid | conname | contype | coninhcount | conislocal
+ ----------+-----------------+---------+-------------+------------
+ cc1 | cc1_f1_not_null | n | 1 | f
+ cc2 | cc2_f1_not_null | n | 2 | f
+ cc1 | nn | n | 0 | t
+ cc2 | nn | n | 1 | f
+ pp1 | pp1_f1_not_null | n | 0 | t
+ (5 rows)
+
+ -- remove constraint from cc2, should fail
+ alter table cc2 alter column a2 drop not null;
+ ERROR: cannot drop inherited NOT NULL constraint "nn", relation "cc2"
+ -- remove constraint cc1, should succeed
+ alter table cc1 alter column a2 drop not null;
+ -- have a look at pg_constraint
+ select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' and conrelid::regclass in ('pp1', 'cc1', 'cc2') order by 2;
+ conrelid | conname | contype | coninhcount | conislocal
+ ----------+-----------------+---------+-------------+------------
+ cc1 | cc1_f1_not_null | n | 1 | f
+ cc2 | cc2_f1_not_null | n | 2 | f
+ pp1 | pp1_f1_not_null | n | 0 | t
+ (3 rows)
+
+ -- same for cc2
+ alter table cc2 alter column f1 drop not null;
+ ERROR: cannot drop inherited NOT NULL constraint "cc2_f1_not_null", relation "cc2"
+ -- remove from cc1, should fail again
+ alter table cc1 alter column f1 drop not null;
+ ERROR: cannot drop inherited NOT NULL constraint "cc1_f1_not_null", relation "cc1"
+ -- remove from pp1, should succeed
+ alter table pp1 alter column f1 drop not null;
+ -- have a look at pg_constraint
+ select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' and conrelid::regclass in ('pp1', 'cc1', 'cc2') order by 2;
+ conrelid | conname | contype | coninhcount | conislocal
+ ----------+---------+---------+-------------+------------
+ (0 rows)
+
+ drop table pp1 cascade;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to table cc1
+ drop cascades to table cc2
+ -- have a look at pg_constraint, everything should be clean now
+ select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' and conrelid::regclass in ('pp1', 'cc1', 'cc2') order by 2;
+ ERROR: relation "pp1" does not exist
+ LINE 1: ...nt where contype = 'n' and conrelid::regclass in ('pp1', 'cc...
+ ^
+ --
+ -- test inherit/deinherit
+ --
+ create table parent(f1 int);
+ create table child1(f1 int not null);
+ create table child2(f1 int);
+ -- child1 should have not null constraint
+ alter table child1 inherit parent;
+ -- should fail, missing NOT NULL constraint
+ alter table child2 inherit child1;
+ ERROR: column "f1" in child table must be marked NOT NULL
+ alter table child2 alter column f1 set not null;
+ alter table child2 inherit child1;
+ -- add NOT NULL constraint recursively
+ alter table parent alter column f1 set not null;
+ \d parent
+ Table "public.parent"
+ Column | Type | Modifiers
+ --------+---------+-----------
+ f1 | integer | not null
+ Number of child tables: 1 (Use \d+ to list them.)
+
+ \d child1
+ Table "public.child1"
+ Column | Type | Modifiers
+ --------+---------+-----------
+ f1 | integer | not null
+ Inherits: parent
+ Number of child tables: 1 (Use \d+ to list them.)
+
+ \d child2
+ Table "public.child2"
+ Column | Type | Modifiers
+ --------+---------+-----------
+ f1 | integer | not null
+ Inherits: child1
+
+ select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' and conrelid::regclass in ('parent', 'child1', 'child2') order by 2;
+ conrelid | conname | contype | coninhcount | conislocal
+ ----------+--------------------+---------+-------------+------------
+ child1 | child1_f1_not_null | n | 1 | t
+ child2 | child2_f1_not_null | n | 1 | t
+ parent | parent_f1_not_null | n | 0 | t
+ (3 rows)
+
+ --
+ -- test deinherit procedure
+ --
+ -- deinherit child1
+ alter table child1 no inherit parent;
+ \d parent
+ Table "public.parent"
+ Column | Type | Modifiers
+ --------+---------+-----------
+ f1 | integer | not null
+
+ \d child1
+ Table "public.child1"
+ Column | Type | Modifiers
+ --------+---------+-----------
+ f1 | integer | not null
+ Number of child tables: 1 (Use \d+ to list them.)
+
+ \d child2
+ Table "public.child2"
+ Column | Type | Modifiers
+ --------+---------+-----------
+ f1 | integer | not null
+ Inherits: child1
+
+ select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' and conrelid::regclass in ('parent', 'child1', 'child2') order by 2;
+ conrelid | conname | contype | coninhcount | conislocal
+ ----------+--------------------+---------+-------------+------------
+ child1 | child1_f1_not_null | n | 0 | t
+ child2 | child2_f1_not_null | n | 1 | t
+ parent | parent_f1_not_null | n | 0 | t
+ (3 rows)
+
+ -- test inhcount of child2, should fail
+ alter table child2 alter f1 drop not null;
+ ERROR: cannot drop inherited NOT NULL constraint "child2_f1_not_null", relation "child2"
+ -- should succeed
+ drop table parent;
+ drop table child1 cascade;
+ NOTICE: drop cascades to table child2
+ --
+ -- test multi inheritance tree
+ --
+ create table parent(f1 int not null);
+ create table c1() inherits(parent);
+ create table c2() inherits(parent);
+ create table d1() inherits(c1, c2);
+ NOTICE: merging multiple inherited definitions of column "f1"
+ -- show constraint info
+ select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' and conrelid::regclass in ('parent', 'c1', 'c2', 'd1') order by 2;
+ conrelid | conname | contype | coninhcount | conislocal
+ ----------+--------------------+---------+-------------+------------
+ parent | parent_f1_not_null | n | 0 | t
+ c1 | parent_f1_not_null | n | 1 | f
+ c2 | parent_f1_not_null | n | 1 | f
+ d1 | parent_f1_not_null | n | 2 | f
+ (4 rows)
+
+ drop table parent cascade;
+ NOTICE: drop cascades to 3 other objects
+ DETAIL: drop cascades to table c1
+ drop cascades to table c2
+ drop cascades to table d1
+ -- test child table with inherited columns and
+ -- with explicitely specified not null constraints
+ create table parent1(f1 int);
+ create table parent2(f2 text);
+ create table child(f1 int not null, f2 text not null) inherits(parent1, parent2);
+ NOTICE: merging column "f1" with inherited definition
+ NOTICE: merging column "f2" with inherited definition
+ -- show constraint info
+ select conrelid::regclass, conname, contype, coninhcount, conislocal, conkey from pg_constraint where contype = 'n' and conrelid::regclass in ('parent1', 'parent2', 'child') order by 2;
+ conrelid | conname | contype | coninhcount | conislocal | conkey
+ ----------+-------------------+---------+-------------+------------+--------
+ child | child_f1_not_null | n | 0 | t | {1}
+ child | child_f2_not_null | n | 0 | t | {2}
+ (2 rows)
+
+ -- also drops child table
+ drop table parent1 cascade;
+ NOTICE: drop cascades to table child
+ drop table parent2;
+ -- test multi layer inheritance tree
+ create table p1(f1 int not null);
+ create table p2(f1 int not null);
+ create table p3(f2 int);
+ create table p4(f1 int not null, f3 text not null);
+ create table c1() inherits(p1, p2, p3, p4);
+ NOTICE: merging multiple inherited definitions of column "f1"
+ NOTICE: merging multiple inherited definitions of column "f1"
+ -- constraint on f1 should have three parents
+ select conrelid::regclass, conname, contype, coninhcount, conislocal, conkey from pg_constraint where contype = 'n' and conrelid::regclass in ('p1', 'p2', 'p3', 'p4', 'c1') order by 1, 2;
+ conrelid | conname | contype | coninhcount | conislocal | conkey
+ ----------+----------------+---------+-------------+------------+--------
+ p1 | p1_f1_not_null | n | 0 | t | {1}
+ p2 | p2_f1_not_null | n | 0 | t | {1}
+ p4 | p4_f1_not_null | n | 0 | t | {1}
+ p4 | p4_f3_not_null | n | 0 | t | {2}
+ c1 | p1_f1_not_null | n | 3 | f | {1}
+ c1 | p4_f3_not_null | n | 1 | f | {3}
+ (6 rows)
+
+ create table d1(a int not null, f1 int) inherits(p3, c1);
+ NOTICE: merging multiple inherited definitions of column "f2"
+ NOTICE: merging column "f1" with inherited definition
+ select conrelid::regclass, conname, contype, coninhcount, conislocal, conkey from pg_constraint where contype = 'n' and conrelid::regclass in ('p1', 'p2', 'p3', 'p4', 'c1', 'd1') order by 1, 2;
+ conrelid | conname | contype | coninhcount | conislocal | conkey
+ ----------+----------------+---------+-------------+------------+--------
+ p1 | p1_f1_not_null | n | 0 | t | {1}
+ p2 | p2_f1_not_null | n | 0 | t | {1}
+ p4 | p4_f1_not_null | n | 0 | t | {1}
+ p4 | p4_f3_not_null | n | 0 | t | {2}
+ c1 | p1_f1_not_null | n | 3 | f | {1}
+ c1 | p4_f3_not_null | n | 1 | f | {3}
+ d1 | d1_a_not_null | n | 0 | t | {4}
+ d1 | p1_f1_not_null | n | 1 | f | {2}
+ d1 | p4_f3_not_null | n | 1 | f | {3}
+ (9 rows)
+
+ drop table p1 cascade;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to table c1
+ drop cascades to table d1
+ drop table p2;
+ drop table p3;
+ drop table p4;
*** a/src/test/regress/sql/domain.sql
--- b/src/test/regress/sql/domain.sql
***************
*** 224,229 **** update domnotnull set col1 = null;
--- 224,239 ----
drop domain dnotnulltest cascade;
+ create domain dnotnulltest integer constraint dnn not null;
+
+ select conname, contype, contypid::regtype from pg_constraint c
+ where contype = 'n' and contypid <> 0 order by 1;
+
+ drop domain dnotnulltest;
+
+ select conname, contype, contypid::regtype from pg_constraint
+ where contype = 'n' and contypid <> 0 order by 1;
+
-- Test ALTER DOMAIN .. DEFAULT ..
create table domdeftest (col1 ddef1);
*** a/src/test/regress/sql/inherit.sql
--- b/src/test/regress/sql/inherit.sql
***************
*** 406,408 **** select * from matest0 order by 1-id;
--- 406,545 ----
reset enable_seqscan;
drop table matest0 cascade;
+
+ --
+ -- Test inheritance of NOT NULL constraints
+ --
+ create table pp1 (f1 int);
+ create table cc1 (f2 text, f3 int) inherits (pp1);
+ \d cc1
+ create table cc2(f4 float) inherits(pp1,cc1);
+ \d cc2
+
+ -- named NOT NULL constraint
+ alter table cc1 add column a2 int constraint nn not null;
+ \d cc1
+ \d cc2
+ alter table pp1 alter column f1 set not null;
+ \d pp1
+ \d cc1
+ \d cc2
+
+ -- have a look at pg_constraint
+ select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' and conrelid::regclass in ('pp1', 'cc1', 'cc2') order by 2;
+
+ -- remove constraint from cc2, should fail
+ alter table cc2 alter column a2 drop not null;
+
+ -- remove constraint cc1, should succeed
+ alter table cc1 alter column a2 drop not null;
+
+ -- have a look at pg_constraint
+ select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' and conrelid::regclass in ('pp1', 'cc1', 'cc2') order by 2;
+
+ -- same for cc2
+ alter table cc2 alter column f1 drop not null;
+
+ -- remove from cc1, should fail again
+ alter table cc1 alter column f1 drop not null;
+
+ -- remove from pp1, should succeed
+ alter table pp1 alter column f1 drop not null;
+
+ -- have a look at pg_constraint
+ select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' and conrelid::regclass in ('pp1', 'cc1', 'cc2') order by 2;
+
+ drop table pp1 cascade;
+
+ -- have a look at pg_constraint, everything should be clean now
+ select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' and conrelid::regclass in ('pp1', 'cc1', 'cc2') order by 2;
+
+ --
+ -- test inherit/deinherit
+ --
+ create table parent(f1 int);
+ create table child1(f1 int not null);
+ create table child2(f1 int);
+
+ -- child1 should have not null constraint
+ alter table child1 inherit parent;
+
+ -- should fail, missing NOT NULL constraint
+ alter table child2 inherit child1;
+
+ alter table child2 alter column f1 set not null;
+ alter table child2 inherit child1;
+
+ -- add NOT NULL constraint recursively
+ alter table parent alter column f1 set not null;
+
+ \d parent
+ \d child1
+ \d child2
+
+ select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' and conrelid::regclass in ('parent', 'child1', 'child2') order by 2;
+
+ --
+ -- test deinherit procedure
+ --
+
+ -- deinherit child1
+ alter table child1 no inherit parent;
+ \d parent
+ \d child1
+ \d child2
+ select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' and conrelid::regclass in ('parent', 'child1', 'child2') order by 2;
+
+ -- test inhcount of child2, should fail
+ alter table child2 alter f1 drop not null;
+
+ -- should succeed
+
+ drop table parent;
+ drop table child1 cascade;
+
+ --
+ -- test multi inheritance tree
+ --
+ create table parent(f1 int not null);
+ create table c1() inherits(parent);
+ create table c2() inherits(parent);
+ create table d1() inherits(c1, c2);
+
+ -- show constraint info
+ select conrelid::regclass, conname, contype, coninhcount, conislocal from pg_constraint where contype = 'n' and conrelid::regclass in ('parent', 'c1', 'c2', 'd1') order by 2;
+
+ drop table parent cascade;
+
+ -- test child table with inherited columns and
+ -- with explicitely specified not null constraints
+ create table parent1(f1 int);
+ create table parent2(f2 text);
+ create table child(f1 int not null, f2 text not null) inherits(parent1, parent2);
+
+ -- show constraint info
+ select conrelid::regclass, conname, contype, coninhcount, conislocal, conkey from pg_constraint where contype = 'n' and conrelid::regclass in ('parent1', 'parent2', 'child') order by 2;
+
+ -- also drops child table
+ drop table parent1 cascade;
+ drop table parent2;
+
+ -- test multi layer inheritance tree
+ create table p1(f1 int not null);
+ create table p2(f1 int not null);
+ create table p3(f2 int);
+ create table p4(f1 int not null, f3 text not null);
+
+ create table c1() inherits(p1, p2, p3, p4);
+
+ -- constraint on f1 should have three parents
+ select conrelid::regclass, conname, contype, coninhcount, conislocal, conkey from pg_constraint where contype = 'n' and conrelid::regclass in ('p1', 'p2', 'p3', 'p4', 'c1') order by 1, 2;
+
+ create table d1(a int not null, f1 int) inherits(p3, c1);
+
+ select conrelid::regclass, conname, contype, coninhcount, conislocal, conkey from pg_constraint where contype = 'n' and conrelid::regclass in ('p1', 'p2', 'p3', 'p4', 'c1', 'd1') order by 1, 2;
+
+ drop table p1 cascade;
+ drop table p2;
+ drop table p3;
+ drop table p4;