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

Commit2bc7e88

Browse files
committed
Fix ON CONFLICT ON CONSTRAINT during REINDEX CONCURRENTLY
When REINDEX CONCURRENTLY is processing the index that supports aconstraint, there are periods during which multiple indexes match theconstraint index's definition. Those must all be included in the set ofinferred index for INSERT ON CONFLICT, in order to avoid spurious"duplicate key" errors.To fix, we set things up to match all indexes against attributes,expressions and predicates of the constraint index, then return allindexes that match those, rather than just the one constraint index.This is more onerous than before, where we would just test the namedconstraint for validity, but it's not more onerous than processing"conventional" inference (where a list of attribute names etc is given).This is closely related to the misbehaviors fixed bybc32a12, for adifferent situation. We're not backpatching this one for now either,for the same reasons.Author: Mihail Nikalayeu <mihailnikalayeu@gmail.com>Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de>Discussion:https://postgr.es/m/CANtu0ojXmqjmEzp-=aJSxjsdE76iAsRgHBoK0QtYHimb_mEfsg@mail.gmail.com
1 parent2fcc5a7 commit2bc7e88

File tree

5 files changed

+490
-47
lines changed

5 files changed

+490
-47
lines changed

‎src/backend/optimizer/util/plancat.c‎

Lines changed: 140 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -806,9 +806,15 @@ infer_arbiter_indexes(PlannerInfo *root)
806806
Relationrelation;
807807
OidindexOidFromConstraint=InvalidOid;
808808
List*indexList;
809-
ListCell*l;
809+
List*indexRelList=NIL;
810810

811-
/* Normalized inference attributes and inference expressions: */
811+
/*
812+
* Required attributes and expressions used to match indexes to the clause
813+
* given by the user. In the ON CONFLICT ON CONSTRAINT case, we compute
814+
* these from that constraint's index to match all other indexes, to
815+
* account for the case where that index is being concurrently reindexed.
816+
*/
817+
List*inferIndexExprs= (List*)onconflict->arbiterWhere;
812818
Bitmapset*inferAttrs=NULL;
813819
List*inferElems=NIL;
814820

@@ -841,12 +847,14 @@ infer_arbiter_indexes(PlannerInfo *root)
841847
* well as a separate list of expression items. This simplifies matching
842848
* the cataloged definition of indexes.
843849
*/
844-
foreach(l,onconflict->arbiterElems)
850+
foreach_ptr(InferenceElem,elem,onconflict->arbiterElems)
845851
{
846-
InferenceElem*elem= (InferenceElem*)lfirst(l);
847852
Var*var;
848853
intattno;
849854

855+
/* we cannot also have a constraint name, per grammar */
856+
Assert(!OidIsValid(onconflict->constraint));
857+
850858
if (!IsA(elem->expr,Var))
851859
{
852860
/* If not a plain Var, just shove it in inferElems for now */
@@ -867,45 +875,96 @@ infer_arbiter_indexes(PlannerInfo *root)
867875
}
868876

869877
/*
870-
* Lookup named constraint's index. This is not immediately returned
871-
* because some additional sanity checks are required.
878+
* Next, open all the indexes. We need this list for two things: first,
879+
* if an ON CONSTRAINT clause was given, and that constraint's index is
880+
* undergoing REINDEX CONCURRENTLY, then we need to consider all matches
881+
* for that index. Second, if an attribute list was specified in the ON
882+
* CONFLICT clause, we use the list to find the indexes whose attributes
883+
* match that list.
884+
*/
885+
indexList=RelationGetIndexList(relation);
886+
foreach_oid(indexoid,indexList)
887+
{
888+
RelationidxRel;
889+
890+
/* obtain the same lock type that the executor will ultimately use */
891+
idxRel=index_open(indexoid,rte->rellockmode);
892+
indexRelList=lappend(indexRelList,idxRel);
893+
}
894+
895+
/*
896+
* If a constraint was named in the command, look up its index. We don't
897+
* return it immediately because we need some additional sanity checks,
898+
* and also because we need to include other indexes as arbiters to
899+
* account for REINDEX CONCURRENTLY processing it.
872900
*/
873901
if (onconflict->constraint!=InvalidOid)
874902
{
875-
indexOidFromConstraint=get_constraint_index(onconflict->constraint);
903+
/* we cannot also have an explicit list of elements, per grammar */
904+
Assert(onconflict->arbiterElems==NIL);
876905

906+
indexOidFromConstraint=get_constraint_index(onconflict->constraint);
877907
if (indexOidFromConstraint==InvalidOid)
878908
ereport(ERROR,
879909
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
880910
errmsg("constraint in ON CONFLICT clause has no associated index")));
911+
912+
/*
913+
* Find the named constraint index to extract its attributes and
914+
* predicates.
915+
*/
916+
foreach_ptr(RelationData,idxRel,indexRelList)
917+
{
918+
Form_pg_indexidxForm=idxRel->rd_index;
919+
920+
if (indexOidFromConstraint==idxForm->indexrelid)
921+
{
922+
/* Found it. */
923+
Assert(idxForm->indisready);
924+
925+
/*
926+
* Set up inferElems and inferPredExprs to match the
927+
* constraint index, so that we can match them in the loop
928+
* below.
929+
*/
930+
for (intnatt=0;natt<idxForm->indnkeyatts;natt++)
931+
{
932+
intattno;
933+
934+
attno=idxRel->rd_index->indkey.values[natt];
935+
if (attno!=InvalidAttrNumber)
936+
inferAttrs=
937+
bms_add_member(inferAttrs,
938+
attno-FirstLowInvalidHeapAttributeNumber);
939+
}
940+
941+
inferElems=RelationGetIndexExpressions(idxRel);
942+
inferIndexExprs=RelationGetIndexPredicate(idxRel);
943+
break;
944+
}
945+
}
881946
}
882947

883948
/*
884949
* Using that representation, iterate through the list of indexes on the
885-
* target relation totry andfinda match
950+
* target relation to findmatches.
886951
*/
887-
indexList=RelationGetIndexList(relation);
888-
889-
foreach(l,indexList)
952+
foreach_ptr(RelationData,idxRel,indexRelList)
890953
{
891-
Oidindexoid=lfirst_oid(l);
892-
RelationidxRel;
893954
Form_pg_indexidxForm;
894955
Bitmapset*indexedAttrs;
895956
List*idxExprs;
896957
List*predExprs;
897958
AttrNumbernatt;
898-
ListCell*el;
959+
boolmatch;
899960

900961
/*
901-
* Extract info from the relation descriptor for the index. Obtain
902-
* the same lock type that the executor will ultimately use.
962+
* Extract info from the relation descriptor for the index.
903963
*
904964
* Let executor complain about !indimmediate case directly, because
905965
* enforcement needs to occur there anyway when an inference clause is
906966
* omitted.
907967
*/
908-
idxRel=index_open(indexoid,rte->rellockmode);
909968
idxForm=idxRel->rd_index;
910969

911970
/*
@@ -924,7 +983,7 @@ infer_arbiter_indexes(PlannerInfo *root)
924983
* indexes at least one index that is marked valid.
925984
*/
926985
if (!idxForm->indisready)
927-
gotonext;
986+
continue;
928987

929988
/*
930989
* Note that we do not perform a check against indcheckxmin (like e.g.
@@ -934,7 +993,7 @@ infer_arbiter_indexes(PlannerInfo *root)
934993
*/
935994

936995
/*
937-
* Look for matchon "ON constraint_name" variant, which may not be
996+
* Look for matchfor "ON constraint_name" variant, which may not be a
938997
* unique constraint. This can only be a constraint name.
939998
*/
940999
if (indexOidFromConstraint==idxForm->indexrelid)
@@ -944,31 +1003,37 @@ infer_arbiter_indexes(PlannerInfo *root)
9441003
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
9451004
errmsg("ON CONFLICT DO UPDATE not supported with exclusion constraints")));
9461005

1006+
/* Consider this one a match already */
9471007
results=lappend_oid(results,idxForm->indexrelid);
9481008
foundValid |=idxForm->indisvalid;
949-
index_close(idxRel,NoLock);
950-
break;
1009+
continue;
9511010
}
9521011
elseif (indexOidFromConstraint!=InvalidOid)
9531012
{
954-
/* No point in further work for index in named constraint case */
955-
gotonext;
1013+
/*
1014+
* In the case of "ON constraint_name DO UPDATE" we need to skip
1015+
* non-unique candidates.
1016+
*/
1017+
if (!idxForm->indisunique&&onconflict->action==ONCONFLICT_UPDATE)
1018+
continue;
1019+
}
1020+
else
1021+
{
1022+
/*
1023+
* Only considering conventional inference at this point (not
1024+
* named constraints), so index under consideration can be
1025+
* immediately skipped if it's not unique.
1026+
*/
1027+
if (!idxForm->indisunique)
1028+
continue;
9561029
}
957-
958-
/*
959-
* Only considering conventional inference at this point (not named
960-
* constraints), so index under consideration can be immediately
961-
* skipped if it's not unique
962-
*/
963-
if (!idxForm->indisunique)
964-
gotonext;
9651030

9661031
/*
9671032
* So-called unique constraints with WITHOUT OVERLAPS are really
9681033
* exclusion constraints, so skip those too.
9691034
*/
9701035
if (idxForm->indisexclusion)
971-
gotonext;
1036+
continue;
9721037

9731038
/* Build BMS representation of plain (non expression) index attrs */
9741039
indexedAttrs=NULL;
@@ -983,17 +1048,20 @@ infer_arbiter_indexes(PlannerInfo *root)
9831048

9841049
/* Non-expression attributes (if any) must match */
9851050
if (!bms_equal(indexedAttrs,inferAttrs))
986-
gotonext;
1051+
continue;
9871052

9881053
/* Expression attributes (if any) must match */
9891054
idxExprs=RelationGetIndexExpressions(idxRel);
9901055
if (idxExprs&&varno!=1)
9911056
ChangeVarNodes((Node*)idxExprs,1,varno,0);
9921057

993-
foreach(el,onconflict->arbiterElems)
1058+
/*
1059+
* If arbiterElems are present, check them. (Note that if a
1060+
* constraint name was given in the command line, this list is NIL.)
1061+
*/
1062+
match= true;
1063+
foreach_ptr(InferenceElem,elem,onconflict->arbiterElems)
9941064
{
995-
InferenceElem*elem= (InferenceElem*)lfirst(el);
996-
9971065
/*
9981066
* Ensure that collation/opclass aspects of inference expression
9991067
* element match. Even though this loop is primarily concerned
@@ -1002,7 +1070,10 @@ infer_arbiter_indexes(PlannerInfo *root)
10021070
* attributes appearing as inference elements.
10031071
*/
10041072
if (!infer_collation_opclass_match(elem,idxRel,idxExprs))
1005-
gotonext;
1073+
{
1074+
match= false;
1075+
break;
1076+
}
10061077

10071078
/*
10081079
* Plain Vars don't factor into count of expression elements, and
@@ -1023,37 +1094,59 @@ infer_arbiter_indexes(PlannerInfo *root)
10231094
list_member(idxExprs,elem->expr))
10241095
continue;
10251096

1026-
gotonext;
1097+
match= false;
1098+
break;
10271099
}
1100+
if (!match)
1101+
continue;
10281102

10291103
/*
1030-
*Now that all inferenceelements were matched, ensure that the
1104+
*In case of inferencefrom an attribute list, ensure that the
10311105
* expression elements from inference clause are not missing any
10321106
* cataloged expressions. This does the right thing when unique
10331107
* indexes redundantly repeat the same attribute, or if attributes
10341108
* redundantly appear multiple times within an inference clause.
1109+
*
1110+
* In case a constraint was named, ensure the candidate has an equal
1111+
* set of expressions as the named constraint's index.
10351112
*/
10361113
if (list_difference(idxExprs,inferElems)!=NIL)
1037-
gotonext;
1114+
continue;
10381115

1039-
/*
1040-
* If it's a partial index, its predicate must be implied by the ON
1041-
* CONFLICT's WHERE clause.
1042-
*/
10431116
predExprs=RelationGetIndexPredicate(idxRel);
10441117
if (predExprs&&varno!=1)
10451118
ChangeVarNodes((Node*)predExprs,1,varno,0);
10461119

1047-
if (!predicate_implied_by(predExprs, (List*)onconflict->arbiterWhere, false))
1048-
gotonext;
1120+
/*
1121+
* Partial indexes affect each form of ON CONFLICT differently: if a
1122+
* constraint was named, then the predicates must be identical. In
1123+
* conventional inference, the index's predicate must be implied by
1124+
* the WHERE clause.
1125+
*/
1126+
if (OidIsValid(indexOidFromConstraint))
1127+
{
1128+
if (list_difference(predExprs,inferIndexExprs)!=NIL)
1129+
continue;
1130+
}
1131+
else
1132+
{
1133+
if (!predicate_implied_by(predExprs,inferIndexExprs, false))
1134+
continue;
1135+
}
10491136

1137+
/* All good -- consider this index a match */
10501138
results=lappend_oid(results,idxForm->indexrelid);
10511139
foundValid |=idxForm->indisvalid;
1052-
next:
1140+
}
1141+
1142+
/* Close all indexes */
1143+
foreach_ptr(RelationData,idxRel,indexRelList)
1144+
{
10531145
index_close(idxRel,NoLock);
10541146
}
10551147

10561148
list_free(indexList);
1149+
list_free(indexRelList);
10571150
table_close(relation,NoLock);
10581151

10591152
/* We require at least one indisvalid index */

‎src/test/modules/injection_points/Makefile‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ ISOLATION = basic \
1919
syscache-update-pruned\
2020
index-concurrently-upsert\
2121
reindex-concurrently-upsert\
22+
reindex-concurrently-upsert-on-constraint\
2223
index-concurrently-upsert-predicate
2324

2425
TAP_TESTS = 1

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp