Fix toast rewrites in logical decoding.
authorAmit Kapila
Wed, 25 Aug 2021 03:53:27 +0000 (09:23 +0530)
committerAmit Kapila
Wed, 25 Aug 2021 03:53:27 +0000 (09:23 +0530)
Commit 325f2ec555 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

contrib/test_decoding/expected/toast.out
contrib/test_decoding/sql/toast.sql
src/backend/catalog/toasting.c
src/backend/commands/cluster.c
src/backend/commands/tablecmds.c
src/include/catalog/toasting.h
src/include/commands/tablecmds.h

index 75c4d22d8013136ed0019746a29b15d236ced6e8..cd03e9d50a16cf5e70f71864fe418964e9628e00 100644 (file)
@@ -360,6 +360,28 @@ WHERE data NOT LIKE '%INSERT: %';
  COMMIT
 (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');
  pg_drop_replication_slot 
 --------------------------
index 016c3ab78424986ff80e4089cc9a5a277c49ad8f..d1c560a174d63bc2463257980519326ebfe73f46 100644 (file)
@@ -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')
 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');
index 3f7ab8d389be8fbf35b389b435c4ebd16e64f513..2d4a7e1b68ac56934be693d2435748e44a5d6f81 100644 (file)
 Oid            binary_upgrade_next_toast_pg_type_oid = InvalidOid;
 
 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,
-                              Datum reloptions, LOCKMODE lockmode, bool check);
+                              Datum reloptions, LOCKMODE lockmode, bool check,
+                              Oid OIDOldToast);
 static bool needs_toast_table(Relation rel);
 
 
@@ -59,30 +61,34 @@ static bool needs_toast_table(Relation rel);
 void
 AlterTableCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode)
 {
-   CheckAndCreateToastTable(relOid, reloptions, lockmode, true);
+   CheckAndCreateToastTable(relOid, reloptions, lockmode, true, InvalidOid);
 }
 
 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
 NewRelationCreateToastTable(Oid relOid, Datum reloptions)
 {
-   CheckAndCreateToastTable(relOid, reloptions, AccessExclusiveLock, false);
+   CheckAndCreateToastTable(relOid, reloptions, AccessExclusiveLock, false,
+                            InvalidOid);
 }
 
 static void
-CheckAndCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode, bool check)
+CheckAndCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode,
+                        bool check, Oid OIDOldToast)
 {
    Relation    rel;
 
    rel = table_open(relOid, lockmode);
 
    /* 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);
 }
@@ -108,7 +114,7 @@ BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
 
    /* create_toast_table does all the work */
    if (!create_toast_table(rel, toastOid, toastIndexOid, (Datum) 0,
-                           AccessExclusiveLock, false))
+                           AccessExclusiveLock, false, InvalidOid))
        elog(ERROR, "\"%s\" does not require a toast table",
             relName);
 
@@ -125,7 +131,8 @@ BootstrapToastTable(char *relName, 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)
 {
    Oid         relOid = RelationGetRelid(rel);
    HeapTuple   reltup;
@@ -270,7 +277,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
                                           false,
                                           true,
                                           true,
-                                          InvalidOid,
+                                          OIDOldToast,
                                           NULL);
    Assert(toast_relid != InvalidOid);
 
index 04d12a7ece69855b12c86c468d17706b08795edf..2b042978db0234df5cd5c838866479c66ff1b520 100644 (file)
@@ -709,7 +709,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
        if (isNull)
            reloptions = (Datum) 0;
 
-       NewHeapCreateToastTable(OIDNewHeap, reloptions, lockmode);
+       NewHeapCreateToastTable(OIDNewHeap, reloptions, lockmode, toastid);
 
        ReleaseSysCache(tuple);
    }
@@ -1487,6 +1487,14 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 
            RenameRelationInternal(toastidx,
                                   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);
    }
index 8784bec0e6d6a13818cd224b3f86c19f8e660fad..b149f2e9d832d050898ef7e2b077b2924248814b 100644 (file)
@@ -3562,6 +3562,37 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
    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
  * any open reference to the target table besides the one just acquired by
index 51491c451314c8492e00d60894ca6b1603f567b9..bde14263a1154672e3262f6cb33e50368bc1708a 100644 (file)
@@ -24,7 +24,7 @@
  */
 extern void NewRelationCreateToastTable(Oid relOid, Datum reloptions);
 extern void NewHeapCreateToastTable(Oid relOid, Datum reloptions,
-                                   LOCKMODE lockmode);
+                                   LOCKMODE lockmode, Oid OIDOldToast);
 extern void AlterTableCreateToastTable(Oid relOid, Datum reloptions,
                                       LOCKMODE lockmode);
 extern void BootstrapToastTable(char *relName,
index c1581ad178eeb4c749880a60f8bcc256b708b7c7..e01a1715d5885cfe4400abb49875c2f6c08c3ddc 100644 (file)
@@ -71,6 +71,8 @@ extern void RenameRelationInternal(Oid myrelid,
                                   const char *newrelname, bool is_internal,
                                   bool is_index);
 
+extern void ResetRelRewrite(Oid myrelid);
+
 extern void find_composite_type_dependencies(Oid typeOid,
                                             Relation origRelation,
                                             const char *origTypeName);