| # 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 'length(x) = %', length(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 'length(x) = %', length(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 'length(r) = %', length(r::text); |
| 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 'length(r) = %', length(r::text); |
| 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 'length(r) = %', length(r::text); |
| end; |
| $$; |
| } |
| |
| # FOR loop must not hold any fetched-but-not-detoasted values across commit |
| 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; |
| $$; |
| } |
| |
| # Check that the results of a query can be detoasted just after committing |
| # (there's no interaction with VACUUM here) |
| step "fetch-after-commit" |
| { |
| do $$ |
| declare |
| r record; |
| t text; |
| begin |
| insert into test1 values (2, repeat('bar', 3000)); |
| insert into test1 values (3, repeat('baz', 4000)); |
| for r in select test1.a from test1 loop |
| commit; |
| select b into t from test1 where a = r.a; |
| raise notice 'length(t) = %', length(t); |
| end loop; |
| 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 |
| permutation lock assign6 vacuum unlock |
| permutation "fetch-after-commit" |