Skip to content

Commit 2bc7e88

Browse files
committed
Fix ON CONFLICT ON CONSTRAINT during REINDEX CONCURRENTLY
When REINDEX CONCURRENTLY is processing the index that supports a constraint, there are periods during which multiple indexes match the constraint index's definition. Those must all be included in the set of inferred 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 all indexes that match those, rather than just the one constraint index. This is more onerous than before, where we would just test the named constraint 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 by bc32a12, for a different 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 parent 2fcc5a7 commit 2bc7e88

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
Relation relation;
807807
Oid indexOidFromConstraint = 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
int attno;
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+
Relation idxRel;
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_index idxForm = 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 (int natt = 0; natt < idxForm->indnkeyatts; natt++)
931+
{
932+
int attno;
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 to try and find a match
950+
* target relation to find matches.
886951
*/
887-
indexList = RelationGetIndexList(relation);
888-
889-
foreach(l, indexList)
952+
foreach_ptr(RelationData, idxRel, indexRelList)
890953
{
891-
Oid indexoid = lfirst_oid(l);
892-
Relation idxRel;
893954
Form_pg_index idxForm;
894955
Bitmapset *indexedAttrs;
895956
List *idxExprs;
896957
List *predExprs;
897958
AttrNumber natt;
898-
ListCell *el;
959+
bool match;
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-
goto next;
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 match on "ON constraint_name" variant, which may not be
996+
* Look for match for "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
else if (indexOidFromConstraint != InvalidOid)
9531012
{
954-
/* No point in further work for index in named constraint case */
955-
goto next;
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-
goto next;
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-
goto next;
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-
goto next;
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-
goto next;
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-
goto next;
1097+
match = false;
1098+
break;
10271099
}
1100+
if (!match)
1101+
continue;
10281102

10291103
/*
1030-
* Now that all inference elements were matched, ensure that the
1104+
* In case of inference from 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-
goto next;
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-
goto next;
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)