public inbox for [email protected]  
help / color / mirror / Atom feed
[Patch] Omit virtual generated columns from test_decoding output
6+ messages / 3 participants
[nested] [flat]

* [Patch] Omit virtual generated columns from test_decoding output
@ 2026-05-05 01:11  SATYANARAYANA NARLAPURAM <[email protected]>
  0 siblings, 1 reply; 6+ messages in thread

From: SATYANARAYANA NARLAPURAM @ 2026-05-05 01:11 UTC (permalink / raw)
  To: PostgreSQL Hackers <[email protected]>

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



^ permalink  raw  reply  [nested|flat] 6+ messages in thread

* Re: [Patch] Omit virtual generated columns from test_decoding output
@ 2026-05-05 03:08  Euler Taveira <[email protected]>
  parent: SATYANARAYANA NARLAPURAM <[email protected]>
  0 siblings, 1 reply; 6+ messages in thread

From: Euler Taveira @ 2026-05-05 03:08 UTC (permalink / raw)
  To: [email protected]; +Cc: SATYANARAYANA NARLAPURAM <[email protected]>

On Mon, May 4, 2026, at 10:11 PM, SATYANARAYANA NARLAPURAM wrote:
>
> 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.
>

I wouldn't say misleading but expected. Logical decoding relies on WAL and
virtual generated columns are not stored in the WAL.

> This matches the pgoutput's logicalrep_should_publish_column()
> which never publishes virtual generated columns. Added a regression test.
> Please find the patch attached.
>

There is no guarantee that test_decoding should match the pgoutput. I agree that
test_decoding shouldn't output virtual generated columns. The problem is that it
already does it. I'm afraid that removing it should break existing applications.
(I heard that some solutions rely on test_decoding for CDC.) Should we change it
as you proposed or add an option to put it back to keep the old behavior?

I didn't review your patch but I noticed that there is a new test file for this
change. There are some concerns about the total test execution time. Do you
really need to include this test? If so, should you combine it with an existing
test file?


-- 
Euler Taveira
EDB   https://www.enterprisedb.com/





^ permalink  raw  reply  [nested|flat] 6+ messages in thread

* Re: [Patch] Omit virtual generated columns from test_decoding output
@ 2026-05-05 05:16  SATYANARAYANA NARLAPURAM <[email protected]>
  parent: Euler Taveira <[email protected]>
  0 siblings, 1 reply; 6+ messages in thread

From: SATYANARAYANA NARLAPURAM @ 2026-05-05 05:16 UTC (permalink / raw)
  To: Euler Taveira <[email protected]>; +Cc: [email protected]

Hi,

On Mon, May 4, 2026 at 8:09 PM Euler Taveira <[email protected]> wrote:

> On Mon, May 4, 2026, at 10:11 PM, SATYANARAYANA NARLAPURAM wrote:
> >
> > 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.
> >
>
> I wouldn't say misleading but expected. Logical decoding relies on WAL and
> virtual generated columns are not stored in the WAL.
>
> > This matches the pgoutput's logicalrep_should_publish_column()
> > which never publishes virtual generated columns. Added a regression test.
> > Please find the patch attached.
> >
>
> There is no guarantee that test_decoding should match the pgoutput.


Agreed, not trying to keep them in sync but giving as a reference.



> I agree that
> test_decoding shouldn't output virtual generated columns. The problem is
> that it
> already does it. I'm afraid that removing it should break existing
> applications.
> (I heard that some solutions rely on test_decoding for CDC.) Should we
> change it
> as you proposed or add an option to put it back to keep the old behavior?
>

It is emitting null, I am not sure if it is meaningful for the consumers to
consume this or
have taken dependency on this. Adding an extra option isn't an overkill for
this? I am open
to this idea if others feel the same.



> I didn't review your patch but I noticed that there is a new test file for
> this
> change. There are some concerns about the total test execution time. Do you
> really need to include this test? If so, should you combine it with an
> existing
> test file?


Fair concern, I moved the tests to ddl.sql.  Please find the attached v2
patch.

Thanks,
Satya


^ permalink  raw  reply  [nested|flat] 6+ messages in thread

* Re: [Patch] Omit virtual generated columns from test_decoding output
@ 2026-05-08 04:31  Fujii Masao <[email protected]>
  parent: SATYANARAYANA NARLAPURAM <[email protected]>
  0 siblings, 1 reply; 6+ messages in thread

From: Fujii Masao @ 2026-05-08 04:31 UTC (permalink / raw)
  To: SATYANARAYANA NARLAPURAM <[email protected]>; +Cc: Euler Taveira <[email protected]>; [email protected]

On Tue, May 5, 2026 at 2:16 PM SATYANARAYANA NARLAPURAM
<[email protected]> wrote:
> Fair concern, I moved the tests to ddl.sql.  Please find the attached v2 patch.

Seems you forgot to attached the patch.

Regards,

-- 
Fujii Masao





^ permalink  raw  reply  [nested|flat] 6+ messages in thread

* Re: [Patch] Omit virtual generated columns from test_decoding output
@ 2026-05-09 20:58  SATYANARAYANA NARLAPURAM <[email protected]>
  parent: Fujii Masao <[email protected]>
  0 siblings, 1 reply; 6+ messages in thread

From: SATYANARAYANA NARLAPURAM @ 2026-05-09 20:58 UTC (permalink / raw)
  To: Fujii Masao <[email protected]>; +Cc: Euler Taveira <[email protected]>; [email protected]

Hi,

On Thu, May 7, 2026 at 9:31 PM Fujii Masao <[email protected]> wrote:

> On Tue, May 5, 2026 at 2:16 PM SATYANARAYANA NARLAPURAM
> <[email protected]> wrote:
> > Fair concern, I moved the tests to ddl.sql.  Please find the attached v2
> patch.
>
> Seems you forgot to attached the patch.
>

Attached now, thanks for letting ne know!


Attachments:

  [application/octet-stream] v2-0001-Omit-virtual-generated-columns-from-test_decoding-ou.patch (5.7K, 3-v2-0001-Omit-virtual-generated-columns-from-test_decoding-ou.patch)
  download | inline diff:
From df631b3f1689a777da9875e0a73c537887a78984 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 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



^ permalink  raw  reply  [nested|flat] 6+ messages in thread

* Re: [Patch] Omit virtual generated columns from test_decoding output
@ 2026-05-09 21:00  SATYANARAYANA NARLAPURAM <[email protected]>
  parent: SATYANARAYANA NARLAPURAM <[email protected]>
  0 siblings, 0 replies; 6+ messages in thread

From: SATYANARAYANA NARLAPURAM @ 2026-05-09 21:00 UTC (permalink / raw)
  To: Fujii Masao <[email protected]>; +Cc: Euler Taveira <[email protected]>; [email protected]

Hi

On Sat, May 9, 2026 at 1:58 PM SATYANARAYANA NARLAPURAM <
[email protected]> wrote:

> Hi,
>
> On Thu, May 7, 2026 at 9:31 PM Fujii Masao <[email protected]> wrote:
>
>> On Tue, May 5, 2026 at 2:16 PM SATYANARAYANA NARLAPURAM
>> <[email protected]> wrote:
>> > Fair concern, I moved the tests to ddl.sql.  Please find the attached
>> v2 patch.
>>
>> Seems you forgot to attached the patch.
>>
>
> Attached now, thanks for letting ne know!
>

Please find the v3 patch.

Thanks,
Satya


Attachments:

  [application/octet-stream] v3-0001-Omit-virtual-generated-columns-from-test_decoding-ou.patch (5.7K, 3-v3-0001-Omit-virtual-generated-columns-from-test_decoding-ou.patch)
  download | inline diff:
From df631b3f1689a777da9875e0a73c537887a78984 Mon Sep 17 00:00:00 2001
From: Satya Narlapuram <[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 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



^ permalink  raw  reply  [nested|flat] 6+ messages in thread


end of thread, other threads:[~2026-05-09 21:00 UTC | newest]

Thread overview: 6+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-05-05 01:11 [Patch] Omit virtual generated columns from test_decoding output SATYANARAYANA NARLAPURAM <[email protected]>
2026-05-05 03:08 ` Euler Taveira <[email protected]>
2026-05-05 05:16   ` SATYANARAYANA NARLAPURAM <[email protected]>
2026-05-08 04:31     ` Fujii Masao <[email protected]>
2026-05-09 20:58       ` SATYANARAYANA NARLAPURAM <[email protected]>
2026-05-09 21:00         ` SATYANARAYANA NARLAPURAM <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox