fix-commit-inside-cursor-loop.patch
text/x-diff
Filename: fix-commit-inside-cursor-loop.patch
Type: text/x-diff
Part: 0
Patch
Format: unified
| File | + | − |
|---|---|---|
| src/pl/plpgsql/src/pl_exec.c | 11 | 0 |
| src/test/isolation/expected/plpgsql-toast.out | 43 | 0 |
| src/test/isolation/specs/plpgsql-toast.spec | 20 | 0 |
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..27d6ea7551 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5826,6 +5826,17 @@ exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
*/
PinPortal(portal);
+ /*
+ * In a non-atomic context, we dare not prefetch, even if it would
+ * otherwise be safe. Aside from any semantic hazards that that might
+ * create, if we prefetch toasted data and then the user commits the
+ * transaction, the toast references could turn into dangling pointers.
+ * (Rows we haven't yet fetched from the cursor are safe, because the
+ * PersistHoldablePortal mechanism handles this scenario.)
+ */
+ if (!estate->atomic)
+ prefetch_ok = false;
+
/*
* Fetch the initial tuple(s). If prefetching is allowed then we grab a
* few more rows to avoid multiple trips through executor startup
diff --git a/src/test/isolation/expected/plpgsql-toast.out b/src/test/isolation/expected/plpgsql-toast.out
index fc557da5e7..4f216b94b6 100644
--- a/src/test/isolation/expected/plpgsql-toast.out
+++ b/src/test/isolation/expected/plpgsql-toast.out
@@ -192,3 +192,46 @@ pg_advisory_unlock
t
s1: NOTICE: length(r) = 6002
step assign5: <... completed>
+
+starting permutation: lock assign6 vacuum unlock
+pg_advisory_unlock_all
+
+
+pg_advisory_unlock_all
+
+
+step lock:
+ SELECT pg_advisory_lock(1);
+
+pg_advisory_lock
+
+
+step assign6:
+do $$
+ declare
+ r record;
+ begin
+ insert into test1 values (2, repeat('bar', 3000));
+ insert into test1 values (3, repeat('baz', 4000));
+ for r in select test1.b from test1 loop
+ delete from test1;
+ commit;
+ perform pg_advisory_lock(1);
+ raise notice 'length(r) = %', length(r::text);
+ end loop;
+ end;
+$$;
+ <waiting ...>
+step vacuum:
+ VACUUM test1;
+
+step unlock:
+ SELECT pg_advisory_unlock(1);
+
+pg_advisory_unlock
+
+t
+s1: NOTICE: length(r) = 6002
+s1: NOTICE: length(r) = 9002
+s1: NOTICE: length(r) = 12002
+step assign6: <... completed>
diff --git a/src/test/isolation/specs/plpgsql-toast.spec b/src/test/isolation/specs/plpgsql-toast.spec
index fe7090addb..bd2948b3d3 100644
--- a/src/test/isolation/specs/plpgsql-toast.spec
+++ b/src/test/isolation/specs/plpgsql-toast.spec
@@ -112,6 +112,25 @@ do $$
$$;
}
+# committing inside a cursor loop presents its own hazards
+step "assign6"
+{
+do $$
+ declare
+ r record;
+ begin
+ insert into test1 values (2, repeat('bar', 3000));
+ insert into test1 values (3, repeat('baz', 4000));
+ for r in select test1.b from test1 loop
+ delete from test1;
+ commit;
+ perform pg_advisory_lock(1);
+ raise notice 'length(r) = %', length(r::text);
+ end loop;
+ end;
+$$;
+}
+
session "s2"
setup
{
@@ -135,3 +154,4 @@ permutation "lock" "assign2" "vacuum" "unlock"
permutation "lock" "assign3" "vacuum" "unlock"
permutation "lock" "assign4" "vacuum" "unlock"
permutation "lock" "assign5" "vacuum" "unlock"
+permutation "lock" "assign6" "vacuum" "unlock"