aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/utils/adt/ri_triggers.c22
-rw-r--r--src/test/regress/expected/foreign_key.out21
-rw-r--r--src/test/regress/sql/foreign_key.sql23
3 files changed, 54 insertions, 12 deletions
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index b4765005feb..983f7647d7d 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -44,6 +44,7 @@
#include "parser/parse_coerce.h"
#include "parser/parse_relation.h"
#include "miscadmin.h"
+#include "storage/bufmgr.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
@@ -286,20 +287,17 @@ RI_FKey_check(TriggerData *trigdata)
* We should not even consider checking the row if it is no longer valid,
* since it was either deleted (so the deferred check should be skipped)
* or updated (in which case only the latest version of the row should be
- * checked). Test its liveness according to SnapshotSelf.
- *
- * NOTE: The normal coding rule is that one must acquire the buffer
- * content lock to call HeapTupleSatisfiesVisibility. We can skip that
- * here because we know that AfterTriggerExecute just fetched the tuple
- * successfully, so there cannot be a VACUUM compaction in progress on the
- * page (either heap_fetch would have waited for the VACUUM, or the
- * VACUUM's LockBufferForCleanup would be waiting for us to drop pin). And
- * since this is a row inserted by our open transaction, no one else can
- * be entitled to change its xmin/xmax.
- */
- Assert(new_row_buf != InvalidBuffer);
+ * checked). Test its liveness according to SnapshotSelf. We need pin
+ * and lock on the buffer to call HeapTupleSatisfiesVisibility. Caller
+ * should be holding pin, but not lock.
+ */
+ LockBuffer(new_row_buf, BUFFER_LOCK_SHARE);
if (!HeapTupleSatisfiesVisibility(new_row, SnapshotSelf, new_row_buf))
+ {
+ LockBuffer(new_row_buf, BUFFER_LOCK_UNLOCK);
return PointerGetDatum(NULL);
+ }
+ LockBuffer(new_row_buf, BUFFER_LOCK_UNLOCK);
/*
* Get the relation descriptors of the FK and PK tables.
diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out
index 044881af711..46a30b44289 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -1381,3 +1381,24 @@ explain (costs off) delete from t1 where a = 1;
(10 rows)
delete from t1 where a = 1;
+--
+-- Test deferred FK check on a tuple deleted by a rolled-back subtransaction
+--
+create table pktable2(f1 int primary key);
+create table fktable2(f1 int references pktable2 deferrable initially deferred);
+insert into pktable2 values(1);
+begin;
+insert into fktable2 values(1);
+savepoint x;
+delete from fktable2;
+rollback to x;
+commit;
+begin;
+insert into fktable2 values(2);
+savepoint x;
+delete from fktable2;
+rollback to x;
+commit; -- fail
+ERROR: insert or update on table "fktable2" violates foreign key constraint "fktable2_f1_fkey"
+DETAIL: Key (f1)=(2) is not present in table "pktable2".
+drop table pktable2, fktable2;
diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql
index 85c9d04d64f..cc36ab5bafc 100644
--- a/src/test/regress/sql/foreign_key.sql
+++ b/src/test/regress/sql/foreign_key.sql
@@ -1019,3 +1019,26 @@ create rule r1 as on delete to t1 do delete from t2 where t2.b = old.a;
explain (costs off) delete from t1 where a = 1;
delete from t1 where a = 1;
+
+--
+-- Test deferred FK check on a tuple deleted by a rolled-back subtransaction
+--
+create table pktable2(f1 int primary key);
+create table fktable2(f1 int references pktable2 deferrable initially deferred);
+insert into pktable2 values(1);
+
+begin;
+insert into fktable2 values(1);
+savepoint x;
+delete from fktable2;
+rollback to x;
+commit;
+
+begin;
+insert into fktable2 values(2);
+savepoint x;
+delete from fktable2;
+rollback to x;
+commit; -- fail
+
+drop table pktable2, fktable2;