Skip to content

Commit 1f66c65

Browse files
committed
Doc: document that we expect CHECK constraint conditions to be immutable.
This restriction is implicit in the check-only-once implementation we use for table and domain constraints, but it wasn't spelled out anywhere, nor was there any advice about how to alter a constraint's behavior safely. Improve that. I was also dissatisfied with the documentation of ALTER DOMAIN VALIDATE CONSTRAINT, which entirely failed to explain the use of that feature; and thence decided that ALTER TABLE VALIDATE CONSTRAINT could be documented better as well. Perhaps we should back-patch this, along with the related commit 36d442a, but for now I refrained. Discussion: https://postgr.es/m/12539.1544107316@sss.pgh.pa.us
1 parent 1464755 commit 1f66c65

File tree

4 files changed

+123
-33
lines changed

4 files changed

+123
-33
lines changed

doc/src/sgml/ddl.sgml

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,33 @@ CREATE TABLE products (
427427
guarantee, a custom <link linkend="triggers">trigger</link> can be used
428428
to implement that. (This approach avoids the dump/reload problem because
429429
<application>pg_dump</application> does not reinstall triggers until after
430-
reloading data, so that the check will not be enforced during a dump/reload.)
430+
reloading data, so that the check will not be enforced during a
431+
dump/reload.)
432+
</para>
433+
</note>
434+
435+
<note>
436+
<para>
437+
<productname>PostgreSQL</productname> assumes that
438+
<literal>CHECK</literal> constraints' conditions are immutable, that
439+
is, they will always give the same result for the same input row.
440+
This assumption is what justifies examining <literal>CHECK</literal>
441+
constraints only when rows are inserted or updated, and not at other
442+
times. (The warning above about not referencing other table data is
443+
really a special case of this restriction.)
444+
</para>
445+
446+
<para>
447+
An example of a common way to break this assumption is to reference a
448+
user-defined function in a <literal>CHECK</literal> expression, and
449+
then change the behavior of that
450+
function. <productname>PostgreSQL</productname> does not disallow
451+
that, but it will not notice if there are rows in the table that now
452+
violate the <literal>CHECK</literal> constraint. That would cause a
453+
subsequent database dump and reload to fail.
454+
The recommended way to handle such a change is to drop the constraint
455+
(using <command>ALTER TABLE</command>), adjust the function definition,
456+
and re-add the constraint, thereby rechecking it against all table rows.
431457
</para>
432458
</note>
433459
</sect2>

doc/src/sgml/ref/alter_domain.sgml

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ ALTER DOMAIN <replaceable class="parameter">name</replaceable>
118118
<listitem>
119119
<para>
120120
This form validates a constraint previously added as
121-
<literal>NOT VALID</literal>, that is, verify that all data in columns using the
122-
domain satisfy the specified constraint.
121+
<literal>NOT VALID</literal>, that is, it verifies that all values in
122+
table columns of the domain type satisfy the specified constraint.
123123
</para>
124124
</listitem>
125125
</varlistentry>
@@ -202,7 +202,7 @@ ALTER DOMAIN <replaceable class="parameter">name</replaceable>
202202
<term><literal>NOT VALID</literal></term>
203203
<listitem>
204204
<para>
205-
Do not verify existing column data for constraint validity.
205+
Do not verify existing stored data for constraint validity.
206206
</para>
207207
</listitem>
208208
</varlistentry>
@@ -272,6 +272,21 @@ ALTER DOMAIN <replaceable class="parameter">name</replaceable>
272272
<refsect1>
273273
<title>Notes</title>
274274

275+
<para>
276+
Although <command>ALTER DOMAIN ADD CONSTRAINT</command> attempts to verify
277+
that existing stored data satisfies the new constraint, this check is not
278+
bulletproof, because the command cannot <quote>see</quote> table rows that
279+
are newly inserted or updated and not yet committed. If there is a hazard
280+
that concurrent operations might insert bad data, the way to proceed is to
281+
add the constraint using the <literal>NOT VALID</literal> option, commit
282+
that command, wait until all transactions started before that commit have
283+
finished, and then issue <command>ALTER DOMAIN VALIDATE
284+
CONSTRAINT</command> to search for data violating the constraint. This
285+
method is reliable because once the constraint is committed, all new
286+
transactions are guaranteed to enforce it against new values of the domain
287+
type.
288+
</para>
289+
275290
<para>
276291
Currently, <command>ALTER DOMAIN ADD CONSTRAINT</command>, <command>ALTER
277292
DOMAIN VALIDATE CONSTRAINT</command>, and <command>ALTER DOMAIN SET NOT

doc/src/sgml/ref/alter_table.sgml

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -358,27 +358,36 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
358358
<xref linkend="sql-createtable"/>, plus the option <literal>NOT
359359
VALID</literal>, which is currently only allowed for foreign key
360360
and CHECK constraints.
361-
If the constraint is marked <literal>NOT VALID</literal>, the
362-
potentially-lengthy initial check to verify that all rows in the table
363-
satisfy the constraint is skipped. The constraint will still be
361+
</para>
362+
363+
<para>
364+
Normally, this form will cause a scan of the table to verify that all
365+
existing rows in the table satisfy the new constraint. But if
366+
the <literal>NOT VALID</literal> option is used, this
367+
potentially-lengthy scan is skipped. The constraint will still be
364368
enforced against subsequent inserts or updates (that is, they'll fail
365369
unless there is a matching row in the referenced table, in the case
366-
of foreign keys; and they'll fail unless the new row matches the
367-
specified check constraints). But the
370+
of foreign keys, or they'll fail unless the new row matches the
371+
specified check condition). But the
368372
database will not assume that the constraint holds for all rows in
369373
the table, until it is validated by using the <literal>VALIDATE
370-
CONSTRAINT</literal> option. Foreign key constraints on partitioned
371-
tables may not be declared <literal>NOT VALID</literal> at present.
374+
CONSTRAINT</literal> option.
375+
See <xref linkend="sql-altertable-notes"
376+
endterm="sql-altertable-notes-title"/> below for more information
377+
about using the <literal>NOT VALID</literal> option.
372378
</para>
373379

374380
<para>
375-
The addition of a foreign key constraint requires a
376-
<literal>SHARE ROW EXCLUSIVE</literal> lock on the referenced table.
381+
Addition of a foreign key constraint requires a
382+
<literal>SHARE ROW EXCLUSIVE</literal> lock on the referenced table,
383+
in addition to the lock on the table receiving the constraint.
377384
</para>
378385

379386
<para>
380387
Additional restrictions apply when unique or primary key constraints
381-
are added to partitioned tables; see <xref linkend="sql-createtable" />.
388+
are added to partitioned tables; see <xref linkend="sql-createtable"/>.
389+
Also, foreign key constraints on partitioned
390+
tables may not be declared <literal>NOT VALID</literal> at present.
382391
</para>
383392

384393
</listitem>
@@ -453,23 +462,13 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
453462
<term><literal>VALIDATE CONSTRAINT</literal></term>
454463
<listitem>
455464
<para>
456-
This form validates a foreign key or check constraint that was previously created
457-
as <literal>NOT VALID</literal>, by scanning the table to ensure there
458-
are no rows for which the constraint is not satisfied.
459-
Nothing happens if the constraint is already marked valid.
460-
</para>
461-
<para>
462-
Validation can be a long process on larger tables. The value of separating
463-
validation from initial creation is that you can defer validation to less
464-
busy times, or can be used to give additional time to correct pre-existing
465-
errors while preventing new errors. Note also that validation on its own
466-
does not prevent normal write commands against the table while it runs.
467-
</para>
468-
<para>
469-
Validation acquires only a <literal>SHARE UPDATE EXCLUSIVE</literal> lock
470-
on the table being altered. If the constraint is a foreign key then
471-
a <literal>ROW SHARE</literal> lock is also required on
472-
the table referenced by the constraint.
465+
This form validates a foreign key or check constraint that was
466+
previously created as <literal>NOT VALID</literal>, by scanning the
467+
table to ensure there are no rows for which the constraint is not
468+
satisfied. Nothing happens if the constraint is already marked valid.
469+
(See <xref linkend="sql-altertable-notes"
470+
endterm="sql-altertable-notes-title"/> below for an explanation of the
471+
usefulness of this command.)
473472
</para>
474473
</listitem>
475474
</varlistentry>
@@ -1153,8 +1152,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
11531152
</variablelist>
11541153
</refsect1>
11551154

1156-
<refsect1>
1157-
<title>Notes</title>
1155+
<refsect1 id="sql-altertable-notes">
1156+
<title id="sql-altertable-notes-title">Notes</title>
11581157

11591158
<para>
11601159
The key word <literal>COLUMN</literal> is noise and can be omitted.
@@ -1199,6 +1198,32 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
11991198
rewrites can thereby be combined into a single pass over the table.
12001199
</para>
12011200

1201+
<para>
1202+
Scanning a large table to verify a new foreign key or check constraint
1203+
can take a long time, and other updates to the table are locked out
1204+
until the <command>ALTER TABLE ADD CONSTRAINT</command> command is
1205+
committed. The main purpose of the <literal>NOT VALID</literal>
1206+
constraint option is to reduce the impact of adding a constraint on
1207+
concurrent updates. With <literal>NOT VALID</literal>,
1208+
the <command>ADD CONSTRAINT</command> command does not scan the table
1209+
and can be committed immediately. After that, a <literal>VALIDATE
1210+
CONSTRAINT</literal> command can be issued to verify that existing rows
1211+
satisfy the constraint. The validation step does not need to lock out
1212+
concurrent updates, since it knows that other transactions will be
1213+
enforcing the constraint for rows that they insert or update; only
1214+
pre-existing rows need to be checked. Hence, validation acquires only
1215+
a <literal>SHARE UPDATE EXCLUSIVE</literal> lock on the table being
1216+
altered. (If the constraint is a foreign key then a <literal>ROW
1217+
SHARE</literal> lock is also required on the table referenced by the
1218+
constraint.) In addition to improving concurrency, it can be useful to
1219+
use <literal>NOT VALID</literal> and <literal>VALIDATE
1220+
CONSTRAINT</literal> in cases where the table is known to contain
1221+
pre-existing violations. Once the constraint is in place, no new
1222+
violations can be inserted, and the existing problems can be corrected
1223+
at leisure until <literal>VALIDATE CONSTRAINT</literal> finally
1224+
succeeds.
1225+
</para>
1226+
12021227
<para>
12031228
The <literal>DROP COLUMN</literal> form does not physically remove
12041229
the column, but simply makes it invisible to SQL operations. Subsequent

doc/src/sgml/ref/create_domain.sgml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,30 @@ INSERT INTO tab (domcol) VALUES ((SELECT domcol FROM tab WHERE false));
214214
and then to apply column <literal>NOT NULL</literal> constraints to columns of
215215
the domain type as needed, rather than directly to the domain type.
216216
</para>
217+
218+
<para>
219+
<productname>PostgreSQL</productname> assumes that
220+
<literal>CHECK</literal> constraints' conditions are immutable, that is,
221+
they will always give the same result for the same input value. This
222+
assumption is what justifies examining <literal>CHECK</literal>
223+
constraints only when a value is first converted to be of a domain type,
224+
and not at other times. (This is essentially the same as the treatment
225+
of table <literal>CHECK</literal> constraints, as described in
226+
<xref linkend="ddl-constraints-check-constraints"/>.)
227+
</para>
228+
229+
<para>
230+
An example of a common way to break this assumption is to reference a
231+
user-defined function in a <literal>CHECK</literal> expression, and then
232+
change the behavior of that
233+
function. <productname>PostgreSQL</productname> does not disallow that,
234+
but it will not notice if there are stored values of the domain type that
235+
now violate the <literal>CHECK</literal> constraint. That would cause a
236+
subsequent database dump and reload to fail. The recommended way to
237+
handle such a change is to drop the constraint (using <command>ALTER
238+
DOMAIN</command>), adjust the function definition, and re-add the
239+
constraint, thereby rechecking it against stored data.
240+
</para>
217241
</refsect1>
218242

219243
<refsect1>

0 commit comments

Comments
 (0)