Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit ca87c41

Browse files
committed
Add support for NOT ENFORCED in CHECK constraints
This adds support for the NOT ENFORCED/ENFORCED flag for constraints, with support for check constraints. The plan is to eventually support this for foreign key constraints, where it is typically more useful. Note that CHECK constraints do not currently support ALTER operations, so changing the enforceability of an existing constraint isn't possible without dropping and recreating it. This could be added later. Author: Amul Sul <amul.sul@enterprisedb.com> Reviewed-by: Peter Eisentraut <peter@eisentraut.org> Reviewed-by: jian he <jian.universality@gmail.com> Tested-by: Triveni N <triveni.n@enterprisedb.com> Discussion: https://www.postgresql.org/message-id/flat/CAAJ_b962c5AcYW9KUt_R_ER5qs3fUGbe4az-SP-vuwPS-w-AGA@mail.gmail.com
1 parent 72ceb21 commit ca87c41

37 files changed

+599
-70
lines changed

doc/src/sgml/catalogs.sgml

+10
Original file line numberDiff line numberDiff line change
@@ -2591,6 +2591,16 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
25912591
</para></entry>
25922592
</row>
25932593

2594+
<row>
2595+
<entry role="catalog_table_entry"><para role="column_definition">
2596+
<structfield>conenforced</structfield> <type>bool</type>
2597+
</para>
2598+
<para>
2599+
Is the constraint enforced?
2600+
Currently, can be false only for CHECK constraints
2601+
</para></entry>
2602+
</row>
2603+
25942604
<row>
25952605
<entry role="catalog_table_entry"><para role="column_definition">
25962606
<structfield>convalidated</structfield> <type>bool</type>

doc/src/sgml/information_schema.sgml

+1-3
Original file line numberDiff line numberDiff line change
@@ -6896,9 +6896,7 @@ ORDER BY c.ordinal_position;
68966896
<structfield>enforced</structfield> <type>yes_or_no</type>
68976897
</para>
68986898
<para>
6899-
Applies to a feature not available in
6900-
<productname>PostgreSQL</productname> (currently always
6901-
<literal>YES</literal>)
6899+
<literal>YES</literal> if the constraint is enforced, <literal>NO</literal> if not
69026900
</para></entry>
69036901
</row>
69046902

doc/src/sgml/ref/alter_table.sgml

+7-5
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
108108
PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
109109
REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
110110
[ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
111-
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
111+
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] [ ENFORCED | NOT ENFORCED ]
112112

113113
<phrase>and <replaceable class="parameter">table_constraint</replaceable> is:</phrase>
114114

@@ -120,7 +120,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
120120
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> ) ] |
121121
FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">column_name</replaceable> ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">refcolumn</replaceable> ] ) ]
122122
[ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
123-
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
123+
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] [ ENFORCED | NOT ENFORCED ]
124124

125125
<phrase>and <replaceable class="parameter">table_constraint_using_index</replaceable> is:</phrase>
126126

@@ -1423,9 +1423,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
14231423
</para>
14241424

14251425
<para>
1426-
Adding a <literal>CHECK</literal> or <literal>NOT NULL</literal> constraint requires
1427-
scanning the table to verify that existing rows meet the constraint,
1428-
but does not require a table rewrite.
1426+
Adding an enforced <literal>CHECK</literal> or <literal>NOT NULL</literal>
1427+
constraint requires scanning the table to verify that existing rows meet the
1428+
constraint, but does not require a table rewrite. If a <literal>CHECK</literal>
1429+
constraint is added as <literal>NOT ENFORCED</literal>, the validation will
1430+
not be performed.
14291431
</para>
14301432

14311433
<para>

doc/src/sgml/ref/create_foreign_table.sgml

+4-2
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,14 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="parameter">table_name
4848
CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
4949
DEFAULT <replaceable>default_expr</replaceable> |
5050
GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED }
51+
[ ENFORCED | NOT ENFORCED ]
5152

5253
<phrase>and <replaceable class="parameter">table_constraint</replaceable> is:</phrase>
5354

5455
[ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
55-
NOT NULL <replaceable class="parameter">column_name</replaceable> [ NO INHERIT ] |
56-
CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ]
56+
{ NOT NULL <replaceable class="parameter">column_name</replaceable> [ NO INHERIT ] |
57+
CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] }
58+
[ ENFORCED | NOT ENFORCED ]
5759

5860
<phrase>and <replaceable class="parameter">partition_bound_spec</replaceable> is:</phrase>
5961

doc/src/sgml/ref/create_table.sgml

+32-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
7171
PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
7272
REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
7373
[ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
74-
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
74+
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] [ ENFORCED | NOT ENFORCED ]
7575

7676
<phrase>and <replaceable class="parameter">table_constraint</replaceable> is:</phrase>
7777

@@ -84,7 +84,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
8484
FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">column_name</replaceable> ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">refcolumn</replaceable> ] ) ]
8585
[ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable
8686
class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
87-
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
87+
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] [ ENFORCED | NOT ENFORCED ]
8888

8989
<phrase>and <replaceable class="parameter">like_option</replaceable> is:</phrase>
9090

@@ -1377,6 +1377,36 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
13771377
</listitem>
13781378
</varlistentry>
13791379

1380+
<varlistentry id="sql-createtable-parms-enforced">
1381+
<term><literal>ENFORCED</literal></term>
1382+
<term><literal>NOT ENFORCED</literal></term>
1383+
<listitem>
1384+
<para>
1385+
When the constraint is <literal>ENFORCED</literal>, then the database
1386+
system will ensure that the constraint is satisfied, by checking the
1387+
constraint at appropriate times (after each statement or at the end of
1388+
the transaction, as appropriate). That is the default. If the
1389+
constraint is <literal>NOT ENFORCED</literal>, the database system will
1390+
not check the constraint. It is then up to the application code to
1391+
ensure that the constraints are satisfied. The database system might
1392+
still assume that the data actually satisfies the constraint for
1393+
optimization decisions where this does not affect the correctness of the
1394+
result.
1395+
</para>
1396+
1397+
<para>
1398+
<literal>NOT ENFORCED</literal> constraints can be useful as
1399+
documentation if the actual checking of the constraint at run time is
1400+
too expensive.
1401+
</para>
1402+
1403+
<para>
1404+
This is currently only supported for <literal>CHECK</literal>
1405+
constraints.
1406+
</para>
1407+
</listitem>
1408+
</varlistentry>
1409+
13801410
<varlistentry id="sql-createtable-method">
13811411
<term><literal>USING <replaceable class="parameter">method</replaceable></literal></term>
13821412
<listitem>

src/backend/access/common/tupdesc.c

+2
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
376376
{
377377
cpy->check[i].ccname = pstrdup(constr->check[i].ccname);
378378
cpy->check[i].ccbin = pstrdup(constr->check[i].ccbin);
379+
cpy->check[i].ccenforced = constr->check[i].ccenforced;
379380
cpy->check[i].ccvalid = constr->check[i].ccvalid;
380381
cpy->check[i].ccnoinherit = constr->check[i].ccnoinherit;
381382
}
@@ -692,6 +693,7 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
692693

693694
if (!(strcmp(check1->ccname, check2->ccname) == 0 &&
694695
strcmp(check1->ccbin, check2->ccbin) == 0 &&
696+
check1->ccenforced == check2->ccenforced &&
695697
check1->ccvalid == check2->ccvalid &&
696698
check1->ccnoinherit == check2->ccnoinherit))
697699
return false;

src/backend/catalog/heap.c

+45-10
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,13 @@ static ObjectAddress AddNewRelationType(const char *typeName,
102102
Oid new_array_type);
103103
static void RelationRemoveInheritance(Oid relid);
104104
static Oid StoreRelCheck(Relation rel, const char *ccname, Node *expr,
105-
bool is_validated, bool is_local, int16 inhcount,
106-
bool is_no_inherit, bool is_internal);
105+
bool is_enforced, bool is_validated, bool is_local,
106+
int16 inhcount, bool is_no_inherit, bool is_internal);
107107
static void StoreConstraints(Relation rel, List *cooked_constraints,
108108
bool is_internal);
109109
static bool MergeWithExistingConstraint(Relation rel, const char *ccname, Node *expr,
110110
bool allow_merge, bool is_local,
111+
bool is_enforced,
111112
bool is_initially_valid,
112113
bool is_no_inherit);
113114
static void SetRelationNumChecks(Relation rel, int numchecks);
@@ -2066,8 +2067,8 @@ SetAttrMissing(Oid relid, char *attname, char *value)
20662067
*/
20672068
static Oid
20682069
StoreRelCheck(Relation rel, const char *ccname, Node *expr,
2069-
bool is_validated, bool is_local, int16 inhcount,
2070-
bool is_no_inherit, bool is_internal)
2070+
bool is_enforced, bool is_validated, bool is_local,
2071+
int16 inhcount, bool is_no_inherit, bool is_internal)
20712072
{
20722073
char *ccbin;
20732074
List *varList;
@@ -2132,6 +2133,7 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
21322133
CONSTRAINT_CHECK, /* Constraint Type */
21332134
false, /* Is Deferrable */
21342135
false, /* Is Deferred */
2136+
is_enforced, /* Is Enforced */
21352137
is_validated,
21362138
InvalidOid, /* no parent constraint */
21372139
RelationGetRelid(rel), /* relation */
@@ -2185,6 +2187,7 @@ StoreRelNotNull(Relation rel, const char *nnname, AttrNumber attnum,
21852187
CONSTRAINT_NOTNULL,
21862188
false,
21872189
false,
2190+
true, /* Is Enforced */
21882191
is_validated,
21892192
InvalidOid,
21902193
RelationGetRelid(rel),
@@ -2254,9 +2257,9 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal)
22542257
case CONSTR_CHECK:
22552258
con->conoid =
22562259
StoreRelCheck(rel, con->name, con->expr,
2257-
!con->skip_validation, con->is_local,
2258-
con->inhcount, con->is_no_inherit,
2259-
is_internal);
2260+
con->is_enforced, !con->skip_validation,
2261+
con->is_local, con->inhcount,
2262+
con->is_no_inherit, is_internal);
22602263
numchecks++;
22612264
break;
22622265

@@ -2390,6 +2393,7 @@ AddRelationNewConstraints(Relation rel,
23902393
cooked->name = NULL;
23912394
cooked->attnum = colDef->attnum;
23922395
cooked->expr = expr;
2396+
cooked->is_enforced = true;
23932397
cooked->skip_validation = false;
23942398
cooked->is_local = is_local;
23952399
cooked->inhcount = is_local ? 0 : 1;
@@ -2461,6 +2465,7 @@ AddRelationNewConstraints(Relation rel,
24612465
*/
24622466
if (MergeWithExistingConstraint(rel, ccname, expr,
24632467
allow_merge, is_local,
2468+
cdef->is_enforced,
24642469
cdef->initially_valid,
24652470
cdef->is_no_inherit))
24662471
continue;
@@ -2509,8 +2514,10 @@ AddRelationNewConstraints(Relation rel,
25092514
* OK, store it.
25102515
*/
25112516
constrOid =
2512-
StoreRelCheck(rel, ccname, expr, cdef->initially_valid, is_local,
2513-
is_local ? 0 : 1, cdef->is_no_inherit, is_internal);
2517+
StoreRelCheck(rel, ccname, expr, cdef->is_enforced,
2518+
cdef->initially_valid, is_local,
2519+
is_local ? 0 : 1, cdef->is_no_inherit,
2520+
is_internal);
25142521

25152522
numchecks++;
25162523

@@ -2520,6 +2527,7 @@ AddRelationNewConstraints(Relation rel,
25202527
cooked->name = ccname;
25212528
cooked->attnum = 0;
25222529
cooked->expr = expr;
2530+
cooked->is_enforced = cdef->is_enforced;
25232531
cooked->skip_validation = cdef->skip_validation;
25242532
cooked->is_local = is_local;
25252533
cooked->inhcount = is_local ? 0 : 1;
@@ -2590,6 +2598,7 @@ AddRelationNewConstraints(Relation rel,
25902598
nncooked->name = nnname;
25912599
nncooked->attnum = colnum;
25922600
nncooked->expr = NULL;
2601+
nncooked->is_enforced = true;
25932602
nncooked->skip_validation = cdef->skip_validation;
25942603
nncooked->is_local = is_local;
25952604
nncooked->inhcount = inhcount;
@@ -2624,6 +2633,7 @@ AddRelationNewConstraints(Relation rel,
26242633
static bool
26252634
MergeWithExistingConstraint(Relation rel, const char *ccname, Node *expr,
26262635
bool allow_merge, bool is_local,
2636+
bool is_enforced,
26272637
bool is_initially_valid,
26282638
bool is_no_inherit)
26292639
{
@@ -2714,12 +2724,24 @@ MergeWithExistingConstraint(Relation rel, const char *ccname, Node *expr,
27142724
* If the child constraint is "not valid" then cannot merge with a
27152725
* valid parent constraint.
27162726
*/
2717-
if (is_initially_valid && !con->convalidated)
2727+
if (is_initially_valid && con->conenforced && !con->convalidated)
27182728
ereport(ERROR,
27192729
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
27202730
errmsg("constraint \"%s\" conflicts with NOT VALID constraint on relation \"%s\"",
27212731
ccname, RelationGetRelationName(rel))));
27222732

2733+
/*
2734+
* A non-enforced child constraint cannot be merged with an enforced
2735+
* parent constraint. However, the reverse is allowed, where the child
2736+
* constraint is enforced.
2737+
*/
2738+
if ((!is_local && is_enforced && !con->conenforced) ||
2739+
(is_local && !is_enforced && con->conenforced))
2740+
ereport(ERROR,
2741+
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
2742+
errmsg("constraint \"%s\" conflicts with NOT ENFORCED constraint on relation \"%s\"",
2743+
ccname, RelationGetRelationName(rel))));
2744+
27232745
/* OK to update the tuple */
27242746
ereport(NOTICE,
27252747
(errmsg("merging constraint \"%s\" with inherited definition",
@@ -2755,6 +2777,19 @@ MergeWithExistingConstraint(Relation rel, const char *ccname, Node *expr,
27552777
con->connoinherit = true;
27562778
}
27572779

2780+
/*
2781+
* If the child constraint is required to be enforced while the parent
2782+
* constraint is not, this should be allowed by marking the child
2783+
* constraint as enforced. In the reverse case, an error would have
2784+
* already been thrown before reaching this point.
2785+
*/
2786+
if (is_enforced && !con->conenforced)
2787+
{
2788+
Assert(is_local);
2789+
con->conenforced = true;
2790+
con->convalidated = true;
2791+
}
2792+
27582793
CatalogTupleUpdate(conDesc, &tup->t_self, tup);
27592794
}
27602795

src/backend/catalog/index.c

+1
Original file line numberDiff line numberDiff line change
@@ -1958,6 +1958,7 @@ index_constraint_create(Relation heapRelation,
19581958
constraintType,
19591959
deferrable,
19601960
initdeferred,
1961+
true, /* Is Enforced */
19611962
true,
19621963
parentConstraintId,
19631964
RelationGetRelid(heapRelation),

src/backend/catalog/information_schema.sql

+1-1
Original file line numberDiff line numberDiff line change
@@ -1844,7 +1844,7 @@ CREATE VIEW table_constraints AS
18441844
AS is_deferrable,
18451845
CAST(CASE WHEN c.condeferred THEN 'YES' ELSE 'NO' END AS yes_or_no)
18461846
AS initially_deferred,
1847-
CAST('YES' AS yes_or_no) AS enforced,
1847+
CAST(CASE WHEN c.conenforced THEN 'YES' ELSE 'NO' END AS yes_or_no) AS enforced,
18481848
CAST(CASE WHEN c.contype = 'u'
18491849
THEN CASE WHEN (SELECT NOT indnullsnotdistinct FROM pg_index WHERE indexrelid = conindid) THEN 'YES' ELSE 'NO' END
18501850
END

src/backend/catalog/pg_constraint.c

+9
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ CreateConstraintEntry(const char *constraintName,
5353
char constraintType,
5454
bool isDeferrable,
5555
bool isDeferred,
56+
bool isEnforced,
5657
bool isValidated,
5758
Oid parentConstrId,
5859
Oid relId,
@@ -99,6 +100,11 @@ CreateConstraintEntry(const char *constraintName,
99100
ObjectAddresses *addrs_auto;
100101
ObjectAddresses *addrs_normal;
101102

103+
/* Only CHECK constraint can be not enforced */
104+
Assert(isEnforced || constraintType == CONSTRAINT_CHECK);
105+
/* NOT ENFORCED constraint must be NOT VALID */
106+
Assert(isEnforced || !isValidated);
107+
102108
conDesc = table_open(ConstraintRelationId, RowExclusiveLock);
103109

104110
Assert(constraintName);
@@ -182,6 +188,7 @@ CreateConstraintEntry(const char *constraintName,
182188
values[Anum_pg_constraint_contype - 1] = CharGetDatum(constraintType);
183189
values[Anum_pg_constraint_condeferrable - 1] = BoolGetDatum(isDeferrable);
184190
values[Anum_pg_constraint_condeferred - 1] = BoolGetDatum(isDeferred);
191+
values[Anum_pg_constraint_conenforced - 1] = BoolGetDatum(isEnforced);
185192
values[Anum_pg_constraint_convalidated - 1] = BoolGetDatum(isValidated);
186193
values[Anum_pg_constraint_conrelid - 1] = ObjectIdGetDatum(relId);
187194
values[Anum_pg_constraint_contypid - 1] = ObjectIdGetDatum(domainId);
@@ -822,6 +829,7 @@ RelationGetNotNullConstraints(Oid relid, bool cooked, bool include_noinh)
822829
cooked->name = pstrdup(NameStr(conForm->conname));
823830
cooked->attnum = colnum;
824831
cooked->expr = NULL;
832+
cooked->is_enforced = true;
825833
cooked->skip_validation = false;
826834
cooked->is_local = true;
827835
cooked->inhcount = 0;
@@ -841,6 +849,7 @@ RelationGetNotNullConstraints(Oid relid, bool cooked, bool include_noinh)
841849
constr->location = -1;
842850
constr->keys = list_make1(makeString(get_attname(relid, colnum,
843851
false)));
852+
constr->is_enforced = true;
844853
constr->skip_validation = false;
845854
constr->initially_valid = true;
846855
constr->is_no_inherit = conForm->connoinherit;

src/backend/catalog/sql_features.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ F461 Named character sets NO
281281
F471 Scalar subquery values YES
282282
F481 Expanded NULL predicate YES
283283
F491 Constraint management YES
284-
F492 Optional table constraint enforcement NO
284+
F492 Optional table constraint enforcement NO check constraints only
285285
F501 Features and conformance views YES
286286
F501 Features and conformance views 01 SQL_FEATURES view YES
287287
F501 Features and conformance views 02 SQL_SIZING view YES

0 commit comments

Comments
 (0)