mirror of
https://github.com/postgres/postgres.git
synced 2025-07-28 23:42:10 +03:00
Detoast plpgsql variables if they might live across a transaction boundary.
Up to now, it's been safe for plpgsql to store TOAST pointers in its variables because the ActiveSnapshot for whatever query called the plpgsql function will surely protect such TOAST values from being vacuumed away, even if the owning table rows are committed dead. With the introduction of procedures, that assumption is no longer good in "non atomic" executions of plpgsql code. We adopt the slightly brute-force solution of detoasting all TOAST pointers at the time they are stored into variables, if we're in a non-atomic context, just in case the owning row goes away. Some care is needed to avoid long-term memory leaks, since plpgsql tends to run with CurrentMemoryContext pointing to its call-lifespan context, but we shouldn't assume that no memory is leaked by heap_tuple_fetch_attr. In plpgsql proper, we can do the detoasting work in the "eval_mcontext". Most of the code thrashing here is due to the need to add this capability to expandedrecord.c as well as plpgsql proper. In expandedrecord.c, we can't assume that the caller's context is short-lived, so make use of the short-term sub-context that was already invented for checking domain constraints. In view of this repurposing, it seems good to rename that variable and associated code from "domain_check_cxt" to "short_term_cxt". Peter Eisentraut and Tom Lane Discussion: https://postgr.es/m/5AC06865.9050005@anastigmatix.net
This commit is contained in:
189
src/test/isolation/expected/plpgsql-toast.out
Normal file
189
src/test/isolation/expected/plpgsql-toast.out
Normal file
@ -0,0 +1,189 @@
|
||||
Parsed test spec with 2 sessions
|
||||
|
||||
starting permutation: lock assign1 vacuum unlock
|
||||
pg_advisory_unlock_all
|
||||
|
||||
|
||||
pg_advisory_unlock_all
|
||||
|
||||
|
||||
step lock:
|
||||
SELECT pg_advisory_lock(1);
|
||||
|
||||
pg_advisory_lock
|
||||
|
||||
|
||||
step assign1:
|
||||
do $$
|
||||
declare
|
||||
x text;
|
||||
begin
|
||||
select test1.b into x from test1;
|
||||
delete from test1;
|
||||
commit;
|
||||
perform pg_advisory_lock(1);
|
||||
raise notice 'x = %', x;
|
||||
end;
|
||||
$$;
|
||||
<waiting ...>
|
||||
step vacuum:
|
||||
VACUUM test1;
|
||||
|
||||
step unlock:
|
||||
SELECT pg_advisory_unlock(1);
|
||||
|
||||
pg_advisory_unlock
|
||||
|
||||
t
|
||||
step assign1: <... completed>
|
||||
|
||||
starting permutation: lock assign2 vacuum unlock
|
||||
pg_advisory_unlock_all
|
||||
|
||||
|
||||
pg_advisory_unlock_all
|
||||
|
||||
|
||||
step lock:
|
||||
SELECT pg_advisory_lock(1);
|
||||
|
||||
pg_advisory_lock
|
||||
|
||||
|
||||
step assign2:
|
||||
do $$
|
||||
declare
|
||||
x text;
|
||||
begin
|
||||
x := (select test1.b from test1);
|
||||
delete from test1;
|
||||
commit;
|
||||
perform pg_advisory_lock(1);
|
||||
raise notice 'x = %', x;
|
||||
end;
|
||||
$$;
|
||||
<waiting ...>
|
||||
step vacuum:
|
||||
VACUUM test1;
|
||||
|
||||
step unlock:
|
||||
SELECT pg_advisory_unlock(1);
|
||||
|
||||
pg_advisory_unlock
|
||||
|
||||
t
|
||||
step assign2: <... completed>
|
||||
|
||||
starting permutation: lock assign3 vacuum unlock
|
||||
pg_advisory_unlock_all
|
||||
|
||||
|
||||
pg_advisory_unlock_all
|
||||
|
||||
|
||||
step lock:
|
||||
SELECT pg_advisory_lock(1);
|
||||
|
||||
pg_advisory_lock
|
||||
|
||||
|
||||
step assign3:
|
||||
do $$
|
||||
declare
|
||||
r record;
|
||||
begin
|
||||
select * into r from test1;
|
||||
r.b := (select test1.b from test1);
|
||||
delete from test1;
|
||||
commit;
|
||||
perform pg_advisory_lock(1);
|
||||
raise notice 'r = %', r;
|
||||
end;
|
||||
$$;
|
||||
<waiting ...>
|
||||
step vacuum:
|
||||
VACUUM test1;
|
||||
|
||||
step unlock:
|
||||
SELECT pg_advisory_unlock(1);
|
||||
|
||||
pg_advisory_unlock
|
||||
|
||||
t
|
||||
step assign3: <... completed>
|
||||
|
||||
starting permutation: lock assign4 vacuum unlock
|
||||
pg_advisory_unlock_all
|
||||
|
||||
|
||||
pg_advisory_unlock_all
|
||||
|
||||
|
||||
step lock:
|
||||
SELECT pg_advisory_lock(1);
|
||||
|
||||
pg_advisory_lock
|
||||
|
||||
|
||||
step assign4:
|
||||
do $$
|
||||
declare
|
||||
r test2;
|
||||
begin
|
||||
select * into r from test1;
|
||||
delete from test1;
|
||||
commit;
|
||||
perform pg_advisory_lock(1);
|
||||
raise notice 'r = %', r;
|
||||
end;
|
||||
$$;
|
||||
<waiting ...>
|
||||
step vacuum:
|
||||
VACUUM test1;
|
||||
|
||||
step unlock:
|
||||
SELECT pg_advisory_unlock(1);
|
||||
|
||||
pg_advisory_unlock
|
||||
|
||||
t
|
||||
step assign4: <... completed>
|
||||
|
||||
starting permutation: lock assign5 vacuum unlock
|
||||
pg_advisory_unlock_all
|
||||
|
||||
|
||||
pg_advisory_unlock_all
|
||||
|
||||
|
||||
step lock:
|
||||
SELECT pg_advisory_lock(1);
|
||||
|
||||
pg_advisory_lock
|
||||
|
||||
|
||||
step assign5:
|
||||
do $$
|
||||
declare
|
||||
r record;
|
||||
begin
|
||||
for r in select test1.b from test1 loop
|
||||
null;
|
||||
end loop;
|
||||
delete from test1;
|
||||
commit;
|
||||
perform pg_advisory_lock(1);
|
||||
raise notice 'r = %', r;
|
||||
end;
|
||||
$$;
|
||||
<waiting ...>
|
||||
step vacuum:
|
||||
VACUUM test1;
|
||||
|
||||
step unlock:
|
||||
SELECT pg_advisory_unlock(1);
|
||||
|
||||
pg_advisory_unlock
|
||||
|
||||
t
|
||||
step assign5: <... completed>
|
@ -74,3 +74,4 @@ test: predicate-gin-nomatch
|
||||
test: partition-key-update-1
|
||||
test: partition-key-update-2
|
||||
test: partition-key-update-3
|
||||
test: plpgsql-toast
|
||||
|
137
src/test/isolation/specs/plpgsql-toast.spec
Normal file
137
src/test/isolation/specs/plpgsql-toast.spec
Normal file
@ -0,0 +1,137 @@
|
||||
# Test TOAST behavior in PL/pgSQL procedures with transaction control.
|
||||
#
|
||||
# We need to ensure that values stored in PL/pgSQL variables are free
|
||||
# of external TOAST references, because those could disappear after a
|
||||
# transaction is committed (leading to errors "missing chunk number
|
||||
# ... for toast value ..."). The tests here do this by running VACUUM
|
||||
# in a second session. Advisory locks are used to have the VACUUM
|
||||
# kick in at the right time. The different "assign" steps test
|
||||
# different code paths for variable assignments in PL/pgSQL.
|
||||
|
||||
setup
|
||||
{
|
||||
CREATE TABLE test1 (a int, b text);
|
||||
ALTER TABLE test1 ALTER COLUMN b SET STORAGE EXTERNAL;
|
||||
INSERT INTO test1 VALUES (1, repeat('foo', 2000));
|
||||
CREATE TYPE test2 AS (a bigint, b text);
|
||||
}
|
||||
|
||||
teardown
|
||||
{
|
||||
DROP TABLE test1;
|
||||
DROP TYPE test2;
|
||||
}
|
||||
|
||||
session "s1"
|
||||
|
||||
setup
|
||||
{
|
||||
SELECT pg_advisory_unlock_all();
|
||||
}
|
||||
|
||||
# assign_simple_var()
|
||||
step "assign1"
|
||||
{
|
||||
do $$
|
||||
declare
|
||||
x text;
|
||||
begin
|
||||
select test1.b into x from test1;
|
||||
delete from test1;
|
||||
commit;
|
||||
perform pg_advisory_lock(1);
|
||||
raise notice 'x = %', x;
|
||||
end;
|
||||
$$;
|
||||
}
|
||||
|
||||
# assign_simple_var()
|
||||
step "assign2"
|
||||
{
|
||||
do $$
|
||||
declare
|
||||
x text;
|
||||
begin
|
||||
x := (select test1.b from test1);
|
||||
delete from test1;
|
||||
commit;
|
||||
perform pg_advisory_lock(1);
|
||||
raise notice 'x = %', x;
|
||||
end;
|
||||
$$;
|
||||
}
|
||||
|
||||
# expanded_record_set_field()
|
||||
step "assign3"
|
||||
{
|
||||
do $$
|
||||
declare
|
||||
r record;
|
||||
begin
|
||||
select * into r from test1;
|
||||
r.b := (select test1.b from test1);
|
||||
delete from test1;
|
||||
commit;
|
||||
perform pg_advisory_lock(1);
|
||||
raise notice 'r = %', r;
|
||||
end;
|
||||
$$;
|
||||
}
|
||||
|
||||
# expanded_record_set_fields()
|
||||
step "assign4"
|
||||
{
|
||||
do $$
|
||||
declare
|
||||
r test2;
|
||||
begin
|
||||
select * into r from test1;
|
||||
delete from test1;
|
||||
commit;
|
||||
perform pg_advisory_lock(1);
|
||||
raise notice 'r = %', r;
|
||||
end;
|
||||
$$;
|
||||
}
|
||||
|
||||
# expanded_record_set_tuple()
|
||||
step "assign5"
|
||||
{
|
||||
do $$
|
||||
declare
|
||||
r record;
|
||||
begin
|
||||
for r in select test1.b from test1 loop
|
||||
null;
|
||||
end loop;
|
||||
delete from test1;
|
||||
commit;
|
||||
perform pg_advisory_lock(1);
|
||||
raise notice 'r = %', r;
|
||||
end;
|
||||
$$;
|
||||
}
|
||||
|
||||
session "s2"
|
||||
setup
|
||||
{
|
||||
SELECT pg_advisory_unlock_all();
|
||||
}
|
||||
step "lock"
|
||||
{
|
||||
SELECT pg_advisory_lock(1);
|
||||
}
|
||||
step "vacuum"
|
||||
{
|
||||
VACUUM test1;
|
||||
}
|
||||
step "unlock"
|
||||
{
|
||||
SELECT pg_advisory_unlock(1);
|
||||
}
|
||||
|
||||
permutation "lock" "assign1" "vacuum" "unlock"
|
||||
permutation "lock" "assign2" "vacuum" "unlock"
|
||||
permutation "lock" "assign3" "vacuum" "unlock"
|
||||
permutation "lock" "assign4" "vacuum" "unlock"
|
||||
permutation "lock" "assign5" "vacuum" "unlock"
|
Reference in New Issue
Block a user