aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/utils/adt/ri_triggers.c20
-rw-r--r--src/test/regress/expected/foreign_key.out22
-rw-r--r--src/test/regress/sql/foreign_key.sql23
3 files changed, 54 insertions, 11 deletions
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 0c1d67e4934..a5db63e0f6d 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -42,6 +42,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"
@@ -289,20 +290,17 @@ RI_FKey_check(PG_FUNCTION_ARGS)
* 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.
+ * 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.
*/
- Assert(new_row_buf != InvalidBuffer);
+ 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 65dfe024a99..de829d71599 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -1319,3 +1319,25 @@ begin;
(2 rows)
commit;
+--
+-- Test deferred FK check on a tuple deleted by a rolled-back subtransaction
+--
+create table pktable2(f1 int primary key);
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "pktable2_pkey" for table "pktable2"
+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 6bd9ddd1a5e..c81c737c5c9 100644
--- a/src/test/regress/sql/foreign_key.sql
+++ b/src/test/regress/sql/foreign_key.sql
@@ -943,3 +943,26 @@ begin;
update selfref set a = 456 where a = 123;
select a, b from selfref;
commit;
+
+--
+-- 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;