From 10338f10221e095127e7671776a478565add4df4 Mon Sep 17 00:00:00 2001 From: jian he Date: Mon, 19 Feb 2024 10:40:53 +0800 Subject: [PATCH v9 2/2] Add option force_array for COPY TO JSON fomrat. make add opening brackets and close brackets for the whole json output. also, separate each json record with comma. discussion: https://postgr.es/m/CALvfUkBxTYy5uWPFVwpk_7ii2zgT07t3d-yR_cy4sfrrLU%3Dkcg%40mail.gmail.com discussion: https://postgr.es/m/6a04628d-0d53-41d9-9e35-5a8dc302c34c@joeconway.com --- doc/src/sgml/ref/copy.sgml | 14 ++++++++++++++ src/backend/commands/copy.c | 17 +++++++++++++++++ src/backend/commands/copyto.c | 28 ++++++++++++++++++++++++++++ src/include/commands/copy.h | 1 + src/test/regress/expected/copy.out | 24 ++++++++++++++++++++++++ src/test/regress/sql/copy.sql | 9 +++++++++ 6 files changed, 93 insertions(+) diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml index ef9e4729..83f4a43f 100644 --- a/doc/src/sgml/ref/copy.sgml +++ b/doc/src/sgml/ref/copy.sgml @@ -43,6 +43,7 @@ COPY { table_name [ ( column_name [, ...] ) | * } FORCE_NOT_NULL { ( column_name [, ...] ) | * } FORCE_NULL { ( column_name [, ...] ) | * } + FORCE_ARRAY [ boolean ] ON_ERROR 'error_action' ENCODING 'encoding_name' @@ -386,6 +387,19 @@ COPY { table_name [ ( + + FORCE_ARRAY + + + Force output of square brackets as array decorations at the beginning + and end of output, and commas between the rows. It is allowed only in + COPY TO, and only when using + JSON format. The default is + false. + + + + ON_ERROR diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 5d5b733d..89373119 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -456,6 +456,7 @@ ProcessCopyOptions(ParseState *pstate, bool freeze_specified = false; bool header_specified = false; bool on_error_specified = false; + bool force_array_specified = false; ListCell *option; /* Support external use for option sanity checking */ @@ -610,6 +611,13 @@ ProcessCopyOptions(ParseState *pstate, defel->defname), parser_errposition(pstate, defel->location))); } + else if (strcmp(defel->defname, "force_array") == 0) + { + if (force_array_specified) + errorConflictingDefElem(defel, pstate); + force_array_specified = true; + opts_out->force_array = defGetBoolean(defel); + } else if (strcmp(defel->defname, "on_error") == 0) { if (on_error_specified) @@ -806,6 +814,15 @@ ProcessCopyOptions(ParseState *pstate, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot use JSON mode in COPY FROM"))); + if (!opts_out->json_mode && opts_out->force_array) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("COPY FORCE_ARRAY requires JSON mode"))); + if (!opts_out->json_mode && force_array_specified) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("COPY FORCE_ARRAY only available in JSON mode"))); + if (opts_out->default_print) { if (!is_from) diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index c948a431..419e7e63 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -860,6 +860,16 @@ DoCopyTo(CopyToState cstate) CopySendEndOfRow(cstate); } + /* + * If JSON has been requested, and FORCE_ARRAY has been specified send + * the opening bracket. + */ + if (cstate->opts.json_mode && cstate->opts.force_array) + { + CopySendChar(cstate, '['); + CopySendEndOfRow(cstate); + } + } if (cstate->rel) @@ -907,6 +917,15 @@ DoCopyTo(CopyToState cstate) CopySendEndOfRow(cstate); } + /* + * If JSON has been requested, and FORCE_ARRAY has been specified send the + * closing bracket. + */ + if (cstate->opts.json_mode && cstate->opts.force_array) + { + CopySendChar(cstate, ']'); + CopySendEndOfRow(cstate); + } MemoryContextDelete(cstate->rowcontext); if (fe_copy) @@ -1012,6 +1031,15 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot) result = makeStringInfo(); composite_to_json(rowdata, result, false); + if (cstate->json_row_delim_needed && cstate->opts.force_array) + CopySendChar(cstate, ','); + else if (cstate->opts.force_array) + { + /* first row needs no delimiter */ + CopySendChar(cstate, ' '); + cstate->json_row_delim_needed = true; + } + CopySendData(cstate, result->data, result->len); } diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h index f591b613..51656eec 100644 --- a/src/include/commands/copy.h +++ b/src/include/commands/copy.h @@ -72,6 +72,7 @@ typedef struct CopyFormatOptions List *force_null; /* list of column names */ bool force_null_all; /* FORCE_NULL *? */ bool *force_null_flags; /* per-column CSV FN flags */ + bool force_array; /* add JSON array decorations */ bool convert_selectively; /* do selective binary conversion? */ CopyOnErrorChoice on_error; /* what to do when error happened */ List *convert_select; /* list of column names (can be NIL) */ diff --git a/src/test/regress/expected/copy.out b/src/test/regress/expected/copy.out index 0c5ade47..7812768c 100644 --- a/src/test/regress/expected/copy.out +++ b/src/test/regress/expected/copy.out @@ -59,6 +59,30 @@ ERROR: cannot specify HEADER in JSON mode -- Error copy copytest from stdout (format json); ERROR: cannot use JSON mode in COPY FROM +--Error +copy copytest to stdout (format csv, force_array false); +ERROR: COPY FORCE_ARRAY only available in JSON mode +copy copytest from stdin (format json, force_array true); +ERROR: cannot use JSON mode in COPY FROM +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} +] +copy copytest to stdout (format json, force_array true); +[ + {"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 to stdout (format json, force_array false); +{"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} -- embedded escaped characters create temp table copyjsontest ( id bigserial, diff --git a/src/test/regress/sql/copy.sql b/src/test/regress/sql/copy.sql index da6b0b0a..f685193b 100644 --- a/src/test/regress/sql/copy.sql +++ b/src/test/regress/sql/copy.sql @@ -64,6 +64,15 @@ copy copytest to stdout (format json, header); -- Error copy copytest from stdout (format json); +--Error +copy copytest to stdout (format csv, force_array false); +copy copytest from stdin (format json, force_array true); + +copy copytest to stdout (format json, force_array); + +copy copytest to stdout (format json, force_array true); + +copy copytest to stdout (format json, force_array false); -- embedded escaped characters create temp table copyjsontest ( id bigserial, -- 2.34.1