Thread: COPY CSV header line feature

COPY CSV header line feature

From
Andrew Dunstan
Date:
The attached patch implements the previously discussed header line
feature for CSV mode COPY. It is triggered by the keyword HEADER (blame
Bruce - he chose it ;-) ).

On input this feature causes the first line to be ignored; on output it
generates a line of column names. This will make playing with
spreadsheets potentially a lot nicer.

Docs and regression tests will be forthcoming.

cheers

andrew
Index: src/backend/commands/copy.c
===================================================================
RCS file: /home/cvsmirror/pgsql/src/backend/commands/copy.c,v
retrieving revision 1.237
diff -c -r1.237 copy.c
*** src/backend/commands/copy.c    12 Mar 2005 05:41:34 -0000    1.237
--- src/backend/commands/copy.c    13 Mar 2005 23:25:50 -0000
***************
*** 131,143 ****
  /* non-export function prototypes */
  static void DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_quote_atts, bool fe_copy);
  static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
   char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
!        List *force_quote_atts);
  static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
   char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
!          List *force_notnull_atts);
  static bool CopyReadLine(char * quote, char * escape);
  static char *CopyReadAttribute(const char *delim, const char *null_print,
                    CopyReadResult *result, bool *isnull);
--- 131,143 ----
  /* non-export function prototypes */
  static void DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_quote_atts, bool header_line, bool fe_copy);
  static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
   char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
!        List *force_quote_atts, bool header_line);
  static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
   char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
!          List *force_notnull_atts, bool header_line);
  static bool CopyReadLine(char * quote, char * escape);
  static char *CopyReadAttribute(const char *delim, const char *null_print,
                    CopyReadResult *result, bool *isnull);
***************
*** 695,700 ****
--- 695,701 ----
      bool        binary = false;
      bool        oids = false;
      bool        csv_mode = false;
+     bool        header_line = false;
      char       *delim = NULL;
      char       *quote = NULL;
      char       *escape = NULL;
***************
*** 752,757 ****
--- 753,766 ----
                           errmsg("conflicting or redundant options")));
              csv_mode = intVal(defel->arg);
          }
+         else if (strcmp(defel->defname, "header") == 0)
+         {
+             if (header_line)
+                 ereport(ERROR,
+                         (errcode(ERRCODE_SYNTAX_ERROR),
+                          errmsg("conflicting or redundant options")));
+             header_line = intVal(defel->arg);
+         }
          else if (strcmp(defel->defname, "quote") == 0)
          {
              if (quote)
***************
*** 828,833 ****
--- 837,850 ----
                   errmsg("COPY delimiter must be a single character")));

      /*
+      * Check header
+      */
+     if (!csv_mode && header_line)
+         ereport(ERROR,
+                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                  errmsg("COPY HEADER available only in CSV mode")));
+
+     /*
       * Check quote
       */
      if (!csv_mode && quote != NULL)
***************
*** 1041,1047 ****
              }
          }
          CopyFrom(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                  quote, escape, force_notnull_atts);
      }
      else
      {                            /* copy from database to file */
--- 1058,1064 ----
              }
          }
          CopyFrom(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                  quote, escape, force_notnull_atts, header_line);
      }
      else
      {                            /* copy from database to file */
***************
*** 1105,1111 ****
          }

          DoCopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                  quote, escape, force_quote_atts, fe_copy);
      }

      if (!pipe)
--- 1122,1128 ----
          }

          DoCopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                  quote, escape, force_quote_atts, header_line, fe_copy);
      }

      if (!pipe)
***************
*** 1137,1143 ****
  static void
  DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_quote_atts, bool fe_copy)
  {
      PG_TRY();
      {
--- 1154,1160 ----
  static void
  DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_quote_atts, bool header_line, bool fe_copy)
  {
      PG_TRY();
      {
***************
*** 1145,1151 ****
              SendCopyBegin(binary, list_length(attnumlist));

          CopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                quote, escape, force_quote_atts);

          if (fe_copy)
              SendCopyEnd(binary);
--- 1162,1168 ----
              SendCopyBegin(binary, list_length(attnumlist));

          CopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                quote, escape, force_quote_atts, header_line);

          if (fe_copy)
              SendCopyEnd(binary);
***************
*** 1169,1175 ****
  static void
  CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
         char *delim, char *null_print, bool csv_mode, char *quote,
!        char *escape, List *force_quote_atts)
  {
      HeapTuple    tuple;
      TupleDesc    tupDesc;
--- 1186,1192 ----
  static void
  CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
         char *delim, char *null_print, bool csv_mode, char *quote,
!        char *escape, List *force_quote_atts, bool header_line)
  {
      HeapTuple    tuple;
      TupleDesc    tupDesc;
***************
*** 1257,1262 ****
--- 1274,1305 ----
              null_print = (char *)
                  pg_server_to_client((unsigned char *) null_print,
                                      strlen(null_print));
+
+         /*
+          * if a header has been requested send the line
+          */
+         if (header_line)
+         {
+             bool hdr_delim = false;
+             char * colname;
+
+             foreach(cur, attnumlist)
+             {
+                 int            attnum = lfirst_int(cur);
+
+                 if (hdr_delim)
+                     CopySendChar(delim[0]);
+                 hdr_delim = true;
+
+                 colname = NameStr(attr[attnum - 1]->attname);
+
+                 CopyAttributeOutCSV(colname, delim, quote, escape,
+                                     strcmp(colname, null_print) == 0 );
+             }
+
+             CopySendEndOfRow(binary);
+
+         }
      }

      scandesc = heap_beginscan(rel, ActiveSnapshot, 0, NULL);
***************
*** 1463,1469 ****
  static void
  CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_notnull_atts)
  {
      HeapTuple    tuple;
      TupleDesc    tupDesc;
--- 1506,1512 ----
  static void
  CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_notnull_atts, bool header_line)
  {
      HeapTuple    tuple;
      TupleDesc    tupDesc;
***************
*** 1690,1695 ****
--- 1733,1747 ----
      errcontext.previous = error_context_stack;
      error_context_stack = &errcontext;

+     /*
+      * on input just throw the header line away
+      */
+     if (header_line)
+     {
+         copy_lineno++;
+         done = CopyReadLine(quote, escape) ;
+     }
+
      while (!done)
      {
          bool        skip_tuple;
Index: src/backend/parser/gram.y
===================================================================
RCS file: /home/cvsmirror/pgsql/src/backend/parser/gram.y,v
retrieving revision 2.483
diff -c -r2.483 gram.y
*** src/backend/parser/gram.y    2 Feb 2005 06:36:01 -0000    2.483
--- src/backend/parser/gram.y    13 Mar 2005 23:25:50 -0000
***************
*** 357,363 ****

      GLOBAL GRANT GROUP_P

!     HANDLER HAVING HOLD HOUR_P

      ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT
      INDEX INHERITS INITIALLY INNER_P INOUT INPUT_P
--- 357,363 ----

      GLOBAL GRANT GROUP_P

!     HANDLER HAVING HEADER HOLD HOUR_P

      ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT
      INDEX INHERITS INITIALLY INNER_P INOUT INPUT_P
***************
*** 1438,1443 ****
--- 1438,1447 ----
                  {
                      $$ = makeDefElem("csv", (Node *)makeInteger(TRUE));
                  }
+             | HEADER
+                 {
+                     $$ = makeDefElem("header", (Node *)makeInteger(TRUE));
+                 }
              | QUOTE opt_as Sconst
                  {
                      $$ = makeDefElem("quote", (Node *)makeString($3));
Index: src/backend/parser/keywords.c
===================================================================
RCS file: /home/cvsmirror/pgsql/src/backend/parser/keywords.c,v
retrieving revision 1.154
diff -c -r1.154 keywords.c
*** src/backend/parser/keywords.c    31 Dec 2004 22:00:27 -0000    1.154
--- src/backend/parser/keywords.c    13 Mar 2005 23:25:50 -0000
***************
*** 148,153 ****
--- 148,154 ----
      {"group", GROUP_P},
      {"handler", HANDLER},
      {"having", HAVING},
+     {"header", HEADER},
      {"hold", HOLD},
      {"hour", HOUR_P},
      {"ilike", ILIKE},
Index: src/bin/psql/copy.c
===================================================================
RCS file: /home/cvsmirror/pgsql/src/bin/psql/copy.c,v
retrieving revision 1.56
diff -c -r1.56 copy.c
*** src/bin/psql/copy.c    22 Feb 2005 04:40:54 -0000    1.56
--- src/bin/psql/copy.c    13 Mar 2005 23:25:50 -0000
***************
*** 66,71 ****
--- 66,72 ----
      bool        binary;
      bool        oids;
      bool        csv_mode;
+     bool        header;
      char       *delim;
      char       *null;
      char       *quote;
***************
*** 289,294 ****
--- 290,297 ----
                  result->oids = true;
              else if (pg_strcasecmp(token, "csv") == 0)
                  result->csv_mode = true;
+             else if (pg_strcasecmp(token, "header") == 0)
+                 result->header = true;
              else if (pg_strcasecmp(token, "delimiter") == 0)
              {
                  token = strtokx(NULL, whitespace, NULL, "'",
***************
*** 481,486 ****
--- 484,492 ----
      if (options->csv_mode)
          appendPQExpBuffer(&query, " CSV");

+     if (options->header)
+         appendPQExpBuffer(&query, " HEADER");
+
      if (options->quote)
      {
          if (options->quote[0] == '\'')

Re: COPY CSV header line feature

From
Alvaro Herrera
Date:
On Sun, Mar 13, 2005 at 06:32:20PM -0500, Andrew Dunstan wrote:

> The attached patch implements the previously discussed header line
> feature for CSV mode COPY. It is triggered by the keyword HEADER (blame
> Bruce - he chose it ;-) ).

I think you should add the new reserved keyword to the
unreserved_keywords list or some other.

--
Alvaro Herrera (<alvherre[@]dcc.uchile.cl>)
"No hay hombre que no aspire a la plenitud, es decir,
la suma de experiencias de que un hombre es capaz"

Re: COPY CSV header line feature

From
"Andrew Dunstan"
Date:
Alvaro Herrera said:
> On Sun, Mar 13, 2005 at 06:32:20PM -0500, Andrew Dunstan wrote:
>
>> The attached patch implements the previously discussed header line
>> feature for CSV mode COPY. It is triggered by the keyword HEADER
>> (blame Bruce - he chose it ;-) ).
>
> I think you should add the new reserved keyword to the
> unreserved_keywords list or some other.
>

Please be more specific. I'll be happy to add in anything I've missed.

cheers

andrew



Re: COPY CSV header line feature

From
Alvaro Herrera
Date:
On Tue, Mar 15, 2005 at 08:55:36PM -0600, Andrew Dunstan wrote:
> Alvaro Herrera said:
> > On Sun, Mar 13, 2005 at 06:32:20PM -0500, Andrew Dunstan wrote:
> >
> >> The attached patch implements the previously discussed header line
> >> feature for CSV mode COPY. It is triggered by the keyword HEADER
> >> (blame Bruce - he chose it ;-) ).
> >
> > I think you should add the new reserved keyword to the
> > unreserved_keywords list or some other.
>
> Please be more specific. I'll be happy to add in anything I've missed.

The Postgres grammar classifies keywords in one of several lists, in
order to make them available as names to users (column names, function
names, etc).  So each time you create a new keyword and add it to the
keywords.c list, you have to add it to one of the lists on gram.y too.
See gram.y line 7669 ff.

I'd add a comment on this on gram.y:

Index: gram.y
===================================================================
RCS file: /home/alvherre/cvs/pgsql/src/backend/parser/gram.y,v
retrieving revision 2.484
diff -c -w -b -B -c -r2.484 gram.y
*** gram.y    14 Mar 2005 00:19:36 -0000    2.484
--- gram.y    16 Mar 2005 03:12:48 -0000
***************
*** 327,333 ****
  /*
   * If you make any token changes, update the keyword table in
   * parser/keywords.c and add new keywords to the appropriate one of
!  * the reserved-or-not-so-reserved keyword lists, below.
   */

  /* ordinary key words in alphabetical order */
--- 327,334 ----
  /*
   * If you make any token changes, update the keyword table in
   * parser/keywords.c and add new keywords to the appropriate one of
!  * the reserved-or-not-so-reserved keyword lists, below; search this
!  * file for "Name classification hierarchy."
   */

  /* ordinary key words in alphabetical order */

--
Alvaro Herrera (<alvherre[@]dcc.uchile.cl>)
Tulio: oh, para qué servirá este boton, Juan Carlos?
Policarpo: No, aléjense, no toquen la consola!
Juan Carlos: Lo apretaré una y otra vez.

Re: COPY CSV header line feature

From
Tom Lane
Date:
Alvaro Herrera <alvherre@dcc.uchile.cl> writes:
> I think you should add the new reserved keyword to the
> unreserved_keywords list or some other.

*Every* keyword needs to be in one of those lists.  I'd like to have
some sort of automatic enforcement of this, because forgetting to
add a new keyword to the right list is an everyday error.

I've been thinking that it would be good if the table in keywords.c
included an indication of the reservation category of each keyword.
So one possible approach is to do that and then somehow check that
the gram.y grammar agrees --- or even better, mechanically generate
the keyword category productions in gram.y from the keywords.c data.
Not sure just how to do that though.

Any ideas?

            regards, tom lane

Re: COPY CSV header line feature

From
Andrew Dunstan
Date:
ammended patch attached. sorry for the oversight. I agree with Tom's
remark - it's far too easy to miss this.

cheers

andrew

Alvaro Herrera wrote:

>On Tue, Mar 15, 2005 at 08:55:36PM -0600, Andrew Dunstan wrote:
>
>
>>Alvaro Herrera said:
>>
>>
>>>On Sun, Mar 13, 2005 at 06:32:20PM -0500, Andrew Dunstan wrote:
>>>
>>>
>>>
>>>>The attached patch implements the previously discussed header line
>>>>feature for CSV mode COPY. It is triggered by the keyword HEADER
>>>>(blame Bruce - he chose it ;-) ).
>>>>
>>>>
>>>I think you should add the new reserved keyword to the
>>>unreserved_keywords list or some other.
>>>
>>>
>>Please be more specific. I'll be happy to add in anything I've missed.
>>
>>
>
>The Postgres grammar classifies keywords in one of several lists, in
>order to make them available as names to users (column names, function
>names, etc).  So each time you create a new keyword and add it to the
>keywords.c list, you have to add it to one of the lists on gram.y too.
>See gram.y line 7669 ff.
>
>I'd add a comment on this on gram.y:
>
>Index: gram.y
>===================================================================
>RCS file: /home/alvherre/cvs/pgsql/src/backend/parser/gram.y,v
>retrieving revision 2.484
>diff -c -w -b -B -c -r2.484 gram.y
>*** gram.y    14 Mar 2005 00:19:36 -0000    2.484
>--- gram.y    16 Mar 2005 03:12:48 -0000
>***************
>*** 327,333 ****
>  /*
>   * If you make any token changes, update the keyword table in
>   * parser/keywords.c and add new keywords to the appropriate one of
>!  * the reserved-or-not-so-reserved keyword lists, below.
>   */
>
>  /* ordinary key words in alphabetical order */
>--- 327,334 ----
>  /*
>   * If you make any token changes, update the keyword table in
>   * parser/keywords.c and add new keywords to the appropriate one of
>!  * the reserved-or-not-so-reserved keyword lists, below; search this
>!  * file for "Name classification hierarchy."
>   */
>
>  /* ordinary key words in alphabetical order */
>
>
>
Index: src/backend/commands/copy.c
===================================================================
RCS file: /home/cvsmirror/pgsql/src/backend/commands/copy.c,v
retrieving revision 1.237
diff -c -r1.237 copy.c
*** src/backend/commands/copy.c    12 Mar 2005 05:41:34 -0000    1.237
--- src/backend/commands/copy.c    16 Mar 2005 12:37:20 -0000
***************
*** 131,143 ****
  /* non-export function prototypes */
  static void DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_quote_atts, bool fe_copy);
  static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
   char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
!        List *force_quote_atts);
  static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
   char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
!          List *force_notnull_atts);
  static bool CopyReadLine(char * quote, char * escape);
  static char *CopyReadAttribute(const char *delim, const char *null_print,
                    CopyReadResult *result, bool *isnull);
--- 131,143 ----
  /* non-export function prototypes */
  static void DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_quote_atts, bool header_line, bool fe_copy);
  static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
   char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
!        List *force_quote_atts, bool header_line);
  static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
   char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
!          List *force_notnull_atts, bool header_line);
  static bool CopyReadLine(char * quote, char * escape);
  static char *CopyReadAttribute(const char *delim, const char *null_print,
                    CopyReadResult *result, bool *isnull);
***************
*** 695,700 ****
--- 695,701 ----
      bool        binary = false;
      bool        oids = false;
      bool        csv_mode = false;
+     bool        header_line = false;
      char       *delim = NULL;
      char       *quote = NULL;
      char       *escape = NULL;
***************
*** 752,757 ****
--- 753,766 ----
                           errmsg("conflicting or redundant options")));
              csv_mode = intVal(defel->arg);
          }
+         else if (strcmp(defel->defname, "header") == 0)
+         {
+             if (header_line)
+                 ereport(ERROR,
+                         (errcode(ERRCODE_SYNTAX_ERROR),
+                          errmsg("conflicting or redundant options")));
+             header_line = intVal(defel->arg);
+         }
          else if (strcmp(defel->defname, "quote") == 0)
          {
              if (quote)
***************
*** 828,833 ****
--- 837,850 ----
                   errmsg("COPY delimiter must be a single character")));

      /*
+      * Check header
+      */
+     if (!csv_mode && header_line)
+         ereport(ERROR,
+                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                  errmsg("COPY HEADER available only in CSV mode")));
+
+     /*
       * Check quote
       */
      if (!csv_mode && quote != NULL)
***************
*** 1041,1047 ****
              }
          }
          CopyFrom(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                  quote, escape, force_notnull_atts);
      }
      else
      {                            /* copy from database to file */
--- 1058,1064 ----
              }
          }
          CopyFrom(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                  quote, escape, force_notnull_atts, header_line);
      }
      else
      {                            /* copy from database to file */
***************
*** 1105,1111 ****
          }

          DoCopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                  quote, escape, force_quote_atts, fe_copy);
      }

      if (!pipe)
--- 1122,1128 ----
          }

          DoCopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                  quote, escape, force_quote_atts, header_line, fe_copy);
      }

      if (!pipe)
***************
*** 1137,1143 ****
  static void
  DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_quote_atts, bool fe_copy)
  {
      PG_TRY();
      {
--- 1154,1160 ----
  static void
  DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_quote_atts, bool header_line, bool fe_copy)
  {
      PG_TRY();
      {
***************
*** 1145,1151 ****
              SendCopyBegin(binary, list_length(attnumlist));

          CopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                quote, escape, force_quote_atts);

          if (fe_copy)
              SendCopyEnd(binary);
--- 1162,1168 ----
              SendCopyBegin(binary, list_length(attnumlist));

          CopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                quote, escape, force_quote_atts, header_line);

          if (fe_copy)
              SendCopyEnd(binary);
***************
*** 1169,1175 ****
  static void
  CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
         char *delim, char *null_print, bool csv_mode, char *quote,
!        char *escape, List *force_quote_atts)
  {
      HeapTuple    tuple;
      TupleDesc    tupDesc;
--- 1186,1192 ----
  static void
  CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
         char *delim, char *null_print, bool csv_mode, char *quote,
!        char *escape, List *force_quote_atts, bool header_line)
  {
      HeapTuple    tuple;
      TupleDesc    tupDesc;
***************
*** 1257,1262 ****
--- 1274,1305 ----
              null_print = (char *)
                  pg_server_to_client((unsigned char *) null_print,
                                      strlen(null_print));
+
+         /*
+          * if a header has been requested send the line
+          */
+         if (header_line)
+         {
+             bool hdr_delim = false;
+             char * colname;
+
+             foreach(cur, attnumlist)
+             {
+                 int            attnum = lfirst_int(cur);
+
+                 if (hdr_delim)
+                     CopySendChar(delim[0]);
+                 hdr_delim = true;
+
+                 colname = NameStr(attr[attnum - 1]->attname);
+
+                 CopyAttributeOutCSV(colname, delim, quote, escape,
+                                     strcmp(colname, null_print) == 0 );
+             }
+
+             CopySendEndOfRow(binary);
+
+         }
      }

      scandesc = heap_beginscan(rel, ActiveSnapshot, 0, NULL);
***************
*** 1463,1469 ****
  static void
  CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_notnull_atts)
  {
      HeapTuple    tuple;
      TupleDesc    tupDesc;
--- 1506,1512 ----
  static void
  CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_notnull_atts, bool header_line)
  {
      HeapTuple    tuple;
      TupleDesc    tupDesc;
***************
*** 1690,1695 ****
--- 1733,1747 ----
      errcontext.previous = error_context_stack;
      error_context_stack = &errcontext;

+     /*
+      * on input just throw the header line away
+      */
+     if (header_line)
+     {
+         copy_lineno++;
+         done = CopyReadLine(quote, escape) ;
+     }
+
      while (!done)
      {
          bool        skip_tuple;
Index: src/backend/parser/gram.y
===================================================================
RCS file: /home/cvsmirror/pgsql/src/backend/parser/gram.y,v
retrieving revision 2.483
diff -c -r2.483 gram.y
*** src/backend/parser/gram.y    2 Feb 2005 06:36:01 -0000    2.483
--- src/backend/parser/gram.y    16 Mar 2005 12:37:20 -0000
***************
*** 357,363 ****

      GLOBAL GRANT GROUP_P

!     HANDLER HAVING HOLD HOUR_P

      ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT
      INDEX INHERITS INITIALLY INNER_P INOUT INPUT_P
--- 357,363 ----

      GLOBAL GRANT GROUP_P

!     HANDLER HAVING HEADER HOLD HOUR_P

      ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT
      INDEX INHERITS INITIALLY INNER_P INOUT INPUT_P
***************
*** 1438,1443 ****
--- 1438,1447 ----
                  {
                      $$ = makeDefElem("csv", (Node *)makeInteger(TRUE));
                  }
+             | HEADER
+                 {
+                     $$ = makeDefElem("header", (Node *)makeInteger(TRUE));
+                 }
              | QUOTE opt_as Sconst
                  {
                      $$ = makeDefElem("quote", (Node *)makeString($3));
***************
*** 7701,7706 ****
--- 7705,7711 ----
              | FUNCTION
              | GLOBAL
              | HANDLER
+             | HEADER
              | HOLD
              | HOUR_P
              | IMMEDIATE
Index: src/backend/parser/keywords.c
===================================================================
RCS file: /home/cvsmirror/pgsql/src/backend/parser/keywords.c,v
retrieving revision 1.154
diff -c -r1.154 keywords.c
*** src/backend/parser/keywords.c    31 Dec 2004 22:00:27 -0000    1.154
--- src/backend/parser/keywords.c    16 Mar 2005 12:37:20 -0000
***************
*** 148,153 ****
--- 148,154 ----
      {"group", GROUP_P},
      {"handler", HANDLER},
      {"having", HAVING},
+     {"header", HEADER},
      {"hold", HOLD},
      {"hour", HOUR_P},
      {"ilike", ILIKE},
Index: src/bin/psql/copy.c
===================================================================
RCS file: /home/cvsmirror/pgsql/src/bin/psql/copy.c,v
retrieving revision 1.56
diff -c -r1.56 copy.c
*** src/bin/psql/copy.c    22 Feb 2005 04:40:54 -0000    1.56
--- src/bin/psql/copy.c    16 Mar 2005 12:37:21 -0000
***************
*** 66,71 ****
--- 66,72 ----
      bool        binary;
      bool        oids;
      bool        csv_mode;
+     bool        header;
      char       *delim;
      char       *null;
      char       *quote;
***************
*** 289,294 ****
--- 290,297 ----
                  result->oids = true;
              else if (pg_strcasecmp(token, "csv") == 0)
                  result->csv_mode = true;
+             else if (pg_strcasecmp(token, "header") == 0)
+                 result->header = true;
              else if (pg_strcasecmp(token, "delimiter") == 0)
              {
                  token = strtokx(NULL, whitespace, NULL, "'",
***************
*** 481,486 ****
--- 484,492 ----
      if (options->csv_mode)
          appendPQExpBuffer(&query, " CSV");

+     if (options->header)
+         appendPQExpBuffer(&query, " HEADER");
+
      if (options->quote)
      {
          if (options->quote[0] == '\'')

Re: COPY CSV header line feature

From
Bruce Momjian
Date:
Here is an updated version of this patch, with documentation changes.

I have already updated the gram.y comment you suggested.

---------------------------------------------------------------------------

Andrew Dunstan wrote:
>
> ammended patch attached. sorry for the oversight. I agree with Tom's
> remark - it's far too easy to miss this.
>
> cheers
>
> andrew
>
> Alvaro Herrera wrote:
>
> >On Tue, Mar 15, 2005 at 08:55:36PM -0600, Andrew Dunstan wrote:
> >
> >
> >>Alvaro Herrera said:
> >>
> >>
> >>>On Sun, Mar 13, 2005 at 06:32:20PM -0500, Andrew Dunstan wrote:
> >>>
> >>>
> >>>
> >>>>The attached patch implements the previously discussed header line
> >>>>feature for CSV mode COPY. It is triggered by the keyword HEADER
> >>>>(blame Bruce - he chose it ;-) ).
> >>>>
> >>>>
> >>>I think you should add the new reserved keyword to the
> >>>unreserved_keywords list or some other.
> >>>
> >>>
> >>Please be more specific. I'll be happy to add in anything I've missed.
> >>
> >>
> >
> >The Postgres grammar classifies keywords in one of several lists, in
> >order to make them available as names to users (column names, function
> >names, etc).  So each time you create a new keyword and add it to the
> >keywords.c list, you have to add it to one of the lists on gram.y too.
> >See gram.y line 7669 ff.
> >
> >I'd add a comment on this on gram.y:
> >
> >Index: gram.y
> >===================================================================
> >RCS file: /home/alvherre/cvs/pgsql/src/backend/parser/gram.y,v
> >retrieving revision 2.484
> >diff -c -w -b -B -c -r2.484 gram.y
> >*** gram.y    14 Mar 2005 00:19:36 -0000    2.484
> >--- gram.y    16 Mar 2005 03:12:48 -0000
> >***************
> >*** 327,333 ****
> >  /*
> >   * If you make any token changes, update the keyword table in
> >   * parser/keywords.c and add new keywords to the appropriate one of
> >!  * the reserved-or-not-so-reserved keyword lists, below.
> >   */
> >
> >  /* ordinary key words in alphabetical order */
> >--- 327,334 ----
> >  /*
> >   * If you make any token changes, update the keyword table in
> >   * parser/keywords.c and add new keywords to the appropriate one of
> >!  * the reserved-or-not-so-reserved keyword lists, below; search this
> >!  * file for "Name classification hierarchy."
> >   */
> >
> >  /* ordinary key words in alphabetical order */
> >
> >
> >


>
> ---------------------------(end of broadcast)---------------------------
> TIP 4: Don't 'kill -9' the postmaster

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
Index: doc/src/sgml/ref/copy.sgml
===================================================================
RCS file: /cvsroot/pgsql/doc/src/sgml/ref/copy.sgml,v
retrieving revision 1.63
diff -c -c -r1.63 copy.sgml
*** doc/src/sgml/ref/copy.sgml    4 Jan 2005 00:39:53 -0000    1.63
--- doc/src/sgml/ref/copy.sgml    6 May 2005 03:36:30 -0000
***************
*** 24,34 ****
  COPY <replaceable class="parameter">tablename</replaceable> [ ( <replaceable class="parameter">column</replaceable>
[,...] ) ] 
      FROM { '<replaceable class="parameter">filename</replaceable>' | STDIN }
      [ [ WITH ]
!           [ BINARY ]
            [ OIDS ]
            [ DELIMITER [ AS ] '<replaceable class="parameter">delimiter</replaceable>' ]
            [ NULL [ AS ] '<replaceable class="parameter">null string</replaceable>' ]
!           [ CSV [ QUOTE [ AS ] '<replaceable class="parameter">quote</replaceable>' ]
                  [ ESCAPE [ AS ] '<replaceable class="parameter">escape</replaceable>' ]
                  [ FORCE NOT NULL <replaceable class="parameter">column</replaceable> [, ...] ]

--- 24,35 ----
  COPY <replaceable class="parameter">tablename</replaceable> [ ( <replaceable class="parameter">column</replaceable>
[,...] ) ] 
      FROM { '<replaceable class="parameter">filename</replaceable>' | STDIN }
      [ [ WITH ]
!           [ BINARY ]
            [ OIDS ]
            [ DELIMITER [ AS ] '<replaceable class="parameter">delimiter</replaceable>' ]
            [ NULL [ AS ] '<replaceable class="parameter">null string</replaceable>' ]
!           [ CSV [ HEADER ]
!                 [ QUOTE [ AS ] '<replaceable class="parameter">quote</replaceable>' ]
                  [ ESCAPE [ AS ] '<replaceable class="parameter">escape</replaceable>' ]
                  [ FORCE NOT NULL <replaceable class="parameter">column</replaceable> [, ...] ]

***************
*** 36,45 ****
      TO { '<replaceable class="parameter">filename</replaceable>' | STDOUT }
      [ [ WITH ]
            [ BINARY ]
            [ OIDS ]
            [ DELIMITER [ AS ] '<replaceable class="parameter">delimiter</replaceable>' ]
            [ NULL [ AS ] '<replaceable class="parameter">null string</replaceable>' ]
!           [ CSV [ QUOTE [ AS ] '<replaceable class="parameter">quote</replaceable>' ]
                  [ ESCAPE [ AS ] '<replaceable class="parameter">escape</replaceable>' ]
                  [ FORCE QUOTE <replaceable class="parameter">column</replaceable> [, ...] ]
  </synopsis>
--- 37,48 ----
      TO { '<replaceable class="parameter">filename</replaceable>' | STDOUT }
      [ [ WITH ]
            [ BINARY ]
+           [ HEADER ]
            [ OIDS ]
            [ DELIMITER [ AS ] '<replaceable class="parameter">delimiter</replaceable>' ]
            [ NULL [ AS ] '<replaceable class="parameter">null string</replaceable>' ]
!           [ CSV [ HEADER ]
!                 [ QUOTE [ AS ] '<replaceable class="parameter">quote</replaceable>' ]
                  [ ESCAPE [ AS ] '<replaceable class="parameter">escape</replaceable>' ]
                  [ FORCE QUOTE <replaceable class="parameter">column</replaceable> [, ...] ]
  </synopsis>
***************
*** 192,197 ****
--- 195,211 ----
     </varlistentry>

     <varlistentry>
+     <term><literal>HEADER</literal></term>
+     <listitem>
+      <para>
+       Specifies 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.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
      <term><replaceable class="parameter">quote</replaceable></term>
      <listitem>
       <para>
Index: src/backend/commands/copy.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/commands/copy.c,v
retrieving revision 1.242
diff -c -c -r1.242 copy.c
*** src/backend/commands/copy.c    6 May 2005 02:56:42 -0000    1.242
--- src/backend/commands/copy.c    6 May 2005 03:36:31 -0000
***************
*** 130,142 ****
  /* non-export function prototypes */
  static void DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_quote_atts, bool fe_copy);
  static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
   char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
!        List *force_quote_atts);
  static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
   char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
!          List *force_notnull_atts);
  static bool CopyReadLine(char * quote, char * escape);
  static char *CopyReadAttribute(const char *delim, const char *null_print,
                    CopyReadResult *result, bool *isnull);
--- 130,142 ----
  /* non-export function prototypes */
  static void DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_quote_atts, bool header_line, bool fe_copy);
  static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
   char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
!        List *force_quote_atts, bool header_line);
  static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
   char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
!          List *force_notnull_atts, bool header_line);
  static bool CopyReadLine(char * quote, char * escape);
  static char *CopyReadAttribute(const char *delim, const char *null_print,
                    CopyReadResult *result, bool *isnull);
***************
*** 694,699 ****
--- 694,700 ----
      bool        binary = false;
      bool        oids = false;
      bool        csv_mode = false;
+     bool        header_line = false;
      char       *delim = NULL;
      char       *quote = NULL;
      char       *escape = NULL;
***************
*** 751,756 ****
--- 752,765 ----
                           errmsg("conflicting or redundant options")));
              csv_mode = intVal(defel->arg);
          }
+         else if (strcmp(defel->defname, "header") == 0)
+         {
+             if (header_line)
+                 ereport(ERROR,
+                         (errcode(ERRCODE_SYNTAX_ERROR),
+                          errmsg("conflicting or redundant options")));
+             header_line = intVal(defel->arg);
+         }
          else if (strcmp(defel->defname, "quote") == 0)
          {
              if (quote)
***************
*** 824,829 ****
--- 833,844 ----
                  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                   errmsg("COPY delimiter must be a single character")));

+       /* Check header */
+     if (!csv_mode && header_line)
+         ereport(ERROR,
+                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                  errmsg("COPY HEADER available only in CSV mode")));
+
      /* Check quote */
      if (!csv_mode && quote != NULL)
          ereport(ERROR,
***************
*** 1014,1020 ****
              }
          }
          CopyFrom(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                  quote, escape, force_notnull_atts);
      }
      else
      {                            /* copy from database to file */
--- 1029,1035 ----
              }
          }
          CopyFrom(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                  quote, escape, force_notnull_atts, header_line);
      }
      else
      {                            /* copy from database to file */
***************
*** 1078,1084 ****
          }

          DoCopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                  quote, escape, force_quote_atts, fe_copy);
      }

      if (!pipe)
--- 1093,1099 ----
          }

          DoCopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                  quote, escape, force_quote_atts, header_line, fe_copy);
      }

      if (!pipe)
***************
*** 1110,1116 ****
  static void
  DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_quote_atts, bool fe_copy)
  {
      PG_TRY();
      {
--- 1125,1131 ----
  static void
  DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_quote_atts, bool header_line, bool fe_copy)
  {
      PG_TRY();
      {
***************
*** 1118,1124 ****
              SendCopyBegin(binary, list_length(attnumlist));

          CopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                quote, escape, force_quote_atts);

          if (fe_copy)
              SendCopyEnd(binary);
--- 1133,1139 ----
              SendCopyBegin(binary, list_length(attnumlist));

          CopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
!                quote, escape, force_quote_atts, header_line);

          if (fe_copy)
              SendCopyEnd(binary);
***************
*** 1142,1148 ****
  static void
  CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
         char *delim, char *null_print, bool csv_mode, char *quote,
!        char *escape, List *force_quote_atts)
  {
      HeapTuple    tuple;
      TupleDesc    tupDesc;
--- 1157,1163 ----
  static void
  CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
         char *delim, char *null_print, bool csv_mode, char *quote,
!        char *escape, List *force_quote_atts, bool header_line)
  {
      HeapTuple    tuple;
      TupleDesc    tupDesc;
***************
*** 1225,1230 ****
--- 1240,1269 ----
              null_print = (char *)
                  pg_server_to_client((unsigned char *) null_print,
                                      strlen(null_print));
+
+         /* if a header has been requested send the line */
+         if (header_line)
+         {
+             bool hdr_delim = false;
+             char *colname;
+
+             foreach(cur, attnumlist)
+             {
+                 int            attnum = lfirst_int(cur);
+
+                 if (hdr_delim)
+                     CopySendChar(delim[0]);
+                 hdr_delim = true;
+
+                 colname = NameStr(attr[attnum - 1]->attname);
+
+                 CopyAttributeOutCSV(colname, delim, quote, escape,
+                                     strcmp(colname, null_print) == 0);
+             }
+
+             CopySendEndOfRow(binary);
+
+         }
      }

      scandesc = heap_beginscan(rel, ActiveSnapshot, 0, NULL);
***************
*** 1426,1432 ****
  static void
  CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_notnull_atts)
  {
      HeapTuple    tuple;
      TupleDesc    tupDesc;
--- 1465,1471 ----
  static void
  CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
           char *delim, char *null_print, bool csv_mode, char *quote,
!          char *escape, List *force_notnull_atts, bool header_line)
  {
      HeapTuple    tuple;
      TupleDesc    tupDesc;
***************
*** 1652,1657 ****
--- 1691,1703 ----
      errcontext.previous = error_context_stack;
      error_context_stack = &errcontext;

+     /* on input just throw the header line away */
+     if (header_line)
+     {
+         copy_lineno++;
+         done = CopyReadLine(quote, escape) ;
+     }
+
      while (!done)
      {
          bool        skip_tuple;
Index: src/backend/parser/gram.y
===================================================================
RCS file: /cvsroot/pgsql/src/backend/parser/gram.y,v
retrieving revision 2.489
diff -c -c -r2.489 gram.y
*** src/backend/parser/gram.y    28 Apr 2005 21:47:14 -0000    2.489
--- src/backend/parser/gram.y    6 May 2005 03:36:36 -0000
***************
*** 361,367 ****

      GLOBAL GRANT GROUP_P

!     HANDLER HAVING HOLD HOUR_P

      ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT
      INDEX INHERITS INITIALLY INNER_P INOUT INPUT_P
--- 361,367 ----

      GLOBAL GRANT GROUP_P

!     HANDLER HAVING HEADER HOLD HOUR_P

      ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT
      INDEX INHERITS INITIALLY INNER_P INOUT INPUT_P
***************
*** 1443,1448 ****
--- 1443,1452 ----
                  {
                      $$ = makeDefElem("csv", (Node *)makeInteger(TRUE));
                  }
+             | HEADER
+                 {
+                     $$ = makeDefElem("header", (Node *)makeInteger(TRUE));
+                 }
              | QUOTE opt_as Sconst
                  {
                      $$ = makeDefElem("quote", (Node *)makeString($3));
***************
*** 7786,7791 ****
--- 7790,7796 ----
              | FUNCTION
              | GLOBAL
              | HANDLER
+             | HEADER
              | HOLD
              | HOUR_P
              | IMMEDIATE
Index: src/backend/parser/keywords.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/parser/keywords.c,v
retrieving revision 1.154
diff -c -c -r1.154 keywords.c
*** src/backend/parser/keywords.c    31 Dec 2004 22:00:27 -0000    1.154
--- src/backend/parser/keywords.c    6 May 2005 03:36:36 -0000
***************
*** 148,153 ****
--- 148,154 ----
      {"group", GROUP_P},
      {"handler", HANDLER},
      {"having", HAVING},
+     {"header", HEADER},
      {"hold", HOLD},
      {"hour", HOUR_P},
      {"ilike", ILIKE},
Index: src/bin/psql/copy.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/copy.c,v
retrieving revision 1.56
diff -c -c -r1.56 copy.c
*** src/bin/psql/copy.c    22 Feb 2005 04:40:54 -0000    1.56
--- src/bin/psql/copy.c    6 May 2005 03:36:39 -0000
***************
*** 66,71 ****
--- 66,72 ----
      bool        binary;
      bool        oids;
      bool        csv_mode;
+     bool        header;
      char       *delim;
      char       *null;
      char       *quote;
***************
*** 289,294 ****
--- 290,297 ----
                  result->oids = true;
              else if (pg_strcasecmp(token, "csv") == 0)
                  result->csv_mode = true;
+             else if (pg_strcasecmp(token, "header") == 0)
+                 result->header = true;
              else if (pg_strcasecmp(token, "delimiter") == 0)
              {
                  token = strtokx(NULL, whitespace, NULL, "'",
***************
*** 481,486 ****
--- 484,492 ----
      if (options->csv_mode)
          appendPQExpBuffer(&query, " CSV");

+     if (options->header)
+         appendPQExpBuffer(&query, " HEADER");
+
      if (options->quote)
      {
          if (options->quote[0] == '\'')
Index: src/bin/psql/tab-complete.c
===================================================================
RCS file: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v
retrieving revision 1.126
diff -c -c -r1.126 tab-complete.c
*** src/bin/psql/tab-complete.c    4 May 2005 14:25:24 -0000    1.126
--- src/bin/psql/tab-complete.c    6 May 2005 03:36:40 -0000
***************
*** 1040,1046 ****
                pg_strcasecmp(prev3_wd, "TO") == 0))
          {
              static const char *const list_CSV[] =
!             {"QUOTE", "ESCAPE", "FORCE QUOTE", NULL};

              COMPLETE_WITH_LIST(list_CSV);
          }
--- 1040,1046 ----
                pg_strcasecmp(prev3_wd, "TO") == 0))
          {
              static const char *const list_CSV[] =
!             {"HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE", NULL};

              COMPLETE_WITH_LIST(list_CSV);
          }

Re: COPY CSV header line feature

From
Bruce Momjian
Date:
Applied.

---------------------------------------------------------------------------

pgman wrote:
>
> Here is an updated version of this patch, with documentation changes.
>
> I have already updated the gram.y comment you suggested.
>
> ---------------------------------------------------------------------------
>
> Andrew Dunstan wrote:
> >
> > ammended patch attached. sorry for the oversight. I agree with Tom's
> > remark - it's far too easy to miss this.
> >
> > cheers
> >
> > andrew
> >
> > Alvaro Herrera wrote:
> >
> > >On Tue, Mar 15, 2005 at 08:55:36PM -0600, Andrew Dunstan wrote:
> > >
> > >
> > >>Alvaro Herrera said:
> > >>
> > >>
> > >>>On Sun, Mar 13, 2005 at 06:32:20PM -0500, Andrew Dunstan wrote:
> > >>>
> > >>>
> > >>>
> > >>>>The attached patch implements the previously discussed header line
> > >>>>feature for CSV mode COPY. It is triggered by the keyword HEADER
> > >>>>(blame Bruce - he chose it ;-) ).
> > >>>>
> > >>>>
> > >>>I think you should add the new reserved keyword to the
> > >>>unreserved_keywords list or some other.
> > >>>
> > >>>
> > >>Please be more specific. I'll be happy to add in anything I've missed.
> > >>
> > >>
> > >
> > >The Postgres grammar classifies keywords in one of several lists, in
> > >order to make them available as names to users (column names, function
> > >names, etc).  So each time you create a new keyword and add it to the
> > >keywords.c list, you have to add it to one of the lists on gram.y too.
> > >See gram.y line 7669 ff.
> > >
> > >I'd add a comment on this on gram.y:
> > >
> > >Index: gram.y
> > >===================================================================
> > >RCS file: /home/alvherre/cvs/pgsql/src/backend/parser/gram.y,v
> > >retrieving revision 2.484
> > >diff -c -w -b -B -c -r2.484 gram.y
> > >*** gram.y    14 Mar 2005 00:19:36 -0000    2.484
> > >--- gram.y    16 Mar 2005 03:12:48 -0000
> > >***************
> > >*** 327,333 ****
> > >  /*
> > >   * If you make any token changes, update the keyword table in
> > >   * parser/keywords.c and add new keywords to the appropriate one of
> > >!  * the reserved-or-not-so-reserved keyword lists, below.
> > >   */
> > >
> > >  /* ordinary key words in alphabetical order */
> > >--- 327,334 ----
> > >  /*
> > >   * If you make any token changes, update the keyword table in
> > >   * parser/keywords.c and add new keywords to the appropriate one of
> > >!  * the reserved-or-not-so-reserved keyword lists, below; search this
> > >!  * file for "Name classification hierarchy."
> > >   */
> > >
> > >  /* ordinary key words in alphabetical order */
> > >
> > >
> > >
>
>
> >
> > ---------------------------(end of broadcast)---------------------------
> > TIP 4: Don't 'kill -9' the postmaster
>
> --
>   Bruce Momjian                        |  http://candle.pha.pa.us
>   pgman@candle.pha.pa.us               |  (610) 359-1001
>   +  If your life is a hard drive,     |  13 Roberts Road
>   +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

> Index: doc/src/sgml/ref/copy.sgml
> ===================================================================
> RCS file: /cvsroot/pgsql/doc/src/sgml/ref/copy.sgml,v
> retrieving revision 1.63
> diff -c -c -r1.63 copy.sgml
> *** doc/src/sgml/ref/copy.sgml    4 Jan 2005 00:39:53 -0000    1.63
> --- doc/src/sgml/ref/copy.sgml    6 May 2005 03:36:30 -0000
> ***************
> *** 24,34 ****
>   COPY <replaceable class="parameter">tablename</replaceable> [ ( <replaceable class="parameter">column</replaceable>
[,...] ) ] 
>       FROM { '<replaceable class="parameter">filename</replaceable>' | STDIN }
>       [ [ WITH ]
> !           [ BINARY ]
>             [ OIDS ]
>             [ DELIMITER [ AS ] '<replaceable class="parameter">delimiter</replaceable>' ]
>             [ NULL [ AS ] '<replaceable class="parameter">null string</replaceable>' ]
> !           [ CSV [ QUOTE [ AS ] '<replaceable class="parameter">quote</replaceable>' ]
>                   [ ESCAPE [ AS ] '<replaceable class="parameter">escape</replaceable>' ]
>                   [ FORCE NOT NULL <replaceable class="parameter">column</replaceable> [, ...] ]
>
> --- 24,35 ----
>   COPY <replaceable class="parameter">tablename</replaceable> [ ( <replaceable class="parameter">column</replaceable>
[,...] ) ] 
>       FROM { '<replaceable class="parameter">filename</replaceable>' | STDIN }
>       [ [ WITH ]
> !           [ BINARY ]
>             [ OIDS ]
>             [ DELIMITER [ AS ] '<replaceable class="parameter">delimiter</replaceable>' ]
>             [ NULL [ AS ] '<replaceable class="parameter">null string</replaceable>' ]
> !           [ CSV [ HEADER ]
> !                 [ QUOTE [ AS ] '<replaceable class="parameter">quote</replaceable>' ]
>                   [ ESCAPE [ AS ] '<replaceable class="parameter">escape</replaceable>' ]
>                   [ FORCE NOT NULL <replaceable class="parameter">column</replaceable> [, ...] ]
>
> ***************
> *** 36,45 ****
>       TO { '<replaceable class="parameter">filename</replaceable>' | STDOUT }
>       [ [ WITH ]
>             [ BINARY ]
>             [ OIDS ]
>             [ DELIMITER [ AS ] '<replaceable class="parameter">delimiter</replaceable>' ]
>             [ NULL [ AS ] '<replaceable class="parameter">null string</replaceable>' ]
> !           [ CSV [ QUOTE [ AS ] '<replaceable class="parameter">quote</replaceable>' ]
>                   [ ESCAPE [ AS ] '<replaceable class="parameter">escape</replaceable>' ]
>                   [ FORCE QUOTE <replaceable class="parameter">column</replaceable> [, ...] ]
>   </synopsis>
> --- 37,48 ----
>       TO { '<replaceable class="parameter">filename</replaceable>' | STDOUT }
>       [ [ WITH ]
>             [ BINARY ]
> +           [ HEADER ]
>             [ OIDS ]
>             [ DELIMITER [ AS ] '<replaceable class="parameter">delimiter</replaceable>' ]
>             [ NULL [ AS ] '<replaceable class="parameter">null string</replaceable>' ]
> !           [ CSV [ HEADER ]
> !                 [ QUOTE [ AS ] '<replaceable class="parameter">quote</replaceable>' ]
>                   [ ESCAPE [ AS ] '<replaceable class="parameter">escape</replaceable>' ]
>                   [ FORCE QUOTE <replaceable class="parameter">column</replaceable> [, ...] ]
>   </synopsis>
> ***************
> *** 192,197 ****
> --- 195,211 ----
>      </varlistentry>
>
>      <varlistentry>
> +     <term><literal>HEADER</literal></term>
> +     <listitem>
> +      <para>
> +       Specifies 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.
> +      </para>
> +     </listitem>
> +    </varlistentry>
> +
> +    <varlistentry>
>       <term><replaceable class="parameter">quote</replaceable></term>
>       <listitem>
>        <para>
> Index: src/backend/commands/copy.c
> ===================================================================
> RCS file: /cvsroot/pgsql/src/backend/commands/copy.c,v
> retrieving revision 1.242
> diff -c -c -r1.242 copy.c
> *** src/backend/commands/copy.c    6 May 2005 02:56:42 -0000    1.242
> --- src/backend/commands/copy.c    6 May 2005 03:36:31 -0000
> ***************
> *** 130,142 ****
>   /* non-export function prototypes */
>   static void DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
>            char *delim, char *null_print, bool csv_mode, char *quote,
> !          char *escape, List *force_quote_atts, bool fe_copy);
>   static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
>    char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
> !        List *force_quote_atts);
>   static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
>    char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
> !          List *force_notnull_atts);
>   static bool CopyReadLine(char * quote, char * escape);
>   static char *CopyReadAttribute(const char *delim, const char *null_print,
>                     CopyReadResult *result, bool *isnull);
> --- 130,142 ----
>   /* non-export function prototypes */
>   static void DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
>            char *delim, char *null_print, bool csv_mode, char *quote,
> !          char *escape, List *force_quote_atts, bool header_line, bool fe_copy);
>   static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
>    char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
> !        List *force_quote_atts, bool header_line);
>   static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
>    char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
> !          List *force_notnull_atts, bool header_line);
>   static bool CopyReadLine(char * quote, char * escape);
>   static char *CopyReadAttribute(const char *delim, const char *null_print,
>                     CopyReadResult *result, bool *isnull);
> ***************
> *** 694,699 ****
> --- 694,700 ----
>       bool        binary = false;
>       bool        oids = false;
>       bool        csv_mode = false;
> +     bool        header_line = false;
>       char       *delim = NULL;
>       char       *quote = NULL;
>       char       *escape = NULL;
> ***************
> *** 751,756 ****
> --- 752,765 ----
>                            errmsg("conflicting or redundant options")));
>               csv_mode = intVal(defel->arg);
>           }
> +         else if (strcmp(defel->defname, "header") == 0)
> +         {
> +             if (header_line)
> +                 ereport(ERROR,
> +                         (errcode(ERRCODE_SYNTAX_ERROR),
> +                          errmsg("conflicting or redundant options")));
> +             header_line = intVal(defel->arg);
> +         }
>           else if (strcmp(defel->defname, "quote") == 0)
>           {
>               if (quote)
> ***************
> *** 824,829 ****
> --- 833,844 ----
>                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
>                    errmsg("COPY delimiter must be a single character")));
>
> +       /* Check header */
> +     if (!csv_mode && header_line)
> +         ereport(ERROR,
> +                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
> +                  errmsg("COPY HEADER available only in CSV mode")));
> +
>       /* Check quote */
>       if (!csv_mode && quote != NULL)
>           ereport(ERROR,
> ***************
> *** 1014,1020 ****
>               }
>           }
>           CopyFrom(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
> !                  quote, escape, force_notnull_atts);
>       }
>       else
>       {                            /* copy from database to file */
> --- 1029,1035 ----
>               }
>           }
>           CopyFrom(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
> !                  quote, escape, force_notnull_atts, header_line);
>       }
>       else
>       {                            /* copy from database to file */
> ***************
> *** 1078,1084 ****
>           }
>
>           DoCopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
> !                  quote, escape, force_quote_atts, fe_copy);
>       }
>
>       if (!pipe)
> --- 1093,1099 ----
>           }
>
>           DoCopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
> !                  quote, escape, force_quote_atts, header_line, fe_copy);
>       }
>
>       if (!pipe)
> ***************
> *** 1110,1116 ****
>   static void
>   DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
>            char *delim, char *null_print, bool csv_mode, char *quote,
> !          char *escape, List *force_quote_atts, bool fe_copy)
>   {
>       PG_TRY();
>       {
> --- 1125,1131 ----
>   static void
>   DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
>            char *delim, char *null_print, bool csv_mode, char *quote,
> !          char *escape, List *force_quote_atts, bool header_line, bool fe_copy)
>   {
>       PG_TRY();
>       {
> ***************
> *** 1118,1124 ****
>               SendCopyBegin(binary, list_length(attnumlist));
>
>           CopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
> !                quote, escape, force_quote_atts);
>
>           if (fe_copy)
>               SendCopyEnd(binary);
> --- 1133,1139 ----
>               SendCopyBegin(binary, list_length(attnumlist));
>
>           CopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
> !                quote, escape, force_quote_atts, header_line);
>
>           if (fe_copy)
>               SendCopyEnd(binary);
> ***************
> *** 1142,1148 ****
>   static void
>   CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
>          char *delim, char *null_print, bool csv_mode, char *quote,
> !        char *escape, List *force_quote_atts)
>   {
>       HeapTuple    tuple;
>       TupleDesc    tupDesc;
> --- 1157,1163 ----
>   static void
>   CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
>          char *delim, char *null_print, bool csv_mode, char *quote,
> !        char *escape, List *force_quote_atts, bool header_line)
>   {
>       HeapTuple    tuple;
>       TupleDesc    tupDesc;
> ***************
> *** 1225,1230 ****
> --- 1240,1269 ----
>               null_print = (char *)
>                   pg_server_to_client((unsigned char *) null_print,
>                                       strlen(null_print));
> +
> +         /* if a header has been requested send the line */
> +         if (header_line)
> +         {
> +             bool hdr_delim = false;
> +             char *colname;
> +
> +             foreach(cur, attnumlist)
> +             {
> +                 int            attnum = lfirst_int(cur);
> +
> +                 if (hdr_delim)
> +                     CopySendChar(delim[0]);
> +                 hdr_delim = true;
> +
> +                 colname = NameStr(attr[attnum - 1]->attname);
> +
> +                 CopyAttributeOutCSV(colname, delim, quote, escape,
> +                                     strcmp(colname, null_print) == 0);
> +             }
> +
> +             CopySendEndOfRow(binary);
> +
> +         }
>       }
>
>       scandesc = heap_beginscan(rel, ActiveSnapshot, 0, NULL);
> ***************
> *** 1426,1432 ****
>   static void
>   CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
>            char *delim, char *null_print, bool csv_mode, char *quote,
> !          char *escape, List *force_notnull_atts)
>   {
>       HeapTuple    tuple;
>       TupleDesc    tupDesc;
> --- 1465,1471 ----
>   static void
>   CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
>            char *delim, char *null_print, bool csv_mode, char *quote,
> !          char *escape, List *force_notnull_atts, bool header_line)
>   {
>       HeapTuple    tuple;
>       TupleDesc    tupDesc;
> ***************
> *** 1652,1657 ****
> --- 1691,1703 ----
>       errcontext.previous = error_context_stack;
>       error_context_stack = &errcontext;
>
> +     /* on input just throw the header line away */
> +     if (header_line)
> +     {
> +         copy_lineno++;
> +         done = CopyReadLine(quote, escape) ;
> +     }
> +
>       while (!done)
>       {
>           bool        skip_tuple;
> Index: src/backend/parser/gram.y
> ===================================================================
> RCS file: /cvsroot/pgsql/src/backend/parser/gram.y,v
> retrieving revision 2.489
> diff -c -c -r2.489 gram.y
> *** src/backend/parser/gram.y    28 Apr 2005 21:47:14 -0000    2.489
> --- src/backend/parser/gram.y    6 May 2005 03:36:36 -0000
> ***************
> *** 361,367 ****
>
>       GLOBAL GRANT GROUP_P
>
> !     HANDLER HAVING HOLD HOUR_P
>
>       ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT
>       INDEX INHERITS INITIALLY INNER_P INOUT INPUT_P
> --- 361,367 ----
>
>       GLOBAL GRANT GROUP_P
>
> !     HANDLER HAVING HEADER HOLD HOUR_P
>
>       ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT
>       INDEX INHERITS INITIALLY INNER_P INOUT INPUT_P
> ***************
> *** 1443,1448 ****
> --- 1443,1452 ----
>                   {
>                       $$ = makeDefElem("csv", (Node *)makeInteger(TRUE));
>                   }
> +             | HEADER
> +                 {
> +                     $$ = makeDefElem("header", (Node *)makeInteger(TRUE));
> +                 }
>               | QUOTE opt_as Sconst
>                   {
>                       $$ = makeDefElem("quote", (Node *)makeString($3));
> ***************
> *** 7786,7791 ****
> --- 7790,7796 ----
>               | FUNCTION
>               | GLOBAL
>               | HANDLER
> +             | HEADER
>               | HOLD
>               | HOUR_P
>               | IMMEDIATE
> Index: src/backend/parser/keywords.c
> ===================================================================
> RCS file: /cvsroot/pgsql/src/backend/parser/keywords.c,v
> retrieving revision 1.154
> diff -c -c -r1.154 keywords.c
> *** src/backend/parser/keywords.c    31 Dec 2004 22:00:27 -0000    1.154
> --- src/backend/parser/keywords.c    6 May 2005 03:36:36 -0000
> ***************
> *** 148,153 ****
> --- 148,154 ----
>       {"group", GROUP_P},
>       {"handler", HANDLER},
>       {"having", HAVING},
> +     {"header", HEADER},
>       {"hold", HOLD},
>       {"hour", HOUR_P},
>       {"ilike", ILIKE},
> Index: src/bin/psql/copy.c
> ===================================================================
> RCS file: /cvsroot/pgsql/src/bin/psql/copy.c,v
> retrieving revision 1.56
> diff -c -c -r1.56 copy.c
> *** src/bin/psql/copy.c    22 Feb 2005 04:40:54 -0000    1.56
> --- src/bin/psql/copy.c    6 May 2005 03:36:39 -0000
> ***************
> *** 66,71 ****
> --- 66,72 ----
>       bool        binary;
>       bool        oids;
>       bool        csv_mode;
> +     bool        header;
>       char       *delim;
>       char       *null;
>       char       *quote;
> ***************
> *** 289,294 ****
> --- 290,297 ----
>                   result->oids = true;
>               else if (pg_strcasecmp(token, "csv") == 0)
>                   result->csv_mode = true;
> +             else if (pg_strcasecmp(token, "header") == 0)
> +                 result->header = true;
>               else if (pg_strcasecmp(token, "delimiter") == 0)
>               {
>                   token = strtokx(NULL, whitespace, NULL, "'",
> ***************
> *** 481,486 ****
> --- 484,492 ----
>       if (options->csv_mode)
>           appendPQExpBuffer(&query, " CSV");
>
> +     if (options->header)
> +         appendPQExpBuffer(&query, " HEADER");
> +
>       if (options->quote)
>       {
>           if (options->quote[0] == '\'')
> Index: src/bin/psql/tab-complete.c
> ===================================================================
> RCS file: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v
> retrieving revision 1.126
> diff -c -c -r1.126 tab-complete.c
> *** src/bin/psql/tab-complete.c    4 May 2005 14:25:24 -0000    1.126
> --- src/bin/psql/tab-complete.c    6 May 2005 03:36:40 -0000
> ***************
> *** 1040,1046 ****
>                 pg_strcasecmp(prev3_wd, "TO") == 0))
>           {
>               static const char *const list_CSV[] =
> !             {"QUOTE", "ESCAPE", "FORCE QUOTE", NULL};
>
>               COMPLETE_WITH_LIST(list_CSV);
>           }
> --- 1040,1046 ----
>                 pg_strcasecmp(prev3_wd, "TO") == 0))
>           {
>               static const char *const list_CSV[] =
> !             {"HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE", NULL};
>
>               COMPLETE_WITH_LIST(list_CSV);
>           }

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073