diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml
index e907da0b5d0..0dd10d6754d 100644
--- a/doc/src/sgml/ref/copy.sgml
+++ b/doc/src/sgml/ref/copy.sgml
@@ -1,5 +1,5 @@
@@ -24,11 +24,12 @@ PostgreSQL documentation
COPY tablename [ ( column [, ...] ) ]
FROM { 'filename' | STDIN }
[ [ WITH ]
- [ BINARY ]
+ [ BINARY ]
[ OIDS ]
[ DELIMITER [ AS ] 'delimiter' ]
[ NULL [ AS ] 'null string' ]
- [ CSV [ QUOTE [ AS ] 'quote' ]
+ [ CSV [ HEADER ]
+ [ QUOTE [ AS ] 'quote' ]
[ ESCAPE [ AS ] 'escape' ]
[ FORCE NOT NULL column [, ...] ]
@@ -36,10 +37,12 @@ COPY tablename [ ( filename' | STDOUT }
[ [ WITH ]
[ BINARY ]
+ [ HEADER ]
[ OIDS ]
[ DELIMITER [ AS ] 'delimiter' ]
[ NULL [ AS ] 'null string' ]
- [ CSV [ QUOTE [ AS ] 'quote' ]
+ [ CSV [ HEADER ]
+ [ QUOTE [ AS ] 'quote' ]
[ ESCAPE [ AS ] 'escape' ]
[ FORCE QUOTE column [, ...] ]
@@ -191,6 +194,17 @@ COPY tablename [ (
+
+ HEADER
+
+
+ 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.
+
+
+
+
quote
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index a40d6fc9222..35ae86814dc 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.243 2005/05/06 17:24:53 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.244 2005/05/07 02:22:46 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -131,13 +131,13 @@ static bool line_buf_converted;
/* 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);
+ 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);
+ 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);
+ 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,6 +695,7 @@ DoCopy(const CopyStmt *stmt)
bool binary = false;
bool oids = false;
bool csv_mode = false;
+ bool header_line = false;
char *delim = NULL;
char *quote = NULL;
char *escape = NULL;
@@ -752,6 +753,14 @@ DoCopy(const CopyStmt *stmt)
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)
@@ -825,6 +834,12 @@ DoCopy(const CopyStmt *stmt)
(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,
@@ -1015,7 +1030,7 @@ DoCopy(const CopyStmt *stmt)
}
}
CopyFrom(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
- quote, escape, force_notnull_atts);
+ quote, escape, force_notnull_atts, header_line);
}
else
{ /* copy from database to file */
@@ -1079,7 +1094,7 @@ DoCopy(const CopyStmt *stmt)
}
DoCopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
- quote, escape, force_quote_atts, fe_copy);
+ quote, escape, force_quote_atts, header_line, fe_copy);
}
if (!pipe)
@@ -1111,7 +1126,7 @@ DoCopy(const CopyStmt *stmt)
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)
+ char *escape, List *force_quote_atts, bool header_line, bool fe_copy)
{
PG_TRY();
{
@@ -1119,7 +1134,7 @@ DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
SendCopyBegin(binary, list_length(attnumlist));
CopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
- quote, escape, force_quote_atts);
+ quote, escape, force_quote_atts, header_line);
if (fe_copy)
SendCopyEnd(binary);
@@ -1143,7 +1158,7 @@ DoCopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
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)
+ char *escape, List *force_quote_atts, bool header_line)
{
HeapTuple tuple;
TupleDesc tupDesc;
@@ -1226,6 +1241,30 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
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);
@@ -1427,7 +1466,7 @@ limit_printout_length(StringInfo buf)
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)
+ char *escape, List *force_notnull_atts, bool header_line)
{
HeapTuple tuple;
TupleDesc tupDesc;
@@ -1653,6 +1692,13 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
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;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 90c2f5528e5..c8ab0d3f4df 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.490 2005/05/06 03:42:17 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.491 2005/05/07 02:22:46 momjian Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -362,7 +362,7 @@ static void doNegateFloat(Value *v);
GLOBAL GRANT GROUP_P
- HANDLER HAVING HOLD HOUR_P
+ HANDLER HAVING HEADER HOLD HOUR_P
ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT
INDEX INHERITS INITIALLY INNER_P INOUT INPUT_P
@@ -1444,6 +1444,10 @@ copy_opt_item:
{
$$ = makeDefElem("csv", (Node *)makeInteger(TRUE));
}
+ | HEADER
+ {
+ $$ = makeDefElem("header", (Node *)makeInteger(TRUE));
+ }
| QUOTE opt_as Sconst
{
$$ = makeDefElem("quote", (Node *)makeString($3));
@@ -7787,6 +7791,7 @@ unreserved_keyword:
| FUNCTION
| GLOBAL
| HANDLER
+ | HEADER
| HOLD
| HOUR_P
| IMMEDIATE
diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c
index 00d0c6bd51e..359fb845659 100644
--- a/src/backend/parser/keywords.c
+++ b/src/backend/parser/keywords.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.154 2004/12/31 22:00:27 pgsql Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.155 2005/05/07 02:22:47 momjian Exp $
*
*-------------------------------------------------------------------------
*/
@@ -148,6 +148,7 @@ static const ScanKeyword ScanKeywords[] = {
{"group", GROUP_P},
{"handler", HANDLER},
{"having", HAVING},
+ {"header", HEADER},
{"hold", HOLD},
{"hour", HOUR_P},
{"ilike", ILIKE},
diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c
index 096f81aeb88..0ebe248b0c1 100644
--- a/src/bin/psql/copy.c
+++ b/src/bin/psql/copy.c
@@ -3,7 +3,7 @@
*
* Copyright (c) 2000-2005, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/bin/psql/copy.c,v 1.56 2005/02/22 04:40:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/copy.c,v 1.57 2005/05/07 02:22:49 momjian Exp $
*/
#include "postgres_fe.h"
#include "copy.h"
@@ -66,6 +66,7 @@ struct copy_options
bool binary;
bool oids;
bool csv_mode;
+ bool header;
char *delim;
char *null;
char *quote;
@@ -289,6 +290,8 @@ parse_slash_copy(const char *args)
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,6 +484,9 @@ do_copy(const char *args)
if (options->csv_mode)
appendPQExpBuffer(&query, " CSV");
+ if (options->header)
+ appendPQExpBuffer(&query, " HEADER");
+
if (options->quote)
{
if (options->quote[0] == '\'')
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b45ceeeeed6..4aed36c66a3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3,7 +3,7 @@
*
* Copyright (c) 2000-2005, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.126 2005/05/04 14:25:24 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.127 2005/05/07 02:22:49 momjian Exp $
*/
/*----------------------------------------------------------------------
@@ -1040,7 +1040,7 @@ psql_completion(char *text, int start, int end)
pg_strcasecmp(prev3_wd, "TO") == 0))
{
static const char *const list_CSV[] =
- {"QUOTE", "ESCAPE", "FORCE QUOTE", NULL};
+ {"HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE", NULL};
COMPLETE_WITH_LIST(list_CSV);
}