Catalog not-null constraints

We now create contype='n' pg_constraint rows for not-null constraints.

We propagate these constraints to other tables during operations such as
adding inheritance relationships, creating and attaching partitions and
creating tables LIKE other tables.  We also spawn not-null constraints
for inheritance child tables when their parents have primary keys.
These related constraints mostly follow the well-known rules of
conislocal and coninhcount that we have for CHECK constraints, with some
adaptations: for example, as opposed to CHECK constraints, we don't
match not-null ones by name when descending a hierarchy to alter it,
instead matching by column name that they apply to.  This means we don't
require the constraint names to be identical across a hierarchy.

For now, we omit them for system catalogs.  Maybe this is worth
reconsidering.  We don't support NOT VALID nor DEFERRABLE clauses
either; these can be added as separate features later (this patch is
already large and complicated enough.)

psql shows these constraints in \d+.

pg_dump requires some ad-hoc hacks, particularly when dumping a primary
key.  We now create one "throwaway" not-null constraint for each column
in the PK together with the CREATE TABLE command, and once the PK is
created, all those throwaway constraints are removed.  This avoids
having to check each tuple for nullness when the dump restores the
primary key creation.

pg_upgrading from an older release requires a somewhat brittle procedure
to create a constraint state that matches what would be created if the
database were being created fresh in Postgres 17.  I have tested all the
scenarios I could think of, and it works correctly as far as I can tell,
but I could have neglected weird cases.

This patch has been very long in the making.  The first patch was
written by Bernd Helmle in 2010 to add a new pg_constraint.contype value
('n'), which I (Álvaro) then hijacked in 2011 and 2012, until that one
was killed by the realization that we ought to use contype='c' instead:
manufactured CHECK constraints.  However, later SQL standard
development, as well as nonobvious emergent properties of that design
(mostly, failure to distinguish them from "normal" CHECK constraints as
well as the performance implication of having to test the CHECK
expression) led us to reconsider this choice, so now the current
implementation uses contype='n' again.  During Postgres 16 this had
already been introduced by commit e056c557ae, but there were some
problems mainly with the pg_upgrade procedure that couldn't be fixed in
reasonable time, so it was reverted.

In 2016 Vitaly Burovoy also worked on this feature[1] but found no
consensus for his proposed approach, which was claimed to be closer to
the letter of the standard, requiring an additional pg_attribute column
to track the OID of the not-null constraint for that column.
[1] https://postgr.es/m/CAKOSWNkN6HSyatuys8xZxzRCR-KL1OkHS5-b9qd9bf1Rad3PLA@mail.gmail.com

Author: Álvaro Herrera <alvherre@alvh.no-ip.org>
Author: Bernd Helmle <mailings@oopsware.de>
Reviewed-by: Justin Pryzby <pryzby@telsasoft.com>
Reviewed-by: Peter Eisentraut <peter.eisentraut@enterprisedb.com>
Reviewed-by: Dean Rasheed <dean.a.rasheed@gmail.com>
This commit is contained in:
Alvaro Herrera 2023-08-25 13:31:24 +02:00
parent 9c13b6814a
commit b0e96f3119
No known key found for this signature in database
GPG Key ID: 1C20ACB9D5C564AE
50 changed files with 3933 additions and 769 deletions

View File

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

View File

@ -49,6 +49,7 @@ LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_reg
LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0
LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0
LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0
LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0
LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" 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_table name="regtest_schema.regtest_table" permissive=0
LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0
@ -269,6 +270,7 @@ LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_reg
LOG: SELinux: allowed { create } 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_4.y" permissive=0
LOG: SELinux: allowed { create } 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_4.z" permissive=0
LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0
LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0
LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" 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_table name="regtest_schema.regtest_table_4" permissive=0
CREATE INDEX regtest_index_tbl4_y ON regtest_table_4(y);

View File

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

View File

@ -1270,7 +1270,8 @@
<structfield>attnotnull</structfield> <type>bool</type>
</para>
<para>
This represents a not-null constraint.
This column is marked not-null, either by a not-null constraint
or a primary key.
</para></entry>
</row>
@ -2484,13 +2485,10 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
</indexterm>
<para>
The catalog <structname>pg_constraint</structname> stores check, primary
key, unique, foreign key, and exclusion constraints on tables.
The catalog <structname>pg_constraint</structname> 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 are represented in the
<link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>
catalog, not here.
</para>
<para>
@ -2552,6 +2550,7 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
<para>
<literal>c</literal> = check constraint,
<literal>f</literal> = foreign key constraint,
<literal>n</literal> = not-null constraint,
<literal>p</literal> = primary key constraint,
<literal>u</literal> = unique constraint,
<literal>t</literal> = constraint trigger,

View File

@ -650,18 +650,39 @@ CREATE TABLE products (
name text <emphasis>NOT NULL</emphasis>,
price numeric
);
</programlisting>
An explicit constraint name can also be specified, for example:
<programlisting>
CREATE TABLE products (
product_no integer NOT NULL,
name text <emphasis>CONSTRAINT products_name_not_null</emphasis> NOT NULL,
price numeric
);
</programlisting>
</para>
<para>
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
<programlisting>
CREATE TABLE products (
product_no integer,
name text,
price numeric,
<emphasis>NOT NULL product_no</emphasis>,
<emphasis>NOT NULL name</emphasis>
);
</programlisting>
But this syntax is not standard and mainly intended for use by
<application>pg_dump</application>.
</para>
<para>
A not-null constraint is functionally equivalent to creating a check
constraint <literal>CHECK (<replaceable>column_name</replaceable>
IS NOT NULL)</literal>, but in
<productname>PostgreSQL</productname> 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.
</para>
<para>
@ -678,6 +699,10 @@ CREATE TABLE products (
order the constraints are checked.
</para>
<para>
However, a column can have at most one explicit not-null constraint.
</para>
<para>
The <literal>NOT NULL</literal> constraint has an inverse: the
<literal>NULL</literal> constraint. This does not mean that the
@ -871,7 +896,7 @@ CREATE TABLE example (
<para>
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
@ -1531,11 +1556,16 @@ ALTER TABLE products ADD CHECK (name &lt;&gt; '');
ALTER TABLE products ADD CONSTRAINT some_name UNIQUE (product_no);
ALTER TABLE products ADD FOREIGN KEY (product_group_id) REFERENCES product_groups;
</programlisting>
To add a not-null constraint, which cannot be written as a table
constraint, use this syntax:
</para>
<para>
To add a not-null constraint, which is normally not written as a table
constraint, this special syntax is available:
<programlisting>
ALTER TABLE products ALTER COLUMN product_no SET NOT NULL;
</programlisting>
This command silently does nothing if the column already has a
not-null constraint.
</para>
<para>
@ -1576,12 +1606,15 @@ ALTER TABLE products DROP CONSTRAINT some_name;
</para>
<para>
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:
<programlisting>
ALTER TABLE products ALTER COLUMN product_no DROP NOT NULL;
</programlisting>
(Recall that not-null constraints do not have names.)
This mirrors the <literal>SET NOT NULL</literal> 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.)
</para>
</sect2>

View File

@ -113,6 +113,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
[ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
{ CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
NOT NULL <replaceable class="parameter">column_name</replaceable> [ NO INHERIT ] |
UNIQUE [ NULLS [ NOT ] DISTINCT ] ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] |
@ -1763,11 +1764,17 @@ ALTER TABLE measurement
<title>Compatibility</title>
<para>
The forms <literal>ADD</literal> (without <literal>USING INDEX</literal>),
The forms <literal>ADD [COLUMN]</literal>,
<literal>DROP [COLUMN]</literal>, <literal>DROP IDENTITY</literal>, <literal>RESTART</literal>,
<literal>SET DEFAULT</literal>, <literal>SET DATA TYPE</literal> (without <literal>USING</literal>),
<literal>SET GENERATED</literal>, and <literal>SET <replaceable>sequence_option</replaceable></literal>
conform with the SQL standard. The other forms are
conform with the SQL standard.
The form <literal>ADD <replaceable>table_constraint</replaceable></literal>
conforms with the SQL standard when the <literal>USING INDEX</literal> and
<literal>NOT VALID</literal> clauses are omitted and the constraint type is
one of <literal>CHECK</literal>, <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>,
or <literal>REFERENCES</literal>.
The other forms are
<productname>PostgreSQL</productname> extensions of the SQL standard.
Also, the ability to specify more than one manipulation in a single
<command>ALTER TABLE</command> command is an extension.

View File

@ -77,6 +77,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
{ CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
NOT NULL <replaceable class="parameter">column_name</replaceable> [ NO INHERIT ] |
UNIQUE [ NULLS [ NOT ] DISTINCT ] ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] |
@ -2314,13 +2315,6 @@ CREATE TABLE cities_partdef
constraint, and index names must be unique across all relations within
the same schema.
</para>
<para>
Currently, <productname>PostgreSQL</productname> does not record names
for <literal>NOT NULL</literal> constraints at all, so they are not
subject to the uniqueness restriction. This might change in a future
release.
</para>
</refsect2>
<refsect2>

View File

@ -1679,7 +1679,7 @@ RemoveAttributeById(Oid relid, AttrNumber attnum)
*/
attStruct->atttypid = InvalidOid;
/* Remove any NOT NULL constraint the column may have */
/* Remove any not-null constraint the column may have */
attStruct->attnotnull = false;
/* We don't want to keep stats for it anymore */
@ -2147,6 +2147,53 @@ 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;
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);
return constrOid;
}
/*
* Store defaults and constraints (passed as a list of CookedConstraint).
*
@ -2191,6 +2238,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);
@ -2248,6 +2303,7 @@ AddRelationNewConstraints(Relation rel,
ParseNamespaceItem *nsitem;
int numchecks;
List *checknames;
List *nnnames;
ListCell *cell;
Node *expr;
CookedConstraint *cooked;
@ -2333,130 +2389,196 @@ AddRelationNewConstraints(Relation rel,
*/
numchecks = numoldchecks;
checknames = NIL;
nnnames = NIL;
foreach(cell, newConstraints)
{
Constraint *cdef = (Constraint *) lfirst(cell);
char *ccname;
Oid constrOid;
if (cdef->contype != CONSTR_CHECK)
continue;
if (cdef->raw_expr != NULL)
if (cdef->contype == CONSTR_CHECK)
{
Assert(cdef->cooked_expr == NULL);
char *ccname;
/*
* Transform raw parsetree to executable expression, and verify
* it's valid as a CHECK constraint.
*/
expr = cookConstraint(pstate, cdef->raw_expr,
RelationGetRelationName(rel));
}
else
{
Assert(cdef->cooked_expr != NULL);
/*
* Here, we assume the parser will only pass us valid CHECK
* expressions, so we do no particular checking.
*/
expr = stringToNode(cdef->cooked_expr);
}
/*
* Check name uniqueness, or generate a name if none was given.
*/
if (cdef->conname != NULL)
{
ListCell *cell2;
ccname = cdef->conname;
/* Check against other new constraints */
/* Needed because we don't do CommandCounterIncrement in loop */
foreach(cell2, checknames)
if (cdef->raw_expr != NULL)
{
if (strcmp((char *) lfirst(cell2), ccname) == 0)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("check constraint \"%s\" already exists",
ccname)));
Assert(cdef->cooked_expr == NULL);
/*
* Transform raw parsetree to executable expression, and
* verify it's valid as a CHECK constraint.
*/
expr = cookConstraint(pstate, cdef->raw_expr,
RelationGetRelationName(rel));
}
else
{
Assert(cdef->cooked_expr != NULL);
/*
* Here, we assume the parser will only pass us valid CHECK
* expressions, so we do no particular checking.
*/
expr = stringToNode(cdef->cooked_expr);
}
/* save name for future checks */
checknames = lappend(checknames, ccname);
/*
* 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.)
* Check name uniqueness, or generate a name if none was given.
*/
if (MergeWithExistingConstraint(rel, ccname, expr,
allow_merge, is_local,
cdef->initially_valid,
cdef->is_no_inherit))
continue;
}
else
{
/*
* When generating a name, we want to create "tab_col_check" for a
* column constraint and "tab_check" for a table constraint. We
* no longer have any info about the syntactic positioning of the
* constraint phrase, so we approximate this by seeing whether the
* expression references more than one column. (If the user
* played by the rules, the result is the same...)
*
* Note: pull_var_clause() doesn't descend into sublinks, but we
* eliminated those above; and anyway this only needs to be an
* approximate answer.
*/
List *vars;
char *colname;
if (cdef->conname != NULL)
{
ListCell *cell2;
vars = pull_var_clause(expr, 0);
ccname = cdef->conname;
/* Check against other new constraints */
/* Needed because we don't do CommandCounterIncrement in loop */
foreach(cell2, checknames)
{
if (strcmp((char *) lfirst(cell2), ccname) == 0)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("check constraint \"%s\" already exists",
ccname)));
}
/* eliminate duplicates */
vars = list_union(NIL, vars);
/* save name for future checks */
checknames = lappend(checknames, ccname);
if (list_length(vars) == 1)
colname = get_attname(RelationGetRelid(rel),
((Var *) linitial(vars))->varattno,
true);
/*
* 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.)
*/
if (MergeWithExistingConstraint(rel, ccname, expr,
allow_merge, is_local,
cdef->initially_valid,
cdef->is_no_inherit))
continue;
}
else
colname = NULL;
{
/*
* When generating a name, we want to create "tab_col_check"
* for a column constraint and "tab_check" for a table
* constraint. We no longer have any info about the syntactic
* positioning of the constraint phrase, so we approximate
* this by seeing whether the expression references more than
* one column. (If the user played by the rules, the result
* is the same...)
*
* Note: pull_var_clause() doesn't descend into sublinks, but
* we eliminated those above; and anyway this only needs to be
* an approximate answer.
*/
List *vars;
char *colname;
ccname = ChooseConstraintName(RelationGetRelationName(rel),
colname,
"check",
RelationGetNamespace(rel),
checknames);
vars = pull_var_clause(expr, 0);
/* save name for future checks */
checknames = lappend(checknames, ccname);
/* eliminate duplicates */
vars = list_union(NIL, vars);
if (list_length(vars) == 1)
colname = get_attname(RelationGetRelid(rel),
((Var *) linitial(vars))->varattno,
true);
else
colname = NULL;
ccname = ChooseConstraintName(RelationGetRelationName(rel),
colname,
"check",
RelationGetNamespace(rel),
checknames);
/* save name for future checks */
checknames = lappend(checknames, ccname);
}
/*
* OK, store it.
*/
constrOid =
StoreRelCheck(rel, ccname, expr, cdef->initially_valid, is_local,
is_local ? 0 : 1, cdef->is_no_inherit, is_internal);
numchecks++;
cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
cooked->contype = CONSTR_CHECK;
cooked->conoid = constrOid;
cooked->name = ccname;
cooked->attnum = 0;
cooked->expr = expr;
cooked->skip_validation = cdef->skip_validation;
cooked->is_local = is_local;
cooked->inhcount = is_local ? 0 : 1;
cooked->is_no_inherit = cdef->is_no_inherit;
cookedConstraints = lappend(cookedConstraints, cooked);
}
else if (cdef->contype == CONSTR_NOTNULL)
{
CookedConstraint *nncooked;
AttrNumber colnum;
char *nnname;
/*
* OK, store it.
*/
constrOid =
StoreRelCheck(rel, ccname, expr, cdef->initially_valid, is_local,
is_local ? 0 : 1, cdef->is_no_inherit, is_internal);
/* Determine which column to modify */
colnum = get_attnum(RelationGetRelid(rel), strVal(linitial(cdef->keys)));
if (colnum == InvalidAttrNumber) /* shouldn't happen */
elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
strVal(linitial(cdef->keys)), RelationGetRelid(rel));
numchecks++;
/*
* If the column already has a not-null constraint, we need only
* update its catalog status and we're done.
*/
if (AdjustNotNullInheritance1(RelationGetRelid(rel), colnum,
cdef->inhcount))
continue;
cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
cooked->contype = CONSTR_CHECK;
cooked->conoid = constrOid;
cooked->name = ccname;
cooked->attnum = 0;
cooked->expr = expr;
cooked->skip_validation = cdef->skip_validation;
cooked->is_local = is_local;
cooked->inhcount = is_local ? 0 : 1;
cooked->is_no_inherit = cdef->is_no_inherit;
cookedConstraints = lappend(cookedConstraints, cooked);
/*
* 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,
cdef->inhcount == 0,
cdef->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 = cdef->inhcount;
nncooked->is_no_inherit = cdef->is_no_inherit;
cookedConstraints = lappend(cookedConstraints, nncooked);
}
}
/*
@ -2626,6 +2748,211 @@ MergeWithExistingConstraint(Relation rel, const char *ccname, Node *expr,
return found;
}
/* list_sort comparator to sort CookedConstraint by attnum */
static int
list_cookedconstr_attnum_cmp(const ListCell *p1, const ListCell *p2)
{
AttrNumber v1 = ((CookedConstraint *) lfirst(p1))->attnum;
AttrNumber v2 = ((CookedConstraint *) lfirst(p2))->attnum;
if (v1 < v2)
return -1;
if (v1 > v2)
return 1;
return 0;
}
/*
* 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.
*
* Note that inherited constraints have two shapes: those coming from another
* not-null constraint in the parent, which have a name already, and those
* coming from a primary key in the parent, which don't. Any name specified
* in a parent is disregarded in case of a conflict.
*
* 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;
ListCell *lc;
/*
* 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.
*/
foreach(lc, constraints)
{
Constraint *constr = lfirst_node(Constraint, lc);
AttrNumber attnum;
char *conname;
bool is_local = true;
int inhcount = 0;
ListCell *lc2;
Assert(constr->contype == CONSTR_NOTNULL);
attnum = get_attnum(RelationGetRelid(rel),
strVal(linitial(constr->keys)));
/*
* Search in the list of inherited constraints for any entries on the
* same column.
*/
foreach(lc2, old_notnulls)
{
CookedConstraint *old = (CookedConstraint *) lfirst(lc2);
if (old->attnum == attnum)
{
inhcount++;
old_notnulls = foreach_delete_current(old_notnulls, lc2);
}
}
/*
* 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(lc2, givennames)
{
if (strcmp(lfirst(lc2), 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, is_local,
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. Because multiple parents could
* specify a not-null constraint for the same column, we must count how
* many there are and add to the original inhcount accordingly, deleting
* elements we've already processed. We sort the list to make it easy.
*
* 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.
*/
list_sort(old_notnulls, list_cookedconstr_attnum_cmp);
for (int outerpos = 0; outerpos < list_length(old_notnulls); outerpos++)
{
CookedConstraint *cooked;
char *conname = NULL;
int add_inhcount = 0;
ListCell *lc2;
cooked = (CookedConstraint *) list_nth(old_notnulls, outerpos);
Assert(cooked->contype == CONSTR_NOTNULL);
/*
* Preserve the first non-conflicting constraint name we come across,
* if any
*/
if (conname == NULL && cooked->name)
conname = cooked->name;
for (int restpos = outerpos + 1; restpos < list_length(old_notnulls);)
{
CookedConstraint *other;
other = (CookedConstraint *) list_nth(old_notnulls, restpos);
if (other->attnum == cooked->attnum)
{
if (conname == NULL && other->name)
conname = other->name;
add_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(lc2, nnnames)
{
if (strcmp(lfirst(lc2), 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);
StoreRelNotNull(rel, conname, cooked->attnum, true,
cooked->is_local, cooked->inhcount + add_inhcount,
cooked->is_no_inherit);
nncols = lappend_int(nncols, cooked->attnum);
}
return nncols;
}
/*
* Update the count of constraints in the relation's pg_class tuple.
*

View File

@ -21,6 +21,7 @@
#include "access/xact.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"
@ -562,6 +563,291 @@ ChooseConstraintName(const char *name1, const char *name2,
return conname;
}
/*
* Find and return the pg_constraint tuple that implements a validated
* not-null constraint for the given column of the given relation.
*
* 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.
*/
HeapTuple
findNotNullConstraint(Oid relid, const char *colname)
{
AttrNumber attnum = get_attnum(relid, colname);
return findNotNullConstraintAttnum(relid, attnum);
}
/*
* 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));
if ((Pointer) arr != DatumGetPointer(adatum))
pfree(arr); /* free de-toasted copy, if any */
return colnum;
}
/*
* AdjustNotNullInheritance1
* Adjust inheritance count for a single not-null constraint
*
* Adjust inheritance count, and possibly islocal status, for the not-null
* constraint row of the given column, if it exists, and return true.
* If no not-null constraint is found for the column, return false.
*/
bool
AdjustNotNullInheritance1(Oid relid, AttrNumber attnum, int count)
{
HeapTuple tup;
tup = findNotNullConstraintAttnum(relid, attnum);
if (HeapTupleIsValid(tup))
{
Relation pg_constraint;
Form_pg_constraint conform;
pg_constraint = table_open(ConstraintRelationId, RowExclusiveLock);
conform = (Form_pg_constraint) GETSTRUCT(tup);
if (count > 0)
conform->coninhcount += count;
/* sanity check */
if (conform->coninhcount < 0)
elog(ERROR, "invalid inhcount %d for constraint \"%s\" on relation \"%s\"",
conform->coninhcount, NameStr(conform->conname),
get_rel_name(relid));
/*
* If the constraints are no longer inherited, mark them local. It's
* arguable that we should drop them instead, but it's hard to see
* that being better. The user can drop it manually later.
*/
if (conform->coninhcount == 0)
conform->conislocal = true;
CatalogTupleUpdate(pg_constraint, &tup->t_self, tup);
table_close(pg_constraint, RowExclusiveLock);
return true;
}
return false;
}
/*
* AdjustNotNullInheritance
* Adjust not-null constraints' inhcount/islocal for
* ALTER TABLE [NO] INHERITS
*
* Mark the NOT NULL constraints for the given relation columns as
* inherited, so that they can't be dropped.
*
* Caller must have checked beforehand that attnotnull was set for all
* columns. However, some of those could be set because of a primary
* key, so throw a proper user-visible error if one is not found.
*/
void
AdjustNotNullInheritance(Oid relid, Bitmapset *columns, int count)
{
Relation pg_constraint;
int attnum;
pg_constraint = table_open(ConstraintRelationId, RowExclusiveLock);
/*
* Scan the set of columns and bump inhcount for each.
*/
attnum = -1;
while ((attnum = bms_next_member(columns, attnum)) >= 0)
{
HeapTuple tup;
Form_pg_constraint conform;
tup = findNotNullConstraintAttnum(relid, attnum);
if (!HeapTupleIsValid(tup))
ereport(ERROR,
errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("column \"%s\" in child table must be marked NOT NULL",
get_attname(relid, attnum,
false)));
conform = (Form_pg_constraint) GETSTRUCT(tup);
conform->coninhcount += count;
if (conform->coninhcount < 0)
elog(ERROR, "invalid inhcount %d for constraint \"%s\" on relation \"%s\"",
conform->coninhcount, NameStr(conform->conname),
get_rel_name(relid));
/*
* If the constraints are no longer inherited, mark them local. It's
* arguable that we should drop them instead, but it's hard to see
* that being better. The user can drop it manually later.
*/
if (conform->coninhcount == 0)
conform->conislocal = true;
CatalogTupleUpdate(pg_constraint, &tup->t_self, tup);
}
table_close(pg_constraint, RowExclusiveLock);
}
/*
* 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.
*
* XXX This is only used to create derived tables, so NO INHERIT constraints
* are always skipped.
*/
List *
RelationGetNotNullConstraints(Oid relid, bool cooked)
{
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)
continue;
colnum = extractNotNullColumn(htup);
if (cooked)
{
CookedConstraint *cooked;
cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
cooked->contype = CONSTR_NOTNULL;
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;
notnulls = lappend(notnulls, constr);
}
}
systable_endscan(conscan);
table_close(constrRel, AccessShareLock);
return notnulls;
}
/*
* Delete a single constraint record.
*/

File diff suppressed because it is too large Load Diff

View File

@ -718,6 +718,11 @@ _outConstraint(StringInfo str, const Constraint *node)
case CONSTR_NOTNULL:
appendStringInfoString(str, "NOT_NULL");
WRITE_NODE_FIELD(keys);
WRITE_INT_FIELD(inhcount);
WRITE_BOOL_FIELD(is_no_inherit);
WRITE_BOOL_FIELD(skip_validation);
WRITE_BOOL_FIELD(initially_valid);
break;
case CONSTR_DEFAULT:

View File

@ -390,10 +390,17 @@ _readConstraint(void)
switch (local_node->contype)
{
case CONSTR_NULL:
case CONSTR_NOTNULL:
/* no extra fields */
break;
case CONSTR_NOTNULL:
READ_NODE_FIELD(keys);
READ_INT_FIELD(inhcount);
READ_BOOL_FIELD(is_no_inherit);
READ_BOOL_FIELD(skip_validation);
READ_BOOL_FIELD(initially_valid);
break;
case CONSTR_DEFAULT:
READ_NODE_FIELD(raw_expr);
READ_STRING_FIELD(cooked_expr);

View File

@ -1644,6 +1644,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);

View File

@ -3837,12 +3837,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
@ -4079,6 +4082,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_c_include opt_definition OptConsTableSpace
ConstraintAttributeSpec
{

View File

@ -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;
@ -346,6 +348,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);
@ -535,6 +538,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
bool saw_default;
bool saw_identity;
bool saw_generated;
bool need_notnull = false;
ListCell *clist;
cxt->columns = lappend(cxt->columns, column);
@ -632,10 +636,8 @@ 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;
}
/* Process column constraints, if any... */
@ -653,7 +655,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
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\"",
@ -665,6 +667,10 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
break;
case CONSTR_NOTNULL:
/*
* Disallow conflicting [NOT] NULL markings
*/
if (saw_nullable && !column->is_not_null)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@ -672,8 +678,25 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
column->colname, cxt->relation->relname),
parser_errposition(cxt->pstate,
constraint->location)));
column->is_not_null = true;
saw_nullable = true;
/* Ignore redundant NOT NULL markings */
/*
* If this is the first time we see this column being marked
* not null, add the constraint entry; and get rid of any
* previous markings to mark the column NOT NULL.
*/
if (!column->is_not_null)
{
column->is_not_null = true;
saw_nullable = true;
constraint->keys = list_make1(makeString(column->colname));
cxt->nnconstraints = lappend(cxt->nnconstraints, constraint);
/* Don't need this anymore, if we had it */
need_notnull = false;
}
break;
case CONSTR_DEFAULT:
@ -723,16 +746,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;
}
@ -838,6 +864,29 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
constraint->location)));
}
/*
* If we need a not-null constraint for SERIAL or IDENTITY, and one was
* not explicitly specified, add one now.
*/
if (need_notnull && !(saw_nullable && column->is_not_null))
{
Constraint *notnull;
column->is_not_null = true;
notnull = makeNode(Constraint);
notnull->contype = CONSTR_NOTNULL;
notnull->conname = NULL;
notnull->deferrable = false;
notnull->initdeferred = false;
notnull->location = -1;
notnull->keys = list_make1(makeString(column->colname));
notnull->skip_validation = false;
notnull->initially_valid = true;
cxt->nnconstraints = lappend(cxt->nnconstraints, notnull);
}
/*
* If needed, generate ALTER FOREIGN TABLE ALTER COLUMN statement to add
* per-column foreign data wrapper options to this column after creation.
@ -907,6 +956,10 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
break;
case CONSTR_NOTNULL:
cxt->nnconstraints = lappend(cxt->nnconstraints, constraint);
break;
case CONSTR_FOREIGN:
if (cxt->isforeign)
ereport(ERROR,
@ -918,7 +971,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:
@ -954,6 +1006,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
AclResult aclresult;
char *comment;
ParseCallbackState pcbstate;
bool process_notnull_constraints = false;
setup_parser_errposition_callback(&pcbstate, cxt->pstate,
table_like_clause->relation->location);
@ -1025,8 +1078,9 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
/*
* 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.
* For constraints, ONLY the not-null constraint is inherited by the
* new column definition per SQL99; however we cannot do that
* correctly here, so we leave it for expandTableLikeClause to handle.
*/
def = makeNode(ColumnDef);
def->colname = pstrdup(attributeName);
@ -1034,7 +1088,9 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
attribute->atttypmod);
def->inhcount = 0;
def->is_local = true;
def->is_not_null = attribute->attnotnull;
def->is_not_null = false;
if (attribute->attnotnull)
process_notnull_constraints = true;
def->is_from_type = false;
def->storage = 0;
def->raw_default = NULL;
@ -1116,19 +1172,77 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
* 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
* called after we do know that; in addition, do that if there are any NOT
* NULL constraints, because those must be propagated even if not
* explicitly requested.
*
* 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 |
CREATE_TABLE_LIKE_GENERATED |
CREATE_TABLE_LIKE_CONSTRAINTS |
CREATE_TABLE_LIKE_INDEXES))
if ((table_like_clause->options &
(CREATE_TABLE_LIKE_DEFAULTS |
CREATE_TABLE_LIKE_GENERATED |
CREATE_TABLE_LIKE_CONSTRAINTS |
CREATE_TABLE_LIKE_INDEXES)) ||
process_notnull_constraints)
{
table_like_clause->relationOid = RelationGetRelid(relation);
cxt->likeclauses = lappend(cxt->likeclauses, table_like_clause);
}
/*
* If INCLUDING INDEXES is not given and a primary key exists, we need to
* add not-null constraints to the columns covered by the PK (except those
* that already have one.) This is required for backwards compatibility.
*/
if ((table_like_clause->options & CREATE_TABLE_LIKE_INDEXES) == 0)
{
Bitmapset *pkcols;
int x = -1;
Bitmapset *donecols = NULL;
ListCell *lc;
/*
* Obtain a bitmapset of columns on which we'll add not-null
* constraints in expandTableLikeClause, so that we skip this for
* those.
*/
foreach(lc, RelationGetNotNullConstraints(RelationGetRelid(relation), true))
{
CookedConstraint *cooked = (CookedConstraint *) lfirst(lc);
donecols = bms_add_member(donecols, cooked->attnum);
}
pkcols = RelationGetIndexAttrBitmap(relation,
INDEX_ATTR_BITMAP_PRIMARY_KEY);
while ((x = bms_next_member(pkcols, x)) >= 0)
{
Constraint *notnull;
AttrNumber attnum = x + FirstLowInvalidHeapAttributeNumber;
Form_pg_attribute attForm;
/* ignore if we already have one for this column */
if (bms_is_member(attnum, donecols))
continue;
attForm = TupleDescAttr(tupleDesc, attnum - 1);
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(makeString(pstrdup(NameStr(attForm->attname))));
notnull->skip_validation = false;
notnull->initially_valid = true;
cxt->nnconstraints = lappend(cxt->nnconstraints, notnull);
}
}
/*
* We may copy extended statistics if requested, since the representation
* of CreateStatsStmt doesn't depend on column numbers.
@ -1195,6 +1309,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
TupleConstr *constr;
AttrMap *attmap;
char *comment;
bool at_pushed = false;
ListCell *lc;
/*
* Open the relation referenced by the LIKE clause. We should still have
@ -1374,6 +1490,20 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
}
}
/*
* Copy not-null constraints, too (these do not require any option to have
* been given).
*/
foreach(lc, RelationGetNotNullConstraints(RelationGetRelid(relation), false))
{
AlterTableCmd *atsubcmd;
atsubcmd = makeNode(AlterTableCmd);
atsubcmd->subtype = AT_AddConstraint;
atsubcmd->def = (Node *) lfirst_node(Constraint, lc);
atsubcmds = lappend(atsubcmds, atsubcmd);
}
/*
* If we generated any ALTER TABLE actions above, wrap them into a single
* ALTER TABLE command. Stick it at the front of the result, so it runs
@ -1388,6 +1518,8 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
atcmd->objtype = OBJECT_TABLE;
atcmd->missing_ok = false;
result = lcons(atcmd, result);
at_pushed = true;
}
/*
@ -1415,6 +1547,39 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
attmap,
NULL);
/*
* The PK columns might not yet non-nullable, so make sure they
* become so.
*/
if (index_stmt->primary)
{
foreach(lc, index_stmt->indexParams)
{
IndexElem *col = lfirst_node(IndexElem, lc);
AlterTableCmd *notnullcmd = makeNode(AlterTableCmd);
notnullcmd->subtype = AT_SetAttNotNull;
notnullcmd->name = pstrdup(col->name);
/* Luckily we can still add more AT-subcmds here */
atsubcmds = lappend(atsubcmds, notnullcmd);
}
/*
* If we had already put the AlterTableStmt into the output
* list, we don't need to do so again; otherwise do it.
*/
if (!at_pushed)
{
AlterTableStmt *atcmd = makeNode(AlterTableStmt);
atcmd->relation = copyObject(heapRel);
atcmd->cmds = atsubcmds;
atcmd->objtype = OBJECT_TABLE;
atcmd->missing_ok = false;
result = lcons(atcmd, result);
}
}
/* Copy comment on index, if requested */
if (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS)
{
@ -1505,8 +1670,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 *
@ -2051,10 +2216,12 @@ 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, in addition we set each column's attnotnull flag true.
* We do not create a separate not-null constraint, as that would be
* redundant: the PRIMARY KEY constraint itself fulfills that role. Other
* constraint types don't need any not-null markings.
*/
foreach(lc, cxt->ixconstraints)
{
@ -2128,9 +2295,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);
}
@ -2138,12 +2303,10 @@ 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 force the columns to be
* marked as not-null, without producing a not-null constraint.
*/
static IndexStmt *
transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
@ -2401,7 +2564,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.
* also make sure they are not-null.
*/
else
{
@ -2409,7 +2572,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;
@ -2428,15 +2590,16 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
{
/*
* column is defined in the new table. For PRIMARY KEY, we
* can apply the NOT NULL constraint cheaply here ... unless
* 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).
* here would be ineffective (see MergeAttributes). Note that
* this isn't effective in ALTER TABLE either, unless the
* column is being added in the same command.
*/
if (constraint->contype == CONSTR_PRIMARY &&
!column->is_from_type)
{
column->is_not_null = true;
forced_not_null = true;
}
}
else if (SystemAttributeByName(key) != NULL)
@ -2479,14 +2642,6 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
if (strcmp(key, inhname) == 0)
{
found = true;
/*
* 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.
*/
break;
}
}
@ -2540,15 +2695,11 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
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)
if (constraint->contype == CONSTR_PRIMARY)
{
AlterTableCmd *notnullcmd = makeNode(AlterTableCmd);
notnullcmd->subtype = AT_SetNotNull;
notnullcmd->subtype = AT_SetAttNotNull;
notnullcmd->name = pstrdup(key);
notnullcmds = lappend(notnullcmds, notnullcmd);
}
@ -3320,6 +3471,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;
@ -3563,8 +3715,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
/*
* 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.
* AT_SetAttNotNull statements generated from primary key constraints.
* We absorb the subcommands of the latter directly.
*/
if (IsA(istmt, IndexStmt))
{
@ -3587,19 +3739,26 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
}
cxt.alist = NIL;
/* Append any CHECK or FK constraints to the commands list */
/* Append any CHECK, NOT NULL or FK constraints to the commands list */
foreach(l, cxt.ckconstraints)
{
newcmd = makeNode(AlterTableCmd);
newcmd->subtype = AT_AddConstraint;
newcmd->def = (Node *) lfirst(l);
newcmd->def = (Node *) lfirst_node(Constraint, l);
newcmds = lappend(newcmds, newcmd);
}
foreach(l, cxt.nnconstraints)
{
newcmd = makeNode(AlterTableCmd);
newcmd->subtype = AT_AddConstraint;
newcmd->def = (Node *) lfirst_node(Constraint, l);
newcmds = lappend(newcmds, newcmd);
}
foreach(l, cxt.fkconstraints)
{
newcmd = makeNode(AlterTableCmd);
newcmd->subtype = AT_AddConstraint;
newcmd->def = (Node *) lfirst(l);
newcmd->def = (Node *) lfirst_node(Constraint, l);
newcmds = lappend(newcmds, newcmd);
}

View File

@ -2490,6 +2490,20 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
conForm->connoinherit ? " NO INHERIT" : "");
break;
}
case CONSTRAINT_NOTNULL:
{
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");
break;
}
case CONSTRAINT_TRIGGER:
/*

View File

@ -4789,19 +4789,41 @@ 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, non-immediate or predicate indexes aren't interesting
* for either oid indexes or replication identity indexes, so don't
* check them.
*/
if (!index->indisvalid || !index->indisunique ||
if (!index->indisunique ||
!index->indimmediate ||
!heap_attisnull(htup, Anum_pg_index_indpred, NULL))
continue;
/* remember primary key index if any */
if (index->indisprimary)
/*
* Remember primary key index, if any. We do this only if the index
* is valid; but if the table is partitioned, then we do it even if
* it's invalid.
*
* The reason for returning invalid primary keys for foreign tables is
* because of pg_dump of NOT NULL constraints, and the fact that PKs
* remain marked invalid until the partitions' PKs are attached to it.
* If we make rd_pkindex invalid, then the attnotnull flag is reset
* after the PK is created, which causes the ALTER INDEX ATTACH
* PARTITION to fail with 'column ... is not marked NOT NULL'. With
* this, dropconstraint_internal() will believe that the columns must
* not have attnotnull reset, so the PKs-on-partitions can be attached
* correctly, until finally the PK-on-parent is marked valid.
*
* Also, this doesn't harm anything, because rd_pkindex is not a
* "real" index anyway, but a RELKIND_PARTITIONED_INDEX.
*/
if (index->indisprimary &&
(index->indisvalid ||
relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
pkeyIndex = index->indexrelid;
if (!index->indisvalid)
continue;
/* remember explicitly chosen replica index */
if (index->indisreplident)
candidateIndex = index->indexrelid;

View File

@ -82,7 +82,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(DumpOptions *dopt, 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);
@ -226,7 +227,7 @@ getSchemaData(Archive *fout, int *numTablesPtr)
getTableAttrs(fout, tblinfo, numTables);
pg_log_info("flagging inherited columns in subtables");
flagInhAttrs(fout->dopt, tblinfo, numTables);
flagInhAttrs(fout, fout->dopt, tblinfo, numTables);
pg_log_info("reading partitioning data");
getPartitioningInfo(fout);
@ -471,7 +472,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 >= 16 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
@ -491,7 +493,7 @@ flagInhIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
* modifies tblinfo
*/
static void
flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables)
flagInhAttrs(Archive *fout, DumpOptions *dopt, TableInfo *tblinfo, int numTables)
{
int i,
j,
@ -554,7 +556,8 @@ flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables)
{
AttrDefInfo *parentDef = parent->attrdefs[inhAttrInd];
foundNotNull |= parent->notnull[inhAttrInd];
foundNotNull |= (parent->notnull_constrs[inhAttrInd] != NULL &&
!parent->notnull_noinh[inhAttrInd]);
foundDefault |= (parentDef != NULL &&
strcmp(parentDef->adef_expr, "NULL") != 0 &&
!parent->attgenerated[inhAttrInd]);
@ -572,8 +575,9 @@ flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables)
}
}
/* Remember if we found inherited NOT NULL */
tbinfo->inhNotNull[j] = foundNotNull;
/* In versions < 17, remember if we found inherited NOT NULL */
if (fout->remoteVersion < 170000)
tbinfo->notnull_inh[j] = foundNotNull;
/*
* Manufacture a DEFAULT NULL clause if necessary. This breaks

View File

@ -4864,7 +4864,7 @@ append_depends_on_extension(Archive *fout,
i_extname = PQfnumber(res, "extname");
for (i = 0; i < ntups; i++)
{
appendPQExpBuffer(create, "ALTER %s %s DEPENDS ON EXTENSION %s;\n",
appendPQExpBuffer(create, "\nALTER %s %s DEPENDS ON EXTENSION %s;",
keyword, nm,
fmtId(PQgetvalue(res, i, i_extname)));
}
@ -8373,7 +8373,10 @@ 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_is_pk;
int i_notnull_inh;
int i_attoptions;
int i_attcollation;
int i_attcompression;
@ -8383,13 +8386,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, '{');
@ -8436,7 +8439,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"
@ -8453,6 +8455,34 @@ 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 17 and up we have
* to read pg_constraint, and keep track whether it's NO INHERIT; in older
* versions we rely on pg_attribute.attnotnull.
*
* We also track whether the constraint was defined directly in this table
* or via an ancestor, for binary upgrade.
*
* Lastly, we need to know if the PK for the table involves each column;
* for columns that are there we need a NOT NULL marking even if there's
* no explicit constraint, to avoid the table having to be scanned for
* NULLs after the data is loaded when the PK is created, later in the
* dump; for this case we add throwaway constraints that are dropped once
* the PK is created.
*/
if (fout->remoteVersion >= 170000)
appendPQExpBufferStr(q,
"co.conname AS notnull_name,\n"
"co.connoinherit AS notnull_noinherit,\n"
"copk.conname IS NOT NULL as notnull_is_pk,\n"
"coalesce(NOT co.conislocal, true) AS notnull_inh,\n");
else
appendPQExpBufferStr(q,
"CASE WHEN a.attnotnull THEN '' ELSE NULL END AS notnull_name,\n"
"false AS notnull_noinherit,\n"
"copk.conname IS NOT NULL AS notnull_is_pk,\n"
"NOT a.attislocal AS notnull_inh,\n");
if (fout->remoteVersion >= 140000)
appendPQExpBufferStr(q,
"a.attcompression AS attcompression,\n");
@ -8487,11 +8517,29 @@ 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 16 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 >= 170000)
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,
"LEFT JOIN pg_catalog.pg_constraint copk ON "
"(copk.conrelid = src.tbloid\n"
" AND copk.contype = 'p' AND "
"copk.conkey @> array[a.attnum])\n"
"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);
@ -8509,7 +8557,10 @@ 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_is_pk = PQfnumber(res, "notnull_is_pk");
i_notnull_inh = PQfnumber(res, "notnull_inh");
i_attoptions = PQfnumber(res, "attoptions");
i_attcollation = PQfnumber(res, "attcollation");
i_attcompression = PQfnumber(res, "attcompression");
@ -8532,6 +8583,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
TableInfo *tbinfo = NULL;
int numatts;
bool hasdefaults;
int notnullcount;
/* Count rows for this table */
for (numatts = 1; numatts < ntups - r; numatts++)
@ -8556,6 +8608,8 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
pg_fatal("unexpected column data for table \"%s\"",
tbinfo->dobj.name);
notnullcount = 0;
/* Save data for this table */
tbinfo->numatts = numatts;
tbinfo->attnames = (char **) pg_malloc(numatts * sizeof(char *));
@ -8574,13 +8628,19 @@ 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_throwaway = (bool *) pg_malloc(numatts * sizeof(bool));
tbinfo->notnull_inh = (bool *) pg_malloc(numatts * sizeof(bool));
tbinfo->attrdefs = (AttrDefInfo **) pg_malloc(numatts * sizeof(AttrDefInfo *));
hasdefaults = false;
for (int j = 0; j < numatts; j++, r++)
{
bool use_named_notnull = false;
bool use_unnamed_notnull = false;
bool use_throwaway_notnull = false;
if (j + 1 != atoi(PQgetvalue(res, r, i_attnum)))
pg_fatal("invalid column numbering in table \"%s\"",
tbinfo->dobj.name);
@ -8596,7 +8656,129 @@ 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');
/*
* Not-null constraints require a jumping through a few hoops.
* First, if the user has specified a constraint name that's not
* the system-assigned default name, then we need to preserve
* that. But if they haven't, then we don't want to use the
* verbose syntax in the dump output. (Also, in versions prior to
* 17, there was no constraint name at all.)
*
* (XXX Comparing the name this way to a supposed default name is
* a bit of a hack, but it beats having to store a boolean flag in
* pg_constraint just for this, or having to compute the knowledge
* at pg_dump time from the server.)
*
* We also need to know if a column is part of the primary key. In
* that case, we want to mark the column as not-null at table
* creation time, so that the table doesn't have to be scanned to
* check for nulls when the PK is created afterwards; this is
* especially critical during pg_upgrade (where the data would not
* be scanned at all otherwise.) If the column is part of the PK
* and does not have any other not-null constraint, then we
* fabricate a throwaway constraint name that we later use to
* remove the constraint after the PK has been created.
*
* For inheritance child tables, we don't want to print not-null
* when the constraint was defined at the parent level instead of
* locally.
*/
/*
* We use notnull_inh to suppress unwanted not-null constraints in
* inheritance children, when said constraints come from the
* parent(s).
*/
tbinfo->notnull_inh[j] = PQgetvalue(res, r, i_notnull_inh)[0] == 't';
if (fout->remoteVersion < 170000)
{
if (!PQgetisnull(res, r, i_notnull_name) &&
dopt->binary_upgrade &&
!tbinfo->ispartition &&
tbinfo->notnull_inh[j])
{
use_named_notnull = true;
/* XXX should match ChooseConstraintName better */
tbinfo->notnull_constrs[j] =
psprintf("%s_%s_not_null", tbinfo->dobj.name,
tbinfo->attnames[j]);
}
else if (PQgetvalue(res, r, i_notnull_is_pk)[0] == 't')
use_throwaway_notnull = true;
else if (!PQgetisnull(res, r, i_notnull_name))
use_unnamed_notnull = true;
}
else
{
if (!PQgetisnull(res, r, i_notnull_name))
{
/*
* 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_inh[j])
{
use_named_notnull = true;
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)
use_unnamed_notnull = true;
else
{
use_named_notnull = true;
tbinfo->notnull_constrs[j] =
pstrdup(PQgetvalue(res, r, i_notnull_name));
}
}
}
else if (PQgetvalue(res, r, i_notnull_is_pk)[0] == 't')
use_throwaway_notnull = true;
}
if (use_unnamed_notnull)
{
tbinfo->notnull_constrs[j] = "";
tbinfo->notnull_throwaway[j] = false;
}
else if (use_named_notnull)
{
/* The name itself has already been determined */
tbinfo->notnull_throwaway[j] = false;
}
else if (use_throwaway_notnull)
{
tbinfo->notnull_constrs[j] =
psprintf("pgdump_throwaway_notnull_%d", notnullcount++);
tbinfo->notnull_throwaway[j] = true;
tbinfo->notnull_inh[j] = false;
}
else
{
tbinfo->notnull_constrs[j] = NULL;
tbinfo->notnull_throwaway[j] = false;
}
/*
* Throwaway constraints must always be NO INHERIT; otherwise do
* what the catalog says.
*/
tbinfo->notnull_noinh[j] = use_throwaway_notnull ||
PQgetvalue(res, r, i_notnull_noinherit)[0] == 't';
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));
@ -8605,8 +8787,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)
@ -15598,13 +15778,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 --- suppress unless it is locally
* defined, except if partition, or in binary-upgrade case
* where that won't work.
*/
print_notnull = (tbinfo->notnull[j] &&
(!tbinfo->inhNotNull[j] ||
tbinfo->ispartition || dopt->binary_upgrade));
print_notnull =
(tbinfo->notnull_constrs[j] != NULL &&
(!tbinfo->notnull_inh[j] || tbinfo->ispartition ||
dopt->binary_upgrade));
/*
* Skip column if fully defined by reloftype, except in
@ -15662,7 +15843,16 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
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]))
@ -15875,6 +16065,25 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
appendPQExpBufferStr(q, "\n AND attrelid = ");
appendStringLiteralAH(q, qualrelname, fout);
appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
/*
* If a not-null constraint comes from inheritance, reset
* conislocal. The inhcount is fixed later.
*/
if (tbinfo->notnull_constrs[j] != NULL &&
!tbinfo->notnull_throwaway[j] &&
tbinfo->notnull_inh[j] &&
!tbinfo->ispartition)
{
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 = ");
appendStringLiteralAH(q, tbinfo->notnull_constrs[j], fout);
appendPQExpBufferStr(q, ";\n");
}
}
}
@ -15992,15 +16201,26 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
/*
* If we didn't dump the column definition explicitly above, and
* it is NOT NULL and did not inherit that property from a parent,
* it is not-null and did not inherit that property from a parent,
* 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_inh[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
@ -16741,6 +16961,14 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo)
* similar code in dumpIndex!
*/
/* Drop any not-null constraints that were added to support the PK */
if (coninfo->contype == 'p')
for (int i = 0; i < tbinfo->numatts; i++)
if (tbinfo->notnull_throwaway[i])
appendPQExpBuffer(q, "\nALTER TABLE ONLY %s DROP CONSTRAINT %s;",
fmtQualifiedDumpable(tbinfo),
tbinfo->notnull_constrs[i]);
/* If the index is clustered, we need to record that. */
if (indxinfo->indisclustered)
{

View File

@ -345,8 +345,13 @@ 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_throwaway; /* drop the NOT NULL constraint later */
bool *notnull_inh; /* true if NOT NULL has no local definition */
struct _attrDefInfo **attrdefs; /* DEFAULT expressions */
struct _constraintInfo *checkexprs; /* CHECK constraints */
bool needs_override; /* has GENERATED ALWAYS AS IDENTITY */

View File

@ -3221,7 +3221,7 @@ my %tests = (
);',
regexp => qr/^
\QCREATE TABLE dump_test.fk_reference_test_table (\E
\n\s+\Qcol1 integer NOT NULL\E
\n\s+\Qcol1 integer CONSTRAINT \E[a-z0-9_]*\Q NOT NULL NO INHERIT\E
\n\);
/xm,
like =>
@ -3319,8 +3319,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
@ -3615,7 +3615,7 @@ my %tests = (
);',
regexp => qr/^
\QCREATE TABLE dump_test.test_table_generated (\E\n
\s+\Qcol1 integer NOT NULL,\E\n
\s+\Qcol1 integer CONSTRAINT \E[a-z0-9_]*\Q NOT NULL NO INHERIT,\E\n
\s+\Qcol2 integer GENERATED ALWAYS AS ((col1 * 2)) STORED\E\n
\);
/xms,
@ -3729,7 +3729,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

View File

@ -3050,6 +3050,50 @@ describeOneTableDetails(const char *schemaname,
}
PQclear(result);
}
/* If verbose, print NOT NULL constraints */
if (verbose)
{
printfPQExpBuffer(&buf,
"SELECT co.conname, at.attname, co.connoinherit, co.conislocal,\n"
"co.coninhcount <> 0\n"
"FROM pg_catalog.pg_constraint co JOIN\n"
"pg_catalog.pg_attribute at ON\n"
"(at.attnum = co.conkey[1])\n"
"WHERE co.contype = 'n' AND\n"
"co.conrelid = '%s'::pg_catalog.regclass AND\n"
"at.attrelid = '%s'::pg_catalog.regclass\n"
"ORDER BY at.attnum",
oid,
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 */

View File

@ -57,6 +57,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 202308241
#define CATALOG_VERSION_NO 202308251
#endif

View File

@ -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 *additional_notnulls);
extern void RelationClearMissing(Relation rel);
extern void SetAttrMissing(Oid relid, char *attname, char *value);

View File

@ -181,6 +181,7 @@ DECLARE_ARRAY_FOREIGN_KEY((confrelid, confkey), pg_attribute, (attrelid, attnum)
/* Valid values for contype */
#define CONSTRAINT_CHECK 'c'
#define CONSTRAINT_FOREIGN 'f'
#define CONSTRAINT_NOTNULL 'n'
#define CONSTRAINT_PRIMARY 'p'
#define CONSTRAINT_UNIQUE 'u'
#define CONSTRAINT_TRIGGER 't'
@ -237,9 +238,6 @@ extern Oid CreateConstraintEntry(const char *constraintName,
bool conNoInherit,
bool is_internal);
extern void RemoveConstraintById(Oid conId);
extern void RenameConstraintById(Oid conId, const char *newname);
extern bool ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
const char *conname);
extern bool ConstraintNameExists(const char *conname, Oid namespaceid);
@ -247,6 +245,16 @@ 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 AttrNumber extractNotNullColumn(HeapTuple constrTup);
extern bool AdjustNotNullInheritance1(Oid relid, AttrNumber attnum, int count);
extern void AdjustNotNullInheritance(Oid relid, Bitmapset *columns, int count);
extern List *RelationGetNotNullConstraints(Oid relid, bool cooked);
extern void RemoveConstraintById(Oid conId);
extern void RenameConstraintById(Oid conId, const char *newname);
extern void AlterConstraintNamespaces(Oid ownerId, Oid oldNspId,
Oid newNspId, bool isType, ObjectAddresses *objsMoved);
extern void ConstraintSetParentConstraint(Oid childConstrId,

View File

@ -2215,8 +2215,8 @@ typedef enum AlterTableType
AT_CookedColumnDefault, /* add a pre-cooked column default */
AT_DropNotNull, /* alter column drop not null */
AT_SetNotNull, /* alter column set not null */
AT_SetAttNotNull, /* set attnotnull w/o a constraint */
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 ) */
@ -2499,10 +2499,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.
* ----------------------
*/
@ -2517,6 +2517,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 */
@ -2605,10 +2606,13 @@ typedef struct Constraint
char *cooked_expr; /* expr, as nodeToString representation */
char generated_when; /* ALWAYS or BY DEFAULT */
/* Fields used for "raw" NOT NULL constraints: */
int inhcount; /* initial inheritance count to apply */
/* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */
bool nulls_not_distinct; /* null treatment for UNIQUE constraints */
List *keys; /* String nodes naming referenced key
* column(s) */
* column(s); also used for NOT NULL */
List *including; /* String nodes naming referenced nonkey
* column(s) */

View File

@ -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,18 @@ 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 SET ATTNOTNULL desc column a of table part
NOTICE: subcommand: type SET ATTNOTNULL desc column a of table part1
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 +111,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

View File

@ -54,6 +54,8 @@ NOTICE: DDL test: type simple, tag CREATE SEQUENCE
NOTICE: DDL test: type simple, tag CREATE SEQUENCE
NOTICE: DDL test: type simple, tag CREATE SEQUENCE
NOTICE: DDL test: type simple, tag CREATE TABLE
NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: type SET ATTNOTNULL desc <NULL>
NOTICE: DDL test: type simple, tag CREATE INDEX
NOTICE: DDL test: type simple, tag CREATE INDEX
NOTICE: DDL test: type simple, tag ALTER SEQUENCE
@ -74,6 +76,8 @@ CREATE TABLE IF NOT EXISTS fkey_table (
EXCLUDE USING btree (check_col_2 WITH =)
);
NOTICE: DDL test: type simple, tag CREATE TABLE
NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: type SET ATTNOTNULL desc <NULL>
NOTICE: DDL test: type simple, tag CREATE INDEX
NOTICE: DDL test: type simple, tag CREATE INDEX
NOTICE: DDL test: type alter table, tag ALTER TABLE
@ -86,7 +90,7 @@ CREATE TABLE employees OF employee_type (
);
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: subcommand: type SET ATTNOTNULL desc column name of table employees
NOTICE: DDL test: type simple, tag CREATE INDEX
-- Inheritance
CREATE TABLE person (
@ -96,6 +100,8 @@ CREATE TABLE person (
location point
);
NOTICE: DDL test: type simple, tag CREATE TABLE
NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: type SET ATTNOTNULL desc <NULL>
NOTICE: DDL test: type simple, tag CREATE INDEX
CREATE TABLE emp (
salary int4,
@ -128,6 +134,10 @@ CREATE TABLE like_datatype_table (
EXCLUDING ALL
);
NOTICE: DDL test: type simple, tag CREATE TABLE
NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint datatype_table_id_big_not_null on table like_datatype_table
NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint datatype_table_id_not_null on table like_datatype_table
NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint datatype_table_is_small_not_null on table like_datatype_table
CREATE TABLE like_fkey_table (
LIKE fkey_table
INCLUDING DEFAULTS
@ -136,7 +146,13 @@ CREATE TABLE like_fkey_table (
);
NOTICE: DDL test: type simple, tag CREATE TABLE
NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: type SET ATTNOTNULL desc column id of table like_fkey_table
NOTICE: subcommand: type ALTER COLUMN SET DEFAULT (precooked) desc column id of table like_fkey_table
NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint fkey_table_big_id_not_null on table like_fkey_table
NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint fkey_table_check_col_1_not_null on table like_fkey_table
NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint fkey_table_check_col_2_not_null on table like_fkey_table
NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint fkey_table_datatype_id_not_null on table like_fkey_table
NOTICE: subcommand: type ADD CONSTRAINT (and recurse) desc constraint fkey_table_id_not_null on table like_fkey_table
NOTICE: DDL test: type simple, tag CREATE INDEX
NOTICE: DDL test: type simple, tag CREATE INDEX
-- Volatile table types
@ -144,21 +160,29 @@ CREATE UNLOGGED TABLE unlogged_table (
id INT PRIMARY KEY
);
NOTICE: DDL test: type simple, tag CREATE TABLE
NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: type SET ATTNOTNULL desc <NULL>
NOTICE: DDL test: type simple, tag CREATE INDEX
CREATE TEMP TABLE temp_table (
id INT PRIMARY KEY
);
NOTICE: DDL test: type simple, tag CREATE TABLE
NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: type SET ATTNOTNULL desc <NULL>
NOTICE: DDL test: type simple, tag CREATE INDEX
CREATE TEMP TABLE temp_table_commit_delete (
id INT PRIMARY KEY
)
ON COMMIT DELETE ROWS;
NOTICE: DDL test: type simple, tag CREATE TABLE
NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: type SET ATTNOTNULL desc <NULL>
NOTICE: DDL test: type simple, tag CREATE INDEX
CREATE TEMP TABLE temp_table_commit_drop (
id INT PRIMARY KEY
)
ON COMMIT DROP;
NOTICE: DDL test: type simple, tag CREATE TABLE
NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: type SET ATTNOTNULL desc <NULL>
NOTICE: DDL test: type simple, tag CREATE INDEX

View File

@ -129,12 +129,12 @@ get_altertable_subcmdinfo(PG_FUNCTION_ARGS)
case AT_SetNotNull:
strtype = "SET NOT NULL";
break;
case AT_SetAttNotNull:
strtype = "SET ATTNOTNULL";
break;
case AT_DropExpression:
strtype = "DROP EXPRESSION";
break;
case AT_CheckNotNull:
strtype = "CHECK NOT NULL";
break;
case AT_SetStatistics:
strtype = "SET STATS";
break;

View File

@ -1118,10 +1118,30 @@ ERROR: relation "non_existent" does not exist
-- test checking for null values and primary key
create table atacc1 (test int not null);
alter table atacc1 add constraint "atacc1_pkey" primary key (test);
\d atacc1
Table "public.atacc1"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
test | integer | | not null |
Indexes:
"atacc1_pkey" PRIMARY KEY, btree (test)
alter table atacc1 alter column test drop not null;
ERROR: column "test" is in a primary key
\d atacc1
Table "public.atacc1"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
test | integer | | not null |
Indexes:
"atacc1_pkey" PRIMARY KEY, btree (test)
alter table atacc1 drop constraint "atacc1_pkey";
alter table atacc1 alter column test drop not null;
\d atacc1
Table "public.atacc1"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
test | integer | | |
insert into atacc1 values (null);
alter table atacc1 alter test set not null;
ERROR: column "test" of relation "atacc1" contains null values
@ -1194,20 +1214,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
@ -3834,6 +3840,29 @@ Referenced by:
TABLE "ataddindex" CONSTRAINT "ataddindex_ref_id_fkey" FOREIGN KEY (ref_id) REFERENCES ataddindex(id)
DROP TABLE ataddindex;
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);
\d+ atnotnull1
Table "public.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"
-- cannot drop column that is part of the partition key
CREATE TABLE partitioned (
a int,
@ -4351,7 +4380,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

View File

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

View File

@ -288,6 +288,39 @@ ERROR: new row for relation "atacc1" violates check constraint "atacc1_test2_ch
DETAIL: Failing row contains (null, 3).
DROP TABLE ATACC1 CASCADE;
NOTICE: drop cascades to table atacc2
-- 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
DROP TABLE ATACC1, ATACC2;
--
-- Check constraints on INSERT INTO
--
@ -754,6 +787,225 @@ 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"
select conname, contype, conkey from pg_constraint where conrelid = 'notnull_tbl1'::regclass;
conname | contype | conkey
-------------------------+---------+--------
notnull_tbl1_a_not_null | n | {1}
(1 row)
-- 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
--------+---------+-----------+----------+---------
a | integer | | |
select conname, contype, conkey from pg_constraint where conrelid = 'notnull_tbl1'::regclass;
conname | contype | conkey
---------+---------+--------
(0 rows)
-- 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
--------+---------+-----------+----------+---------
a | integer | | 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)
-- 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
--------+---------+-----------+----------+---------
a | integer | | not null |
select conname, contype, conkey from pg_constraint where conrelid = 'notnull_tbl1'::regclass;
conname | contype | conkey
---------+---------+--------
foobar | n | {1}
(1 row)
DROP TABLE notnull_tbl1;
-- nope
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
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
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 | | |
b | integer | | |
Check constraints:
"notnull_tbl3_a_check" CHECK (a IS NOT NULL)
-- Primary keys in parent table cause NOT NULL constraint to spawn on their
-- children. Verify that they work correctly.
CREATE TABLE cnn_parent (a int, b int);
CREATE TABLE cnn_child () INHERITS (cnn_parent);
CREATE TABLE cnn_grandchild (NOT NULL b) INHERITS (cnn_child);
CREATE TABLE cnn_child2 (NOT NULL a NO INHERIT) INHERITS (cnn_parent);
CREATE TABLE cnn_grandchild2 () INHERITS (cnn_grandchild, cnn_child2);
NOTICE: merging multiple inherited definitions of column "a"
NOTICE: merging multiple inherited definitions of column "b"
ALTER TABLE cnn_parent ADD PRIMARY KEY (b);
\d+ cnn_grandchild
Table "public.cnn_grandchild"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain | |
b | integer | | not null | | plain | |
Not-null constraints:
"cnn_grandchild_b_not_null" NOT NULL "b" (local, inherited)
Inherits: cnn_child
Child tables: cnn_grandchild2
\d+ cnn_grandchild2
Table "public.cnn_grandchild2"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain | |
b | integer | | not null | | plain | |
Not-null constraints:
"cnn_grandchild_b_not_null" NOT NULL "b" (inherited)
Inherits: cnn_grandchild,
cnn_child2
ALTER TABLE cnn_parent DROP CONSTRAINT cnn_parent_pkey;
\set VERBOSITY terse
DROP TABLE cnn_parent CASCADE;
NOTICE: drop cascades to 4 other objects
\set VERBOSITY default
-- As above, but create the primary key ahead of time
CREATE TABLE cnn_parent (a int, b int PRIMARY KEY);
CREATE TABLE cnn_child () INHERITS (cnn_parent);
CREATE TABLE cnn_grandchild (NOT NULL b) INHERITS (cnn_child);
CREATE TABLE cnn_child2 (NOT NULL a NO INHERIT) INHERITS (cnn_parent);
CREATE TABLE cnn_grandchild2 () INHERITS (cnn_grandchild, cnn_child2);
NOTICE: merging multiple inherited definitions of column "a"
NOTICE: merging multiple inherited definitions of column "b"
ALTER TABLE cnn_parent ADD PRIMARY KEY (b);
ERROR: multiple primary keys for table "cnn_parent" are not allowed
\d+ cnn_grandchild
Table "public.cnn_grandchild"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain | |
b | integer | | not null | | plain | |
Not-null constraints:
"cnn_grandchild_b_not_null" NOT NULL "b" (local, inherited)
Inherits: cnn_child
Child tables: cnn_grandchild2
\d+ cnn_grandchild2
Table "public.cnn_grandchild2"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain | |
b | integer | | not null | | plain | |
Not-null constraints:
"cnn_grandchild_b_not_null" NOT NULL "b" (inherited)
Inherits: cnn_grandchild,
cnn_child2
ALTER TABLE cnn_parent DROP CONSTRAINT cnn_parent_pkey;
\set VERBOSITY terse
DROP TABLE cnn_parent CASCADE;
NOTICE: drop cascades to 4 other objects
\set VERBOSITY default
-- As above, but create the primary key using a UNIQUE index
CREATE TABLE cnn_parent (a int, b int);
CREATE TABLE cnn_child () INHERITS (cnn_parent);
CREATE TABLE cnn_grandchild (NOT NULL b) INHERITS (cnn_child);
CREATE TABLE cnn_child2 (NOT NULL a NO INHERIT) INHERITS (cnn_parent);
CREATE TABLE cnn_grandchild2 () INHERITS (cnn_grandchild, cnn_child2);
NOTICE: merging multiple inherited definitions of column "a"
NOTICE: merging multiple inherited definitions of column "b"
CREATE UNIQUE INDEX b_uq ON cnn_parent (b);
ALTER TABLE cnn_parent ADD PRIMARY KEY USING INDEX b_uq;
\d+ cnn_grandchild
Table "public.cnn_grandchild"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain | |
b | integer | | not null | | plain | |
Not-null constraints:
"cnn_grandchild_b_not_null" NOT NULL "b" (local, inherited)
Inherits: cnn_child
Child tables: cnn_grandchild2
\d+ cnn_grandchild2
Table "public.cnn_grandchild2"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain | |
b | integer | | not null | | plain | |
Not-null constraints:
"cnn_grandchild_b_not_null" NOT NULL "b" (inherited)
Inherits: cnn_grandchild,
cnn_child2
ALTER TABLE cnn_parent DROP CONSTRAINT cnn_parent_pkey;
ERROR: constraint "cnn_parent_pkey" of relation "cnn_parent" does not exist
-- keeps these tables around, for pg_upgrade testing
-- Comments
-- Setup a low-level role to enforce non-superuser checks.
CREATE ROLE regress_constraint_comments;

View File

@ -758,22 +758,24 @@ CREATE TABLE part_b PARTITION OF parted (
) FOR VALUES IN ('b');
NOTICE: merging constraint "check_a" with inherited definition
-- conislocal should be false for any merged constraints, true otherwise
SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass ORDER BY conislocal, coninhcount;
conislocal | coninhcount
------------+-------------
f | 1
t | 0
(2 rows)
SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass ORDER BY coninhcount DESC, conname;
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 conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass;
conislocal | coninhcount
------------+-------------
f | 1
f | 1
(2 rows)
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
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;
@ -784,10 +786,11 @@ ERROR: cannot drop inherited constraint "check_b" of relation "part_b"
-- traditional inheritance where they will be left behind, because they would
-- be local constraints.
ALTER TABLE parted DROP CONSTRAINT check_a, DROP CONSTRAINT check_b;
SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass;
conislocal | coninhcount
------------+-------------
(0 rows)
SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass ORDER BY coninhcount DESC, conname;
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);
@ -851,6 +854,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
@ -862,6 +867,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
@ -873,6 +880,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

View File

@ -333,6 +333,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:
"ctlt12_storage_a_not_null" NOT NULL "a"
CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDING COMMENTS);
\d+ ctlt12_comments
@ -342,6 +344,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:
"ctlt12_comments_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
@ -355,6 +359,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_inh_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;
@ -376,6 +382,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:
"ctlt13_inh_a_not_null" NOT NULL "a" (inherited)
Inherits: ctlt1,
ctlt3
@ -394,6 +402,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:
"ctlt13_like_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;

View File

@ -408,6 +408,7 @@ NOTICE: END: command_tag=CREATE SCHEMA type=schema identity=evttrig
NOTICE: END: command_tag=CREATE SEQUENCE type=sequence identity=evttrig.one_col_a_seq
NOTICE: END: command_tag=CREATE SEQUENCE type=sequence identity=evttrig.one_col_c_seq
NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.one
NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.one
NOTICE: END: command_tag=CREATE INDEX type=index identity=evttrig.one_pkey
NOTICE: END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_a_seq
NOTICE: END: command_tag=ALTER SEQUENCE type=sequence identity=evttrig.one_col_c_seq
@ -422,6 +423,7 @@ CREATE TABLE evttrig.parted (
id int PRIMARY KEY)
PARTITION BY RANGE (id);
NOTICE: END: command_tag=CREATE TABLE type=table identity=evttrig.parted
NOTICE: END: command_tag=ALTER TABLE type=table identity=evttrig.parted
NOTICE: END: command_tag=CREATE INDEX type=index identity=evttrig.parted_pkey
CREATE TABLE evttrig.part_1_10 PARTITION OF evttrig.parted (id)
FOR VALUES FROM (1) TO (10);

View File

@ -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')
@ -1404,6 +1409,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
@ -1413,6 +1420,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
@ -1425,6 +1434,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,
@ -1438,6 +1449,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')
@ -1449,6 +1462,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
@ -1458,6 +1473,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
@ -1479,6 +1496,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
@ -1492,6 +1511,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
@ -1501,6 +1522,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
@ -1522,6 +1545,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
@ -1536,6 +1562,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
@ -1554,6 +1583,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
@ -1568,6 +1600,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
@ -1596,6 +1631,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
@ -1610,6 +1648,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
@ -1629,6 +1670,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
@ -1638,6 +1681,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
@ -1652,11 +1697,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
@ -1669,6 +1715,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
@ -1680,6 +1728,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
@ -1716,6 +1766,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
@ -1727,6 +1779,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
@ -1746,6 +1800,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
@ -1758,6 +1814,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
@ -1773,6 +1831,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
@ -1785,6 +1845,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
@ -1804,6 +1866,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
@ -1816,6 +1880,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
@ -1862,6 +1928,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
@ -1873,6 +1941,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')
@ -1892,6 +1962,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')
@ -1907,6 +1979,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 (
@ -1921,6 +1995,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')
@ -1934,6 +2010,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
@ -1945,6 +2023,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')
@ -1962,6 +2042,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
@ -1975,6 +2057,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')
@ -1992,6 +2077,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
@ -2003,6 +2091,9 @@ 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')
@ -2022,6 +2113,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
@ -2033,6 +2127,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')

View File

@ -2036,13 +2036,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;
@ -2065,13 +2071,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.

View File

@ -315,6 +315,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:
"gtestx_a_not_null" NOT NULL "a" (inherited)
Inherits: gtest1
CREATE TABLE gtestxx_1 (a int NOT NULL, b int);

View File

@ -506,6 +506,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"

View File

@ -1116,16 +1116,18 @@ create table idxpart3 (b int not null, a int not null);
alter table idxpart attach partition idxpart3 for values from (20, 20) to (30, 30);
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
----------------+---------+-----------+----------------+--------
idxpart1_pkey | p | idxpart1 | idxpart1_pkey | {1,2}
idxpart21_pkey | p | idxpart21 | idxpart21_pkey | {1,2}
idxpart22_pkey | p | idxpart22 | idxpart22_pkey | {1,2}
idxpart2_pkey | p | idxpart2 | idxpart2_pkey | {1,2}
idxpart3_pkey | p | idxpart3 | idxpart3_pkey | {2,1}
idxpart_pkey | p | idxpart | idxpart_pkey | {1,2}
(6 rows)
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_a_not_null | n | idxpart3 | - | {2}
idxpart3_b_not_null | n | idxpart3 | - | {1}
idxpart3_pkey | p | idxpart3 | idxpart3_pkey | {2,1}
(8 rows)
drop table idxpart;
-- Verify that multi-layer partitioning honors the requirement that all
@ -1258,12 +1260,21 @@ create table idxpart (a int) partition by range (a);
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.
alter table only idxpart add primary key (a); -- works, but idxpart0.a is nullable
\d idxpart0
Table "public.idxpart0"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
a | integer | | |
Partition of: idxpart DEFAULT
Indexes:
"idxpart0_a_key" UNIQUE CONSTRAINT, btree (a)
alter index idxpart_pkey attach partition idxpart0_a_key; -- fails, lacks NOT NULL
ERROR: invalid primary key definition
DETAIL: Column "a" of relation "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;

View File

@ -1956,6 +1956,448 @@ 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);
\d cc1
Table "public.cc1"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
f1 | integer | | |
f2 | text | | |
f3 | integer | | |
Inherits: pp1
create table cc2(f4 float) inherits(pp1,cc1);
NOTICE: merging multiple inherited definitions of column "f1"
\d cc2
Table "public.cc2"
Column | Type | Collation | Nullable | Default
--------+------------------+-----------+----------+---------
f1 | integer | | |
f2 | text | | |
f3 | integer | | |
f4 | double precision | | |
Inherits: pp1,
cc1
-- named NOT NULL constraint
alter table cc1 add column a2 int constraint nn not null;
\d+ cc1
Table "public.cc1"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+----------+--------------+-------------
f1 | integer | | | | plain | |
f2 | text | | | | extended | |
f3 | integer | | | | plain | |
a2 | integer | | not null | | plain | |
Not-null constraints:
"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 | | | | plain | |
f2 | text | | | | extended | |
f3 | integer | | | | plain | |
f4 | double precision | | | | plain | |
a2 | integer | | not null | | plain | |
Not-null constraints:
"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
-- 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 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 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_child () inherits (inh_parent1, inh_parent2);
\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 | |
Not-null constraints:
"nn" NOT NULL "a" (inherited)
"inh_child_b_not_null" NOT NULL "b" (inherited)
Inherits: inh_parent1,
inh_parent2
drop table inh_parent1, inh_parent2, inh_child;
-- 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_pkey | p | {1,2} | 0 | t | t
inh_parent2 | inh_parent2_pkey | p | {1,3} | 0 | t | t
inh_child | inh_child_a_not_null | n | {1} | 1 | f | f
inh_child | inh_child_b_not_null | n | {2} | 2 | f | f
inh_child | inh_child_d_not_null | n | {4} | 1 | f | f
(5 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_child_a_not_null" NOT NULL "a" (inherited)
"inh_child_b_not_null" NOT NULL "b" (inherited)
"inh_child_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;
--
-- 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 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_grandchld () 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_grandchld
\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_grandchld')
order by 2, 1;
conrelid | conname | contype | coninhcount | conislocal
---------------+------------------------+---------+-------------+------------
inh_child1 | inh_child1_f1_not_null | n | 0 | t
inh_grandchld | 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_grandchld;
-- a PK in parent must have a not-null in child that it can mark inherited
create table inh_parent (a int primary key);
create table inh_child (a int primary key);
alter table inh_child inherit inh_parent; -- nope
ERROR: column "a" in child table must be marked NOT NULL
alter table inh_child alter a set not null;
alter table inh_child inherit inh_parent; -- now it works
drop table inh_parent, inh_child;
--
-- 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_grandchld() 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_grandchld'::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_grandchld | 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_grandchld
-- 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
--
-- Check use of temporary tables with inheritance trees
--
create table inh_perm_parent (a1 int);

View File

@ -193,6 +193,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
@ -1147,6 +1149,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
@ -1172,6 +1176,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

View File

@ -170,6 +170,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;
@ -227,6 +231,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.)
@ -249,6 +256,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
@ -261,10 +270,25 @@ 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;
ERROR: column "b" is in index used as replica identity
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;
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;

View File

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

View File

@ -850,9 +850,11 @@ alter table non_existent alter column bar drop not null;
-- test checking for null values and primary key
create table atacc1 (test int not null);
alter table atacc1 add constraint "atacc1_pkey" primary key (test);
\d atacc1
alter table atacc1 alter column test drop not null;
\d atacc1
alter table atacc1 drop constraint "atacc1_pkey";
alter table atacc1 alter column test drop not null;
\d atacc1
insert into atacc1 values (null);
alter table atacc1 alter test set not null;
delete from atacc1;
@ -917,14 +919,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;
@ -2342,6 +2336,18 @@ ALTER TABLE ataddindex
\d ataddindex
DROP TABLE ataddindex;
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);
\d+ atnotnull1
-- cannot drop column that is part of the partition key
CREATE TABLE partitioned (
a int,

View File

@ -196,6 +196,22 @@ INSERT INTO ATACC2 (TEST2) VALUES (3);
INSERT INTO ATACC1 (TEST2) VALUES (3);
DROP TABLE ATACC1 CASCADE;
-- 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
DROP TABLE ATACC1, ATACC2;
--
-- Check constraints on INSERT INTO
--
@ -556,6 +572,92 @@ 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
select conname, contype, conkey from pg_constraint where conrelid = 'notnull_tbl1'::regclass;
-- 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
select conname, contype, conkey from pg_constraint where conrelid = 'notnull_tbl1'::regclass;
-- SET NOT NULL puts both back
ALTER TABLE notnull_tbl1 ALTER a SET NOT NULL;
\d notnull_tbl1
select conname, contype, conkey from pg_constraint where conrelid = 'notnull_tbl1'::regclass;
-- 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
select conname, contype, conkey from pg_constraint where conrelid = 'notnull_tbl1'::regclass;
DROP TABLE notnull_tbl1;
-- nope
CREATE TABLE notnull_tbl2 (a INTEGER CONSTRAINT blah NOT NULL, b INTEGER CONSTRAINT blah NOT NULL);
CREATE TABLE notnull_tbl2 (a INTEGER PRIMARY KEY);
ALTER TABLE notnull_tbl2 ALTER a DROP NOT NULL;
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 in parent table cause NOT NULL constraint to spawn on their
-- children. Verify that they work correctly.
CREATE TABLE cnn_parent (a int, b int);
CREATE TABLE cnn_child () INHERITS (cnn_parent);
CREATE TABLE cnn_grandchild (NOT NULL b) INHERITS (cnn_child);
CREATE TABLE cnn_child2 (NOT NULL a NO INHERIT) INHERITS (cnn_parent);
CREATE TABLE cnn_grandchild2 () INHERITS (cnn_grandchild, cnn_child2);
ALTER TABLE cnn_parent ADD PRIMARY KEY (b);
\d+ cnn_grandchild
\d+ cnn_grandchild2
ALTER TABLE cnn_parent DROP CONSTRAINT cnn_parent_pkey;
\set VERBOSITY terse
DROP TABLE cnn_parent CASCADE;
\set VERBOSITY default
-- As above, but create the primary key ahead of time
CREATE TABLE cnn_parent (a int, b int PRIMARY KEY);
CREATE TABLE cnn_child () INHERITS (cnn_parent);
CREATE TABLE cnn_grandchild (NOT NULL b) INHERITS (cnn_child);
CREATE TABLE cnn_child2 (NOT NULL a NO INHERIT) INHERITS (cnn_parent);
CREATE TABLE cnn_grandchild2 () INHERITS (cnn_grandchild, cnn_child2);
ALTER TABLE cnn_parent ADD PRIMARY KEY (b);
\d+ cnn_grandchild
\d+ cnn_grandchild2
ALTER TABLE cnn_parent DROP CONSTRAINT cnn_parent_pkey;
\set VERBOSITY terse
DROP TABLE cnn_parent CASCADE;
\set VERBOSITY default
-- As above, but create the primary key using a UNIQUE index
CREATE TABLE cnn_parent (a int, b int);
CREATE TABLE cnn_child () INHERITS (cnn_parent);
CREATE TABLE cnn_grandchild (NOT NULL b) INHERITS (cnn_child);
CREATE TABLE cnn_child2 (NOT NULL a NO INHERIT) INHERITS (cnn_parent);
CREATE TABLE cnn_grandchild2 () INHERITS (cnn_grandchild, cnn_child2);
CREATE UNIQUE INDEX b_uq ON cnn_parent (b);
ALTER TABLE cnn_parent ADD PRIMARY KEY USING INDEX b_uq;
\d+ cnn_grandchild
\d+ cnn_grandchild2
ALTER TABLE cnn_parent DROP CONSTRAINT cnn_parent_pkey;
-- keeps these tables around, for pg_upgrade testing
-- Comments
-- Setup a low-level role to enforce non-superuser checks.
CREATE ROLE regress_constraint_comments;

View File

@ -526,11 +526,11 @@ CREATE TABLE part_b PARTITION OF parted (
CONSTRAINT check_b CHECK (b >= 0)
) FOR VALUES IN ('b');
-- conislocal should be false for any merged constraints, true otherwise
SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass ORDER BY conislocal, coninhcount;
SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass ORDER BY coninhcount DESC, conname;
-- 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);
SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass;
SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass ORDER BY coninhcount DESC, conname;
-- Neither check_a nor check_b are droppable from part_b
ALTER TABLE part_b DROP CONSTRAINT check_a;
@ -540,7 +540,7 @@ ALTER TABLE part_b DROP CONSTRAINT check_b;
-- traditional inheritance where they will be left behind, because they would
-- be local constraints.
ALTER TABLE parted DROP CONSTRAINT check_a, DROP CONSTRAINT check_b;
SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass;
SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_b'::regclass ORDER BY coninhcount DESC, conname;
-- specify PARTITION BY for a partition
CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c);

View File

@ -569,7 +569,7 @@ create table idxpart3 (b int not null, a int not null);
alter table idxpart attach partition idxpart3 for values from (20, 20) to (30, 30);
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;
-- Verify that multi-layer partitioning honors the requirement that all
@ -667,9 +667,11 @@ create table idxpart (a int) partition by range (a);
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
alter table only idxpart add primary key (a); -- works, but idxpart0.a is nullable
\d idxpart0
alter index idxpart_pkey attach partition idxpart0_a_key; -- fails, lacks 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
drop table idxpart;

View File

@ -718,6 +718,189 @@ 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);
\d cc1
create table cc2(f4 float) inherits(pp1,cc1);
\d cc2
-- named NOT NULL constraint
alter table cc1 add column a2 int constraint nn not null;
\d+ cc1
\d+ cc2
alter table pp1 alter column f1 set not null;
\d+ pp1
\d+ cc1
\d+ cc2
-- remove constraint from cc2: no dice, it's inherited
alter table cc2 alter column a2 drop not null;
-- remove constraint 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 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_child () inherits (inh_parent1, inh_parent2);
\d+ inh_child
drop table inh_parent1, inh_parent2, inh_child;
-- 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;
--
-- 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_grandchld () 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_grandchld')
order by 2, 1;
drop table inh_parent, inh_child1, inh_child2, inh_grandchld;
-- a PK in parent must have a not-null in child that it can mark inherited
create table inh_parent (a int primary key);
create table inh_child (a int primary key);
alter table inh_child inherit inh_parent; -- nope
alter table inh_child alter a set not null;
alter table inh_child inherit inh_parent; -- now it works
drop table inh_parent, inh_child;
--
-- 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_grandchld() 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_grandchld'::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;
--
-- Check use of temporary tables with inheritance trees
--

View File

@ -97,6 +97,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.
@ -117,8 +120,20 @@ 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;