diff --git a/contrib/sepgsql/expected/alter.out b/contrib/sepgsql/expected/alter.out
index ae43537505..1462cfa3cb 100644
--- a/contrib/sepgsql/expected/alter.out
+++ b/contrib/sepgsql/expected/alter.out
@@ -164,7 +164,6 @@ LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_re
LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.b" permissive=0
ALTER TABLE regtest_table ALTER b DROP NOT NULL;
LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table.b" permissive=0
-LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.b" permissive=0
ALTER TABLE regtest_table ALTER b SET STATISTICS -1;
LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table.b" permissive=0
LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.b" permissive=0
@@ -249,8 +248,6 @@ LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_re
LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_1_tens.p" permissive=0
ALTER TABLE regtest_ptable ALTER p DROP NOT NULL;
LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_ptable.p" permissive=0
-LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table_part.p" permissive=0
-LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_1_tens.p" permissive=0
ALTER TABLE regtest_ptable ALTER p SET STATISTICS -1;
LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_ptable.p" permissive=0
LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table_part.p" permissive=0
diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index 5713b8ab1c..bcd1f74b2b 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -492,6 +492,9 @@ WITH (user_catalog_table = true)
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Not-null constraints:
+ "replication_metadata_id_not_null" NOT NULL "id"
+ "replication_metadata_relation_not_null" NOT NULL "relation"
Options: user_catalog_table=true
INSERT INTO replication_metadata(relation, options)
@@ -506,6 +509,9 @@ ALTER TABLE replication_metadata RESET (user_catalog_table);
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Not-null constraints:
+ "replication_metadata_id_not_null" NOT NULL "id"
+ "replication_metadata_relation_not_null" NOT NULL "relation"
INSERT INTO replication_metadata(relation, options)
VALUES ('bar', ARRAY['a', 'b']);
@@ -519,6 +525,9 @@ ALTER TABLE replication_metadata SET (user_catalog_table = true);
options | text[] | | | | extended | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Not-null constraints:
+ "replication_metadata_id_not_null" NOT NULL "id"
+ "replication_metadata_relation_not_null" NOT NULL "relation"
Options: user_catalog_table=true
INSERT INTO replication_metadata(relation, options)
@@ -538,6 +547,9 @@ ALTER TABLE replication_metadata SET (user_catalog_table = false);
rewritemeornot | integer | | | | plain | |
Indexes:
"replication_metadata_pkey" PRIMARY KEY, btree (id)
+Not-null constraints:
+ "replication_metadata_id_not_null" NOT NULL "id"
+ "replication_metadata_relation_not_null" NOT NULL "relation"
Options: user_catalog_table=false
INSERT INTO replication_metadata(relation, options)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 964c819a02..c180ed7abb 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1271,7 +1271,7 @@
attnotnull bool
- This represents a not-null constraint.
+ This column has a not-null constraint.
@@ -2502,14 +2502,10 @@ SCRAM-SHA-256$<iteration count>:&l
- The catalog pg_constraint stores check, primary
- key, unique, foreign key, and exclusion constraints on tables, as well as
- not-null constraints on domains.
+ The catalog pg_constraint stores check, not-null,
+ 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 on relations are represented in the
- pg_attribute
- catalog, not here.
@@ -2571,7 +2567,7 @@ SCRAM-SHA-256$<iteration count>:&l
c = check constraint,
f = foreign key constraint,
- n = not-null constraint (domains only),
+ n = not-null constraint,
p = primary key constraint,
u = unique constraint,
t = constraint trigger,
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 898b6ddc8d..3c56610d2a 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -771,18 +771,39 @@ CREATE TABLE products (
name text NOT NULL,
price numeric
);
+
+ An explicit constraint name can also be specified, for example:
+
+CREATE TABLE products (
+ product_no integer NOT NULL,
+ name text CONSTRAINT products_name_not_null NOT NULL,
+ price numeric
+);
- A not-null constraint is always written as a column constraint. A
- not-null constraint is functionally equivalent to creating a check
+ A not-null constraint is usually written as a column constraint. The
+ syntax for writing it as a table constraint is
+
+CREATE TABLE products (
+ product_no integer,
+ name text,
+ price numeric,
+ NOT NULL product_no,
+ NOT NULL name
+);
+
+ But this syntax is not standard and mainly intended for use by
+ pg_dump.
+
+
+
+ A not-null constraint is functionally equivalent to creating a check
constraint CHECK (column_name
IS NOT NULL), but in
PostgreSQL creating an explicit
- not-null constraint is more efficient. The drawback is that you
- cannot give explicit names to not-null constraints created this
- way.
+ not-null constraint is more efficient.
@@ -799,6 +820,10 @@ CREATE TABLE products (
order the constraints are checked.
+
+ However, a column can have at most one explicit not-null constraint.
+
+
The NOT NULL constraint has an inverse: the
NULL constraint. This does not mean that the
@@ -992,7 +1017,7 @@ CREATE TABLE example (
A table can have at most one primary key. (There can be any number
- of unique and not-null constraints, which are functionally almost the
+ of unique constraints, which combined with not-null constraints are functionally almost the
same thing, but only one can be identified as the primary key.)
Relational database theory
dictates that every table must have a primary key. This rule is
@@ -1652,11 +1677,16 @@ ALTER TABLE products ADD CHECK (name <> '');
ALTER TABLE products ADD CONSTRAINT some_name UNIQUE (product_no);
ALTER TABLE products ADD FOREIGN KEY (product_group_id) REFERENCES product_groups;
- To add a not-null constraint, which cannot be written as a table
- constraint, use this syntax:
+
+
+
+ To add a not-null constraint, which is normally not written as a table
+ constraint, this special syntax is available:
ALTER TABLE products ALTER COLUMN product_no SET NOT NULL;
+ This command silently does nothing if the column already has a
+ not-null constraint.
@@ -1697,12 +1727,15 @@ ALTER TABLE products DROP CONSTRAINT some_name;
- This works the same for all constraint types except not-null
- constraints. To drop a not-null constraint use:
+ Simplified syntax is available to drop a not-null constraint:
ALTER TABLE products ALTER COLUMN product_no DROP NOT NULL;
- (Recall that not-null constraints do not have names.)
+ This mirrors the SET NOT NULL syntax for adding a
+ not-null constraint. This command will silently do nothing if the column
+ does not have a not-null constraint. (Recall that a column can have at
+ most one not-null constraint, so it is never ambiguous which constraint
+ this command acts on.)
@@ -4446,12 +4479,10 @@ ALTER INDEX measurement_city_id_logdate_key
Both CHECK and NOT NULL
constraints of a partitioned table are always inherited by all its
- partitions. CHECK constraints that are marked
- NO INHERIT are not allowed to be created on
- partitioned tables.
- You cannot drop a NOT NULL constraint on a
- partition's column if the same constraint is present in the parent
- table.
+ partitions; it is not allowed to create NO INHERIT
+ constraints of those types.
+ You cannot drop a constraint of those types if the same constraint
+ is present in the parent table.
diff --git a/doc/src/sgml/ref/alter_foreign_table.sgml b/doc/src/sgml/ref/alter_foreign_table.sgml
index 3cb6f08fcf..e2da3cc719 100644
--- a/doc/src/sgml/ref/alter_foreign_table.sgml
+++ b/doc/src/sgml/ref/alter_foreign_table.sgml
@@ -173,7 +173,8 @@ ALTER FOREIGN TABLE [ IF EXISTS ] name
This form adds a new constraint to a foreign table, using the same
syntax as CREATE FOREIGN TABLE.
- Currently only CHECK constraints are supported.
+ Currently only CHECK and NOT NULL
+ constraints are supported.
@@ -182,7 +183,8 @@ ALTER FOREIGN TABLE [ IF EXISTS ] nameCREATE FOREIGN TABLE.)
- If the constraint is marked NOT VALID, then it isn't
+ If the constraint is marked NOT VALID (allowed only for
+ the CHECK case), then it isn't
assumed to hold, but is only recorded for possible future use.
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 61a0fb3dec..6098ebed43 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -98,7 +98,7 @@ WITH ( MODULUS numeric_literal, REM
and column_constraint is:
[ CONSTRAINT constraint_name ]
-{ NOT NULL |
+{ NOT NULL [ NO INHERIT ] |
NULL |
CHECK ( expression ) [ NO INHERIT ] |
DEFAULT default_expr |
@@ -114,6 +114,7 @@ WITH ( MODULUS numeric_literal, REM
[ CONSTRAINT constraint_name ]
{ CHECK ( expression ) [ NO INHERIT ] |
+ NOT NULL column_name [ NO INHERIT ] |
UNIQUE [ NULLS [ NOT ] DISTINCT ] ( column_name [, ... ] ) index_parameters |
PRIMARY KEY ( column_name [, ... ] ) index_parameters |
EXCLUDE [ USING index_method ] ( exclude_element WITH operator [, ... ] ) index_parameters [ WHERE ( predicate ) ] |
@@ -849,19 +850,16 @@ WITH ( MODULUS numeric_literal, REM
table. Subsequently, queries against the parent will include records
of the target table. To be added as a child, the target table must
already contain all the same columns as the parent (it could have
- additional columns, too). The columns must have matching data types,
- and if they have NOT NULL constraints in the parent
- then they must also have NOT NULL constraints in the
- child.
+ additional columns, too). The columns must have matching data types.
- There must also be matching child-table constraints for all
- CHECK constraints of the parent, except those
- marked non-inheritable (that is, created with ALTER TABLE ... ADD CONSTRAINT ... NO INHERIT)
- in the parent, which are ignored; all child-table constraints matched
- must not be marked non-inheritable.
- Currently
+ In addition, all CHECK and NOT NULL
+ constraints on the parent must also exist on the child, except those
+ marked non-inheritable (that is, created with
+ ALTER TABLE ... ADD CONSTRAINT ... NO INHERIT), which
+ are ignored. All child-table constraints matched must not be marked
+ non-inheritable. Currently
UNIQUE, PRIMARY KEY, and
FOREIGN KEY constraints are not considered, but
this might change in the future.
@@ -1793,11 +1791,17 @@ ALTER TABLE measurement
Compatibility
- The forms ADD (without USING INDEX),
+ The forms ADD [COLUMN],
DROP [COLUMN], DROP IDENTITY, RESTART,
SET DEFAULT, SET DATA TYPE (without USING),
SET GENERATED, and SET sequence_option
- conform with the SQL standard. The other forms are
+ conform with the SQL standard.
+ The form ADD table_constraint
+ conforms with the SQL standard when the USING INDEX and
+ NOT VALID clauses are omitted and the constraint type is
+ one of CHECK, UNIQUE, PRIMARY KEY,
+ or REFERENCES.
+ The other forms are
PostgreSQL extensions of the SQL standard.
Also, the ability to specify more than one manipulation in a single
ALTER TABLE command is an extension.
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index dc4b907599..fc81ba3c49 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -43,7 +43,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name
where column_constraint is:
[ CONSTRAINT constraint_name ]
-{ NOT NULL |
+{ NOT NULL [ NO INHERIT ] |
NULL |
CHECK ( expression ) [ NO INHERIT ] |
DEFAULT default_expr |
@@ -52,6 +52,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name
and table_constraint is:
[ CONSTRAINT constraint_name ]
+ NOT NULL column_name [ NO INHERIT ] |
CHECK ( expression ) [ NO INHERIT ]
and partition_bound_spec is:
@@ -203,11 +204,16 @@ WITH ( MODULUS numeric_literal, REM
- NOT NULL
+ NOT NULL [ NO INHERIT ]
The column is not allowed to contain null values.
+
+
+ A constraint marked with NO INHERIT will not propagate to
+ child tables.
+
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 83859bac76..dd83b07d65 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -61,7 +61,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
where column_constraint is:
[ CONSTRAINT constraint_name ]
-{ NOT NULL |
+{ NOT NULL [ NO INHERIT ] |
NULL |
CHECK ( expression ) [ NO INHERIT ] |
DEFAULT default_expr |
@@ -77,6 +77,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ CONSTRAINT constraint_name ]
{ CHECK ( expression ) [ NO INHERIT ] |
+ NOT NULL column_name [ NO INHERIT ] |
UNIQUE [ NULLS [ NOT ] DISTINCT ] ( column_name [, ... ] [, column_name WITHOUT OVERLAPS ] ) index_parameters |
PRIMARY KEY ( column_name [, ... ] [, column_name WITHOUT OVERLAPS ] ) index_parameters |
EXCLUDE [ USING index_method ] ( exclude_element WITH operator [, ... ] ) index_parameters [ WHERE ( predicate ) ] |
@@ -818,11 +819,16 @@ WITH ( MODULUS numeric_literal, REM
- NOT NULL
+ NOT NULL [ NO INHERIT ]
The column is not allowed to contain null values.
+
+
+ A constraint marked with NO INHERIT will not propagate to
+ child tables.
+
@@ -2398,13 +2404,6 @@ CREATE TABLE cities_partdef
constraint, and index names must be unique across all relations within
the same schema.
-
-
- Currently, PostgreSQL does not record names
- for not-null constraints at all, so they are not
- subject to the uniqueness restriction. This might change in a future
- release.
-
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index c54a543c53..003af4bf21 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2173,6 +2173,56 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
return constrOid;
}
+/*
+ * Store a not-null constraint for the given relation
+ *
+ * The OID of the new constraint is returned.
+ */
+static Oid
+StoreRelNotNull(Relation rel, const char *nnname, AttrNumber attnum,
+ bool is_validated, bool is_local, int inhcount,
+ bool is_no_inherit)
+{
+ Oid constrOid;
+
+ Assert(attnum > InvalidAttrNumber);
+
+ constrOid =
+ CreateConstraintEntry(nnname,
+ RelationGetNamespace(rel),
+ CONSTRAINT_NOTNULL,
+ false,
+ false,
+ is_validated,
+ InvalidOid,
+ RelationGetRelid(rel),
+ &attnum,
+ 1,
+ 1,
+ InvalidOid, /* not a domain constraint */
+ InvalidOid, /* no associated index */
+ InvalidOid, /* Foreign key fields */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ ' ',
+ ' ',
+ NULL,
+ 0,
+ ' ',
+ NULL, /* not an exclusion constraint */
+ NULL,
+ NULL,
+ is_local,
+ inhcount,
+ is_no_inherit,
+ false,
+ false);
+ return constrOid;
+}
+
/*
* Store defaults and constraints (passed as a list of CookedConstraint).
*
@@ -2217,6 +2267,14 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal)
is_internal);
numchecks++;
break;
+
+ case CONSTR_NOTNULL:
+ con->conoid =
+ StoreRelNotNull(rel, con->name, con->attnum,
+ !con->skip_validation, con->is_local,
+ con->inhcount, con->is_no_inherit);
+ break;
+
default:
elog(ERROR, "unrecognized constraint type: %d",
(int) con->contype);
@@ -2245,7 +2303,7 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal)
* cooked CHECK constraints
*
* All entries in newColDefaults will be processed. Entries in newConstraints
- * will be processed only if they are CONSTR_CHECK type.
+ * will be processed only if they are CONSTR_CHECK or CONSTR_NOTNULL types.
*
* Returns a list of CookedConstraint nodes that shows the cooked form of
* the default and constraint expressions added to the relation.
@@ -2274,6 +2332,7 @@ AddRelationNewConstraints(Relation rel,
ParseNamespaceItem *nsitem;
int numchecks;
List *checknames;
+ List *nnnames;
Node *expr;
CookedConstraint *cooked;
@@ -2358,6 +2417,7 @@ AddRelationNewConstraints(Relation rel,
*/
numchecks = numoldchecks;
checknames = NIL;
+ nnnames = NIL;
foreach_node(Constraint, cdef, newConstraints)
{
Oid constrOid;
@@ -2412,7 +2472,7 @@ AddRelationNewConstraints(Relation rel,
* Check against pre-existing constraints. If we are allowed
* to merge with an existing constraint, there's no more to do
* here. (We omit the duplicate constraint from the result,
- * which is what ATAddCheckConstraint wants.)
+ * which is what ATAddCheckNNConstraint wants.)
*/
if (MergeWithExistingConstraint(rel, ccname, expr,
allow_merge, is_local,
@@ -2481,6 +2541,77 @@ AddRelationNewConstraints(Relation rel,
cooked->is_no_inherit = cdef->is_no_inherit;
cookedConstraints = lappend(cookedConstraints, cooked);
}
+ else if (cdef->contype == CONSTR_NOTNULL)
+ {
+ CookedConstraint *nncooked;
+ AttrNumber colnum;
+ int16 inhcount = is_local ? 0 : 1;
+ char *nnname;
+
+ /* Determine which column to modify */
+ colnum = get_attnum(RelationGetRelid(rel), strVal(linitial(cdef->keys)));
+ if (colnum == InvalidAttrNumber)
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ strVal(linitial(cdef->keys)), RelationGetRelationName(rel)));
+ if (colnum < InvalidAttrNumber)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot add not-null constraint on system column \"%s\"",
+ strVal(linitial(cdef->keys))));
+
+ /*
+ * If the column already has a not-null constraint, we don't want
+ * to add another one; just adjust inheritance status as needed.
+ */
+ if (AdjustNotNullInheritance(RelationGetRelid(rel), colnum,
+ is_local, cdef->is_no_inherit))
+ continue;
+
+ /*
+ * If a constraint name is specified, check that it isn't already
+ * used. Otherwise, choose a non-conflicting one ourselves.
+ */
+ if (cdef->conname)
+ {
+ if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
+ RelationGetRelid(rel),
+ cdef->conname))
+ ereport(ERROR,
+ errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("constraint \"%s\" for relation \"%s\" already exists",
+ cdef->conname, RelationGetRelationName(rel)));
+ nnname = cdef->conname;
+ }
+ else
+ nnname = ChooseConstraintName(RelationGetRelationName(rel),
+ strVal(linitial(cdef->keys)),
+ "not_null",
+ RelationGetNamespace(rel),
+ nnnames);
+ nnnames = lappend(nnnames, nnname);
+
+ constrOid =
+ StoreRelNotNull(rel, nnname, colnum,
+ cdef->initially_valid,
+ is_local,
+ inhcount,
+ cdef->is_no_inherit);
+
+ nncooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
+ nncooked->contype = CONSTR_NOTNULL;
+ nncooked->conoid = constrOid;
+ nncooked->name = nnname;
+ nncooked->attnum = colnum;
+ nncooked->expr = NULL;
+ nncooked->skip_validation = cdef->skip_validation;
+ nncooked->is_local = is_local;
+ nncooked->inhcount = inhcount;
+ nncooked->is_no_inherit = cdef->is_no_inherit;
+
+ cookedConstraints = lappend(cookedConstraints, nncooked);
+ }
}
/*
@@ -2648,6 +2779,262 @@ MergeWithExistingConstraint(Relation rel, const char *ccname, Node *expr,
return found;
}
+/*
+ * Create the not-null constraints when creating a new relation
+ *
+ * These come from two sources: the 'constraints' list (of Constraint) is
+ * specified directly by the user; the 'old_notnulls' list (of
+ * CookedConstraint) comes from inheritance. We create one constraint
+ * for each column, giving priority to user-specified ones, and setting
+ * inhcount according to how many parents cause each column to get a
+ * not-null constraint. If a user-specified name clashes with another
+ * user-specified name, an error is raised.
+ *
+ * Returns a list of AttrNumber for columns that need to have the attnotnull
+ * flag set.
+ */
+List *
+AddRelationNotNullConstraints(Relation rel, List *constraints,
+ List *old_notnulls)
+{
+ List *givennames;
+ List *nnnames;
+ List *nncols = NIL;
+
+ /*
+ * We track two lists of names: nnnames keeps all the constraint names,
+ * givennames tracks user-generated names. The distinction is important,
+ * because we must raise error for user-generated name conflicts, but for
+ * system-generated name conflicts we just generate another.
+ */
+ nnnames = NIL;
+ givennames = NIL;
+
+ /*
+ * First, create all not-null constraints that are directly specified by
+ * the user. Note that inheritance might have given us another source for
+ * each, so we must scan the old_notnulls list and increment inhcount for
+ * each element with identical attnum. We delete from there any element
+ * that we process.
+ *
+ * We don't use foreach() here because we have two nested loops over the
+ * constraint list, with possible element deletions in the inner one. If
+ * we used foreach_delete_current() it could only fix up the state of one
+ * of the loops, so it seems cleaner to use looping over list indexes for
+ * both loops. Note that any deletion will happen beyond where the outer
+ * loop is, so its index never needs adjustment.
+ */
+ for (int outerpos = 0; outerpos < list_length(constraints); outerpos++)
+ {
+ Constraint *constr;
+ AttrNumber attnum;
+ char *conname;
+ int inhcount = 0;
+
+ constr = list_nth_node(Constraint, constraints, outerpos);
+
+ Assert(constr->contype == CONSTR_NOTNULL);
+
+ attnum = get_attnum(RelationGetRelid(rel),
+ strVal(linitial(constr->keys)));
+ if (attnum == InvalidAttrNumber)
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ strVal(linitial(constr->keys)),
+ RelationGetRelationName(rel)));
+ if (attnum < InvalidAttrNumber)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot add not-null constraint on system column \"%s\"",
+ strVal(linitial(constr->keys))));
+
+ /*
+ * A column can only have one not-null constraint, so discard any
+ * additional ones that appear for columns we already saw; but check
+ * that the NO INHERIT flags match.
+ */
+ for (int restpos = outerpos + 1; restpos < list_length(constraints);)
+ {
+ Constraint *other;
+
+ other = list_nth_node(Constraint, constraints, restpos);
+ if (strcmp(strVal(linitial(constr->keys)),
+ strVal(linitial(other->keys))) == 0)
+ {
+ if (other->is_no_inherit != constr->is_no_inherit)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting NO INHERIT declaration for not-null constraint on column \"%s\"",
+ strVal(linitial(constr->keys))));
+
+ /*
+ * Preserve constraint name if one is specified, but raise an
+ * error if conflicting ones are specified.
+ */
+ if (other->conname)
+ {
+ if (!constr->conname)
+ constr->conname = pstrdup(other->conname);
+ else if (strcmp(constr->conname, other->conname) != 0)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting not-null constraint names \"%s\" and \"%s\"",
+ constr->conname, other->conname));
+ }
+
+ /* XXX do we need to verify any other fields? */
+ constraints = list_delete_nth_cell(constraints, restpos);
+ }
+ else
+ restpos++;
+ }
+
+ /*
+ * Search in the list of inherited constraints for any entries on the
+ * same column; determine an inheritance count from that. Also, if at
+ * least one parent has a constraint for this column, then we must not
+ * accept a user specification for a NO INHERIT one. Any constraint
+ * from parents that we process here is deleted from the list: we no
+ * longer need to process it in the loop below.
+ */
+ foreach_ptr(CookedConstraint, old, old_notnulls)
+ {
+ if (old->attnum == attnum)
+ {
+ /*
+ * If we get a constraint from the parent, having a local NO
+ * INHERIT one doesn't work.
+ */
+ if (constr->is_no_inherit)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot define not-null constraint on column \"%s\" with NO INHERIT",
+ strVal(linitial(constr->keys))),
+ errdetail("The column has an inherited not-null constraint.")));
+
+ inhcount++;
+ old_notnulls = foreach_delete_current(old_notnulls, old);
+ }
+ }
+
+ /*
+ * Determine a constraint name, which may have been specified by the
+ * user, or raise an error if a conflict exists with another
+ * user-specified name.
+ */
+ if (constr->conname)
+ {
+ foreach_ptr(char, thisname, givennames)
+ {
+ if (strcmp(thisname, constr->conname) == 0)
+ ereport(ERROR,
+ errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("constraint \"%s\" for relation \"%s\" already exists",
+ constr->conname,
+ RelationGetRelationName(rel)));
+ }
+
+ conname = constr->conname;
+ givennames = lappend(givennames, conname);
+ }
+ else
+ conname = ChooseConstraintName(RelationGetRelationName(rel),
+ get_attname(RelationGetRelid(rel),
+ attnum, false),
+ "not_null",
+ RelationGetNamespace(rel),
+ nnnames);
+ nnnames = lappend(nnnames, conname);
+
+ StoreRelNotNull(rel, conname,
+ attnum, true, true,
+ inhcount, constr->is_no_inherit);
+
+ nncols = lappend_int(nncols, attnum);
+ }
+
+ /*
+ * If any column remains in the old_notnulls list, we must create a not-
+ * null constraint marked not-local for that column. Because multiple
+ * parents could specify a not-null constraint for the same column, we
+ * must count how many there are and set an appropriate inhcount
+ * accordingly, deleting elements we've already processed.
+ *
+ * We don't use foreach() here because we have two nested loops over the
+ * constraint list, with possible element deletions in the inner one. If
+ * we used foreach_delete_current() it could only fix up the state of one
+ * of the loops, so it seems cleaner to use looping over list indexes for
+ * both loops. Note that any deletion will happen beyond where the outer
+ * loop is, so its index never needs adjustment.
+ */
+ for (int outerpos = 0; outerpos < list_length(old_notnulls); outerpos++)
+ {
+ CookedConstraint *cooked;
+ char *conname = NULL;
+ int inhcount = 1;
+
+ cooked = (CookedConstraint *) list_nth(old_notnulls, outerpos);
+ Assert(cooked->contype == CONSTR_NOTNULL);
+ Assert(cooked->name);
+
+ /*
+ * Preserve the first non-conflicting constraint name we come across.
+ */
+ if (conname == NULL)
+ conname = cooked->name;
+
+ for (int restpos = outerpos + 1; restpos < list_length(old_notnulls);)
+ {
+ CookedConstraint *other;
+
+ other = (CookedConstraint *) list_nth(old_notnulls, restpos);
+ Assert(other->name);
+ if (other->attnum == cooked->attnum)
+ {
+ if (conname == NULL)
+ conname = other->name;
+
+ inhcount++;
+ old_notnulls = list_delete_nth_cell(old_notnulls, restpos);
+ }
+ else
+ restpos++;
+ }
+
+ /* If we got a name, make sure it isn't one we've already used */
+ if (conname != NULL)
+ {
+ foreach_ptr(char, thisname, nnnames)
+ {
+ if (strcmp(thisname, conname) == 0)
+ {
+ conname = NULL;
+ break;
+ }
+ }
+ }
+
+ /* and choose a name, if needed */
+ if (conname == NULL)
+ conname = ChooseConstraintName(RelationGetRelationName(rel),
+ get_attname(RelationGetRelid(rel),
+ cooked->attnum, false),
+ "not_null",
+ RelationGetNamespace(rel),
+ nnnames);
+ nnnames = lappend(nnnames, conname);
+
+ /* ignore the origin constraint's is_local and inhcount */
+ StoreRelNotNull(rel, conname, cooked->attnum, true,
+ false, inhcount, false);
+
+ nncols = lappend_int(nncols, cooked->attnum);
+ }
+
+ return nncols;
+}
+
/*
* Update the count of constraints in the relation's pg_class tuple.
*
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index c4145131ce..76c78c0d18 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -440,9 +440,8 @@ CREATE VIEW check_constraints AS
WHERE pg_has_role(coalesce(c.relowner, t.typowner), 'USAGE')
AND con.contype = 'c'
- UNION
- -- not-null constraints on domains
-
+ UNION ALL
+ -- not-null constraints
SELECT current_database()::information_schema.sql_identifier AS constraint_catalog,
rs.nspname::information_schema.sql_identifier AS constraint_schema,
con.conname::information_schema.sql_identifier AS constraint_name,
@@ -453,24 +452,7 @@ CREATE VIEW check_constraints AS
LEFT JOIN pg_type t ON t.oid = con.contypid
LEFT JOIN pg_attribute at ON (con.conrelid = at.attrelid AND con.conkey[1] = at.attnum)
WHERE pg_has_role(coalesce(c.relowner, t.typowner), 'USAGE'::text)
- AND con.contype = 'n'
-
- UNION
- -- not-null constraints on relations
-
- 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 IN ('r', 'p')
- AND pg_has_role(r.relowner, 'USAGE');
+ AND con.contype = 'n';
GRANT SELECT ON check_constraints TO PUBLIC;
@@ -839,6 +821,20 @@ CREATE VIEW constraint_column_usage AS
UNION ALL
+ /* not-null constraints */
+ SELECT DISTINCT nr.nspname, r.relname, r.relowner, a.attname, nc.nspname, c.conname
+ FROM pg_namespace nr, pg_class r, pg_attribute a, pg_namespace nc, pg_constraint c
+ WHERE nr.oid = r.relnamespace
+ AND r.oid = a.attrelid
+ AND r.oid = c.conrelid
+ AND a.attnum = c.conkey[1]
+ AND c.connamespace = nc.oid
+ AND c.contype = 'n'
+ AND r.relkind in ('r', 'p')
+ AND not a.attisdropped
+
+ UNION ALL
+
/* unique/primary key/foreign key constraints */
SELECT nr.nspname, r.relname, r.relowner, a.attname, nc.nspname, c.conname
FROM pg_namespace nr, pg_class r, pg_attribute a, pg_namespace nc,
@@ -1839,6 +1835,7 @@ CREATE VIEW table_constraints AS
CAST(r.relname AS sql_identifier) AS table_name,
CAST(
CASE c.contype WHEN 'c' THEN 'CHECK'
+ WHEN 'n' THEN 'CHECK'
WHEN 'f' THEN 'FOREIGN KEY'
WHEN 'p' THEN 'PRIMARY KEY'
WHEN 'u' THEN 'UNIQUE' END
@@ -1863,38 +1860,6 @@ CREATE VIEW table_constraints AS
AND c.contype NOT IN ('t', 'x') -- ignore nonstandard constraints
AND r.relkind IN ('r', 'p')
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') )
-
- 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,
- CAST('YES' AS yes_or_no) AS enforced,
- CAST(NULL AS yes_or_no) AS nulls_distinct
-
- 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 IN ('r', 'p')
- 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')
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 54f3fb50a5..e953000c01 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -21,6 +21,7 @@
#include "access/table.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
+#include "catalog/heap.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_constraint.h"
@@ -564,6 +565,78 @@ ChooseConstraintName(const char *name1, const char *name2,
return conname;
}
+/*
+ * Find and return a copy of the pg_constraint tuple that implements a
+ * validated not-null constraint for the given column of the given relation.
+ * If no such constraint exists, return NULL.
+ *
+ * XXX This would be easier if we had pg_attribute.notnullconstr with the OID
+ * of the constraint that implements the not-null constraint for that column.
+ * I'm not sure it's worth the catalog bloat and de-normalization, however.
+ */
+HeapTuple
+findNotNullConstraintAttnum(Oid relid, AttrNumber attnum)
+{
+ Relation pg_constraint;
+ HeapTuple conTup,
+ retval = NULL;
+ SysScanDesc scan;
+ ScanKeyData key;
+
+ pg_constraint = table_open(ConstraintRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ scan = systable_beginscan(pg_constraint, ConstraintRelidTypidNameIndexId,
+ true, NULL, 1, &key);
+
+ while (HeapTupleIsValid(conTup = systable_getnext(scan)))
+ {
+ Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(conTup);
+ AttrNumber conkey;
+
+ /*
+ * We're looking for a NOTNULL constraint that's marked validated,
+ * with the column we're looking for as the sole element in conkey.
+ */
+ if (con->contype != CONSTRAINT_NOTNULL)
+ continue;
+ if (!con->convalidated)
+ continue;
+
+ conkey = extractNotNullColumn(conTup);
+ if (conkey != attnum)
+ continue;
+
+ /* Found it */
+ retval = heap_copytuple(conTup);
+ break;
+ }
+
+ systable_endscan(scan);
+ table_close(pg_constraint, AccessShareLock);
+
+ return retval;
+}
+
+/*
+ * Find and return the pg_constraint tuple that implements a validated
+ * not-null constraint for the given column of the given relation. If
+ * no such column or no such constraint exists, return NULL.
+ */
+HeapTuple
+findNotNullConstraint(Oid relid, const char *colname)
+{
+ AttrNumber attnum;
+
+ attnum = get_attnum(relid, colname);
+ if (attnum <= InvalidAttrNumber)
+ return NULL;
+
+ return findNotNullConstraintAttnum(relid, attnum);
+}
+
/*
* Find and return the pg_constraint tuple that implements a validated
* not-null constraint for the given domain.
@@ -608,6 +681,185 @@ findDomainNotNullConstraint(Oid typid)
return retval;
}
+/*
+ * Given a pg_constraint tuple for a not-null constraint, return the column
+ * number it is for.
+ */
+AttrNumber
+extractNotNullColumn(HeapTuple constrTup)
+{
+ AttrNumber colnum;
+ Datum adatum;
+ ArrayType *arr;
+
+ /* only tuples for not-null constraints should be given */
+ Assert(((Form_pg_constraint) GETSTRUCT(constrTup))->contype == CONSTRAINT_NOTNULL);
+
+ adatum = SysCacheGetAttrNotNull(CONSTROID, constrTup,
+ Anum_pg_constraint_conkey);
+ arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
+ if (ARR_NDIM(arr) != 1 ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != INT2OID ||
+ ARR_DIMS(arr)[0] != 1)
+ elog(ERROR, "conkey is not a 1-D smallint array");
+
+ memcpy(&colnum, ARR_DATA_PTR(arr), sizeof(AttrNumber));
+ Assert(colnum > 0 && colnum <= MaxAttrNumber);
+
+ if ((Pointer) arr != DatumGetPointer(adatum))
+ pfree(arr); /* free de-toasted copy, if any */
+
+ return colnum;
+}
+
+/*
+ * AdjustNotNullInheritance
+ * Adjust inheritance status for a single not-null constraint
+ *
+ * If no not-null constraint is found for the column, return false.
+ * Caller can create one.
+ * If a constraint exists but the connoinherit flag is not what the caller
+ * wants, throw an error about the incompatibility. Otherwise, we adjust
+ * conislocal/coninhcount and return true.
+ * In the latter case, if is_local is true we flip conislocal true, or do
+ * nothing if it's already true; otherwise we increment coninhcount by 1.
+ */
+bool
+AdjustNotNullInheritance(Oid relid, AttrNumber attnum,
+ bool is_local, bool is_no_inherit)
+{
+ HeapTuple tup;
+
+ tup = findNotNullConstraintAttnum(relid, attnum);
+ if (HeapTupleIsValid(tup))
+ {
+ Relation pg_constraint;
+ Form_pg_constraint conform;
+ bool changed = false;
+
+ pg_constraint = table_open(ConstraintRelationId, RowExclusiveLock);
+ conform = (Form_pg_constraint) GETSTRUCT(tup);
+
+ /*
+ * If the NO INHERIT flag we're asked for doesn't match what the
+ * existing constraint has, throw an error.
+ */
+ if (is_no_inherit != conform->connoinherit)
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot change NO INHERIT status of NOT NULL constraint \"%s\" on relation \"%s\"",
+ NameStr(conform->conname), get_rel_name(relid)));
+
+ if (!is_local)
+ {
+ if (pg_add_s16_overflow(conform->coninhcount, 1,
+ &conform->coninhcount))
+ ereport(ERROR,
+ errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("too many inheritance parents"));
+ changed = true;
+ }
+ else if (!conform->conislocal)
+ {
+ conform->conislocal = true;
+ changed = true;
+ }
+
+ if (changed)
+ CatalogTupleUpdate(pg_constraint, &tup->t_self, tup);
+
+ table_close(pg_constraint, RowExclusiveLock);
+
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * RelationGetNotNullConstraints
+ * Return the list of not-null constraints for the given rel
+ *
+ * Caller can request cooked constraints, or raw.
+ *
+ * This is seldom needed, so we just scan pg_constraint each time.
+ *
+ * 'include_noinh' determines whether to include NO INHERIT constraints or not.
+ */
+List *
+RelationGetNotNullConstraints(Oid relid, bool cooked, bool include_noinh)
+{
+ List *notnulls = NIL;
+ Relation constrRel;
+ HeapTuple htup;
+ SysScanDesc conscan;
+ ScanKeyData skey;
+
+ constrRel = table_open(ConstraintRelationId, AccessShareLock);
+ ScanKeyInit(&skey,
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ conscan = systable_beginscan(constrRel, ConstraintRelidTypidNameIndexId, true,
+ NULL, 1, &skey);
+
+ while (HeapTupleIsValid(htup = systable_getnext(conscan)))
+ {
+ Form_pg_constraint conForm = (Form_pg_constraint) GETSTRUCT(htup);
+ AttrNumber colnum;
+
+ if (conForm->contype != CONSTRAINT_NOTNULL)
+ continue;
+ if (conForm->connoinherit && !include_noinh)
+ continue;
+
+ colnum = extractNotNullColumn(htup);
+
+ if (cooked)
+ {
+ CookedConstraint *cooked;
+
+ cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
+
+ cooked->contype = CONSTR_NOTNULL;
+ cooked->conoid = conForm->oid;
+ cooked->name = pstrdup(NameStr(conForm->conname));
+ cooked->attnum = colnum;
+ cooked->expr = NULL;
+ cooked->skip_validation = false;
+ cooked->is_local = true;
+ cooked->inhcount = 0;
+ cooked->is_no_inherit = conForm->connoinherit;
+
+ notnulls = lappend(notnulls, cooked);
+ }
+ else
+ {
+ Constraint *constr;
+
+ constr = makeNode(Constraint);
+ constr->contype = CONSTR_NOTNULL;
+ constr->conname = pstrdup(NameStr(conForm->conname));
+ constr->deferrable = false;
+ constr->initdeferred = false;
+ constr->location = -1;
+ constr->keys = list_make1(makeString(get_attname(relid, colnum,
+ false)));
+ constr->skip_validation = false;
+ constr->initially_valid = true;
+ constr->is_no_inherit = conForm->connoinherit;
+ notnulls = lappend(notnulls, constr);
+ }
+ }
+
+ systable_endscan(conscan);
+ table_close(constrRel, AccessShareLock);
+
+ return notnulls;
+}
+
+
/*
* Delete a single constraint record.
*/
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index eaa8142427..ccd9645e7d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -371,7 +371,8 @@ static void truncate_check_activity(Relation rel);
static void RangeVarCallbackForTruncate(const RangeVar *relation,
Oid relId, Oid oldRelId, void *arg);
static List *MergeAttributes(List *columns, const List *supers, char relpersistence,
- bool is_partition, List **supconstr);
+ bool is_partition, List **supconstr,
+ List **supnotnulls);
static List *MergeCheckConstraint(List *constraints, const char *name, Node *expr);
static void MergeChildAttribute(List *inh_columns, int exist_attno, int newcol_attno, const ColumnDef *newdef);
static ColumnDef *MergeInheritedAttribute(List *inh_columns, int exist_attno, const ColumnDef *newdef);
@@ -456,15 +457,14 @@ static bool check_for_column_name_collision(Relation rel, const char *colname,
bool if_not_exists);
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 ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
-static void ATPrepSetNotNull(List **wqueue, Relation rel,
- AlterTableCmd *cmd, bool recurse, bool recursing,
- LOCKMODE lockmode,
- AlterTableUtilityContext *context);
-static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
- const char *colName, LOCKMODE lockmode);
-static void ATExecCheckNotNull(AlteredTableInfo *tab, Relation rel,
- const char *colName, LOCKMODE lockmode);
+static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, bool recurse,
+ LOCKMODE lockmode);
+static void set_attnotnull(List **wqueue, Relation rel, AttrNumber attnum,
+ LOCKMODE lockmode);
+static ObjectAddress ATExecSetNotNull(List **wqueue, Relation rel,
+ char *constrname, char *colName,
+ bool recurse, bool recursing,
+ LOCKMODE lockmode);
static bool NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr);
static bool ConstraintImpliedByRelConstraint(Relation scanrel,
List *testConstraint, List *provenConstraint);
@@ -496,6 +496,9 @@ static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *c
bool recurse, bool recursing,
bool missing_ok, LOCKMODE lockmode,
ObjectAddresses *addrs);
+static void ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
+ bool recurse, LOCKMODE lockmode,
+ AlterTableUtilityContext *context);
static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
static ObjectAddress ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
@@ -507,11 +510,11 @@ static ObjectAddress ATExecAddConstraint(List **wqueue,
static char *ChooseForeignKeyConstraintNameAddition(List *colnames);
static ObjectAddress ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
IndexStmt *stmt, LOCKMODE lockmode);
-static ObjectAddress ATAddCheckConstraint(List **wqueue,
- AlteredTableInfo *tab, Relation rel,
- Constraint *constr,
- bool recurse, bool recursing, bool is_readd,
- LOCKMODE lockmode);
+static ObjectAddress ATAddCheckNNConstraint(List **wqueue,
+ AlteredTableInfo *tab, Relation rel,
+ Constraint *constr,
+ bool recurse, bool recursing, bool is_readd,
+ LOCKMODE lockmode);
static ObjectAddress ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab,
Relation rel, Constraint *fkconstraint,
bool recurse, bool recursing,
@@ -577,9 +580,12 @@ static void GetForeignKeyCheckTriggers(Relation trigrel,
Oid *insertTriggerOid,
Oid *updateTriggerOid);
static void ATExecDropConstraint(Relation rel, const char *constrName,
- DropBehavior behavior,
- bool recurse, bool recursing,
+ DropBehavior behavior, bool recurse,
bool missing_ok, LOCKMODE lockmode);
+static ObjectAddress dropconstraint_internal(Relation rel,
+ HeapTuple constraintTup, DropBehavior behavior,
+ bool recurse, bool recursing,
+ bool missing_ok, LOCKMODE lockmode);
static void ATPrepAlterColumnType(List **wqueue,
AlteredTableInfo *tab, Relation rel,
bool recurse, bool recursing,
@@ -677,6 +683,7 @@ static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx,
static void validatePartitionedIndex(Relation partedIdx, Relation partedTbl);
static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
Relation partitionTbl);
+static void verifyPartitionIndexNotNull(IndexInfo *iinfo, Relation partIdx);
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static char GetAttributeCompression(Oid atttypid, const char *compression);
@@ -714,8 +721,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
TupleDesc descriptor;
List *inheritOids;
List *old_constraints;
+ List *old_notnulls;
List *rawDefaults;
List *cookedDefaults;
+ List *nncols;
Datum reloptions;
ListCell *listptr;
AttrNumber attnum;
@@ -906,12 +915,13 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
MergeAttributes(stmt->tableElts, inheritOids,
stmt->relation->relpersistence,
stmt->partbound != NULL,
- &old_constraints);
+ &old_constraints, &old_notnulls);
/*
* Create a tuple descriptor from the relation schema. Note that this
- * deals with column names, types, and not-null constraints, but not
- * default values or CHECK constraints; we handle those below.
+ * deals with column names, types, and in-descriptor NOT NULL flags, but
+ * not default values, NOT NULL or CHECK constraints; we handle those
+ * below.
*/
descriptor = BuildDescForRelation(stmt->tableElts);
@@ -1283,6 +1293,17 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, NIL, stmt->constraints,
true, true, false, queryString);
+ /*
+ * Finally, merge the not-null constraints that are declared directly with
+ * those that come from parent relations (making sure to count inheritance
+ * appropriately for each), create them, and set the attnotnull flag on
+ * columns that don't yet have it.
+ */
+ nncols = AddRelationNotNullConstraints(rel, stmt->nnconstraints,
+ old_notnulls);
+ foreach_int(attrnum, nncols)
+ set_attnotnull(NULL, rel, attrnum, NoLock);
+
ObjectAddressSet(address, RelationRelationId, relationId);
/*
@@ -2414,6 +2435,8 @@ storage_name(char c)
* Output arguments:
* 'supconstr' receives a list of constraints belonging to the parents,
* updated as necessary to be valid for the child.
+ * 'supnotnulls' receives a list of CookedConstraints that corresponds to
+ * constraints coming from inheritance parents.
*
* Return value:
* Completed schema list.
@@ -2444,7 +2467,10 @@ storage_name(char c)
*
* Constraints (including not-null constraints) for the child table
* are the union of all relevant constraints, from both the child schema
- * and parent tables.
+ * and parent tables. In addition, in legacy inheritance, each column that
+ * appears in a primary key in any of the parents also gets a NOT NULL
+ * constraint (partitioning doesn't need this, because the PK itself gets
+ * inherited.)
*
* The default value for a child column is defined as:
* (1) If the child schema specifies a default, that value is used.
@@ -2463,10 +2489,11 @@ storage_name(char c)
*/
static List *
MergeAttributes(List *columns, const List *supers, char relpersistence,
- bool is_partition, List **supconstr)
+ bool is_partition, List **supconstr, List **supnotnulls)
{
List *inh_columns = NIL;
List *constraints = NIL;
+ List *nnconstraints = NIL;
bool have_bogus_defaults = false;
int child_attno;
static Node bogus_marker = {0}; /* marks conflicting defaults */
@@ -2577,8 +2604,10 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
AttrMap *newattmap;
List *inherited_defaults;
List *cols_with_defaults;
+ List *nnconstrs;
ListCell *lc1;
ListCell *lc2;
+ Bitmapset *nncols = NULL;
/* caller already got lock */
relation = table_open(parent, NoLock);
@@ -2666,6 +2695,15 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
/* We can't process inherited defaults until newattmap is complete. */
inherited_defaults = cols_with_defaults = NIL;
+ /*
+ * Request attnotnull on columns that have a not-null constraint
+ * that's not marked NO INHERIT.
+ */
+ nnconstrs = RelationGetNotNullConstraints(RelationGetRelid(relation),
+ true, false);
+ foreach_ptr(CookedConstraint, cc, nnconstrs)
+ nncols = bms_add_member(nncols, cc->attnum);
+
for (AttrNumber parent_attno = 1; parent_attno <= tupleDesc->natts;
parent_attno++)
{
@@ -2687,7 +2725,6 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
*/
newdef = makeColumnDef(attributeName, attribute->atttypid,
attribute->atttypmod, attribute->attcollation);
- newdef->is_not_null = attribute->attnotnull;
newdef->storage = attribute->attstorage;
newdef->generated = attribute->attgenerated;
if (CompressionMethodIsValid(attribute->attcompression))
@@ -2735,6 +2772,12 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
mergeddef = newdef;
}
+ /*
+ * mark attnotnull if parent has it
+ */
+ if (bms_is_member(parent_attno, nncols))
+ mergeddef->is_not_null = true;
+
/*
* Locate default/generation expression if any
*/
@@ -2846,6 +2889,19 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
}
}
+ /*
+ * Also copy the not-null constraints from this parent. The
+ * attnotnull markings were already installed above.
+ */
+ foreach_ptr(CookedConstraint, nn, nnconstrs)
+ {
+ Assert(nn->contype == CONSTR_NOTNULL);
+
+ nn->attnum = newattmap->attnums[nn->attnum - 1];
+
+ nnconstraints = lappend(nnconstraints, nn);
+ }
+
free_attrmap(newattmap);
/*
@@ -2916,8 +2972,7 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
/*
* Now that we have the column definition list for a partition, we can
* check whether the columns referenced in the column constraint specs
- * actually exist. Also, we merge parent's not-null constraints and
- * defaults into each corresponding column definition.
+ * actually exist. Also, merge column defaults.
*/
if (is_partition)
{
@@ -2934,7 +2989,6 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
if (strcmp(coldef->colname, restdef->colname) == 0)
{
found = true;
- coldef->is_not_null |= restdef->is_not_null;
/*
* Check for conflicts related to generated columns.
@@ -3023,6 +3077,7 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
}
*supconstr = constraints;
+ *supnotnulls = nnconstraints;
return columns;
}
@@ -3303,11 +3358,6 @@ MergeInheritedAttribute(List *inh_columns,
format_type_with_typemod(prevtypeid, prevtypmod),
format_type_with_typemod(newtypeid, newtypmod))));
- /*
- * Merge of not-null constraints = OR 'em together
- */
- prevdef->is_not_null |= newdef->is_not_null;
-
/*
* Must have the same collation
*/
@@ -3946,7 +3996,10 @@ rename_constraint_internal(Oid myrelid,
constraintOid);
con = (Form_pg_constraint) GETSTRUCT(tuple);
- if (myrelid && con->contype == CONSTRAINT_CHECK && !con->connoinherit)
+ if (myrelid &&
+ (con->contype == CONSTRAINT_CHECK ||
+ con->contype == CONSTRAINT_NOTNULL) &&
+ !con->connoinherit)
{
if (recurse)
{
@@ -4704,15 +4757,6 @@ AlterTableGetLockLevel(List *cmds)
cmd_lockmode = ShareUpdateExclusiveLock;
break;
- case AT_CheckNotNull:
-
- /*
- * This only examines the table's schema; but lock must be
- * strong enough to prevent concurrent DROP NOT NULL.
- */
- cmd_lockmode = AccessShareLock;
- break;
-
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -4881,22 +4925,17 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
- ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
+ /* Set up recursion for phase 2; no other prep needed */
+ if (recurse)
+ cmd->recurse = true;
pass = AT_PASS_DROP;
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
- /* Need command-specific recursion decision */
- ATPrepSetNotNull(wqueue, rel, cmd, recurse, recursing,
- lockmode, context);
- pass = AT_PASS_COL_ATTRS;
- break;
- case AT_CheckNotNull: /* check column is already marked NOT NULL */
- ATSimplePermissions(cmd->subtype, rel,
- ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
- ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
- /* No command-specific prep needed */
+ /* Set up recursion for phase 2; no other prep needed */
+ if (recurse)
+ cmd->recurse = true;
pass = AT_PASS_COL_ATTRS;
break;
case AT_SetExpression: /* ALTER COLUMN SET EXPRESSION */
@@ -4961,10 +5000,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
case AT_AddConstraint: /* ADD CONSTRAINT */
ATSimplePermissions(cmd->subtype, rel,
ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
- /* Recursion occurs during execution phase */
- /* No command-specific prep needed except saving recurse flag */
+ ATPrepAddPrimaryKey(wqueue, rel, cmd, recurse, lockmode, context);
if (recurse)
+ {
+ /* recurses at exec time; lock descendants and set flag */
+ (void) find_all_inheritors(RelationGetRelid(rel), lockmode, NULL);
cmd->recurse = true;
+ }
pass = AT_PASS_ADD_CONSTR;
break;
case AT_AddIndexConstraint: /* ADD CONSTRAINT USING INDEX */
@@ -5279,13 +5321,11 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
address = ATExecDropIdentity(rel, cmd->name, cmd->missing_ok, lockmode, cmd->recurse, false);
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
- address = ATExecDropNotNull(rel, cmd->name, lockmode);
+ address = ATExecDropNotNull(rel, cmd->name, cmd->recurse, lockmode);
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
- address = ATExecSetNotNull(tab, rel, cmd->name, lockmode);
- break;
- case AT_CheckNotNull: /* check column is already marked NOT NULL */
- ATExecCheckNotNull(tab, rel, cmd->name, lockmode);
+ address = ATExecSetNotNull(wqueue, rel, NULL, cmd->name,
+ cmd->recurse, false, lockmode);
break;
case AT_SetExpression:
address = ATExecSetExpression(tab, rel, cmd->name, cmd->def, lockmode);
@@ -5368,7 +5408,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
break;
case AT_DropConstraint: /* DROP CONSTRAINT */
ATExecDropConstraint(rel, cmd->name, cmd->behavior,
- cmd->recurse, false,
+ cmd->recurse,
cmd->missing_ok, lockmode);
break;
case AT_AlterColumnType: /* ALTER COLUMN TYPE */
@@ -5631,21 +5671,10 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
*/
switch (cmd2->subtype)
{
- case AT_SetNotNull:
- /* Need command-specific recursion decision */
- ATPrepSetNotNull(wqueue, rel, cmd2,
- recurse, false,
- lockmode, context);
- pass = AT_PASS_COL_ATTRS;
- break;
case AT_AddIndex:
- /* This command never recurses */
- /* No command-specific prep needed */
pass = AT_PASS_ADD_INDEX;
break;
case AT_AddIndexConstraint:
- /* This command never recurses */
- /* No command-specific prep needed */
pass = AT_PASS_ADD_INDEXCONSTR;
break;
case AT_AddConstraint:
@@ -5654,6 +5683,9 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
cmd2->recurse = true;
switch (castNode(Constraint, cmd2->def)->contype)
{
+ case CONSTR_NOTNULL:
+ pass = AT_PASS_COL_ATTRS;
+ break;
case CONSTR_PRIMARY:
case CONSTR_UNIQUE:
case CONSTR_EXCLUSION:
@@ -6093,8 +6125,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
/*
* If we are rebuilding the tuples OR if we added any new but not
* verified 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.
+ * is a bit of overkill but it minimizes risk of bugs.
*/
for (i = 0; i < newTupDesc->natts; i++)
{
@@ -6314,6 +6345,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
RelationGetRelationName(oldrel)),
errtableconstraint(oldrel, con->name)));
break;
+ case CONSTR_NOTNULL:
case CONSTR_FOREIGN:
/* Nothing to do here */
break;
@@ -6427,8 +6459,6 @@ alter_table_type_to_string(AlterTableType cmdtype)
return "ALTER COLUMN ... SET EXPRESSION";
case AT_DropExpression:
return "ALTER COLUMN ... DROP EXPRESSION";
- case AT_CheckNotNull:
- return NULL; /* not real grammar */
case AT_SetStatistics:
return "ALTER COLUMN ... SET STATISTICS";
case AT_SetOptions:
@@ -7524,13 +7554,14 @@ add_column_collation_dependency(Oid relid, int32 attnum, Oid collid)
* nullable, InvalidObjectAddress is returned.
*/
static ObjectAddress
-ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
+ATExecDropNotNull(Relation rel, const char *colName, bool recurse,
+ LOCKMODE lockmode)
{
HeapTuple tuple;
+ HeapTuple conTup;
Form_pg_attribute attTup;
AttrNumber attnum;
Relation attr_rel;
- List *indexoidlist;
ObjectAddress address;
/*
@@ -7546,6 +7577,15 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
colName, RelationGetRelationName(rel))));
attTup = (Form_pg_attribute) GETSTRUCT(tuple);
attnum = attTup->attnum;
+ ObjectAddressSubSet(address, RelationRelationId,
+ RelationGetRelid(rel), attnum);
+
+ /* If the column is already nullable there's nothing to do. */
+ if (!attTup->attnotnull)
+ {
+ table_close(attr_rel, RowExclusiveLock);
+ return InvalidObjectAddress;
+ }
/* Prevent them from altering a system attribute */
if (attnum <= 0)
@@ -7561,60 +7601,8 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
colName, RelationGetRelationName(rel))));
/*
- * Check that the attribute is not in a primary key or in an index used as
- * a replica identity.
- *
- * Note: we'll throw error even if the pkey index is not valid.
+ * If rel is partition, shouldn't drop NOT NULL if parent has the same.
*/
-
- /* Loop over all indexes on the relation */
- indexoidlist = RelationGetIndexList(rel);
-
- foreach_oid(indexoid, indexoidlist)
- {
- HeapTuple indexTuple;
- Form_pg_index indexStruct;
-
- 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 or an index used as replica
- * identity, skip the check.
- */
- if (indexStruct->indisprimary || indexStruct->indisreplident)
- {
- /*
- * Loop over each attribute in the primary key or the index used
- * as replica identity and see if it matches the to-be-altered
- * attribute.
- */
- for (int i = 0; i < indexStruct->indnkeyatts; i++)
- {
- if (indexStruct->indkey.values[i] == attnum)
- {
- if (indexStruct->indisprimary)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("column \"%s\" is in a primary key",
- colName)));
- else
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("column \"%s\" is in index used as replica identity",
- colName)));
- }
- }
- }
-
- ReleaseSysCache(indexTuple);
- }
-
- list_free(indexoidlist);
-
- /* If rel is partition, shouldn't drop NOT NULL if parent has the same */
if (rel->rd_rel->relispartition)
{
Oid parentId = get_partition_parent(RelationGetRelid(rel), false);
@@ -7632,19 +7620,18 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
}
/*
- * Okay, actually perform the catalog change ... if needed
+ * Find the constraint that makes this column NOT NULL, and drop it.
+ * dropconstraint_internal() resets attnotnull.
*/
- if (attTup->attnotnull)
- {
- attTup->attnotnull = false;
+ conTup = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum);
+ if (conTup == NULL)
+ elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation \"%s\"",
+ colName, RelationGetRelationName(rel));
- CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
-
- ObjectAddressSubSet(address, RelationRelationId,
- RelationGetRelid(rel), attnum);
- }
- else
- address = InvalidObjectAddress;
+ /* The normal case: we have a pg_constraint row, remove it */
+ dropconstraint_internal(rel, conTup, DROP_RESTRICT, recurse, false,
+ false, lockmode);
+ heap_freetuple(conTup);
InvokeObjectPostAlterHook(RelationRelationId,
RelationGetRelid(rel), attnum);
@@ -7655,104 +7642,105 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
}
/*
- * ALTER TABLE ALTER COLUMN SET NOT NULL
+ * Helper to set pg_attribute.attnotnull if it isn't set, and to tell phase 3
+ * to verify it.
+ *
+ * When called to alter an existing table, 'wqueue' must be given so that we
+ * can queue a check that existing tuples pass the constraint. When called
+ * from table creation, 'wqueue' should be passed as NULL.
*/
-
static void
-ATPrepSetNotNull(List **wqueue, Relation rel,
- AlterTableCmd *cmd, bool recurse, bool recursing,
- LOCKMODE lockmode, AlterTableUtilityContext *context)
+set_attnotnull(List **wqueue, Relation rel, AttrNumber attnum,
+ LOCKMODE lockmode)
{
+ Form_pg_attribute attr;
+
+ CheckAlterTableIsSafe(rel);
+
/*
- * If we're already recursing, there's nothing to do; the topmost
- * invocation of ATSimpleRecursion already visited all children.
+ * Exit quickly by testing attnotnull from the tupledesc's copy of the
+ * attribute.
*/
- if (recursing)
+ attr = TupleDescAttr(RelationGetDescr(rel), attnum - 1);
+ if (attr->attisdropped)
return;
- /*
- * If the target column is already marked NOT NULL, we can skip recursing
- * to children, because their columns should already be marked NOT NULL as
- * well. But there's no point in checking here unless the relation has
- * some children; else we can just wait till execution to check. (If it
- * does have children, however, this can save taking per-child locks
- * unnecessarily. This greatly improves concurrency in some parallel
- * restore scenarios.)
- *
- * Unfortunately, we can only apply this optimization to partitioned
- * tables, because traditional inheritance doesn't enforce that child
- * columns be NOT NULL when their parent is. (That's a bug that should
- * get fixed someday.)
- */
- if (rel->rd_rel->relhassubclass &&
- rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ if (!attr->attnotnull)
{
+ Relation attr_rel;
HeapTuple tuple;
- bool attnotnull;
- tuple = SearchSysCacheAttName(RelationGetRelid(rel), cmd->name);
+ attr_rel = table_open(AttributeRelationId, RowExclusiveLock);
- /* Might as well throw the error now, if name is bad */
+ tuple = SearchSysCacheCopyAttNum(RelationGetRelid(rel), attnum);
if (!HeapTupleIsValid(tuple))
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
- cmd->name, RelationGetRelationName(rel))));
+ elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+ attnum, RelationGetRelid(rel));
- attnotnull = ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull;
- ReleaseSysCache(tuple);
- if (attnotnull)
- return;
+ attr = (Form_pg_attribute) GETSTRUCT(tuple);
+ Assert(!attr->attnotnull);
+ attr->attnotnull = true;
+ CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
+
+ /*
+ * If the nullness isn't already proven by validated constraints, have
+ * ALTER TABLE phase 3 test for it.
+ */
+ if (wqueue && !NotNullImpliedByRelConstraints(rel, attr))
+ {
+ AlteredTableInfo *tab;
+
+ tab = ATGetQueueEntry(wqueue, rel);
+ tab->verify_new_notnull = true;
+ }
+
+ CommandCounterIncrement();
+
+ table_close(attr_rel, RowExclusiveLock);
+ heap_freetuple(tuple);
}
-
- /*
- * If we have ALTER TABLE ONLY ... SET NOT NULL on a partitioned table,
- * apply ALTER TABLE ... CHECK NOT NULL to every child. Otherwise, use
- * normal recursion logic.
- */
- if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
- !recurse)
- {
- AlterTableCmd *newcmd = makeNode(AlterTableCmd);
-
- newcmd->subtype = AT_CheckNotNull;
- newcmd->name = pstrdup(cmd->name);
- ATSimpleRecursion(wqueue, rel, newcmd, true, lockmode, context);
- }
- else
- ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
}
/*
- * Return the address of the modified column. If the column was already NOT
- * NULL, InvalidObjectAddress is returned.
+ * ALTER TABLE ALTER COLUMN SET NOT NULL
+ *
+ * Add a not-null constraint to a single table and its children. Returns
+ * the address of the constraint added to the parent relation, if one gets
+ * added, or InvalidObjectAddress otherwise.
+ *
+ * We must recurse to child tables during execution, rather than using
+ * ALTER TABLE's normal prep-time recursion.
*/
static ObjectAddress
-ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
- const char *colName, LOCKMODE lockmode)
+ATExecSetNotNull(List **wqueue, Relation rel, char *conName, char *colName,
+ bool recurse, bool recursing, LOCKMODE lockmode)
{
HeapTuple tuple;
- Form_pg_attribute attTup;
AttrNumber attnum;
- Relation attr_rel;
ObjectAddress address;
+ Constraint *constraint;
+ CookedConstraint *ccon;
+ List *cooked;
+ bool is_no_inherit = false;
- /*
- * lookup the attribute
- */
- attr_rel = table_open(AttributeRelationId, RowExclusiveLock);
+ /* Guard against stack overflow due to overly deep inheritance tree. */
+ check_stack_depth();
- tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
+ /* At top level, permission check was done in ATPrepCmd, else do it */
+ if (recursing)
+ {
+ ATSimplePermissions(AT_AddConstraint, rel,
+ ATT_PARTITIONED_TABLE | ATT_TABLE | ATT_FOREIGN_TABLE);
+ Assert(conName != NULL);
+ }
- if (!HeapTupleIsValid(tuple))
+ attnum = get_attnum(RelationGetRelid(rel), colName);
+ if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel))));
- attTup = (Form_pg_attribute) GETSTRUCT(tuple);
- attnum = attTup->attnum;
-
/* Prevent them from altering a system attribute */
if (attnum <= 0)
ereport(ERROR,
@@ -7760,81 +7748,132 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
errmsg("cannot alter system column \"%s\"",
colName)));
- /*
- * Okay, actually perform the catalog change ... if needed
- */
- if (!attTup->attnotnull)
+ /* See if there's already a constraint */
+ tuple = findNotNullConstraintAttnum(RelationGetRelid(rel), attnum);
+ if (HeapTupleIsValid(tuple))
{
- attTup->attnotnull = true;
-
- CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
+ Form_pg_constraint conForm = (Form_pg_constraint) GETSTRUCT(tuple);
+ bool changed = false;
/*
- * Ordinarily phase 3 must ensure that no NULLs exist in columns that
- * are set NOT NULL; however, if we can find a constraint which proves
- * this then we can skip that. We needn't bother looking if we've
- * already found that we must verify some other not-null constraint.
+ * Don't let a NO INHERIT constraint be changed into inherit.
*/
- if (!tab->verify_new_notnull && !NotNullImpliedByRelConstraints(rel, attTup))
+ if (conForm->connoinherit && recurse)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change NO INHERIT status of NOT NULL constraint \"%s\" on relation \"%s\"",
+ NameStr(conForm->conname),
+ RelationGetRelationName(rel)));
+
+ /*
+ * If we find an appropriate constraint, we're almost done, but just
+ * need to change some properties on it: if we're recursing, increment
+ * coninhcount; if not, set conislocal if not already set.
+ */
+ if (recursing)
{
- /* Tell Phase 3 it needs to test the constraint */
- tab->verify_new_notnull = true;
+ if (pg_add_s16_overflow(conForm->coninhcount, 1,
+ &conForm->coninhcount))
+ ereport(ERROR,
+ errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("too many inheritance parents"));
+ changed = true;
+ }
+ else if (!conForm->conislocal)
+ {
+ conForm->conislocal = true;
+ changed = true;
}
- ObjectAddressSubSet(address, RelationRelationId,
- RelationGetRelid(rel), attnum);
+ if (changed)
+ {
+ Relation constr_rel;
+
+ constr_rel = table_open(ConstraintRelationId, RowExclusiveLock);
+
+ CatalogTupleUpdate(constr_rel, &tuple->t_self, tuple);
+ ObjectAddressSet(address, ConstraintRelationId, conForm->oid);
+ table_close(constr_rel, RowExclusiveLock);
+ }
+
+ if (changed)
+ return address;
+ else
+ return InvalidObjectAddress;
}
- else
- address = InvalidObjectAddress;
+
+ /*
+ * If we're asked not to recurse, and children exist, raise an error for
+ * partitioned tables. For inheritance, we act as if NO INHERIT had been
+ * specified.
+ */
+ if (!recurse &&
+ find_inheritance_children(RelationGetRelid(rel),
+ NoLock) != NIL)
+ {
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraint must be added to child tables too"),
+ errhint("Do not specify the ONLY keyword."));
+ else
+ is_no_inherit = true;
+ }
+
+ /*
+ * No constraint exists; we must add one. First determine a name to use,
+ * if we haven't already.
+ */
+ if (!recursing)
+ {
+ Assert(conName == NULL);
+ conName = ChooseConstraintName(RelationGetRelationName(rel),
+ colName, "not_null",
+ RelationGetNamespace(rel),
+ NIL);
+ }
+
+ constraint = makeNotNullConstraint(makeString(colName));
+ constraint->is_no_inherit = is_no_inherit;
+ constraint->conname = conName;
+
+ /* and do it */
+ cooked = AddRelationNewConstraints(rel, NIL, list_make1(constraint),
+ false, !recursing, false, NULL);
+ ccon = linitial(cooked);
+ ObjectAddressSet(address, ConstraintRelationId, ccon->conoid);
InvokeObjectPostAlterHook(RelationRelationId,
RelationGetRelid(rel), attnum);
- table_close(attr_rel, RowExclusiveLock);
+ /* Mark pg_attribute.attnotnull for the column */
+ set_attnotnull(wqueue, rel, attnum, lockmode);
+
+ /*
+ * Recurse to propagate the constraint to children that don't have one.
+ */
+ if (recurse)
+ {
+ List *children;
+
+ children = find_inheritance_children(RelationGetRelid(rel),
+ lockmode);
+
+ foreach_oid(childoid, children)
+ {
+ Relation childrel = table_open(childoid, NoLock);
+
+ CommandCounterIncrement();
+
+ ATExecSetNotNull(wqueue, childrel, conName, colName,
+ recurse, true, lockmode);
+ table_close(childrel, NoLock);
+ }
+ }
return address;
}
-/*
- * ALTER TABLE ALTER COLUMN CHECK NOT NULL
- *
- * This doesn't exist in the grammar, but we generate AT_CheckNotNull
- * commands against the partitions of a partitioned table if the user
- * writes ALTER TABLE ONLY ... SET NOT NULL on the partitioned table,
- * or tries to create a primary key on it (which internally creates
- * AT_SetNotNull on the partitioned table). Such a command doesn't
- * allow us to actually modify any partition, but we want to let it
- * go through if the partitions are already properly marked.
- *
- * In future, this might need to adjust the child table's state, likely
- * by incrementing an inheritance count for the attnotnull constraint.
- * For now we need only check for the presence of the flag.
- */
-static void
-ATExecCheckNotNull(AlteredTableInfo *tab, Relation rel,
- const char *colName, LOCKMODE lockmode)
-{
- HeapTuple tuple;
-
- tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
-
- if (!HeapTupleIsValid(tuple))
- ereport(ERROR,
- errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
- colName, RelationGetRelationName(rel)));
-
- if (!((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("constraint must be added to child tables too"),
- errdetail("Column \"%s\" of relation \"%s\" is not already NOT NULL.",
- colName, RelationGetRelationName(rel)),
- errhint("Do not specify the ONLY keyword.")));
-
- ReleaseSysCache(tuple);
-}
-
/*
* NotNullImpliedByRelConstraints
* Does rel's existing constraints imply NOT NULL for the given attribute?
@@ -9139,6 +9178,71 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
return object;
}
+/*
+ * Prepare to add a primary key on table, by adding not-null constraints
+ * on all columns.
+ */
+static void
+ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
+ bool recurse, LOCKMODE lockmode,
+ AlterTableUtilityContext *context)
+{
+ ListCell *lc;
+ Constraint *pkconstr;
+
+ pkconstr = castNode(Constraint, cmd->def);
+ if (pkconstr->contype != CONSTR_PRIMARY)
+ return;
+
+ /*
+ * If not recursing, we must ensure that all children have a NOT NULL
+ * constraint on the columns, and error out if not.
+ */
+ if (!recurse)
+ {
+ List *children;
+
+ children = find_inheritance_children(RelationGetRelid(rel),
+ lockmode);
+ foreach_oid(childrelid, children)
+ {
+ foreach(lc, pkconstr->keys)
+ {
+ HeapTuple tup;
+ Form_pg_attribute attrForm;
+ char *attname = strVal(lfirst(lc));
+
+ tup = SearchSysCacheAttName(childrelid, attname);
+ if (!tup)
+ elog(ERROR, "cache lookup failed for attribute %s of relation %u",
+ attname, childrelid);
+ attrForm = (Form_pg_attribute) GETSTRUCT(tup);
+ if (!attrForm->attnotnull)
+ ereport(ERROR,
+ errmsg("column \"%s\" of table \"%s\" is not marked NOT NULL",
+ attname, get_rel_name(childrelid)));
+ ReleaseSysCache(tup);
+ }
+ }
+ }
+
+ /* Insert not-null constraints in the queue for the PK columns */
+ foreach(lc, pkconstr->keys)
+ {
+ AlterTableCmd *newcmd;
+ Constraint *nnconstr;
+
+ nnconstr = makeNotNullConstraint(lfirst(lc));
+
+ newcmd = makeNode(AlterTableCmd);
+ newcmd->subtype = AT_AddConstraint;
+ newcmd->recurse = true;
+ newcmd->def = (Node *) nnconstr;
+
+ ATPrepCmd(wqueue, rel, newcmd, true, false, lockmode, context);
+ }
+}
+
/*
* ALTER TABLE ADD INDEX
*
@@ -9334,17 +9438,18 @@ ATExecAddConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
Assert(IsA(newConstraint, Constraint));
/*
- * Currently, we only expect to see CONSTR_CHECK and CONSTR_FOREIGN nodes
- * arriving here (see the preprocessing done in parse_utilcmd.c). Use a
- * switch anyway to make it easier to add more code later.
+ * Currently, we only expect to see CONSTR_CHECK, CONSTR_NOTNULL and
+ * CONSTR_FOREIGN nodes arriving here (see the preprocessing done in
+ * parse_utilcmd.c).
*/
switch (newConstraint->contype)
{
case CONSTR_CHECK:
+ case CONSTR_NOTNULL:
address =
- ATAddCheckConstraint(wqueue, tab, rel,
- newConstraint, recurse, false, is_readd,
- lockmode);
+ ATAddCheckNNConstraint(wqueue, tab, rel,
+ newConstraint, recurse, false, is_readd,
+ lockmode);
break;
case CONSTR_FOREIGN:
@@ -9425,9 +9530,9 @@ ChooseForeignKeyConstraintNameAddition(List *colnames)
}
/*
- * Add a check constraint to a single table and its children. Returns the
- * address of the constraint added to the parent relation, if one gets added,
- * or InvalidObjectAddress otherwise.
+ * Add a check or not-null constraint to a single table and its children.
+ * Returns the address of the constraint added to the parent relation,
+ * if one gets added, or InvalidObjectAddress otherwise.
*
* Subroutine for ATExecAddConstraint.
*
@@ -9440,9 +9545,9 @@ ChooseForeignKeyConstraintNameAddition(List *colnames)
* the parent table and pass that down.
*/
static ObjectAddress
-ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
- Constraint *constr, bool recurse, bool recursing,
- bool is_readd, LOCKMODE lockmode)
+ATAddCheckNNConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
+ Constraint *constr, bool recurse, bool recursing,
+ bool is_readd, LOCKMODE lockmode)
{
List *newcons;
ListCell *lcon;
@@ -9450,6 +9555,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
ListCell *child;
ObjectAddress address = InvalidObjectAddress;
+ /* Guard against stack overflow due to overly deep inheritance tree. */
+ check_stack_depth();
+
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
ATSimplePermissions(AT_AddConstraint, rel,
@@ -9481,7 +9589,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
{
CookedConstraint *ccon = (CookedConstraint *) lfirst(lcon);
- if (!ccon->skip_validation)
+ if (!ccon->skip_validation && ccon->contype != CONSTR_NOTNULL)
{
NewConstraint *newcon;
@@ -9497,11 +9605,18 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (constr->conname == NULL)
constr->conname = ccon->name;
+ /*
+ * If adding a not-null constraint, set the pg_attribute flag and tell
+ * phase 3 to verify existing rows, if needed.
+ */
+ if (constr->contype == CONSTR_NOTNULL)
+ set_attnotnull(wqueue, rel, ccon->attnum, lockmode);
+
ObjectAddressSet(address, ConstraintRelationId, ccon->conoid);
}
/* At this point we must have a locked-down name to use */
- Assert(constr->conname != NULL);
+ Assert(newcons == NIL || constr->conname != NULL);
/* Advance command counter in case same table is visited multiple times */
CommandCounterIncrement();
@@ -9531,7 +9646,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/*
* Check if ONLY was specified with ALTER TABLE. If so, allow the
- * constraint creation only if there are no children currently. Error out
+ * constraint creation only if there are no children currently. Error out
* otherwise.
*/
if (!recurse && children != NIL)
@@ -9539,6 +9654,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("constraint must be added to child tables too")));
+ /*
+ * Recurse to create the constraint on each child.
+ */
foreach(child, children)
{
Oid childrelid = lfirst_oid(child);
@@ -9552,9 +9670,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/* Find or create work queue entry for this table */
childtab = ATGetQueueEntry(wqueue, childrel);
- /* Recurse to child */
- ATAddCheckConstraint(wqueue, childtab, childrel,
- constr, recurse, true, is_readd, lockmode);
+ /* Recurse to this child */
+ ATAddCheckNNConstraint(wqueue, childtab, childrel,
+ constr, recurse, true, is_readd, lockmode);
table_close(childrel, NoLock);
}
@@ -12667,24 +12785,14 @@ createForeignKeyCheckTriggers(Oid myRelOid, Oid refRelOid,
*/
static void
ATExecDropConstraint(Relation rel, const char *constrName,
- DropBehavior behavior,
- bool recurse, bool recursing,
+ DropBehavior behavior, bool recurse,
bool missing_ok, LOCKMODE lockmode)
{
- List *children;
Relation conrel;
- Form_pg_constraint con;
SysScanDesc scan;
ScanKeyData skey[3];
HeapTuple tuple;
bool found = false;
- bool is_no_inherit_constraint = false;
- char contype;
-
- /* At top level, permission check was done in ATPrepCmd, else do it */
- if (recursing)
- ATSimplePermissions(AT_DropConstraint, rel,
- ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
conrel = table_open(ConstraintRelationId, RowExclusiveLock);
@@ -12709,47 +12817,8 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/* There can be at most one matching row */
if (HeapTupleIsValid(tuple = systable_getnext(scan)))
{
- ObjectAddress conobj;
-
- con = (Form_pg_constraint) GETSTRUCT(tuple);
-
- /* Don't drop inherited constraints */
- if (con->coninhcount > 0 && !recursing)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot drop inherited constraint \"%s\" of relation \"%s\"",
- constrName, RelationGetRelationName(rel))));
-
- is_no_inherit_constraint = con->connoinherit;
- contype = con->contype;
-
- /*
- * If it's a foreign-key constraint, we'd better lock the referenced
- * table and check that that's not in use, just as we've already done
- * for the constrained table (else we might, eg, be dropping a trigger
- * that has unfired events). But we can/must skip that in the
- * self-referential case.
- */
- if (contype == CONSTRAINT_FOREIGN &&
- con->confrelid != RelationGetRelid(rel))
- {
- Relation frel;
-
- /* Must match lock taken by RemoveTriggerById: */
- frel = table_open(con->confrelid, AccessExclusiveLock);
- CheckAlterTableIsSafe(frel);
- table_close(frel, NoLock);
- }
-
- /*
- * Perform the actual constraint deletion
- */
- conobj.classId = ConstraintRelationId;
- conobj.objectId = con->oid;
- conobj.objectSubId = 0;
-
- performDeletion(&conobj, behavior, 0);
-
+ dropconstraint_internal(rel, tuple, behavior, recurse, false,
+ missing_ok, lockmode);
found = true;
}
@@ -12758,31 +12827,180 @@ ATExecDropConstraint(Relation rel, const char *constrName,
if (!found)
{
if (!missing_ok)
- {
ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("constraint \"%s\" of relation \"%s\" does not exist",
- constrName, RelationGetRelationName(rel))));
- }
+ errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("constraint \"%s\" of relation \"%s\" does not exist",
+ constrName, RelationGetRelationName(rel)));
else
- {
ereport(NOTICE,
- (errmsg("constraint \"%s\" of relation \"%s\" does not exist, skipping",
- constrName, RelationGetRelationName(rel))));
- table_close(conrel, RowExclusiveLock);
- return;
+ errmsg("constraint \"%s\" of relation \"%s\" does not exist, skipping",
+ constrName, RelationGetRelationName(rel)));
+ }
+
+ table_close(conrel, RowExclusiveLock);
+}
+
+/*
+ * Remove a constraint, using its pg_constraint tuple
+ *
+ * Implementation for ALTER TABLE DROP CONSTRAINT and ALTER TABLE ALTER COLUMN
+ * DROP NOT NULL.
+ *
+ * Returns the address of the constraint being removed.
+ */
+static ObjectAddress
+dropconstraint_internal(Relation rel, HeapTuple constraintTup, DropBehavior behavior,
+ bool recurse, bool recursing, bool missing_ok,
+ LOCKMODE lockmode)
+{
+ Relation conrel;
+ Form_pg_constraint con;
+ ObjectAddress conobj;
+ List *children;
+ bool is_no_inherit_constraint = false;
+ char *constrName;
+ char *colname = NULL;
+
+ /* Guard against stack overflow due to overly deep inheritance tree. */
+ check_stack_depth();
+
+ /* At top level, permission check was done in ATPrepCmd, else do it */
+ if (recursing)
+ ATSimplePermissions(AT_DropConstraint, rel,
+ ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
+
+ conrel = table_open(ConstraintRelationId, RowExclusiveLock);
+
+ con = (Form_pg_constraint) GETSTRUCT(constraintTup);
+ constrName = NameStr(con->conname);
+
+ /* Don't allow drop of inherited constraints */
+ if (con->coninhcount > 0 && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop inherited constraint \"%s\" of relation \"%s\"",
+ constrName, RelationGetRelationName(rel))));
+
+ /*
+ * Reset pg_constraint.attnotnull, if this is a not-null constraint.
+ *
+ * While doing that, we're in a good position to disallow dropping a not-
+ * null constraint underneath a primary key, a replica identity index, or
+ * a generated identity column.
+ */
+ if (con->contype == CONSTRAINT_NOTNULL)
+ {
+ Relation attrel = table_open(AttributeRelationId, RowExclusiveLock);
+ AttrNumber attnum = extractNotNullColumn(constraintTup);
+ Bitmapset *pkattrs;
+ Bitmapset *irattrs;
+ HeapTuple atttup;
+ Form_pg_attribute attForm;
+
+ /* save column name for recursion step */
+ colname = get_attname(RelationGetRelid(rel), attnum, false);
+
+ /*
+ * Disallow if it's in the primary key. For partitioned tables we
+ * cannot rely solely on RelationGetIndexAttrBitmap, because it'll
+ * return NULL if the primary key is invalid; but we still need to
+ * protect not-null constraints under such a constraint, so check the
+ * slow way.
+ */
+ pkattrs = RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_PRIMARY_KEY);
+
+ if (pkattrs == NULL &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ Oid pkindex = RelationGetPrimaryKeyIndex(rel, true);
+
+ if (OidIsValid(pkindex))
+ {
+ Relation pk = relation_open(pkindex, AccessShareLock);
+
+ pkattrs = NULL;
+ for (int i = 0; i < pk->rd_index->indnkeyatts; i++)
+ pkattrs = bms_add_member(pkattrs, pk->rd_index->indkey.values[i]);
+
+ relation_close(pk, AccessShareLock);
+ }
}
+
+ if (pkattrs &&
+ bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, pkattrs))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is in a primary key",
+ get_attname(RelationGetRelid(rel), attnum, false)));
+
+ /* Disallow if it's in the replica identity */
+ irattrs = RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_IDENTITY_KEY);
+ if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, irattrs))
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is in index used as replica identity",
+ get_attname(RelationGetRelid(rel), attnum, false)));
+
+ /* Disallow if it's a GENERATED AS IDENTITY column */
+ atttup = SearchSysCacheCopyAttNum(RelationGetRelid(rel), attnum);
+ if (!HeapTupleIsValid(atttup))
+ elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+ attnum, RelationGetRelid(rel));
+ attForm = (Form_pg_attribute) GETSTRUCT(atttup);
+ if (attForm->attidentity != '\0')
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("column \"%s\" of relation \"%s\" is an identity column",
+ get_attname(RelationGetRelid(rel), attnum,
+ false),
+ RelationGetRelationName(rel)));
+
+ /* All good -- reset attnotnull if needed */
+ if (attForm->attnotnull)
+ {
+ attForm->attnotnull = false;
+ CatalogTupleUpdate(attrel, &atttup->t_self, atttup);
+ }
+
+ table_close(attrel, RowExclusiveLock);
+ }
+
+ is_no_inherit_constraint = con->connoinherit;
+
+ /*
+ * If it's a foreign-key constraint, we'd better lock the referenced table
+ * and check that that's not in use, just as we've already done for the
+ * constrained table (else we might, eg, be dropping a trigger that has
+ * unfired events). But we can/must skip that in the self-referential
+ * case.
+ */
+ if (con->contype == CONSTRAINT_FOREIGN &&
+ con->confrelid != RelationGetRelid(rel))
+ {
+ Relation frel;
+
+ /* Must match lock taken by RemoveTriggerById: */
+ frel = table_open(con->confrelid, AccessExclusiveLock);
+ CheckAlterTableIsSafe(frel);
+ table_close(frel, NoLock);
}
/*
- * For partitioned tables, non-CHECK inherited constraints are dropped via
- * the dependency mechanism, so we're done here.
+ * Perform the actual constraint deletion
*/
- if (contype != CONSTRAINT_CHECK &&
+ ObjectAddressSet(conobj, ConstraintRelationId, con->oid);
+ performDeletion(&conobj, behavior, 0);
+
+ /*
+ * For partitioned tables, non-CHECK, non-NOT-NULL inherited constraints
+ * are dropped via the dependency mechanism, so we're done here.
+ */
+ if (con->contype != CONSTRAINT_CHECK &&
+ con->contype != CONSTRAINT_NOTNULL &&
rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
table_close(conrel, RowExclusiveLock);
- return;
+ return conobj;
}
/*
@@ -12798,48 +13016,65 @@ ATExecDropConstraint(Relation rel, const char *constrName,
foreach_oid(childrelid, children)
{
Relation childrel;
- HeapTuple copy_tuple;
+ HeapTuple tuple;
+ Form_pg_constraint childcon;
/* find_inheritance_children already got lock */
childrel = table_open(childrelid, NoLock);
CheckAlterTableIsSafe(childrel);
- ScanKeyInit(&skey[0],
- Anum_pg_constraint_conrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(childrelid));
- ScanKeyInit(&skey[1],
- Anum_pg_constraint_contypid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(InvalidOid));
- ScanKeyInit(&skey[2],
- Anum_pg_constraint_conname,
- BTEqualStrategyNumber, F_NAMEEQ,
- CStringGetDatum(constrName));
- scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
- true, NULL, 3, skey);
+ /*
+ * We search for not-null constraints by column name, and others by
+ * constraint name.
+ */
+ if (con->contype == CONSTRAINT_NOTNULL)
+ {
+ tuple = findNotNullConstraint(childrelid, colname);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for not-null constraint on column \"%s\" of relation %u",
+ colname, RelationGetRelid(childrel));
+ }
+ else
+ {
+ SysScanDesc scan;
+ ScanKeyData skey[3];
- /* There can be at most one matching row */
- if (!HeapTupleIsValid(tuple = systable_getnext(scan)))
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("constraint \"%s\" of relation \"%s\" does not exist",
- constrName,
- RelationGetRelationName(childrel))));
+ ScanKeyInit(&skey[0],
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(childrelid));
+ ScanKeyInit(&skey[1],
+ Anum_pg_constraint_contypid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(InvalidOid));
+ ScanKeyInit(&skey[2],
+ Anum_pg_constraint_conname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(constrName));
+ scan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId,
+ true, NULL, 3, skey);
+ /* There can only be one, so no need to loop */
+ tuple = systable_getnext(scan);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("constraint \"%s\" of relation \"%s\" does not exist",
+ constrName,
+ RelationGetRelationName(childrel))));
+ tuple = heap_copytuple(tuple);
+ systable_endscan(scan);
+ }
- copy_tuple = heap_copytuple(tuple);
+ childcon = (Form_pg_constraint) GETSTRUCT(tuple);
- systable_endscan(scan);
+ /* Right now only CHECK and not-null constraints can be inherited */
+ if (childcon->contype != CONSTRAINT_CHECK &&
+ childcon->contype != CONSTRAINT_NOTNULL)
+ elog(ERROR, "inherited constraint is not a CHECK or not-null constraint");
- con = (Form_pg_constraint) GETSTRUCT(copy_tuple);
-
- /* Right now only CHECK constraints can be inherited */
- if (con->contype != CONSTRAINT_CHECK)
- elog(ERROR, "inherited constraint is not a CHECK constraint");
-
- if (con->coninhcount <= 0) /* shouldn't happen */
+ if (childcon->coninhcount <= 0) /* shouldn't happen */
elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
- childrelid, constrName);
+ childrelid, NameStr(childcon->conname));
if (recurse)
{
@@ -12847,18 +13082,18 @@ ATExecDropConstraint(Relation rel, const char *constrName,
* If the child constraint has other definition sources, just
* decrement its inheritance count; if not, recurse to delete it.
*/
- if (con->coninhcount == 1 && !con->conislocal)
+ if (childcon->coninhcount == 1 && !childcon->conislocal)
{
/* Time to delete this child constraint, too */
- ATExecDropConstraint(childrel, constrName, behavior,
- true, true,
- false, lockmode);
+ dropconstraint_internal(childrel, tuple, behavior,
+ recurse, true, missing_ok,
+ lockmode);
}
else
{
/* Child constraint must survive my deletion */
- con->coninhcount--;
- CatalogTupleUpdate(conrel, ©_tuple->t_self, copy_tuple);
+ childcon->coninhcount--;
+ CatalogTupleUpdate(conrel, &tuple->t_self, tuple);
/* Make update visible */
CommandCounterIncrement();
@@ -12867,25 +13102,29 @@ ATExecDropConstraint(Relation rel, const char *constrName,
else
{
/*
- * If we were told to drop ONLY in this table (no recursion), we
- * need to mark the inheritors' constraints as locally defined
- * rather than inherited.
+ * If we were told to drop ONLY in this table (no recursion) and
+ * there are no further parents for this constraint, we need to
+ * mark the inheritors' constraints as locally defined rather than
+ * inherited.
*/
- con->coninhcount--;
- con->conislocal = true;
+ childcon->coninhcount--;
+ if (childcon->coninhcount == 0)
+ childcon->conislocal = true;
- CatalogTupleUpdate(conrel, ©_tuple->t_self, copy_tuple);
+ CatalogTupleUpdate(conrel, &tuple->t_self, tuple);
/* Make update visible */
CommandCounterIncrement();
}
- heap_freetuple(copy_tuple);
+ heap_freetuple(tuple);
table_close(childrel, NoLock);
}
table_close(conrel, RowExclusiveLock);
+
+ return conobj;
}
/*
@@ -13834,10 +14073,26 @@ RememberConstraintForRebuilding(Oid conoid, AlteredTableInfo *tab)
char *defstring = pg_get_constraintdef_command(conoid);
Oid indoid;
- tab->changedConstraintOids = lappend_oid(tab->changedConstraintOids,
- conoid);
- tab->changedConstraintDefs = lappend(tab->changedConstraintDefs,
- defstring);
+ /*
+ * It is critical to create not-null constraints ahead of primary key
+ * indexes; otherwise, the not-null constraint would be created by the
+ * primary key, and the constraint name would be wrong.
+ */
+ if (get_constraint_type(conoid) == CONSTRAINT_NOTNULL)
+ {
+ tab->changedConstraintOids = lcons_oid(conoid,
+ tab->changedConstraintOids);
+ tab->changedConstraintDefs = lcons(defstring,
+ tab->changedConstraintDefs);
+ }
+ else
+ {
+
+ tab->changedConstraintOids = lappend_oid(tab->changedConstraintOids,
+ conoid);
+ tab->changedConstraintDefs = lappend(tab->changedConstraintDefs,
+ defstring);
+ }
/*
* For the index of a constraint, if any, remember if it is used for
@@ -14000,9 +14255,10 @@ ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE lockmode)
/*
* If the constraint is inherited (only), we don't want to inject a
- * new definition here; it'll get recreated when ATAddCheckConstraint
- * recurses from adding the parent table's constraint. But we had to
- * carry the info this far so that we can drop the constraint below.
+ * new definition here; it'll get recreated when
+ * ATAddCheckNNConstraint recurses from adding the parent table's
+ * constraint. But we had to carry the info this far so that we can
+ * drop the constraint below.
*/
if (!conislocal)
continue;
@@ -14241,23 +14497,21 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
tab->subcmds[AT_PASS_OLD_CONSTR] =
lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
- /* recreate any comment on the constraint */
- RebuildConstraintComment(tab,
- AT_PASS_OLD_CONSTR,
- oldId,
- rel,
- NIL,
- con->conname);
- }
- else if (cmd->subtype == AT_SetNotNull)
- {
/*
- * The parser will create AT_SetNotNull subcommands for
- * columns of PRIMARY KEY indexes/constraints, but we need
- * not do anything with them here, because the columns'
- * NOT NULL marks will already have been propagated into
- * the new table definition.
+ * Recreate any comment on the constraint. If we have
+ * recreated a primary key, then transformTableConstraint
+ * has added an unnamed not-null constraint here; skip
+ * this in that case.
*/
+ if (con->conname)
+ RebuildConstraintComment(tab,
+ AT_PASS_OLD_CONSTR,
+ oldId,
+ rel,
+ NIL,
+ con->conname);
+ else
+ Assert(con->contype == CONSTR_NOTNULL);
}
else
elog(ERROR, "unexpected statement subtype: %d",
@@ -16012,14 +16266,24 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispart
RelationGetRelationName(child_rel), parent_attname)));
/*
- * Check child doesn't discard NOT NULL property. (Other
- * constraints are checked elsewhere.)
+ * If the parent has a not-null constraint that's not NO INHERIT,
+ * make sure the child has one too.
+ *
+ * Other constraints are checked elsewhere.
*/
if (parent_att->attnotnull && !child_att->attnotnull)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table must be marked NOT NULL",
- parent_attname)));
+ {
+ HeapTuple contup;
+
+ contup = findNotNullConstraintAttnum(RelationGetRelid(parent_rel),
+ parent_att->attnum);
+ if (HeapTupleIsValid(contup) &&
+ !((Form_pg_constraint) GETSTRUCT(contup))->connoinherit)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("column \"%s\" in child table \"%s\" must be marked NOT NULL",
+ parent_attname, RelationGetRelationName(child_rel)));
+ }
/*
* Child column must be generated if and only if parent column is.
@@ -16101,6 +16365,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
ScanKeyData parent_key;
HeapTuple parent_tuple;
Oid parent_relid = RelationGetRelid(parent_rel);
+ AttrMap *attmap;
constraintrel = table_open(ConstraintRelationId, RowExclusiveLock);
@@ -16112,21 +16377,32 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
parent_scan = systable_beginscan(constraintrel, ConstraintRelidTypidNameIndexId,
true, NULL, 1, &parent_key);
+ attmap = build_attrmap_by_name(RelationGetDescr(parent_rel),
+ RelationGetDescr(child_rel),
+ true);
+
while (HeapTupleIsValid(parent_tuple = systable_getnext(parent_scan)))
{
Form_pg_constraint parent_con = (Form_pg_constraint) GETSTRUCT(parent_tuple);
SysScanDesc child_scan;
ScanKeyData child_key;
HeapTuple child_tuple;
+ AttrNumber parent_attno;
bool found = false;
- if (parent_con->contype != CONSTRAINT_CHECK)
+ if (parent_con->contype != CONSTRAINT_CHECK &&
+ parent_con->contype != CONSTRAINT_NOTNULL)
continue;
/* if the parent's constraint is marked NO INHERIT, it's not inherited */
if (parent_con->connoinherit)
continue;
+ if (parent_con->contype == CONSTRAINT_NOTNULL)
+ parent_attno = extractNotNullColumn(parent_tuple);
+ else
+ parent_attno = InvalidAttrNumber;
+
/* Search for a child constraint matching this one */
ScanKeyInit(&child_key,
Anum_pg_constraint_conrelid,
@@ -16140,20 +16416,46 @@ 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)
+ if (child_con->contype != parent_con->contype)
continue;
- if (strcmp(NameStr(parent_con->conname),
- NameStr(child_con->conname)) != 0)
- continue;
+ /*
+ * CHECK constraint are matched by constraint name, NOT NULL ones
+ * by attribute number.
+ */
+ if (child_con->contype == CONSTRAINT_CHECK)
+ {
+ if (strcmp(NameStr(parent_con->conname),
+ NameStr(child_con->conname)) != 0)
+ continue;
+ }
+ else if (child_con->contype == CONSTRAINT_NOTNULL)
+ {
+ Form_pg_attribute parent_attr;
+ Form_pg_attribute child_attr;
+ AttrNumber child_attno;
- if (!constraints_equivalent(parent_tuple, child_tuple, RelationGetDescr(constraintrel)))
+ parent_attr = TupleDescAttr(parent_rel->rd_att, parent_attno - 1);
+ child_attno = extractNotNullColumn(child_tuple);
+ if (parent_attno != attmap->attnums[child_attno - 1])
+ continue;
+
+ child_attr = TupleDescAttr(child_rel->rd_att, child_attno - 1);
+ /* there shouldn't be constraints on dropped columns */
+ if (parent_attr->attisdropped || child_attr->attisdropped)
+ elog(ERROR, "found not-null constraint on dropped columns");
+ }
+
+ if (child_con->contype == CONSTRAINT_CHECK &&
+ !constraints_equivalent(parent_tuple, child_tuple, RelationGetDescr(constraintrel)))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
RelationGetRelationName(child_rel), NameStr(parent_con->conname))));
- /* If the child constraint is "no inherit" then cannot merge */
+ /*
+ * If the child constraint is "no inherit" then cannot merge
+ */
if (child_con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -16204,10 +16506,21 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
systable_endscan(child_scan);
if (!found)
+ {
+ if (parent_con->contype == CONSTRAINT_NOTNULL)
+ ereport(ERROR,
+ errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("column \"%s\" in child table \"%s\" must be marked NOT NULL",
+ get_attname(parent_relid,
+ extractNotNullColumn(parent_tuple),
+ false),
+ RelationGetRelationName(child_rel)));
+
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("child table is missing constraint \"%s\"",
NameStr(parent_con->conname))));
+ }
}
systable_endscan(parent_scan);
@@ -16352,7 +16665,9 @@ RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
ScanKeyData key[3];
HeapTuple attributeTuple,
constraintTuple;
+ AttrMap *attmap;
List *connames;
+ List *nncolumns;
bool found;
bool is_partitioning;
@@ -16417,11 +16732,18 @@ RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
table_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,
+ * 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 constraints. (We cheat a bit by only checking for name matches,
* assuming that the expressions will match.)
+ *
+ * For NOT NULL columns, we store column numbers to match, mapping them in
+ * to the child rel's attribute numbers.
*/
+ attmap = build_attrmap_by_name(RelationGetDescr(child_rel),
+ RelationGetDescr(parent_rel),
+ false);
+
catalogRelation = table_open(ConstraintRelationId, RowExclusiveLock);
ScanKeyInit(&key[0],
Anum_pg_constraint_conrelid,
@@ -16431,18 +16753,28 @@ RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
true, NULL, 1, key);
connames = NIL;
+ nncolumns = NIL;
while (HeapTupleIsValid(constraintTuple = systable_getnext(scan)))
{
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
+ if (con->connoinherit)
+ continue;
+
if (con->contype == CONSTRAINT_CHECK)
connames = lappend(connames, pstrdup(NameStr(con->conname)));
+ if (con->contype == CONSTRAINT_NOTNULL)
+ {
+ AttrNumber parent_attno = extractNotNullColumn(constraintTuple);
+
+ nncolumns = lappend_int(nncolumns, attmap->attnums[parent_attno - 1]);
+ }
}
systable_endscan(scan);
- /* Now scan the child's constraints */
+ /* Now scan the child's constraints to find matches */
ScanKeyInit(&key[0],
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
@@ -16453,20 +16785,41 @@ RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
while (HeapTupleIsValid(constraintTuple = systable_getnext(scan)))
{
Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTuple);
- bool match;
+ bool match = false;
- if (con->contype != CONSTRAINT_CHECK)
- continue;
-
- match = false;
- foreach_ptr(char, chkname, connames)
+ /*
+ * Match CHECK constraints by name, not-null constraints by column
+ * number, and ignore all others.
+ */
+ if (con->contype == CONSTRAINT_CHECK)
{
- if (strcmp(NameStr(con->conname), chkname) == 0)
+ foreach_ptr(char, chkname, connames)
{
- match = true;
- break;
+ if (con->contype == CONSTRAINT_CHECK &&
+ strcmp(NameStr(con->conname), chkname) == 0)
+ {
+ match = true;
+ connames = foreach_delete_current(connames, chkname);
+ break;
+ }
}
}
+ else if (con->contype == CONSTRAINT_NOTNULL)
+ {
+ AttrNumber child_attno = extractNotNullColumn(constraintTuple);
+
+ foreach_int(prevattno, nncolumns)
+ {
+ if (prevattno == child_attno)
+ {
+ match = true;
+ nncolumns = foreach_delete_current(nncolumns, prevattno);
+ break;
+ }
+ }
+ }
+ else
+ continue;
if (match)
{
@@ -16487,6 +16840,12 @@ RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
}
}
+ /* We should have matched all constraints */
+ if (connames != NIL || nncolumns != NIL)
+ elog(ERROR, "%d unmatched constraints while removing inheritance from \"%s\" to \"%s\"",
+ list_length(connames) + list_length(nncolumns),
+ RelationGetRelationName(child_rel), RelationGetRelationName(parent_rel));
+
systable_endscan(scan);
table_close(catalogRelation, RowExclusiveLock);
@@ -19039,7 +19398,8 @@ AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel)
/*
* If no suitable index was found in the partition-to-be, create one
- * now.
+ * now. Note that if this is a PK, not-null constraints must already
+ * exist.
*/
if (!found)
{
@@ -19737,7 +20097,7 @@ ATExecDetachPartitionFinalize(Relation rel, RangeVar *name)
* DetachAddConstraintIfNeeded
* Subroutine for ATExecDetachPartition. Create a constraint that
* takes the place of the partition constraint, but avoid creating
- * a dupe if an constraint already exists which implies the needed
+ * a dupe if a constraint already exists which implies the needed
* constraint.
*/
static void
@@ -19770,8 +20130,8 @@ DetachAddConstraintIfNeeded(List **wqueue, Relation partRel)
n->initially_valid = true;
n->skip_validation = true;
/* It's a re-add, since it nominally already exists */
- ATAddCheckConstraint(wqueue, tab, partRel, n,
- true, false, true, ShareUpdateExclusiveLock);
+ ATAddCheckNNConstraint(wqueue, tab, partRel, n,
+ true, false, true, ShareUpdateExclusiveLock);
}
}
@@ -20040,6 +20400,13 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
RelationGetRelationName(partIdx))));
}
+ /*
+ * If it's a primary key, make sure the columns in the partition are
+ * NOT NULL.
+ */
+ if (parentIdx->rd_index->indisprimary)
+ verifyPartitionIndexNotNull(childInfo, partTbl);
+
/* All good -- do it */
IndexSetParentIndex(partIdx, RelationGetRelid(parentIdx));
if (OidIsValid(constraintOid))
@@ -20183,6 +20550,29 @@ validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
}
}
+/*
+ * When attaching an index as a partition of a partitioned index which is a
+ * primary key, verify that all the columns in the partition are marked NOT
+ * NULL.
+ */
+static void
+verifyPartitionIndexNotNull(IndexInfo *iinfo, Relation partition)
+{
+ for (int i = 0; i < iinfo->ii_NumIndexKeyAttrs; i++)
+ {
+ Form_pg_attribute att = TupleDescAttr(RelationGetDescr(partition),
+ iinfo->ii_IndexAttrNumbers[i] - 1);
+
+ if (!att->attnotnull)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid primary key definition"),
+ errdetail("Column \"%s\" of relation \"%s\" is not marked NOT NULL.",
+ NameStr(att->attname),
+ RelationGetRelationName(partition)));
+ }
+}
+
/*
* Return an OID list of constraints that reference the given relation
* that are marked as having a parent constraints.
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 2a6550de90..859e2191f0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -944,6 +944,10 @@ DefineDomain(CreateDomainStmt *stmt)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting NULL/NOT NULL constraints")));
+ if (constr->is_no_inherit)
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("not-null constraints for domains cannot be marked NO INHERIT"));
typNotNull = true;
nullDefined = true;
break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 9cac3c1c27..7e5df7bea4 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -436,6 +436,29 @@ makeRangeVar(char *schemaname, char *relname, int location)
return r;
}
+/*
+ * makeNotNullConstraint -
+ * creates a Constraint node for NOT NULL constraints
+ */
+Constraint *
+makeNotNullConstraint(String *colname)
+{
+ Constraint *notnull;
+
+ notnull = makeNode(Constraint);
+ notnull->contype = CONSTR_NOTNULL;
+ notnull->conname = NULL;
+ notnull->is_no_inherit = false;
+ notnull->deferrable = false;
+ notnull->initdeferred = false;
+ notnull->location = -1;
+ notnull->keys = list_make1(colname);
+ notnull->skip_validation = false;
+ notnull->initially_valid = true;
+
+ return notnull;
+}
+
/*
* makeTypeName -
* build a TypeName node for an unqualified name.
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index b913f91ff0..37b0ca2e43 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1698,6 +1698,8 @@ relation_excluded_by_constraints(PlannerInfo *root,
* Currently, attnotnull constraints must be treated as NO INHERIT unless
* this is a partitioned table. In future we might track their
* inheritance status more accurately, allowing this to be refined.
+ *
+ * XXX do we need/want to change this?
*/
include_notnull = (!rte->inh || rte->relkind == RELKIND_PARTITIONED_TABLE);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 89fdb94c23..67eb96396a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3908,12 +3908,15 @@ ColConstraint:
* or be part of a_expr NOT LIKE or similar constructs).
*/
ColConstraintElem:
- NOT NULL_P
+ NOT NULL_P opt_no_inherit
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_NOTNULL;
n->location = @1;
+ n->is_no_inherit = $3;
+ n->skip_validation = false;
+ n->initially_valid = true;
$$ = (Node *) n;
}
| NULL_P
@@ -4150,6 +4153,20 @@ ConstraintElem:
n->initially_valid = !n->skip_validation;
$$ = (Node *) n;
}
+ | NOT NULL_P ColId ConstraintAttributeSpec
+ {
+ Constraint *n = makeNode(Constraint);
+
+ n->contype = CONSTR_NOTNULL;
+ n->location = @1;
+ n->keys = list_make1(makeString($3));
+ /* no NOT VALID support yet */
+ processCASbits($4, @4, "NOT NULL",
+ NULL, NULL, NULL,
+ &n->is_no_inherit, yyscanner);
+ n->initially_valid = true;
+ $$ = (Node *) n;
+ }
| UNIQUE opt_unique_null_treatment '(' columnList opt_without_overlaps ')' opt_c_include opt_definition OptConsTableSpace
ConstraintAttributeSpec
{
@@ -4317,10 +4334,10 @@ DomainConstraintElem:
n->contype = CONSTR_NOTNULL;
n->location = @1;
n->keys = list_make1(makeString("value"));
- /* no NOT VALID support yet */
+ /* no NOT VALID, NO INHERIT support */
processCASbits($3, @3, "NOT NULL",
NULL, NULL, NULL,
- &n->is_no_inherit, yyscanner);
+ NULL, yyscanner);
n->initially_valid = true;
$$ = (Node *) n;
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 1e15ce10b4..0f324ee4e3 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -81,6 +81,7 @@ typedef struct
bool isalter; /* true if altering existing table */
List *columns; /* ColumnDef items */
List *ckconstraints; /* CHECK constraints */
+ List *nnconstraints; /* NOT NULL constraints */
List *fkconstraints; /* FOREIGN KEY constraints */
List *ixconstraints; /* index-creating constraints */
List *likeclauses; /* LIKE clauses that need post-processing */
@@ -240,6 +241,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.isalter = false;
cxt.columns = NIL;
cxt.ckconstraints = NIL;
+ cxt.nnconstraints = NIL;
cxt.fkconstraints = NIL;
cxt.ixconstraints = NIL;
cxt.likeclauses = NIL;
@@ -303,6 +305,32 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
Assert(stmt->constraints == NIL);
+ /*
+ * Before processing index constraints, which could include a primary key,
+ * we must scan all not-null constraints to propagate the is_not_null flag
+ * to each corresponding ColumnDef. This is necessary because table-level
+ * not-null constraints have not been marked in each ColumnDef, and the PK
+ * processing code needs to know whether one constraint has already been
+ * declared in order not to declare a redundant one.
+ */
+ foreach_node(Constraint, nn, cxt.nnconstraints)
+ {
+ char *colname = strVal(linitial(nn->keys));
+
+ foreach_node(ColumnDef, cd, cxt.columns)
+ {
+ /* not our column? */
+ if (strcmp(cd->colname, colname) != 0)
+ continue;
+ /* Already marked not-null? Nothing to do */
+ if (cd->is_not_null)
+ break;
+ /* Bingo, we're done for this constraint */
+ cd->is_not_null = true;
+ break;
+ }
+ }
+
/*
* Postprocess constraints that give rise to index definitions.
*/
@@ -340,6 +368,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ckconstraints;
+ stmt->nnconstraints = cxt.nnconstraints;
result = lappend(cxt.blist, stmt);
result = list_concat(result, cxt.alist);
@@ -566,7 +595,9 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
bool saw_default;
bool saw_identity;
bool saw_generated;
- ListCell *clist;
+ bool need_notnull = false;
+ bool disallow_noinherit_notnull = false;
+ Constraint *notnull_constraint = NULL;
cxt->columns = lappend(cxt->columns, column);
@@ -663,28 +694,54 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
constraint->cooked_expr = NULL;
column->constraints = lappend(column->constraints, constraint);
- constraint = makeNode(Constraint);
- constraint->contype = CONSTR_NOTNULL;
- constraint->location = -1;
- column->constraints = lappend(column->constraints, constraint);
+ /* have a not-null constraint added later */
+ need_notnull = true;
+ disallow_noinherit_notnull = true;
}
/* Process column constraints, if any... */
transformConstraintAttrs(cxt, column->constraints);
+ /*
+ * First, scan the column's constraints to see if a not-null constraint
+ * that we add must be prevented from being NO INHERIT. This should be
+ * enforced only for PRIMARY KEY, not IDENTITY or SERIAL. However, if the
+ * not-null constraint is specified as a table constraint rather than as a
+ * column constraint, AddRelationNotNullConstraints would raise an error
+ * if a NO INHERIT mismatch is found. To avoid inconsistently disallowing
+ * it in the table constraint case but not the column constraint case, we
+ * disallow it here as well. Maybe AddRelationNotNullConstraints can be
+ * improved someday, so that it doesn't complain, and then we can remove
+ * the restriction for SERIAL and IDENTITY here as well.
+ */
+ if (!disallow_noinherit_notnull)
+ {
+ foreach_node(Constraint, constraint, column->constraints)
+ {
+ switch (constraint->contype)
+ {
+ case CONSTR_IDENTITY:
+ case CONSTR_PRIMARY:
+ disallow_noinherit_notnull = true;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /* Now scan them again to do full processing */
saw_nullable = false;
saw_default = false;
saw_identity = false;
saw_generated = false;
- foreach(clist, column->constraints)
+ foreach_node(Constraint, constraint, column->constraints)
{
- Constraint *constraint = lfirst_node(Constraint, clist);
-
switch (constraint->contype)
{
case CONSTR_NULL:
- if (saw_nullable && column->is_not_null)
+ if ((saw_nullable && column->is_not_null) || need_notnull)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
@@ -696,6 +753,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
break;
case CONSTR_NOTNULL:
+ if (cxt->ispartitioned && constraint->is_no_inherit)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("not-null constraints on partitioned tables cannot be NO INHERIT"));
+
+ /* Disallow conflicting [NOT] NULL markings */
if (saw_nullable && !column->is_not_null)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -703,8 +766,52 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
column->colname, cxt->relation->relname),
parser_errposition(cxt->pstate,
constraint->location)));
- column->is_not_null = true;
- saw_nullable = true;
+
+ if (disallow_noinherit_notnull && constraint->is_no_inherit)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting NO INHERIT declarations for not-null constraints on column \"%s\"",
+ column->colname));
+
+ /*
+ * If this is the first time we see this column being marked
+ * not-null, add the constraint entry and keep track of it.
+ * Also, remove previous markings that we need one.
+ *
+ * If this is a redundant not-null specification, just check
+ * that it doesn't conflict with what was specified earlier.
+ *
+ * Any conflicts with table constraints will be further
+ * checked in AddRelationNotNullConstraints().
+ */
+ if (!column->is_not_null)
+ {
+ column->is_not_null = true;
+ saw_nullable = true;
+ need_notnull = false;
+
+ constraint->keys = list_make1(makeString(column->colname));
+ notnull_constraint = constraint;
+ cxt->nnconstraints = lappend(cxt->nnconstraints, constraint);
+ }
+ else if (notnull_constraint)
+ {
+ if (constraint->conname &&
+ notnull_constraint->conname &&
+ strcmp(notnull_constraint->conname, constraint->conname) != 0)
+ elog(ERROR, "conflicting not-null constraint names \"%s\" and \"%s\"",
+ notnull_constraint->conname, constraint->conname);
+
+ if (notnull_constraint->is_no_inherit != constraint->is_no_inherit)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting NO INHERIT declarations for not-null constraints on column \"%s\"",
+ column->colname));
+
+ if (!notnull_constraint->conname && constraint->conname)
+ notnull_constraint->conname = constraint->conname;
+ }
+
break;
case CONSTR_DEFAULT:
@@ -754,16 +861,19 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
column->identity = constraint->generated_when;
saw_identity = true;
- /* An identity column is implicitly NOT NULL */
- if (saw_nullable && !column->is_not_null)
+ /*
+ * Identity columns are always NOT NULL, but we may have a
+ * constraint already.
+ */
+ if (!saw_nullable)
+ need_notnull = true;
+ else if (!column->is_not_null)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
column->colname, cxt->relation->relname),
parser_errposition(cxt->pstate,
constraint->location)));
- column->is_not_null = true;
- saw_nullable = true;
break;
}
@@ -790,6 +900,15 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
break;
case CONSTR_PRIMARY:
+ if (saw_nullable && !column->is_not_null)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"",
+ column->colname, cxt->relation->relname),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
+ need_notnull = true;
+
if (cxt->isforeign)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -869,6 +988,17 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
constraint->location)));
}
+ /*
+ * If we need a not-null constraint for PRIMARY KEY, SERIAL or IDENTITY,
+ * and one was not explicitly specified, add one now.
+ */
+ if (need_notnull && !(saw_nullable && column->is_not_null))
+ {
+ column->is_not_null = true;
+ notnull_constraint = makeNotNullConstraint(makeString(column->colname));
+ cxt->nnconstraints = lappend(cxt->nnconstraints, notnull_constraint);
+ }
+
/*
* If needed, generate ALTER FOREIGN TABLE ALTER COLUMN statement to add
* per-column foreign data wrapper options to this column after creation.
@@ -938,6 +1068,15 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
break;
+ case CONSTR_NOTNULL:
+ if (cxt->ispartitioned && constraint->is_no_inherit)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("not-null constraints on partitioned tables cannot be NO INHERIT"));
+
+ cxt->nnconstraints = lappend(cxt->nnconstraints, constraint);
+ break;
+
case CONSTR_FOREIGN:
if (cxt->isforeign)
ereport(ERROR,
@@ -949,7 +1088,6 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
break;
case CONSTR_NULL:
- case CONSTR_NOTNULL:
case CONSTR_DEFAULT:
case CONSTR_ATTR_DEFERRABLE:
case CONSTR_ATTR_NOT_DEFERRABLE:
@@ -1053,14 +1191,10 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
continue;
/*
- * Create a new column, which is marked as NOT inherited.
- *
- * For constraints, ONLY the not-null constraint is inherited by the
- * new column definition per SQL99.
+ * Create a new column definition
*/
def = makeColumnDef(NameStr(attribute->attname), attribute->atttypid,
attribute->atttypmod, attribute->attcollation);
- def->is_not_null = attribute->attnotnull;
/*
* Add to column list
@@ -1129,14 +1263,28 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
}
}
+ /*
+ * Reproduce not-null constraints, if any, by copying them. We do this
+ * regardless of options given.
+ */
+ if (tupleDesc->constr && tupleDesc->constr->has_not_null)
+ {
+ List *lst;
+
+ lst = RelationGetNotNullConstraints(RelationGetRelid(relation), false,
+ true);
+ cxt->nnconstraints = list_concat(cxt->nnconstraints, lst);
+ }
+
/*
* We cannot yet deal with defaults, CHECK constraints, indexes, or
* statistics, since we don't yet know what column numbers the copied
* columns will have in the finished table. If any of those options are
* specified, add the LIKE clause to cxt->likeclauses so that
- * expandTableLikeClause will be called after we do know that. Also,
- * remember the relation OID so that expandTableLikeClause is certain to
- * open the same table.
+ * expandTableLikeClause will be called after we do know that.
+ *
+ * In order for this to work, we remember the relation OID so that
+ * expandTableLikeClause is certain to open the same table.
*/
if (table_like_clause->options &
(CREATE_TABLE_LIKE_DEFAULTS |
@@ -1506,8 +1654,8 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
* with the index there.
*
* Unlike transformIndexConstraint, we don't make any effort to force primary
- * key columns to be NOT NULL. The larger cloning process this is part of
- * should have cloned their NOT NULL status separately (and DefineIndex will
+ * key columns to be not-null. The larger cloning process this is part of
+ * should have cloned their not-null status separately (and DefineIndex will
* complain if that fails to happen).
*/
IndexStmt *
@@ -2066,10 +2214,10 @@ transformIndexConstraints(CreateStmtContext *cxt)
ListCell *lc;
/*
- * Run through the constraints that need to generate an index. For PRIMARY
- * KEY, mark each column as NOT NULL and create an index. For UNIQUE or
- * EXCLUDE, create an index as for PRIMARY KEY, but do not insist on NOT
- * NULL.
+ * Run through the constraints that need to generate an index, and do so.
+ *
+ * For PRIMARY KEY, this queues not-null constraints for each column, if
+ * needed.
*/
foreach(lc, cxt->ixconstraints)
{
@@ -2143,9 +2291,7 @@ transformIndexConstraints(CreateStmtContext *cxt)
}
/*
- * Now append all the IndexStmts to cxt->alist. If we generated an ALTER
- * TABLE SET NOT NULL statement to support a primary key, it's already in
- * cxt->alist.
+ * Now append all the IndexStmts to cxt->alist.
*/
cxt->alist = list_concat(cxt->alist, finalindexlist);
}
@@ -2153,18 +2299,15 @@ transformIndexConstraints(CreateStmtContext *cxt)
/*
* transformIndexConstraint
* Transform one UNIQUE, PRIMARY KEY, or EXCLUDE constraint for
- * transformIndexConstraints.
+ * transformIndexConstraints. An IndexStmt is returned.
*
- * We return an IndexStmt. For a PRIMARY KEY constraint, we additionally
- * produce not-null constraints, either by marking ColumnDefs in cxt->columns
- * as is_not_null or by adding an ALTER TABLE SET NOT NULL command to
- * cxt->alist.
+ * For a PRIMARY KEY constraint, we additionally create not-null constraints
+ * for columns that don't already have them.
*/
static IndexStmt *
transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
{
IndexStmt *index;
- List *notnullcmds = NIL;
ListCell *lc;
index = makeNode(IndexStmt);
@@ -2384,6 +2527,12 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
errdetail("Cannot create a primary key or unique constraint using such an index."),
parser_errposition(cxt->pstate, constraint->location)));
+ /* If a PK, ensure the columns get not null constraints */
+ if (constraint->contype == CONSTR_PRIMARY)
+ cxt->nnconstraints =
+ lappend(cxt->nnconstraints,
+ makeNotNullConstraint(makeString(attname)));
+
constraint->keys = lappend(constraint->keys, makeString(attname));
}
else
@@ -2422,7 +2571,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
* For UNIQUE and PRIMARY KEY, we just have a list of column names.
*
* Make sure referenced keys exist. If we are making a PRIMARY KEY index,
- * also make sure they are NOT NULL. For WITHOUT OVERLAPS constraints, we
+ * also make sure they are not-null. For WITHOUT OVERLAPS constraints, we
* make sure the last part is a range or multirange.
*/
else
@@ -2431,7 +2580,6 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
{
char *key = strVal(lfirst(lc));
bool found = false;
- bool forced_not_null = false;
ColumnDef *column = NULL;
ListCell *columns;
IndexElem *iparam;
@@ -2453,24 +2601,51 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
if (found)
{
/*
- * column is defined in the new table. For PRIMARY KEY, we
- * can apply the not-null constraint cheaply here ... unless
- * the column is marked is_from_type, in which case marking it
- * here would be ineffective (see MergeAttributes).
+ * column is defined in the new table. For CREATE TABLE with
+ * a PRIMARY KEY, we can apply the not-null constraint cheaply
+ * here. If the not-null constraint already exists, we can
+ * (albeit not so cheaply) verify that it's not a NO INHERIT
+ * constraint.
+ *
+ * Note that ALTER TABLE never needs either check, because
+ * those constraints have already been added by
+ * ATPrepAddPrimaryKey.
*/
if (constraint->contype == CONSTR_PRIMARY &&
- !column->is_from_type)
+ !cxt->isalter)
{
- column->is_not_null = true;
- forced_not_null = true;
+ if (column->is_not_null)
+ {
+ foreach_node(Constraint, nn, cxt->nnconstraints)
+ {
+ if (strcmp(strVal(linitial(nn->keys)), key) == 0)
+ {
+ if (nn->is_no_inherit)
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting NO INHERIT declaration for not-null constraint on column \"%s\"",
+ key));
+ break;
+ }
+ }
+ }
+ else
+ {
+ column->is_not_null = true;
+ cxt->nnconstraints =
+ lappend(cxt->nnconstraints,
+ makeNotNullConstraint(makeString(key)));
+ }
}
+ else if (constraint->contype == CONSTR_PRIMARY)
+ Assert(column->is_not_null);
}
else if (SystemAttributeByName(key) != NULL)
{
/*
* column will be a system column in the new table, so accept
* it. System columns can't ever be null, so no need to worry
- * about PRIMARY/not-null constraint.
+ * about PRIMARY/NOT NULL constraint.
*/
found = true;
}
@@ -2507,13 +2682,10 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
found = true;
typid = inhattr->atttypid;
- /*
- * It's tempting to set forced_not_null if the
- * parent column is already NOT NULL, but that
- * seems unsafe because the column's NOT NULL
- * marking might disappear between now and
- * execution. Do the runtime check to be safe.
- */
+ if (constraint->contype == CONSTR_PRIMARY)
+ cxt->nnconstraints =
+ lappend(cxt->nnconstraints,
+ makeNotNullConstraint(makeString(pstrdup(inhname))));
break;
}
}
@@ -2610,19 +2782,6 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
iparam->ordering = SORTBY_DEFAULT;
iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
index->indexParams = lappend(index->indexParams, iparam);
-
- /*
- * For a primary-key column, also create an item for ALTER TABLE
- * SET NOT NULL if we couldn't ensure it via is_not_null above.
- */
- if (constraint->contype == CONSTR_PRIMARY && !forced_not_null)
- {
- AlterTableCmd *notnullcmd = makeNode(AlterTableCmd);
-
- notnullcmd->subtype = AT_SetNotNull;
- notnullcmd->name = pstrdup(key);
- notnullcmds = lappend(notnullcmds, notnullcmd);
- }
}
if (constraint->without_overlaps)
@@ -2741,22 +2900,6 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
index->indexIncludingParams = lappend(index->indexIncludingParams, iparam);
}
- /*
- * If we found anything that requires run-time SET NOT NULL, build a full
- * ALTER TABLE command for that and add it to cxt->alist.
- */
- if (notnullcmds)
- {
- AlterTableStmt *alterstmt = makeNode(AlterTableStmt);
-
- alterstmt->relation = copyObject(cxt->relation);
- alterstmt->cmds = notnullcmds;
- alterstmt->objtype = OBJECT_TABLE;
- alterstmt->missing_ok = false;
-
- cxt->alist = lappend(cxt->alist, alterstmt);
- }
-
return index;
}
@@ -3395,6 +3538,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.isalter = true;
cxt.columns = NIL;
cxt.ckconstraints = NIL;
+ cxt.nnconstraints = NIL;
cxt.fkconstraints = NIL;
cxt.ixconstraints = NIL;
cxt.likeclauses = NIL;
@@ -3644,9 +3788,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
Node *istmt = (Node *) lfirst(l);
/*
- * We assume here that cxt.alist contains only IndexStmts and possibly
- * ALTER TABLE SET NOT NULL statements generated from primary key
- * constraints. We absorb the subcommands of the latter directly.
+ * We assume here that cxt.alist contains only IndexStmts generated
+ * from primary key constraints.
*/
if (IsA(istmt, IndexStmt))
{
@@ -3658,30 +3801,31 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
newcmd->def = (Node *) idxstmt;
newcmds = lappend(newcmds, newcmd);
}
- else if (IsA(istmt, AlterTableStmt))
- {
- AlterTableStmt *alterstmt = (AlterTableStmt *) istmt;
-
- newcmds = list_concat(newcmds, alterstmt->cmds);
- }
else
elog(ERROR, "unexpected stmt type %d", (int) nodeTag(istmt));
}
cxt.alist = NIL;
- /* Append any CHECK or FK constraints to the commands list */
- foreach(l, cxt.ckconstraints)
+ /* Append any CHECK, NOT NULL or FK constraints to the commands list */
+ foreach_node(Constraint, def, cxt.ckconstraints)
{
newcmd = makeNode(AlterTableCmd);
newcmd->subtype = AT_AddConstraint;
- newcmd->def = (Node *) lfirst_node(Constraint, l);
+ newcmd->def = (Node *) def;
newcmds = lappend(newcmds, newcmd);
}
- foreach(l, cxt.fkconstraints)
+ foreach_node(Constraint, def, cxt.nnconstraints)
{
newcmd = makeNode(AlterTableCmd);
newcmd->subtype = AT_AddConstraint;
- newcmd->def = (Node *) lfirst_node(Constraint, l);
+ newcmd->def = (Node *) def;
+ newcmds = lappend(newcmds, newcmd);
+ }
+ foreach_node(Constraint, def, cxt.fkconstraints)
+ {
+ newcmd = makeNode(AlterTableCmd);
+ newcmd->subtype = AT_AddConstraint;
+ newcmd->def = (Node *) def;
newcmds = lappend(newcmds, newcmd);
}
diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c
index f139e7b01e..f5a0ef2bd9 100644
--- a/src/backend/replication/logical/relation.c
+++ b/src/backend/replication/logical/relation.c
@@ -855,7 +855,7 @@ GetRelationIdentityOrPK(Relation rel)
idxoid = RelationGetReplicaIndex(rel);
if (!OidIsValid(idxoid))
- idxoid = RelationGetPrimaryKeyIndex(rel);
+ idxoid = RelationGetPrimaryKeyIndex(rel, false);
return idxoid;
}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 2177d17e27..a39068d1bf 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2516,6 +2516,28 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
conForm->connoinherit ? " NO INHERIT" : "");
break;
}
+ case CONSTRAINT_NOTNULL:
+ {
+ if (conForm->conrelid)
+ {
+ AttrNumber attnum;
+
+ attnum = extractNotNullColumn(tup);
+
+ appendStringInfo(&buf, "NOT NULL %s",
+ quote_identifier(get_attname(conForm->conrelid,
+ attnum, false)));
+ if (((Form_pg_constraint) GETSTRUCT(tup))->connoinherit)
+ appendStringInfoString(&buf, " NO INHERIT");
+ }
+ else if (conForm->contypid)
+ {
+ /* conkey is null for domain not-null constraints */
+ appendStringInfoString(&buf, "NOT NULL");
+ }
+ break;
+ }
+
case CONSTRAINT_TRIGGER:
/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 5bbb654a5d..342467fd18 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4817,18 +4817,38 @@ RelationGetIndexList(Relation relation)
result = lappend_oid(result, index->indexrelid);
/*
- * Invalid, non-unique, non-immediate or predicate indexes aren't
- * interesting for either oid indexes or replication identity indexes,
- * so don't check them.
+ * Non-unique or predicate indexes aren't interesting for either oid
+ * indexes or replication identity indexes, so don't check them.
+ * Deferred ones are not useful for replication identity either; but
+ * we do include them if they are PKs.
*/
- if (!index->indisvalid || !index->indisunique ||
- !index->indimmediate ||
+ if (!index->indisunique ||
!heap_attisnull(htup, Anum_pg_index_indpred, NULL))
continue;
- /* remember primary key index if any */
- if (index->indisprimary)
+ /*
+ * Remember primary key index, if any. For regular tables we do this
+ * only if the index is valid; but for partitioned tables, then we do
+ * it even if it's invalid.
+ *
+ * The reason for returning invalid primary keys for partitioned
+ * tables is that we need it to prevent drop of not-null constraints
+ * that may underlie such a primary key, which is only a problem for
+ * partitioned tables.
+ */
+ if (index->indisprimary &&
+ (index->indisvalid ||
+ relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
+ {
pkeyIndex = index->indexrelid;
+ pkdeferrable = !index->indimmediate;
+ }
+
+ if (!index->indimmediate)
+ continue;
+
+ if (!index->indisvalid)
+ continue;
/* remember explicitly chosen replica index */
if (index->indisreplident)
@@ -4952,10 +4972,10 @@ RelationGetStatExtList(Relation relation)
* RelationGetPrimaryKeyIndex -- get OID of the relation's primary key index
*
* Returns InvalidOid if there is no such index, or if the primary key is
- * DEFERRABLE.
+ * DEFERRABLE and the caller isn't OK with that.
*/
Oid
-RelationGetPrimaryKeyIndex(Relation relation)
+RelationGetPrimaryKeyIndex(Relation relation, bool deferrable_ok)
{
List *ilist;
@@ -4967,7 +4987,11 @@ RelationGetPrimaryKeyIndex(Relation relation)
Assert(relation->rd_indexvalid);
}
- return relation->rd_ispkdeferrable ? InvalidOid : relation->rd_pkindex;
+ if (deferrable_ok)
+ return relation->rd_pkindex;
+ else if (relation->rd_ispkdeferrable)
+ return InvalidOid;
+ return relation->rd_pkindex;
}
/*
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 9b2d34e281..33d323085f 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -83,7 +83,8 @@ static catalogid_hash *catalogIdHash = NULL;
static void flagInhTables(Archive *fout, TableInfo *tblinfo, int numTables,
InhInfo *inhinfo, int numInherits);
static void flagInhIndexes(Archive *fout, TableInfo *tblinfo, int numTables);
-static void flagInhAttrs(Archive *fout, TableInfo *tblinfo, int numTables);
+static void flagInhAttrs(Archive *fout, DumpOptions *dopt, TableInfo *tblinfo,
+ int numTables);
static int strInArray(const char *pattern, char **arr, int arr_size);
static IndxInfo *findIndexByOid(Oid oid);
@@ -204,7 +205,7 @@ getSchemaData(Archive *fout, int *numTablesPtr)
getTableAttrs(fout, tblinfo, numTables);
pg_log_info("flagging inherited columns in subtables");
- flagInhAttrs(fout, tblinfo, numTables);
+ flagInhAttrs(fout, fout->dopt, tblinfo, numTables);
pg_log_info("reading partitioning data");
getPartitioningInfo(fout);
@@ -452,7 +453,8 @@ flagInhIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
* What we need to do here is:
*
* - Detect child columns that inherit NOT NULL bits from their parents, so
- * that we needn't specify that again for the child.
+ * that we needn't specify that again for the child. (Versions >= 18 no
+ * longer need this.)
*
* - Detect child columns that have DEFAULT NULL when their parents had some
* non-null default. In this case, we make up a dummy AttrDefInfo object so
@@ -472,9 +474,8 @@ flagInhIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
* modifies tblinfo
*/
static void
-flagInhAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
+flagInhAttrs(Archive *fout, DumpOptions *dopt, TableInfo *tblinfo, int numTables)
{
- DumpOptions *dopt = fout->dopt;
int i,
j,
k;
@@ -536,7 +537,15 @@ flagInhAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
{
AttrDefInfo *parentDef = parent->attrdefs[inhAttrInd];
- foundNotNull |= parent->notnull[inhAttrInd];
+ /*
+ * Account for each parent having a not-null constraint.
+ * In versions 18 and later, we don't need this (and those
+ * didn't have NO INHERIT.)
+ */
+ if (fout->remoteVersion < 180000 &&
+ parent->notnull_constrs[inhAttrInd] != NULL)
+ foundNotNull = true;
+
foundDefault |= (parentDef != NULL &&
strcmp(parentDef->adef_expr, "NULL") != 0 &&
!parent->attgenerated[inhAttrInd]);
@@ -554,8 +563,13 @@ flagInhAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
}
}
- /* Remember if we found inherited NOT NULL */
- tbinfo->inhNotNull[j] = foundNotNull;
+ /*
+ * In versions < 18, for lack of a better system, we arbitrarily
+ * decide that a not-null constraint is not locally defined if at
+ * least one of the parents has it.
+ */
+ if (fout->remoteVersion < 180000 && foundNotNull)
+ tbinfo->notnull_islocal[j] = false;
/*
* Manufacture a DEFAULT NULL clause if necessary. This breaks
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b7b822da62..a8c141b689 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -343,6 +343,10 @@ static void getTableData(DumpOptions *dopt, TableInfo *tblinfo, int numTables, c
static void makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo);
static void buildMatViewRefreshDependencies(Archive *fout);
static void getTableDataFKConstraints(void);
+static void determineNotNullFlags(Archive *fout, PGresult *res, int r,
+ TableInfo *tbinfo, int j,
+ int i_notnull_name, int i_notnull_noinherit,
+ int i_notnull_islocal);
static char *format_function_arguments(const FuncInfo *finfo, const char *funcargs,
bool is_agg);
static char *format_function_signature(Archive *fout,
@@ -8751,7 +8755,9 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
int i_attlen;
int i_attalign;
int i_attislocal;
- int i_attnotnull;
+ int i_notnull_name;
+ int i_notnull_noinherit;
+ int i_notnull_islocal;
int i_attoptions;
int i_attcollation;
int i_attcompression;
@@ -8761,13 +8767,13 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
/*
* We want to perform just one query against pg_attribute, and then just
- * one against pg_attrdef (for DEFAULTs) and one against pg_constraint
- * (for CHECK constraints). However, we mustn't try to select every row
- * of those catalogs and then sort it out on the client side, because some
- * of the server-side functions we need would be unsafe to apply to tables
- * we don't have lock on. Hence, we build an array of the OIDs of tables
- * we care about (and now have lock on!), and use a WHERE clause to
- * constrain which rows are selected.
+ * one against pg_attrdef (for DEFAULTs) and two against pg_constraint
+ * (for CHECK constraints and for NOT NULL constraints). However, we
+ * mustn't try to select every row of those catalogs and then sort it out
+ * on the client side, because some of the server-side functions we need
+ * would be unsafe to apply to tables we don't have lock on. Hence, we
+ * build an array of the OIDs of tables we care about (and now have lock
+ * on!), and use a WHERE clause to constrain which rows are selected.
*/
appendPQExpBufferChar(tbloids, '{');
appendPQExpBufferChar(checkoids, '{');
@@ -8814,7 +8820,6 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
"a.attstattarget,\n"
"a.attstorage,\n"
"t.typstorage,\n"
- "a.attnotnull,\n"
"a.atthasdef,\n"
"a.attisdropped,\n"
"a.attlen,\n"
@@ -8831,6 +8836,30 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
"ORDER BY option_name"
"), E',\n ') AS attfdwoptions,\n");
+ /*
+ * Find out any NOT NULL markings for each column. In 18 and up we read
+ * pg_constraint to obtain the constraint name. notnull_noinherit is set
+ * according to the NO INHERIT property. For versions prior to 18, we
+ * store an empty string as the name when a constraint is marked as
+ * attnotnull (this cues dumpTableSchema to print the NOT NULL clause
+ * without a name); also, such cases are never NO INHERIT.
+ *
+ * We track in notnull_islocal whether the constraint was defined directly
+ * in this table or via an ancestor, for binary upgrade. flagInhAttrs
+ * might modify this later for servers older than 18; it's also in charge
+ * of determining the correct inhcount.
+ */
+ if (fout->remoteVersion >= 180000)
+ appendPQExpBufferStr(q,
+ "co.conname AS notnull_name,\n"
+ "co.connoinherit AS notnull_noinherit,\n"
+ "co.conislocal AS notnull_islocal,\n");
+ else
+ appendPQExpBufferStr(q,
+ "CASE WHEN a.attnotnull THEN '' ELSE NULL END AS notnull_name,\n"
+ "false AS notnull_noinherit,\n"
+ "a.attislocal AS notnull_islocal,\n");
+
if (fout->remoteVersion >= 140000)
appendPQExpBufferStr(q,
"a.attcompression AS attcompression,\n");
@@ -8865,11 +8894,25 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
"FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n"
"JOIN pg_catalog.pg_attribute a ON (src.tbloid = a.attrelid) "
"LEFT JOIN pg_catalog.pg_type t "
- "ON (a.atttypid = t.oid)\n"
- "WHERE a.attnum > 0::pg_catalog.int2\n"
- "ORDER BY a.attrelid, a.attnum",
+ "ON (a.atttypid = t.oid)\n",
tbloids->data);
+ /*
+ * In versions 18 and up, we need pg_constraint for explicit NOT NULL
+ * entries. Also, we need to know if the NOT NULL for each column is
+ * backing a primary key.
+ */
+ if (fout->remoteVersion >= 180000)
+ appendPQExpBufferStr(q,
+ " LEFT JOIN pg_catalog.pg_constraint co ON "
+ "(a.attrelid = co.conrelid\n"
+ " AND co.contype = 'n' AND "
+ "co.conkey = array[a.attnum])\n");
+
+ appendPQExpBufferStr(q,
+ "WHERE a.attnum > 0::pg_catalog.int2\n"
+ "ORDER BY a.attrelid, a.attnum");
+
res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
ntups = PQntuples(res);
@@ -8887,7 +8930,9 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
i_attlen = PQfnumber(res, "attlen");
i_attalign = PQfnumber(res, "attalign");
i_attislocal = PQfnumber(res, "attislocal");
- i_attnotnull = PQfnumber(res, "attnotnull");
+ i_notnull_name = PQfnumber(res, "notnull_name");
+ i_notnull_noinherit = PQfnumber(res, "notnull_noinherit");
+ i_notnull_islocal = PQfnumber(res, "notnull_islocal");
i_attoptions = PQfnumber(res, "attoptions");
i_attcollation = PQfnumber(res, "attcollation");
i_attcompression = PQfnumber(res, "attcompression");
@@ -8952,8 +8997,9 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
tbinfo->attcompression = (char *) pg_malloc(numatts * sizeof(char));
tbinfo->attfdwoptions = (char **) pg_malloc(numatts * sizeof(char *));
tbinfo->attmissingval = (char **) pg_malloc(numatts * sizeof(char *));
- tbinfo->notnull = (bool *) pg_malloc(numatts * sizeof(bool));
- tbinfo->inhNotNull = (bool *) pg_malloc(numatts * sizeof(bool));
+ tbinfo->notnull_constrs = (char **) pg_malloc(numatts * sizeof(char *));
+ tbinfo->notnull_noinh = (bool *) pg_malloc(numatts * sizeof(bool));
+ tbinfo->notnull_islocal = (bool *) pg_malloc(numatts * sizeof(bool));
tbinfo->attrdefs = (AttrDefInfo **) pg_malloc(numatts * sizeof(AttrDefInfo *));
hasdefaults = false;
@@ -8977,7 +9023,13 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
tbinfo->attlen[j] = atoi(PQgetvalue(res, r, i_attlen));
tbinfo->attalign[j] = *(PQgetvalue(res, r, i_attalign));
tbinfo->attislocal[j] = (PQgetvalue(res, r, i_attislocal)[0] == 't');
- tbinfo->notnull[j] = (PQgetvalue(res, r, i_attnotnull)[0] == 't');
+
+ /* Handle not-null constraint name and flags */
+ determineNotNullFlags(fout, res, r,
+ tbinfo, j,
+ i_notnull_name, i_notnull_noinherit,
+ i_notnull_islocal);
+
tbinfo->attoptions[j] = pg_strdup(PQgetvalue(res, r, i_attoptions));
tbinfo->attcollation[j] = atooid(PQgetvalue(res, r, i_attcollation));
tbinfo->attcompression[j] = *(PQgetvalue(res, r, i_attcompression));
@@ -8986,8 +9038,6 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
tbinfo->attrdefs[j] = NULL; /* fix below */
if (PQgetvalue(res, r, i_atthasdef)[0] == 't')
hasdefaults = true;
- /* these flags will be set in flagInhAttrs() */
- tbinfo->inhNotNull[j] = false;
}
if (hasdefaults)
@@ -9268,6 +9318,110 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
destroyPQExpBuffer(checkoids);
}
+/*
+ * Based on the getTableAttrs query's row corresponding to one column, set
+ * the name and flags to handle a not-null constraint for that column in
+ * the tbinfo struct.
+ *
+ * Result row 'r' is for tbinfo's attribute 'j'.
+ *
+ * There are three possibilities:
+ * 1) the column has no not-null constraints. In that case, ->notnull_constrs
+ * (the constraint name) remains NULL.
+ * 2) The column has a constraint with no name (this is the case when
+ * constraints come from pre-18 servers). In this case, ->notnull_constrs
+ * is set to the empty string; dumpTableSchema will print just "NOT NULL".
+ * 3) The column has a constraint with a known name; in that case
+ * notnull_constrs carries that name and dumpTableSchema will print
+ * "CONSTRAINT the_name NOT NULL". However, if the name is the default
+ * (table_column_not_null), there's no need to print that name in the dump,
+ * so notnull_constrs is set to the empty string and it behaves as the case
+ * above.
+ *
+ * In a child table that inherits from a parent already containing NOT NULL
+ * constraints and the columns in the child don't have their own NOT NULL
+ * declarations, we suppress printing constraints in the child: the
+ * constraints are acquired at the point where the child is attached to the
+ * parent. This is tracked in ->notnull_inh (which is set in flagInhAttrs for
+ * servers pre-18).
+ *
+ * Any of these constraints might have the NO INHERIT bit. If so we set
+ * ->notnull_noinh and NO INHERIT will be printed by dumpTableSchema.
+ *
+ * In case 3 above, the name comparison is a bit of a hack; it actually fails
+ * to do the right thing in all but the trivial case. However, the downside
+ * of getting it wrong is simply that the name is printed rather than
+ * suppressed, so it's not a big deal.
+ */
+static void
+determineNotNullFlags(Archive *fout, PGresult *res, int r,
+ TableInfo *tbinfo, int j,
+ int i_notnull_name, int i_notnull_noinherit,
+ int i_notnull_islocal)
+{
+ DumpOptions *dopt = fout->dopt;
+
+ /*
+ * notnull_noinh is straight from the query result. notnull_islocal also,
+ * though flagInhAttrs may change that one later in versions < 18.
+ */
+ tbinfo->notnull_noinh[j] = PQgetvalue(res, r, i_notnull_noinherit)[0] == 't';
+ tbinfo->notnull_islocal[j] = PQgetvalue(res, r, i_notnull_islocal)[0] == 't';
+
+ /*
+ * Determine a constraint name to use. If the column is not marked not-
+ * null, we set NULL which cues ... to do nothing. An empty string says
+ * to print an unnamed NOT NULL, and anything else is a constraint name to
+ * use.
+ */
+ if (fout->remoteVersion < 180000)
+ {
+ /*
+ * < 18 doesn't have not-null names, so an unnamed constraint is
+ * sufficient.
+ */
+ if (PQgetisnull(res, r, i_notnull_name))
+ tbinfo->notnull_constrs[j] = NULL;
+ else
+ tbinfo->notnull_constrs[j] = "";
+ }
+ else
+ {
+ if (PQgetisnull(res, r, i_notnull_name))
+ tbinfo->notnull_constrs[j] = NULL;
+ else
+ {
+ /*
+ * In binary upgrade of inheritance child tables, must have a
+ * constraint name that we can UPDATE later.
+ */
+ if (dopt->binary_upgrade &&
+ !tbinfo->ispartition &&
+ !tbinfo->notnull_islocal)
+ {
+ tbinfo->notnull_constrs[j] =
+ pstrdup(PQgetvalue(res, r, i_notnull_name));
+ }
+ else
+ {
+ char *default_name;
+
+ /* XXX should match ChooseConstraintName better */
+ default_name = psprintf("%s_%s_not_null", tbinfo->dobj.name,
+ tbinfo->attnames[j]);
+ if (strcmp(default_name,
+ PQgetvalue(res, r, i_notnull_name)) == 0)
+ tbinfo->notnull_constrs[j] = "";
+ else
+ {
+ tbinfo->notnull_constrs[j] =
+ pstrdup(PQgetvalue(res, r, i_notnull_name));
+ }
+ }
+ }
+ }
+}
+
/*
* Test whether a column should be printed as part of table's CREATE TABLE.
* Column number is zero-based.
@@ -15970,13 +16124,14 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
!tbinfo->attrdefs[j]->separate);
/*
- * Not Null constraint --- suppress if inherited, except
- * if partition, or in binary-upgrade case where that
- * won't work.
+ * Not Null constraint --- print it if it is locally
+ * defined, or if binary upgrade. (In the latter case, we
+ * reset conislocal below.)
*/
- print_notnull = (tbinfo->notnull[j] &&
- (!tbinfo->inhNotNull[j] ||
- tbinfo->ispartition || dopt->binary_upgrade));
+ print_notnull = (tbinfo->notnull_constrs[j] != NULL &&
+ (tbinfo->notnull_islocal[j] ||
+ dopt->binary_upgrade ||
+ tbinfo->ispartition));
/*
* Skip column if fully defined by reloftype, except in
@@ -16032,9 +16187,22 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
tbinfo->attrdefs[j]->adef_expr);
}
+ print_notnull = (tbinfo->notnull_constrs[j] != NULL &&
+ (tbinfo->notnull_islocal[j] ||
+ dopt->binary_upgrade ||
+ tbinfo->ispartition));
if (print_notnull)
- appendPQExpBufferStr(q, " NOT NULL");
+ {
+ if (tbinfo->notnull_constrs[j][0] == '\0')
+ appendPQExpBufferStr(q, " NOT NULL");
+ else
+ appendPQExpBuffer(q, " CONSTRAINT %s NOT NULL",
+ fmtId(tbinfo->notnull_constrs[j]));
+
+ if (tbinfo->notnull_noinh[j])
+ appendPQExpBufferStr(q, " NO INHERIT");
+ }
/* Add collation if not default for the type */
if (OidIsValid(tbinfo->attcollation[j]))
@@ -16212,6 +16380,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
tbinfo->relkind == RELKIND_PARTITIONED_TABLE))
{
bool firstitem;
+ bool firstitem_extra;
/*
* Drop any dropped columns. Merge the pg_attribute manipulations
@@ -16289,6 +16458,71 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
if (!firstitem)
appendPQExpBufferStr(q, ");\n");
+ /*
+ * Fix up not-null constraints that come from inheritance. As
+ * above, do the pg_constraint manipulations in a single SQL
+ * command. (Actually, two in special cases, if we're doing an
+ * upgrade from < 18).
+ */
+ firstitem = true;
+ firstitem_extra = true;
+ resetPQExpBuffer(extra);
+ for (j = 0; j < tbinfo->numatts; j++)
+ {
+ /*
+ * If a not-null constraint comes from inheritance, reset
+ * conislocal. The inhcount is fixed by ALTER TABLE INHERIT,
+ * below. Special hack: in versions < 18, columns with no
+ * local definition need their constraint to be matched by
+ * column number in conkeys instead of by contraint name,
+ * because the latter is not available. (We distinguish the
+ * case because the constraint name is the empty string.)
+ */
+ if (tbinfo->notnull_constrs[j] != NULL &&
+ !tbinfo->notnull_islocal[j])
+ {
+ if (tbinfo->notnull_constrs[j][0] != '\0')
+ {
+ if (firstitem)
+ {
+ appendPQExpBufferStr(q, "UPDATE pg_catalog.pg_constraint\n"
+ "SET conislocal = false\n"
+ "WHERE contype = 'n' AND conrelid = ");
+ appendStringLiteralAH(q, qualrelname, fout);
+ appendPQExpBufferStr(q, "::pg_catalog.regclass AND\n"
+ "conname IN (");
+ firstitem = false;
+ }
+ else
+ appendPQExpBufferStr(q, ", ");
+ appendStringLiteralAH(q, tbinfo->notnull_constrs[j], fout);
+ }
+ else
+ {
+ if (firstitem_extra)
+ {
+ appendPQExpBufferStr(extra, "UPDATE pg_catalog.pg_constraint\n"
+ "SET conislocal = false\n"
+ "WHERE contype = 'n' AND conrelid = ");
+ appendStringLiteralAH(extra, qualrelname, fout);
+ appendPQExpBufferStr(extra, "::pg_catalog.regclass AND\n"
+ "conkey IN (");
+ firstitem_extra = false;
+ }
+ else
+ appendPQExpBufferStr(extra, ", ");
+ appendPQExpBuffer(extra, "'{%d}'", j + 1);
+ }
+ }
+ }
+ if (!firstitem)
+ appendPQExpBufferStr(q, ");\n");
+ if (!firstitem_extra)
+ appendPQExpBufferStr(extra, ");\n");
+
+ if (extra->len > 0)
+ appendBinaryPQExpBuffer(q, extra->data, extra->len);
+
/*
* Add inherited CHECK constraints, if any.
*
@@ -16428,11 +16662,22 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
* we have to mark it separately.
*/
if (!shouldPrintColumn(dopt, tbinfo, j) &&
- tbinfo->notnull[j] && !tbinfo->inhNotNull[j])
- appendPQExpBuffer(q,
- "ALTER %sTABLE ONLY %s ALTER COLUMN %s SET NOT NULL;\n",
- foreign, qualrelname,
- fmtId(tbinfo->attnames[j]));
+ tbinfo->notnull_constrs[j] != NULL &&
+ (tbinfo->notnull_islocal[j] && !tbinfo->ispartition && !dopt->binary_upgrade))
+ {
+ /* No constraint name desired? */
+ if (tbinfo->notnull_constrs[j][0] == '\0')
+ appendPQExpBuffer(q,
+ "ALTER %sTABLE ONLY %s ALTER COLUMN %s SET NOT NULL;\n",
+ foreign, qualrelname,
+ fmtId(tbinfo->attnames[j]));
+ else
+ appendPQExpBuffer(q,
+ "ALTER %sTABLE ONLY %s ADD CONSTRAINT %s NOT NULL %s;\n",
+ foreign, qualrelname,
+ tbinfo->notnull_constrs[j],
+ fmtId(tbinfo->attnames[j]));
+ }
/*
* Dump per-column statistics information. We only issue an ALTER
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index c1552ead45..d65f558565 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -347,8 +347,12 @@ typedef struct _tableInfo
char *attcompression; /* per-attribute compression method */
char **attfdwoptions; /* per-attribute fdw options */
char **attmissingval; /* per attribute missing value */
- bool *notnull; /* not-null constraints on attributes */
- bool *inhNotNull; /* true if NOT NULL is inherited */
+ char **notnull_constrs; /* NOT NULL constraint names. If null,
+ * there isn't one on this column. If
+ * empty string, unnamed constraint
+ * (pre-v17) */
+ bool *notnull_noinh; /* NOT NULL is NO INHERIT */
+ bool *notnull_islocal; /* true if NOT NULL has local definition */
struct _attrDefInfo **attrdefs; /* DEFAULT expressions */
struct _constraintInfo *checkexprs; /* CHECK constraints */
bool needs_override; /* has GENERATED ALWAYS AS IDENTITY */
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 213904440f..aa1564cd45 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3350,8 +3350,8 @@ my %tests = (
FOR VALUES FROM (\'2006-02-01\') TO (\'2006-03-01\');',
regexp => qr/^
\QCREATE TABLE dump_test_second_schema.measurement_y2006m2 (\E\n
- \s+\Qcity_id integer DEFAULT nextval('dump_test.measurement_city_id_seq'::regclass) NOT NULL,\E\n
- \s+\Qlogdate date NOT NULL,\E\n
+ \s+\Qcity_id integer DEFAULT nextval('dump_test.measurement_city_id_seq'::regclass) CONSTRAINT measurement_city_id_not_null NOT NULL,\E\n
+ \s+\Qlogdate date CONSTRAINT measurement_logdate_not_null NOT NULL,\E\n
\s+\Qpeaktemp integer,\E\n
\s+\Qunitsales integer DEFAULT 0,\E\n
\s+\QCONSTRAINT measurement_peaktemp_check CHECK ((peaktemp >= '-460'::integer)),\E\n
@@ -3759,7 +3759,7 @@ my %tests = (
) INHERITS (dump_test.test_inheritance_parent);',
regexp => qr/^
\QCREATE TABLE dump_test.test_inheritance_child (\E\n
- \s+\Qcol1 integer,\E\n
+ \s+\Qcol1 integer NOT NULL,\E\n
\s+\QCONSTRAINT test_inheritance_child CHECK ((col2 >= 142857))\E\n
\)\n
\QINHERITS (dump_test.test_inheritance_parent);\E\n
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index bbe632cc79..5bfebad64d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3053,6 +3053,50 @@ describeOneTableDetails(const char *schemaname,
}
PQclear(result);
}
+
+ /*
+ * If verbose, print NOT NULL constraints.
+ */
+ if (verbose)
+ {
+ printfPQExpBuffer(&buf,
+ "SELECT c.conname, a.attname, c.connoinherit,\n"
+ " c.conislocal, c.coninhcount <> 0\n"
+ "FROM pg_catalog.pg_constraint c JOIN\n"
+ " pg_catalog.pg_attribute a ON\n"
+ " (a.attrelid = c.conrelid AND a.attnum = c.conkey[1])\n"
+ "WHERE c.contype = 'n' AND\n"
+ " c.conrelid = '%s'::pg_catalog.regclass\n"
+ "ORDER BY a.attnum",
+ oid);
+
+ result = PSQLexec(buf.data);
+ if (!result)
+ goto error_return;
+ else
+ tuples = PQntuples(result);
+
+ if (tuples > 0)
+ printTableAddFooter(&cont, _("Not-null constraints:"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ {
+ bool islocal = PQgetvalue(result, i, 3)[0] == 't';
+ bool inherited = PQgetvalue(result, i, 4)[0] == 't';
+
+ printfPQExpBuffer(&buf, " \"%s\" NOT NULL \"%s\"%s",
+ PQgetvalue(result, i, 0),
+ PQgetvalue(result, i, 1),
+ PQgetvalue(result, i, 2)[0] == 't' ?
+ " NO INHERIT" :
+ islocal && inherited ? _(" (local, inherited)") :
+ inherited ? _(" (inherited)") : "");
+
+ printTableAddFooter(&cont, buf.data);
+ }
+ PQclear(result);
+ }
}
/* Get view_def if table is a view or materialized view */
diff --git a/src/bin/psql/t/010_tab_completion.pl b/src/bin/psql/t/010_tab_completion.pl
index fd645896c9..f3e6c8beaa 100644
--- a/src/bin/psql/t/010_tab_completion.pl
+++ b/src/bin/psql/t/010_tab_completion.pl
@@ -39,7 +39,7 @@ $node->start;
# set up a few database objects
$node->safe_psql('postgres',
- "CREATE TABLE tab1 (c1 int primary key, c2 text);\n"
+ "CREATE TABLE tab1 (c1 int primary key constraint foo not null, c2 text);\n"
. "CREATE TABLE mytab123 (f1 int, f2 text);\n"
. "CREATE TABLE mytab246 (f1 int, f2 text);\n"
. "CREATE TABLE \"mixedName\" (f1 int, f2 text);\n"
@@ -209,21 +209,21 @@ clear_query();
# check interpretation of referenced names
check_completion(
- "alter table tab1 drop constraint \t",
+ "alter table tab1 drop constraint t\t",
qr/tab1_pkey /,
"complete index name for referenced table");
clear_query();
check_completion(
- "alter table TAB1 drop constraint \t",
+ "alter table TAB1 drop constraint t\t",
qr/tab1_pkey /,
"complete index name for referenced table, with downcasing");
clear_query();
check_completion(
- "alter table public.\"tab1\" drop constraint \t",
+ "alter table public.\"tab1\" drop constraint t\t",
qr/tab1_pkey /,
"complete index name for referenced table, with schema and quoting");
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 2abc523f5c..86436e0356 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -57,6 +57,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202411071
+#define CATALOG_VERSION_NO 202411081
#endif
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index d6a2c79129..8c278f202b 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -34,10 +34,11 @@ typedef struct RawColumnDefault
typedef struct CookedConstraint
{
- ConstrType contype; /* CONSTR_DEFAULT or CONSTR_CHECK */
+ ConstrType contype; /* CONSTR_DEFAULT, CONSTR_CHECK,
+ * CONSTR_NOTNULL */
Oid conoid; /* constr OID if created, otherwise Invalid */
char *name; /* name, or NULL if none */
- AttrNumber attnum; /* which attr (only for DEFAULT) */
+ AttrNumber attnum; /* which attr (only for NOTNULL, DEFAULT) */
Node *expr; /* transformed default or check expr */
bool skip_validation; /* skip validation? (only for CHECK) */
bool is_local; /* constraint has local (non-inherited) def */
@@ -113,6 +114,9 @@ extern List *AddRelationNewConstraints(Relation rel,
bool is_local,
bool is_internal,
const char *queryString);
+extern List *AddRelationNotNullConstraints(Relation rel,
+ List *constraints,
+ List *old_notnulls);
extern void RelationClearMissing(Relation rel);
extern void SetAttrMissing(Oid relid, char *attname, char *value);
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 35788315bc..4b4476738a 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -257,7 +257,14 @@ extern char *ChooseConstraintName(const char *name1, const char *name2,
const char *label, Oid namespaceid,
List *others);
+extern HeapTuple findNotNullConstraintAttnum(Oid relid, AttrNumber attnum);
+extern HeapTuple findNotNullConstraint(Oid relid, const char *colname);
extern HeapTuple findDomainNotNullConstraint(Oid typid);
+extern AttrNumber extractNotNullColumn(HeapTuple constrTup);
+extern bool AdjustNotNullInheritance(Oid relid, AttrNumber attnum,
+ bool is_local, bool is_no_inherit);
+extern List *RelationGetNotNullConstraints(Oid relid, bool cooked,
+ bool include_noinh);
extern void RemoveConstraintById(Oid conId);
extern void RenameConstraintById(Oid conId, const char *newname);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 0765e5c57b..028f8815d1 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -68,6 +68,7 @@ extern RelabelType *makeRelabelType(Expr *arg, Oid rtype, int32 rtypmod,
Oid rcollid, CoercionForm rformat);
extern RangeVar *makeRangeVar(char *schemaname, char *relname, int location);
+extern Constraint *makeNotNullConstraint(String *colname);
extern TypeName *makeTypeName(char *typnam);
extern TypeName *makeTypeNameFromNameList(List *names);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0d96db5638..0f9462493e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2365,7 +2365,6 @@ typedef enum AlterTableType
AT_SetNotNull, /* alter column set not null */
AT_SetExpression, /* alter column set expression */
AT_DropExpression, /* alter column drop expression */
- AT_CheckNotNull, /* check column is already marked not null */
AT_SetStatistics, /* alter column set statistics */
AT_SetOptions, /* alter column set ( options ) */
AT_ResetOptions, /* alter column reset ( options ) */
@@ -2663,10 +2662,10 @@ typedef struct VariableShowStmt
* Create Table Statement
*
* NOTE: in the raw gram.y output, ColumnDef and Constraint nodes are
- * intermixed in tableElts, and constraints is NIL. After parse analysis,
- * tableElts contains just ColumnDefs, and constraints contains just
- * Constraint nodes (in fact, only CONSTR_CHECK nodes, in the present
- * implementation).
+ * intermixed in tableElts, and constraints and nnconstraints are NIL. After
+ * parse analysis, tableElts contains just ColumnDefs, nnconstraints contains
+ * Constraint nodes of CONSTR_NOTNULL type from various sources, and
+ * constraints contains just CONSTR_CHECK Constraint nodes.
* ----------------------
*/
@@ -2681,6 +2680,7 @@ typedef struct CreateStmt
PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
+ List *nnconstraints; /* NOT NULL constraints (ditto) */
List *options; /* options from WITH clause */
OnCommitAction oncommit; /* what do we do at COMMIT? */
char *tablespacename; /* table space to use, or NULL */
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 18c32ea700..8d23959e95 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -46,7 +46,7 @@ extern void RelationClose(Relation relation);
extern List *RelationGetFKeyList(Relation relation);
extern List *RelationGetIndexList(Relation relation);
extern List *RelationGetStatExtList(Relation relation);
-extern Oid RelationGetPrimaryKeyIndex(Relation relation);
+extern Oid RelationGetPrimaryKeyIndex(Relation relation, bool deferrable_ok);
extern Oid RelationGetReplicaIndex(Relation relation);
extern List *RelationGetIndexExpressions(Relation relation);
extern List *RelationGetDummyIndexExpressions(Relation relation);
diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/test/modules/test_ddl_deparse/expected/alter_table.out
index 6daa186a84..50d0354a34 100644
--- a/src/test/modules/test_ddl_deparse/expected/alter_table.out
+++ b/src/test/modules/test_ddl_deparse/expected/alter_table.out
@@ -28,6 +28,7 @@ ALTER TABLE parent ADD COLUMN b serial;
NOTICE: DDL test: type simple, tag CREATE SEQUENCE
NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: type ADD COLUMN (and recurse) desc column b of table parent
+NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint parent_b_not_null on table parent
NOTICE: DDL test: type simple, tag ALTER SEQUENCE
ALTER TABLE parent RENAME COLUMN b TO c;
NOTICE: DDL test: type simple, tag ALTER TABLE
@@ -57,24 +58,17 @@ NOTICE: subcommand: type DETACH PARTITION desc table part2
DROP TABLE part2;
ALTER TABLE part ADD PRIMARY KEY (a);
NOTICE: DDL test: type alter table, tag ALTER TABLE
-NOTICE: subcommand: type SET NOT NULL desc column a of table part
-NOTICE: subcommand: type SET NOT NULL desc column a of table part1
+NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint part_a_not_null on table part
NOTICE: subcommand: type ADD INDEX desc index part_pkey
ALTER TABLE parent ALTER COLUMN a SET NOT NULL;
NOTICE: DDL test: type alter table, tag ALTER TABLE
-NOTICE: subcommand: type SET NOT NULL desc column a of table parent
-NOTICE: subcommand: type SET NOT NULL desc column a of table child
-NOTICE: subcommand: type SET NOT NULL desc column a of table grandchild
+NOTICE: subcommand: type SET NOT NULL (and recurse) desc constraint parent_a_not_null on table parent
ALTER TABLE parent ALTER COLUMN a DROP NOT NULL;
NOTICE: DDL test: type alter table, tag ALTER TABLE
-NOTICE: subcommand: type DROP NOT NULL desc column a of table parent
-NOTICE: subcommand: type DROP NOT NULL desc column a of table child
-NOTICE: subcommand: type DROP NOT NULL desc column a of table grandchild
+NOTICE: subcommand: type DROP NOT NULL (and recurse) desc column a of table parent
ALTER TABLE parent ALTER COLUMN a SET NOT NULL;
NOTICE: DDL test: type alter table, tag ALTER TABLE
-NOTICE: subcommand: type SET NOT NULL desc column a of table parent
-NOTICE: subcommand: type SET NOT NULL desc column a of table child
-NOTICE: subcommand: type SET NOT NULL desc column a of table grandchild
+NOTICE: subcommand: type SET NOT NULL (and recurse) desc constraint parent_a_not_null on table parent
ALTER TABLE parent ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;
NOTICE: DDL test: type simple, tag CREATE SEQUENCE
NOTICE: DDL test: type simple, tag ALTER SEQUENCE
@@ -116,6 +110,7 @@ NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: type ALTER COLUMN SET TYPE desc column c of table parent
NOTICE: subcommand: type ALTER COLUMN SET TYPE desc column c of table child
NOTICE: subcommand: type ALTER COLUMN SET TYPE desc column c of table grandchild
+NOTICE: subcommand: type (re) ADD CONSTRAINT desc constraint parent_b_not_null on table parent
NOTICE: subcommand: type (re) ADD STATS desc statistics object parent_stat
ALTER TABLE parent ALTER COLUMN c SET DEFAULT 0;
NOTICE: DDL test: type alter table, tag ALTER TABLE
diff --git a/src/test/modules/test_ddl_deparse/expected/create_table.out b/src/test/modules/test_ddl_deparse/expected/create_table.out
index 2178ce83e9..14915f661a 100644
--- a/src/test/modules/test_ddl_deparse/expected/create_table.out
+++ b/src/test/modules/test_ddl_deparse/expected/create_table.out
@@ -85,8 +85,6 @@ CREATE TABLE employees OF employee_type (
salary WITH OPTIONS DEFAULT 1000
);
NOTICE: DDL test: type simple, tag CREATE TABLE
-NOTICE: DDL test: type alter table, tag ALTER TABLE
-NOTICE: subcommand: type SET NOT NULL desc column name of table employees
NOTICE: DDL test: type simple, tag CREATE INDEX
-- Inheritance
CREATE TABLE person (
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 97cf52d133..98d237ac68 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -134,9 +134,6 @@ get_altertable_subcmdinfo(PG_FUNCTION_ARGS)
case AT_DropExpression:
strtype = "DROP EXPRESSION";
break;
- case AT_CheckNotNull:
- strtype = "CHECK NOT NULL";
- break;
case AT_SetStatistics:
strtype = "SET STATS";
break;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 332cb16917..2212c8dbb5 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1216,20 +1216,6 @@ alter table only parent alter a set not null;
ERROR: column "a" of relation "parent" contains null values
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;
-insert into parent values (NULL);
-ERROR: null value in column "a" of relation "parent" violates not-null constraint
-DETAIL: Failing row contains (null).
-alter table child alter a set not null;
-insert into child (a, b) values (NULL, 'foo');
-ERROR: null value in column "a" of relation "child" violates not-null constraint
-DETAIL: Failing row contains (null, foo).
-delete from child;
-alter table child alter a set not null;
-insert into child (a, b) values (NULL, 'foo');
-ERROR: null value in column "a" of relation "child" violates not-null constraint
-DETAIL: Failing row contains (null, foo).
drop table child;
drop table parent;
-- test setting and removing default values
@@ -3385,6 +3371,7 @@ DROP TABLE tt9;
-- Check that comments on constraints and indexes are not lost at ALTER TABLE.
CREATE TABLE comment_test (
id int,
+ constraint id_notnull_constraint not null id,
positive_col int CHECK (positive_col > 0),
indexed_col int,
CONSTRAINT comment_test_pk PRIMARY KEY (id));
@@ -3393,6 +3380,7 @@ COMMENT ON COLUMN comment_test.id IS 'Column ''id'' on comment_test';
COMMENT ON INDEX comment_test_index IS 'Simple index on comment_test';
COMMENT ON CONSTRAINT comment_test_positive_col_check ON comment_test IS 'CHECK constraint on comment_test.positive_col';
COMMENT ON CONSTRAINT comment_test_pk ON comment_test IS 'PRIMARY KEY constraint of comment_test';
+COMMENT ON CONSTRAINT id_notnull_constraint ON comment_test IS 'NOT NULL constraint of comment_test';
COMMENT ON INDEX comment_test_pk IS 'Index backing the PRIMARY KEY of comment_test';
SELECT col_description('comment_test'::regclass, 1) as comment;
comment
@@ -3412,7 +3400,8 @@ SELECT conname as constraint, obj_description(oid, 'pg_constraint') as comment F
---------------------------------+-----------------------------------------------
comment_test_pk | PRIMARY KEY constraint of comment_test
comment_test_positive_col_check | CHECK constraint on comment_test.positive_col
-(2 rows)
+ id_notnull_constraint | NOT NULL constraint of comment_test
+(3 rows)
-- Change the datatype of all the columns. ALTER TABLE is optimized to not
-- rebuild an index if the new data type is binary compatible with the old
@@ -3443,7 +3432,8 @@ SELECT conname as constraint, obj_description(oid, 'pg_constraint') as comment F
---------------------------------+-----------------------------------------------
comment_test_pk | PRIMARY KEY constraint of comment_test
comment_test_positive_col_check | CHECK constraint on comment_test.positive_col
-(2 rows)
+ id_notnull_constraint | NOT NULL constraint of comment_test
+(3 rows)
-- Check compatibility for foreign keys and comments. This is done
-- separately as rebuilding the column type of the parent leads
@@ -3859,6 +3849,9 @@ CREATE TABLE atnotnull1 ();
ALTER TABLE atnotnull1
ADD COLUMN a INT,
ALTER a SET NOT NULL;
+ALTER TABLE atnotnull1
+ ADD COLUMN b INT,
+ ADD NOT NULL b;
ALTER TABLE atnotnull1
ADD COLUMN c INT,
ADD PRIMARY KEY (c);
@@ -3867,9 +3860,14 @@ ALTER TABLE atnotnull1
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | not null | | plain | |
+ b | integer | | not null | | plain | |
c | integer | | not null | | plain | |
Indexes:
"atnotnull1_pkey" PRIMARY KEY, btree (c)
+Not-null constraints:
+ "atnotnull1_a_not_null" NOT NULL "a"
+ "atnotnull1_b_not_null" NOT NULL "b"
+ "atnotnull1_c_not_null" NOT NULL "c"
-- cannot drop column that is part of the partition key
CREATE TABLE partitioned (
@@ -4028,6 +4026,14 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg
f | 1
(1 row)
+-- check that NOT NULL NO INHERIT cannot be merged to a normal NOT NULL
+CREATE TABLE part_fail (a int NOT NULL NO INHERIT,
+ b char(2) COLLATE "C",
+ CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_fail FOR VALUES IN (2);
+ERROR: constraint "part_fail_a_not_null" conflicts with non-inherited constraint on child table "part_fail"
+DROP TABLE part_fail;
-- check that the new partition won't overlap with an existing partition
CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
@@ -4404,7 +4410,6 @@ ERROR: cannot alter inherited column "b"
-- partitions exist
ALTER TABLE ONLY list_parted2 ALTER b SET NOT NULL;
ERROR: constraint must be added to child tables too
-DETAIL: Column "b" of relation "part_2" is not already NOT NULL.
HINT: Do not specify the ONLY keyword.
ALTER TABLE ONLY list_parted2 ADD CONSTRAINT check_b CHECK (b <> 'zz');
ERROR: constraint must be added to child tables too
@@ -4424,6 +4429,8 @@ Partition of: list_parted2 FOR VALUES IN (2)
Partition constraint: ((a IS NOT NULL) AND (a = 2))
Check constraints:
"check_b" CHECK (b <> 'zz'::bpchar)
+Not-null constraints:
+ "list_parted2_b_not_null" NOT NULL "b"
-- It's alright though, if no partitions are yet created
CREATE TABLE parted_no_parts (a int) PARTITION BY LIST (a);
@@ -4436,6 +4443,12 @@ ALTER TABLE part_2 ALTER b DROP NOT NULL;
ERROR: column "b" is marked NOT NULL in parent table
ALTER TABLE part_2 DROP CONSTRAINT check_a2;
ERROR: cannot drop inherited constraint "check_a2" of relation "part_2"
+-- can't drop NOT NULL from under an invalid PK
+CREATE TABLE list_parted3 (a int NOT NULL) PARTITION BY LIST (a);
+CREATE TABLE list_parted3_1 PARTITION OF list_parted3 FOR VALUES IN (1);
+ALTER TABLE ONLY list_parted3 ADD PRIMARY KEY (a);
+ALTER TABLE ONLY list_parted3 DROP CONSTRAINT list_parted3_a_not_null;
+ERROR: column "a" is in a primary key
-- Doesn't make sense to add NO INHERIT constraints on partitioned tables
ALTER TABLE list_parted2 add constraint check_b2 check (b <> 'zz') NO INHERIT;
ERROR: cannot add NO INHERIT constraint to partitioned table "list_parted2"
@@ -4462,7 +4475,7 @@ SELECT * FROM list_parted;
(0 rows)
-- cleanup
-DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE list_parted, list_parted2, range_parted, list_parted3;
DROP TABLE fail_def_part;
DROP TABLE hash_parted;
-- more tests for certain multi-level partitioning scenarios
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index a13aafff0b..4d40a6809a 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -247,11 +247,12 @@ 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
-----------------
+ conname
+----------------------
+ clstr_tst_a_not_null
clstr_tst_con
clstr_tst_pkey
-(2 rows)
+(3 rows)
SELECT relname, relkind,
EXISTS(SELECT 1 FROM pg_class WHERE oid = c.reltoastrelid) AS hastoast
diff --git a/src/test/regress/expected/constraints.out b/src/test/regress/expected/constraints.out
index cf0b80d616..71200c90ed 100644
--- a/src/test/regress/expected/constraints.out
+++ b/src/test/regress/expected/constraints.out
@@ -6,6 +6,7 @@
-- - PRIMARY KEY clauses
-- - UNIQUE clauses
-- - EXCLUDE clauses
+-- - NOT NULL clauses
--
-- directory paths are passed to us in environment variables
\getenv abs_srcdir PG_ABS_SRCDIR
@@ -797,6 +798,530 @@ ALTER TABLE deferred_excl ADD EXCLUDE (f1 WITH =);
ERROR: could not create exclusion constraint "deferred_excl_f1_excl"
DETAIL: Key (f1)=(3) conflicts with key (f1)=(3).
DROP TABLE deferred_excl;
+-- verify constraints created for NOT NULL clauses
+CREATE TABLE notnull_tbl1 (a INTEGER NOT NULL NOT NULL);
+\d+ notnull_tbl1
+ Table "public.notnull_tbl1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Not-null constraints:
+ "notnull_tbl1_a_not_null" NOT NULL "a"
+
+-- no-op
+ALTER TABLE notnull_tbl1 ADD CONSTRAINT nn NOT NULL a;
+\d+ notnull_tbl1
+ Table "public.notnull_tbl1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Not-null constraints:
+ "notnull_tbl1_a_not_null" NOT NULL "a"
+
+-- duplicate name
+ALTER TABLE notnull_tbl1 ADD COLUMN b INT CONSTRAINT notnull_tbl1_a_not_null NOT NULL;
+ERROR: constraint "notnull_tbl1_a_not_null" for relation "notnull_tbl1" already exists
+-- DROP NOT NULL gets rid of both the attnotnull flag and the constraint itself
+ALTER TABLE notnull_tbl1 ALTER a DROP NOT NULL;
+\d+ notnull_tbl1
+ Table "public.notnull_tbl1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+
+-- SET NOT NULL puts both back
+ALTER TABLE notnull_tbl1 ALTER a SET NOT NULL;
+\d+ notnull_tbl1
+ Table "public.notnull_tbl1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Not-null constraints:
+ "notnull_tbl1_a_not_null" NOT NULL "a"
+
+-- Doing it twice doesn't create a redundant constraint
+ALTER TABLE notnull_tbl1 ALTER a SET NOT NULL;
+select conname, contype, conkey from pg_constraint where conrelid = 'notnull_tbl1'::regclass;
+ conname | contype | conkey
+-------------------------+---------+--------
+ notnull_tbl1_a_not_null | n | {1}
+(1 row)
+
+-- Using the "table constraint" syntax also works
+ALTER TABLE notnull_tbl1 ALTER a DROP NOT NULL;
+ALTER TABLE notnull_tbl1 ADD CONSTRAINT foobar NOT NULL a;
+\d+ notnull_tbl1
+ Table "public.notnull_tbl1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Not-null constraints:
+ "foobar" NOT NULL "a"
+
+DROP TABLE notnull_tbl1;
+-- Verify that constraint names and NO INHERIT are properly considered when
+-- multiple constraint are specified, either explicitly or via SERIAL/PK/etc,
+-- and that conflicting cases are rejected. Mind that table constraints
+-- handle this separately from column constraints.
+create table notnull_tbl1 (a int primary key constraint foo not null);
+\d+ notnull_tbl1
+ Table "public.notnull_tbl1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Indexes:
+ "notnull_tbl1_pkey" PRIMARY KEY, btree (a)
+Not-null constraints:
+ "foo" NOT NULL "a"
+
+create table notnull_tbl2 (a serial, constraint foo not null a);
+\d+ notnull_tbl2
+ Table "public.notnull_tbl2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+-----------------------------------------+---------+--------------+-------------
+ a | integer | | not null | nextval('notnull_tbl2_a_seq'::regclass) | plain | |
+Not-null constraints:
+ "foo" NOT NULL "a"
+
+create table notnull_tbl3 (constraint foo not null a, a int generated by default as identity);
+\d+ notnull_tbl3
+ Table "public.notnull_tbl3"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+----------------------------------+---------+--------------+-------------
+ a | integer | | not null | generated by default as identity | plain | |
+Not-null constraints:
+ "foo" NOT NULL "a"
+
+create table notnull_tbl4 (a int not null constraint foo not null);
+\d+ notnull_tbl4
+ Table "public.notnull_tbl4"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Not-null constraints:
+ "foo" NOT NULL "a"
+
+create table notnull_tbl5 (a int constraint foo not null constraint foo not null);
+\d+ notnull_tbl5
+ Table "public.notnull_tbl5"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Not-null constraints:
+ "foo" NOT NULL "a"
+
+create table notnull_tbl6 (like notnull_tbl1, constraint foo not null a);
+\d+ notnull_tbl6
+ Table "public.notnull_tbl6"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Not-null constraints:
+ "foo" NOT NULL "a"
+
+drop table notnull_tbl2, notnull_tbl3, notnull_tbl4, notnull_tbl5, notnull_tbl6;
+-- error cases:
+create table notnull_tbl_fail (a serial constraint foo not null constraint bar not null);
+ERROR: conflicting not-null constraint names "foo" and "bar"
+create table notnull_tbl_fail (a serial constraint foo not null no inherit constraint foo not null);
+ERROR: conflicting NO INHERIT declarations for not-null constraints on column "a"
+create table notnull_tbl_fail (a int constraint foo not null, constraint foo not null a no inherit);
+ERROR: conflicting NO INHERIT declaration for not-null constraint on column "a"
+create table notnull_tbl_fail (a serial constraint foo not null, constraint bar not null a);
+ERROR: conflicting not-null constraint names "foo" and "bar"
+create table notnull_tbl_fail (a serial, constraint foo not null a, constraint bar not null a);
+ERROR: conflicting not-null constraint names "foo" and "bar"
+create table notnull_tbl_fail (a serial, constraint foo not null a no inherit);
+ERROR: conflicting NO INHERIT declaration for not-null constraint on column "a"
+create table notnull_tbl_fail (a serial not null no inherit);
+ERROR: conflicting NO INHERIT declarations for not-null constraints on column "a"
+create table notnull_tbl_fail (like notnull_tbl1, constraint foo2 not null a);
+ERROR: conflicting not-null constraint names "foo" and "foo2"
+create table notnull_tbl_fail (a int primary key constraint foo not null no inherit);
+ERROR: conflicting NO INHERIT declarations for not-null constraints on column "a"
+create table notnull_tbl_fail (a int not null no inherit primary key);
+ERROR: conflicting NO INHERIT declarations for not-null constraints on column "a"
+create table notnull_tbl_fail (a int primary key, not null a no inherit);
+ERROR: conflicting NO INHERIT declaration for not-null constraint on column "a"
+create table notnull_tbl_fail (a int, primary key(a), not null a no inherit);
+ERROR: conflicting NO INHERIT declaration for not-null constraint on column "a"
+create table notnull_tbl_fail (a int generated by default as identity, constraint foo not null a no inherit);
+ERROR: conflicting NO INHERIT declaration for not-null constraint on column "a"
+create table notnull_tbl_fail (a int generated by default as identity not null no inherit);
+ERROR: conflicting NO INHERIT declarations for not-null constraints on column "a"
+drop table notnull_tbl1;
+-- NOT NULL NO INHERIT
+CREATE TABLE ATACC1 (a int, NOT NULL a NO INHERIT);
+CREATE TABLE ATACC2 () INHERITS (ATACC1);
+\d+ ATACC2
+ Table "public.atacc2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Inherits: atacc1
+
+DROP TABLE ATACC1, ATACC2;
+CREATE TABLE ATACC1 (a int);
+ALTER TABLE ATACC1 ADD NOT NULL a NO INHERIT;
+CREATE TABLE ATACC2 () INHERITS (ATACC1);
+\d+ ATACC2
+ Table "public.atacc2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Inherits: atacc1
+
+DROP TABLE ATACC1, ATACC2;
+CREATE TABLE ATACC1 (a int);
+CREATE TABLE ATACC2 () INHERITS (ATACC1);
+ALTER TABLE ATACC1 ADD NOT NULL a NO INHERIT;
+\d+ ATACC2
+ Table "public.atacc2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Inherits: atacc1
+
+CREATE TABLE ATACC3 (PRIMARY KEY (a)) INHERITS (ATACC1);
+\d+ ATACC3
+ Table "public.atacc3"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Indexes:
+ "atacc3_pkey" PRIMARY KEY, btree (a)
+Not-null constraints:
+ "atacc3_a_not_null" NOT NULL "a"
+Inherits: atacc1
+
+DROP TABLE ATACC1, ATACC2, ATACC3;
+-- NOT NULL NO INHERIT is not possible on partitioned tables
+CREATE TABLE ATACC1 (a int NOT NULL NO INHERIT) PARTITION BY LIST (a);
+ERROR: not-null constraints on partitioned tables cannot be NO INHERIT
+CREATE TABLE ATACC1 (a int, NOT NULL a NO INHERIT) PARTITION BY LIST (a);
+ERROR: not-null constraints on partitioned tables cannot be NO INHERIT
+-- it's not possible to override a no-inherit constraint with an inheritable one
+CREATE TABLE ATACC2 (a int, CONSTRAINT a_is_not_null NOT NULL a NO INHERIT);
+CREATE TABLE ATACC1 (a int);
+CREATE TABLE ATACC3 (a int) INHERITS (ATACC2);
+NOTICE: merging column "a" with inherited definition
+ALTER TABLE ATACC2 INHERIT ATACC1;
+-- can't override
+ALTER TABLE ATACC1 ADD CONSTRAINT ditto NOT NULL a;
+ERROR: cannot change NO INHERIT status of NOT NULL constraint "a_is_not_null" on relation "atacc2"
+-- dropping the NO INHERIT constraint allows this to work
+ALTER TABLE ATACC2 DROP CONSTRAINT a_is_not_null;
+ALTER TABLE ATACC1 ADD CONSTRAINT ditto NOT NULL a;
+\d+ ATACC3
+ Table "public.atacc3"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Not-null constraints:
+ "ditto" NOT NULL "a" (inherited)
+Inherits: atacc2
+
+DROP TABLE ATACC1, ATACC2, ATACC3;
+-- Can't have two constraints with the same name
+CREATE TABLE notnull_tbl2 (a INTEGER CONSTRAINT blah NOT NULL, b INTEGER CONSTRAINT blah NOT NULL);
+ERROR: constraint "blah" for relation "notnull_tbl2" already exists
+-- can't drop not-null in primary key
+CREATE TABLE notnull_tbl2 (a INTEGER PRIMARY KEY);
+ALTER TABLE notnull_tbl2 ALTER a DROP NOT NULL;
+ERROR: column "a" is in a primary key
+DROP TABLE notnull_tbl2;
+CREATE TABLE notnull_tbl3 (a INTEGER NOT NULL, CHECK (a IS NOT NULL));
+ALTER TABLE notnull_tbl3 ALTER A DROP NOT NULL;
+ALTER TABLE notnull_tbl3 ADD b int, ADD CONSTRAINT pk PRIMARY KEY (a, b);
+\d notnull_tbl3
+ Table "public.notnull_tbl3"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | not null |
+ b | integer | | not null |
+Indexes:
+ "pk" PRIMARY KEY, btree (a, b)
+Check constraints:
+ "notnull_tbl3_a_check" CHECK (a IS NOT NULL)
+
+ALTER TABLE notnull_tbl3 DROP CONSTRAINT pk;
+\d notnull_tbl3
+ Table "public.notnull_tbl3"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | not null |
+ b | integer | | not null |
+Check constraints:
+ "notnull_tbl3_a_check" CHECK (a IS NOT NULL)
+
+-- Primary keys cause not-null constraints to be created.
+CREATE TABLE cnn_pk (a int, b int);
+CREATE TABLE cnn_pk_child () INHERITS (cnn_pk);
+ALTER TABLE cnn_pk ADD CONSTRAINT cnn_primarykey PRIMARY KEY (b);
+\d+ cnn_pk*
+ Table "public.cnn_pk"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+ b | integer | | not null | | plain | |
+Indexes:
+ "cnn_primarykey" PRIMARY KEY, btree (b)
+Not-null constraints:
+ "cnn_pk_b_not_null" NOT NULL "b"
+Child tables: cnn_pk_child
+
+ Table "public.cnn_pk_child"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+ b | integer | | not null | | plain | |
+Not-null constraints:
+ "cnn_pk_b_not_null" NOT NULL "b" (inherited)
+Inherits: cnn_pk
+
+ALTER TABLE cnn_pk DROP CONSTRAINT cnn_primarykey;
+\d+ cnn_pk*
+ Table "public.cnn_pk"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+ b | integer | | not null | | plain | |
+Not-null constraints:
+ "cnn_pk_b_not_null" NOT NULL "b"
+Child tables: cnn_pk_child
+
+ Table "public.cnn_pk_child"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+ b | integer | | not null | | plain | |
+Not-null constraints:
+ "cnn_pk_b_not_null" NOT NULL "b" (inherited)
+Inherits: cnn_pk
+
+DROP TABLE cnn_pk, cnn_pk_child;
+-- As above, but create the primary key ahead of time
+CREATE TABLE cnn_pk (a int, b int, CONSTRAINT cnn_primarykey PRIMARY KEY (b));
+CREATE TABLE cnn_pk_child () INHERITS (cnn_pk);
+\d+ cnn_pk*
+ Table "public.cnn_pk"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+ b | integer | | not null | | plain | |
+Indexes:
+ "cnn_primarykey" PRIMARY KEY, btree (b)
+Not-null constraints:
+ "cnn_pk_b_not_null" NOT NULL "b"
+Child tables: cnn_pk_child
+
+ Table "public.cnn_pk_child"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+ b | integer | | not null | | plain | |
+Not-null constraints:
+ "cnn_pk_b_not_null" NOT NULL "b" (inherited)
+Inherits: cnn_pk
+
+ALTER TABLE cnn_pk DROP CONSTRAINT cnn_primarykey;
+\d+ cnn_pk*
+ Table "public.cnn_pk"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+ b | integer | | not null | | plain | |
+Not-null constraints:
+ "cnn_pk_b_not_null" NOT NULL "b"
+Child tables: cnn_pk_child
+
+ Table "public.cnn_pk_child"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+ b | integer | | not null | | plain | |
+Not-null constraints:
+ "cnn_pk_b_not_null" NOT NULL "b" (inherited)
+Inherits: cnn_pk
+
+DROP TABLE cnn_pk, cnn_pk_child;
+-- As above, but create the primary key using a UNIQUE index
+CREATE TABLE cnn_pk (a int, b int);
+CREATE UNIQUE INDEX cnn_uq ON cnn_pk (b);
+CREATE TABLE cnn_pk_child () INHERITS (cnn_pk);
+ALTER TABLE cnn_pk ADD CONSTRAINT cnn_primarykey PRIMARY KEY USING INDEX cnn_uq;
+NOTICE: ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index "cnn_uq" to "cnn_primarykey"
+\d+ cnn_pk*
+ Table "public.cnn_pk"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+ b | integer | | not null | | plain | |
+Indexes:
+ "cnn_primarykey" PRIMARY KEY, btree (b)
+Not-null constraints:
+ "cnn_pk_b_not_null" NOT NULL "b"
+Child tables: cnn_pk_child
+
+ Table "public.cnn_pk_child"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+ b | integer | | not null | | plain | |
+Not-null constraints:
+ "cnn_pk_b_not_null" NOT NULL "b" (inherited)
+Inherits: cnn_pk
+
+DROP TABLE cnn_pk, cnn_pk_child;
+-- Unique constraints don't give raise to not-null constraints, however.
+create table cnn_uq (a int);
+alter table cnn_uq add unique (a);
+\d+ cnn_uq
+ Table "public.cnn_uq"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Indexes:
+ "cnn_uq_a_key" UNIQUE CONSTRAINT, btree (a)
+
+drop table cnn_uq;
+create table cnn_uq (a int);
+create unique index cnn_uq_idx on cnn_uq (a);
+alter table cnn_uq add unique using index cnn_uq_idx;
+\d+ cnn_uq
+ Table "public.cnn_uq"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Indexes:
+ "cnn_uq_idx" UNIQUE CONSTRAINT, btree (a)
+
+-- Ensure partitions are scanned for null values when adding a PK
+create table cnn2_parted(a int) partition by list (a);
+create table cnn_part1 partition of cnn2_parted for values in (1, null);
+insert into cnn_part1 values (null);
+alter table cnn2_parted add primary key (a);
+ERROR: column "a" of relation "cnn_part1" contains null values
+drop table cnn2_parted;
+-- columns in regular and LIKE inheritance should be marked not-nullable
+-- for primary keys, even if those are deferred
+CREATE TABLE notnull_tbl4 (a INTEGER PRIMARY KEY INITIALLY DEFERRED);
+CREATE TABLE notnull_tbl4_lk (LIKE notnull_tbl4);
+CREATE TABLE notnull_tbl4_lk2 (LIKE notnull_tbl4 INCLUDING INDEXES);
+CREATE TABLE notnull_tbl4_lk3 (LIKE notnull_tbl4 INCLUDING INDEXES, NOT NULL a);
+ALTER TABLE notnull_tbl4_lk3 RENAME CONSTRAINT notnull_tbl4_a_not_null TO a_nn;
+CREATE TABLE notnull_tbl4_cld () INHERITS (notnull_tbl4);
+CREATE TABLE notnull_tbl4_cld2 (PRIMARY KEY (a) DEFERRABLE) INHERITS (notnull_tbl4);
+CREATE TABLE notnull_tbl4_cld3 (PRIMARY KEY (a) DEFERRABLE, CONSTRAINT a_nn NOT NULL a) INHERITS (notnull_tbl4);
+\d+ notnull_tbl4
+ Table "public.notnull_tbl4"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Indexes:
+ "notnull_tbl4_pkey" PRIMARY KEY, btree (a) DEFERRABLE INITIALLY DEFERRED
+Not-null constraints:
+ "notnull_tbl4_a_not_null" NOT NULL "a"
+Child tables: notnull_tbl4_cld,
+ notnull_tbl4_cld2,
+ notnull_tbl4_cld3
+
+\d+ notnull_tbl4_lk
+ Table "public.notnull_tbl4_lk"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Not-null constraints:
+ "notnull_tbl4_a_not_null" NOT NULL "a"
+
+\d+ notnull_tbl4_lk2
+ Table "public.notnull_tbl4_lk2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Indexes:
+ "notnull_tbl4_lk2_pkey" PRIMARY KEY, btree (a) DEFERRABLE INITIALLY DEFERRED
+Not-null constraints:
+ "notnull_tbl4_a_not_null" NOT NULL "a"
+
+\d+ notnull_tbl4_lk3
+ Table "public.notnull_tbl4_lk3"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Indexes:
+ "notnull_tbl4_lk3_pkey" PRIMARY KEY, btree (a) DEFERRABLE INITIALLY DEFERRED
+Not-null constraints:
+ "a_nn" NOT NULL "a"
+
+\d+ notnull_tbl4_cld
+ Table "public.notnull_tbl4_cld"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Not-null constraints:
+ "notnull_tbl4_a_not_null" NOT NULL "a" (inherited)
+Inherits: notnull_tbl4
+
+\d+ notnull_tbl4_cld2
+ Table "public.notnull_tbl4_cld2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Indexes:
+ "notnull_tbl4_cld2_pkey" PRIMARY KEY, btree (a) DEFERRABLE
+Not-null constraints:
+ "notnull_tbl4_cld2_a_not_null" NOT NULL "a" (local, inherited)
+Inherits: notnull_tbl4
+
+\d+ notnull_tbl4_cld3
+ Table "public.notnull_tbl4_cld3"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Indexes:
+ "notnull_tbl4_cld3_pkey" PRIMARY KEY, btree (a) DEFERRABLE
+Not-null constraints:
+ "a_nn" NOT NULL "a" (local, inherited)
+Inherits: notnull_tbl4
+
+-- leave these tables around for pg_upgrade testing
+-- It's possible to remove a constraint from parents without affecting children
+CREATE TABLE notnull_tbl5 (a int CONSTRAINT ann NOT NULL,
+ b int CONSTRAINT bnn NOT NULL);
+CREATE TABLE notnull_tbl5_child () INHERITS (notnull_tbl5);
+ALTER TABLE ONLY notnull_tbl5 DROP CONSTRAINT ann;
+ALTER TABLE ONLY notnull_tbl5 ALTER b DROP NOT NULL;
+\d+ notnull_tbl5_child
+ Table "public.notnull_tbl5_child"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+ b | integer | | not null | | plain | |
+Not-null constraints:
+ "ann" NOT NULL "a"
+ "bnn" NOT NULL "b"
+Inherits: notnull_tbl5
+
+CREATE TABLE notnull_tbl6 (a int CONSTRAINT ann NOT NULL,
+ b int CONSTRAINT bnn NOT NULL, check (a > 0)) PARTITION BY LIST (a);
+CREATE TABLE notnull_tbl6_1 PARTITION OF notnull_tbl6 FOR VALUES IN (1);
+ALTER TABLE ONLY notnull_tbl6 DROP CONSTRAINT ann;
+ALTER TABLE ONLY notnull_tbl6 ALTER b DROP NOT NULL;
+\d+ notnull_tbl6_1
+ Table "public.notnull_tbl6_1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+ b | integer | | not null | | plain | |
+Partition of: notnull_tbl6 FOR VALUES IN (1)
+Partition constraint: ((a IS NOT NULL) AND (a = 1))
+Check constraints:
+ "notnull_tbl6_a_check" CHECK (a > 0)
+Not-null constraints:
+ "ann" NOT NULL "a"
+ "bnn" NOT NULL "b"
+
-- Comments
-- Setup a low-level role to enforce non-superuser checks.
CREATE ROLE regress_constraint_comments;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 57a24050ab..76604705a9 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -771,21 +771,23 @@ CREATE TABLE part_b PARTITION OF parted (
NOTICE: merging constraint "check_a" with inherited definition
-- conislocal should be false for any merged constraints, true otherwise
SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass ORDER BY coninhcount DESC, conname;
- conname | conislocal | coninhcount
----------+------------+-------------
- check_a | f | 1
- check_b | t | 0
-(2 rows)
+ conname | conislocal | coninhcount
+-------------------+------------+-------------
+ check_a | f | 1
+ part_b_b_not_null | t | 1
+ check_b | t | 0
+(3 rows)
-- Once check_b is added to the parent, it should be made non-local for part_b
ALTER TABLE parted ADD CONSTRAINT check_b CHECK (b >= 0);
NOTICE: merging constraint "check_b" with inherited definition
SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass ORDER BY coninhcount DESC, conname;
- conname | conislocal | coninhcount
----------+------------+-------------
- check_a | f | 1
- check_b | f | 1
-(2 rows)
+ conname | conislocal | coninhcount
+-------------------+------------+-------------
+ check_a | f | 1
+ check_b | f | 1
+ part_b_b_not_null | t | 1
+(3 rows)
-- Neither check_a nor check_b are droppable from part_b
ALTER TABLE part_b DROP CONSTRAINT check_a;
@@ -797,9 +799,10 @@ ERROR: cannot drop inherited constraint "check_b" of relation "part_b"
-- be local constraints.
ALTER TABLE parted DROP CONSTRAINT check_a, DROP CONSTRAINT check_b;
SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass ORDER BY coninhcount DESC, conname;
- conname | conislocal | coninhcount
----------+------------+-------------
-(0 rows)
+ conname | conislocal | coninhcount
+-------------------+------------+-------------
+ part_b_b_not_null | t | 1
+(1 row)
-- specify PARTITION BY for a partition
CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);
@@ -863,6 +866,8 @@ drop table test_part_coll_posix;
b | integer | | not null | 1 | plain | |
Partition of: parted FOR VALUES IN ('b')
Partition constraint: ((a IS NOT NULL) AND (a = 'b'::text))
+Not-null constraints:
+ "part_b_b_not_null" NOT NULL "b" (local, inherited)
-- Both partition bound and partition key in describe output
\d+ part_c
@@ -874,6 +879,8 @@ Partition constraint: ((a IS NOT NULL) AND (a = 'b'::text))
Partition of: parted FOR VALUES IN ('c')
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
Partition key: RANGE (b)
+Not-null constraints:
+ "part_c_b_not_null" NOT NULL "b" (local, inherited)
Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
-- a level-2 partition's constraint will include the parent's expressions
@@ -885,6 +892,8 @@ Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
b | integer | | not null | 0 | plain | |
Partition of: part_c FOR VALUES FROM (1) TO (10)
Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
+Not-null constraints:
+ "part_c_b_not_null" NOT NULL "b" (inherited)
-- Show partition count in the parent's describe output
-- Tempted to include \d+ output listing partitions with bound info but
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index 6bfc6d040f..d091da5a1e 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -348,6 +348,8 @@ CREATE TABLE ctlt12_storage (LIKE ctlt1 INCLUDING STORAGE, LIKE ctlt2 INCLUDING
a | text | | not null | | main | |
b | text | | | | extended | |
c | text | | | | external | |
+Not-null constraints:
+ "ctlt1_a_not_null" NOT NULL "a"
CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDING COMMENTS);
\d+ ctlt12_comments
@@ -357,6 +359,8 @@ CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDIN
a | text | | not null | | extended | | A
b | text | | | | extended | | B
c | text | | | | extended | | C
+Not-null constraints:
+ "ctlt1_a_not_null" NOT NULL "a"
CREATE TABLE ctlt1_inh (LIKE ctlt1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (ctlt1);
NOTICE: merging column "a" with inherited definition
@@ -370,6 +374,8 @@ NOTICE: merging constraint "ctlt1_a_check" with inherited definition
b | text | | | | extended | | B
Check constraints:
"ctlt1_a_check" CHECK (length(a) > 2)
+Not-null constraints:
+ "ctlt1_a_not_null" NOT NULL "a" (local, inherited)
Inherits: ctlt1
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt1_inh'::regclass;
@@ -391,6 +397,8 @@ Check constraints:
"ctlt1_a_check" CHECK (length(a) > 2)
"ctlt3_a_check" CHECK (length(a) < 5)
"ctlt3_c_check" CHECK (length(c) < 7)
+Not-null constraints:
+ "ctlt1_a_not_null" NOT NULL "a" (inherited)
Inherits: ctlt1,
ctlt3
@@ -409,6 +417,8 @@ Check constraints:
"ctlt1_a_check" CHECK (length(a) > 2)
"ctlt3_a_check" CHECK (length(a) < 5)
"ctlt3_c_check" CHECK (length(c) < 7)
+Not-null constraints:
+ "ctlt1_a_not_null" NOT NULL "a" (inherited)
Inherits: ctlt1
SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 'ctlt13_like'::regclass;
@@ -433,6 +443,8 @@ Check constraints:
Statistics objects:
"public.ctlt_all_a_b_stat" ON a, b FROM ctlt_all
"public.ctlt_all_expr_stat" ON (a || b) FROM ctlt_all
+Not-null constraints:
+ "ctlt1_a_not_null" NOT NULL "a"
SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 'ctlt_all'::regclass ORDER BY c.relname, objsubid;
relname | objsubid | description
@@ -473,6 +485,8 @@ Check constraints:
Statistics objects:
"public.pg_attrdef_a_b_stat" ON a, b FROM public.pg_attrdef
"public.pg_attrdef_expr_stat" ON (a || b) FROM public.pg_attrdef
+Not-null constraints:
+ "ctlt1_a_not_null" NOT NULL "a"
DROP TABLE public.pg_attrdef;
-- Check that LIKE isn't confused when new table masks the old, either
@@ -495,20 +509,28 @@ Check constraints:
Statistics objects:
"ctl_schema.ctlt1_a_b_stat" ON a, b FROM ctlt1
"ctl_schema.ctlt1_expr_stat" ON (a || b) FROM ctlt1
+Not-null constraints:
+ "ctlt1_a_not_null" NOT NULL "a"
ROLLBACK;
DROP TABLE ctlt1, ctlt2, ctlt3, ctlt4, ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctla, ctlb CASCADE;
NOTICE: drop cascades to table inhe
-- LIKE must respect NO INHERIT property of constraints
-CREATE TABLE noinh_con_copy (a int CHECK (a > 0) NO INHERIT);
+CREATE TABLE noinh_con_copy (a int CHECK (a > 0) NO INHERIT, b int not null,
+ c int not null no inherit);
CREATE TABLE noinh_con_copy1 (LIKE noinh_con_copy INCLUDING CONSTRAINTS);
-\d noinh_con_copy1
- Table "public.noinh_con_copy1"
- Column | Type | Collation | Nullable | Default
---------+---------+-----------+----------+---------
- a | integer | | |
+\d+ noinh_con_copy1
+ Table "public.noinh_con_copy1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+ b | integer | | not null | | plain | |
+ c | integer | | not null | | plain | |
Check constraints:
"noinh_con_copy_a_check" CHECK (a > 0) NO INHERIT
+Not-null constraints:
+ "noinh_con_copy_b_not_null" NOT NULL "b"
+ "noinh_con_copy_c_not_null" NOT NULL "c" NO INHERIT
-- fail, as partitioned tables don't allow NO INHERIT constraints
CREATE TABLE noinh_con_copy1_parted (LIKE noinh_con_copy INCLUDING ALL)
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index 6ed50fdcfa..cce49e509a 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -742,6 +742,8 @@ COMMENT ON COLUMN ft1.c1 IS 'ft1.c1';
Check constraints:
"ft1_c2_check" CHECK (c2 <> ''::text)
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
+Not-null constraints:
+ "ft1_c1_not_null" NOT NULL "c1"
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
@@ -864,6 +866,9 @@ ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 SET STORAGE PLAIN;
Check constraints:
"ft1_c2_check" CHECK (c2 <> ''::text)
"ft1_c3_check" CHECK (c3 >= '01-01-1994'::date AND c3 <= '01-31-1994'::date)
+Not-null constraints:
+ "ft1_c1_not_null" NOT NULL "c1"
+ "ft1_c6_not_null" NOT NULL "c6"
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
@@ -1409,6 +1414,8 @@ CREATE FOREIGN TABLE ft2 () INHERITS (fd_pt1)
c1 | integer | | not null | | plain | |
c2 | text | | | | extended | |
c3 | date | | | | plain | |
+Not-null constraints:
+ "fd_pt1_c1_not_null" NOT NULL "c1"
Child tables: ft2, FOREIGN
\d+ ft2
@@ -1418,6 +1425,8 @@ Child tables: ft2, FOREIGN
c1 | integer | | not null | | | plain | |
c2 | text | | | | | extended | |
c3 | date | | | | | plain | |
+Not-null constraints:
+ "fd_pt1_c1_not_null" NOT NULL "c1" (inherited)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
@@ -1430,6 +1439,8 @@ DROP FOREIGN TABLE ft2;
c1 | integer | | not null | | plain | |
c2 | text | | | | extended | |
c3 | date | | | | plain | |
+Not-null constraints:
+ "fd_pt1_c1_not_null" NOT NULL "c1"
CREATE FOREIGN TABLE ft2 (
c1 integer NOT NULL,
@@ -1443,6 +1454,8 @@ CREATE FOREIGN TABLE ft2 (
c1 | integer | | not null | | | plain | |
c2 | text | | | | | extended | |
c3 | date | | | | | plain | |
+Not-null constraints:
+ "ft2_c1_not_null" NOT NULL "c1"
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
@@ -1454,6 +1467,8 @@ ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
c1 | integer | | not null | | plain | |
c2 | text | | | | extended | |
c3 | date | | | | plain | |
+Not-null constraints:
+ "fd_pt1_c1_not_null" NOT NULL "c1"
Child tables: ft2, FOREIGN
\d+ ft2
@@ -1463,6 +1478,8 @@ Child tables: ft2, FOREIGN
c1 | integer | | not null | | | plain | |
c2 | text | | | | | extended | |
c3 | date | | | | | plain | |
+Not-null constraints:
+ "ft2_c1_not_null" NOT NULL "c1" (local, inherited)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
@@ -1484,6 +1501,8 @@ NOTICE: merging column "c3" with inherited definition
c1 | integer | | not null | | | plain | |
c2 | text | | | | | extended | |
c3 | date | | | | | plain | |
+Not-null constraints:
+ "ft2_c1_not_null" NOT NULL "c1" (local, inherited)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
@@ -1497,6 +1516,8 @@ Child tables: ct3,
c1 | integer | | not null | | plain | |
c2 | text | | | | extended | |
c3 | date | | | | plain | |
+Not-null constraints:
+ "ft2_c1_not_null" NOT NULL "c1" (inherited)
Inherits: ft2
\d+ ft3
@@ -1506,6 +1527,8 @@ Inherits: ft2
c1 | integer | | not null | | | plain | |
c2 | text | | | | | extended | |
c3 | date | | | | | plain | |
+Not-null constraints:
+ "ft3_c1_not_null" NOT NULL "c1" (local, inherited)
Server: s0
Inherits: ft2
@@ -1527,6 +1550,9 @@ ALTER TABLE fd_pt1 ADD COLUMN c8 integer;
c6 | integer | | | | plain | |
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
+Not-null constraints:
+ "fd_pt1_c1_not_null" NOT NULL "c1"
+ "fd_pt1_c7_not_null" NOT NULL "c7"
Child tables: ft2, FOREIGN
\d+ ft2
@@ -1541,6 +1567,9 @@ Child tables: ft2, FOREIGN
c6 | integer | | | | | plain | |
c7 | integer | | not null | | | plain | |
c8 | integer | | | | | plain | |
+Not-null constraints:
+ "ft2_c1_not_null" NOT NULL "c1" (local, inherited)
+ "fd_pt1_c7_not_null" NOT NULL "c7" (inherited)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
@@ -1559,6 +1588,9 @@ Child tables: ct3,
c6 | integer | | | | plain | |
c7 | integer | | not null | | plain | |
c8 | integer | | | | plain | |
+Not-null constraints:
+ "ft2_c1_not_null" NOT NULL "c1" (inherited)
+ "fd_pt1_c7_not_null" NOT NULL "c7" (inherited)
Inherits: ft2
\d+ ft3
@@ -1573,6 +1605,9 @@ Inherits: ft2
c6 | integer | | | | | plain | |
c7 | integer | | not null | | | plain | |
c8 | integer | | | | | plain | |
+Not-null constraints:
+ "ft3_c1_not_null" NOT NULL "c1" (local, inherited)
+ "fd_pt1_c7_not_null" NOT NULL "c7" (inherited)
Server: s0
Inherits: ft2
@@ -1601,6 +1636,9 @@ ALTER TABLE fd_pt1 ALTER COLUMN c8 SET STORAGE EXTERNAL;
c6 | integer | | not null | | plain | |
c7 | integer | | | | plain | |
c8 | text | | | | external | |
+Not-null constraints:
+ "fd_pt1_c1_not_null" NOT NULL "c1"
+ "fd_pt1_c6_not_null" NOT NULL "c6"
Child tables: ft2, FOREIGN
\d+ ft2
@@ -1615,6 +1653,9 @@ Child tables: ft2, FOREIGN
c6 | integer | | not null | | | plain | |
c7 | integer | | | | | plain | |
c8 | text | | | | | external | |
+Not-null constraints:
+ "ft2_c1_not_null" NOT NULL "c1" (local, inherited)
+ "fd_pt1_c6_not_null" NOT NULL "c6" (inherited)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
@@ -1634,6 +1675,8 @@ ALTER TABLE fd_pt1 DROP COLUMN c8;
c1 | integer | | not null | | plain | 10000 |
c2 | text | | | | extended | |
c3 | date | | | | plain | |
+Not-null constraints:
+ "fd_pt1_c1_not_null" NOT NULL "c1"
Child tables: ft2, FOREIGN
\d+ ft2
@@ -1643,6 +1686,8 @@ Child tables: ft2, FOREIGN
c1 | integer | | not null | | | plain | 10000 |
c2 | text | | | | | extended | |
c3 | date | | | | | plain | |
+Not-null constraints:
+ "ft2_c1_not_null" NOT NULL "c1" (local, inherited)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
@@ -1657,11 +1702,12 @@ SELECT relname, conname, contype, conislocal, coninhcount, connoinherit
FROM pg_class AS pc JOIN pg_constraint AS pgc ON (conrelid = pc.oid)
WHERE pc.relname = 'fd_pt1'
ORDER BY 1,2;
- relname | conname | contype | conislocal | coninhcount | connoinherit
----------+------------+---------+------------+-------------+--------------
- fd_pt1 | fd_pt1chk1 | c | t | 0 | t
- fd_pt1 | fd_pt1chk2 | c | t | 0 | f
-(2 rows)
+ relname | conname | contype | conislocal | coninhcount | connoinherit
+---------+--------------------+---------+------------+-------------+--------------
+ fd_pt1 | fd_pt1_c1_not_null | n | t | 0 | f
+ fd_pt1 | fd_pt1chk1 | c | t | 0 | t
+ fd_pt1 | fd_pt1chk2 | c | t | 0 | f
+(3 rows)
-- child does not inherit NO INHERIT constraints
\d+ fd_pt1
@@ -1674,6 +1720,8 @@ SELECT relname, conname, contype, conislocal, coninhcount, connoinherit
Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
+Not-null constraints:
+ "fd_pt1_c1_not_null" NOT NULL "c1"
Child tables: ft2, FOREIGN
\d+ ft2
@@ -1685,6 +1733,8 @@ Child tables: ft2, FOREIGN
c3 | date | | | | | plain | |
Check constraints:
"fd_pt1chk2" CHECK (c2 <> ''::text)
+Not-null constraints:
+ "ft2_c1_not_null" NOT NULL "c1" (local, inherited)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
@@ -1721,6 +1771,8 @@ ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
Check constraints:
"fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
"fd_pt1chk2" CHECK (c2 <> ''::text)
+Not-null constraints:
+ "fd_pt1_c1_not_null" NOT NULL "c1"
Child tables: ft2, FOREIGN
\d+ ft2
@@ -1732,6 +1784,8 @@ Child tables: ft2, FOREIGN
c3 | date | | | | | plain | |
Check constraints:
"fd_pt1chk2" CHECK (c2 <> ''::text)
+Not-null constraints:
+ "ft2_c1_not_null" NOT NULL "c1" (local, inherited)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
@@ -1751,6 +1805,8 @@ ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk3 CHECK (c2 <> '') NOT VALID;
c3 | date | | | | plain | |
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text) NOT VALID
+Not-null constraints:
+ "fd_pt1_c1_not_null" NOT NULL "c1"
Child tables: ft2, FOREIGN
\d+ ft2
@@ -1763,6 +1819,8 @@ Child tables: ft2, FOREIGN
Check constraints:
"fd_pt1chk2" CHECK (c2 <> ''::text)
"fd_pt1chk3" CHECK (c2 <> ''::text) NOT VALID
+Not-null constraints:
+ "ft2_c1_not_null" NOT NULL "c1" (local, inherited)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
@@ -1778,6 +1836,8 @@ ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
c3 | date | | | | plain | |
Check constraints:
"fd_pt1chk3" CHECK (c2 <> ''::text)
+Not-null constraints:
+ "fd_pt1_c1_not_null" NOT NULL "c1"
Child tables: ft2, FOREIGN
\d+ ft2
@@ -1790,6 +1850,8 @@ Child tables: ft2, FOREIGN
Check constraints:
"fd_pt1chk2" CHECK (c2 <> ''::text)
"fd_pt1chk3" CHECK (c2 <> ''::text)
+Not-null constraints:
+ "ft2_c1_not_null" NOT NULL "c1" (local, inherited)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
@@ -1809,6 +1871,8 @@ ALTER TABLE fd_pt1 RENAME CONSTRAINT fd_pt1chk3 TO f2_check;
f3 | date | | | | plain | |
Check constraints:
"f2_check" CHECK (f2 <> ''::text)
+Not-null constraints:
+ "fd_pt1_c1_not_null" NOT NULL "f1"
Child tables: ft2, FOREIGN
\d+ ft2
@@ -1821,6 +1885,8 @@ Child tables: ft2, FOREIGN
Check constraints:
"f2_check" CHECK (f2 <> ''::text)
"fd_pt1chk2" CHECK (f2 <> ''::text)
+Not-null constraints:
+ "ft2_c1_not_null" NOT NULL "f1" (local, inherited)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
Inherits: fd_pt1
@@ -1867,6 +1933,8 @@ CREATE FOREIGN TABLE fd_pt2_1 PARTITION OF fd_pt2 FOR VALUES IN (1)
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Partition key: LIST (c1)
+Not-null constraints:
+ "fd_pt2_c1_not_null" NOT NULL "c1"
Partitions: fd_pt2_1 FOR VALUES IN (1), FOREIGN
\d+ fd_pt2_1
@@ -1878,6 +1946,8 @@ Partitions: fd_pt2_1 FOR VALUES IN (1), FOREIGN
c3 | date | | | | | plain | |
Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
+Not-null constraints:
+ "fd_pt2_c1_not_null" NOT NULL "c1" (inherited)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
@@ -1897,6 +1967,8 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c2 | text | | | | | extended | |
c3 | date | | | | | plain | |
c4 | character(1) | | | | | extended | |
+Not-null constraints:
+ "fd_pt2_1_c1_not_null" NOT NULL "c1"
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
@@ -1912,6 +1984,8 @@ DROP FOREIGN TABLE fd_pt2_1;
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Partition key: LIST (c1)
+Not-null constraints:
+ "fd_pt2_c1_not_null" NOT NULL "c1"
Number of partitions: 0
CREATE FOREIGN TABLE fd_pt2_1 (
@@ -1926,6 +2000,8 @@ CREATE FOREIGN TABLE fd_pt2_1 (
c1 | integer | | not null | | | plain | |
c2 | text | | | | | extended | |
c3 | date | | | | | plain | |
+Not-null constraints:
+ "fd_pt2_1_c1_not_null" NOT NULL "c1"
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
@@ -1939,6 +2015,8 @@ ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Partition key: LIST (c1)
+Not-null constraints:
+ "fd_pt2_c1_not_null" NOT NULL "c1"
Partitions: fd_pt2_1 FOR VALUES IN (1), FOREIGN
\d+ fd_pt2_1
@@ -1950,6 +2028,8 @@ Partitions: fd_pt2_1 FOR VALUES IN (1), FOREIGN
c3 | date | | | | | plain | |
Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
+Not-null constraints:
+ "fd_pt2_1_c1_not_null" NOT NULL "c1" (inherited)
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
@@ -1967,6 +2047,8 @@ ALTER TABLE fd_pt2_1 ADD CONSTRAINT p21chk CHECK (c2 <> '');
c2 | text | | | | extended | |
c3 | date | | | | plain | |
Partition key: LIST (c1)
+Not-null constraints:
+ "fd_pt2_c1_not_null" NOT NULL "c1"
Partitions: fd_pt2_1 FOR VALUES IN (1), FOREIGN
\d+ fd_pt2_1
@@ -1980,6 +2062,9 @@ Partition of: fd_pt2 FOR VALUES IN (1)
Partition constraint: ((c1 IS NOT NULL) AND (c1 = 1))
Check constraints:
"p21chk" CHECK (c2 <> ''::text)
+Not-null constraints:
+ "fd_pt2_1_c1_not_null" NOT NULL "c1" (inherited)
+ "fd_pt2_1_c3_not_null" NOT NULL "c3"
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
@@ -1997,6 +2082,9 @@ ALTER TABLE fd_pt2 ALTER c2 SET NOT NULL;
c2 | text | | not null | | extended | |
c3 | date | | | | plain | |
Partition key: LIST (c1)
+Not-null constraints:
+ "fd_pt2_c1_not_null" NOT NULL "c1"
+ "fd_pt2_c2_not_null" NOT NULL "c2"
Number of partitions: 0
\d+ fd_pt2_1
@@ -2008,11 +2096,14 @@ Number of partitions: 0
c3 | date | | not null | | | plain | |
Check constraints:
"p21chk" CHECK (c2 <> ''::text)
+Not-null constraints:
+ "fd_pt2_1_c1_not_null" NOT NULL "c1"
+ "fd_pt2_1_c3_not_null" NOT NULL "c3"
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1); -- ERROR
-ERROR: column "c2" in child table must be marked NOT NULL
+ERROR: column "c2" in child table "fd_pt2_1" must be marked NOT NULL
ALTER FOREIGN TABLE fd_pt2_1 ALTER c2 SET NOT NULL;
ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
ALTER TABLE fd_pt2 DETACH PARTITION fd_pt2_1;
@@ -2027,6 +2118,9 @@ ALTER TABLE fd_pt2 ADD CONSTRAINT fd_pt2chk1 CHECK (c1 > 0);
Partition key: LIST (c1)
Check constraints:
"fd_pt2chk1" CHECK (c1 > 0)
+Not-null constraints:
+ "fd_pt2_c1_not_null" NOT NULL "c1"
+ "fd_pt2_c2_not_null" NOT NULL "c2"
Number of partitions: 0
\d+ fd_pt2_1
@@ -2038,6 +2132,10 @@ Number of partitions: 0
c3 | date | | not null | | | plain | |
Check constraints:
"p21chk" CHECK (c2 <> ''::text)
+Not-null constraints:
+ "fd_pt2_1_c1_not_null" NOT NULL "c1"
+ "fd_pt2_1_c2_not_null" NOT NULL "c2"
+ "fd_pt2_1_c3_not_null" NOT NULL "c3"
Server: s0
FDW options: (delimiter ',', quote '"', "be quoted" 'value')
diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out
index 9bcc849573..a5165270c2 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -2053,13 +2053,19 @@ ORDER BY co.contype, cr.relname, co.conname, p.conname;
part33_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk
part3_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk
parted_self_fk | parted_self_fk_id_abc_fkey | f | t | | | parted_self_fk
+ part1_self_fk | part1_self_fk_id_not_null | n | t | | |
+ part2_self_fk | parted_self_fk_id_not_null | n | t | | |
+ part32_self_fk | part3_self_fk_id_not_null | n | t | | |
+ part33_self_fk | part33_self_fk_id_not_null | n | t | | |
+ part3_self_fk | part3_self_fk_id_not_null | n | t | | |
+ parted_self_fk | parted_self_fk_id_not_null | n | t | | |
part1_self_fk | part1_self_fk_pkey | p | t | parted_self_fk_pkey | t |
part2_self_fk | part2_self_fk_pkey | p | t | parted_self_fk_pkey | t |
part32_self_fk | part32_self_fk_pkey | p | t | part3_self_fk_pkey | t |
part33_self_fk | part33_self_fk_pkey | p | t | part3_self_fk_pkey | t |
part3_self_fk | part3_self_fk_pkey | p | t | parted_self_fk_pkey | t |
parted_self_fk | parted_self_fk_pkey | p | t | | |
-(12 rows)
+(18 rows)
-- detach and re-attach multiple times just to ensure everything is kosher
ALTER TABLE parted_self_fk DETACH PARTITION part2_self_fk;
@@ -2082,13 +2088,19 @@ ORDER BY co.contype, cr.relname, co.conname, p.conname;
part33_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk
part3_self_fk | parted_self_fk_id_abc_fkey | f | t | parted_self_fk_id_abc_fkey | t | parted_self_fk
parted_self_fk | parted_self_fk_id_abc_fkey | f | t | | | parted_self_fk
+ part1_self_fk | part1_self_fk_id_not_null | n | t | | |
+ part2_self_fk | parted_self_fk_id_not_null | n | t | | |
+ part32_self_fk | part3_self_fk_id_not_null | n | t | | |
+ part33_self_fk | part33_self_fk_id_not_null | n | t | | |
+ part3_self_fk | part3_self_fk_id_not_null | n | t | | |
+ parted_self_fk | parted_self_fk_id_not_null | n | t | | |
part1_self_fk | part1_self_fk_pkey | p | t | parted_self_fk_pkey | t |
part2_self_fk | part2_self_fk_pkey | p | t | parted_self_fk_pkey | t |
part32_self_fk | part32_self_fk_pkey | p | t | part3_self_fk_pkey | t |
part33_self_fk | part33_self_fk_pkey | p | t | part3_self_fk_pkey | t |
part3_self_fk | part3_self_fk_pkey | p | t | parted_self_fk_pkey | t |
parted_self_fk | parted_self_fk_pkey | p | t | | |
-(12 rows)
+(18 rows)
-- Leave this table around, for pg_upgrade/pg_dump tests
-- Test creating a constraint at the parent that already exists in partitions.
diff --git a/src/test/regress/expected/generated_stored.out b/src/test/regress/expected/generated_stored.out
index 8ea8a3a92d..0d037d48ca 100644
--- a/src/test/regress/expected/generated_stored.out
+++ b/src/test/regress/expected/generated_stored.out
@@ -321,6 +321,8 @@ NOTICE: merging column "b" with inherited definition
a | integer | | not null | | plain | |
b | integer | | | generated always as (a * 22) stored | plain | |
x | integer | | | | plain | |
+Not-null constraints:
+ "gtest1_a_not_null" NOT NULL "a" (inherited)
Inherits: gtest1
CREATE TABLE gtestxx_1 (a int NOT NULL, b int);
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index f14bfccfb1..2a2b777c89 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -578,6 +578,10 @@ TABLE itest8;
f3 | integer | | not null | generated by default as identity | plain | |
f4 | bigint | | not null | generated always as identity | plain | |
f5 | bigint | | | | plain | |
+Not-null constraints:
+ "itest8_f2_not_null" NOT NULL "f2"
+ "itest8_f3_not_null" NOT NULL "f3"
+ "itest8_f4_not_null" NOT NULL "f4"
\d itest8_f2_seq
Sequence "public.itest8_f2_seq"
@@ -618,7 +622,7 @@ INSERT into pitest1_p1 (f1, f2) VALUES ('2016-07-3', 'from pitest1_p1');
CREATE TABLE pitest1_p2 (f3 bigint, f2 text, f1 date NOT NULL);
INSERT INTO pitest1_p2 (f1, f2, f3) VALUES ('2016-08-2', 'before attaching', 100);
ALTER TABLE pitest1 ATTACH PARTITION pitest1_p2 FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); -- requires NOT NULL constraint
-ERROR: column "f3" in child table must be marked NOT NULL
+ERROR: column "f3" in child table "pitest1_p2" must be marked NOT NULL
ALTER TABLE pitest1_p2 ALTER COLUMN f3 SET NOT NULL;
ALTER TABLE pitest1 ATTACH PARTITION pitest1_p2 FOR VALUES FROM ('2016-08-01') TO ('2016-09-01');
INSERT INTO pitest1_p2 (f1, f2) VALUES ('2016-08-3', 'from pitest1_p2');
diff --git a/src/test/regress/expected/index_including.out b/src/test/regress/expected/index_including.out
index ea8b2454bf..4e8fe49c8c 100644
--- a/src/test/regress/expected/index_including.out
+++ b/src/test/regress/expected/index_including.out
@@ -113,7 +113,7 @@ SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, i
covering | 4 | 2 | t | t | 1 2 3 4 | 1978 1978
(1 row)
-SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid AND contype = 'p';
pg_get_constraintdef | conname | conkey
---------------------------------------+----------+--------
PRIMARY KEY (c1, c2) INCLUDE (c3, c4) | covering | {1,2}
@@ -191,7 +191,7 @@ SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, i
tbl_pkey | 4 | 2 | t | t | 1 2 3 4 | 1978 1978
(1 row)
-SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid AND contype = 'p';
pg_get_constraintdef | conname | conkey
---------------------------------------+----------+--------
PRIMARY KEY (c1, c2) INCLUDE (c3, c4) | tbl_pkey | {1,2}
diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.out
index 69becce19b..bcf1db11d7 100644
--- a/src/test/regress/expected/indexing.out
+++ b/src/test/regress/expected/indexing.out
@@ -1117,15 +1117,27 @@ alter table idxpart attach partition idxpart3 for values from (20, 20) to (30, 3
select conname, contype, conrelid::regclass, conindid::regclass, conkey
from pg_constraint where conrelid::regclass::text like 'idxpart%'
order by conrelid::regclass::text, conname;
- conname | contype | conrelid | conindid | conkey
-----------------+---------+-----------+----------------+--------
- idxpart_pkey | p | idxpart | idxpart_pkey | {1,2}
- idxpart1_pkey | p | idxpart1 | idxpart1_pkey | {1,2}
- idxpart2_pkey | p | idxpart2 | idxpart2_pkey | {1,2}
- idxpart21_pkey | p | idxpart21 | idxpart21_pkey | {1,2}
- idxpart22_pkey | p | idxpart22 | idxpart22_pkey | {1,2}
- idxpart3_pkey | p | idxpart3 | idxpart3_pkey | {2,1}
-(6 rows)
+ conname | contype | conrelid | conindid | conkey
+---------------------+---------+-----------+----------------+--------
+ idxpart_a_not_null | n | idxpart | - | {1}
+ idxpart_b_not_null | n | idxpart | - | {2}
+ idxpart_pkey | p | idxpart | idxpart_pkey | {1,2}
+ idxpart1_pkey | p | idxpart1 | idxpart1_pkey | {1,2}
+ idxpart_a_not_null | n | idxpart1 | - | {1}
+ idxpart_b_not_null | n | idxpart1 | - | {2}
+ idxpart2_pkey | p | idxpart2 | idxpart2_pkey | {1,2}
+ idxpart_a_not_null | n | idxpart2 | - | {1}
+ idxpart_b_not_null | n | idxpart2 | - | {2}
+ idxpart21_pkey | p | idxpart21 | idxpart21_pkey | {1,2}
+ idxpart_a_not_null | n | idxpart21 | - | {1}
+ idxpart_b_not_null | n | idxpart21 | - | {2}
+ idxpart22_pkey | p | idxpart22 | idxpart22_pkey | {1,2}
+ idxpart_a_not_null | n | idxpart22 | - | {1}
+ idxpart_b_not_null | n | idxpart22 | - | {2}
+ idxpart3_a_not_null | n | idxpart3 | - | {2}
+ idxpart3_b_not_null | n | idxpart3 | - | {1}
+ idxpart3_pkey | p | idxpart3 | idxpart3_pkey | {2,1}
+(18 rows)
drop table idxpart;
-- Verify that multi-layer partitioning honors the requirement that all
@@ -1150,13 +1162,19 @@ create table idxpart2 partition of idxpart for values from (0) to (1000) partiti
create table idxpart21 partition of idxpart2 for values from (0) to (1000);
select conname, contype, conrelid::regclass, conindid::regclass, conkey
from pg_constraint where conrelid::regclass::text like 'idxpart%'
- order by conname;
- conname | contype | conrelid | conindid | conkey
-----------------+---------+-----------+----------------+--------
- idxpart21_pkey | p | idxpart21 | idxpart21_pkey | {1,2}
- idxpart2_pkey | p | idxpart2 | idxpart2_pkey | {1,2}
- idxpart_pkey | p | idxpart | idxpart_pkey | {1,2}
-(3 rows)
+ order by conrelid::regclass::text, conname;
+ conname | contype | conrelid | conindid | conkey
+--------------------+---------+-----------+----------------+--------
+ idxpart_a_not_null | n | idxpart | - | {1}
+ idxpart_b_not_null | n | idxpart | - | {2}
+ idxpart_pkey | p | idxpart | idxpart_pkey | {1,2}
+ idxpart2_pkey | p | idxpart2 | idxpart2_pkey | {1,2}
+ idxpart_a_not_null | n | idxpart2 | - | {1}
+ idxpart_b_not_null | n | idxpart2 | - | {2}
+ idxpart21_pkey | p | idxpart21 | idxpart21_pkey | {1,2}
+ idxpart_a_not_null | n | idxpart21 | - | {1}
+ idxpart_b_not_null | n | idxpart21 | - | {2}
+(9 rows)
drop table idxpart;
-- If a partitioned table has a unique/PK constraint, then it's not possible
@@ -1259,14 +1277,10 @@ create table idxpart0 (like idxpart);
alter table idxpart0 add unique (a);
alter table idxpart attach partition idxpart0 default;
alter table only idxpart add primary key (a); -- fail, no not-null constraint
-ERROR: constraint must be added to child tables too
-DETAIL: Column "a" of relation "idxpart0" is not already NOT NULL.
-HINT: Do not specify the ONLY keyword.
+ERROR: column "a" of table "idxpart0" is not marked NOT NULL
alter table idxpart0 alter column a set not null;
alter table only idxpart add primary key (a); -- now it works
alter index idxpart_pkey attach partition idxpart0_a_key;
-alter table idxpart0 alter column a drop not null; -- fail, pkey needs it
-ERROR: column "a" is marked NOT NULL in parent table
drop table idxpart;
-- if a partition has a unique index without a constraint, does not attach
-- automatically; creates a new index instead.
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 482fc47933..bb81f6d2b4 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -539,6 +539,9 @@ CREATE TEMP TABLE z (b TEXT, PRIMARY KEY(aa, b)) inherits (a);
INSERT INTO z VALUES (NULL, 'text'); -- should fail
ERROR: null value in column "aa" of relation "z" violates not-null constraint
DETAIL: Failing row contains (null, text).
+-- ... but not UNIQUE.
+CREATE TEMP TABLE z2 (b TEXT, UNIQUE(aa, b)) inherits (a);
+INSERT INTO z2 VALUES (NULL, 'text'); -- should work
-- Check inherited UPDATE with first child excluded
create table some_tab (f1 int, f2 int, f3 int, check (f1 < 10) no inherit);
create table some_tab_child () inherits(some_tab);
@@ -1252,6 +1255,8 @@ Indexes:
"test_primary_constraints_pkey" PRIMARY KEY, btree (id)
Referenced by:
TABLE "test_foreign_constraints" CONSTRAINT "test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
+Not-null constraints:
+ "test_primary_constraints_id_not_null" NOT NULL "id"
\d+ test_foreign_constraints
Table "public.test_foreign_constraints"
@@ -2054,6 +2059,613 @@ select * from cnullparent where f1 = 2;
drop table cnullparent cascade;
NOTICE: drop cascades to table cnullchild
--
+-- Test inheritance of NOT NULL constraints
+--
+create table pp1 (f1 int);
+create table cc1 (f2 text, f3 int) inherits (pp1);
+create table cc2 (f4 float) inherits (pp1,cc1);
+NOTICE: merging multiple inherited definitions of column "f1"
+create table cc3 () inherits (pp1,cc1,cc2);
+NOTICE: merging multiple inherited definitions of column "f1"
+NOTICE: merging multiple inherited definitions of column "f1"
+NOTICE: merging multiple inherited definitions of column "f2"
+NOTICE: merging multiple inherited definitions of column "f3"
+alter table pp1 alter f1 set not null;
+\d+ cc3
+ Table "public.cc3"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+------------------+-----------+----------+---------+----------+--------------+-------------
+ f1 | integer | | not null | | plain | |
+ f2 | text | | | | extended | |
+ f3 | integer | | | | plain | |
+ f4 | double precision | | | | plain | |
+Not-null constraints:
+ "pp1_f1_not_null" NOT NULL "f1" (inherited)
+Inherits: pp1,
+ cc1,
+ cc2
+
+alter table cc3 no inherit pp1;
+alter table cc3 no inherit cc1;
+alter table cc3 no inherit cc2;
+\d+ cc3
+ Table "public.cc3"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+------------------+-----------+----------+---------+----------+--------------+-------------
+ f1 | integer | | not null | | plain | |
+ f2 | text | | | | extended | |
+ f3 | integer | | | | plain | |
+ f4 | double precision | | | | plain | |
+Not-null constraints:
+ "pp1_f1_not_null" NOT NULL "f1"
+
+drop table cc3;
+-- named NOT NULL constraint
+alter table cc1 add column a2 int constraint nn not null;
+\d+ cc1
+ Table "public.cc1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ f1 | integer | | not null | | plain | |
+ f2 | text | | | | extended | |
+ f3 | integer | | | | plain | |
+ a2 | integer | | not null | | plain | |
+Not-null constraints:
+ "pp1_f1_not_null" NOT NULL "f1" (inherited)
+ "nn" NOT NULL "a2"
+Inherits: pp1
+Child tables: cc2
+
+\d+ cc2
+ Table "public.cc2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+------------------+-----------+----------+---------+----------+--------------+-------------
+ f1 | integer | | not null | | plain | |
+ f2 | text | | | | extended | |
+ f3 | integer | | | | plain | |
+ f4 | double precision | | | | plain | |
+ a2 | integer | | not null | | plain | |
+Not-null constraints:
+ "pp1_f1_not_null" NOT NULL "f1" (inherited)
+ "nn" NOT NULL "a2" (inherited)
+Inherits: pp1,
+ cc1
+
+alter table pp1 alter column f1 set not null;
+\d+ pp1
+ Table "public.pp1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ f1 | integer | | not null | | plain | |
+Not-null constraints:
+ "pp1_f1_not_null" NOT NULL "f1"
+Child tables: cc1,
+ cc2
+
+\d+ cc1
+ Table "public.cc1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ f1 | integer | | not null | | plain | |
+ f2 | text | | | | extended | |
+ f3 | integer | | | | plain | |
+ a2 | integer | | not null | | plain | |
+Not-null constraints:
+ "pp1_f1_not_null" NOT NULL "f1" (inherited)
+ "nn" NOT NULL "a2"
+Inherits: pp1
+Child tables: cc2
+
+\d+ cc2
+ Table "public.cc2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+------------------+-----------+----------+---------+----------+--------------+-------------
+ f1 | integer | | not null | | plain | |
+ f2 | text | | | | extended | |
+ f3 | integer | | | | plain | |
+ f4 | double precision | | | | plain | |
+ a2 | integer | | not null | | plain | |
+Not-null constraints:
+ "pp1_f1_not_null" NOT NULL "f1" (inherited)
+ "nn" NOT NULL "a2" (inherited)
+Inherits: pp1,
+ cc1
+
+-- cannot create table with inconsistent NO INHERIT constraint
+create table cc3 (a2 int not null no inherit) inherits (cc1);
+NOTICE: moving and merging column "a2" with inherited definition
+DETAIL: User-specified column moved to the position of the inherited column.
+ERROR: cannot define not-null constraint on column "a2" with NO INHERIT
+DETAIL: The column has an inherited not-null constraint.
+-- change NO INHERIT status of inherited constraint: no dice, it's inherited
+alter table cc2 add not null a2 no inherit;
+ERROR: cannot change NO INHERIT status of NOT NULL constraint "nn" on relation "cc2"
+-- remove constraint from cc2: no dice, it's inherited
+alter table cc2 alter column a2 drop not null;
+ERROR: cannot drop inherited constraint "nn" of relation "cc2"
+-- remove constraint from cc1, should succeed
+alter table cc1 alter column a2 drop not null;
+\d+ cc1
+ Table "public.cc1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ f1 | integer | | not null | | plain | |
+ f2 | text | | | | extended | |
+ f3 | integer | | | | plain | |
+ a2 | integer | | | | plain | |
+Not-null constraints:
+ "pp1_f1_not_null" NOT NULL "f1" (inherited)
+Inherits: pp1
+Child tables: cc2
+
+-- same for cc2
+alter table cc2 alter column f1 drop not null;
+ERROR: cannot drop inherited constraint "pp1_f1_not_null" of relation "cc2"
+\d+ cc2
+ Table "public.cc2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+------------------+-----------+----------+---------+----------+--------------+-------------
+ f1 | integer | | not null | | plain | |
+ f2 | text | | | | extended | |
+ f3 | integer | | | | plain | |
+ f4 | double precision | | | | plain | |
+ a2 | integer | | | | plain | |
+Not-null constraints:
+ "pp1_f1_not_null" NOT NULL "f1" (inherited)
+Inherits: pp1,
+ cc1
+
+-- remove from cc1, should fail again
+alter table cc1 alter column f1 drop not null;
+ERROR: cannot drop inherited constraint "pp1_f1_not_null" of relation "cc1"
+-- remove from pp1, should succeed
+alter table pp1 alter column f1 drop not null;
+\d+ pp1
+ Table "public.pp1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ f1 | integer | | | | plain | |
+Child tables: cc1,
+ cc2
+
+alter table pp1 add primary key (f1);
+-- Leave these tables around, for pg_upgrade testing
+-- test that removing inheritance of NOT NULL NO INHERIT works correctly
+create table inh_parent (f1 int not null no inherit, f2 int not null no inherit);
+create table inh_child (f1 int not null no inherit, f2 int);
+alter table inh_child inherit inh_parent;
+alter table inh_child no inherit inh_parent;
+\d+ inh_child
+ Table "public.inh_child"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ f1 | integer | | not null | | plain | |
+ f2 | integer | | | | plain | |
+Not-null constraints:
+ "inh_child_f1_not_null" NOT NULL "f1" NO INHERIT
+
+drop table inh_parent, inh_child;
+-- test that inhcount is updated correctly through multiple inheritance
+create table inh_pp1 (f1 int);
+create table inh_cc1 (f2 text, f3 int) inherits (inh_pp1);
+create table inh_cc2(f4 float) inherits(inh_pp1,inh_cc1);
+NOTICE: merging multiple inherited definitions of column "f1"
+alter table inh_pp1 alter column f1 set not null;
+alter table inh_cc2 no inherit inh_pp1;
+alter table inh_cc2 no inherit inh_cc1;
+\d+ inh_cc2
+ Table "public.inh_cc2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+------------------+-----------+----------+---------+----------+--------------+-------------
+ f1 | integer | | not null | | plain | |
+ f2 | text | | | | extended | |
+ f3 | integer | | | | plain | |
+ f4 | double precision | | | | plain | |
+Not-null constraints:
+ "inh_pp1_f1_not_null" NOT NULL "f1"
+
+drop table inh_pp1, inh_cc1, inh_cc2;
+create table inh_pp1 (f1 int not null);
+create table inh_cc1 (f2 text, f3 int) inherits (inh_pp1);
+create table inh_cc2(f4 float) inherits(inh_pp1,inh_cc1);
+NOTICE: merging multiple inherited definitions of column "f1"
+alter table inh_pp1 alter column f1 drop not null;
+\d+ inh_cc2
+ Table "public.inh_cc2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+------------------+-----------+----------+---------+----------+--------------+-------------
+ f1 | integer | | | | plain | |
+ f2 | text | | | | extended | |
+ f3 | integer | | | | plain | |
+ f4 | double precision | | | | plain | |
+Inherits: inh_pp1,
+ inh_cc1
+
+drop table inh_pp1, inh_cc1, inh_cc2;
+-- Test a not-null addition that must walk down the hierarchy
+CREATE TABLE inh_parent ();
+CREATE TABLE inh_child (i int) INHERITS (inh_parent);
+CREATE TABLE inh_grandchild () INHERITS (inh_parent, inh_child);
+ALTER TABLE inh_parent ADD COLUMN i int NOT NULL;
+NOTICE: merging definition of column "i" for child "inh_child"
+NOTICE: merging definition of column "i" for child "inh_grandchild"
+drop table inh_parent, inh_child, inh_grandchild;
+-- Test the same constraint name for different columns in different parents
+create table inh_parent1(a int constraint nn not null);
+create table inh_parent2(b int constraint nn not null);
+create table inh_child1 () inherits (inh_parent1, inh_parent2);
+\d+ inh_child1
+ Table "public.inh_child1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+ b | integer | | not null | | plain | |
+Not-null constraints:
+ "nn" NOT NULL "a" (inherited)
+ "inh_child1_b_not_null" NOT NULL "b" (inherited)
+Inherits: inh_parent1,
+ inh_parent2
+
+create table inh_child2 (constraint foo not null a) inherits (inh_parent1, inh_parent2);
+alter table inh_child2 no inherit inh_parent2;
+\d+ inh_child2
+ Table "public.inh_child2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+ b | integer | | not null | | plain | |
+Not-null constraints:
+ "foo" NOT NULL "a" (local, inherited)
+ "nn" NOT NULL "b"
+Inherits: inh_parent1
+
+drop table inh_parent1, inh_parent2, inh_child1, inh_child2;
+-- Test multiple parents with overlapping primary keys
+create table inh_parent1(a int, b int, c int, primary key (a, b));
+create table inh_parent2(d int, e int, b int, primary key (d, b));
+create table inh_child() inherits (inh_parent1, inh_parent2);
+NOTICE: merging multiple inherited definitions of column "b"
+select conrelid::regclass, conname, contype, conkey,
+ coninhcount, conislocal, connoinherit
+ from pg_constraint where contype in ('n','p') and
+ conrelid::regclass::text in ('inh_child', 'inh_parent1', 'inh_parent2')
+ order by 1, 2;
+ conrelid | conname | contype | conkey | coninhcount | conislocal | connoinherit
+-------------+------------------------+---------+--------+-------------+------------+--------------
+ inh_parent1 | inh_parent1_a_not_null | n | {1} | 0 | t | f
+ inh_parent1 | inh_parent1_b_not_null | n | {2} | 0 | t | f
+ inh_parent1 | inh_parent1_pkey | p | {1,2} | 0 | t | t
+ inh_parent2 | inh_parent2_b_not_null | n | {3} | 0 | t | f
+ inh_parent2 | inh_parent2_d_not_null | n | {1} | 0 | t | f
+ inh_parent2 | inh_parent2_pkey | p | {1,3} | 0 | t | t
+ inh_child | inh_parent1_a_not_null | n | {1} | 1 | f | f
+ inh_child | inh_parent1_b_not_null | n | {2} | 2 | f | f
+ inh_child | inh_parent2_d_not_null | n | {4} | 1 | f | f
+(9 rows)
+
+\d+ inh_child
+ Table "public.inh_child"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+ b | integer | | not null | | plain | |
+ c | integer | | | | plain | |
+ d | integer | | not null | | plain | |
+ e | integer | | | | plain | |
+Not-null constraints:
+ "inh_parent1_a_not_null" NOT NULL "a" (inherited)
+ "inh_parent1_b_not_null" NOT NULL "b" (inherited)
+ "inh_parent2_d_not_null" NOT NULL "d" (inherited)
+Inherits: inh_parent1,
+ inh_parent2
+
+drop table inh_parent1, inh_parent2, inh_child;
+-- NOT NULL NO INHERIT
+create table inh_nn_parent(a int);
+create table inh_nn_child() inherits (inh_nn_parent);
+alter table inh_nn_parent add not null a no inherit;
+create table inh_nn_child2() inherits (inh_nn_parent);
+select conrelid::regclass, conname, contype, conkey,
+ (select attname from pg_attribute where attrelid = conrelid and attnum = conkey[1]),
+ coninhcount, conislocal, connoinherit
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass::text like 'inh\_nn\_%'
+ order by 2, 1;
+ conrelid | conname | contype | conkey | attname | coninhcount | conislocal | connoinherit
+---------------+--------------------------+---------+--------+---------+-------------+------------+--------------
+ inh_nn_parent | inh_nn_parent_a_not_null | n | {1} | a | 0 | t | t
+(1 row)
+
+\d+ inh_nn*
+ Table "public.inh_nn_child"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Inherits: inh_nn_parent
+
+ Table "public.inh_nn_child2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Inherits: inh_nn_parent
+
+ Table "public.inh_nn_parent"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | not null | | plain | |
+Not-null constraints:
+ "inh_nn_parent_a_not_null" NOT NULL "a" NO INHERIT
+Child tables: inh_nn_child,
+ inh_nn_child2
+
+drop table inh_nn_parent, inh_nn_child, inh_nn_child2;
+CREATE TABLE inh_nn_parent (a int, NOT NULL a NO INHERIT);
+CREATE TABLE inh_nn_child() INHERITS (inh_nn_parent);
+ALTER TABLE inh_nn_parent ADD CONSTRAINT nna NOT NULL a;
+ERROR: cannot change NO INHERIT status of NOT NULL constraint "inh_nn_parent_a_not_null" on relation "inh_nn_parent"
+ALTER TABLE inh_nn_parent ALTER a SET NOT NULL;
+ERROR: cannot change NO INHERIT status of NOT NULL constraint "inh_nn_parent_a_not_null" on relation "inh_nn_parent"
+DROP TABLE inh_nn_parent cascade;
+NOTICE: drop cascades to table inh_nn_child
+-- Adding a PK at the top level of a hierarchy should cause all descendants
+-- to be checked for nulls, even past a no-inherit constraint
+CREATE TABLE inh_nn_lvl1 (a int);
+CREATE TABLE inh_nn_lvl2 () INHERITS (inh_nn_lvl1);
+CREATE TABLE inh_nn_lvl3 (CONSTRAINT foo NOT NULL a NO INHERIT) INHERITS (inh_nn_lvl2);
+ALTER TABLE inh_nn_lvl1 ADD PRIMARY KEY (a);
+ERROR: cannot change NO INHERIT status of NOT NULL constraint "foo" on relation "inh_nn_lvl3"
+DROP TABLE inh_nn_lvl1, inh_nn_lvl2, inh_nn_lvl3;
+-- Disallow specifying conflicting NO INHERIT flags for the same constraint
+CREATE TABLE inh_nn1 (a int primary key, b int, not null a no inherit);
+ERROR: conflicting NO INHERIT declaration for not-null constraint on column "a"
+CREATE TABLE inh_nn1 (a int not null);
+CREATE TABLE inh_nn2 (a int not null no inherit) INHERITS (inh_nn1);
+NOTICE: merging column "a" with inherited definition
+ERROR: cannot define not-null constraint on column "a" with NO INHERIT
+DETAIL: The column has an inherited not-null constraint.
+CREATE TABLE inh_nn3 (a int not null, b int, not null a no inherit);
+ERROR: conflicting NO INHERIT declaration for not-null constraint on column "a"
+CREATE TABLE inh_nn4 (a int not null no inherit, b int, not null a);
+ERROR: conflicting NO INHERIT declaration for not-null constraint on column "a"
+DROP TABLE inh_nn1, inh_nn2, inh_nn3, inh_nn4;
+ERROR: table "inh_nn2" does not exist
+--
+-- test inherit/deinherit
+--
+create table inh_parent(f1 int);
+create table inh_child1(f1 int not null);
+create table inh_child2(f1 int);
+-- inh_child1 should have not null constraint
+alter table inh_child1 inherit inh_parent;
+-- should fail, missing NOT NULL constraint
+alter table inh_child2 inherit inh_child1;
+ERROR: column "f1" in child table "inh_child2" must be marked NOT NULL
+alter table inh_child2 alter column f1 set not null;
+alter table inh_child2 inherit inh_child1;
+-- add NOT NULL constraint recursively
+alter table inh_parent alter column f1 set not null;
+\d+ inh_parent
+ Table "public.inh_parent"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ f1 | integer | | not null | | plain | |
+Not-null constraints:
+ "inh_parent_f1_not_null" NOT NULL "f1"
+Child tables: inh_child1
+
+\d+ inh_child1
+ Table "public.inh_child1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ f1 | integer | | not null | | plain | |
+Not-null constraints:
+ "inh_child1_f1_not_null" NOT NULL "f1" (local, inherited)
+Inherits: inh_parent
+Child tables: inh_child2
+
+\d+ inh_child2
+ Table "public.inh_child2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ f1 | integer | | not null | | plain | |
+Not-null constraints:
+ "inh_child2_f1_not_null" NOT NULL "f1" (local, inherited)
+Inherits: inh_child1
+
+select conrelid::regclass, conname, contype, coninhcount, conislocal
+ from pg_constraint where contype = 'n' and
+ conrelid in ('inh_parent'::regclass, 'inh_child1'::regclass, 'inh_child2'::regclass)
+ order by 2, 1;
+ conrelid | conname | contype | coninhcount | conislocal
+------------+------------------------+---------+-------------+------------
+ inh_child1 | inh_child1_f1_not_null | n | 1 | t
+ inh_child2 | inh_child2_f1_not_null | n | 1 | t
+ inh_parent | inh_parent_f1_not_null | n | 0 | t
+(3 rows)
+
+--
+-- test deinherit procedure
+--
+-- deinherit inh_child1
+create table inh_child3 () inherits (inh_child1);
+alter table inh_child1 no inherit inh_parent;
+\d+ inh_parent
+ Table "public.inh_parent"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ f1 | integer | | not null | | plain | |
+Not-null constraints:
+ "inh_parent_f1_not_null" NOT NULL "f1"
+
+\d+ inh_child1
+ Table "public.inh_child1"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ f1 | integer | | not null | | plain | |
+Not-null constraints:
+ "inh_child1_f1_not_null" NOT NULL "f1"
+Child tables: inh_child2,
+ inh_child3
+
+\d+ inh_child2
+ Table "public.inh_child2"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ f1 | integer | | not null | | plain | |
+Not-null constraints:
+ "inh_child2_f1_not_null" NOT NULL "f1" (local, inherited)
+Inherits: inh_child1
+
+select conrelid::regclass, conname, contype, coninhcount, conislocal
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass::text in ('inh_parent', 'inh_child1', 'inh_child2', 'inh_child3')
+ order by 2, 1;
+ conrelid | conname | contype | coninhcount | conislocal
+------------+------------------------+---------+-------------+------------
+ inh_child1 | inh_child1_f1_not_null | n | 0 | t
+ inh_child3 | inh_child1_f1_not_null | n | 1 | f
+ inh_child2 | inh_child2_f1_not_null | n | 1 | t
+ inh_parent | inh_parent_f1_not_null | n | 0 | t
+(4 rows)
+
+drop table inh_parent, inh_child1, inh_child2, inh_child3;
+-- ALTER TABLE INHERIT ensures that the child has not-null constraints
+create table inh_parent (a int not null);
+create table inh_child (a int);
+alter table inh_child inherit inh_parent; -- nope
+ERROR: column "a" in child table "inh_child" must be marked NOT NULL
+drop table inh_parent, inh_child;
+-- Can't merge a NO INHERIT constraint with a normal one
+create table inh_parent (a int not null);
+create table inh_child (a int not null no inherit);
+alter table inh_child inherit inh_parent;
+ERROR: constraint "inh_child_a_not_null" conflicts with non-inherited constraint on child table "inh_child"
+drop table inh_parent, inh_child;
+-- don't interfere with other types of constraints
+create table inh_parent (a int primary key);
+create table inh_child (a int primary key) inherits (inh_parent);
+NOTICE: merging column "a" with inherited definition
+alter table inh_parent add constraint inh_parent_excl exclude ((1) with =);
+alter table inh_parent add constraint inh_parent_uq unique (a);
+alter table inh_parent add constraint inh_parent_fk foreign key (a) references inh_parent (a);
+create table inh_child2 () inherits (inh_parent);
+create table inh_child3 (like inh_parent);
+alter table inh_child3 inherit inh_parent;
+select conrelid::regclass, conname, contype, coninhcount, conislocal
+ from pg_constraint
+ where conrelid::regclass::text in ('inh_parent', 'inh_child', 'inh_child2', 'inh_child3')
+ order by 2, 1;
+ conrelid | conname | contype | coninhcount | conislocal
+------------+-----------------------+---------+-------------+------------
+ inh_child | inh_child_a_not_null | n | 1 | t
+ inh_child | inh_child_pkey | p | 0 | t
+ inh_parent | inh_parent_a_not_null | n | 0 | t
+ inh_child2 | inh_parent_a_not_null | n | 1 | f
+ inh_child3 | inh_parent_a_not_null | n | 1 | t
+ inh_parent | inh_parent_excl | x | 0 | t
+ inh_parent | inh_parent_fk | f | 0 | t
+ inh_parent | inh_parent_pkey | p | 0 | t
+ inh_parent | inh_parent_uq | u | 0 | t
+(9 rows)
+
+drop table inh_parent, inh_child, inh_child2, inh_child3;
+--
+-- test multi inheritance tree
+--
+create table inh_parent(f1 int not null);
+create table inh_child1() inherits(inh_parent);
+create table inh_child2() inherits(inh_parent);
+create table inh_child3() inherits(inh_child1, inh_child2);
+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 in ('inh_parent'::regclass, 'inh_child1'::regclass, 'inh_child2'::regclass, 'inh_child3'::regclass)
+ order by 2, conrelid::regclass::text;
+ conrelid | conname | contype | coninhcount | conislocal
+------------+------------------------+---------+-------------+------------
+ inh_child1 | inh_parent_f1_not_null | n | 1 | f
+ inh_child2 | inh_parent_f1_not_null | n | 1 | f
+ inh_child3 | inh_parent_f1_not_null | n | 2 | f
+ inh_parent | inh_parent_f1_not_null | n | 0 | t
+(4 rows)
+
+drop table inh_parent cascade;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table inh_child1
+drop cascades to table inh_child2
+drop cascades to table inh_child3
+-- test child table with inherited columns and
+-- with explicitly specified not null constraints
+create table inh_parent_1(f1 int);
+create table inh_parent_2(f2 text);
+create table inh_child(f1 int not null, f2 text not null) inherits(inh_parent_1, inh_parent_2);
+NOTICE: merging column "f1" with inherited definition
+NOTICE: merging column "f2" with inherited definition
+-- show constraint info
+select conrelid::regclass, conname, contype, coninhcount, conislocal
+ from pg_constraint where contype = 'n' and
+ conrelid in ('inh_parent_1'::regclass, 'inh_parent_2'::regclass, 'inh_child'::regclass)
+ order by 2, conrelid::regclass::text;
+ conrelid | conname | contype | coninhcount | conislocal
+-----------+-----------------------+---------+-------------+------------
+ inh_child | inh_child_f1_not_null | n | 0 | t
+ inh_child | inh_child_f2_not_null | n | 0 | t
+(2 rows)
+
+-- also drops inh_child table
+drop table inh_parent_1 cascade;
+NOTICE: drop cascades to table inh_child
+drop table inh_parent_2;
+-- test multi layer inheritance tree
+create table inh_p1(f1 int not null);
+create table inh_p2(f1 int not null);
+create table inh_p3(f2 int);
+create table inh_p4(f1 int not null, f3 text not null);
+create table inh_multiparent() inherits(inh_p1, inh_p2, inh_p3, inh_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, contype, conname,
+ (select attname from pg_attribute where attrelid = conrelid and attnum = conkey[1]),
+ coninhcount, conislocal
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass in ('inh_p1', 'inh_p2', 'inh_p3', 'inh_p4',
+ 'inh_multiparent')
+ order by conrelid::regclass::text, conname;
+ conrelid | contype | conname | attname | coninhcount | conislocal
+-----------------+---------+--------------------+---------+-------------+------------
+ inh_multiparent | n | inh_p1_f1_not_null | f1 | 3 | f
+ inh_multiparent | n | inh_p4_f3_not_null | f3 | 1 | f
+ inh_p1 | n | inh_p1_f1_not_null | f1 | 0 | t
+ inh_p2 | n | inh_p2_f1_not_null | f1 | 0 | t
+ inh_p4 | n | inh_p4_f1_not_null | f1 | 0 | t
+ inh_p4 | n | inh_p4_f3_not_null | f3 | 0 | t
+(6 rows)
+
+create table inh_multiparent2 (a int not null, f1 int) inherits(inh_p3, inh_multiparent);
+NOTICE: merging multiple inherited definitions of column "f2"
+NOTICE: merging column "f1" with inherited definition
+select conrelid::regclass, contype, conname,
+ (select attname from pg_attribute where attrelid = conrelid and attnum = conkey[1]),
+ coninhcount, conislocal
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass in ('inh_p3', 'inh_multiparent', 'inh_multiparent2')
+ order by conrelid::regclass::text, conname;
+ conrelid | contype | conname | attname | coninhcount | conislocal
+------------------+---------+-----------------------------+---------+-------------+------------
+ inh_multiparent | n | inh_p1_f1_not_null | f1 | 3 | f
+ inh_multiparent | n | inh_p4_f3_not_null | f3 | 1 | f
+ inh_multiparent2 | n | inh_multiparent2_a_not_null | a | 0 | t
+ inh_multiparent2 | n | inh_p1_f1_not_null | f1 | 1 | f
+ inh_multiparent2 | n | inh_p4_f3_not_null | f3 | 1 | f
+(5 rows)
+
+drop table inh_p1, inh_p2, inh_p3, inh_p4 cascade;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table inh_multiparent
+drop cascades to table inh_multiparent2
+--
-- Mixed ownership inheritance tree
--
create role regress_alice;
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index a8949ffc2c..5de2d64d01 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -199,6 +199,8 @@ Indexes:
"testpub_tbl2_pkey" PRIMARY KEY, btree (id)
Publications:
"testpub_foralltables"
+Not-null constraints:
+ "testpub_tbl2_id_not_null" NOT NULL "id"
\dRp+ testpub_foralltables
Publication testpub_foralltables
@@ -777,6 +779,8 @@ Indexes:
"testpub_tbl7_pkey" PRIMARY KEY, btree (a)
Publications:
"testpub_fortable" (a, b)
+Not-null constraints:
+ "testpub_tbl7_a_not_null" NOT NULL "a"
-- ok: the column list is the same, we should skip this table (or at least not fail)
ALTER PUBLICATION testpub_fortable SET TABLE testpub_tbl7 (a, b);
@@ -791,6 +795,8 @@ Indexes:
"testpub_tbl7_pkey" PRIMARY KEY, btree (a)
Publications:
"testpub_fortable" (a, b)
+Not-null constraints:
+ "testpub_tbl7_a_not_null" NOT NULL "a"
-- ok: the column list changes, make sure the catalog gets updated
ALTER PUBLICATION testpub_fortable SET TABLE testpub_tbl7 (a, c);
@@ -805,6 +811,8 @@ Indexes:
"testpub_tbl7_pkey" PRIMARY KEY, btree (a)
Publications:
"testpub_fortable" (a, c)
+Not-null constraints:
+ "testpub_tbl7_a_not_null" NOT NULL "a"
-- column list for partitioned tables has to cover replica identities for
-- all child relations
@@ -941,6 +949,9 @@ Indexes:
"testpub_tbl_both_filters_pkey" PRIMARY KEY, btree (a, c) REPLICA IDENTITY
Publications:
"testpub_both_filters" (a, c) WHERE (c <> 1)
+Not-null constraints:
+ "testpub_tbl_both_filters_a_not_null" NOT NULL "a"
+ "testpub_tbl_both_filters_c_not_null" NOT NULL "c"
DROP TABLE testpub_tbl_both_filters;
DROP PUBLICATION testpub_both_filters;
@@ -1170,6 +1181,8 @@ Publications:
"testpib_ins_trunct"
"testpub_default"
"testpub_fortbl"
+Not-null constraints:
+ "testpub_tbl1_id_not_null" NOT NULL "id"
\dRp+ testpub_default
Publication testpub_default
@@ -1195,6 +1208,8 @@ Indexes:
Publications:
"testpib_ins_trunct"
"testpub_fortbl"
+Not-null constraints:
+ "testpub_tbl1_id_not_null" NOT NULL "id"
-- verify relation cache invalidation when a primary key is added using
-- an existing index
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
index e9d7315a9c..b9b8dde018 100644
--- a/src/test/regress/expected/replica_identity.out
+++ b/src/test/regress/expected/replica_identity.out
@@ -174,6 +174,10 @@ Indexes:
"test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text
"test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE
"test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb)
+Not-null constraints:
+ "test_replica_identity_id_not_null" NOT NULL "id"
+ "test_replica_identity_keya_not_null" NOT NULL "keya"
+ "test_replica_identity_keyb_not_null" NOT NULL "keyb"
Replica Identity: FULL
ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING;
@@ -231,6 +235,9 @@ Indexes:
-- used as replica identity.
ALTER TABLE test_replica_identity3 ALTER COLUMN id DROP NOT NULL;
ERROR: column "id" is in index used as replica identity
+-- but it's OK when the identity is FULL
+ALTER TABLE test_replica_identity3 REPLICA IDENTITY FULL;
+ALTER TABLE test_replica_identity3 ALTER COLUMN id DROP NOT NULL;
--
-- Test that replica identity can be set on an index that's not yet valid.
-- (This matches the way pg_dump will try to dump a partitioned table.)
@@ -253,6 +260,8 @@ ALTER TABLE ONLY test_replica_identity4_1
Partition key: LIST (id)
Indexes:
"test_replica_identity4_pkey" PRIMARY KEY, btree (id) INVALID REPLICA IDENTITY
+Not-null constraints:
+ "test_replica_identity4_id_not_null" NOT NULL "id"
Partitions: test_replica_identity4_1 FOR VALUES IN (1)
ALTER INDEX test_replica_identity4_pkey
@@ -265,11 +274,26 @@ ALTER INDEX test_replica_identity4_pkey
Partition key: LIST (id)
Indexes:
"test_replica_identity4_pkey" PRIMARY KEY, btree (id) REPLICA IDENTITY
+Not-null constraints:
+ "test_replica_identity4_id_not_null" NOT NULL "id"
Partitions: test_replica_identity4_1 FOR VALUES IN (1)
+-- Dropping the primary key is not allowed if that would leave the replica
+-- identity as nullable
+CREATE TABLE test_replica_identity5 (a int not null, b int, c int,
+ PRIMARY KEY (b, c));
+CREATE UNIQUE INDEX test_replica_identity5_a_b_key ON test_replica_identity5 (a, b);
+ALTER TABLE test_replica_identity5 REPLICA IDENTITY USING INDEX test_replica_identity5_a_b_key;
+ALTER TABLE test_replica_identity5 DROP CONSTRAINT test_replica_identity5_pkey;
+ALTER TABLE test_replica_identity5 ALTER b SET NOT NULL;
+ALTER TABLE test_replica_identity5 DROP CONSTRAINT test_replica_identity5_pkey;
+ERROR: constraint "test_replica_identity5_pkey" of relation "test_replica_identity5" does not exist
+ALTER TABLE test_replica_identity5 ALTER b DROP NOT NULL;
+ERROR: column "b" is in index used as replica identity
DROP TABLE test_replica_identity;
DROP TABLE test_replica_identity2;
DROP TABLE test_replica_identity3;
DROP TABLE test_replica_identity4;
+DROP TABLE test_replica_identity5;
DROP TABLE test_replica_identity_othertable;
DROP TABLE test_replica_identity_t3;
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 319190855b..4ccf98d8e9 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -955,6 +955,8 @@ Policies:
POLICY "pp1r" AS RESTRICTIVE
TO regress_rls_dave
USING ((cid < 55))
+Not-null constraints:
+ "part_document_dlevel_not_null" NOT NULL "dlevel"
Partitions: part_document_fiction FOR VALUES FROM (11) TO (12),
part_document_nonfiction FOR VALUES FROM (99) TO (100),
part_document_satire FOR VALUES FROM (55) TO (56)
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index c5dd43a15c..637e3dac38 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -920,14 +920,6 @@ insert into parent values (NULL);
insert into child (a, b) values (NULL, 'foo');
alter table only parent alter a set not null;
alter table child alter a set not null;
-delete from parent;
-alter table only parent alter a set not null;
-insert into parent values (NULL);
-alter table child alter a set not null;
-insert into child (a, b) values (NULL, 'foo');
-delete from child;
-alter table child alter a set not null;
-insert into child (a, b) values (NULL, 'foo');
drop table child;
drop table parent;
@@ -2125,6 +2117,7 @@ DROP TABLE tt9;
-- Check that comments on constraints and indexes are not lost at ALTER TABLE.
CREATE TABLE comment_test (
id int,
+ constraint id_notnull_constraint not null id,
positive_col int CHECK (positive_col > 0),
indexed_col int,
CONSTRAINT comment_test_pk PRIMARY KEY (id));
@@ -2134,6 +2127,7 @@ COMMENT ON COLUMN comment_test.id IS 'Column ''id'' on comment_test';
COMMENT ON INDEX comment_test_index IS 'Simple index on comment_test';
COMMENT ON CONSTRAINT comment_test_positive_col_check ON comment_test IS 'CHECK constraint on comment_test.positive_col';
COMMENT ON CONSTRAINT comment_test_pk ON comment_test IS 'PRIMARY KEY constraint of comment_test';
+COMMENT ON CONSTRAINT id_notnull_constraint ON comment_test IS 'NOT NULL constraint of comment_test';
COMMENT ON INDEX comment_test_pk IS 'Index backing the PRIMARY KEY of comment_test';
SELECT col_description('comment_test'::regclass, 1) as comment;
@@ -2347,6 +2341,9 @@ CREATE TABLE atnotnull1 ();
ALTER TABLE atnotnull1
ADD COLUMN a INT,
ALTER a SET NOT NULL;
+ALTER TABLE atnotnull1
+ ADD COLUMN b INT,
+ ADD NOT NULL b;
ALTER TABLE atnotnull1
ADD COLUMN c INT,
ADD PRIMARY KEY (c);
@@ -2487,6 +2484,14 @@ ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1);
SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0;
SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a';
+-- check that NOT NULL NO INHERIT cannot be merged to a normal NOT NULL
+CREATE TABLE part_fail (a int NOT NULL NO INHERIT,
+ b char(2) COLLATE "C",
+ CONSTRAINT check_a CHECK (a > 0)
+);
+ALTER TABLE list_parted ATTACH PARTITION part_fail FOR VALUES IN (2);
+DROP TABLE part_fail;
+
-- check that the new partition won't overlap with an existing partition
CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS);
ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1);
@@ -2837,6 +2842,12 @@ ALTER TABLE list_parted2 ALTER b SET NOT NULL, ADD CONSTRAINT check_a2 CHECK (a
ALTER TABLE part_2 ALTER b DROP NOT NULL;
ALTER TABLE part_2 DROP CONSTRAINT check_a2;
+-- can't drop NOT NULL from under an invalid PK
+CREATE TABLE list_parted3 (a int NOT NULL) PARTITION BY LIST (a);
+CREATE TABLE list_parted3_1 PARTITION OF list_parted3 FOR VALUES IN (1);
+ALTER TABLE ONLY list_parted3 ADD PRIMARY KEY (a);
+ALTER TABLE ONLY list_parted3 DROP CONSTRAINT list_parted3_a_not_null;
+
-- Doesn't make sense to add NO INHERIT constraints on partitioned tables
ALTER TABLE list_parted2 add constraint check_b2 check (b <> 'zz') NO INHERIT;
@@ -2857,7 +2868,7 @@ ALTER TABLE list_parted DROP COLUMN b;
SELECT * FROM list_parted;
-- cleanup
-DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE list_parted, list_parted2, range_parted, list_parted3;
DROP TABLE fail_def_part;
DROP TABLE hash_parted;
diff --git a/src/test/regress/sql/constraints.sql b/src/test/regress/sql/constraints.sql
index e3e3bea709..e607eb1fdd 100644
--- a/src/test/regress/sql/constraints.sql
+++ b/src/test/regress/sql/constraints.sql
@@ -6,6 +6,7 @@
-- - PRIMARY KEY clauses
-- - UNIQUE clauses
-- - EXCLUDE clauses
+-- - NOT NULL clauses
--
-- directory paths are passed to us in environment variables
@@ -597,6 +598,190 @@ ALTER TABLE deferred_excl ADD EXCLUDE (f1 WITH =);
DROP TABLE deferred_excl;
+-- verify constraints created for NOT NULL clauses
+CREATE TABLE notnull_tbl1 (a INTEGER NOT NULL NOT NULL);
+\d+ notnull_tbl1
+-- no-op
+ALTER TABLE notnull_tbl1 ADD CONSTRAINT nn NOT NULL a;
+\d+ notnull_tbl1
+-- duplicate name
+ALTER TABLE notnull_tbl1 ADD COLUMN b INT CONSTRAINT notnull_tbl1_a_not_null NOT NULL;
+-- DROP NOT NULL gets rid of both the attnotnull flag and the constraint itself
+ALTER TABLE notnull_tbl1 ALTER a DROP NOT NULL;
+\d+ notnull_tbl1
+-- SET NOT NULL puts both back
+ALTER TABLE notnull_tbl1 ALTER a SET NOT NULL;
+\d+ notnull_tbl1
+-- Doing it twice doesn't create a redundant constraint
+ALTER TABLE notnull_tbl1 ALTER a SET NOT NULL;
+select conname, contype, conkey from pg_constraint where conrelid = 'notnull_tbl1'::regclass;
+-- Using the "table constraint" syntax also works
+ALTER TABLE notnull_tbl1 ALTER a DROP NOT NULL;
+ALTER TABLE notnull_tbl1 ADD CONSTRAINT foobar NOT NULL a;
+\d+ notnull_tbl1
+DROP TABLE notnull_tbl1;
+
+-- Verify that constraint names and NO INHERIT are properly considered when
+-- multiple constraint are specified, either explicitly or via SERIAL/PK/etc,
+-- and that conflicting cases are rejected. Mind that table constraints
+-- handle this separately from column constraints.
+create table notnull_tbl1 (a int primary key constraint foo not null);
+\d+ notnull_tbl1
+create table notnull_tbl2 (a serial, constraint foo not null a);
+\d+ notnull_tbl2
+create table notnull_tbl3 (constraint foo not null a, a int generated by default as identity);
+\d+ notnull_tbl3
+create table notnull_tbl4 (a int not null constraint foo not null);
+\d+ notnull_tbl4
+create table notnull_tbl5 (a int constraint foo not null constraint foo not null);
+\d+ notnull_tbl5
+create table notnull_tbl6 (like notnull_tbl1, constraint foo not null a);
+\d+ notnull_tbl6
+drop table notnull_tbl2, notnull_tbl3, notnull_tbl4, notnull_tbl5, notnull_tbl6;
+
+-- error cases:
+create table notnull_tbl_fail (a serial constraint foo not null constraint bar not null);
+create table notnull_tbl_fail (a serial constraint foo not null no inherit constraint foo not null);
+create table notnull_tbl_fail (a int constraint foo not null, constraint foo not null a no inherit);
+create table notnull_tbl_fail (a serial constraint foo not null, constraint bar not null a);
+create table notnull_tbl_fail (a serial, constraint foo not null a, constraint bar not null a);
+create table notnull_tbl_fail (a serial, constraint foo not null a no inherit);
+create table notnull_tbl_fail (a serial not null no inherit);
+create table notnull_tbl_fail (like notnull_tbl1, constraint foo2 not null a);
+create table notnull_tbl_fail (a int primary key constraint foo not null no inherit);
+create table notnull_tbl_fail (a int not null no inherit primary key);
+create table notnull_tbl_fail (a int primary key, not null a no inherit);
+create table notnull_tbl_fail (a int, primary key(a), not null a no inherit);
+create table notnull_tbl_fail (a int generated by default as identity, constraint foo not null a no inherit);
+create table notnull_tbl_fail (a int generated by default as identity not null no inherit);
+
+drop table notnull_tbl1;
+
+-- NOT NULL NO INHERIT
+CREATE TABLE ATACC1 (a int, NOT NULL a NO INHERIT);
+CREATE TABLE ATACC2 () INHERITS (ATACC1);
+\d+ ATACC2
+DROP TABLE ATACC1, ATACC2;
+CREATE TABLE ATACC1 (a int);
+ALTER TABLE ATACC1 ADD NOT NULL a NO INHERIT;
+CREATE TABLE ATACC2 () INHERITS (ATACC1);
+\d+ ATACC2
+DROP TABLE ATACC1, ATACC2;
+CREATE TABLE ATACC1 (a int);
+CREATE TABLE ATACC2 () INHERITS (ATACC1);
+ALTER TABLE ATACC1 ADD NOT NULL a NO INHERIT;
+\d+ ATACC2
+CREATE TABLE ATACC3 (PRIMARY KEY (a)) INHERITS (ATACC1);
+\d+ ATACC3
+DROP TABLE ATACC1, ATACC2, ATACC3;
+
+-- NOT NULL NO INHERIT is not possible on partitioned tables
+CREATE TABLE ATACC1 (a int NOT NULL NO INHERIT) PARTITION BY LIST (a);
+CREATE TABLE ATACC1 (a int, NOT NULL a NO INHERIT) PARTITION BY LIST (a);
+
+-- it's not possible to override a no-inherit constraint with an inheritable one
+CREATE TABLE ATACC2 (a int, CONSTRAINT a_is_not_null NOT NULL a NO INHERIT);
+CREATE TABLE ATACC1 (a int);
+CREATE TABLE ATACC3 (a int) INHERITS (ATACC2);
+ALTER TABLE ATACC2 INHERIT ATACC1;
+-- can't override
+ALTER TABLE ATACC1 ADD CONSTRAINT ditto NOT NULL a;
+-- dropping the NO INHERIT constraint allows this to work
+ALTER TABLE ATACC2 DROP CONSTRAINT a_is_not_null;
+ALTER TABLE ATACC1 ADD CONSTRAINT ditto NOT NULL a;
+\d+ ATACC3
+DROP TABLE ATACC1, ATACC2, ATACC3;
+
+-- Can't have two constraints with the same name
+CREATE TABLE notnull_tbl2 (a INTEGER CONSTRAINT blah NOT NULL, b INTEGER CONSTRAINT blah NOT NULL);
+
+-- can't drop not-null in primary key
+CREATE TABLE notnull_tbl2 (a INTEGER PRIMARY KEY);
+ALTER TABLE notnull_tbl2 ALTER a DROP NOT NULL;
+DROP TABLE notnull_tbl2;
+
+CREATE TABLE notnull_tbl3 (a INTEGER NOT NULL, CHECK (a IS NOT NULL));
+ALTER TABLE notnull_tbl3 ALTER A DROP NOT NULL;
+ALTER TABLE notnull_tbl3 ADD b int, ADD CONSTRAINT pk PRIMARY KEY (a, b);
+\d notnull_tbl3
+ALTER TABLE notnull_tbl3 DROP CONSTRAINT pk;
+\d notnull_tbl3
+
+-- Primary keys cause not-null constraints to be created.
+CREATE TABLE cnn_pk (a int, b int);
+CREATE TABLE cnn_pk_child () INHERITS (cnn_pk);
+ALTER TABLE cnn_pk ADD CONSTRAINT cnn_primarykey PRIMARY KEY (b);
+\d+ cnn_pk*
+ALTER TABLE cnn_pk DROP CONSTRAINT cnn_primarykey;
+\d+ cnn_pk*
+DROP TABLE cnn_pk, cnn_pk_child;
+
+-- As above, but create the primary key ahead of time
+CREATE TABLE cnn_pk (a int, b int, CONSTRAINT cnn_primarykey PRIMARY KEY (b));
+CREATE TABLE cnn_pk_child () INHERITS (cnn_pk);
+\d+ cnn_pk*
+ALTER TABLE cnn_pk DROP CONSTRAINT cnn_primarykey;
+\d+ cnn_pk*
+DROP TABLE cnn_pk, cnn_pk_child;
+
+-- As above, but create the primary key using a UNIQUE index
+CREATE TABLE cnn_pk (a int, b int);
+CREATE UNIQUE INDEX cnn_uq ON cnn_pk (b);
+CREATE TABLE cnn_pk_child () INHERITS (cnn_pk);
+ALTER TABLE cnn_pk ADD CONSTRAINT cnn_primarykey PRIMARY KEY USING INDEX cnn_uq;
+\d+ cnn_pk*
+DROP TABLE cnn_pk, cnn_pk_child;
+
+-- Unique constraints don't give raise to not-null constraints, however.
+create table cnn_uq (a int);
+alter table cnn_uq add unique (a);
+\d+ cnn_uq
+drop table cnn_uq;
+create table cnn_uq (a int);
+create unique index cnn_uq_idx on cnn_uq (a);
+alter table cnn_uq add unique using index cnn_uq_idx;
+\d+ cnn_uq
+
+-- Ensure partitions are scanned for null values when adding a PK
+create table cnn2_parted(a int) partition by list (a);
+create table cnn_part1 partition of cnn2_parted for values in (1, null);
+insert into cnn_part1 values (null);
+alter table cnn2_parted add primary key (a);
+drop table cnn2_parted;
+
+-- columns in regular and LIKE inheritance should be marked not-nullable
+-- for primary keys, even if those are deferred
+CREATE TABLE notnull_tbl4 (a INTEGER PRIMARY KEY INITIALLY DEFERRED);
+CREATE TABLE notnull_tbl4_lk (LIKE notnull_tbl4);
+CREATE TABLE notnull_tbl4_lk2 (LIKE notnull_tbl4 INCLUDING INDEXES);
+CREATE TABLE notnull_tbl4_lk3 (LIKE notnull_tbl4 INCLUDING INDEXES, NOT NULL a);
+ALTER TABLE notnull_tbl4_lk3 RENAME CONSTRAINT notnull_tbl4_a_not_null TO a_nn;
+CREATE TABLE notnull_tbl4_cld () INHERITS (notnull_tbl4);
+CREATE TABLE notnull_tbl4_cld2 (PRIMARY KEY (a) DEFERRABLE) INHERITS (notnull_tbl4);
+CREATE TABLE notnull_tbl4_cld3 (PRIMARY KEY (a) DEFERRABLE, CONSTRAINT a_nn NOT NULL a) INHERITS (notnull_tbl4);
+\d+ notnull_tbl4
+\d+ notnull_tbl4_lk
+\d+ notnull_tbl4_lk2
+\d+ notnull_tbl4_lk3
+\d+ notnull_tbl4_cld
+\d+ notnull_tbl4_cld2
+\d+ notnull_tbl4_cld3
+-- leave these tables around for pg_upgrade testing
+
+-- It's possible to remove a constraint from parents without affecting children
+CREATE TABLE notnull_tbl5 (a int CONSTRAINT ann NOT NULL,
+ b int CONSTRAINT bnn NOT NULL);
+CREATE TABLE notnull_tbl5_child () INHERITS (notnull_tbl5);
+ALTER TABLE ONLY notnull_tbl5 DROP CONSTRAINT ann;
+ALTER TABLE ONLY notnull_tbl5 ALTER b DROP NOT NULL;
+\d+ notnull_tbl5_child
+CREATE TABLE notnull_tbl6 (a int CONSTRAINT ann NOT NULL,
+ b int CONSTRAINT bnn NOT NULL, check (a > 0)) PARTITION BY LIST (a);
+CREATE TABLE notnull_tbl6_1 PARTITION OF notnull_tbl6 FOR VALUES IN (1);
+ALTER TABLE ONLY notnull_tbl6 DROP CONSTRAINT ann;
+ALTER TABLE ONLY notnull_tbl6 ALTER b DROP NOT NULL;
+\d+ notnull_tbl6_1
+
-- Comments
-- Setup a low-level role to enforce non-superuser checks.
CREATE ROLE regress_constraint_comments;
diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql
index 04008a027b..dea8942c71 100644
--- a/src/test/regress/sql/create_table_like.sql
+++ b/src/test/regress/sql/create_table_like.sql
@@ -194,9 +194,10 @@ ROLLBACK;
DROP TABLE ctlt1, ctlt2, ctlt3, ctlt4, ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctla, ctlb CASCADE;
-- LIKE must respect NO INHERIT property of constraints
-CREATE TABLE noinh_con_copy (a int CHECK (a > 0) NO INHERIT);
+CREATE TABLE noinh_con_copy (a int CHECK (a > 0) NO INHERIT, b int not null,
+ c int not null no inherit);
CREATE TABLE noinh_con_copy1 (LIKE noinh_con_copy INCLUDING CONSTRAINTS);
-\d noinh_con_copy1
+\d+ noinh_con_copy1
-- fail, as partitioned tables don't allow NO INHERIT constraints
CREATE TABLE noinh_con_copy1_parted (LIKE noinh_con_copy INCLUDING ALL)
diff --git a/src/test/regress/sql/index_including.sql b/src/test/regress/sql/index_including.sql
index 11c95974ec..43bb6ea585 100644
--- a/src/test/regress/sql/index_including.sql
+++ b/src/test/regress/sql/index_including.sql
@@ -68,7 +68,7 @@ DROP TABLE tbl;
CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box,
CONSTRAINT covering PRIMARY KEY(c1,c2) INCLUDE(c3,c4));
SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid;
-SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid AND contype = 'p';
-- ensure that constraint works
INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
INSERT INTO tbl SELECT 1, NULL, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
@@ -95,7 +95,7 @@ DROP TABLE tbl;
CREATE TABLE tbl (c1 int,c2 int, c3 int, c4 box,
PRIMARY KEY(c1,c2) INCLUDE(c3,c4));
SELECT indexrelid::regclass, indnatts, indnkeyatts, indisunique, indisprimary, indkey, indclass FROM pg_index WHERE indrelid = 'tbl'::regclass::oid;
-SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid;
+SELECT pg_get_constraintdef(oid), conname, conkey FROM pg_constraint WHERE conrelid = 'tbl'::regclass::oid AND contype = 'p';
-- ensure that constraint works
INSERT INTO tbl SELECT 1, 2, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
INSERT INTO tbl SELECT 1, NULL, 3*x, box('4,4,4,4') FROM generate_series(1,10) AS x;
diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sql
index 04834441db..b5cb01c2d7 100644
--- a/src/test/regress/sql/indexing.sql
+++ b/src/test/regress/sql/indexing.sql
@@ -592,7 +592,7 @@ create table idxpart2 partition of idxpart for values from (0) to (1000) partiti
create table idxpart21 partition of idxpart2 for values from (0) to (1000);
select conname, contype, conrelid::regclass, conindid::regclass, conkey
from pg_constraint where conrelid::regclass::text like 'idxpart%'
- order by conname;
+ order by conrelid::regclass::text, conname;
drop table idxpart;
-- If a partitioned table has a unique/PK constraint, then it's not possible
@@ -671,7 +671,6 @@ alter table only idxpart add primary key (a); -- fail, no not-null constraint
alter table idxpart0 alter column a set not null;
alter table only idxpart add primary key (a); -- now it works
alter index idxpart_pkey attach partition idxpart0_a_key;
-alter table idxpart0 alter column a drop not null; -- fail, pkey needs it
drop table idxpart;
-- if a partition has a unique index without a constraint, does not attach
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index 51251b0e51..f51c70d6b0 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -96,6 +96,9 @@ SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid;
-- Confirm PRIMARY KEY adds NOT NULL constraint to child table
CREATE TEMP TABLE z (b TEXT, PRIMARY KEY(aa, b)) inherits (a);
INSERT INTO z VALUES (NULL, 'text'); -- should fail
+-- ... but not UNIQUE.
+CREATE TEMP TABLE z2 (b TEXT, UNIQUE(aa, b)) inherits (a);
+INSERT INTO z2 VALUES (NULL, 'text'); -- should work
-- Check inherited UPDATE with first child excluded
create table some_tab (f1 int, f2 int, f3 int, check (f1 < 10) no inherit);
@@ -767,6 +770,281 @@ select * from cnullparent;
select * from cnullparent where f1 = 2;
drop table cnullparent cascade;
+--
+-- Test inheritance of NOT NULL constraints
+--
+create table pp1 (f1 int);
+create table cc1 (f2 text, f3 int) inherits (pp1);
+create table cc2 (f4 float) inherits (pp1,cc1);
+create table cc3 () inherits (pp1,cc1,cc2);
+alter table pp1 alter f1 set not null;
+\d+ cc3
+alter table cc3 no inherit pp1;
+alter table cc3 no inherit cc1;
+alter table cc3 no inherit cc2;
+\d+ cc3
+drop table cc3;
+
+-- 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
+
+-- cannot create table with inconsistent NO INHERIT constraint
+create table cc3 (a2 int not null no inherit) inherits (cc1);
+
+-- change NO INHERIT status of inherited constraint: no dice, it's inherited
+alter table cc2 add not null a2 no inherit;
+
+-- remove constraint from cc2: no dice, it's inherited
+alter table cc2 alter column a2 drop not null;
+
+-- remove constraint from cc1, should succeed
+alter table cc1 alter column a2 drop not null;
+\d+ cc1
+
+-- same for cc2
+alter table cc2 alter column f1 drop not null;
+\d+ cc2
+
+-- 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;
+\d+ pp1
+
+alter table pp1 add primary key (f1);
+-- Leave these tables around, for pg_upgrade testing
+
+-- test that removing inheritance of NOT NULL NO INHERIT works correctly
+create table inh_parent (f1 int not null no inherit, f2 int not null no inherit);
+create table inh_child (f1 int not null no inherit, f2 int);
+alter table inh_child inherit inh_parent;
+alter table inh_child no inherit inh_parent;
+\d+ inh_child
+drop table inh_parent, inh_child;
+
+-- test that inhcount is updated correctly through multiple inheritance
+create table inh_pp1 (f1 int);
+create table inh_cc1 (f2 text, f3 int) inherits (inh_pp1);
+create table inh_cc2(f4 float) inherits(inh_pp1,inh_cc1);
+alter table inh_pp1 alter column f1 set not null;
+alter table inh_cc2 no inherit inh_pp1;
+alter table inh_cc2 no inherit inh_cc1;
+\d+ inh_cc2
+drop table inh_pp1, inh_cc1, inh_cc2;
+
+create table inh_pp1 (f1 int not null);
+create table inh_cc1 (f2 text, f3 int) inherits (inh_pp1);
+create table inh_cc2(f4 float) inherits(inh_pp1,inh_cc1);
+alter table inh_pp1 alter column f1 drop not null;
+\d+ inh_cc2
+drop table inh_pp1, inh_cc1, inh_cc2;
+
+
+-- Test a not-null addition that must walk down the hierarchy
+CREATE TABLE inh_parent ();
+CREATE TABLE inh_child (i int) INHERITS (inh_parent);
+CREATE TABLE inh_grandchild () INHERITS (inh_parent, inh_child);
+ALTER TABLE inh_parent ADD COLUMN i int NOT NULL;
+drop table inh_parent, inh_child, inh_grandchild;
+
+-- Test the same constraint name for different columns in different parents
+create table inh_parent1(a int constraint nn not null);
+create table inh_parent2(b int constraint nn not null);
+create table inh_child1 () inherits (inh_parent1, inh_parent2);
+\d+ inh_child1
+
+create table inh_child2 (constraint foo not null a) inherits (inh_parent1, inh_parent2);
+alter table inh_child2 no inherit inh_parent2;
+\d+ inh_child2
+
+drop table inh_parent1, inh_parent2, inh_child1, inh_child2;
+
+-- Test multiple parents with overlapping primary keys
+create table inh_parent1(a int, b int, c int, primary key (a, b));
+create table inh_parent2(d int, e int, b int, primary key (d, b));
+create table inh_child() inherits (inh_parent1, inh_parent2);
+select conrelid::regclass, conname, contype, conkey,
+ coninhcount, conislocal, connoinherit
+ from pg_constraint where contype in ('n','p') and
+ conrelid::regclass::text in ('inh_child', 'inh_parent1', 'inh_parent2')
+ order by 1, 2;
+\d+ inh_child
+drop table inh_parent1, inh_parent2, inh_child;
+
+-- NOT NULL NO INHERIT
+create table inh_nn_parent(a int);
+create table inh_nn_child() inherits (inh_nn_parent);
+alter table inh_nn_parent add not null a no inherit;
+create table inh_nn_child2() inherits (inh_nn_parent);
+select conrelid::regclass, conname, contype, conkey,
+ (select attname from pg_attribute where attrelid = conrelid and attnum = conkey[1]),
+ coninhcount, conislocal, connoinherit
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass::text like 'inh\_nn\_%'
+ order by 2, 1;
+\d+ inh_nn*
+drop table inh_nn_parent, inh_nn_child, inh_nn_child2;
+
+CREATE TABLE inh_nn_parent (a int, NOT NULL a NO INHERIT);
+CREATE TABLE inh_nn_child() INHERITS (inh_nn_parent);
+ALTER TABLE inh_nn_parent ADD CONSTRAINT nna NOT NULL a;
+ALTER TABLE inh_nn_parent ALTER a SET NOT NULL;
+DROP TABLE inh_nn_parent cascade;
+
+-- Adding a PK at the top level of a hierarchy should cause all descendants
+-- to be checked for nulls, even past a no-inherit constraint
+CREATE TABLE inh_nn_lvl1 (a int);
+CREATE TABLE inh_nn_lvl2 () INHERITS (inh_nn_lvl1);
+CREATE TABLE inh_nn_lvl3 (CONSTRAINT foo NOT NULL a NO INHERIT) INHERITS (inh_nn_lvl2);
+ALTER TABLE inh_nn_lvl1 ADD PRIMARY KEY (a);
+DROP TABLE inh_nn_lvl1, inh_nn_lvl2, inh_nn_lvl3;
+
+-- Disallow specifying conflicting NO INHERIT flags for the same constraint
+CREATE TABLE inh_nn1 (a int primary key, b int, not null a no inherit);
+CREATE TABLE inh_nn1 (a int not null);
+CREATE TABLE inh_nn2 (a int not null no inherit) INHERITS (inh_nn1);
+CREATE TABLE inh_nn3 (a int not null, b int, not null a no inherit);
+CREATE TABLE inh_nn4 (a int not null no inherit, b int, not null a);
+DROP TABLE inh_nn1, inh_nn2, inh_nn3, inh_nn4;
+
+--
+-- test inherit/deinherit
+--
+create table inh_parent(f1 int);
+create table inh_child1(f1 int not null);
+create table inh_child2(f1 int);
+
+-- inh_child1 should have not null constraint
+alter table inh_child1 inherit inh_parent;
+
+-- should fail, missing NOT NULL constraint
+alter table inh_child2 inherit inh_child1;
+
+alter table inh_child2 alter column f1 set not null;
+alter table inh_child2 inherit inh_child1;
+
+-- add NOT NULL constraint recursively
+alter table inh_parent alter column f1 set not null;
+
+\d+ inh_parent
+\d+ inh_child1
+\d+ inh_child2
+
+select conrelid::regclass, conname, contype, coninhcount, conislocal
+ from pg_constraint where contype = 'n' and
+ conrelid in ('inh_parent'::regclass, 'inh_child1'::regclass, 'inh_child2'::regclass)
+ order by 2, 1;
+
+--
+-- test deinherit procedure
+--
+
+-- deinherit inh_child1
+create table inh_child3 () inherits (inh_child1);
+alter table inh_child1 no inherit inh_parent;
+\d+ inh_parent
+\d+ inh_child1
+\d+ inh_child2
+select conrelid::regclass, conname, contype, coninhcount, conislocal
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass::text in ('inh_parent', 'inh_child1', 'inh_child2', 'inh_child3')
+ order by 2, 1;
+drop table inh_parent, inh_child1, inh_child2, inh_child3;
+
+-- ALTER TABLE INHERIT ensures that the child has not-null constraints
+create table inh_parent (a int not null);
+create table inh_child (a int);
+alter table inh_child inherit inh_parent; -- nope
+drop table inh_parent, inh_child;
+
+-- Can't merge a NO INHERIT constraint with a normal one
+create table inh_parent (a int not null);
+create table inh_child (a int not null no inherit);
+alter table inh_child inherit inh_parent;
+drop table inh_parent, inh_child;
+
+-- don't interfere with other types of constraints
+create table inh_parent (a int primary key);
+create table inh_child (a int primary key) inherits (inh_parent);
+alter table inh_parent add constraint inh_parent_excl exclude ((1) with =);
+alter table inh_parent add constraint inh_parent_uq unique (a);
+alter table inh_parent add constraint inh_parent_fk foreign key (a) references inh_parent (a);
+create table inh_child2 () inherits (inh_parent);
+create table inh_child3 (like inh_parent);
+alter table inh_child3 inherit inh_parent;
+select conrelid::regclass, conname, contype, coninhcount, conislocal
+ from pg_constraint
+ where conrelid::regclass::text in ('inh_parent', 'inh_child', 'inh_child2', 'inh_child3')
+ order by 2, 1;
+
+drop table inh_parent, inh_child, inh_child2, inh_child3;
+
+--
+-- test multi inheritance tree
+--
+create table inh_parent(f1 int not null);
+create table inh_child1() inherits(inh_parent);
+create table inh_child2() inherits(inh_parent);
+create table inh_child3() inherits(inh_child1, inh_child2);
+
+-- show constraint info
+select conrelid::regclass, conname, contype, coninhcount, conislocal
+ from pg_constraint where contype = 'n' and
+ conrelid in ('inh_parent'::regclass, 'inh_child1'::regclass, 'inh_child2'::regclass, 'inh_child3'::regclass)
+ order by 2, conrelid::regclass::text;
+
+drop table inh_parent cascade;
+
+-- test child table with inherited columns and
+-- with explicitly specified not null constraints
+create table inh_parent_1(f1 int);
+create table inh_parent_2(f2 text);
+create table inh_child(f1 int not null, f2 text not null) inherits(inh_parent_1, inh_parent_2);
+
+-- show constraint info
+select conrelid::regclass, conname, contype, coninhcount, conislocal
+ from pg_constraint where contype = 'n' and
+ conrelid in ('inh_parent_1'::regclass, 'inh_parent_2'::regclass, 'inh_child'::regclass)
+ order by 2, conrelid::regclass::text;
+
+-- also drops inh_child table
+drop table inh_parent_1 cascade;
+drop table inh_parent_2;
+
+-- test multi layer inheritance tree
+create table inh_p1(f1 int not null);
+create table inh_p2(f1 int not null);
+create table inh_p3(f2 int);
+create table inh_p4(f1 int not null, f3 text not null);
+
+create table inh_multiparent() inherits(inh_p1, inh_p2, inh_p3, inh_p4);
+
+-- constraint on f1 should have three parents
+select conrelid::regclass, contype, conname,
+ (select attname from pg_attribute where attrelid = conrelid and attnum = conkey[1]),
+ coninhcount, conislocal
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass in ('inh_p1', 'inh_p2', 'inh_p3', 'inh_p4',
+ 'inh_multiparent')
+ order by conrelid::regclass::text, conname;
+
+create table inh_multiparent2 (a int not null, f1 int) inherits(inh_p3, inh_multiparent);
+select conrelid::regclass, contype, conname,
+ (select attname from pg_attribute where attrelid = conrelid and attnum = conkey[1]),
+ coninhcount, conislocal
+ from pg_constraint where contype = 'n' and
+ conrelid::regclass in ('inh_p3', 'inh_multiparent', 'inh_multiparent2')
+ order by conrelid::regclass::text, conname;
+
+drop table inh_p1, inh_p2, inh_p3, inh_p4 cascade;
+
--
-- Mixed ownership inheritance tree
--
diff --git a/src/test/regress/sql/replica_identity.sql b/src/test/regress/sql/replica_identity.sql
index 039cca25e8..30daec05b7 100644
--- a/src/test/regress/sql/replica_identity.sql
+++ b/src/test/regress/sql/replica_identity.sql
@@ -100,6 +100,9 @@ ALTER TABLE test_replica_identity3 ALTER COLUMN id TYPE bigint;
-- ALTER TABLE DROP NOT NULL is not allowed for columns part of an index
-- used as replica identity.
ALTER TABLE test_replica_identity3 ALTER COLUMN id DROP NOT NULL;
+-- but it's OK when the identity is FULL
+ALTER TABLE test_replica_identity3 REPLICA IDENTITY FULL;
+ALTER TABLE test_replica_identity3 ALTER COLUMN id DROP NOT NULL;
--
-- Test that replica identity can be set on an index that's not yet valid.
@@ -120,9 +123,21 @@ ALTER INDEX test_replica_identity4_pkey
ATTACH PARTITION test_replica_identity4_1_pkey;
\d+ test_replica_identity4
+-- Dropping the primary key is not allowed if that would leave the replica
+-- identity as nullable
+CREATE TABLE test_replica_identity5 (a int not null, b int, c int,
+ PRIMARY KEY (b, c));
+CREATE UNIQUE INDEX test_replica_identity5_a_b_key ON test_replica_identity5 (a, b);
+ALTER TABLE test_replica_identity5 REPLICA IDENTITY USING INDEX test_replica_identity5_a_b_key;
+ALTER TABLE test_replica_identity5 DROP CONSTRAINT test_replica_identity5_pkey;
+ALTER TABLE test_replica_identity5 ALTER b SET NOT NULL;
+ALTER TABLE test_replica_identity5 DROP CONSTRAINT test_replica_identity5_pkey;
+ALTER TABLE test_replica_identity5 ALTER b DROP NOT NULL;
+
DROP TABLE test_replica_identity;
DROP TABLE test_replica_identity2;
DROP TABLE test_replica_identity3;
DROP TABLE test_replica_identity4;
+DROP TABLE test_replica_identity5;
DROP TABLE test_replica_identity_othertable;
DROP TABLE test_replica_identity_t3;