Skip to content

Commit c80d807

Browse files
jianhe-funCommitfest Bot
authored andcommitted
json format for COPY TO
JSON format is only supported with the COPY TO operation. It is incompatible with options such as HEADER, DEFAULT, NULL, DELIMITER, and several others. This has been thoroughly tested in src/test/regress/sql/copy.sql The CopyFormat enum was originally contributed by Joel Jacobson [email protected], later refactored by Jian He to address various issues, and further adapted by Junwang Zhao to support the newly introduced CopyToRoutine struct (commit 2e4127b). Author: Joe Conway <[email protected]> Reviewed-by: "Andrey M. Borodin" <[email protected]>, Reviewed-by: Dean Rasheed <[email protected]>, Reviewed-by: Daniel Verite <[email protected]>, Reviewed-by: Andrew Dunstan <[email protected]>, Reviewed-by: Davin Shearer <[email protected]>, Reviewed-by: Masahiko Sawada <[email protected]>, Reviewed-by: Alvaro Herrera <[email protected]> discussion: https://postgr.es/m/CALvfUkBxTYy5uWPFVwpk_7ii2zgT07t3d-yR_cy4sfrrLU%3Dkcg%40mail.gmail.com discussion: https://postgr.es/m/[email protected]
1 parent 1468ecb commit c80d807

File tree

10 files changed

+268
-34
lines changed

10 files changed

+268
-34
lines changed

doc/src/sgml/ref/copy.sgml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,15 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable
228228
Selects the data format to be read or written:
229229
<literal>text</literal>,
230230
<literal>csv</literal> (Comma Separated Values),
231+
<literal>json</literal> (JavaScript Object Notation),
231232
or <literal>binary</literal>.
232233
The default is <literal>text</literal>.
233234
See <xref linkend="sql-copy-file-formats"/> below for details.
234235
</para>
236+
<para>
237+
The <literal>json</literal> option is allowed only in
238+
<command>COPY TO</command>.
239+
</para>
235240
</listitem>
236241
</varlistentry>
237242

@@ -266,7 +271,7 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable
266271
(line) of the file. The default is a tab character in text format,
267272
a comma in <literal>CSV</literal> format.
268273
This must be a single one-byte character.
269-
This option is not allowed when using <literal>binary</literal> format.
274+
This option is not allowed when using <literal>binary</literal> or <literal>json</literal> format.
270275
</para>
271276
</listitem>
272277
</varlistentry>
@@ -280,7 +285,7 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable
280285
string in <literal>CSV</literal> format. You might prefer an
281286
empty string even in text format for cases where you don't want to
282287
distinguish nulls from empty strings.
283-
This option is not allowed when using <literal>binary</literal> format.
288+
This option is not allowed when using <literal>binary</literal> or <literal>json</literal> format.
284289
</para>
285290

286291
<note>
@@ -303,7 +308,7 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable
303308
is found in the input file, the default value of the corresponding column
304309
will be used.
305310
This option is allowed only in <command>COPY FROM</command>, and only when
306-
not using <literal>binary</literal> format.
311+
not using <literal>binary</literal> or <literal>json</literal> format.
307312
</para>
308313
</listitem>
309314
</varlistentry>
@@ -330,7 +335,7 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable
330335
<command>COPY FROM</command> commands.
331336
</para>
332337
<para>
333-
This option is not allowed when using <literal>binary</literal> format.
338+
This option is not allowed when using <literal>binary</literal> or <literal>json</literal> format.
334339
</para>
335340
</listitem>
336341
</varlistentry>

src/backend/commands/copy.c

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,8 @@ ProcessCopyOptions(ParseState *pstate,
585585
opts_out->format = COPY_FORMAT_CSV;
586586
else if (strcmp(fmt, "binary") == 0)
587587
opts_out->format = COPY_FORMAT_BINARY;
588+
else if (strcmp(fmt, "json") == 0)
589+
opts_out->format = COPY_FORMAT_JSON;
588590
else
589591
ereport(ERROR,
590592
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -744,21 +746,42 @@ ProcessCopyOptions(ParseState *pstate,
744746
* Check for incompatible options (must do these three before inserting
745747
* defaults)
746748
*/
747-
if (opts_out->format == COPY_FORMAT_BINARY && opts_out->delim)
748-
ereport(ERROR,
749-
(errcode(ERRCODE_SYNTAX_ERROR),
750-
/*- translator: %s is the name of a COPY option, e.g. ON_ERROR */
751-
errmsg("cannot specify %s in BINARY mode", "DELIMITER")));
749+
if (opts_out->delim)
750+
{
751+
if (opts_out->format == COPY_FORMAT_BINARY)
752+
ereport(ERROR,
753+
errcode(ERRCODE_SYNTAX_ERROR),
754+
/*- translator: %s is the name of a COPY option, e.g. ON_ERROR */
755+
errmsg("cannot specify %s in BINARY mode", "DELIMITER"));
756+
else if (opts_out->format == COPY_FORMAT_JSON)
757+
ereport(ERROR,
758+
errcode(ERRCODE_SYNTAX_ERROR),
759+
errmsg("cannot specify %s in JSON mode", "DELIMITER"));
760+
}
752761

753-
if (opts_out->format == COPY_FORMAT_BINARY && opts_out->null_print)
754-
ereport(ERROR,
755-
(errcode(ERRCODE_SYNTAX_ERROR),
756-
errmsg("cannot specify %s in BINARY mode", "NULL")));
762+
if (opts_out->null_print)
763+
{
764+
if (opts_out->format == COPY_FORMAT_BINARY)
765+
ereport(ERROR,
766+
errcode(ERRCODE_SYNTAX_ERROR),
767+
errmsg("cannot specify %s in BINARY mode", "NULL"));
768+
else if (opts_out->format == COPY_FORMAT_JSON)
769+
ereport(ERROR,
770+
errcode(ERRCODE_SYNTAX_ERROR),
771+
errmsg("cannot specify %s in JSON mode", "NULL"));
772+
}
757773

758-
if (opts_out->format == COPY_FORMAT_BINARY && opts_out->default_print)
759-
ereport(ERROR,
760-
(errcode(ERRCODE_SYNTAX_ERROR),
761-
errmsg("cannot specify %s in BINARY mode", "DEFAULT")));
774+
if (opts_out->default_print)
775+
{
776+
if (opts_out->format == COPY_FORMAT_BINARY)
777+
ereport(ERROR,
778+
errcode(ERRCODE_SYNTAX_ERROR),
779+
errmsg("cannot specify %s in BINARY mode", "DEFAULT"));
780+
else if (opts_out->format == COPY_FORMAT_JSON)
781+
ereport(ERROR,
782+
errcode(ERRCODE_SYNTAX_ERROR),
783+
errmsg("cannot specify %s in JSON mode", "DEFAULT"));
784+
}
762785

763786
/* Set defaults for omitted options */
764787
if (!opts_out->delim)
@@ -824,11 +847,18 @@ ProcessCopyOptions(ParseState *pstate,
824847
errmsg("COPY delimiter cannot be \"%s\"", opts_out->delim)));
825848

826849
/* Check header */
827-
if (opts_out->format == COPY_FORMAT_BINARY && opts_out->header_line != COPY_HEADER_FALSE)
828-
ereport(ERROR,
829-
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
830-
/*- translator: %s is the name of a COPY option, e.g. ON_ERROR */
831-
errmsg("cannot specify %s in BINARY mode", "HEADER")));
850+
if (opts_out->header_line != COPY_HEADER_FALSE)
851+
{
852+
if (opts_out->format == COPY_FORMAT_BINARY)
853+
ereport(ERROR,
854+
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
855+
/*- translator: %s is the name of a COPY option, e.g. ON_ERROR */
856+
errmsg("cannot specify %s in BINARY mode", "HEADER"));
857+
else if(opts_out->format == COPY_FORMAT_JSON)
858+
ereport(ERROR,
859+
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
860+
errmsg("cannot specify %s in JSON mode", "HEADER"));
861+
}
832862

833863
/* Check quote */
834864
if (opts_out->format != COPY_FORMAT_CSV && opts_out->quote != NULL)
@@ -932,6 +962,12 @@ ProcessCopyOptions(ParseState *pstate,
932962
errmsg("COPY %s cannot be used with %s", "FREEZE",
933963
"COPY TO")));
934964

965+
/* Check json format */
966+
if (opts_out->format == COPY_FORMAT_JSON && is_from)
967+
ereport(ERROR,
968+
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
969+
errmsg("COPY %s mode cannot be used with %s", "json", "COPY FROM"));
970+
935971
if (opts_out->default_print)
936972
{
937973
if (!is_from)

src/backend/commands/copyto.c

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@
2626
#include "executor/execdesc.h"
2727
#include "executor/executor.h"
2828
#include "executor/tuptable.h"
29+
#include "funcapi.h"
2930
#include "libpq/libpq.h"
3031
#include "libpq/pqformat.h"
3132
#include "mb/pg_wchar.h"
3233
#include "miscadmin.h"
3334
#include "pgstat.h"
3435
#include "storage/fd.h"
3536
#include "tcop/tcopprot.h"
37+
#include "utils/json.h"
3638
#include "utils/lsyscache.h"
3739
#include "utils/memutils.h"
3840
#include "utils/rel.h"
@@ -130,6 +132,7 @@ static void CopyToCSVOneRow(CopyToState cstate, TupleTableSlot *slot);
130132
static void CopyToTextLikeOneRow(CopyToState cstate, TupleTableSlot *slot,
131133
bool is_csv);
132134
static void CopyToTextLikeEnd(CopyToState cstate);
135+
static void CopyToJsonOneRow(CopyToState cstate, TupleTableSlot *slot);
133136
static void CopyToBinaryStart(CopyToState cstate, TupleDesc tupDesc);
134137
static void CopyToBinaryOutFunc(CopyToState cstate, Oid atttypid, FmgrInfo *finfo);
135138
static void CopyToBinaryOneRow(CopyToState cstate, TupleTableSlot *slot);
@@ -149,7 +152,7 @@ static void CopySendInt16(CopyToState cstate, int16 val);
149152
/*
150153
* COPY TO routines for built-in formats.
151154
*
152-
* CSV and text formats share the same TextLike routines except for the
155+
* CSV and text, json formats share the same TextLike routines except for the
153156
* one-row callback.
154157
*/
155158

@@ -169,6 +172,14 @@ static const CopyToRoutine CopyToRoutineCSV = {
169172
.CopyToEnd = CopyToTextLikeEnd,
170173
};
171174

175+
/* json format */
176+
static const CopyToRoutine CopyToRoutineJson = {
177+
.CopyToStart = CopyToTextLikeStart,
178+
.CopyToOutFunc = CopyToTextLikeOutFunc,
179+
.CopyToOneRow = CopyToJsonOneRow,
180+
.CopyToEnd = CopyToTextLikeEnd,
181+
};
182+
172183
/* binary format */
173184
static const CopyToRoutine CopyToRoutineBinary = {
174185
.CopyToStart = CopyToBinaryStart,
@@ -185,12 +196,14 @@ CopyToGetRoutine(const CopyFormatOptions *opts)
185196
return &CopyToRoutineCSV;
186197
else if (opts->format == COPY_FORMAT_BINARY)
187198
return &CopyToRoutineBinary;
199+
else if (opts->format == COPY_FORMAT_JSON)
200+
return &CopyToRoutineJson;
188201

189202
/* default is text */
190203
return &CopyToRoutineText;
191204
}
192205

193-
/* Implementation of the start callback for text and CSV formats */
206+
/* Implementation of the start callback for text, CSV, and json formats */
194207
static void
195208
CopyToTextLikeStart(CopyToState cstate, TupleDesc tupDesc)
196209
{
@@ -209,6 +222,8 @@ CopyToTextLikeStart(CopyToState cstate, TupleDesc tupDesc)
209222
ListCell *cur;
210223
bool hdr_delim = false;
211224

225+
Assert(cstate->opts.format != COPY_FORMAT_JSON);
226+
212227
foreach(cur, cstate->attnumlist)
213228
{
214229
int attnum = lfirst_int(cur);
@@ -231,7 +246,7 @@ CopyToTextLikeStart(CopyToState cstate, TupleDesc tupDesc)
231246
}
232247

233248
/*
234-
* Implementation of the outfunc callback for text and CSV formats. Assign
249+
* Implementation of the outfunc callback for text, CSV, and json formats. Assign
235250
* the output function data to the given *finfo.
236251
*/
237252
static void
@@ -304,13 +319,46 @@ CopyToTextLikeOneRow(CopyToState cstate,
304319
CopySendTextLikeEndOfRow(cstate);
305320
}
306321

307-
/* Implementation of the end callback for text and CSV formats */
322+
/* Implementation of the end callback for text, CSV, and json formats */
308323
static void
309324
CopyToTextLikeEnd(CopyToState cstate)
310325
{
311326
/* Nothing to do here */
312327
}
313328

329+
/* Implementation of per-row callback for json format */
330+
static void
331+
CopyToJsonOneRow(CopyToState cstate, TupleTableSlot *slot)
332+
{
333+
Datum rowdata;
334+
StringInfo result;
335+
336+
/*
337+
* If COPY TO source data come from query rather than plain table, we need
338+
* copy CopyToState->QueryDesc->TupleDesc to slot->tts_tupleDescriptor.
339+
* This is necessary because the slot's TupleDesc may change during query
340+
* execution, and we depend on it when calling composite_to_json.
341+
*/
342+
if (!cstate->rel)
343+
{
344+
memcpy(TupleDescAttr(slot->tts_tupleDescriptor, 0),
345+
TupleDescAttr(cstate->queryDesc->tupDesc, 0),
346+
cstate->queryDesc->tupDesc->natts * sizeof(FormData_pg_attribute));
347+
348+
for (int i = 0; i < cstate->queryDesc->tupDesc->natts; i++)
349+
populate_compact_attribute(slot->tts_tupleDescriptor, i);
350+
351+
BlessTupleDesc(slot->tts_tupleDescriptor);
352+
}
353+
rowdata = ExecFetchSlotHeapTupleDatum(slot);
354+
result = makeStringInfo();
355+
composite_to_json(rowdata, result, false);
356+
357+
CopySendData(cstate, result->data, result->len);
358+
359+
CopySendTextLikeEndOfRow(cstate);
360+
}
361+
314362
/*
315363
* Implementation of the start callback for binary format. Send a header
316364
* for a binary copy.
@@ -402,9 +450,21 @@ SendCopyBegin(CopyToState cstate)
402450

403451
pq_beginmessage(&buf, PqMsg_CopyOutResponse);
404452
pq_sendbyte(&buf, format); /* overall format */
405-
pq_sendint16(&buf, natts);
406-
for (i = 0; i < natts; i++)
407-
pq_sendint16(&buf, format); /* per-column formats */
453+
if (cstate->opts.format != COPY_FORMAT_JSON)
454+
{
455+
pq_sendint16(&buf, natts);
456+
for (i = 0; i < natts; i++)
457+
pq_sendint16(&buf, format); /* per-column formats */
458+
}
459+
else
460+
{
461+
/*
462+
* JSON format is always one non-binary column
463+
*/
464+
pq_sendint16(&buf, 1);
465+
pq_sendint16(&buf, 0);
466+
}
467+
408468
pq_endmessage(&buf);
409469
cstate->copy_dest = COPY_FRONTEND;
410470
}
@@ -504,7 +564,7 @@ CopySendEndOfRow(CopyToState cstate)
504564
}
505565

506566
/*
507-
* Wrapper function of CopySendEndOfRow for text and CSV formats. Sends the
567+
* Wrapper function of CopySendEndOfRow for text, CSV, and json formats. Sends the
508568
* line termination and do common appropriate things for the end of row.
509569
*/
510570
static inline void

src/backend/parser/gram.y

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3557,6 +3557,10 @@ copy_opt_item:
35573557
{
35583558
$$ = makeDefElem("format", (Node *) makeString("csv"), @1);
35593559
}
3560+
| JSON
3561+
{
3562+
$$ = makeDefElem("format", (Node *) makeString("json"), @1);
3563+
}
35603564
| HEADER_P
35613565
{
35623566
$$ = makeDefElem("header", (Node *) makeBoolean(true), @1);
@@ -3639,6 +3643,10 @@ copy_generic_opt_elem:
36393643
{
36403644
$$ = makeDefElem($1, $2, @1);
36413645
}
3646+
| FORMAT_LA copy_generic_opt_arg
3647+
{
3648+
$$ = makeDefElem("format", $2, @1);
3649+
}
36423650
;
36433651

36443652
copy_generic_opt_arg:

src/backend/utils/adt/json.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,6 @@ typedef struct JsonAggState
8686
JsonUniqueBuilderState unique_check;
8787
} JsonAggState;
8888

89-
static void composite_to_json(Datum composite, StringInfo result,
90-
bool use_line_feeds);
9189
static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims,
9290
const Datum *vals, const bool *nulls, int *valcount,
9391
JsonTypeCategory tcategory, Oid outfuncoid,
@@ -517,8 +515,9 @@ array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds)
517515

518516
/*
519517
* Turn a composite / record into JSON.
518+
* Exported so COPY TO can use it.
520519
*/
521-
static void
520+
void
522521
composite_to_json(Datum composite, StringInfo result, bool use_line_feeds)
523522
{
524523
HeapTupleHeader td;

src/bin/psql/tab-complete.in.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3377,7 +3377,7 @@ match_previous_words(int pattern_id,
33773377
/* Complete COPY <sth> FROM|TO [PROGRAM] <sth> WITH (FORMAT */
33783378
else if (Matches("COPY|\\copy", MatchAny, "FROM|TO", MatchAnyExcept("PROGRAM"), "WITH", "(", "FORMAT") ||
33793379
Matches("COPY|\\copy", MatchAny, "FROM|TO", "PROGRAM", MatchAny, "WITH", "(", "FORMAT"))
3380-
COMPLETE_WITH("binary", "csv", "text");
3380+
COMPLETE_WITH("binary", "csv", "text", "json");
33813381

33823382
/* Complete COPY <sth> FROM [PROGRAM] filename WITH (ON_ERROR */
33833383
else if (Matches("COPY|\\copy", MatchAny, "FROM", MatchAnyExcept("PROGRAM"), "WITH", "(", "ON_ERROR") ||

src/include/commands/copy.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ typedef enum CopyFormat
5656
COPY_FORMAT_TEXT = 0,
5757
COPY_FORMAT_BINARY,
5858
COPY_FORMAT_CSV,
59+
COPY_FORMAT_JSON,
5960
} CopyFormat;
6061

6162
/*

src/include/utils/json.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
#include "lib/stringinfo.h"
1818

1919
/* functions in json.c */
20+
extern void composite_to_json(Datum composite, StringInfo result,
21+
bool use_line_feeds);
2022
extern void escape_json(StringInfo buf, const char *str);
2123
extern void escape_json_with_len(StringInfo buf, const char *str, int len);
2224
extern void escape_json_text(StringInfo buf, const text *txt);

0 commit comments

Comments
 (0)