Skip to content

Commit 9c810a2

Browse files
committed
Fix failure to handle conflicts in non-arbiter exclusion constraints.
ExecInsertIndexTuples treated an exclusion constraint as subject to noDupErr processing even when it was not listed in arbiterIndexes, and would therefore not error out for a conflict in such a constraint, instead returning it as an arbiter-index failure. That led to an infinite loop in ExecInsert, since ExecCheckIndexConstraints ignored the index as-intended and therefore didn't throw the expected error. To fix, make the exclusion constraint code path use the same condition as the index_insert call does to decide whether no-error-for-duplicates behavior is appropriate. While at it, refactor a little bit to avoid unnecessary list_member_oid calls. (That surely wouldn't save anything worth noticing, but I find the code a bit clearer this way.) Per bug report from Heikki Rauhala. Back-patch to 9.5 where ON CONFLICT was introduced. Report: <4C976D6B-76B4-434C-8052-D009F7B7AEDA@reaktor.fi>
1 parent 29a2195 commit 9c810a2

File tree

3 files changed

+49
-7
lines changed

3 files changed

+49
-7
lines changed

src/backend/executor/execIndexing.c

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,9 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo)
259259
* the same is done for non-deferred constraints, but report
260260
* if conflict was speculative or deferred conflict to caller)
261261
*
262+
* If 'arbiterIndexes' is nonempty, noDupErr applies only to
263+
* those indexes. NIL means noDupErr applies to all indexes.
264+
*
262265
* CAUTION: this must not be called for a HOT update.
263266
* We can't defend against that here for lack of info.
264267
* Should we change the API to make it safer?
@@ -308,19 +311,15 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
308311
{
309312
Relation indexRelation = relationDescs[i];
310313
IndexInfo *indexInfo;
314+
bool applyNoDupErr;
311315
IndexUniqueCheck checkUnique;
312316
bool satisfiesConstraint;
313-
bool arbiter;
314317

315318
if (indexRelation == NULL)
316319
continue;
317320

318321
indexInfo = indexInfoArray[i];
319322

320-
/* Record if speculative insertion arbiter */
321-
arbiter = list_member_oid(arbiterIndexes,
322-
indexRelation->rd_index->indexrelid);
323-
324323
/* If the index is marked as read-only, ignore it */
325324
if (!indexInfo->ii_ReadyForInserts)
326325
continue;
@@ -358,6 +357,12 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
358357
values,
359358
isnull);
360359

360+
/* Check whether to apply noDupErr to this index */
361+
applyNoDupErr = noDupErr &&
362+
(arbiterIndexes == NIL ||
363+
list_member_oid(arbiterIndexes,
364+
indexRelation->rd_index->indexrelid));
365+
361366
/*
362367
* The index AM does the actual insertion, plus uniqueness checking.
363368
*
@@ -373,7 +378,7 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
373378
*/
374379
if (!indexRelation->rd_index->indisunique)
375380
checkUnique = UNIQUE_CHECK_NO;
376-
else if (noDupErr && (arbiterIndexes == NIL || arbiter))
381+
else if (applyNoDupErr)
377382
checkUnique = UNIQUE_CHECK_PARTIAL;
378383
else if (indexRelation->rd_index->indimmediate)
379384
checkUnique = UNIQUE_CHECK_YES;
@@ -407,7 +412,7 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
407412
bool violationOK;
408413
CEOUC_WAIT_MODE waitMode;
409414

410-
if (noDupErr)
415+
if (applyNoDupErr)
411416
{
412417
violationOK = true;
413418
waitMode = CEOUC_LIVELOCK_PREVENTING_WAIT;

src/test/regress/expected/insert_conflict.out

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,3 +704,26 @@ insert into dropcol(key, keep1, keep2) values(1, '5', 5) on conflict(key)
704704

705705
;
706706
DROP TABLE dropcol;
707+
-- check handling of regular btree constraint along with gist constraint
708+
create table twoconstraints (f1 int unique, f2 box,
709+
exclude using gist(f2 with &&));
710+
insert into twoconstraints values(1, '((0,0),(1,1))');
711+
insert into twoconstraints values(1, '((2,2),(3,3))'); -- fail on f1
712+
ERROR: duplicate key value violates unique constraint "twoconstraints_f1_key"
713+
DETAIL: Key (f1)=(1) already exists.
714+
insert into twoconstraints values(2, '((0,0),(1,2))'); -- fail on f2
715+
ERROR: conflicting key value violates exclusion constraint "twoconstraints_f2_excl"
716+
DETAIL: Key (f2)=((1,2),(0,0)) conflicts with existing key (f2)=((1,1),(0,0)).
717+
insert into twoconstraints values(2, '((0,0),(1,2))')
718+
on conflict on constraint twoconstraints_f1_key do nothing; -- fail on f2
719+
ERROR: conflicting key value violates exclusion constraint "twoconstraints_f2_excl"
720+
DETAIL: Key (f2)=((1,2),(0,0)) conflicts with existing key (f2)=((1,1),(0,0)).
721+
insert into twoconstraints values(2, '((0,0),(1,2))')
722+
on conflict on constraint twoconstraints_f2_excl do nothing; -- do nothing
723+
select * from twoconstraints;
724+
f1 | f2
725+
----+-------------
726+
1 | (1,1),(0,0)
727+
(1 row)
728+
729+
drop table twoconstraints;

src/test/regress/sql/insert_conflict.sql

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,3 +407,17 @@ insert into dropcol(key, keep1, keep2) values(1, '5', 5) on conflict(key)
407407
;
408408

409409
DROP TABLE dropcol;
410+
411+
-- check handling of regular btree constraint along with gist constraint
412+
413+
create table twoconstraints (f1 int unique, f2 box,
414+
exclude using gist(f2 with &&));
415+
insert into twoconstraints values(1, '((0,0),(1,1))');
416+
insert into twoconstraints values(1, '((2,2),(3,3))'); -- fail on f1
417+
insert into twoconstraints values(2, '((0,0),(1,2))'); -- fail on f2
418+
insert into twoconstraints values(2, '((0,0),(1,2))')
419+
on conflict on constraint twoconstraints_f1_key do nothing; -- fail on f2
420+
insert into twoconstraints values(2, '((0,0),(1,2))')
421+
on conflict on constraint twoconstraints_f2_excl do nothing; -- do nothing
422+
select * from twoconstraints;
423+
drop table twoconstraints;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy