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