Skip to content

Commit 096f213

Browse files
committed
Fix infer_arbiter_indexes() to not assume resultRelation is 1.
infer_arbiter_indexes failed to renumber varnos in index expressions or predicates that it got from the catalogs. This escaped detection up to now because the stored varnos in such trees will be 1, and an INSERT's result relation is usually the first rangetable entry, so that that was fine. However, in cases such as inserting through an updatable view, it's not fine, leading to failure to match the expressions to the query with ensuing "there is no unique or exclusion constraint matching the ON CONFLICT specification" errors. Fix by copy-and-paste from get_relation_info(). Per bug #18502 from Michael Wang. Back-patch to all supported versions. Discussion: https://postgr.es/m/18502-545b53f5b81e54e0@postgresql.org
1 parent 5dcaefc commit 096f213

File tree

3 files changed

+27
-1
lines changed

3 files changed

+27
-1
lines changed

src/backend/optimizer/util/plancat.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@ infer_arbiter_indexes(PlannerInfo *root)
629629
OnConflictExpr *onconflict = root->parse->onConflict;
630630

631631
/* Iteration state */
632+
Index varno;
632633
RangeTblEntry *rte;
633634
Relation relation;
634635
Oid indexOidFromConstraint = InvalidOid;
@@ -657,7 +658,8 @@ infer_arbiter_indexes(PlannerInfo *root)
657658
* the rewriter or when expand_inherited_rtentry() added it to the query's
658659
* rangetable.
659660
*/
660-
rte = rt_fetch(root->parse->resultRelation, root->parse->rtable);
661+
varno = root->parse->resultRelation;
662+
rte = rt_fetch(varno, root->parse->rtable);
661663

662664
relation = table_open(rte->relid, NoLock);
663665

@@ -791,6 +793,9 @@ infer_arbiter_indexes(PlannerInfo *root)
791793

792794
/* Expression attributes (if any) must match */
793795
idxExprs = RelationGetIndexExpressions(idxRel);
796+
if (idxExprs && varno != 1)
797+
ChangeVarNodes((Node *) idxExprs, 1, varno, 0);
798+
794799
foreach(el, onconflict->arbiterElems)
795800
{
796801
InferenceElem *elem = (InferenceElem *) lfirst(el);
@@ -842,6 +847,8 @@ infer_arbiter_indexes(PlannerInfo *root)
842847
* CONFLICT's WHERE clause.
843848
*/
844849
predExprs = RelationGetIndexPredicate(idxRel);
850+
if (predExprs && varno != 1)
851+
ChangeVarNodes((Node *) predExprs, 1, varno, 0);
845852

846853
if (!predicate_implied_by(predExprs, (List *) onconflict->arbiterWhere, false))
847854
goto next;

src/test/regress/expected/insert_conflict.out

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
-- insert...on conflict do unique index inference
33
--
44
create table insertconflicttest(key int4, fruit text);
5+
-- These things should work through a view, as well
6+
create view insertconflictview as select * from insertconflicttest;
57
--
68
-- Test unique index inference with operator class specifications and
79
-- named collations
@@ -43,6 +45,15 @@ explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on con
4345
-> Result
4446
(4 rows)
4547

48+
explain (costs off) insert into insertconflictview values(0, 'Crowberry') on conflict (lower(fruit), key, lower(fruit), key) do nothing;
49+
QUERY PLAN
50+
-------------------------------------------------
51+
Insert on insertconflicttest
52+
Conflict Resolution: NOTHING
53+
Conflict Arbiter Indexes: both_index_expr_key
54+
-> Result
55+
(4 rows)
56+
4657
explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key, fruit) do update set fruit = excluded.fruit
4758
where exists (select 1 from insertconflicttest ii where ii.key = excluded.key);
4859
QUERY PLAN
@@ -374,6 +385,7 @@ create unique index partial_key_index on insertconflicttest(key) where fruit lik
374385
-- Succeeds
375386
insert into insertconflicttest values (23, 'Blackberry') on conflict (key) where fruit like '%berry' do update set fruit = excluded.fruit;
376387
insert into insertconflicttest as t values (23, 'Blackberry') on conflict (key) where fruit like '%berry' and t.fruit = 'inconsequential' do nothing;
388+
insert into insertconflictview as t values (23, 'Blackberry') on conflict (key) where fruit like '%berry' and t.fruit = 'inconsequential' do nothing;
377389
-- fails
378390
insert into insertconflicttest values (23, 'Blackberry') on conflict (key) do update set fruit = excluded.fruit;
379391
ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
@@ -439,6 +451,7 @@ explain (costs off) insert into insertconflicttest as i values (23, 'Avocado') o
439451

440452
drop index plain;
441453
-- Cleanup
454+
drop view insertconflictview;
442455
drop table insertconflicttest;
443456
--
444457
-- Verify that EXCLUDED does not allow system column references. These

src/test/regress/sql/insert_conflict.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
--
44
create table insertconflicttest(key int4, fruit text);
55

6+
-- These things should work through a view, as well
7+
create view insertconflictview as select * from insertconflicttest;
8+
69
--
710
-- Test unique index inference with operator class specifications and
811
-- named collations
@@ -20,6 +23,7 @@ explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on con
2023
explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key, fruit) do nothing;
2124
explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (fruit, key, fruit, key) do nothing;
2225
explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (lower(fruit), key, lower(fruit), key) do nothing;
26+
explain (costs off) insert into insertconflictview values(0, 'Crowberry') on conflict (lower(fruit), key, lower(fruit), key) do nothing;
2327
explain (costs off) insert into insertconflicttest values(0, 'Crowberry') on conflict (key, fruit) do update set fruit = excluded.fruit
2428
where exists (select 1 from insertconflicttest ii where ii.key = excluded.key);
2529
-- Neither collation nor operator class specifications are required --
@@ -215,6 +219,7 @@ create unique index partial_key_index on insertconflicttest(key) where fruit lik
215219
-- Succeeds
216220
insert into insertconflicttest values (23, 'Blackberry') on conflict (key) where fruit like '%berry' do update set fruit = excluded.fruit;
217221
insert into insertconflicttest as t values (23, 'Blackberry') on conflict (key) where fruit like '%berry' and t.fruit = 'inconsequential' do nothing;
222+
insert into insertconflictview as t values (23, 'Blackberry') on conflict (key) where fruit like '%berry' and t.fruit = 'inconsequential' do nothing;
218223

219224
-- fails
220225
insert into insertconflicttest values (23, 'Blackberry') on conflict (key) do update set fruit = excluded.fruit;
@@ -247,6 +252,7 @@ explain (costs off) insert into insertconflicttest as i values (23, 'Avocado') o
247252
drop index plain;
248253

249254
-- Cleanup
255+
drop view insertconflictview;
250256
drop table insertconflicttest;
251257

252258

0 commit comments

Comments
 (0)