]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix COPY TO FORMAT JSON to exclude generated columns.
authorAndrew Dunstan <andrew@dunslane.net>
Wed, 15 Apr 2026 11:47:12 +0000 (07:47 -0400)
committerAndrew Dunstan <andrew@dunslane.net>
Wed, 15 Apr 2026 11:58:17 +0000 (07:58 -0400)
COPY TO with FORMAT json was including generated columns in the
output, unlike TEXT and CSV formats.  Virtual generated columns
appeared as null, and stored ones showed their computed values.

The JSON code path only built a restricted TupleDesc when an explicit
column list was given (attnamelist != NIL), but CopyGetAttnums()
also excludes generated columns from the default list.  Fix by
checking whether the attnumlist is shorter than the full TupleDesc
instead.

Bug introduced in 7dadd38cda9.

Author: Satya Narlapuram <satya.narlapuram@gmail.com>
Reviewed-by: Jian He <jian.universality@gmail.com>
Discussion: https://postgr.es/m/CAHg+QDcfpGDoPL3fvfjXRtfn=fny6DdJR6BAy6TpS1Xj2EZfXA@mail.gmail.com

src/backend/commands/copyto.c
src/test/regress/expected/generated_stored.out
src/test/regress/expected/generated_virtual.out
src/test/regress/sql/generated_stored.sql
src/test/regress/sql/generated_virtual.sql

index f0e0147c665014da04e8c7205942622df27ce5c9..85d15353647a666cf0b174eebff9a93650e87c1a 100644 (file)
@@ -1033,7 +1033,7 @@ BeginCopyTo(ParseState *pstate,
        {
                cstate->json_buf = makeStringInfo();
 
-               if (attnamelist != NIL && rel)
+               if (rel && list_length(cstate->attnumlist) < tupDesc->natts)
                {
                        int                     natts = list_length(cstate->attnumlist);
                        TupleDesc       resultDesc;
index 43cddeac373e829450880f0053c6583a7f1d47aa..4d329c60994cae46d3d72c2503df4098e3e009fa 100644 (file)
@@ -541,6 +541,12 @@ SELECT * FROM gtest3 ORDER BY a;
  4 | 12
 (4 rows)
 
+-- COPY JSON should exclude generated columns, same as text/CSV
+COPY gtest1 TO stdout WITH (FORMAT json);
+{"a":1}
+{"a":2}
+{"a":3}
+{"a":4}
 -- null values
 CREATE TABLE gtest2 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (NULL) STORED);
 INSERT INTO gtest2 VALUES (1);
index 234061fa1f7febb4ba8a95dc78beb00b7f7a71f6..fc41c480d40eeaea8afff198dea2120325d91f3a 100644 (file)
@@ -535,6 +535,12 @@ SELECT * FROM gtest3 ORDER BY a;
  4 | 12
 (4 rows)
 
+-- COPY JSON should exclude generated columns, same as text/CSV
+COPY gtest1 TO stdout WITH (FORMAT json);
+{"a":1}
+{"a":2}
+{"a":3}
+{"a":4}
 -- null values
 CREATE TABLE gtest2 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (NULL) VIRTUAL);
 INSERT INTO gtest2 VALUES (1);
index 280021d79b75bbe71c3eeced87d545b0505056df..1064839dcd2fabfe7036193c2c6957c229498625 100644 (file)
@@ -239,6 +239,9 @@ COPY gtest3 (a, b) FROM stdin;
 
 SELECT * FROM gtest3 ORDER BY a;
 
+-- COPY JSON should exclude generated columns, same as text/CSV
+COPY gtest1 TO stdout WITH (FORMAT json);
+
 -- null values
 CREATE TABLE gtest2 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (NULL) STORED);
 INSERT INTO gtest2 VALUES (1);
index 4d9ad3c5dcaa4d801112161799d398eb4d2c8d10..9b32413e3a983525976345251bbb3425db72ec86 100644 (file)
@@ -239,6 +239,9 @@ COPY gtest3 (a, b) FROM stdin;
 
 SELECT * FROM gtest3 ORDER BY a;
 
+-- COPY JSON should exclude generated columns, same as text/CSV
+COPY gtest1 TO stdout WITH (FORMAT json);
+
 -- null values
 CREATE TABLE gtest2 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (NULL) VIRTUAL);
 INSERT INTO gtest2 VALUES (1);