diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c index e49affba9ee..28fccaf381e 100644 --- a/src/backend/commands/constraint.c +++ b/src/backend/commands/constraint.c @@ -89,9 +89,10 @@ unique_key_recheck(PG_FUNCTION_ARGS) * because this trigger gets queued only in response to index insertions; * which means it does not get queued for HOT updates. The row we are * called for might now be dead, but have a live HOT child, in which case - * we still need to make the check. Therefore we have to use - * heap_hot_search, not just HeapTupleSatisfiesVisibility as is done in - * the comparable test in RI_FKey_check. + * we still need to make the check --- effectively, we're applying the + * check against the live child row, although we can use the values from + * this row since by definition all columns of interest to us are the + * same. * * This might look like just an optimization, because the index AM will * make this identical test before throwing an error. But it's actually @@ -159,7 +160,9 @@ unique_key_recheck(PG_FUNCTION_ARGS) { /* * Note: this is not a real insert; it is a check that the index entry - * that has already been inserted is unique. + * that has already been inserted is unique. Passing t_self is + * correct even if t_self is now dead, because that is the TID the + * index will know about. */ index_insert(indexRel, values, isnull, &(new_row->t_self), trigdata->tg_relation, UNIQUE_CHECK_EXISTING); @@ -168,10 +171,12 @@ unique_key_recheck(PG_FUNCTION_ARGS) { /* * For exclusion constraints we just do the normal check, but now it's - * okay to throw error. + * okay to throw error. In the HOT-update case, we must use the live + * HOT child's TID here, else check_exclusion_constraint will think + * the child is a conflict. */ check_exclusion_constraint(trigdata->tg_relation, indexRel, indexInfo, - &(new_row->t_self), values, isnull, + &tmptid, values, isnull, estate, false); } diff --git a/src/test/regress/input/constraints.source b/src/test/regress/input/constraints.source index 7647544f9b9..dbab8f159b8 100644 --- a/src/test/regress/input/constraints.source +++ b/src/test/regress/input/constraints.source @@ -467,6 +467,7 @@ DROP TABLE circles; CREATE TABLE deferred_excl ( f1 int, + f2 int, CONSTRAINT deferred_excl_con EXCLUDE (f1 WITH =) INITIALLY DEFERRED ); @@ -482,6 +483,15 @@ INSERT INTO deferred_excl VALUES(3); INSERT INTO deferred_excl VALUES(3); -- no fail here COMMIT; -- should fail here +-- bug #13148: deferred constraint versus HOT update +BEGIN; +INSERT INTO deferred_excl VALUES(2, 1); -- no fail here +DELETE FROM deferred_excl WHERE f1 = 2 AND f2 IS NULL; -- remove old row +UPDATE deferred_excl SET f2 = 2 WHERE f1 = 2; +COMMIT; -- should not fail + +SELECT * FROM deferred_excl; + ALTER TABLE deferred_excl DROP CONSTRAINT deferred_excl_con; -- This should fail, but worth testing because of HOT updates diff --git a/src/test/regress/output/constraints.source b/src/test/regress/output/constraints.source index bbe4ed19760..1c4a0440527 100644 --- a/src/test/regress/output/constraints.source +++ b/src/test/regress/output/constraints.source @@ -634,6 +634,7 @@ DROP TABLE circles; -- Check deferred exclusion constraint CREATE TABLE deferred_excl ( f1 int, + f2 int, CONSTRAINT deferred_excl_con EXCLUDE (f1 WITH =) INITIALLY DEFERRED ); INSERT INTO deferred_excl VALUES(1); @@ -654,6 +655,19 @@ INSERT INTO deferred_excl VALUES(3); -- no fail here COMMIT; -- should fail here ERROR: conflicting key value violates exclusion constraint "deferred_excl_con" DETAIL: Key (f1)=(3) conflicts with existing key (f1)=(3). +-- bug #13148: deferred constraint versus HOT update +BEGIN; +INSERT INTO deferred_excl VALUES(2, 1); -- no fail here +DELETE FROM deferred_excl WHERE f1 = 2 AND f2 IS NULL; -- remove old row +UPDATE deferred_excl SET f2 = 2 WHERE f1 = 2; +COMMIT; -- should not fail +SELECT * FROM deferred_excl; + f1 | f2 +----+---- + 1 | + 2 | 2 +(2 rows) + ALTER TABLE deferred_excl DROP CONSTRAINT deferred_excl_con; -- This should fail, but worth testing because of HOT updates UPDATE deferred_excl SET f1 = 3;