Commit 9d7a80ce authored by Amit Kapila's avatar Amit Kapila

Fix toast rewrites in logical decoding.

Commit 325f2ec5 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
parent 22583ede
...@@ -360,6 +360,28 @@ WHERE data NOT LIKE '%INSERT: %'; ...@@ -360,6 +360,28 @@ WHERE data NOT LIKE '%INSERT: %';
COMMIT COMMIT
(4 rows) (4 rows)
/*
* Test decoding relation rewrite with toast. The insert into tbl2 within the
* same transaction is there to check that there is no remaining toast_hash not
* being reset.
*/
CREATE TABLE tbl1 (a INT, b TEXT);
CREATE TABLE tbl2 (a INT);
ALTER TABLE tbl1 ALTER COLUMN b SET STORAGE EXTERNAL;
BEGIN;
INSERT INTO tbl1 VALUES(1, repeat('a', 4000)) ;
ALTER TABLE tbl1 ADD COLUMN id serial primary key;
INSERT INTO tbl2 VALUES(1);
commit;
SELECT substr(data, 1, 200) FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
substr
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
BEGIN
table public.tbl1: INSERT: a[integer]:1 b[text]:'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
table public.tbl2: INSERT: a[integer]:1
COMMIT
(4 rows)
SELECT pg_drop_replication_slot('regression_slot'); SELECT pg_drop_replication_slot('regression_slot');
pg_drop_replication_slot pg_drop_replication_slot
-------------------------- --------------------------
......
...@@ -308,4 +308,20 @@ DROP TABLE toasted_several; ...@@ -308,4 +308,20 @@ DROP TABLE toasted_several;
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') 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')
WHERE data NOT LIKE '%INSERT: %'; WHERE data NOT LIKE '%INSERT: %';
/*
* Test decoding relation rewrite with toast. The insert into tbl2 within the
* same transaction is there to check that there is no remaining toast_hash not
* being reset.
*/
CREATE TABLE tbl1 (a INT, b TEXT);
CREATE TABLE tbl2 (a INT);
ALTER TABLE tbl1 ALTER COLUMN b SET STORAGE EXTERNAL;
BEGIN;
INSERT INTO tbl1 VALUES(1, repeat('a', 4000)) ;
ALTER TABLE tbl1 ADD COLUMN id serial primary key;
INSERT INTO tbl2 VALUES(1);
commit;
SELECT substr(data, 1, 200) FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
SELECT pg_drop_replication_slot('regression_slot'); SELECT pg_drop_replication_slot('regression_slot');
...@@ -36,9 +36,11 @@ ...@@ -36,9 +36,11 @@
#include "utils/syscache.h" #include "utils/syscache.h"
static void CheckAndCreateToastTable(Oid relOid, Datum reloptions, static void CheckAndCreateToastTable(Oid relOid, Datum reloptions,
LOCKMODE lockmode, bool check); LOCKMODE lockmode, bool check,
Oid OIDOldToast);
static bool create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, static bool create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
Datum reloptions, LOCKMODE lockmode, bool check); Datum reloptions, LOCKMODE lockmode, bool check,
Oid OIDOldToast);
static bool needs_toast_table(Relation rel); static bool needs_toast_table(Relation rel);
...@@ -57,30 +59,34 @@ static bool needs_toast_table(Relation rel); ...@@ -57,30 +59,34 @@ static bool needs_toast_table(Relation rel);
void void
AlterTableCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode) AlterTableCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode)
{ {
CheckAndCreateToastTable(relOid, reloptions, lockmode, true); CheckAndCreateToastTable(relOid, reloptions, lockmode, true, InvalidOid);
} }
void void
NewHeapCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode) NewHeapCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode,
Oid OIDOldToast)
{ {
CheckAndCreateToastTable(relOid, reloptions, lockmode, false); CheckAndCreateToastTable(relOid, reloptions, lockmode, false, OIDOldToast);
} }
void void
NewRelationCreateToastTable(Oid relOid, Datum reloptions) NewRelationCreateToastTable(Oid relOid, Datum reloptions)
{ {
CheckAndCreateToastTable(relOid, reloptions, AccessExclusiveLock, false); CheckAndCreateToastTable(relOid, reloptions, AccessExclusiveLock, false,
InvalidOid);
} }
static void static void
CheckAndCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode, bool check) CheckAndCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode,
bool check, Oid OIDOldToast)
{ {
Relation rel; Relation rel;
rel = table_open(relOid, lockmode); rel = table_open(relOid, lockmode);
/* create_toast_table does all the work */ /* create_toast_table does all the work */
(void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions, lockmode, check); (void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions, lockmode,
check, OIDOldToast);
table_close(rel, NoLock); table_close(rel, NoLock);
} }
...@@ -106,7 +112,7 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid) ...@@ -106,7 +112,7 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
/* create_toast_table does all the work */ /* create_toast_table does all the work */
if (!create_toast_table(rel, toastOid, toastIndexOid, (Datum) 0, if (!create_toast_table(rel, toastOid, toastIndexOid, (Datum) 0,
AccessExclusiveLock, false)) AccessExclusiveLock, false, InvalidOid))
elog(ERROR, "\"%s\" does not require a toast table", elog(ERROR, "\"%s\" does not require a toast table",
relName); relName);
...@@ -123,7 +129,8 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid) ...@@ -123,7 +129,8 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
*/ */
static bool static bool
create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
Datum reloptions, LOCKMODE lockmode, bool check) Datum reloptions, LOCKMODE lockmode, bool check,
Oid OIDOldToast)
{ {
Oid relOid = RelationGetRelid(rel); Oid relOid = RelationGetRelid(rel);
HeapTuple reltup; HeapTuple reltup;
...@@ -260,7 +267,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, ...@@ -260,7 +267,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
false, false,
true, true,
true, true,
InvalidOid, OIDOldToast,
NULL); NULL);
Assert(toast_relid != InvalidOid); Assert(toast_relid != InvalidOid);
......
...@@ -733,7 +733,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence, ...@@ -733,7 +733,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
if (isNull) if (isNull)
reloptions = (Datum) 0; reloptions = (Datum) 0;
NewHeapCreateToastTable(OIDNewHeap, reloptions, lockmode); NewHeapCreateToastTable(OIDNewHeap, reloptions, lockmode, toastid);
ReleaseSysCache(tuple); ReleaseSysCache(tuple);
} }
...@@ -1512,6 +1512,14 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, ...@@ -1512,6 +1512,14 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
RenameRelationInternal(toastidx, RenameRelationInternal(toastidx,
NewToastName, true, true); NewToastName, true, true);
/*
* Reset the relrewrite for the toast. The command-counter
* increment is required here as we are about to update
* the tuple that is updated as part of RenameRelationInternal.
*/
CommandCounterIncrement();
ResetRelRewrite(newrel->rd_rel->reltoastrelid);
} }
relation_close(newrel, NoLock); relation_close(newrel, NoLock);
} }
......
...@@ -3847,6 +3847,37 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo ...@@ -3847,6 +3847,37 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
relation_close(targetrelation, NoLock); relation_close(targetrelation, NoLock);
} }
/*
* ResetRelRewrite - reset relrewrite
*/
void
ResetRelRewrite(Oid myrelid)
{
Relation relrelation; /* for RELATION relation */
HeapTuple reltup;
Form_pg_class relform;
/*
* Find relation's pg_class tuple.
*/
relrelation = table_open(RelationRelationId, RowExclusiveLock);
reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
if (!HeapTupleIsValid(reltup)) /* shouldn't happen */
elog(ERROR, "cache lookup failed for relation %u", myrelid);
relform = (Form_pg_class) GETSTRUCT(reltup);
/*
* Update pg_class tuple.
*/
relform->relrewrite = InvalidOid;
CatalogTupleUpdate(relrelation, &reltup->t_self, reltup);
heap_freetuple(reltup);
table_close(relrelation, RowExclusiveLock);
}
/* /*
* Disallow ALTER TABLE (and similar commands) when the current backend has * Disallow ALTER TABLE (and similar commands) when the current backend has
* any open reference to the target table besides the one just acquired by * any open reference to the target table besides the one just acquired by
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
*/ */
extern void NewRelationCreateToastTable(Oid relOid, Datum reloptions); extern void NewRelationCreateToastTable(Oid relOid, Datum reloptions);
extern void NewHeapCreateToastTable(Oid relOid, Datum reloptions, extern void NewHeapCreateToastTable(Oid relOid, Datum reloptions,
LOCKMODE lockmode); LOCKMODE lockmode, Oid OIDOldToast);
extern void AlterTableCreateToastTable(Oid relOid, Datum reloptions, extern void AlterTableCreateToastTable(Oid relOid, Datum reloptions,
LOCKMODE lockmode); LOCKMODE lockmode);
extern void BootstrapToastTable(char *relName, extern void BootstrapToastTable(char *relName,
......
...@@ -78,6 +78,8 @@ extern void RenameRelationInternal(Oid myrelid, ...@@ -78,6 +78,8 @@ extern void RenameRelationInternal(Oid myrelid,
const char *newrelname, bool is_internal, const char *newrelname, bool is_internal,
bool is_index); bool is_index);
extern void ResetRelRewrite(Oid myrelid);
extern void find_composite_type_dependencies(Oid typeOid, extern void find_composite_type_dependencies(Oid typeOid,
Relation origRelation, Relation origRelation,
const char *origTypeName); const char *origTypeName);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment