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 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 @@ ...@@ -25,6 +25,7 @@
#include "catalog/namespace.h" #include "catalog/namespace.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "commands/copy.h" #include "commands/copy.h"
#include "commands/defrem.h"
#include "commands/trigger.h" #include "commands/trigger.h"
#include "executor/executor.h" #include "executor/executor.h"
#include "libpq/libpq.h" #include "libpq/libpq.h"
...@@ -723,6 +724,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString) ...@@ -723,6 +724,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
List *force_quote = NIL; List *force_quote = NIL;
List *force_notnull = NIL; List *force_notnull = NIL;
bool force_quote_all = false; bool force_quote_all = false;
bool format_specified = false;
AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT); AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT);
AclMode relPerms; AclMode relPerms;
AclMode remainingPerms; AclMode remainingPerms;
...@@ -739,13 +741,25 @@ DoCopy(const CopyStmt *stmt, const char *queryString) ...@@ -739,13 +741,25 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
{ {
DefElem *defel = (DefElem *) lfirst(option); 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, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"))); 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) else if (strcmp(defel->defname, "oids") == 0)
{ {
...@@ -753,7 +767,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString) ...@@ -753,7 +767,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"))); errmsg("conflicting or redundant options")));
cstate->oids = intVal(defel->arg); cstate->oids = defGetBoolean(defel);
} }
else if (strcmp(defel->defname, "delimiter") == 0) else if (strcmp(defel->defname, "delimiter") == 0)
{ {
...@@ -761,7 +775,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString) ...@@ -761,7 +775,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"))); errmsg("conflicting or redundant options")));
cstate->delim = strVal(defel->arg); cstate->delim = defGetString(defel);
} }
else if (strcmp(defel->defname, "null") == 0) else if (strcmp(defel->defname, "null") == 0)
{ {
...@@ -769,15 +783,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString) ...@@ -769,15 +783,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"))); errmsg("conflicting or redundant options")));
cstate->null_print = strVal(defel->arg); cstate->null_print = defGetString(defel);
}
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);
} }
else if (strcmp(defel->defname, "header") == 0) else if (strcmp(defel->defname, "header") == 0)
{ {
...@@ -785,7 +791,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString) ...@@ -785,7 +791,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"))); errmsg("conflicting or redundant options")));
cstate->header_line = intVal(defel->arg); cstate->header_line = defGetBoolean(defel);
} }
else if (strcmp(defel->defname, "quote") == 0) else if (strcmp(defel->defname, "quote") == 0)
{ {
...@@ -793,7 +799,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString) ...@@ -793,7 +799,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"))); errmsg("conflicting or redundant options")));
cstate->quote = strVal(defel->arg); cstate->quote = defGetString(defel);
} }
else if (strcmp(defel->defname, "escape") == 0) else if (strcmp(defel->defname, "escape") == 0)
{ {
...@@ -801,7 +807,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString) ...@@ -801,7 +807,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"))); errmsg("conflicting or redundant options")));
cstate->escape = strVal(defel->arg); cstate->escape = defGetString(defel);
} }
else if (strcmp(defel->defname, "force_quote") == 0) else if (strcmp(defel->defname, "force_quote") == 0)
{ {
...@@ -811,33 +817,44 @@ DoCopy(const CopyStmt *stmt, const char *queryString) ...@@ -811,33 +817,44 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
errmsg("conflicting or redundant options"))); errmsg("conflicting or redundant options")));
if (defel->arg && IsA(defel->arg, A_Star)) if (defel->arg && IsA(defel->arg, A_Star))
force_quote_all = true; force_quote_all = true;
else else if (defel->arg && IsA(defel->arg, List))
force_quote = (List *) defel->arg; 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) if (force_notnull)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options"))); 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 else
elog(ERROR, "option \"%s\" not recognized", ereport(ERROR,
defel->defname); (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) if (cstate->binary && cstate->delim)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot specify DELIMITER in BINARY mode"))); 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) if (cstate->binary && cstate->null_print)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* *
* *
* IDENTIFICATION * 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 * DESCRIPTION
* The "DefineFoo" routines take the parse tree and pick out the * The "DefineFoo" routines take the parse tree and pick out the
...@@ -88,6 +88,8 @@ defGetString(DefElem *def) ...@@ -88,6 +88,8 @@ defGetString(DefElem *def)
return TypeNameToString((TypeName *) def->arg); return TypeNameToString((TypeName *) def->arg);
case T_List: case T_List:
return NameListToString((List *) def->arg); return NameListToString((List *) def->arg);
case T_A_Star:
return pstrdup("*");
default: default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(def->arg)); elog(ERROR, "unrecognized node type: %d", (int) nodeTag(def->arg));
} }
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
* *
* *
* IDENTIFICATION * 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 * HISTORY
* AUTHOR DATE MAJOR EVENT * AUTHOR DATE MAJOR EVENT
...@@ -373,6 +373,10 @@ static TypeName *TableFuncTypeName(List *columns); ...@@ -373,6 +373,10 @@ static TypeName *TableFuncTypeName(List *columns);
%type <node> explain_option_arg %type <node> explain_option_arg
%type <defelt> explain_option_elem %type <defelt> explain_option_elem
%type <list> explain_option_list %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 %type <typnam> Typename SimpleTypename ConstTypename
GenericType Numeric opt_float GenericType Numeric opt_float
...@@ -1934,19 +1938,23 @@ ClosePortalStmt: ...@@ -1934,19 +1938,23 @@ ClosePortalStmt:
/***************************************************************************** /*****************************************************************************
* *
* QUERY : * 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 * In the preferred syntax the options are comma-separated
* for backward compatibility. 2002-06-18 * 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] * Really old syntax, from versions 7.2 and prior:
* This form doesn't have the backwards-compatible option * COPY [ BINARY ] table [ WITH OIDS ] FROM/TO file
* syntax. * [ [ 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 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); CopyStmt *n = makeNode(CopyStmt);
n->relation = $3; n->relation = $3;
...@@ -1967,8 +1975,7 @@ CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids ...@@ -1967,8 +1975,7 @@ CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids
n->options = list_concat(n->options, $10); n->options = list_concat(n->options, $10);
$$ = (Node *)n; $$ = (Node *)n;
} }
| COPY select_with_parens TO copy_file_name opt_with | COPY select_with_parens TO copy_file_name opt_with copy_options
copy_opt_list
{ {
CopyStmt *n = makeNode(CopyStmt); CopyStmt *n = makeNode(CopyStmt);
n->relation = NULL; n->relation = NULL;
...@@ -1997,18 +2004,20 @@ copy_file_name: ...@@ -1997,18 +2004,20 @@ copy_file_name:
| STDOUT { $$ = NULL; } | 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_list copy_opt_item { $$ = lappend($1, $2); } copy_opt_list copy_opt_item { $$ = lappend($1, $2); }
| /* EMPTY */ { $$ = NIL; } | /* EMPTY */ { $$ = NIL; }
; ;
copy_opt_item: copy_opt_item:
BINARY BINARY
{ {
$$ = makeDefElem("binary", (Node *)makeInteger(TRUE)); $$ = makeDefElem("format", (Node *)makeString("binary"));
} }
| OIDS | OIDS
{ {
...@@ -2024,7 +2033,7 @@ copy_opt_item: ...@@ -2024,7 +2033,7 @@ copy_opt_item:
} }
| CSV | CSV
{ {
$$ = makeDefElem("csv", (Node *)makeInteger(TRUE)); $$ = makeDefElem("format", (Node *)makeString("csv"));
} }
| HEADER_P | HEADER_P
{ {
...@@ -2048,16 +2057,16 @@ copy_opt_item: ...@@ -2048,16 +2057,16 @@ copy_opt_item:
} }
| FORCE NOT NULL_P columnList | 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: opt_binary:
BINARY BINARY
{ {
$$ = makeDefElem("binary", (Node *)makeInteger(TRUE)); $$ = makeDefElem("format", (Node *)makeString("binary"));
} }
| /*EMPTY*/ { $$ = NULL; } | /*EMPTY*/ { $$ = NULL; }
; ;
...@@ -2071,7 +2080,6 @@ opt_oids: ...@@ -2071,7 +2080,6 @@ opt_oids:
; ;
copy_delimiter: copy_delimiter:
/* USING DELIMITERS kept for backward compatibility. 2002-06-15 */
opt_using DELIMITERS Sconst opt_using DELIMITERS Sconst
{ {
$$ = makeDefElem("delimiter", (Node *)makeString($3)); $$ = makeDefElem("delimiter", (Node *)makeString($3));
...@@ -2084,6 +2092,51 @@ opt_using: ...@@ -2084,6 +2092,51 @@ opt_using:
| /*EMPTY*/ {} | /*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 *; ...@@ -195,6 +195,39 @@ COPY y TO stdout WITH CSV FORCE QUOTE *;
"Jackson, Sam","\h" "Jackson, Sam","\h"
"It is ""perfect""."," " "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 --test that we read consecutive LFs properly
CREATE TEMP TABLE testnl (a int, b text, c int); CREATE TEMP TABLE testnl (a int, b text, c int);
COPY testnl FROM stdin CSV; COPY testnl FROM stdin CSV;
......
...@@ -130,6 +130,18 @@ COPY y TO stdout WITH CSV QUOTE '''' DELIMITER '|'; ...@@ -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 col2 ESCAPE E'\\';
COPY y TO stdout WITH CSV FORCE QUOTE *; 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 --test that we read consecutive LFs properly
CREATE TEMP TABLE testnl (a int, b text, c int); 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