public inbox for [email protected]
help / color / mirror / Atom feedFrom: SATYANARAYANA NARLAPURAM <[email protected]>
To: PostgreSQL Hackers <[email protected]>
Subject: [Patch] Omit virtual generated columns from test_decoding output
Date: Mon, 4 May 2026 18:11:11 -0700
Message-ID: <CAHg+QDfTh3UbB-Ed--o2Bd=SBDJoEiG-qp3C0+ETDibF63y=dw@mail.gmail.com> (raw)
Hi Hackers,
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 pgoutput's logicalrep_should_publish_column()
which never publishes virtual generated columns. Added a regression test.
Please find the patch attached.
Thanks,
Satya
Attachments:
[application/octet-stream] 0001-Omit-virtual-generated-columns-from-test_decoding-ou.patch (7.3K, 3-0001-Omit-virtual-generated-columns-from-test_decoding-ou.patch)
download | inline diff:
From fd227526fe7ea6f766120451fad0f634b6b17e78 Mon Sep 17 00:00:00 2001
From: Bug Hunt <[email protected]>
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 add a regression test (sql/generated.sql,
expected/generated.out).
---
contrib/test_decoding/Makefile | 3 +-
contrib/test_decoding/expected/generated.out | 63 ++++++++++++++++++++
contrib/test_decoding/meson.build | 1 +
contrib/test_decoding/sql/generated.sql | 39 ++++++++++++
contrib/test_decoding/test_decoding.c | 11 ++++
5 files changed, 116 insertions(+), 1 deletion(-)
create mode 100644 contrib/test_decoding/expected/generated.out
create mode 100644 contrib/test_decoding/sql/generated.sql
diff --git a/contrib/test_decoding/Makefile b/contrib/test_decoding/Makefile
index 01111243..6d050765 100644
--- a/contrib/test_decoding/Makefile
+++ b/contrib/test_decoding/Makefile
@@ -5,7 +5,8 @@ PGFILEDESC = "test_decoding - example of a logical decoding output plugin"
REGRESS = ddl xact rewrite toast permissions decoding_in_xact \
decoding_into_rel binary prepared replorigin time messages \
- repack spill slot truncate stream stats twophase twophase_stream
+ repack spill slot truncate stream stats twophase twophase_stream \
+ generated
ISOLATION = mxact delayed_startup ondisk_startup concurrent_ddl_dml \
oldest_xmin snapshot_transfer subxact_without_top concurrent_stream \
twophase_snapshot slot_creation_error catalog_change_snapshot \
diff --git a/contrib/test_decoding/expected/generated.out b/contrib/test_decoding/expected/generated.out
new file mode 100644
index 00000000..5aedb6b9
--- /dev/null
+++ b/contrib/test_decoding/expected/generated.out
@@ -0,0 +1,63 @@
+-- predictability
+SET synchronous_commit = on;
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding');
+ ?column?
+----------
+ init
+(1 row)
+
+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;
+-- Virtual generated column "c" must be omitted (its value is not stored on
+-- disk so heap_getattr() would otherwise emit a misleading NULL). Stored
+-- generated column "d" is emitted normally because its value is on disk.
+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 'stop' FROM pg_drop_replication_slot('regression_slot');
+ ?column?
+----------
+ stop
+(1 row)
+
diff --git a/contrib/test_decoding/meson.build b/contrib/test_decoding/meson.build
index ac655853..24de6543 100644
--- a/contrib/test_decoding/meson.build
+++ b/contrib/test_decoding/meson.build
@@ -42,6 +42,7 @@ tests += {
'stats',
'twophase',
'twophase_stream',
+ 'generated',
],
'regress_args': [
'--temp-config', files('logical.conf'),
diff --git a/contrib/test_decoding/sql/generated.sql b/contrib/test_decoding/sql/generated.sql
new file mode 100644
index 00000000..c57ee3a1
--- /dev/null
+++ b/contrib/test_decoding/sql/generated.sql
@@ -0,0 +1,39 @@
+-- predictability
+SET synchronous_commit = on;
+
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding');
+
+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;
+
+-- Virtual generated column "c" must be omitted (its value is not stored on
+-- disk so heap_getattr() would otherwise emit a misleading NULL). Stored
+-- generated column "d" is emitted normally because its value is on disk.
+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 'stop' FROM pg_drop_replication_slot('regression_slot');
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
view thread (6+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected]
Subject: Re: [Patch] Omit virtual generated columns from test_decoding output
In-Reply-To: <CAHg+QDfTh3UbB-Ed--o2Bd=SBDJoEiG-qp3C0+ETDibF63y=dw@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox