From df631b3f1689a777da9875e0a73c537887a78984 Mon Sep 17 00:00:00 2001 From: Bug Hunt 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