Skip to content

Commit 16cd99b

Browse files
jianhe-funCommitfest Bot
authored andcommitted
1 parent 040cc5f commit 16cd99b

File tree

6 files changed

+345
-11
lines changed

6 files changed

+345
-11
lines changed

doc/src/sgml/ref/alter_table.sgml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -569,8 +569,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
569569
<listitem>
570570
<para>
571571
This form alters the attributes of a constraint that was previously
572-
created. Currently only foreign key constraints may be altered in
573-
this fashion, but see below.
572+
created. Currently <literal>FOREIGN KEY</literal> and
573+
<literal>CHECK</literal> constraints may be altered in this fashion, but see below.
574574
</para>
575575
</listitem>
576576
</varlistentry>

src/backend/commands/tablecmds.c

Lines changed: 154 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,9 @@ static bool ATExecAlterConstrEnforceability(List **wqueue, ATAlterConstraint *cm
404404
Oid ReferencedParentUpdTrigger,
405405
Oid ReferencingParentInsTrigger,
406406
Oid ReferencingParentUpdTrigger);
407+
static bool ATExecAlterCheckConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
408+
Relation conrel, HeapTuple contuple,
409+
bool recurse, bool recursing, LOCKMODE lockmode);
407410
static bool ATExecAlterConstrDeferrability(List **wqueue, ATAlterConstraint *cmdcon,
408411
Relation conrel, Relation tgrel, Relation rel,
409412
HeapTuple contuple, bool recurse,
@@ -422,6 +425,9 @@ static void AlterConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *c
422425
Oid ReferencedParentUpdTrigger,
423426
Oid ReferencingParentInsTrigger,
424427
Oid ReferencingParentUpdTrigger);
428+
static void AlterCheckConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
429+
Relation conrel, Oid conrelid,
430+
bool recurse, bool recursing, LOCKMODE lockmode);
425431
static void AlterConstrDeferrabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
426432
Relation conrel, Relation tgrel, Relation rel,
427433
HeapTuple contuple, bool recurse,
@@ -12181,7 +12187,7 @@ GetForeignKeyCheckTriggers(Relation trigrel,
1218112187
*
1218212188
* Update the attributes of a constraint.
1218312189
*
12184-
* Currently only works for Foreign Key and not null constraints.
12190+
* Currently works for Foreign Key, CHECK, and not null constraints.
1218512191
*
1218612192
* If the constraint is modified, returns its address; otherwise, return
1218712193
* InvalidObjectAddress.
@@ -12243,11 +12249,13 @@ ATExecAlterConstraint(List **wqueue, Relation rel, ATAlterConstraint *cmdcon,
1224312249
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
1224412250
errmsg("constraint \"%s\" of relation \"%s\" is not a foreign key constraint",
1224512251
cmdcon->conname, RelationGetRelationName(rel))));
12246-
if (cmdcon->alterEnforceability && currcon->contype != CONSTRAINT_FOREIGN)
12252+
if (cmdcon->alterEnforceability &&
12253+
(currcon->contype != CONSTRAINT_FOREIGN && currcon->contype != CONSTRAINT_CHECK))
1224712254
ereport(ERROR,
12248-
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
12249-
errmsg("cannot alter enforceability of constraint \"%s\" of relation \"%s\"",
12250-
cmdcon->conname, RelationGetRelationName(rel))));
12255+
errcode(ERRCODE_WRONG_OBJECT_TYPE),
12256+
errmsg("cannot alter enforceability of constraint \"%s\" of relation \"%s\"",
12257+
cmdcon->conname, RelationGetRelationName(rel)),
12258+
errhint("Only foreign key and check constraints can change enforceability"));
1225112259
if (cmdcon->alterInheritability &&
1225212260
currcon->contype != CONSTRAINT_NOTNULL)
1225312261
ereport(ERROR,
@@ -12349,16 +12357,20 @@ ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
1234912357
* enforceability, we don't need to explicitly update multiple entries in
1235012358
* pg_trigger related to deferrability.
1235112359
*
12352-
* Modifying enforceability involves either creating or dropping the
12353-
* trigger, during which the deferrability setting will be adjusted
12360+
* Modifying foreign key enforceability involves either creating or dropping
12361+
* the trigger, during which the deferrability setting will be adjusted
1235412362
* automatically.
1235512363
*/
12356-
if (cmdcon->alterEnforceability &&
12364+
if (cmdcon->alterEnforceability && currcon->contype == CONSTRAINT_FOREIGN &&
1235712365
ATExecAlterConstrEnforceability(wqueue, cmdcon, conrel, tgrel,
1235812366
currcon->conrelid, currcon->confrelid,
1235912367
contuple, lockmode, InvalidOid,
1236012368
InvalidOid, InvalidOid, InvalidOid))
1236112369
changed = true;
12370+
else if (cmdcon->alterEnforceability && currcon->contype == CONSTRAINT_CHECK &&
12371+
ATExecAlterCheckConstrEnforceability(wqueue, cmdcon, conrel, contuple,
12372+
recurse, false, lockmode))
12373+
changed = true;
1236212374

1236312375
else if (cmdcon->alterDeferrability &&
1236412376
ATExecAlterConstrDeferrability(wqueue, cmdcon, conrel, tgrel, rel,
@@ -12389,7 +12401,140 @@ ATExecAlterConstraintInternal(List **wqueue, ATAlterConstraint *cmdcon,
1238912401
}
1239012402

1239112403
/*
12392-
* Returns true if the constraint's enforceability is altered.
12404+
* Returns true if the CHECK constraint's enforceability is altered.
12405+
*
12406+
* Note that we must recurse even when trying to change a check constraint to
12407+
* not enforced if it is already not enforced, in case descendant constraints
12408+
* might be enforced and need to be changed to not enforced. Conversely, we
12409+
* should do nothing if a constraint is being set to enforced and is already
12410+
* enforced, as descendant constraints cannot be different in that case.
12411+
*/
12412+
static bool
12413+
ATExecAlterCheckConstrEnforceability(List **wqueue, ATAlterConstraint *cmdcon,
12414+
Relation conrel, HeapTuple contuple,
12415+
bool recurse, bool recursing, LOCKMODE lockmode)
12416+
{
12417+
Form_pg_constraint currcon;
12418+
Relation rel;
12419+
bool changed = false;
12420+
List *children = NIL;
12421+
12422+
/* Since this function recurses, it could be driven to stack overflow */
12423+
check_stack_depth();
12424+
12425+
Assert(cmdcon->alterEnforceability);
12426+
12427+
currcon = (Form_pg_constraint) GETSTRUCT(contuple);
12428+
12429+
Assert(currcon->contype == CONSTRAINT_CHECK);
12430+
12431+
rel = table_open(currcon->conrelid, lockmode);
12432+
if (currcon->conenforced != cmdcon->is_enforced)
12433+
{
12434+
AlterConstrUpdateConstraintEntry(cmdcon, conrel, contuple);
12435+
changed = true;
12436+
}
12437+
12438+
if (!cmdcon->is_enforced || changed)
12439+
{
12440+
/*
12441+
* If we're recursing, the parent has already done this, so skip it.
12442+
* Also, if the constraint is a NO INHERIT constraint, we shouldn't try
12443+
* to look for it in the children.
12444+
*/
12445+
if (!recursing && !currcon->connoinherit)
12446+
children = find_all_inheritors(RelationGetRelid(rel),
12447+
lockmode, NULL);
12448+
12449+
foreach_oid(childoid, children)
12450+
{
12451+
if (childoid == RelationGetRelid(rel))
12452+
continue;
12453+
12454+
/*
12455+
* If we are told not to recurse, there had better not be any child
12456+
* tables, because we can't change constraint enforceability on the
12457+
* parent unless we have changed enforceability for all child.
12458+
*/
12459+
if (!recurse)
12460+
ereport(ERROR,
12461+
errcode(ERRCODE_INVALID_TABLE_DEFINITION),
12462+
errmsg("constraint must be altered on child tables too"));
12463+
12464+
AlterCheckConstrEnforceabilityRecurse(wqueue, cmdcon, conrel, childoid, false, true, lockmode);
12465+
}
12466+
}
12467+
12468+
/*
12469+
* Tell Phase 3 to check that the constraint is satisfied by existing rows.
12470+
* We do this only when alter the constraint from not enforced to enforced.
12471+
*/
12472+
if (rel->rd_rel->relkind == RELKIND_RELATION &&
12473+
cmdcon->is_enforced &&
12474+
!currcon->conenforced)
12475+
{
12476+
AlteredTableInfo *tab;
12477+
NewConstraint *newcon;
12478+
Datum val;
12479+
char *conbin;
12480+
12481+
newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
12482+
newcon->name = pstrdup(NameStr(currcon->conname));
12483+
newcon->contype = CONSTR_CHECK;
12484+
val = SysCacheGetAttrNotNull(CONSTROID, contuple,
12485+
Anum_pg_constraint_conbin);
12486+
conbin = TextDatumGetCString(val);
12487+
newcon->qual = expand_generated_columns_in_expr(stringToNode(conbin), rel, 1);
12488+
12489+
/* Find or create work queue entry for this table */
12490+
tab = ATGetQueueEntry(wqueue, rel);
12491+
tab->constraints = lappend(tab->constraints, newcon);
12492+
}
12493+
12494+
table_close(rel, NoLock);
12495+
return changed;
12496+
}
12497+
12498+
static void
12499+
AlterCheckConstrEnforceabilityRecurse(List **wqueue, ATAlterConstraint *cmdcon,
12500+
Relation conrel, Oid conrelid,
12501+
bool recurse, bool recursing,
12502+
LOCKMODE lockmode)
12503+
{
12504+
SysScanDesc pscan;
12505+
HeapTuple childtup;
12506+
ScanKeyData skey[3];
12507+
12508+
ScanKeyInit(&skey[0],
12509+
Anum_pg_constraint_conrelid,
12510+
BTEqualStrategyNumber, F_OIDEQ,
12511+
ObjectIdGetDatum(conrelid));
12512+
ScanKeyInit(&skey[1],
12513+
Anum_pg_constraint_contypid,
12514+
BTEqualStrategyNumber, F_OIDEQ,
12515+
ObjectIdGetDatum(InvalidOid));
12516+
ScanKeyInit(&skey[2],
12517+
Anum_pg_constraint_conname,
12518+
BTEqualStrategyNumber, F_NAMEEQ,
12519+
CStringGetDatum(cmdcon->conname));
12520+
12521+
pscan = systable_beginscan(conrel, ConstraintRelidTypidNameIndexId, true,
12522+
NULL, 3, skey);
12523+
12524+
if (!HeapTupleIsValid(childtup = systable_getnext(pscan)))
12525+
ereport(ERROR,
12526+
errcode(ERRCODE_UNDEFINED_OBJECT),
12527+
errmsg("constraint \"%s\" of relation \"%s\" does not exist",
12528+
cmdcon->conname, get_rel_name(conrelid)));
12529+
12530+
ATExecAlterCheckConstrEnforceability(wqueue, cmdcon, conrel, childtup,
12531+
recurse, recursing, lockmode);
12532+
12533+
systable_endscan(pscan);
12534+
}
12535+
12536+
/*
12537+
* Returns true if the FOREIGN KEY constraint's enforceability is altered.
1239312538
*
1239412539
* Depending on whether the constraint is being set to ENFORCED or NOT
1239512540
* ENFORCED, it creates or drops the trigger accordingly.

src/test/regress/expected/constraints.out

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,59 @@ SELECT * FROM COPY_TBL;
390390
6 | OK | 4
391391
(2 rows)
392392

393+
--
394+
-- CHECK constraints
395+
-- ALTER TABLE ALTER CONSTRAINT [NOT] ENFORCED
396+
create table parted_ch_tbl(a int, constraint cc check (a > 10) not enforced, b int ) partition by range(a);
397+
create table parted_ch_tbl_1 partition of parted_ch_tbl for values from (0) to (10) partition by list(b);
398+
create table parted_ch_tbl_11 partition of parted_ch_tbl_1 for values in (0, 1);
399+
create table parted_ch_tbl_12 partition of parted_ch_tbl_1 for values in (2);
400+
create table parted_ch_tbl_2 partition of parted_ch_tbl for values from (10) to (20);
401+
alter table parted_ch_tbl_2 add constraint cc_2 check( a < 15) not enforced;
402+
insert into parted_ch_tbl values (1, 2), (9, 1), (16, 16);
403+
create view check_constraint_status as
404+
select conname, conrelid::regclass, conenforced, convalidated
405+
from pg_constraint
406+
where conrelid::regclass::text ~* '^parted_ch' and contype = 'c'
407+
order by conrelid::regclass, conname;
408+
alter table parted_ch_tbl alter constraint cc not enforced; --no-op
409+
alter table parted_ch_tbl alter constraint cc enforced; --error
410+
ERROR: check constraint "cc" of relation "parted_ch_tbl_11" is violated by some row
411+
delete from parted_ch_tbl where a = 1;
412+
alter table parted_ch_tbl alter constraint cc enforced; --error
413+
ERROR: check constraint "cc" of relation "parted_ch_tbl_11" is violated by some row
414+
delete from parted_ch_tbl where a = 9;
415+
alter table parted_ch_tbl alter constraint cc enforced; --ok
416+
--check these CHECK constraint status
417+
select * from check_constraint_status;
418+
conname | conrelid | conenforced | convalidated
419+
---------+------------------+-------------+--------------
420+
cc | parted_ch_tbl | t | t
421+
cc | parted_ch_tbl_1 | t | t
422+
cc | parted_ch_tbl_11 | t | t
423+
cc | parted_ch_tbl_12 | t | t
424+
cc | parted_ch_tbl_2 | t | t
425+
cc_2 | parted_ch_tbl_2 | f | f
426+
(6 rows)
427+
428+
alter table parted_ch_tbl_2 alter constraint cc_2 enforced; --error
429+
ERROR: check constraint "cc_2" of relation "parted_ch_tbl_2" is violated by some row
430+
delete from parted_ch_tbl where a = 16;
431+
alter table parted_ch_tbl_2 alter constraint cc_2 enforced; --ok
432+
alter table parted_ch_tbl alter constraint cc not enforced; --ok
433+
--check these CHECK constraint status again
434+
select * from check_constraint_status;
435+
conname | conrelid | conenforced | convalidated
436+
---------+------------------+-------------+--------------
437+
cc | parted_ch_tbl | f | f
438+
cc | parted_ch_tbl_1 | f | f
439+
cc | parted_ch_tbl_11 | f | f
440+
cc | parted_ch_tbl_12 | f | f
441+
cc | parted_ch_tbl_2 | f | f
442+
cc_2 | parted_ch_tbl_2 | t | t
443+
(6 rows)
444+
445+
drop table parted_ch_tbl;
393446
--
394447
-- Primary keys
395448
--
@@ -746,8 +799,10 @@ LINE 1: CREATE TABLE UNIQUE_NOTEN_TBL(i int UNIQUE NOT ENFORCED);
746799
^
747800
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key ENFORCED;
748801
ERROR: cannot alter enforceability of constraint "unique_tbl_i_key" of relation "unique_tbl"
802+
HINT: Only foreign key and check constraints can change enforceability
749803
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key NOT ENFORCED;
750804
ERROR: cannot alter enforceability of constraint "unique_tbl_i_key" of relation "unique_tbl"
805+
HINT: Only foreign key and check constraints can change enforceability
751806
-- can't make an existing constraint NOT VALID
752807
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key NOT VALID;
753808
ERROR: constraints cannot be altered to be NOT VALID

src/test/regress/expected/inherit.out

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,11 +1421,50 @@ order by 1, 2;
14211421
p1_c3 | inh_check_constraint9 | f | 2 | t | t
14221422
(38 rows)
14231423

1424+
-- Tests for ALTER TABLE ALTER CONSTRAINT [NOT] ENFORCED
1425+
alter table p1 drop constraint inh_check_constraint1;
1426+
alter table p1_c1 drop constraint inh_check_constraint1;
1427+
insert into p1_c1 values(-2);
1428+
insert into p1_c3 values(-3);
1429+
alter table only p1 alter constraint inh_check_constraint3 enforced; --error
1430+
ERROR: constraint must be altered on child tables too
1431+
alter table only p1 alter constraint inh_check_constraint3 not enforced; --error
1432+
ERROR: constraint must be altered on child tables too
1433+
alter table p1 alter constraint inh_check_constraint3 enforced; --error
1434+
ERROR: check constraint "inh_check_constraint3" of relation "p1_c1" is violated by some row
1435+
delete from only p1_c1 where f1 = -2;
1436+
alter table p1_c1 alter constraint inh_check_constraint3 enforced; --error
1437+
ERROR: check constraint "inh_check_constraint3" of relation "p1_c3" is violated by some row
1438+
delete from only p1_c3 where f1 = -3;
1439+
alter table p1 alter constraint inh_check_constraint3 enforced; --ok
1440+
alter table p1 alter constraint inh_check_constraint3 not enforced; --ok
1441+
select conname, conenforced, convalidated, conrelid::regclass
1442+
from pg_constraint
1443+
where conname = 'inh_check_constraint3' and contype = 'c'
1444+
order by conrelid::regclass::text collate "C";
1445+
conname | conenforced | convalidated | conrelid
1446+
-----------------------+-------------+--------------+----------
1447+
inh_check_constraint3 | f | f | p1
1448+
inh_check_constraint3 | f | f | p1_c1
1449+
inh_check_constraint3 | f | f | p1_c2
1450+
inh_check_constraint3 | f | f | p1_c3
1451+
(4 rows)
1452+
14241453
drop table p1 cascade;
14251454
NOTICE: drop cascades to 3 other objects
14261455
DETAIL: drop cascades to table p1_c1
14271456
drop cascades to table p1_c2
14281457
drop cascades to table p1_c3
1458+
--for "no inherit" check constraint, it will not recurse to child table
1459+
create table p1(f1 int constraint p1_a_check check (f1 > 0) no inherit not enforced);
1460+
create table p1_c1(f1 int constraint p1_a_check check (f1 > 0) not enforced);
1461+
alter table p1_c1 inherit p1;
1462+
insert into p1_c1 values(-11);
1463+
alter table p1 alter constraint p1_a_check enforced; --ok
1464+
alter table p1_c1 alter constraint p1_a_check enforced; --error
1465+
ERROR: check constraint "p1_a_check" of relation "p1_c1" is violated by some row
1466+
drop table p1 cascade;
1467+
NOTICE: drop cascades to table p1_c1
14291468
--
14301469
-- Similarly, check the merging of existing constraints; a parent constraint
14311470
-- marked as NOT ENFORCED can merge with an ENFORCED child constraint, but the
@@ -1434,6 +1473,25 @@ drop cascades to table p1_c3
14341473
create table p1(f1 int constraint p1_a_check check (f1 > 0) not enforced);
14351474
create table p1_c1(f1 int constraint p1_a_check check (f1 > 0) enforced);
14361475
alter table p1_c1 inherit p1;
1476+
insert into p1 values(-1); --ok
1477+
insert into p1_c1 values(-1); --error
1478+
ERROR: new row for relation "p1_c1" violates check constraint "p1_a_check"
1479+
DETAIL: Failing row contains (-1).
1480+
alter table p1 alter constraint p1_a_check enforced; --error
1481+
ERROR: check constraint "p1_a_check" of relation "p1" is violated by some row
1482+
truncate p1;
1483+
alter table p1 alter constraint p1_a_check enforced; --ok
1484+
alter table p1 alter constraint p1_a_check not enforced; --ok
1485+
select conname, conenforced, convalidated, conrelid::regclass
1486+
from pg_constraint
1487+
where conname = 'p1_a_check' and contype = 'c'
1488+
order by conrelid::regclass::text collate "C";
1489+
conname | conenforced | convalidated | conrelid
1490+
------------+-------------+--------------+----------
1491+
p1_a_check | f | f | p1
1492+
p1_a_check | f | f | p1_c1
1493+
(2 rows)
1494+
14371495
drop table p1 cascade;
14381496
NOTICE: drop cascades to table p1_c1
14391497
create table p1(f1 int constraint p1_a_check check (f1 > 0) enforced);

0 commit comments

Comments
 (0)