mirror of
https://github.com/postgres/postgres.git
synced 2026-10-23 13:29:09 +03:00
Fix ON CONFLICT inference for index expressions simplified to a Var
infer_arbiter_indexes() classifies each inference element by its
const-simplified form: a plain Var is matched against the index's
plain attributes, and anything else against its expressions. Index
columns, however, were classified by indkey alone, so an expression
column stayed an expression even when const-simplification reduced it
to a plain Var. Since commit 10c4fe074, COALESCE on a NOT NULL column
is reduced to the column itself, so an inference element such as
COALESCE(b, '') became a plain Var that no longer matched the unique
index on that same expression, and the statement failed with "there is
no unique or exclusion constraint matching the ON CONFLICT
specification".
To fix, classify index columns the same way as inference elements: an
index expression that is simplified to a plain Var is treated as a
plain attribute. Also, teach infer_collation_opclass_match() to
compare such a column as an expression, so that an inference element
that specifies a collation or operator class can still match it.
As a consequence, an inference element naming the column directly now
matches such an index too, which is consistent with how an element
written as the expression already matches a plain index on the column.
Author: Shihao Zhong <zhong950419@gmail.com>
Reviewed-by: jian he <jian.universality@gmail.com>
Reviewed-by: Richard Guo <guofenglinux@gmail.com>
Discussion: https://postgr.es/m/CAGRkXqR7VXacmRP41UBaM5u4aseeCH5ECVmQgn_YS=L7=xdhTw@mail.gmail.com
Backpatch-through: 19
This commit is contained in:
@@ -940,6 +940,8 @@ infer_arbiter_indexes(PlannerInfo *root)
|
||||
Form_pg_index idxForm;
|
||||
Bitmapset *indexedAttrs;
|
||||
List *idxExprs;
|
||||
List *idxNonVarExprs;
|
||||
ListCell *idxExprItem;
|
||||
List *predExprs;
|
||||
AttrNumber natt;
|
||||
bool match;
|
||||
@@ -1044,22 +1046,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)
|
||||
{
|
||||
@@ -1069,6 +1056,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)
|
||||
@@ -1118,7 +1143,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);
|
||||
@@ -1244,12 +1269,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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
--
|
||||
|
||||
Reference in New Issue
Block a user