]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
COPY TO FORMAT JSON: respect column list order
authorAndrew Dunstan <andrew@dunslane.net>
Sat, 27 Jun 2026 20:48:35 +0000 (16:48 -0400)
committerAndrew Dunstan <andrew@dunslane.net>
Sat, 27 Jun 2026 21:50:45 +0000 (17:50 -0400)
When COPY TO with FORMAT json is given an explicit column list that
names all columns in a different order, the JSON output incorrectly
used the table's physical column order instead of the requested order.

This happened because BeginCopyTo() only built a restricted TupleDesc
when list_length(attnumlist) < tupDesc->natts.  When all columns are
listed (just reordered), this condition was false and no projected
TupleDesc was built, causing CopyToJsonOneRow() to emit columns in
physical order.

Fix by also building the projected TupleDesc when an explicit column
list was provided (attnamelist != NIL), even if it names all columns.

Author: Baji Shaik <baji.pgdev@gmail.com>
Reviewed-by: Andrew Dunstan <andrew@dunslane.net>
Discussion: https://postgr.es/m/CA+fm-ROd4cNKM524n6EdgtZ9xOzOHJDNv8J_9Mvr2+2t1qWSDw@mail.gmail.com

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

index 6755bb698de738c9715cd73ad6ef809a11cbacf8..d30cad019c739fa3e962dbd0db4718cb34a5ebf5 100644 (file)
@@ -1051,15 +1051,19 @@ BeginCopyTo(ParseState *pstate,
        {
                cstate->json_buf = makeStringInfo();
 
-               if (rel && list_length(cstate->attnumlist) < tupDesc->natts)
+               /*
+                * Build a projected TupleDesc describing only the selected columns
+                * so that composite_to_json() emits the right column names and
+                * types; needed when an explicit column list was given (possibly
+                * with a different column order) or when generated columns are
+                * excluded from the output.
+                */
+               if (rel && (attnamelist != NIL ||
+                                       list_length(cstate->attnumlist) < tupDesc->natts))
                {
                        int                     natts = list_length(cstate->attnumlist);
                        TupleDesc       resultDesc;
 
-                       /*
-                        * Build a TupleDesc describing only the selected columns so that
-                        * composite_to_json() emits the right column names and types.
-                        */
                        resultDesc = CreateTemplateTupleDesc(natts);
 
                        foreach_int(attnum, cstate->attnumlist)
index 37498cdd6e7aaea7829b09c6e76bec875738a6da..ace38225623afd928c16d8dc49dae5c3127576ac 100644 (file)
@@ -73,6 +73,15 @@ copy copytest3 to stdout csv header;
 c1,"col with , comma","col with "" quote"
 1,a,1
 2,b,2
+-- testing explicit column order
+create temp table copytest_order (a int, b int, c int);
+copy copytest_order from stdin;
+copy copytest_order (c, b, a) to stdout;
+3      2       1
+copy copytest_order (c, b, a) to stdout (format csv);
+3,2,1
+copy copytest_order (c, b, a) to stdout (format json);
+{"c":3,"b":2,"a":1}
 --- test copying in JSON mode with various styles
 copy (select 1 union all select 2) to stdout with (format json);
 {"?column?":1}
index 094fd76c12bfbc61fe0f7a83db91cbed1d32acaf..507b822946fb203d144dffe1449daefe0115a459 100644 (file)
@@ -82,6 +82,15 @@ this is just a line full of junk that would error out if parsed
 
 copy copytest3 to stdout csv header;
 
+-- testing explicit column order
+create temp table copytest_order (a int, b int, c int);
+copy copytest_order from stdin;
+1      2       3
+\.
+copy copytest_order (c, b, a) to stdout;
+copy copytest_order (c, b, a) to stdout (format csv);
+copy copytest_order (c, b, a) to stdout (format json);
+
 --- test copying in JSON mode with various styles
 copy (select 1 union all select 2) to stdout with (format json);
 copy (select 1 as foo union all select 2) to stdout with (format json);