Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit11ff192

Browse files
committed
Elide not-null constraint checks on child tables during PK creation
We were unnecessarily acquiring AccessExclusiveLock on all child tableswhen "ALTER TABLE ONLY sometab ADD PRIMARY KEY" was run on their parenttable, an oversight in commit14e87ff. This caused deadlocksduring pg_restore of partitioned tables.The reason to acquire the AEL was that we need to verify that childtables have the involved columns already marked as not-null; but if theparent table has an inheritable not-null constraint, then all childrenmust necessarily be in the correct state already, so we can skip thecheck, which avoids acquiring the lock. Reorder the code so that itworks that way. This doesn't change things in the case where theconstraint doesn't exist, but that case is of lesser importance becauseit doesn't occur during parallel pg_restore.While at it, reword some errmsg() and add errhint() to similar cases inrelated but not adjacent code.Diagnosed-by: Tom Lane <tgl@sss.pgh.pa.us>Reviewed-by: Tender Wang <tndrwang@gmail.com>Discussion:https://postgr.es/m/67469c1c-38bc-7d94-918a-67033f5dd731@gmx.netDiscussion:https://postgr.es/m/2045026.1743801143@sss.pgh.pa.usDiscussion:https://postgr.es/m/1280408.1744650810@sss.pgh.pa.us
1 parent1fd3566 commit11ff192

File tree

5 files changed

+136
-65
lines changed

5 files changed

+136
-65
lines changed

‎src/backend/catalog/pg_constraint.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,9 @@ AdjustNotNullInheritance(Oid relid, AttrNumber attnum,
756756
ereport(ERROR,
757757
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
758758
errmsg("cannot change NO INHERIT status of NOT NULL constraint \"%s\" on relation \"%s\"",
759-
NameStr(conform->conname),get_rel_name(relid)));
759+
NameStr(conform->conname),get_rel_name(relid)),
760+
errhint("You might need to make the existing constraint inheritable using %s.",
761+
"ALTER TABLE ... ALTER CONSTRAINT ... INHERIT"));
760762

761763
/*
762764
* Throw an error if the existing constraint is NOT VALID and caller
@@ -767,7 +769,8 @@ AdjustNotNullInheritance(Oid relid, AttrNumber attnum,
767769
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
768770
errmsg("incompatible NOT VALID constraint \"%s\" on relation \"%s\"",
769771
NameStr(conform->conname),get_rel_name(relid)),
770-
errhint("You will need to use ALTER TABLE ... VALIDATE CONSTRAINT to validate it."));
772+
errhint("You might need to validate it using %s.",
773+
"ALTER TABLE ... VALIDATE CONSTRAINT"));
771774

772775
if (!is_local)
773776
{

‎src/backend/commands/tablecmds.c

Lines changed: 88 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,7 @@ static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *c
540540
static void ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
541541
bool recurse, LOCKMODE lockmode,
542542
AlterTableUtilityContext *context);
543+
static void verifyNotNullPKCompatible(HeapTuple tuple, const char *colname);
543544
static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
544545
IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
545546
static ObjectAddress ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
@@ -9438,51 +9439,40 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
94389439
}
94399440

94409441
/*
9441-
* Prepare to add a primary key on table, by adding not-null constraints
9442+
* Prepare to add a primary key onatable, by adding not-null constraints
94429443
* on all columns.
9444+
*
9445+
* The not-null constraints for a primary key must cover the whole inheritance
9446+
* hierarchy (failing to ensure that leads to funny corner cases). For the
9447+
* normal case where we're asked to recurse, this routine ensures that the
9448+
* not-null constraints either exist already, or queues a requirement for them
9449+
* to be created by phase 2.
9450+
*
9451+
* For the case where we're asked not to recurse, we verify that a not-null
9452+
* constraint exists on each column of each (direct) child table, throwing an
9453+
* error if not. Not throwing an error would also work, because a not-null
9454+
* constraint would be created anyway, but it'd cause a silent scan of the
9455+
* child table to verify absence of nulls. We prefer to let the user know so
9456+
* that they can add the constraint manually without having to hold
9457+
* AccessExclusiveLock while at it.
9458+
*
9459+
* However, it's also important that we do not acquire locks on children if
9460+
* the not-null constraints already exist on the parent, to avoid risking
9461+
* deadlocks during parallel pg_restore of PKs on partitioned tables.
94439462
*/
94449463
static void
94459464
ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
94469465
bool recurse, LOCKMODE lockmode,
94479466
AlterTableUtilityContext *context)
94489467
{
94499468
Constraint *pkconstr;
9469+
List *children;
9470+
boolgot_children = false;
94509471

94519472
pkconstr = castNode(Constraint, cmd->def);
94529473
if (pkconstr->contype != CONSTR_PRIMARY)
94539474
return;
94549475

9455-
/*
9456-
* If not recursing, we must ensure that all children have a NOT NULL
9457-
* constraint on the columns, and error out if not.
9458-
*/
9459-
if (!recurse)
9460-
{
9461-
List *children;
9462-
9463-
children = find_inheritance_children(RelationGetRelid(rel),
9464-
lockmode);
9465-
foreach_oid(childrelid, children)
9466-
{
9467-
foreach_node(String, attname, pkconstr->keys)
9468-
{
9469-
HeapTupletup;
9470-
Form_pg_attribute attrForm;
9471-
9472-
tup = SearchSysCacheAttName(childrelid, strVal(attname));
9473-
if (!tup)
9474-
elog(ERROR, "cache lookup failed for attribute %s of relation %u",
9475-
strVal(attname), childrelid);
9476-
attrForm = (Form_pg_attribute) GETSTRUCT(tup);
9477-
if (!attrForm->attnotnull)
9478-
ereport(ERROR,
9479-
errmsg("column \"%s\" of table \"%s\" is not marked NOT NULL",
9480-
strVal(attname), get_rel_name(childrelid)));
9481-
ReleaseSysCache(tup);
9482-
}
9483-
}
9484-
}
9485-
94869476
/* Verify that columns are not-null, or request that they be made so */
94879477
foreach_node(String, column, pkconstr->keys)
94889478
{
@@ -9498,49 +9488,90 @@ ATPrepAddPrimaryKey(List **wqueue, Relation rel, AlterTableCmd *cmd,
94989488
tuple = findNotNullConstraint(RelationGetRelid(rel), strVal(column));
94999489
if (tuple != NULL)
95009490
{
9501-
Form_pg_constraint conForm = (Form_pg_constraint) GETSTRUCT(tuple);
9502-
9503-
/* a NO INHERIT constraint is no good */
9504-
if (conForm->connoinherit)
9505-
ereport(ERROR,
9506-
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
9507-
errmsg("cannot create primary key on column \"%s\"",
9508-
strVal(column)),
9509-
/*- translator: third %s is a constraint characteristic such as NOT VALID */
9510-
errdetail("The constraint \"%s\" on column \"%s\", marked %s, is incompatible with a primary key.",
9511-
NameStr(conForm->conname), strVal(column), "NO INHERIT"),
9512-
errhint("You will need to make it inheritable using %s.",
9513-
"ALTER TABLE ... ALTER CONSTRAINT ... INHERIT"));
9514-
9515-
/* an unvalidated constraint is no good */
9516-
if (!conForm->convalidated)
9517-
ereport(ERROR,
9518-
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
9519-
errmsg("cannot create primary key on column \"%s\"",
9520-
strVal(column)),
9521-
/*- translator: third %s is a constraint characteristic such as NOT VALID */
9522-
errdetail("The constraint \"%s\" on column \"%s\", marked %s, is incompatible with a primary key.",
9523-
NameStr(conForm->conname), strVal(column), "NOT VALID"),
9524-
errhint("You will need to validate it using %s.",
9525-
"ALTER TABLE ... VALIDATE CONSTRAINT"));
9491+
verifyNotNullPKCompatible(tuple, strVal(column));
95269492

95279493
/* All good with this one; don't request another */
95289494
heap_freetuple(tuple);
95299495
continue;
95309496
}
9497+
else if (!recurse)
9498+
{
9499+
/*
9500+
* No constraint on this column. Asked not to recurse, we won't
9501+
* create one here, but verify that all children have one.
9502+
*/
9503+
if (!got_children)
9504+
{
9505+
children = find_inheritance_children(RelationGetRelid(rel),
9506+
lockmode);
9507+
/* only search for children on the first time through */
9508+
got_children = true;
9509+
}
9510+
9511+
foreach_oid(childrelid, children)
9512+
{
9513+
HeapTupletup;
9514+
9515+
tup = findNotNullConstraint(childrelid, strVal(column));
9516+
if (!tup)
9517+
ereport(ERROR,
9518+
errmsg("column \"%s\" of table \"%s\" is not marked NOT NULL",
9519+
strVal(column), get_rel_name(childrelid)));
9520+
/* verify it's good enough */
9521+
verifyNotNullPKCompatible(tup, strVal(column));
9522+
}
9523+
}
95319524

95329525
/* This column is not already not-null, so add it to the queue */
95339526
nnconstr = makeNotNullConstraint(column);
95349527

95359528
newcmd = makeNode(AlterTableCmd);
95369529
newcmd->subtype = AT_AddConstraint;
9530+
/* note we force recurse=true here; see above */
95379531
newcmd->recurse = true;
95389532
newcmd->def = (Node *) nnconstr;
95399533

95409534
ATPrepCmd(wqueue, rel, newcmd, true, false, lockmode, context);
95419535
}
95429536
}
95439537

9538+
/*
9539+
* Verify whether the given not-null constraint is compatible with a
9540+
* primary key. If not, an error is thrown.
9541+
*/
9542+
static void
9543+
verifyNotNullPKCompatible(HeapTuple tuple, const char *colname)
9544+
{
9545+
Form_pg_constraint conForm = (Form_pg_constraint) GETSTRUCT(tuple);
9546+
9547+
if (conForm->contype != CONSTRAINT_NOTNULL)
9548+
elog(ERROR, "constraint %u is not a not-null constraint", conForm->oid);
9549+
9550+
/* a NO INHERIT constraint is no good */
9551+
if (conForm->connoinherit)
9552+
ereport(ERROR,
9553+
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
9554+
errmsg("cannot create primary key on column \"%s\"", colname),
9555+
/*- translator: fourth %s is a constraint characteristic such as NOT VALID */
9556+
errdetail("The constraint \"%s\" on column \"%s\" of table \"%s\", marked %s, is incompatible with a primary key.",
9557+
NameStr(conForm->conname), colname,
9558+
get_rel_name(conForm->conrelid), "NO INHERIT"),
9559+
errhint("You might need to make the existing constraint inheritable using %s.",
9560+
"ALTER TABLE ... ALTER CONSTRAINT ... INHERIT"));
9561+
9562+
/* an unvalidated constraint is no good */
9563+
if (!conForm->convalidated)
9564+
ereport(ERROR,
9565+
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
9566+
errmsg("cannot create primary key on column \"%s\"", colname),
9567+
/*- translator: fourth %s is a constraint characteristic such as NOT VALID */
9568+
errdetail("The constraint \"%s\" on column \"%s\" of table \"%s\", marked %s, is incompatible with a primary key.",
9569+
NameStr(conForm->conname), colname,
9570+
get_rel_name(conForm->conrelid), "NOT VALID"),
9571+
errhint("You might need to validate it using %s.",
9572+
"ALTER TABLE ... VALIDATE CONSTRAINT"));
9573+
}
9574+
95449575
/*
95459576
* ALTER TABLE ADD INDEX
95469577
*

‎src/test/regress/expected/constraints.out

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,6 +1042,7 @@ ALTER TABLE ATACC2 INHERIT ATACC1;
10421042
-- can't override
10431043
ALTER TABLE ATACC1 ADD CONSTRAINT ditto NOT NULL a;
10441044
ERROR: cannot change NO INHERIT status of NOT NULL constraint "a_is_not_null" on relation "atacc2"
1045+
HINT: You might need to make the existing constraint inheritable using ALTER TABLE ... ALTER CONSTRAINT ... INHERIT.
10451046
-- dropping the NO INHERIT constraint allows this to work
10461047
ALTER TABLE ATACC2 DROP CONSTRAINT a_is_not_null;
10471048
ALTER TABLE ATACC1 ADD CONSTRAINT ditto NOT NULL a;
@@ -1233,8 +1234,8 @@ Indexes:
12331234
create table cnn_pk (a int not null no inherit);
12341235
alter table cnn_pk add primary key (a);
12351236
ERROR: cannot create primary key on column "a"
1236-
DETAIL: The constraint "cnn_pk_a_not_null" on column "a", marked NO INHERIT, is incompatible with a primary key.
1237-
HINT: Youwill need to makeit inheritable using ALTER TABLE ... ALTER CONSTRAINT ... INHERIT.
1237+
DETAIL: The constraint "cnn_pk_a_not_null" on column "a" of table "cnn_pk", marked NO INHERIT, is incompatible with a primary key.
1238+
HINT: Youmight need to makethe existing constraint inheritable using ALTER TABLE ... ALTER CONSTRAINT ... INHERIT.
12381239
drop table cnn_pk;
12391240
-- Ensure partitions are scanned for null values when adding a PK
12401241
create table cnn2_parted(a int) partition by list (a);
@@ -1389,14 +1390,15 @@ Not-null constraints:
13891390
-- If we have an invalid constraint, we can't have another
13901391
ALTER TABLE notnull_tbl1 ADD CONSTRAINT nn1 NOT NULL a NOT VALID NO INHERIT;
13911392
ERROR: cannot change NO INHERIT status of NOT NULL constraint "nn" on relation "notnull_tbl1"
1393+
HINT: You might need to make the existing constraint inheritable using ALTER TABLE ... ALTER CONSTRAINT ... INHERIT.
13921394
ALTER TABLE notnull_tbl1 ADD CONSTRAINT nn NOT NULL a;
13931395
ERROR: incompatible NOT VALID constraint "nn" on relation "notnull_tbl1"
1394-
HINT: Youwill need touseALTER TABLE ... VALIDATE CONSTRAINT to validate it.
1396+
HINT: Youmight need tovalidate it usingALTER TABLE ... VALIDATE CONSTRAINT.
13951397
-- cannot add primary key on a column with an invalid not-null
13961398
ALTER TABLE notnull_tbl1 ADD PRIMARY KEY (a);
13971399
ERROR: cannot create primary key on column "a"
1398-
DETAIL: The constraint "nn" on column "a", marked NOT VALID, is incompatible with a primary key.
1399-
HINT: Youwill need to validate it using ALTER TABLE ... VALIDATE CONSTRAINT.
1400+
DETAIL: The constraint "nn" on column "a" of table "notnull_tbl1", marked NOT VALID, is incompatible with a primary key.
1401+
HINT: Youmight need to validate it using ALTER TABLE ... VALIDATE CONSTRAINT.
14001402
-- ALTER column SET NOT NULL validates an invalid constraint (but this fails
14011403
-- because of rows with null values)
14021404
ALTER TABLE notnull_tbl1 ALTER a SET NOT NULL;
@@ -1502,7 +1504,7 @@ NOTICE: merging multiple inherited definitions of column "i"
15021504
ALTER TABLE notnull_inhparent ADD CONSTRAINT nn NOT NULL i NOT VALID;
15031505
ALTER TABLE notnull_inhchild ADD CONSTRAINT nn1 NOT NULL i; -- error
15041506
ERROR: incompatible NOT VALID constraint "nn" on relation "notnull_inhchild"
1505-
HINT: Youwill need touseALTER TABLE ... VALIDATE CONSTRAINT to validate it.
1507+
HINT: Youmight need tovalidate it usingALTER TABLE ... VALIDATE CONSTRAINT.
15061508
EXECUTE get_nnconstraint_info('{notnull_inhparent, notnull_inhchild, notnull_inhgrand}');
15071509
tabname | conname | convalidated | conislocal | coninhcount
15081510
-------------------+---------+--------------+------------+-------------
@@ -1567,6 +1569,24 @@ ERROR: constraint "nn1" conflicts with NOT VALID constraint on child table "pp_
15671569
ALTER TABLE pp_nn_1 VALIDATE CONSTRAINT nn1;
15681570
ALTER TABLE pp_nn ATTACH PARTITION pp_nn_1 FOR VALUES IN (NULL,5); --ok
15691571
DROP TABLE pp_nn;
1572+
-- Try a partition with an invalid constraint and create a PK on the parent.
1573+
CREATE TABLE pp_nn (a int) PARTITION BY HASH (a);
1574+
CREATE TABLE pp_nn_1 PARTITION OF pp_nn FOR VALUES WITH (MODULUS 2, REMAINDER 0);
1575+
ALTER TABLE pp_nn_1 ADD CONSTRAINT nn NOT NULL a NOT VALID;
1576+
ALTER TABLE ONLY pp_nn ADD PRIMARY KEY (a);
1577+
ERROR: cannot create primary key on column "a"
1578+
DETAIL: The constraint "nn" on column "a" of table "pp_nn_1", marked NOT VALID, is incompatible with a primary key.
1579+
HINT: You might need to validate it using ALTER TABLE ... VALIDATE CONSTRAINT.
1580+
DROP TABLE pp_nn;
1581+
-- same as above, but the constraint is NO INHERIT
1582+
CREATE TABLE pp_nn (a int) PARTITION BY HASH (a);
1583+
CREATE TABLE pp_nn_1 PARTITION OF pp_nn FOR VALUES WITH (MODULUS 2, REMAINDER 0);
1584+
ALTER TABLE pp_nn_1 ADD CONSTRAINT nn NOT NULL a NO INHERIT;
1585+
ALTER TABLE ONLY pp_nn ADD PRIMARY KEY (a);
1586+
ERROR: cannot create primary key on column "a"
1587+
DETAIL: The constraint "nn" on column "a" of table "pp_nn_1", marked NO INHERIT, is incompatible with a primary key.
1588+
HINT: You might need to make the existing constraint inheritable using ALTER TABLE ... ALTER CONSTRAINT ... INHERIT.
1589+
DROP TABLE pp_nn;
15701590
-- Create table with NOT NULL INVALID constraint, for pg_upgrade.
15711591
CREATE TABLE notnull_tbl1_upg (a int, b int);
15721592
INSERT INTO notnull_tbl1_upg VALUES (NULL, 1), (NULL, 2), (300, 3);

‎src/test/regress/expected/inherit.out

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2286,6 +2286,7 @@ DETAIL: The column has an inherited not-null constraint.
22862286
-- change NO INHERIT status of inherited constraint: no dice, it's inherited
22872287
alter table cc2 add not null a2 no inherit;
22882288
ERROR: cannot change NO INHERIT status of NOT NULL constraint "nn" on relation "cc2"
2289+
HINT: You might need to make the existing constraint inheritable using ALTER TABLE ... ALTER CONSTRAINT ... INHERIT.
22892290
-- remove constraint from cc2: no dice, it's inherited
22902291
alter table cc2 alter column a2 drop not null;
22912292
ERROR: cannot drop inherited constraint "nn" of relation "cc2"
@@ -2509,6 +2510,7 @@ CREATE TABLE inh_nn_parent (a int, NOT NULL a NO INHERIT);
25092510
CREATE TABLE inh_nn_child() INHERITS (inh_nn_parent);
25102511
ALTER TABLE inh_nn_parent ADD CONSTRAINT nna NOT NULL a;
25112512
ERROR: cannot change NO INHERIT status of NOT NULL constraint "inh_nn_parent_a_not_null" on relation "inh_nn_parent"
2513+
HINT: You might need to make the existing constraint inheritable using ALTER TABLE ... ALTER CONSTRAINT ... INHERIT.
25122514
ALTER TABLE inh_nn_parent ALTER a SET NOT NULL;
25132515
ERROR: cannot change NO INHERIT status of NOT NULL constraint "inh_nn_parent_a_not_null" on relation "inh_nn_parent"
25142516
DROP TABLE inh_nn_parent cascade;
@@ -2520,6 +2522,7 @@ CREATE TABLE inh_nn_lvl2 () INHERITS (inh_nn_lvl1);
25202522
CREATE TABLE inh_nn_lvl3 (CONSTRAINT foo NOT NULL a NO INHERIT) INHERITS (inh_nn_lvl2);
25212523
ALTER TABLE inh_nn_lvl1 ADD PRIMARY KEY (a);
25222524
ERROR: cannot change NO INHERIT status of NOT NULL constraint "foo" on relation "inh_nn_lvl3"
2525+
HINT: You might need to make the existing constraint inheritable using ALTER TABLE ... ALTER CONSTRAINT ... INHERIT.
25232526
DROP TABLE inh_nn_lvl1, inh_nn_lvl2, inh_nn_lvl3;
25242527
-- Disallow specifying conflicting NO INHERIT flags for the same constraint
25252528
CREATE TABLE inh_nn1 (a int primary key, b int, not null a no inherit);

‎src/test/regress/sql/constraints.sql

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,20 @@ ALTER TABLE pp_nn_1 VALIDATE CONSTRAINT nn1;
940940
ALTERTABLE pp_nn ATTACH PARTITION pp_nn_1 FORVALUESIN (NULL,5);--ok
941941
DROPTABLE pp_nn;
942942

943+
-- Try a partition with an invalid constraint and create a PK on the parent.
944+
CREATETABLEpp_nn (aint) PARTITION BY HASH (a);
945+
CREATETABLEpp_nn_1 PARTITION OF pp_nn FORVALUES WITH (MODULUS2, REMAINDER0);
946+
ALTERTABLE pp_nn_1 ADDCONSTRAINT nnNOT NULL a NOT VALID;
947+
ALTERTABLE ONLY pp_nn ADDPRIMARY KEY (a);
948+
DROPTABLE pp_nn;
949+
950+
-- same as above, but the constraint is NO INHERIT
951+
CREATETABLEpp_nn (aint) PARTITION BY HASH (a);
952+
CREATETABLEpp_nn_1 PARTITION OF pp_nn FORVALUES WITH (MODULUS2, REMAINDER0);
953+
ALTERTABLE pp_nn_1 ADDCONSTRAINT nnNOT NULL a NO INHERIT;
954+
ALTERTABLE ONLY pp_nn ADDPRIMARY KEY (a);
955+
DROPTABLE pp_nn;
956+
943957
-- Create table with NOT NULL INVALID constraint, for pg_upgrade.
944958
CREATETABLEnotnull_tbl1_upg (aint, bint);
945959
INSERT INTO notnull_tbl1_upgVALUES (NULL,1), (NULL,2), (300,3);

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp