Skip to content

Commit 29b5905

Browse files
author
Amit Kapila
committed
Fix toast rewrites in logical decoding.
Commit 325f2ec introduced pg_class.relwrite to skip operations on tables created as part of a heap rewrite during DDL. It links such transient heaps to the original relation OID via this new field in pg_class but forgot to do anything about toast tables. So, logical decoding was not able to skip operations on internally created toast tables. This leads to an error when we tried to decode the WAL for the next operation for which it appeared that there is a toast data where actually it didn't have any toast data. To fix this, we set pg_class.relwrite for internally created toast tables as well which allowed skipping operations on them during logical decoding. Author: Bertrand Drouvot Reviewed-by: David Zhang, Amit Kapila Backpatch-through: 11, where it was introduced Discussion: https://postgr.es/m/b5146fb1-ad9e-7d6e-f980-98ed68744a7c@amazon.com
1 parent 255ed90 commit 29b5905

File tree

7 files changed

+99
-13
lines changed

7 files changed

+99
-13
lines changed

contrib/test_decoding/expected/toast.out

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,28 @@ WHERE data NOT LIKE '%INSERT: %';
360360
COMMIT
361361
(4 rows)
362362

363+
/*
364+
* Test decoding relation rewrite with toast. The insert into tbl2 within the
365+
* same transaction is there to check that there is no remaining toast_hash not
366+
* being reset.
367+
*/
368+
CREATE TABLE tbl1 (a INT, b TEXT);
369+
CREATE TABLE tbl2 (a INT);
370+
ALTER TABLE tbl1 ALTER COLUMN b SET STORAGE EXTERNAL;
371+
BEGIN;
372+
INSERT INTO tbl1 VALUES(1, repeat('a', 4000)) ;
373+
ALTER TABLE tbl1 ADD COLUMN id serial primary key;
374+
INSERT INTO tbl2 VALUES(1);
375+
commit;
376+
SELECT substr(data, 1, 200) FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
377+
substr
378+
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
379+
BEGIN
380+
table public.tbl1: INSERT: a[integer]:1 b[text]:'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
381+
table public.tbl2: INSERT: a[integer]:1
382+
COMMIT
383+
(4 rows)
384+
363385
SELECT pg_drop_replication_slot('regression_slot');
364386
pg_drop_replication_slot
365387
--------------------------

contrib/test_decoding/sql/toast.sql

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,4 +308,20 @@ DROP TABLE toasted_several;
308308

309309
SELECT regexp_replace(data, '^(.{100}).*(.{100})$', '\1..\2') FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1')
310310
WHERE data NOT LIKE '%INSERT: %';
311+
312+
/*
313+
* Test decoding relation rewrite with toast. The insert into tbl2 within the
314+
* same transaction is there to check that there is no remaining toast_hash not
315+
* being reset.
316+
*/
317+
CREATE TABLE tbl1 (a INT, b TEXT);
318+
CREATE TABLE tbl2 (a INT);
319+
ALTER TABLE tbl1 ALTER COLUMN b SET STORAGE EXTERNAL;
320+
BEGIN;
321+
INSERT INTO tbl1 VALUES(1, repeat('a', 4000)) ;
322+
ALTER TABLE tbl1 ADD COLUMN id serial primary key;
323+
INSERT INTO tbl2 VALUES(1);
324+
commit;
325+
SELECT substr(data, 1, 200) FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
326+
311327
SELECT pg_drop_replication_slot('regression_slot');

src/backend/catalog/toasting.c

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@
3636
#include "utils/syscache.h"
3737

3838
static void CheckAndCreateToastTable(Oid relOid, Datum reloptions,
39-
LOCKMODE lockmode, bool check);
39+
LOCKMODE lockmode, bool check,
40+
Oid OIDOldToast);
4041
static bool create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
41-
Datum reloptions, LOCKMODE lockmode, bool check);
42+
Datum reloptions, LOCKMODE lockmode, bool check,
43+
Oid OIDOldToast);
4244
static bool needs_toast_table(Relation rel);
4345

4446

@@ -57,30 +59,34 @@ static bool needs_toast_table(Relation rel);
5759
void
5860
AlterTableCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode)
5961
{
60-
CheckAndCreateToastTable(relOid, reloptions, lockmode, true);
62+
CheckAndCreateToastTable(relOid, reloptions, lockmode, true, InvalidOid);
6163
}
6264

6365
void
64-
NewHeapCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode)
66+
NewHeapCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode,
67+
Oid OIDOldToast)
6568
{
66-
CheckAndCreateToastTable(relOid, reloptions, lockmode, false);
69+
CheckAndCreateToastTable(relOid, reloptions, lockmode, false, OIDOldToast);
6770
}
6871

6972
void
7073
NewRelationCreateToastTable(Oid relOid, Datum reloptions)
7174
{
72-
CheckAndCreateToastTable(relOid, reloptions, AccessExclusiveLock, false);
75+
CheckAndCreateToastTable(relOid, reloptions, AccessExclusiveLock, false,
76+
InvalidOid);
7377
}
7478

7579
static void
76-
CheckAndCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode, bool check)
80+
CheckAndCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode,
81+
bool check, Oid OIDOldToast)
7782
{
7883
Relation rel;
7984

8085
rel = table_open(relOid, lockmode);
8186

8287
/* create_toast_table does all the work */
83-
(void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions, lockmode, check);
88+
(void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions, lockmode,
89+
check, OIDOldToast);
8490

8591
table_close(rel, NoLock);
8692
}
@@ -104,7 +110,7 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
104110

105111
/* create_toast_table does all the work */
106112
if (!create_toast_table(rel, toastOid, toastIndexOid, (Datum) 0,
107-
AccessExclusiveLock, false))
113+
AccessExclusiveLock, false, InvalidOid))
108114
elog(ERROR, "\"%s\" does not require a toast table",
109115
relName);
110116

@@ -121,7 +127,8 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
121127
*/
122128
static bool
123129
create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
124-
Datum reloptions, LOCKMODE lockmode, bool check)
130+
Datum reloptions, LOCKMODE lockmode, bool check,
131+
Oid OIDOldToast)
125132
{
126133
Oid relOid = RelationGetRelid(rel);
127134
HeapTuple reltup;
@@ -258,7 +265,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
258265
false,
259266
true,
260267
true,
261-
InvalidOid,
268+
OIDOldToast,
262269
NULL);
263270
Assert(toast_relid != InvalidOid);
264271

src/backend/commands/cluster.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
735735
if (isNull)
736736
reloptions = (Datum) 0;
737737

738-
NewHeapCreateToastTable(OIDNewHeap, reloptions, lockmode);
738+
NewHeapCreateToastTable(OIDNewHeap, reloptions, lockmode, toastid);
739739

740740
ReleaseSysCache(tuple);
741741
}
@@ -1526,6 +1526,14 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
15261526

15271527
RenameRelationInternal(toastidx,
15281528
NewToastName, true, true);
1529+
1530+
/*
1531+
* Reset the relrewrite for the toast. The command-counter
1532+
* increment is required here as we are about to update
1533+
* the tuple that is updated as part of RenameRelationInternal.
1534+
*/
1535+
CommandCounterIncrement();
1536+
ResetRelRewrite(newrel->rd_rel->reltoastrelid);
15291537
}
15301538
relation_close(newrel, NoLock);
15311539
}

src/backend/commands/tablecmds.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3849,6 +3849,37 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
38493849
relation_close(targetrelation, NoLock);
38503850
}
38513851

3852+
/*
3853+
* ResetRelRewrite - reset relrewrite
3854+
*/
3855+
void
3856+
ResetRelRewrite(Oid myrelid)
3857+
{
3858+
Relation relrelation; /* for RELATION relation */
3859+
HeapTuple reltup;
3860+
Form_pg_class relform;
3861+
3862+
/*
3863+
* Find relation's pg_class tuple.
3864+
*/
3865+
relrelation = table_open(RelationRelationId, RowExclusiveLock);
3866+
3867+
reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
3868+
if (!HeapTupleIsValid(reltup)) /* shouldn't happen */
3869+
elog(ERROR, "cache lookup failed for relation %u", myrelid);
3870+
relform = (Form_pg_class) GETSTRUCT(reltup);
3871+
3872+
/*
3873+
* Update pg_class tuple.
3874+
*/
3875+
relform->relrewrite = InvalidOid;
3876+
3877+
CatalogTupleUpdate(relrelation, &reltup->t_self, reltup);
3878+
3879+
heap_freetuple(reltup);
3880+
table_close(relrelation, RowExclusiveLock);
3881+
}
3882+
38523883
/*
38533884
* Disallow ALTER TABLE (and similar commands) when the current backend has
38543885
* any open reference to the target table besides the one just acquired by

src/include/catalog/toasting.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
*/
2222
extern void NewRelationCreateToastTable(Oid relOid, Datum reloptions);
2323
extern void NewHeapCreateToastTable(Oid relOid, Datum reloptions,
24-
LOCKMODE lockmode);
24+
LOCKMODE lockmode, Oid OIDOldToast);
2525
extern void AlterTableCreateToastTable(Oid relOid, Datum reloptions,
2626
LOCKMODE lockmode);
2727
extern void BootstrapToastTable(char *relName,

src/include/commands/tablecmds.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ extern void RenameRelationInternal(Oid myrelid,
7878
const char *newrelname, bool is_internal,
7979
bool is_index);
8080

81+
extern void ResetRelRewrite(Oid myrelid);
82+
8183
extern void find_composite_type_dependencies(Oid typeOid,
8284
Relation origRelation,
8385
const char *origTypeName);

0 commit comments

Comments
 (0)