Fix fetching default toast value during decoding of in-progress transactions.
authorAmit Kapila <akapila@postgresql.org>
Mon, 7 Oct 2024 10:08:45 +0000 (15:38 +0530)
committerAmit Kapila <akapila@postgresql.org>
Mon, 7 Oct 2024 10:08:45 +0000 (15:38 +0530)
During logical decoding of in-progress transactions, we perform the toast
table scan while fetching the default toast value for an attribute. We
forgot to initialize the flag during this scan to indicate that the system
table scan is in progress. We need this flag to ensure that during logical
decoding we never directly access the tableam or heap APIs because we check
for concurrent aborts only in systable_* APIs.

Reported-by: Alexander Lakhin
Author: Takeshi Ideriha, Hou Zhijie
Reviewed-by: Amit Kapila, Hou Zhijie
Backpatch-through: 14
Discussion: https://postgr.es/m/18641-6687273b7f15269d@postgresql.org

contrib/test_decoding/expected/stream.out
contrib/test_decoding/sql/stream.sql
src/backend/access/index/genam.c

index a76f77601e26f7333565aea78503074a6bd99b16..6a8a00a65ba24f6a751f77ae1f126df0c4e792e2 100644 (file)
@@ -128,6 +128,28 @@ SELECT count(*) FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL,
      5
 (1 row)
 
+-- Test that accessing a TOAST table in streaming mode is allowed.
+-- Create a table with a column that uses a TOASTed default value.
+-- (temporarily hide query, to avoid the long CREATE TABLE stmt)
+\set ECHO none
+SET debug_logical_replication_streaming = immediate;
+BEGIN;
+INSERT INTO test_tab VALUES(1);
+-- Force WAL flush, so that the above changes will be streamed.
+SELECT 'force flush' FROM pg_switch_wal();
+  ?column?   
+-------------
+ force flush
+(1 row)
+
+SELECT count(*) FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1');
+ count 
+-------
+     3
+(1 row)
+
+COMMIT;
+RESET debug_logical_replication_streaming;
 DROP TABLE stream_test;
 SELECT pg_drop_replication_slot('regression_slot');
  pg_drop_replication_slot 
index 7f43f0c2ab72a0a437298ed7d3d9553ebfb38eec..d7a696586e9ed2f79f8b44ea5a4183acbbbc1ddd 100644 (file)
@@ -59,5 +59,27 @@ ROLLBACK TO s1;
 COMMIT;
 SELECT count(*) FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1');
 
+-- Test that accessing a TOAST table in streaming mode is allowed.
+
+-- Create a table with a column that uses a TOASTed default value.
+-- (temporarily hide query, to avoid the long CREATE TABLE stmt)
+\set ECHO none
+SELECT 'CREATE TABLE test_tab (a text DEFAULT ''' || string_agg('toast value', '') || ''');' FROM generate_series(1, 4000)
+\gexec
+\set ECHO all
+
+SET debug_logical_replication_streaming = immediate;
+
+BEGIN;
+INSERT INTO test_tab VALUES(1);
+
+-- Force WAL flush, so that the above changes will be streamed.
+SELECT 'force flush' FROM pg_switch_wal();
+
+SELECT count(*) FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1');
+COMMIT;
+
+RESET debug_logical_replication_streaming;
+
 DROP TABLE stream_test;
 SELECT pg_drop_replication_slot('regression_slot');
index 52fde5cc4d4ce6dc4edd0e3b1b2a4cba06b66534..69c36084321a0d7f4d05313e176c1f058f994e24 100644 (file)
@@ -713,6 +713,14 @@ systable_beginscan_ordered(Relation heapRelation,
    index_rescan(sysscan->iscan, idxkey, nkeys, NULL, 0);
    sysscan->scan = NULL;
 
+   /*
+    * If CheckXidAlive is set then set a flag to indicate that system table
+    * scan is in-progress.  See detailed comments in xact.c where these
+    * variables are declared.
+    */
+   if (TransactionIdIsValid(CheckXidAlive))
+       bsysscan = true;
+
    return sysscan;
 }
 
@@ -757,6 +765,14 @@ systable_endscan_ordered(SysScanDesc sysscan)
    index_endscan(sysscan->iscan);
    if (sysscan->snapshot)
        UnregisterSnapshot(sysscan->snapshot);
+
+   /*
+    * Reset the bsysscan flag at the end of the systable scan.  See detailed
+    * comments in xact.c where these variables are declared.
+    */
+   if (TransactionIdIsValid(CheckXidAlive))
+       bsysscan = false;
+
    pfree(sysscan);
 }