Skip to content

Commit 14e87ff

Browse files
committed
Add pg_constraint rows for not-null constraints
We now create contype='n' pg_constraint rows for not-null constraints on user tables. Only one such constraint is allowed for a column. We propagate these constraints to other tables during operations such as adding inheritance relationships, creating and attaching partitions and creating tables LIKE other tables. 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 or remove it, instead matching by the name of the column that they apply to. This means we don't require the constraint names to be identical across a hierarchy. The inheritance status of these constraints can be controlled: now we can be sure that if a parent table has one, then all children will have it as well. They can optionally be marked NO INHERIT, and then children are free not to have one. (There's currently no support for altering a NO INHERIT constraint into inheriting down the hierarchy, but that's a desirable future feature.) This also opens the door for having these constraints be marked NOT VALID, as well as allowing UNIQUE+NOT NULL to be used for functional dependency determination, as envisioned by commit e49ae8d. It's likely possible to allow DEFERRABLE constraints as followup work, as well. psql shows these constraints in \d+, though we may want to reconsider if this turns out to be too noisy. Earlier versions of this patch hid constraints that were on the same columns of the primary key, but I'm not sure that that's very useful. If clutter is a problem, we might be better off inventing a new \d++ command and not showing the constraints in \d+. For now, we omit these constraints on system catalog columns, because they're unlikely to achieve anything. The main difference to the previous attempt at this (b0e96f3) is that we now require that such a constraint always exists when a primary key is in the column; we didn't require this previously which had a number of unpalatable consequences. With this requirement, the code is easier to reason about. For example: - We no longer have "throwaway constraints" during pg_dump. We needed those for the case where a table had a PK without a not-null underneath, to prevent a slow scan of the data during restore of the PK creation, which was particularly problematic for pg_upgrade. - We no longer have to cope with attnotnull being set spuriously in case a primary key is dropped indirectly (e.g., via DROP COLUMN). Some bits of code in this patch were authored by Jian He. Author: Álvaro Herrera <[email protected]> Author: Bernd Helmle <[email protected]> Reviewed-by: 何建 (jian he) <[email protected]> Reviewed-by: 王刚 (Tender Wang) <[email protected]> Reviewed-by: Justin Pryzby <[email protected]> Reviewed-by: Peter Eisentraut <[email protected]> Reviewed-by: Dean Rasheed <[email protected]> Discussion: https://postgr.es/m/[email protected]
1 parent 075acdd commit 14e87ff

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+4246
-818
lines changed

contrib/sepgsql/expected/alter.out

-3
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,6 @@ LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_re
164164
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
165165
ALTER TABLE regtest_table ALTER b DROP NOT NULL;
166166
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
167-
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
168167
ALTER TABLE regtest_table ALTER b SET STATISTICS -1;
169168
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
170169
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
249248
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
250249
ALTER TABLE regtest_ptable ALTER p DROP NOT NULL;
251250
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
252-
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
253-
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
254251
ALTER TABLE regtest_ptable ALTER p SET STATISTICS -1;
255252
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
256253
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

contrib/test_decoding/expected/ddl.out

+12
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,9 @@ WITH (user_catalog_table = true)
492492
options | text[] | | | | extended | |
493493
Indexes:
494494
"replication_metadata_pkey" PRIMARY KEY, btree (id)
495+
Not-null constraints:
496+
"replication_metadata_id_not_null" NOT NULL "id"
497+
"replication_metadata_relation_not_null" NOT NULL "relation"
495498
Options: user_catalog_table=true
496499

497500
INSERT INTO replication_metadata(relation, options)
@@ -506,6 +509,9 @@ ALTER TABLE replication_metadata RESET (user_catalog_table);
506509
options | text[] | | | | extended | |
507510
Indexes:
508511
"replication_metadata_pkey" PRIMARY KEY, btree (id)
512+
Not-null constraints:
513+
"replication_metadata_id_not_null" NOT NULL "id"
514+
"replication_metadata_relation_not_null" NOT NULL "relation"
509515

510516
INSERT INTO replication_metadata(relation, options)
511517
VALUES ('bar', ARRAY['a', 'b']);
@@ -519,6 +525,9 @@ ALTER TABLE replication_metadata SET (user_catalog_table = true);
519525
options | text[] | | | | extended | |
520526
Indexes:
521527
"replication_metadata_pkey" PRIMARY KEY, btree (id)
528+
Not-null constraints:
529+
"replication_metadata_id_not_null" NOT NULL "id"
530+
"replication_metadata_relation_not_null" NOT NULL "relation"
522531
Options: user_catalog_table=true
523532

524533
INSERT INTO replication_metadata(relation, options)
@@ -538,6 +547,9 @@ ALTER TABLE replication_metadata SET (user_catalog_table = false);
538547
rewritemeornot | integer | | | | plain | |
539548
Indexes:
540549
"replication_metadata_pkey" PRIMARY KEY, btree (id)
550+
Not-null constraints:
551+
"replication_metadata_id_not_null" NOT NULL "id"
552+
"replication_metadata_relation_not_null" NOT NULL "relation"
541553
Options: user_catalog_table=false
542554

543555
INSERT INTO replication_metadata(relation, options)

doc/src/sgml/catalogs.sgml

+4-8
Original file line numberDiff line numberDiff line change
@@ -1271,7 +1271,7 @@
12711271
<structfield>attnotnull</structfield> <type>bool</type>
12721272
</para>
12731273
<para>
1274-
This represents a not-null constraint.
1274+
This column has a not-null constraint.
12751275
</para></entry>
12761276
</row>
12771277

@@ -2502,14 +2502,10 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
25022502
</indexterm>
25032503

25042504
<para>
2505-
The catalog <structname>pg_constraint</structname> stores check, primary
2506-
key, unique, foreign key, and exclusion constraints on tables, as well as
2507-
not-null constraints on domains.
2505+
The catalog <structname>pg_constraint</structname> stores check, not-null,
2506+
primary key, unique, foreign key, and exclusion constraints on tables.
25082507
(Column constraints are not treated specially. Every column constraint is
25092508
equivalent to some table constraint.)
2510-
Not-null constraints on relations are represented in the
2511-
<link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>
2512-
catalog, not here.
25132509
</para>
25142510

25152511
<para>
@@ -2571,7 +2567,7 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
25712567
<para>
25722568
<literal>c</literal> = check constraint,
25732569
<literal>f</literal> = foreign key constraint,
2574-
<literal>n</literal> = not-null constraint (domains only),
2570+
<literal>n</literal> = not-null constraint,
25752571
<literal>p</literal> = primary key constraint,
25762572
<literal>u</literal> = unique constraint,
25772573
<literal>t</literal> = constraint trigger,

doc/src/sgml/ddl.sgml

+48-17
Original file line numberDiff line numberDiff line change
@@ -772,17 +772,38 @@ CREATE TABLE products (
772772
price numeric
773773
);
774774
</programlisting>
775+
An explicit constraint name can also be specified, for example:
776+
<programlisting>
777+
CREATE TABLE products (
778+
product_no integer NOT NULL,
779+
name text <emphasis>CONSTRAINT products_name_not_null</emphasis> NOT NULL,
780+
price numeric
781+
);
782+
</programlisting>
783+
</para>
784+
785+
<para>
786+
A not-null constraint is usually written as a column constraint. The
787+
syntax for writing it as a table constraint is
788+
<programlisting>
789+
CREATE TABLE products (
790+
product_no integer,
791+
name text,
792+
price numeric,
793+
<emphasis>NOT NULL product_no</emphasis>,
794+
<emphasis>NOT NULL name</emphasis>
795+
);
796+
</programlisting>
797+
But this syntax is not standard and mainly intended for use by
798+
<application>pg_dump</application>.
775799
</para>
776800

777801
<para>
778-
A not-null constraint is always written as a column constraint. A
779-
not-null constraint is functionally equivalent to creating a check
802+
A not-null constraint is functionally equivalent to creating a check
780803
constraint <literal>CHECK (<replaceable>column_name</replaceable>
781804
IS NOT NULL)</literal>, but in
782805
<productname>PostgreSQL</productname> creating an explicit
783-
not-null constraint is more efficient. The drawback is that you
784-
cannot give explicit names to not-null constraints created this
785-
way.
806+
not-null constraint is more efficient.
786807
</para>
787808

788809
<para>
@@ -799,6 +820,10 @@ CREATE TABLE products (
799820
order the constraints are checked.
800821
</para>
801822

823+
<para>
824+
However, a column can have at most one explicit not-null constraint.
825+
</para>
826+
802827
<para>
803828
The <literal>NOT NULL</literal> constraint has an inverse: the
804829
<literal>NULL</literal> constraint. This does not mean that the
@@ -992,7 +1017,7 @@ CREATE TABLE example (
9921017

9931018
<para>
9941019
A table can have at most one primary key. (There can be any number
995-
of unique and not-null constraints, which are functionally almost the
1020+
of unique constraints, which combined with not-null constraints are functionally almost the
9961021
same thing, but only one can be identified as the primary key.)
9971022
Relational database theory
9981023
dictates that every table must have a primary key. This rule is
@@ -1652,11 +1677,16 @@ ALTER TABLE products ADD CHECK (name &lt;&gt; '');
16521677
ALTER TABLE products ADD CONSTRAINT some_name UNIQUE (product_no);
16531678
ALTER TABLE products ADD FOREIGN KEY (product_group_id) REFERENCES product_groups;
16541679
</programlisting>
1655-
To add a not-null constraint, which cannot be written as a table
1656-
constraint, use this syntax:
1680+
</para>
1681+
1682+
<para>
1683+
To add a not-null constraint, which is normally not written as a table
1684+
constraint, this special syntax is available:
16571685
<programlisting>
16581686
ALTER TABLE products ALTER COLUMN product_no SET NOT NULL;
16591687
</programlisting>
1688+
This command silently does nothing if the column already has a
1689+
not-null constraint.
16601690
</para>
16611691

16621692
<para>
@@ -1697,12 +1727,15 @@ ALTER TABLE products DROP CONSTRAINT some_name;
16971727
</para>
16981728

16991729
<para>
1700-
This works the same for all constraint types except not-null
1701-
constraints. To drop a not-null constraint use:
1730+
Simplified syntax is available to drop a not-null constraint:
17021731
<programlisting>
17031732
ALTER TABLE products ALTER COLUMN product_no DROP NOT NULL;
17041733
</programlisting>
1705-
(Recall that not-null constraints do not have names.)
1734+
This mirrors the <literal>SET NOT NULL</literal> syntax for adding a
1735+
not-null constraint. This command will silently do nothing if the column
1736+
does not have a not-null constraint. (Recall that a column can have at
1737+
most one not-null constraint, so it is never ambiguous which constraint
1738+
this command acts on.)
17061739
</para>
17071740
</sect2>
17081741

@@ -4446,12 +4479,10 @@ ALTER INDEX measurement_city_id_logdate_key
44464479
<para>
44474480
Both <literal>CHECK</literal> and <literal>NOT NULL</literal>
44484481
constraints of a partitioned table are always inherited by all its
4449-
partitions. <literal>CHECK</literal> constraints that are marked
4450-
<literal>NO INHERIT</literal> are not allowed to be created on
4451-
partitioned tables.
4452-
You cannot drop a <literal>NOT NULL</literal> constraint on a
4453-
partition's column if the same constraint is present in the parent
4454-
table.
4482+
partitions; it is not allowed to create <literal>NO INHERIT</literal>
4483+
constraints of those types.
4484+
You cannot drop a constraint of those types if the same constraint
4485+
is present in the parent table.
44554486
</para>
44564487
</listitem>
44574488

doc/src/sgml/ref/alter_foreign_table.sgml

+4-2
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,8 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
173173
<para>
174174
This form adds a new constraint to a foreign table, using the same
175175
syntax as <link linkend="sql-createforeigntable"><command>CREATE FOREIGN TABLE</command></link>.
176-
Currently only <literal>CHECK</literal> constraints are supported.
176+
Currently only <literal>CHECK</literal> and <literal>NOT NULL</literal>
177+
constraints are supported.
177178
</para>
178179

179180
<para>
@@ -182,7 +183,8 @@ ALTER FOREIGN TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceab
182183
declares that some new condition should be assumed to hold for all rows
183184
in the foreign table. (See the discussion
184185
in <link linkend="sql-createforeigntable"><command>CREATE FOREIGN TABLE</command></link>.)
185-
If the constraint is marked <literal>NOT VALID</literal>, then it isn't
186+
If the constraint is marked <literal>NOT VALID</literal> (allowed only for
187+
the <literal>CHECK</literal> case), then it isn't
186188
assumed to hold, but is only recorded for possible future use.
187189
</para>
188190
</listitem>

doc/src/sgml/ref/alter_table.sgml

+17-13
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
9898
<phrase>and <replaceable class="parameter">column_constraint</replaceable> is:</phrase>
9999

100100
[ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
101-
{ NOT NULL |
101+
{ NOT NULL [ NO INHERIT ] |
102102
NULL |
103103
CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
104104
DEFAULT <replaceable>default_expr</replaceable> |
@@ -114,6 +114,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
114114

115115
[ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
116116
{ CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
117+
NOT NULL <replaceable class="parameter">column_name</replaceable> [ NO INHERIT ] |
117118
UNIQUE [ NULLS [ NOT ] DISTINCT ] ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
118119
PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
119120
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> ) ] |
@@ -849,19 +850,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
849850
table. Subsequently, queries against the parent will include records
850851
of the target table. To be added as a child, the target table must
851852
already contain all the same columns as the parent (it could have
852-
additional columns, too). The columns must have matching data types,
853-
and if they have <literal>NOT NULL</literal> constraints in the parent
854-
then they must also have <literal>NOT NULL</literal> constraints in the
855-
child.
853+
additional columns, too). The columns must have matching data types.
856854
</para>
857855

858856
<para>
859-
There must also be matching child-table constraints for all
860-
<literal>CHECK</literal> constraints of the parent, except those
861-
marked non-inheritable (that is, created with <literal>ALTER TABLE ... ADD CONSTRAINT ... NO INHERIT</literal>)
862-
in the parent, which are ignored; all child-table constraints matched
863-
must not be marked non-inheritable.
864-
Currently
857+
In addition, all <literal>CHECK</literal> and <literal>NOT NULL</literal>
858+
constraints on the parent must also exist on the child, except those
859+
marked non-inheritable (that is, created with
860+
<literal>ALTER TABLE ... ADD CONSTRAINT ... NO INHERIT</literal>), which
861+
are ignored. All child-table constraints matched must not be marked
862+
non-inheritable. Currently
865863
<literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and
866864
<literal>FOREIGN KEY</literal> constraints are not considered, but
867865
this might change in the future.
@@ -1793,11 +1791,17 @@ ALTER TABLE measurement
17931791
<title>Compatibility</title>
17941792

17951793
<para>
1796-
The forms <literal>ADD</literal> (without <literal>USING INDEX</literal>),
1794+
The forms <literal>ADD [COLUMN]</literal>,
17971795
<literal>DROP [COLUMN]</literal>, <literal>DROP IDENTITY</literal>, <literal>RESTART</literal>,
17981796
<literal>SET DEFAULT</literal>, <literal>SET DATA TYPE</literal> (without <literal>USING</literal>),
17991797
<literal>SET GENERATED</literal>, and <literal>SET <replaceable>sequence_option</replaceable></literal>
1800-
conform with the SQL standard. The other forms are
1798+
conform with the SQL standard.
1799+
The form <literal>ADD <replaceable>table_constraint</replaceable></literal>
1800+
conforms with the SQL standard when the <literal>USING INDEX</literal> and
1801+
<literal>NOT VALID</literal> clauses are omitted and the constraint type is
1802+
one of <literal>CHECK</literal>, <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>,
1803+
or <literal>REFERENCES</literal>.
1804+
The other forms are
18011805
<productname>PostgreSQL</productname> extensions of the SQL standard.
18021806
Also, the ability to specify more than one manipulation in a single
18031807
<command>ALTER TABLE</command> command is an extension.

doc/src/sgml/ref/create_foreign_table.sgml

+8-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
4343
<phrase>where <replaceable class="parameter">column_constraint</replaceable> is:</phrase>
4444

4545
[ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
46-
{ NOT NULL |
46+
{ NOT NULL [ NO INHERIT ] |
4747
NULL |
4848
CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
4949
DEFAULT <replaceable>default_expr</replaceable> |
@@ -52,6 +52,7 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
5252
<phrase>and <replaceable class="parameter">table_constraint</replaceable> is:</phrase>
5353

5454
[ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
55+
NOT NULL <replaceable class="parameter">column_name</replaceable> [ NO INHERIT ] |
5556
CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ]
5657

5758
<phrase>and <replaceable class="parameter">partition_bound_spec</replaceable> is:</phrase>
@@ -203,11 +204,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
203204
</varlistentry>
204205

205206
<varlistentry>
206-
<term><literal>NOT NULL</literal></term>
207+
<term><literal>NOT NULL</literal> [ NO INHERIT ]</term>
207208
<listitem>
208209
<para>
209210
The column is not allowed to contain null values.
210211
</para>
212+
213+
<para>
214+
A constraint marked with <literal>NO INHERIT</literal> will not propagate to
215+
child tables.
216+
</para>
211217
</listitem>
212218
</varlistentry>
213219

0 commit comments

Comments
 (0)