Add HEADER support to COPY text format
authorPeter Eisentraut <peter@eisentraut.org>
Fri, 28 Jan 2022 08:22:53 +0000 (09:22 +0100)
committerPeter Eisentraut <peter@eisentraut.org>
Fri, 28 Jan 2022 08:44:47 +0000 (09:44 +0100)
The COPY CSV format supports the HEADER option to output a header
line.  This patch adds the same option to the default text format.  On
input, the HEADER option causes the first line to be skipped, same as
with CSV.

Author: RĂ©mi Lapeyre <remi.lapeyre@lenstra.fr>
Discussion: https://www.postgresql.org/message-id/flat/CAF1-J-0PtCWMeLtswwGV2M70U26n4g33gpe1rcKQqe6wVQDrFA@mail.gmail.com

contrib/file_fdw/expected/file_fdw.out
contrib/file_fdw/sql/file_fdw.sql
doc/src/sgml/ref/copy.sgml
src/backend/commands/copy.c
src/backend/commands/copyto.c
src/include/commands/copy.h
src/test/regress/expected/copy.out
src/test/regress/sql/copy.sql

index 891146fef385cae1402e3ba8a82c2339772d868d..0ac6e4e0d73dbd59ca31c606c6105d7ef538f3ca 100644 (file)
@@ -50,14 +50,12 @@ CREATE USER MAPPING FOR regress_no_priv_user SERVER file_server;
 -- validator tests
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'xml');  -- ERROR
 ERROR:  COPY format "xml" not recognized
-CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', header 'true');      -- ERROR
-ERROR:  COPY HEADER available only in CSV mode
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', quote ':');          -- ERROR
 ERROR:  COPY quote available only in CSV mode
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', escape ':');         -- ERROR
 ERROR:  COPY escape available only in CSV mode
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'binary', header 'true');    -- ERROR
-ERROR:  COPY HEADER available only in CSV mode
+ERROR:  cannot specify HEADER in BINARY mode
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'binary', quote ':');        -- ERROR
 ERROR:  COPY quote available only in CSV mode
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'binary', escape ':');       -- ERROR
index 0ea8b1450808b14cab433209597b5f2e941df81c..86f876d565f1766da5bbfcb812b1b57e2bafaa14 100644 (file)
@@ -56,7 +56,6 @@ CREATE USER MAPPING FOR regress_no_priv_user SERVER file_server;
 
 -- validator tests
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'xml');  -- ERROR
-CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', header 'true');      -- ERROR
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', quote ':');          -- ERROR
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', escape ':');         -- ERROR
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'binary', header 'true');    -- ERROR
index 4624c8f4c98611e59beb8849ed7850e16b9cfe0f..1b7d001963667c0299bc4f6f57750164cc88ecee 100644 (file)
@@ -277,7 +277,7 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable
       Specifies that the file contains a header line with the names of each
       column in the file.  On output, the first line contains the column
       names from the table, and on input, the first line is ignored.
-      This option is allowed only when using <literal>CSV</literal> format.
+      This option is not allowed when using <literal>binary</literal> format.
      </para>
     </listitem>
    </varlistentry>
index bb9c21bc6b41eae3c8422b03604c191f8eddd45a..7da7105d44b1ac37cc8ff6e3872958a840bd2466 100644 (file)
@@ -555,10 +555,10 @@ ProcessCopyOptions(ParseState *pstate,
                                 errmsg("COPY delimiter cannot be \"%s\"", opts_out->delim)));
 
        /* Check header */
-       if (!opts_out->csv_mode && opts_out->header_line)
+       if (opts_out->binary && opts_out->header_line)
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                errmsg("COPY HEADER available only in CSV mode")));
+                                errmsg("cannot specify HEADER in BINARY mode")));
 
        /* Check quote */
        if (!opts_out->csv_mode && opts_out->quote != NULL)
index 20bfd49112a5f44e1b1180c4109f1b060ef5bf06..e793b64bdab1d983ff425af287e635f88c428423 100644 (file)
@@ -863,8 +863,11 @@ DoCopyTo(CopyToState cstate)
 
                                colname = NameStr(TupleDescAttr(tupDesc, attnum - 1)->attname);
 
-                               CopyAttributeOutCSV(cstate, colname, false,
+                               if (cstate->opts.csv_mode)
+                                       CopyAttributeOutCSV(cstate, colname, false,
                                                                        list_length(cstate->attnumlist) == 1);
+                               else
+                                       CopyAttributeOutText(cstate, colname);
                        }
 
                        CopySendEndOfRow(cstate);
index c01cf42bcd1c61e369377e8377c029d872db7bcf..8694da5004cdaebf7cea9d5a4992fe6532ee15e6 100644 (file)
@@ -32,7 +32,7 @@ typedef struct CopyFormatOptions
        bool            binary;                 /* binary format? */
        bool            freeze;                 /* freeze rows on loading? */
        bool            csv_mode;               /* Comma Separated Value format? */
-       bool            header_line;    /* CSV header line? */
+       bool            header_line;    /* header line? */
        char       *null_print;         /* NULL marker string (server encoding!) */
        int                     null_print_len; /* length of same */
        char       *null_print_client;  /* same converted to file encoding */
index 931e7b2e699c6dedb52a42944018cddce63e1cf5..851b9a4a2d85ca557e114b42d957c73e06d90e5f 100644 (file)
@@ -120,6 +120,14 @@ copy copytest3 to stdout csv header;
 c1,"col with , comma","col with "" quote"
 1,a,1
 2,b,2
+create temp table copytest4 (
+       c1 int,
+       "colname with tab:      " text);
+copy copytest4 from stdin (header);
+copy copytest4 to stdout (header);
+c1     colname with tab: \t
+1      a
+2      b
 -- test copy from with a partitioned table
 create table parted_copytest (
        a int,
index 15e26517ecb71c8cc6c9437706d7c3ac2287dede..016fedf6753cf7cf81f73b5c405e4cbbe1ed73de 100644 (file)
@@ -160,6 +160,18 @@ this is just a line full of junk that would error out if parsed
 
 copy copytest3 to stdout csv header;
 
+create temp table copytest4 (
+       c1 int,
+       "colname with tab:      " text);
+
+copy copytest4 from stdin (header);
+this is just a line full of junk that would error out if parsed
+1      a
+2      b
+\.
+
+copy copytest4 to stdout (header);
+
 -- test copy from with a partitioned table
 create table parted_copytest (
        a int,