Skip to content

Commit 9d7a80c

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 22583ed commit 9d7a80c

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
}
@@ -106,7 +112,7 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
106112

107113
/* create_toast_table does all the work */
108114
if (!create_toast_table(rel, toastOid, toastIndexOid, (Datum) 0,
109-
AccessExclusiveLock, false))
115+
AccessExclusiveLock, false, InvalidOid))
110116
elog(ERROR, "\"%s\" does not require a toast table",
111117
relName);
112118

@@ -123,7 +129,8 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
123129
*/
124130
static bool
125131
create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
126-
Datum reloptions, LOCKMODE lockmode, bool check)
132+
Datum reloptions, LOCKMODE lockmode, bool check,
133+
Oid OIDOldToast)
127134
{
128135
Oid relOid = RelationGetRelid(rel);
129136
HeapTuple reltup;
@@ -260,7 +267,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
260267
false,
261268
true,
262269
true,
263-
InvalidOid,
270+
OIDOldToast,
264271
NULL);
265272
Assert(toast_relid != InvalidOid);
266273

src/backend/commands/cluster.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
733733
if (isNull)
734734
reloptions = (Datum) 0;
735735

736-
NewHeapCreateToastTable(OIDNewHeap, reloptions, lockmode);
736+
NewHeapCreateToastTable(OIDNewHeap, reloptions, lockmode, toastid);
737737

738738
ReleaseSysCache(tuple);
739739
}
@@ -1512,6 +1512,14 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
15121512

15131513
RenameRelationInternal(toastidx,
15141514
NewToastName, true, true);
1515+
1516+
/*
1517+
* Reset the relrewrite for the toast. The command-counter
1518+
* increment is required here as we are about to update
1519+
* the tuple that is updated as part of RenameRelationInternal.
1520+
*/
1521+
CommandCounterIncrement();
1522+
ResetRelRewrite(newrel->rd_rel->reltoastrelid);
15151523
}
15161524
relation_close(newrel, NoLock);
15171525
}

src/backend/commands/tablecmds.c

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

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