mirror of
https://github.com/postgres/postgres.git
synced 2026-10-23 13:29:09 +03:00
Fix heap_update() ignoring TABLE_UPDATE_NO_LOGICAL for TOAST tuples.
Previously, heap_update() honored TABLE_UPDATE_NO_LOGICAL for the main
tuple but passed a hardcoded 0 to the tuple toaster, so the tuples it
wrote to the TOAST relation were logged for decoding anyway. That was
harmless until commit 28d534e2ae gave updates the flag; the insert
path has propagated its own ever since suppression was introduced for
heap rewrites.
REPACK (CONCURRENTLY), the only user of the flag, relies on it to keep
the changes it applies to the transient heap out of the logical
stream. Logical decoding therefore reassembled the TOAST value and
then dereferenced a new tuple that the suppressed record doesn't
carry, crashing the backend. This is reachable only if an output
plugin asks for the changes made by heap rewrites. In core that is
just test_decoding with include-rewrites.
Fix this by passing HEAP_INSERT_NO_LOGICAL down to the tuple toaster
when TABLE_UPDATE_NO_LOGICAL is set.
Backpatch to v19, where REPACK (CONCURRENTLY) was introduced.
Reported-by: Thom Brown <thom@linux.com>
Author: Antonin Houska <ah@cybertec.at>
Reviewed-by: Masahiko Sawada <sawada.mshk@gmail.com>
Reviewed-by: Zhijie Hou (Fujitsu) <houzj.fnst@fujitsu.com>
Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/CAA-aLv7L_-dOuHXjLh0Di66dExdOb=uTOzR=jtrqCmV0Wxyd2Q@mail.gmail.com
Backpatch-through: 19
This commit is contained in:
@@ -3961,8 +3961,12 @@ l2:
|
|||||||
*/
|
*/
|
||||||
if (need_toast)
|
if (need_toast)
|
||||||
{
|
{
|
||||||
/* Note we always use WAL and FSM during updates */
|
/*
|
||||||
heaptup = heap_toast_insert_or_update(relation, newtup, &oldtup, 0);
|
* If logical decoding is not needed, suppress it for the TOAST
|
||||||
|
* tuples too. We never skip the FSM here.
|
||||||
|
*/
|
||||||
|
heaptup = heap_toast_insert_or_update(relation, newtup, &oldtup,
|
||||||
|
walLogical ? 0 : HEAP_INSERT_NO_LOGICAL);
|
||||||
newtupsize = MAXALIGN(heaptup->t_len);
|
newtupsize = MAXALIGN(heaptup->t_len);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ EXTENSION = injection_points
|
|||||||
DATA = injection_points--1.0.sql
|
DATA = injection_points--1.0.sql
|
||||||
PGFILEDESC = "injection_points - facility for injection points"
|
PGFILEDESC = "injection_points - facility for injection points"
|
||||||
|
|
||||||
|
EXTRA_INSTALL = contrib/test_decoding
|
||||||
|
|
||||||
REGRESS = injection_points hashagg reindex_conc vacuum
|
REGRESS = injection_points hashagg reindex_conc vacuum
|
||||||
REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress
|
REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress
|
||||||
|
|
||||||
@@ -16,6 +18,7 @@ ISOLATION = basic \
|
|||||||
inplace \
|
inplace \
|
||||||
reindex_concurrently_deferred \
|
reindex_concurrently_deferred \
|
||||||
repack \
|
repack \
|
||||||
|
repack_decode \
|
||||||
repack_temporal \
|
repack_temporal \
|
||||||
repack_temporal_multirange \
|
repack_temporal_multirange \
|
||||||
repack_toast \
|
repack_toast \
|
||||||
|
|||||||
36
src/test/modules/injection_points/expected/repack_decode.out
Normal file
36
src/test/modules/injection_points/expected/repack_decode.out
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
Parsed test spec with 2 sessions
|
||||||
|
|
||||||
|
starting permutation: s1_wait_before_lock s2_changes s2_wakeup_before_lock s1_decode
|
||||||
|
injection_points_attach
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
step s1_wait_before_lock:
|
||||||
|
REPACK (CONCURRENTLY) repack_toast;
|
||||||
|
<waiting ...>
|
||||||
|
step s2_changes:
|
||||||
|
UPDATE repack_toast SET t = gen_external() WHERE i=1;
|
||||||
|
|
||||||
|
step s2_wakeup_before_lock:
|
||||||
|
SELECT injection_points_wakeup('repack-concurrently-before-lock');
|
||||||
|
|
||||||
|
injection_points_wakeup
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
step s1_wait_before_lock: <... completed>
|
||||||
|
step s1_decode:
|
||||||
|
SELECT count(*) FROM pg_logical_slot_peek_changes('s', NULL, NULL, 'include-rewrites', '1');
|
||||||
|
|
||||||
|
count
|
||||||
|
-----
|
||||||
|
9
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
pg_drop_replication_slot
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
@@ -47,6 +47,7 @@ tests += {
|
|||||||
'inplace',
|
'inplace',
|
||||||
'reindex_concurrently_deferred',
|
'reindex_concurrently_deferred',
|
||||||
'repack',
|
'repack',
|
||||||
|
'repack_decode',
|
||||||
'repack_temporal',
|
'repack_temporal',
|
||||||
'repack_temporal_multirange',
|
'repack_temporal_multirange',
|
||||||
'repack_toast',
|
'repack_toast',
|
||||||
|
|||||||
60
src/test/modules/injection_points/specs/repack_decode.spec
Normal file
60
src/test/modules/injection_points/specs/repack_decode.spec
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
setup
|
||||||
|
{
|
||||||
|
CREATE EXTENSION injection_points;
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
-- Generate a string of random characters that is not likely to be
|
||||||
|
-- compressed, but is big enough to be stored externally.
|
||||||
|
CREATE FUNCTION gen_external()
|
||||||
|
RETURNS text
|
||||||
|
LANGUAGE sql as $$
|
||||||
|
SELECT string_agg(chr(65 + trunc(25 * random())::int), '')
|
||||||
|
FROM generate_series(1, 2048) s(x);
|
||||||
|
$$;
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
SELECT pg_create_logical_replication_slot('s', 'test_decoding');
|
||||||
|
|
||||||
|
CREATE TABLE repack_toast(i int PRIMARY KEY, t text);
|
||||||
|
INSERT INTO repack_toast(i, t) VALUES (1, gen_external());
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown
|
||||||
|
{
|
||||||
|
DROP TABLE repack_toast;
|
||||||
|
DROP EXTENSION injection_points;
|
||||||
|
DROP FUNCTION gen_external();
|
||||||
|
SELECT pg_drop_replication_slot('s');
|
||||||
|
}
|
||||||
|
|
||||||
|
session s1
|
||||||
|
setup
|
||||||
|
{
|
||||||
|
SELECT injection_points_set_local();
|
||||||
|
SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
|
||||||
|
}
|
||||||
|
# Perform the initial load and wait for s2 to do some data changes.
|
||||||
|
step s1_wait_before_lock
|
||||||
|
{
|
||||||
|
REPACK (CONCURRENTLY) repack_toast;
|
||||||
|
}
|
||||||
|
step s1_decode
|
||||||
|
{
|
||||||
|
SELECT count(*) FROM pg_logical_slot_peek_changes('s', NULL, NULL, 'include-rewrites', '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
session s2
|
||||||
|
step s2_changes
|
||||||
|
{
|
||||||
|
UPDATE repack_toast SET t = gen_external() WHERE i=1;
|
||||||
|
}
|
||||||
|
step s2_wakeup_before_lock
|
||||||
|
{
|
||||||
|
SELECT injection_points_wakeup('repack-concurrently-before-lock');
|
||||||
|
}
|
||||||
|
|
||||||
|
permutation
|
||||||
|
s1_wait_before_lock
|
||||||
|
s2_changes
|
||||||
|
s2_wakeup_before_lock
|
||||||
|
s1_decode
|
||||||
Reference in New Issue
Block a user