Skip to content

Commit 794025e

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 7d9026c commit 794025e

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
@@ -38,9 +38,11 @@
3838
Oid binary_upgrade_next_toast_pg_type_oid = InvalidOid;
3939

4040
static void CheckAndCreateToastTable(Oid relOid, Datum reloptions,
41-
LOCKMODE lockmode, bool check);
41+
LOCKMODE lockmode, bool check,
42+
Oid OIDOldToast);
4243
static bool create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
43-
Datum reloptions, LOCKMODE lockmode, bool check);
44+
Datum reloptions, LOCKMODE lockmode, bool check,
45+
Oid OIDOldToast);
4446
static bool needs_toast_table(Relation rel);
4547

4648

@@ -59,30 +61,34 @@ static bool needs_toast_table(Relation rel);
5961
void
6062
AlterTableCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode)
6163
{
62-
CheckAndCreateToastTable(relOid, reloptions, lockmode, true);
64+
CheckAndCreateToastTable(relOid, reloptions, lockmode, true, InvalidOid);
6365
}
6466

6567
void
66-
NewHeapCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode)
68+
NewHeapCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode,
69+
Oid OIDOldToast)
6770
{
68-
CheckAndCreateToastTable(relOid, reloptions, lockmode, false);
71+
CheckAndCreateToastTable(relOid, reloptions, lockmode, false, OIDOldToast);
6972
}
7073

7174
void
7275
NewRelationCreateToastTable(Oid relOid, Datum reloptions)
7376
{
74-
CheckAndCreateToastTable(relOid, reloptions, AccessExclusiveLock, false);
77+
CheckAndCreateToastTable(relOid, reloptions, AccessExclusiveLock, false,
78+
InvalidOid);
7579
}
7680

7781
static void
78-
CheckAndCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode, bool check)
82+
CheckAndCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode,
83+
bool check, Oid OIDOldToast)
7984
{
8085
Relation rel;
8186

8287
rel = table_open(relOid, lockmode);
8388

8489
/* create_toast_table does all the work */
85-
(void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions, lockmode, check);
90+
(void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions, lockmode,
91+
check, OIDOldToast);
8692

8793
table_close(rel, NoLock);
8894
}
@@ -108,7 +114,7 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
108114

109115
/* create_toast_table does all the work */
110116
if (!create_toast_table(rel, toastOid, toastIndexOid, (Datum) 0,
111-
AccessExclusiveLock, false))
117+
AccessExclusiveLock, false, InvalidOid))
112118
elog(ERROR, "\"%s\" does not require a toast table",
113119
relName);
114120

@@ -125,7 +131,8 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
125131
*/
126132
static bool
127133
create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
128-
Datum reloptions, LOCKMODE lockmode, bool check)
134+
Datum reloptions, LOCKMODE lockmode, bool check,
135+
Oid OIDOldToast)
129136
{
130137
Oid relOid = RelationGetRelid(rel);
131138
HeapTuple reltup;
@@ -270,7 +277,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
270277
false,
271278
true,
272279
true,
273-
InvalidOid,
280+
OIDOldToast,
274281
NULL);
275282
Assert(toast_relid != InvalidOid);
276283

src/backend/commands/cluster.c

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

712-
NewHeapCreateToastTable(OIDNewHeap, reloptions, lockmode);
712+
NewHeapCreateToastTable(OIDNewHeap, reloptions, lockmode, toastid);
713713

714714
ReleaseSysCache(tuple);
715715
}
@@ -1487,6 +1487,14 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
14871487

14881488
RenameRelationInternal(toastidx,
14891489
NewToastName, true, true);
1490+
1491+
/*
1492+
* Reset the relrewrite for the toast. The command-counter
1493+
* increment is required here as we are about to update
1494+
* the tuple that is updated as part of RenameRelationInternal.
1495+
*/
1496+
CommandCounterIncrement();
1497+
ResetRelRewrite(newrel->rd_rel->reltoastrelid);
14901498
}
14911499
relation_close(newrel, NoLock);
14921500
}

src/backend/commands/tablecmds.c

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

3565+
/*
3566+
* ResetRelRewrite - reset relrewrite
3567+
*/
3568+
void
3569+
ResetRelRewrite(Oid myrelid)
3570+
{
3571+
Relation relrelation; /* for RELATION relation */
3572+
HeapTuple reltup;
3573+
Form_pg_class relform;
3574+
3575+
/*
3576+
* Find relation's pg_class tuple.
3577+
*/
3578+
relrelation = table_open(RelationRelationId, RowExclusiveLock);
3579+
3580+
reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
3581+
if (!HeapTupleIsValid(reltup)) /* shouldn't happen */
3582+
elog(ERROR, "cache lookup failed for relation %u", myrelid);
3583+
relform = (Form_pg_class) GETSTRUCT(reltup);
3584+
3585+
/*
3586+
* Update pg_class tuple.
3587+
*/
3588+
relform->relrewrite = InvalidOid;
3589+
3590+
CatalogTupleUpdate(relrelation, &reltup->t_self, reltup);
3591+
3592+
heap_freetuple(reltup);
3593+
table_close(relrelation, RowExclusiveLock);
3594+
}
3595+
35653596
/*
35663597
* Disallow ALTER TABLE (and similar commands) when the current backend has
35673598
* 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
@@ -24,7 +24,7 @@
2424
*/
2525
extern void NewRelationCreateToastTable(Oid relOid, Datum reloptions);
2626
extern void NewHeapCreateToastTable(Oid relOid, Datum reloptions,
27-
LOCKMODE lockmode);
27+
LOCKMODE lockmode, Oid OIDOldToast);
2828
extern void AlterTableCreateToastTable(Oid relOid, Datum reloptions,
2929
LOCKMODE lockmode);
3030
extern void BootstrapToastTable(char *relName,

src/include/commands/tablecmds.h

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

74+
extern void ResetRelRewrite(Oid myrelid);
75+
7476
extern void find_composite_type_dependencies(Oid typeOid,
7577
Relation origRelation,
7678
const char *origTypeName);

0 commit comments

Comments
 (0)