1
0
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:
Tom Lane
2018-05-16 14:56:52 -04:00
parent a11b3bd37f
commit 2efc924180
7 changed files with 522 additions and 75 deletions

View 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>

View File

@ -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

View 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"