v2-0001-Omit-virtual-generated-columns-from-test_decoding-ou.patch
application/octet-stream
Filename: v2-0001-Omit-virtual-generated-columns-from-test_decoding-ou.patch
Type: application/octet-stream
Part: 0
From df631b3f1689a777da9875e0a73c537887a78984 Mon Sep 17 00:00:00 2001
From: Bug Hunt <bug-hunt@example.com>
Date: Tue, 5 May 2026 00:51:03 +0000
Subject: [PATCH] Omit virtual generated columns from test_decoding output
Virtual generated columns are not stored on disk, so heap_getattr() in
tuple_to_stringinfo() always returned NULL for them, producing
misleading output such as
table public.t: INSERT: a[integer]:1 b[integer]:10 c[integer]:null
even though the user could observe a non-null value via SELECT. Stored
generated columns continue to be emitted as before because their values
do live in the heap tuple.
This matches the policy in pgoutput's logicalrep_should_publish_column()
which never publishes virtual generated columns.
Also extend the existing ddl regression test (sql/ddl.sql,
expected/ddl.out) to cover both a mixed virtual+stored table and a
table whose only non-key columns are virtual.
---
contrib/test_decoding/expected/ddl.out | 45 ++++++++++++++++++++++++++
contrib/test_decoding/sql/ddl.sql | 25 ++++++++++++++
contrib/test_decoding/test_decoding.c | 11 +++++++
3 files changed, 81 insertions(+)
diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index 6819812e..992abf84 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -895,6 +895,51 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc
data
(0 rows)
\pset format aligned
+-- check that virtual generated columns are omitted from the output (their
+-- values are not stored on disk so heap_getattr() would otherwise emit a
+-- misleading NULL), while stored generated columns are emitted normally.
+CREATE TABLE gtest1 (
+ a int PRIMARY KEY,
+ b int,
+ c int GENERATED ALWAYS AS (a + b) VIRTUAL,
+ d int GENERATED ALWAYS AS (a * 2) STORED
+);
+INSERT INTO gtest1 (a, b) VALUES (1, 10), (2, 20);
+UPDATE gtest1 SET b = 99 WHERE a = 1;
+DELETE FROM gtest1 WHERE a = 2;
+SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+ data
+----------------------------------------------------------------------
+ BEGIN
+ table public.gtest1: INSERT: a[integer]:1 b[integer]:10 d[integer]:2
+ table public.gtest1: INSERT: a[integer]:2 b[integer]:20 d[integer]:4
+ COMMIT
+ BEGIN
+ table public.gtest1: UPDATE: a[integer]:1 b[integer]:99 d[integer]:2
+ COMMIT
+ BEGIN
+ table public.gtest1: DELETE: a[integer]:2
+ COMMIT
+(10 rows)
+
+-- table with only virtual generated columns alongside the key
+CREATE TABLE gtest2 (
+ a int PRIMARY KEY,
+ b int GENERATED ALWAYS AS (a + 1) VIRTUAL,
+ c text GENERATED ALWAYS AS ('row-' || a::text) VIRTUAL
+);
+INSERT INTO gtest2 (a) VALUES (10), (20);
+SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+ data
+--------------------------------------------
+ BEGIN
+ table public.gtest2: INSERT: a[integer]:10
+ table public.gtest2: INSERT: a[integer]:20
+ COMMIT
+(4 rows)
+
+DROP TABLE gtest1;
+DROP TABLE gtest2;
SELECT pg_drop_replication_slot('regression_slot');
pg_drop_replication_slot
--------------------------
diff --git a/contrib/test_decoding/sql/ddl.sql b/contrib/test_decoding/sql/ddl.sql
index 6d0b7d77..ca9bfb94 100644
--- a/contrib/test_decoding/sql/ddl.sql
+++ b/contrib/test_decoding/sql/ddl.sql
@@ -467,6 +467,31 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc
SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
\pset format aligned
+-- check that virtual generated columns are omitted from the output (their
+-- values are not stored on disk so heap_getattr() would otherwise emit a
+-- misleading NULL), while stored generated columns are emitted normally.
+CREATE TABLE gtest1 (
+ a int PRIMARY KEY,
+ b int,
+ c int GENERATED ALWAYS AS (a + b) VIRTUAL,
+ d int GENERATED ALWAYS AS (a * 2) STORED
+);
+INSERT INTO gtest1 (a, b) VALUES (1, 10), (2, 20);
+UPDATE gtest1 SET b = 99 WHERE a = 1;
+DELETE FROM gtest1 WHERE a = 2;
+SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+
+-- table with only virtual generated columns alongside the key
+CREATE TABLE gtest2 (
+ a int PRIMARY KEY,
+ b int GENERATED ALWAYS AS (a + 1) VIRTUAL,
+ c text GENERATED ALWAYS AS ('row-' || a::text) VIRTUAL
+);
+INSERT INTO gtest2 (a) VALUES (10), (20);
+SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1');
+DROP TABLE gtest1;
+DROP TABLE gtest2;
+
SELECT pg_drop_replication_slot('regression_slot');
/* check that the slot is gone */
diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c
index d5cf0fa0..83ce9e75 100644
--- a/contrib/test_decoding/test_decoding.c
+++ b/contrib/test_decoding/test_decoding.c
@@ -554,6 +554,17 @@ tuple_to_stringinfo(StringInfo s, TupleDesc tupdesc, HeapTuple tuple, bool skip_
if (attr->attnum < 0)
continue;
+ /*
+ * Don't print virtual generated columns. Their values are not
+ * stored in the heap tuple, so heap_getattr() would always return
+ * NULL, which is misleading. This matches pgoutput's policy of
+ * never publishing virtual generated columns (see
+ * logicalrep_should_publish_column()). Stored generated columns
+ * are emitted as usual since their values are actually on disk.
+ */
+ if (attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ continue;
+
typid = attr->atttypid;
/* get Datum from tuple */
--
2.43.0