public inbox for [email protected]
help / color / mirror / Atom feedCOPY JSON: use trailing commas in FORCE_ARRAY output
6+ messages / 4 participants
[nested] [flat]
* COPY JSON: use trailing commas in FORCE_ARRAY output
@ 2026-05-06 06:40 Chao Li <[email protected]>
0 siblings, 3 replies; 6+ messages in thread
From: Chao Li @ 2026-05-06 06:40 UTC (permalink / raw)
To: PostgreSQL Hackers <[email protected]>
Hi,
Another issue I found with COPY TO (FORMAT json, FORCE_ARRAY) is that it places the delimiter comma at the beginning of the next line, like this:
```
evantest=# copy test_json_copy to stdout with (format json, force_array);
[
{"id":1,"name":"Alice","is_active":true,"tags":["dev","db"],"created_at":"2026-05-01"}
,{"id":2,"name":"Bob","is_active":false,"tags":["manager"],"created_at":"2026-05-02"}
,{"id":3,"name":"Charlie","is_active":null,"tags":null,"created_at":null}
,{"id":4,"name":"Special Case: \"Quotes\"","is_active":true,"tags":["a","b"],"created_at":"2026-05-04"}
]
```
I was surprised by the comma placement. It is valid JSON, but it looks quite uncommon.
For comparison, the existing json_agg() places commas at the end of the line:
```
evantest=# select json_agg(t) from (select id, name from test_json_copy) t;
json_agg
----------------------------------------------
[{"id":1,"name":"Alice"}, +
{"id":2,"name":"Bob"}, +
{"id":3,"name":"Charlie"}, +
{"id":4,"name":"Special Case: \"Quotes\""}]
(1 row)
```
If this feature had already been released, I would not think it worth changing just for formatting. But since "FORMAT json" is a new PG19 feature and has not been released yet, I think it is better to make the output to follow the more common style.
This patch changes the output to place the comma at the end of the previous line instead. The fix only adjusts how commas and newlines are emitted. It does not buffer the whole result, so it should not have any performance impact.
See the attached patch for details.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
Attachments:
[application/octet-stream] v1-0001-COPY-JSON-use-trailing-commas-in-FORCE_ARRAY-outp.patch (5.2K, 2-v1-0001-COPY-JSON-use-trailing-commas-in-FORCE_ARRAY-outp.patch)
download | inline diff:
From 6d6b9fcd3144d10cdf1853902e4054931a7a9ffe Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Wed, 6 May 2026 14:14:03 +0800
Subject: [PATCH v1] COPY JSON: use trailing commas in FORCE_ARRAY output
Change COPY TO ... FORMAT JSON, FORCE_ARRAY output to place commas at
the end of each array element line, instead of at the beginning of the
next line.
Previously, output looked like this:
```
[
{"id":1}
,{"id":2}
]
```
This is valid JSON, but it is an unusual formatting style and can be
surprising to readers. Make it emit the more conventional form instead:
```
[
{"id":1},
{"id":2}
]
```
Implement this without buffering the whole result by adjusting how JSON
rows are terminated and how the separator is emitted between rows.
Update the regression test output accordingly.
Author: Chao Li <[email protected]>
Reviewed-by:
Discussion: https://postgr.es/m/
---
src/backend/commands/copyto.c | 29 ++++++++++++++++++++++++-----
src/test/regress/expected/copy.out | 24 ++++++++++++------------
2 files changed, 36 insertions(+), 17 deletions(-)
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index 85d15353647..3dcea96b3a7 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -156,6 +156,7 @@ static void CopySendData(CopyToState cstate, const void *databuf, int datasize);
static void CopySendString(CopyToState cstate, const char *str);
static void CopySendChar(CopyToState cstate, char c);
static void CopySendEndOfRow(CopyToState cstate);
+static void CopySendTextLikeEOL(CopyToState cstate);
static void CopySendTextLikeEndOfRow(CopyToState cstate);
static void CopySendInt32(CopyToState cstate, int32 val);
static void CopySendInt16(CopyToState cstate, int16 val);
@@ -349,6 +350,8 @@ CopyToJsonEnd(CopyToState cstate)
{
if (cstate->opts.force_array)
{
+ if (cstate->json_row_delim_needed)
+ CopySendTextLikeEOL(cstate);
CopySendChar(cstate, ']');
CopySendTextLikeEndOfRow(cstate);
}
@@ -418,7 +421,11 @@ CopyToJsonOneRow(CopyToState cstate, TupleTableSlot *slot)
if (cstate->opts.force_array)
{
if (cstate->json_row_delim_needed)
+ {
CopySendChar(cstate, ',');
+ CopySendTextLikeEOL(cstate);
+ CopySendChar(cstate, ' ');
+ }
else
{
/* first row needs no delimiter */
@@ -429,7 +436,10 @@ CopyToJsonOneRow(CopyToState cstate, TupleTableSlot *slot)
CopySendData(cstate, cstate->json_buf->data, cstate->json_buf->len);
- CopySendTextLikeEndOfRow(cstate);
+ if (cstate->opts.force_array)
+ CopySendEndOfRow(cstate);
+ else
+ CopySendTextLikeEndOfRow(cstate);
}
/*
@@ -641,11 +651,10 @@ CopySendEndOfRow(CopyToState cstate)
}
/*
- * Wrapper function of CopySendEndOfRow for text, CSV, and json formats. Sends the
- * line termination and do common appropriate things for the end of row.
+ * Append the platform-appropriate line termination for text-like output.
*/
-static inline void
-CopySendTextLikeEndOfRow(CopyToState cstate)
+static void
+CopySendTextLikeEOL(CopyToState cstate)
{
switch (cstate->copy_dest)
{
@@ -664,6 +673,16 @@ CopySendTextLikeEndOfRow(CopyToState cstate)
default:
break;
}
+}
+
+/*
+ * Wrapper function of CopySendEndOfRow for text, CSV, and json formats. Sends the
+ * line termination and do common appropriate things for the end of row.
+ */
+static inline void
+CopySendTextLikeEndOfRow(CopyToState cstate)
+{
+ CopySendTextLikeEOL(cstate);
/* Now take the actions related to the end of a row */
CopySendEndOfRow(cstate);
diff --git a/src/test/regress/expected/copy.out b/src/test/regress/expected/copy.out
index 1714faab39c..3da23de8551 100644
--- a/src/test/regress/expected/copy.out
+++ b/src/test/regress/expected/copy.out
@@ -85,13 +85,13 @@ copy (values (1), (2)) TO stdout with (format json);
{"column1":2}
copy (select 1 union all select 2) to stdout with (format json, force_array true);
[
- {"?column?":1}
-,{"?column?":2}
+ {"?column?":1},
+ {"?column?":2}
]
copy (values (1), (2)) TO stdout with (format json, force_array true);
[
- {"column1":1}
-,{"column1":2}
+ {"column1":1},
+ {"column1":2}
]
copy copytest to stdout json;
{"style":"DOS","test":"abc\r\ndef","filler":1}
@@ -150,17 +150,17 @@ ERROR: COPY FORCE_ARRAY can only be used with JSON mode
-- force_array variants
copy copytest to stdout (format json, force_array);
[
- {"style":"DOS","test":"abc\r\ndef","filler":1}
-,{"style":"Unix","test":"abc\ndef","filler":2}
-,{"style":"Mac","test":"abc\rdef","filler":3}
-,{"style":"esc\\ape","test":"a\\r\\\r\\\n\\nb","filler":4}
+ {"style":"DOS","test":"abc\r\ndef","filler":1},
+ {"style":"Unix","test":"abc\ndef","filler":2},
+ {"style":"Mac","test":"abc\rdef","filler":3},
+ {"style":"esc\\ape","test":"a\\r\\\r\\\n\\nb","filler":4}
]
copy copytest(style, test) to stdout (format json, force_array true);
[
- {"style":"DOS","test":"abc\r\ndef"}
-,{"style":"Unix","test":"abc\ndef"}
-,{"style":"Mac","test":"abc\rdef"}
-,{"style":"esc\\ape","test":"a\\r\\\r\\\n\\nb"}
+ {"style":"DOS","test":"abc\r\ndef"},
+ {"style":"Unix","test":"abc\ndef"},
+ {"style":"Mac","test":"abc\rdef"},
+ {"style":"esc\\ape","test":"a\\r\\\r\\\n\\nb"}
]
copy copytest to stdout (format json, force_array false);
{"style":"DOS","test":"abc\r\ndef","filler":1}
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 6+ messages in thread
* Re: COPY JSON: use trailing commas in FORCE_ARRAY output
@ 2026-05-06 07:42 Ayush Tiwari <[email protected]>
parent: Chao Li <[email protected]>
2 siblings, 0 replies; 6+ messages in thread
From: Ayush Tiwari @ 2026-05-06 07:42 UTC (permalink / raw)
To: Chao Li <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>
Hi,
On Wed, 6 May 2026 at 12:11, Chao Li <[email protected]> wrote:
> Hi,
>
> Another issue I found with COPY TO (FORMAT json, FORCE_ARRAY) is that it
> places the delimiter comma at the beginning of the next line, like this:
> ```
> evantest=# copy test_json_copy to stdout with (format json, force_array);
> [
>
> {"id":1,"name":"Alice","is_active":true,"tags":["dev","db"],"created_at":"2026-05-01"}
>
> ,{"id":2,"name":"Bob","is_active":false,"tags":["manager"],"created_at":"2026-05-02"}
> ,{"id":3,"name":"Charlie","is_active":null,"tags":null,"created_at":null}
> ,{"id":4,"name":"Special Case:
> \"Quotes\"","is_active":true,"tags":["a","b"],"created_at":"2026-05-04"}
> ]
> ```
>
> I was surprised by the comma placement. It is valid JSON, but it looks
> quite uncommon.
>
> For comparison, the existing json_agg() places commas at the end of the
> line:
> ```
> evantest=# select json_agg(t) from (select id, name from test_json_copy) t;
> json_agg
> ----------------------------------------------
> [{"id":1,"name":"Alice"}, +
> {"id":2,"name":"Bob"}, +
> {"id":3,"name":"Charlie"}, +
> {"id":4,"name":"Special Case: \"Quotes\""}]
> (1 row)
> ```
>
> If this feature had already been released, I would not think it worth
> changing just for formatting. But since "FORMAT json" is a new PG19 feature
> and has not been released yet, I think it is better to make the output to
> follow the more common style.
>
> This patch changes the output to place the comma at the end of the
> previous line instead. The fix only adjusts how commas and newlines are
> emitted. It does not buffer the whole result, so it should not have any
> performance impact.
>
The patch looks good to me. I applied and the focused 'copy'
regression test too passed.
I agree that since FORMAT json is new for PG19, it is reasonable to adjust
the formatting before release. The implementation still streams the output
and only changes where the separator/newline are emitted.
Regards,
Ayush
^ permalink raw reply [nested|flat] 6+ messages in thread
* Re: COPY JSON: use trailing commas in FORCE_ARRAY output
@ 2026-05-06 08:23 Daniel Gustafsson <[email protected]>
parent: Chao Li <[email protected]>
2 siblings, 1 reply; 6+ messages in thread
From: Daniel Gustafsson @ 2026-05-06 08:23 UTC (permalink / raw)
To: Chao Li <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>
> On 6 May 2026, at 08:40, Chao Li <[email protected]> wrote:
> I was surprised by the comma placement. It is valid JSON, but it looks quite uncommon.
It might look uncommon, but for very wide lines it's IMHO preferrable to not
have to scroll all the way to the end of the line to know that the line is part
of an array.
> For comparison, the existing json_agg() places commas at the end of the line:
That's true, but json_agg() and COPY TO in ndjson format have different use
cases.
> ..it should not have any performance impact.
It does add branches though, and in one branch use a non-inlined function where
previously it would unconditionally use an inline function. ISTM it would
still be valuable to do performance testing given that COPY is commonly used in
performance sensitive settings.
--
Daniel Gustafsson
^ permalink raw reply [nested|flat] 6+ messages in thread
* Re: COPY JSON: use trailing commas in FORCE_ARRAY output
@ 2026-05-06 08:46 Alex Guo <[email protected]>
parent: Chao Li <[email protected]>
2 siblings, 1 reply; 6+ messages in thread
From: Alex Guo @ 2026-05-06 08:46 UTC (permalink / raw)
To: Chao Li <[email protected]>; PostgreSQL Hackers <[email protected]>
On 5/6/26 2:40 PM, Chao Li wrote:
> Hi,
>
> Another issue I found with COPY TO (FORMAT json, FORCE_ARRAY) is that it places the delimiter comma at the beginning of the next line, like this:
> ```
> evantest=# copy test_json_copy to stdout with (format json, force_array);
> [
> {"id":1,"name":"Alice","is_active":true,"tags":["dev","db"],"created_at":"2026-05-01"}
> ,{"id":2,"name":"Bob","is_active":false,"tags":["manager"],"created_at":"2026-05-02"}
> ,{"id":3,"name":"Charlie","is_active":null,"tags":null,"created_at":null}
> ,{"id":4,"name":"Special Case: \"Quotes\"","is_active":true,"tags":["a","b"],"created_at":"2026-05-04"}
> ]
> ```
>
> I was surprised by the comma placement. It is valid JSON, but it looks quite uncommon.
>
> For comparison, the existing json_agg() places commas at the end of the line:
> ```
> evantest=# select json_agg(t) from (select id, name from test_json_copy) t;
> json_agg
> ----------------------------------------------
> [{"id":1,"name":"Alice"}, +
> {"id":2,"name":"Bob"}, +
> {"id":3,"name":"Charlie"}, +
> {"id":4,"name":"Special Case: \"Quotes\""}]
> (1 row)
> ```
>
> If this feature had already been released, I would not think it worth changing just for formatting. But since "FORMAT json" is a new PG19 feature and has not been released yet, I think it is better to make the output to follow the more common style.
>
> This patch changes the output to place the comma at the end of the previous line instead. The fix only adjusts how commas and newlines are emitted. It does not buffer the whole result, so it should not have any performance impact.
>
> See the attached patch for details.
>
> Best regards,
> --
> Chao Li (Evan)
> HighGo Software Co., Ltd.
> https://www.highgo.com/
>
>
>
Thanks for the patch, I like it as I feel better with placing commas at the end of lines.
I have a small suggestion. The function name CopySendTextLikeEOL reads very similar to the existing CopySendTextLikeEndOfRow. Would it better to rename it to CopySendTextLikeLineTerminator?
Other than that, the patch looks good to me.
Regards,
Alex Guo
^ permalink raw reply [nested|flat] 6+ messages in thread
* Re: COPY JSON: use trailing commas in FORCE_ARRAY output
@ 2026-05-06 09:18 Chao Li <[email protected]>
parent: Daniel Gustafsson <[email protected]>
0 siblings, 0 replies; 6+ messages in thread
From: Chao Li @ 2026-05-06 09:18 UTC (permalink / raw)
To: Daniel Gustafsson <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>
> On May 6, 2026, at 16:23, Daniel Gustafsson <[email protected]> wrote:
>
>> On 6 May 2026, at 08:40, Chao Li <[email protected]> wrote:
>
>> I was surprised by the comma placement. It is valid JSON, but it looks quite uncommon.
>
> It might look uncommon, but for very wide lines it's IMHO preferrable to not
> have to scroll all the way to the end of the line to know that the line is part
> of an array.
>
>> For comparison, the existing json_agg() places commas at the end of the line:
>
> That's true, but json_agg() and COPY TO in ndjson format have different use
> cases.
>
>> ..it should not have any performance impact.
>
> It does add branches though, and in one branch use a non-inlined function where
> previously it would unconditionally use an inline function. ISTM it would
> still be valuable to do performance testing given that COPY is commonly used in
> performance sensitive settings.
>
Make sense. I just did a test to compare the performance between master and the patch:
For the data setup, since the patch only changes where the comma is emitted, I intentionally used a table with only one column, to minimize the cost of formatting each row:
```
DROP TABLE IF EXISTS copy_json_force_array_perf;
CREATE UNLOGGED TABLE copy_json_force_array_perf(id int);
INSERT INTO copy_json_force_array_perf
SELECT g
FROM generate_series(1, 10000000) AS g;
VACUUM ANALYZE copy_json_force_array_perf;
\timing on
```
On master:
```
evantest=# COPY copy_json_force_array_perf TO '/dev/null' WITH (FORMAT json, FORCE_ARRAY);
COPY 10000000
Time: 1208.694 ms (00:01.209)
evantest=# COPY copy_json_force_array_perf TO '/dev/null' WITH (FORMAT json, FORCE_ARRAY);
COPY 10000000
Time: 1200.203 ms (00:01.200)
evantest=# COPY copy_json_force_array_perf TO '/dev/null' WITH (FORMAT json, FORCE_ARRAY);
COPY 10000000
Time: 1238.639 ms (00:01.239)
evantest=# COPY copy_json_force_array_perf TO '/dev/null' WITH (FORMAT json, FORCE_ARRAY);
COPY 10000000
Time: 1211.344 ms (00:01.211)
evantest=# COPY copy_json_force_array_perf TO '/dev/null' WITH (FORMAT json, FORCE_ARRAY);
COPY 10000000
Time: 1252.197 ms (00:01.252)
evantest=# COPY copy_json_force_array_perf TO '/dev/null' WITH (FORMAT json, FORCE_ARRAY);
COPY 10000000
Time: 1223.510 ms (00:01.224)
evantest=# COPY copy_json_force_array_perf TO '/dev/null' WITH (FORMAT json, FORCE_ARRAY);
COPY 10000000
Time: 1212.378 ms (00:01.212)
```
Average: ~1221 ms
With the patch:
```
evantest=# COPY copy_json_force_array_perf TO '/dev/null' WITH (FORMAT json, FORCE_ARRAY);
COPY 10000000
Time: 1218.580 ms (00:01.219)
evantest=# COPY copy_json_force_array_perf TO '/dev/null' WITH (FORMAT json, FORCE_ARRAY);
COPY 10000000
Time: 1212.913 ms (00:01.213)
evantest=# COPY copy_json_force_array_perf TO '/dev/null' WITH (FORMAT json, FORCE_ARRAY);
COPY 10000000
Time: 1204.350 ms (00:01.204)
evantest=# COPY copy_json_force_array_perf TO '/dev/null' WITH (FORMAT json, FORCE_ARRAY);
COPY 10000000
Time: 1205.276 ms (00:01.205)
evantest=# COPY copy_json_force_array_perf TO '/dev/null' WITH (FORMAT json, FORCE_ARRAY);
COPY 10000000
Time: 1202.088 ms (00:01.202)
evantest=# COPY copy_json_force_array_perf TO '/dev/null' WITH (FORMAT json, FORCE_ARRAY);
COPY 10000000
Time: 1222.390 ms (00:01.222)
```
Average: ~1211 ms
The difference doesn't look quite meaningful from this test. I built with debug and asserts disabled, and compiled with -O2. The output was written to /dev/null to avoid client/network overhead. The tests ran on my MacBook M4.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
^ permalink raw reply [nested|flat] 6+ messages in thread
* Re: COPY JSON: use trailing commas in FORCE_ARRAY output
@ 2026-05-06 09:25 Chao Li <[email protected]>
parent: Alex Guo <[email protected]>
0 siblings, 0 replies; 6+ messages in thread
From: Chao Li @ 2026-05-06 09:25 UTC (permalink / raw)
To: Alex Guo <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>
> On May 6, 2026, at 16:46, Alex Guo <[email protected]> wrote:
>
>
> On 5/6/26 2:40 PM, Chao Li wrote:
>> Hi,
>>
>> Another issue I found with COPY TO (FORMAT json, FORCE_ARRAY) is that it places the delimiter comma at the beginning of the next line, like this:
>> ```
>> evantest=# copy test_json_copy to stdout with (format json, force_array);
>> [
>> {"id":1,"name":"Alice","is_active":true,"tags":["dev","db"],"created_at":"2026-05-01"}
>> ,{"id":2,"name":"Bob","is_active":false,"tags":["manager"],"created_at":"2026-05-02"}
>> ,{"id":3,"name":"Charlie","is_active":null,"tags":null,"created_at":null}
>> ,{"id":4,"name":"Special Case: \"Quotes\"","is_active":true,"tags":["a","b"],"created_at":"2026-05-04"}
>> ]
>> ```
>>
>> I was surprised by the comma placement. It is valid JSON, but it looks quite uncommon.
>>
>> For comparison, the existing json_agg() places commas at the end of the line:
>> ```
>> evantest=# select json_agg(t) from (select id, name from test_json_copy) t;
>> json_agg
>> ----------------------------------------------
>> [{"id":1,"name":"Alice"}, +
>> {"id":2,"name":"Bob"}, +
>> {"id":3,"name":"Charlie"}, +
>> {"id":4,"name":"Special Case: \"Quotes\""}]
>> (1 row)
>> ```
>>
>> If this feature had already been released, I would not think it worth changing just for formatting. But since "FORMAT json" is a new PG19 feature and has not been released yet, I think it is better to make the output to follow the more common style.
>>
>> This patch changes the output to place the comma at the end of the previous line instead. The fix only adjusts how commas and newlines are emitted. It does not buffer the whole result, so it should not have any performance impact.
>>
>> See the attached patch for details.
>>
>> Best regards,
>> --
>> Chao Li (Evan)
>> HighGo Software Co., Ltd.
>> https://www.highgo.com/
>>
>>
>>
> Thanks for the patch, I like it as I feel better with placing commas at the end of lines.
>
> I have a small suggestion. The function name CopySendTextLikeEOL reads very similar to the existing CopySendTextLikeEndOfRow. Would it better to rename it to CopySendTextLikeLineTerminator?
>
> Other than that, the patch looks good to me.
>
> Regards,
> Alex Guo
Thanks for the suggestion, I take it.
PFA v2 - Renamed CopySendTextLikeEOL to CopySendTextLikeLineTerminator.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
Attachments:
[application/octet-stream] v2-0001-COPY-JSON-use-trailing-commas-in-FORCE_ARRAY-outp.patch (5.4K, 2-v2-0001-COPY-JSON-use-trailing-commas-in-FORCE_ARRAY-outp.patch)
download | inline diff:
From 4f2151348d89f9417d161729b5b717d32804d702 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Wed, 6 May 2026 14:14:03 +0800
Subject: [PATCH v2] COPY JSON: use trailing commas in FORCE_ARRAY output
Change COPY TO ... FORMAT JSON, FORCE_ARRAY output to place commas at
the end of each array element line, instead of at the beginning of the
next line.
Previously, output looked like this:
```
[
{"id":1}
,{"id":2}
]
```
This is valid JSON, but it is an unusual formatting style and can be
surprising to readers. Make it emit the more conventional form instead:
```
[
{"id":1},
{"id":2}
]
```
Implement this without buffering the whole result by adjusting how JSON
rows are terminated and how the separator is emitted between rows.
Update the regression test output accordingly.
Author: Chao Li <[email protected]>
Reviewed-by: Ayush Tiwari <[email protected]>
Reviewed-by: Daniel Gustafsson <[email protected]>
Reviewed-by: Alex Guo <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
src/backend/commands/copyto.c | 29 ++++++++++++++++++++++++-----
src/test/regress/expected/copy.out | 24 ++++++++++++------------
2 files changed, 36 insertions(+), 17 deletions(-)
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index 85d15353647..e98a15dcd64 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -156,6 +156,7 @@ static void CopySendData(CopyToState cstate, const void *databuf, int datasize);
static void CopySendString(CopyToState cstate, const char *str);
static void CopySendChar(CopyToState cstate, char c);
static void CopySendEndOfRow(CopyToState cstate);
+static void CopySendTextLikeLineTerminator(CopyToState cstate);
static void CopySendTextLikeEndOfRow(CopyToState cstate);
static void CopySendInt32(CopyToState cstate, int32 val);
static void CopySendInt16(CopyToState cstate, int16 val);
@@ -349,6 +350,8 @@ CopyToJsonEnd(CopyToState cstate)
{
if (cstate->opts.force_array)
{
+ if (cstate->json_row_delim_needed)
+ CopySendTextLikeLineTerminator(cstate);
CopySendChar(cstate, ']');
CopySendTextLikeEndOfRow(cstate);
}
@@ -418,7 +421,11 @@ CopyToJsonOneRow(CopyToState cstate, TupleTableSlot *slot)
if (cstate->opts.force_array)
{
if (cstate->json_row_delim_needed)
+ {
CopySendChar(cstate, ',');
+ CopySendTextLikeLineTerminator(cstate);
+ CopySendChar(cstate, ' ');
+ }
else
{
/* first row needs no delimiter */
@@ -429,7 +436,10 @@ CopyToJsonOneRow(CopyToState cstate, TupleTableSlot *slot)
CopySendData(cstate, cstate->json_buf->data, cstate->json_buf->len);
- CopySendTextLikeEndOfRow(cstate);
+ if (cstate->opts.force_array)
+ CopySendEndOfRow(cstate);
+ else
+ CopySendTextLikeEndOfRow(cstate);
}
/*
@@ -641,11 +651,10 @@ CopySendEndOfRow(CopyToState cstate)
}
/*
- * Wrapper function of CopySendEndOfRow for text, CSV, and json formats. Sends the
- * line termination and do common appropriate things for the end of row.
+ * Append the platform-appropriate line termination for text-like output.
*/
-static inline void
-CopySendTextLikeEndOfRow(CopyToState cstate)
+static void
+CopySendTextLikeLineTerminator(CopyToState cstate)
{
switch (cstate->copy_dest)
{
@@ -664,6 +673,16 @@ CopySendTextLikeEndOfRow(CopyToState cstate)
default:
break;
}
+}
+
+/*
+ * Wrapper function of CopySendEndOfRow for text, CSV, and json formats. Sends the
+ * line termination and do common appropriate things for the end of row.
+ */
+static inline void
+CopySendTextLikeEndOfRow(CopyToState cstate)
+{
+ CopySendTextLikeLineTerminator(cstate);
/* Now take the actions related to the end of a row */
CopySendEndOfRow(cstate);
diff --git a/src/test/regress/expected/copy.out b/src/test/regress/expected/copy.out
index 1714faab39c..3da23de8551 100644
--- a/src/test/regress/expected/copy.out
+++ b/src/test/regress/expected/copy.out
@@ -85,13 +85,13 @@ copy (values (1), (2)) TO stdout with (format json);
{"column1":2}
copy (select 1 union all select 2) to stdout with (format json, force_array true);
[
- {"?column?":1}
-,{"?column?":2}
+ {"?column?":1},
+ {"?column?":2}
]
copy (values (1), (2)) TO stdout with (format json, force_array true);
[
- {"column1":1}
-,{"column1":2}
+ {"column1":1},
+ {"column1":2}
]
copy copytest to stdout json;
{"style":"DOS","test":"abc\r\ndef","filler":1}
@@ -150,17 +150,17 @@ ERROR: COPY FORCE_ARRAY can only be used with JSON mode
-- force_array variants
copy copytest to stdout (format json, force_array);
[
- {"style":"DOS","test":"abc\r\ndef","filler":1}
-,{"style":"Unix","test":"abc\ndef","filler":2}
-,{"style":"Mac","test":"abc\rdef","filler":3}
-,{"style":"esc\\ape","test":"a\\r\\\r\\\n\\nb","filler":4}
+ {"style":"DOS","test":"abc\r\ndef","filler":1},
+ {"style":"Unix","test":"abc\ndef","filler":2},
+ {"style":"Mac","test":"abc\rdef","filler":3},
+ {"style":"esc\\ape","test":"a\\r\\\r\\\n\\nb","filler":4}
]
copy copytest(style, test) to stdout (format json, force_array true);
[
- {"style":"DOS","test":"abc\r\ndef"}
-,{"style":"Unix","test":"abc\ndef"}
-,{"style":"Mac","test":"abc\rdef"}
-,{"style":"esc\\ape","test":"a\\r\\\r\\\n\\nb"}
+ {"style":"DOS","test":"abc\r\ndef"},
+ {"style":"Unix","test":"abc\ndef"},
+ {"style":"Mac","test":"abc\rdef"},
+ {"style":"esc\\ape","test":"a\\r\\\r\\\n\\nb"}
]
copy copytest to stdout (format json, force_array false);
{"style":"DOS","test":"abc\r\ndef","filler":1}
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 6+ messages in thread
end of thread, other threads:[~2026-05-06 09:25 UTC | newest]
Thread overview: 6+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-05-06 06:40 COPY JSON: use trailing commas in FORCE_ARRAY output Chao Li <[email protected]>
2026-05-06 07:42 ` Ayush Tiwari <[email protected]>
2026-05-06 08:23 ` Daniel Gustafsson <[email protected]>
2026-05-06 09:18 ` Chao Li <[email protected]>
2026-05-06 08:46 ` Alex Guo <[email protected]>
2026-05-06 09:25 ` Chao Li <[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