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
[email protected] | (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);
}
---------------------------(end of broadcast)---------------------------
TIP 7: don't forget to increase your free space map settings