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

Commit d1a9f12

Browse files
committed
Fix two bugs in merging of inherited CHECK constraints.
Historically, we've allowed users to add a CHECK constraint to a child table and then add an identical CHECK constraint to the parent. This results in "merging" the two constraints so that the pre-existing child constraint ends up with both conislocal = true and coninhcount > 0. However, if you tried to do it in the other order, you got a duplicate constraint error. This is problematic for pg_dump, which needs to issue separated ADD CONSTRAINT commands in some cases, but has no good way to ensure that the constraints will be added in the required order. And it's more than a bit arbitrary, too. The goal of complaining about duplicated ADD CONSTRAINT commands can be served if we reject the case of adding a constraint when the existing one already has conislocal = true; but if it has conislocal = false, let's just make the ADD CONSTRAINT set conislocal = true. In this way, either order of adding the constraints has the same end result. Another problem was that the code allowed creation of a parent constraint marked convalidated that is merged with a child constraint that is !convalidated. In this case, an inheritance scan of the parent table could emit some rows violating the constraint condition, which would be an unexpected result given the marking of the parent constraint as validated. Hence, forbid merging of constraints in this case. (Note: valid child and not-valid parent seems fine, so continue to allow that.) Per report from Benedikt Grundmann. Back-patch to 9.2 where we introduced possibly-not-valid check constraints. The second bug obviously doesn't apply before that, and I think the first doesn't either, because pg_dump only gets into this situation when dealing with not-valid constraints. Report: <CADbMkNPT-Jz5PRSQ4RbUASYAjocV_KHUWapR%2Bg8fNvhUAyRpxA%40mail.gmail.com> Discussion: <22108.1475874586@sss.pgh.pa.us>
1 parent d159552 commit d1a9f12

File tree

5 files changed

+135
-9
lines changed

5 files changed

+135
-9
lines changed

src/backend/catalog/heap.c

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ static void StoreConstraints(Relation rel, List *cooked_constraints,
104104
bool is_internal);
105105
static bool MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
106106
bool allow_merge, bool is_local,
107+
bool is_initially_valid,
107108
bool is_no_inherit);
108109
static void SetRelationNumChecks(Relation rel, int numchecks);
109110
static Node *cookConstraint(ParseState *pstate,
@@ -2302,6 +2303,7 @@ AddRelationNewConstraints(Relation rel,
23022303
*/
23032304
if (MergeWithExistingConstraint(rel, ccname, expr,
23042305
allow_merge, is_local,
2306+
cdef->initially_valid,
23052307
cdef->is_no_inherit))
23062308
continue;
23072309
}
@@ -2392,6 +2394,7 @@ AddRelationNewConstraints(Relation rel,
23922394
static bool
23932395
MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
23942396
bool allow_merge, bool is_local,
2397+
bool is_initially_valid,
23952398
bool is_no_inherit)
23962399
{
23972400
bool found;
@@ -2439,22 +2442,47 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
24392442
if (equal(expr, stringToNode(TextDatumGetCString(val))))
24402443
found = true;
24412444
}
2445+
2446+
/*
2447+
* If the existing constraint is purely inherited (no local
2448+
* definition) then interpret addition of a local constraint as a
2449+
* legal merge. This allows ALTER ADD CONSTRAINT on parent and
2450+
* child tables to be given in either order with same end state.
2451+
*/
2452+
if (is_local && !con->conislocal)
2453+
allow_merge = true;
2454+
24422455
if (!found || !allow_merge)
24432456
ereport(ERROR,
24442457
(errcode(ERRCODE_DUPLICATE_OBJECT),
24452458
errmsg("constraint \"%s\" for relation \"%s\" already exists",
24462459
ccname, RelationGetRelationName(rel))));
24472460

2448-
tup = heap_copytuple(tup);
2449-
con = (Form_pg_constraint) GETSTRUCT(tup);
2450-
2451-
/* If the constraint is "no inherit" then cannot merge */
2461+
/* If the child constraint is "no inherit" then cannot merge */
24522462
if (con->connoinherit)
24532463
ereport(ERROR,
24542464
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
24552465
errmsg("constraint \"%s\" conflicts with non-inherited constraint on relation \"%s\"",
24562466
ccname, RelationGetRelationName(rel))));
24572467

2468+
/*
2469+
* If the child constraint is "not valid" then cannot merge with a
2470+
* valid parent constraint
2471+
*/
2472+
if (is_initially_valid && !con->convalidated)
2473+
ereport(ERROR,
2474+
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
2475+
errmsg("constraint \"%s\" conflicts with NOT VALID constraint on relation \"%s\"",
2476+
ccname, RelationGetRelationName(rel))));
2477+
2478+
/* OK to update the tuple */
2479+
ereport(NOTICE,
2480+
(errmsg("merging constraint \"%s\" with inherited definition",
2481+
ccname)));
2482+
2483+
tup = heap_copytuple(tup);
2484+
con = (Form_pg_constraint) GETSTRUCT(tup);
2485+
24582486
if (is_local)
24592487
con->conislocal = true;
24602488
else
@@ -2464,10 +2492,6 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr,
24642492
Assert(is_local);
24652493
con->connoinherit = true;
24662494
}
2467-
/* OK to update the tuple */
2468-
ereport(NOTICE,
2469-
(errmsg("merging constraint \"%s\" with inherited definition",
2470-
ccname)));
24712495
simple_heap_update(conDesc, &tup->t_self, tup);
24722496
CatalogUpdateIndexes(conDesc, tup);
24732497
break;

src/backend/commands/tablecmds.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10342,14 +10342,25 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
1034210342
RelationGetRelationName(child_rel),
1034310343
NameStr(parent_con->conname))));
1034410344

10345-
/* If the constraint is "no inherit" then cannot merge */
10345+
/* If the child constraint is "no inherit" then cannot merge */
1034610346
if (child_con->connoinherit)
1034710347
ereport(ERROR,
1034810348
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
1034910349
errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
1035010350
NameStr(child_con->conname),
1035110351
RelationGetRelationName(child_rel))));
1035210352

10353+
/*
10354+
* If the child constraint is "not valid" then cannot merge with a
10355+
* valid parent constraint
10356+
*/
10357+
if (parent_con->convalidated && !child_con->convalidated)
10358+
ereport(ERROR,
10359+
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
10360+
errmsg("constraint \"%s\" conflicts with NOT VALID constraint on child table \"%s\"",
10361+
NameStr(child_con->conname),
10362+
RelationGetRelationName(child_rel))));
10363+
1035310364
/*
1035410365
* OK, bump the child constraint's inheritance count. (If we fail
1035510366
* later on, this change will just roll back.)

src/test/regress/expected/inherit.out

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,56 @@ Inherits: test_foreign_constraints
10901090
DROP TABLE test_foreign_constraints_inh;
10911091
DROP TABLE test_foreign_constraints;
10921092
DROP TABLE test_primary_constraints;
1093+
-- Test that parent and child CHECK constraints can be created in either order
1094+
create table p1(f1 int);
1095+
create table p1_c1() inherits(p1);
1096+
alter table p1 add constraint inh_check_constraint1 check (f1 > 0);
1097+
alter table p1_c1 add constraint inh_check_constraint1 check (f1 > 0);
1098+
NOTICE: merging constraint "inh_check_constraint1" with inherited definition
1099+
alter table p1_c1 add constraint inh_check_constraint2 check (f1 < 10);
1100+
alter table p1 add constraint inh_check_constraint2 check (f1 < 10);
1101+
NOTICE: merging constraint "inh_check_constraint2" with inherited definition
1102+
select conrelid::regclass::text as relname, conname, conislocal, coninhcount
1103+
from pg_constraint where conname like 'inh\_check\_constraint%'
1104+
order by 1, 2;
1105+
relname | conname | conislocal | coninhcount
1106+
---------+-----------------------+------------+-------------
1107+
p1 | inh_check_constraint1 | t | 0
1108+
p1 | inh_check_constraint2 | t | 0
1109+
p1_c1 | inh_check_constraint1 | t | 1
1110+
p1_c1 | inh_check_constraint2 | t | 1
1111+
(4 rows)
1112+
1113+
drop table p1 cascade;
1114+
NOTICE: drop cascades to table p1_c1
1115+
-- Test that a valid child can have not-valid parent, but not vice versa
1116+
create table invalid_check_con(f1 int);
1117+
create table invalid_check_con_child() inherits(invalid_check_con);
1118+
alter table invalid_check_con_child add constraint inh_check_constraint check(f1 > 0) not valid;
1119+
alter table invalid_check_con add constraint inh_check_constraint check(f1 > 0); -- fail
1120+
ERROR: constraint "inh_check_constraint" conflicts with NOT VALID constraint on relation "invalid_check_con_child"
1121+
alter table invalid_check_con_child drop constraint inh_check_constraint;
1122+
insert into invalid_check_con values(0);
1123+
alter table invalid_check_con_child add constraint inh_check_constraint check(f1 > 0);
1124+
alter table invalid_check_con add constraint inh_check_constraint check(f1 > 0) not valid;
1125+
NOTICE: merging constraint "inh_check_constraint" with inherited definition
1126+
insert into invalid_check_con values(0); -- fail
1127+
ERROR: new row for relation "invalid_check_con" violates check constraint "inh_check_constraint"
1128+
DETAIL: Failing row contains (0).
1129+
insert into invalid_check_con_child values(0); -- fail
1130+
ERROR: new row for relation "invalid_check_con_child" violates check constraint "inh_check_constraint"
1131+
DETAIL: Failing row contains (0).
1132+
select conrelid::regclass::text as relname, conname,
1133+
convalidated, conislocal, coninhcount, connoinherit
1134+
from pg_constraint where conname like 'inh\_check\_constraint%'
1135+
order by 1, 2;
1136+
relname | conname | convalidated | conislocal | coninhcount | connoinherit
1137+
-------------------------+----------------------+--------------+------------+-------------+--------------
1138+
invalid_check_con | inh_check_constraint | f | t | 0 | f
1139+
invalid_check_con_child | inh_check_constraint | t | t | 1 | f
1140+
(2 rows)
1141+
1142+
-- We don't drop the invalid_check_con* tables, to test dump/reload with
10931143
--
10941144
-- Test parameterized append plans for inheritance trees
10951145
--

src/test/regress/expected/sanity_check.out

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ int2_tbl|f
6262
int4_tbl|f
6363
int8_tbl|f
6464
interval_tbl|f
65+
invalid_check_con|f
66+
invalid_check_con_child|f
6567
iportaltest|f
6668
kd_point_tbl|t
6769
line_tbl|f

src/test/regress/sql/inherit.sql

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,45 @@ DROP TABLE test_foreign_constraints_inh;
334334
DROP TABLE test_foreign_constraints;
335335
DROP TABLE test_primary_constraints;
336336

337+
-- Test that parent and child CHECK constraints can be created in either order
338+
create table p1(f1 int);
339+
create table p1_c1() inherits(p1);
340+
341+
alter table p1 add constraint inh_check_constraint1 check (f1 > 0);
342+
alter table p1_c1 add constraint inh_check_constraint1 check (f1 > 0);
343+
344+
alter table p1_c1 add constraint inh_check_constraint2 check (f1 < 10);
345+
alter table p1 add constraint inh_check_constraint2 check (f1 < 10);
346+
347+
select conrelid::regclass::text as relname, conname, conislocal, coninhcount
348+
from pg_constraint where conname like 'inh\_check\_constraint%'
349+
order by 1, 2;
350+
351+
drop table p1 cascade;
352+
353+
-- Test that a valid child can have not-valid parent, but not vice versa
354+
create table invalid_check_con(f1 int);
355+
create table invalid_check_con_child() inherits(invalid_check_con);
356+
357+
alter table invalid_check_con_child add constraint inh_check_constraint check(f1 > 0) not valid;
358+
alter table invalid_check_con add constraint inh_check_constraint check(f1 > 0); -- fail
359+
alter table invalid_check_con_child drop constraint inh_check_constraint;
360+
361+
insert into invalid_check_con values(0);
362+
363+
alter table invalid_check_con_child add constraint inh_check_constraint check(f1 > 0);
364+
alter table invalid_check_con add constraint inh_check_constraint check(f1 > 0) not valid;
365+
366+
insert into invalid_check_con values(0); -- fail
367+
insert into invalid_check_con_child values(0); -- fail
368+
369+
select conrelid::regclass::text as relname, conname,
370+
convalidated, conislocal, coninhcount, connoinherit
371+
from pg_constraint where conname like 'inh\_check\_constraint%'
372+
order by 1, 2;
373+
374+
-- We don't drop the invalid_check_con* tables, to test dump/reload with
375+
337376
--
338377
-- Test parameterized append plans for inheritance trees
339378
--

0 commit comments

Comments
 (0)