COPY TO FORMAT JSON: respect column list order

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
This commit is contained in:
Andrew Dunstan 2026-06-27 16:48:35 -04:00
parent a40fdf6588
commit effb923d9d
3 changed files with 27 additions and 5 deletions

View 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)

View 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}

View 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);