diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 85c25d7d0b6..84b9c188fb5 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -942,6 +942,8 @@ infer_arbiter_indexes(PlannerInfo *root) Form_pg_index idxForm; Bitmapset *indexedAttrs; List *idxExprs; + List *idxNonVarExprs; + ListCell *idxExprItem; List *predExprs; AttrNumber natt; bool match; @@ -1046,22 +1048,7 @@ infer_arbiter_indexes(PlannerInfo *root) if (idxForm->indisexclusion) continue; - /* Build BMS representation of plain (non expression) index attrs */ - indexedAttrs = NULL; - for (natt = 0; natt < idxForm->indnkeyatts; natt++) - { - int attno = idxRel->rd_index->indkey.values[natt]; - - if (attno != 0) - indexedAttrs = bms_add_member(indexedAttrs, - attno - FirstLowInvalidHeapAttributeNumber); - } - - /* Non-expression attributes (if any) must match */ - if (!bms_equal(indexedAttrs, inferAttrs)) - continue; - - /* Expression attributes (if any) must match */ + /* Simplify the index expressions the same way arbiterElems were */ idxExprs = RelationGetIndexExpressions(idxRel); if (idxExprs) { @@ -1071,6 +1058,44 @@ infer_arbiter_indexes(PlannerInfo *root) idxExprs = (List *) eval_const_expressions(root, (Node *) idxExprs); } + /* + * Build BMS representation of plain index attrs. An index expression + * that was simplified to a plain Var is treated as a plain attr too, + * to match how arbiterElems are classified above. + */ + indexedAttrs = NULL; + idxNonVarExprs = NIL; + idxExprItem = list_head(idxExprs); + for (natt = 0; natt < idxForm->indnkeyatts; natt++) + { + int attno = idxRel->rd_index->indkey.values[natt]; + + if (attno == 0) + { + Node *idxExpr; + + if (idxExprItem == NULL) + elog(ERROR, "wrong number of index expressions"); + idxExpr = (Node *) lfirst(idxExprItem); + idxExprItem = lnext(idxExprs, idxExprItem); + + if (!IsA(idxExpr, Var) || ((Var *) idxExpr)->varattno == 0) + { + idxNonVarExprs = lappend(idxNonVarExprs, idxExpr); + continue; + } + + attno = ((Var *) idxExpr)->varattno; + } + + indexedAttrs = bms_add_member(indexedAttrs, + attno - FirstLowInvalidHeapAttributeNumber); + } + + /* Plain attributes (if any) must match */ + if (!bms_equal(indexedAttrs, inferAttrs)) + continue; + /* Check the arbiterElems against this index. */ match = true; foreach_ptr(InferenceElem, elem, onconflict->arbiterElems) @@ -1120,7 +1145,7 @@ infer_arbiter_indexes(PlannerInfo *root) * indexes redundantly repeat the same attribute, or if attributes * redundantly appear multiple times within an inference clause. */ - if (list_difference(idxExprs, inferElems) != NIL) + if (list_difference(idxNonVarExprs, inferElems) != NIL) continue; predExprs = RelationGetIndexPredicate(idxRel); @@ -1246,12 +1271,13 @@ infer_collation_opclass_match(InferenceElem *elem, Relation idxRel, } /* If one matching index att found, good enough -- return true */ - if (IsA(elem->expr, Var)) + if (attno != 0) { - if (((Var *) elem->expr)->varattno == attno) + if (IsA(elem->expr, Var) && + ((Var *) elem->expr)->varattno == attno) return true; } - else if (attno == 0) + else { Node *nattExpr = list_nth(idxExprs, (natt - 1) - nplain); diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out index b4c9bdb0e55..0dd8bc734a0 100644 --- a/src/test/regress/expected/insert_conflict.out +++ b/src/test/regress/expected/insert_conflict.out @@ -181,6 +181,33 @@ explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on con drop index cross_match; -- +-- Index expressions that are simplified to a plain column can be inferred +-- +create table insertconflict_nn (a int not null, b text); +create unique index nn_expr_key on insertconflict_nn(coalesce(a, 0), lower(b)); +explain (costs off) insert into insertconflict_nn values(1, 'x') on conflict (coalesce(a, 0), lower(b)) do nothing; + QUERY PLAN +----------------------------------------- + Insert on insertconflict_nn + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: nn_expr_key + -> Result +(4 rows) + +explain (costs off) insert into insertconflict_nn values(1, 'x') on conflict (a int4_ops, lower(b)) do nothing; + QUERY PLAN +----------------------------------------- + Insert on insertconflict_nn + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: nn_expr_key + -> Result +(4 rows) + +-- fails: +explain (costs off) insert into insertconflict_nn values(1, 'x') on conflict (a) do nothing; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +drop table insertconflict_nn; +-- -- Single key tests -- create unique index key_index on insertconflicttest(key); diff --git a/src/test/regress/sql/insert_conflict.sql b/src/test/regress/sql/insert_conflict.sql index d119158549f..8539c49c939 100644 --- a/src/test/regress/sql/insert_conflict.sql +++ b/src/test/regress/sql/insert_conflict.sql @@ -78,6 +78,19 @@ explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on con drop index cross_match; +-- +-- Index expressions that are simplified to a plain column can be inferred +-- +create table insertconflict_nn (a int not null, b text); +create unique index nn_expr_key on insertconflict_nn(coalesce(a, 0), lower(b)); + +explain (costs off) insert into insertconflict_nn values(1, 'x') on conflict (coalesce(a, 0), lower(b)) do nothing; +explain (costs off) insert into insertconflict_nn values(1, 'x') on conflict (a int4_ops, lower(b)) do nothing; +-- fails: +explain (costs off) insert into insertconflict_nn values(1, 'x') on conflict (a) do nothing; + +drop table insertconflict_nn; + -- -- Single key tests --