Commit 923413ac authored by Tom Lane's avatar Tom Lane

Define a new, more extensible syntax for COPY options.

This is intentionally similar to the recently revised syntax for EXPLAIN
options, ie, (name value, ...).  The old syntax is still supported for
backwards compatibility, but we intend that any options added in future
will be provided only in the new syntax.

Robert Haas, Emmanuel Cecchet
parent 0f427dfe
This diff is collapsed.
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.316 2009/07/29 20:56:18 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.317 2009/09/21 20:10:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -25,6 +25,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "commands/copy.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "libpq/libpq.h"
......@@ -723,6 +724,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
List *force_quote = NIL;
List *force_notnull = NIL;
bool force_quote_all = false;
bool format_specified = false;
AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT);
AclMode relPerms;
AclMode remainingPerms;
......@@ -739,13 +741,25 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
{
DefElem *defel = (DefElem *) lfirst(option);
if (strcmp(defel->defname, "binary") == 0)
if (strcmp(defel->defname, "format") == 0)
{
if (cstate->binary)
char *fmt = defGetString(defel);
if (format_specified)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
cstate->binary = intVal(defel->arg);
format_specified = true;
if (strcmp(fmt, "text") == 0)
/* default format */ ;
else if (strcmp(fmt, "csv") == 0)
cstate->csv_mode = true;
else if (strcmp(fmt, "binary") == 0)
cstate->binary = true;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("COPY format \"%s\" not recognized", fmt)));
}
else if (strcmp(defel->defname, "oids") == 0)
{
......@@ -753,7 +767,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
cstate->oids = intVal(defel->arg);
cstate->oids = defGetBoolean(defel);
}
else if (strcmp(defel->defname, "delimiter") == 0)
{
......@@ -761,7 +775,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
cstate->delim = strVal(defel->arg);
cstate->delim = defGetString(defel);
}
else if (strcmp(defel->defname, "null") == 0)
{
......@@ -769,15 +783,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
cstate->null_print = strVal(defel->arg);
}
else if (strcmp(defel->defname, "csv") == 0)
{
if (cstate->csv_mode)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
cstate->csv_mode = intVal(defel->arg);
cstate->null_print = defGetString(defel);
}
else if (strcmp(defel->defname, "header") == 0)
{
......@@ -785,7 +791,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
cstate->header_line = intVal(defel->arg);
cstate->header_line = defGetBoolean(defel);
}
else if (strcmp(defel->defname, "quote") == 0)
{
......@@ -793,7 +799,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
cstate->quote = strVal(defel->arg);
cstate->quote = defGetString(defel);
}
else if (strcmp(defel->defname, "escape") == 0)
{
......@@ -801,7 +807,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
cstate->escape = strVal(defel->arg);
cstate->escape = defGetString(defel);
}
else if (strcmp(defel->defname, "force_quote") == 0)
{
......@@ -811,33 +817,44 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
errmsg("conflicting or redundant options")));
if (defel->arg && IsA(defel->arg, A_Star))
force_quote_all = true;
else
else if (defel->arg && IsA(defel->arg, List))
force_quote = (List *) defel->arg;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("argument to option \"%s\" must be a list of column names",
defel->defname)));
}
else if (strcmp(defel->defname, "force_notnull") == 0)
else if (strcmp(defel->defname, "force_not_null") == 0)
{
if (force_notnull)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
force_notnull = (List *) defel->arg;
if (defel->arg && IsA(defel->arg, List))
force_notnull = (List *) defel->arg;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("argument to option \"%s\" must be a list of column names",
defel->defname)));
}
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("option \"%s\" not recognized",
defel->defname)));
}
/* Check for incompatible options */
/*
* Check for incompatible options (must do these two before inserting
* defaults)
*/
if (cstate->binary && cstate->delim)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot specify DELIMITER in BINARY mode")));
if (cstate->binary && cstate->csv_mode)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot specify CSV in BINARY mode")));
if (cstate->binary && cstate->null_print)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
......
......@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/define.c,v 1.105 2009/07/26 23:34:17 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/define.c,v 1.106 2009/09/21 20:10:21 tgl Exp $
*
* DESCRIPTION
* The "DefineFoo" routines take the parse tree and pick out the
......@@ -88,6 +88,8 @@ defGetString(DefElem *def)
return TypeNameToString((TypeName *) def->arg);
case T_List:
return NameListToString((List *) def->arg);
case T_A_Star:
return pstrdup("*");
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(def->arg));
}
......
......@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.677 2009/08/18 23:40:20 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.678 2009/09/21 20:10:21 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
......@@ -373,6 +373,10 @@ static TypeName *TableFuncTypeName(List *columns);
%type <node> explain_option_arg
%type <defelt> explain_option_elem
%type <list> explain_option_list
%type <node> copy_generic_opt_arg copy_generic_opt_arg_list_item
%type <defelt> copy_generic_opt_elem
%type <list> copy_generic_opt_list copy_generic_opt_arg_list
%type <list> copy_options
%type <typnam> Typename SimpleTypename ConstTypename
GenericType Numeric opt_float
......@@ -1934,19 +1938,23 @@ ClosePortalStmt:
/*****************************************************************************
*
* QUERY :
* COPY relname ['(' columnList ')'] FROM/TO file [WITH options]
* COPY relname [(columnList)] FROM/TO file [WITH] [(options)]
* COPY ( SELECT ... ) TO file [WITH] [(options)]
*
* BINARY, OIDS, and DELIMITERS kept in old locations
* for backward compatibility. 2002-06-18
* In the preferred syntax the options are comma-separated
* and use generic identifiers instead of keywords. The pre-8.5
* syntax had a hard-wired, space-separated set of options.
*
* COPY ( SELECT ... ) TO file [WITH options]
* This form doesn't have the backwards-compatible option
* syntax.
* Really old syntax, from versions 7.2 and prior:
* COPY [ BINARY ] table [ WITH OIDS ] FROM/TO file
* [ [ USING ] DELIMITERS 'delimiter' ] ]
* [ WITH NULL AS 'null string' ]
* This option placement is not supported with COPY (SELECT...).
*
*****************************************************************************/
CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids
copy_from copy_file_name copy_delimiter opt_with copy_opt_list
copy_from copy_file_name copy_delimiter opt_with copy_options
{
CopyStmt *n = makeNode(CopyStmt);
n->relation = $3;
......@@ -1967,8 +1975,7 @@ CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids
n->options = list_concat(n->options, $10);
$$ = (Node *)n;
}
| COPY select_with_parens TO copy_file_name opt_with
copy_opt_list
| COPY select_with_parens TO copy_file_name opt_with copy_options
{
CopyStmt *n = makeNode(CopyStmt);
n->relation = NULL;
......@@ -1997,18 +2004,20 @@ copy_file_name:
| STDOUT { $$ = NULL; }
;
copy_options: copy_opt_list { $$ = $1; }
| '(' copy_generic_opt_list ')' { $$ = $2; }
;
/* old COPY option syntax */
copy_opt_list:
copy_opt_list copy_opt_item { $$ = lappend($1, $2); }
| /* EMPTY */ { $$ = NIL; }
;
copy_opt_item:
BINARY
{
$$ = makeDefElem("binary", (Node *)makeInteger(TRUE));
$$ = makeDefElem("format", (Node *)makeString("binary"));
}
| OIDS
{
......@@ -2024,7 +2033,7 @@ copy_opt_item:
}
| CSV
{
$$ = makeDefElem("csv", (Node *)makeInteger(TRUE));
$$ = makeDefElem("format", (Node *)makeString("csv"));
}
| HEADER_P
{
......@@ -2048,16 +2057,16 @@ copy_opt_item:
}
| FORCE NOT NULL_P columnList
{
$$ = makeDefElem("force_notnull", (Node *)$4);
$$ = makeDefElem("force_not_null", (Node *)$4);
}
;
/* The following exist for backward compatibility */
/* The following exist for backward compatibility with very old versions */
opt_binary:
BINARY
{
$$ = makeDefElem("binary", (Node *)makeInteger(TRUE));
$$ = makeDefElem("format", (Node *)makeString("binary"));
}
| /*EMPTY*/ { $$ = NULL; }
;
......@@ -2071,7 +2080,6 @@ opt_oids:
;
copy_delimiter:
/* USING DELIMITERS kept for backward compatibility. 2002-06-15 */
opt_using DELIMITERS Sconst
{
$$ = makeDefElem("delimiter", (Node *)makeString($3));
......@@ -2084,6 +2092,51 @@ opt_using:
| /*EMPTY*/ {}
;
/* new COPY option syntax */
copy_generic_opt_list:
copy_generic_opt_elem
{
$$ = list_make1($1);
}
| copy_generic_opt_list ',' copy_generic_opt_elem
{
$$ = lappend($1, $3);
}
;
copy_generic_opt_elem:
ColLabel copy_generic_opt_arg
{
$$ = makeDefElem($1, $2);
}
;
copy_generic_opt_arg:
opt_boolean { $$ = (Node *) makeString($1); }
| ColId_or_Sconst { $$ = (Node *) makeString($1); }
| NumericOnly { $$ = (Node *) $1; }
| '*' { $$ = (Node *) makeNode(A_Star); }
| '(' copy_generic_opt_arg_list ')' { $$ = (Node *) $2; }
| /* EMPTY */ { $$ = NULL; }
;
copy_generic_opt_arg_list:
copy_generic_opt_arg_list_item
{
$$ = list_make1($1);
}
| copy_generic_opt_arg_list ',' copy_generic_opt_arg_list_item
{
$$ = lappend($1, $3);
}
;
/* beware of emitting non-string list elements here; see commands/define.c */
copy_generic_opt_arg_list_item:
opt_boolean { $$ = (Node *) makeString($1); }
| ColId_or_Sconst { $$ = (Node *) makeString($1); }
;
/*****************************************************************************
*
......
......@@ -195,6 +195,39 @@ COPY y TO stdout WITH CSV FORCE QUOTE *;
"Jackson, Sam","\h"
"It is ""perfect""."," "
"",
-- Repeat above tests with new 8.5 option syntax
COPY y TO stdout (FORMAT CSV);
"Jackson, Sam",\h
"It is ""perfect"".",
"",
COPY y TO stdout (FORMAT CSV, QUOTE '''', DELIMITER '|');
Jackson, Sam|\h
It is "perfect".|
''|
COPY y TO stdout (FORMAT CSV, FORCE_QUOTE (col2), ESCAPE E'\\');
"Jackson, Sam","\\h"
"It is \"perfect\"."," "
"",
COPY y TO stdout (FORMAT CSV, FORCE_QUOTE *);
"Jackson, Sam","\h"
"It is ""perfect""."," "
"",
\copy y TO stdout (FORMAT CSV)
"Jackson, Sam",\h
"It is ""perfect"".",
"",
\copy y TO stdout (FORMAT CSV, QUOTE '''', DELIMITER '|')
Jackson, Sam|\h
It is "perfect".|
''|
\copy y TO stdout (FORMAT CSV, FORCE_QUOTE (col2), ESCAPE E'\\')
"Jackson, Sam","\\h"
"It is \"perfect\"."," "
"",
\copy y TO stdout (FORMAT CSV, FORCE_QUOTE *)
"Jackson, Sam","\h"
"It is ""perfect""."," "
"",
--test that we read consecutive LFs properly
CREATE TEMP TABLE testnl (a int, b text, c int);
COPY testnl FROM stdin CSV;
......
......@@ -130,6 +130,18 @@ COPY y TO stdout WITH CSV QUOTE '''' DELIMITER '|';
COPY y TO stdout WITH CSV FORCE QUOTE col2 ESCAPE E'\\';
COPY y TO stdout WITH CSV FORCE QUOTE *;
-- Repeat above tests with new 8.5 option syntax
COPY y TO stdout (FORMAT CSV);
COPY y TO stdout (FORMAT CSV, QUOTE '''', DELIMITER '|');
COPY y TO stdout (FORMAT CSV, FORCE_QUOTE (col2), ESCAPE E'\\');
COPY y TO stdout (FORMAT CSV, FORCE_QUOTE *);
\copy y TO stdout (FORMAT CSV)
\copy y TO stdout (FORMAT CSV, QUOTE '''', DELIMITER '|')
\copy y TO stdout (FORMAT CSV, FORCE_QUOTE (col2), ESCAPE E'\\')
\copy y TO stdout (FORMAT CSV, FORCE_QUOTE *)
--test that we read consecutive LFs properly
CREATE TEMP TABLE testnl (a int, b text, c int);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment