On Fri, Sep 11, 2009 at 5:45 PM, Robert Haas <robertmh...@gmail.com> wrote:
> On Fri, Sep 11, 2009 at 5:32 PM, Tom Lane <t...@sss.pgh.pa.us> wrote:
>> Robert Haas <robertmh...@gmail.com> writes:
>>> The biggest problem I have with this change is that it's going to
>>> massively break anyone who is using the existing COPY syntax.
>>
>> Why?  We'd certainly still support the old syntax for existing options,
>> just as we did with EXPLAIN.
>
> None of the syntax proposals upthread had that property, which doesn't
> mean we can't do it.  However, we'd need some way to differentiate the
> old syntax from the new one. I guess we could throw an unnecessary set
> of parentheses around the option list (blech), but you have to be able
> to tell from the first token which kind of list you're reading if you
> want to support both sets of syntax.

Here's a half-baked proof of concept for the above approach.  This
probably needs more testing than I've given it, and I haven't
attempted to fix the psql parser or update the documentation, but it's
at least an outline of a solution.  I did patch all the regression
tests to use the new syntax, so you can look at that part of the patch
to get a flavor for it.  If this is broadly acceptable I can attempt
to nail down the details, or someone else is welcome to pick it up.
It's on my git repo as well, as usual.

...Robert
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 25,30 ****
--- 25,31 ----
  #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"
***************
*** 745,751 **** DoCopy(const CopyStmt *stmt, const char *queryString)
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			cstate->binary = intVal(defel->arg);
  		}
  		else if (strcmp(defel->defname, "oids") == 0)
  		{
--- 746,752 ----
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			cstate->binary = defGetBoolean(defel);
  		}
  		else if (strcmp(defel->defname, "oids") == 0)
  		{
***************
*** 753,759 **** DoCopy(const CopyStmt *stmt, const char *queryString)
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			cstate->oids = intVal(defel->arg);
  		}
  		else if (strcmp(defel->defname, "delimiter") == 0)
  		{
--- 754,760 ----
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			cstate->oids = defGetBoolean(defel);
  		}
  		else if (strcmp(defel->defname, "delimiter") == 0)
  		{
***************
*** 761,767 **** DoCopy(const CopyStmt *stmt, const char *queryString)
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			cstate->delim = strVal(defel->arg);
  		}
  		else if (strcmp(defel->defname, "null") == 0)
  		{
--- 762,768 ----
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			cstate->delim = defGetString(defel);
  		}
  		else if (strcmp(defel->defname, "null") == 0)
  		{
***************
*** 769,775 **** 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)
  		{
--- 770,776 ----
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			cstate->null_print = defGetString(defel);
  		}
  		else if (strcmp(defel->defname, "csv") == 0)
  		{
***************
*** 777,783 **** DoCopy(const CopyStmt *stmt, const char *queryString)
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			cstate->csv_mode = intVal(defel->arg);
  		}
  		else if (strcmp(defel->defname, "header") == 0)
  		{
--- 778,784 ----
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			cstate->csv_mode = defGetBoolean(defel);
  		}
  		else if (strcmp(defel->defname, "header") == 0)
  		{
***************
*** 785,791 **** DoCopy(const CopyStmt *stmt, const char *queryString)
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			cstate->header_line = intVal(defel->arg);
  		}
  		else if (strcmp(defel->defname, "quote") == 0)
  		{
--- 786,792 ----
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			cstate->header_line = defGetBoolean(defel);
  		}
  		else if (strcmp(defel->defname, "quote") == 0)
  		{
***************
*** 793,799 **** DoCopy(const CopyStmt *stmt, const char *queryString)
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			cstate->quote = strVal(defel->arg);
  		}
  		else if (strcmp(defel->defname, "escape") == 0)
  		{
--- 794,800 ----
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			cstate->quote = defGetString(defel);
  		}
  		else if (strcmp(defel->defname, "escape") == 0)
  		{
***************
*** 801,818 **** DoCopy(const CopyStmt *stmt, const char *queryString)
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			cstate->escape = strVal(defel->arg);
  		}
  		else if (strcmp(defel->defname, "force_quote") == 0)
  		{
  			if (force_quote || force_quote_all)
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
  			if (defel->arg && IsA(defel->arg, A_Star))
  				force_quote_all = true;
! 			else
  				force_quote = (List *) defel->arg;
  		}
  		else if (strcmp(defel->defname, "force_notnull") == 0)
  		{
--- 802,837 ----
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			cstate->escape = defGetString(defel);
  		}
  		else if (strcmp(defel->defname, "force_quote") == 0)
  		{
+ 
  			if (force_quote || force_quote_all)
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
  			if (defel->arg && IsA(defel->arg, A_Star))
  				force_quote_all = true;
! 			else if (defel->arg && IsA(defel->arg, List))
! 			{
! 				ListCell *lc;
! 
  				force_quote = (List *) defel->arg;
+ 				foreach (lc, force_quote)
+ 				{
+ 					if (!IsA(lfirst(lc), String))
+ 						ereport(ERROR,
+ 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 							 errmsg("argument to option \"%s\" must be a list of column names",
+ 								defel->defname)));
+ 				}
+ 			}
+ 			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)
  		{
***************
*** 820,830 **** DoCopy(const CopyStmt *stmt, const char *queryString)
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			force_notnull = (List *) defel->arg;
  		}
  		else
! 			elog(ERROR, "option \"%s\" not recognized",
! 				 defel->defname);
  	}
  
  	/* Check for incompatible options */
--- 839,857 ----
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			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",
! 							defel->defname)));
  		}
  		else
! 			ereport(ERROR,
! 					(errcode(ERRCODE_SYNTAX_ERROR),
! 					 errmsg("option \"%s\" not recognized",
! 							defel->defname)));
  	}
  
  	/* Check for incompatible options */
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 373,378 **** static TypeName *TableFuncTypeName(List *columns);
--- 373,382 ----
  %type <node>	explain_option_arg
  %type <defelt>	explain_option_elem
  %type <list>	explain_option_list
+ %type <str>		copy_generic_option_name
+ %type <node>	copy_generic_option_arg copy_generic_option_arg_item
+ %type <defelt>	copy_generic_option_elem
+ %type <list>	copy_generic_option_list copy_generic_option_arg_list
  
  %type <typnam>	Typename SimpleTypename ConstTypename
  				GenericType Numeric opt_float
***************
*** 1934,1947 **** ClosePortalStmt:
  /*****************************************************************************
   *
   *		QUERY :
!  *				COPY relname ['(' columnList ')'] FROM/TO file [WITH options]
!  *
!  *				BINARY, OIDS, and DELIMITERS kept in old locations
!  *				for backward compatibility.  2002-06-18
   *
   *				COPY ( SELECT ... ) TO file [WITH options]
!  *				This form doesn't have the backwards-compatible option
!  *				syntax.
   *
   *****************************************************************************/
  
--- 1938,1956 ----
  /*****************************************************************************
   *
   *		QUERY :
!  *				New, more generic syntax, supported beginning with PostgreSQL
!  *				8.5.  Options are comma-separated.
!  *				COPY relname ['(' columnList ')'] FROM/TO file '(' options ')'
   *
+  *				Older syntax, used from 7.3 to 8.4 and still supported for
+  *				backwards compatibility
+  *				COPY relname ['(' columnList ')'] FROM/TO file [WITH options]
   *				COPY ( SELECT ... ) TO file [WITH options]
!  *
!  *				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' ]
   *
   *****************************************************************************/
  
***************
*** 2001,2006 **** copy_file_name:
--- 2010,2016 ----
  
  copy_opt_list:
  			copy_opt_list copy_opt_item				{ $$ = lappend($1, $2); }
+ 			| '(' copy_generic_option_list ')'		{ $$ = $2 ; }
  			| /* EMPTY */							{ $$ = NIL; }
  		;
  
***************
*** 2084,2089 **** opt_using:
--- 2094,2145 ----
  			| /*EMPTY*/								{}
  		;
  
+ copy_generic_option_list:
+ 			copy_generic_option_elem
+ 				{
+ 					$$ = list_make1($1);
+ 				}
+ 			| copy_generic_option_list ',' copy_generic_option_elem
+ 				{
+ 					$$ = lappend($1, $3);
+ 				}
+ 		;
+ 
+ copy_generic_option_elem:
+ 			copy_generic_option_name copy_generic_option_arg
+ 				{
+ 					$$ = makeDefElem($1, $2);
+ 				}
+ 		;
+ 
+ copy_generic_option_name:
+ 			ColLabel								{ $$ = $1; }
+ 		;
+ 
+ copy_generic_option_arg:
+ 			  copy_generic_option_arg_item			{ $$ = $1; }
+ 			| '(' copy_generic_option_arg_list ')'	{ $$ = (Node *) $2; }
+ 			| '(' ')'								{ $$ = NULL; }
+ 			| /* EMPTY */							{ $$ = NULL; }
+ 		;
+ 
+ copy_generic_option_arg_list:
+ 			  copy_generic_option_arg_item
+ 				{
+ 					$$ = list_make1($1);
+ 				}
+ 			| copy_generic_option_arg_list ',' copy_generic_option_arg_item
+ 				{
+ 					$$ = lappend($1, $3);
+ 				}
+ 		;
+ 
+ copy_generic_option_arg_item:
+ 			opt_boolean				{ $$ = (Node *) makeString($1); }
+ 			| ColId_or_Sconst		{ $$ = (Node *) makeString($1); }
+ 			| NumericOnly			{ $$ = (Node *) $1; }
+ 		;
+ 
  
  /*****************************************************************************
   *
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
***************
*** 326,332 **** FROM bitwise_test;
     |  
  (1 row)
  
! COPY bitwise_test FROM STDIN NULL 'null';
  SELECT
    BIT_AND(i2) AS "1",
    BIT_AND(i4) AS "1",
--- 326,332 ----
     |  
  (1 row)
  
! COPY bitwise_test FROM STDIN (NULL 'null');
  SELECT
    BIT_AND(i2) AS "1",
    BIT_AND(i4) AS "1",
***************
*** 401,407 **** FROM bool_test;
     | 
  (1 row)
  
! COPY bool_test FROM STDIN NULL 'null';
  SELECT
    BOOL_AND(b1)     AS "f",
    BOOL_AND(b2)     AS "t",
--- 401,407 ----
     | 
  (1 row)
  
! COPY bool_test FROM STDIN (NULL 'null');
  SELECT
    BOOL_AND(b1)     AS "f",
    BOOL_AND(b2)     AS "t",
*** a/src/test/regress/expected/copy2.out
--- b/src/test/regress/expected/copy2.out
***************
*** 47,55 **** COPY x from stdin;
  ERROR:  extra data after last expected column
  CONTEXT:  COPY x, line 1: "2002	232	40	50	60	70	80"
  -- various COPY options: delimiters, oids, NULL string
! COPY x (b, c, d, e) from stdin with oids delimiter ',' null 'x';
! COPY x from stdin WITH DELIMITER AS ';' NULL AS '';
! COPY x from stdin WITH DELIMITER AS ':' NULL AS E'\\X';
  -- check results of copy in
  SELECT * FROM x;
     a   | b  |     c      |   d    |          e           
--- 47,55 ----
  ERROR:  extra data after last expected column
  CONTEXT:  COPY x, line 1: "2002	232	40	50	60	70	80"
  -- various COPY options: delimiters, oids, NULL string
! COPY x (b, c, d, e) from stdin (oids, delimiter ',', null 'x');
! COPY x from stdin (DELIMITER ';', NULL '');
! COPY x from stdin (DELIMITER ':', NULL E'\\X');
  -- check results of copy in
  SELECT * FROM x;
     a   | b  |     c      |   d    |          e           
***************
*** 89,97 **** CREATE TABLE no_oids (
  INSERT INTO no_oids (a, b) VALUES (5, 10);
  INSERT INTO no_oids (a, b) VALUES (20, 30);
  -- should fail
! COPY no_oids FROM stdin WITH OIDS;
  ERROR:  table "no_oids" does not have OIDs
! COPY no_oids TO stdout WITH OIDS;
  ERROR:  table "no_oids" does not have OIDs
  -- check copy out
  COPY x TO stdout;
--- 89,97 ----
  INSERT INTO no_oids (a, b) VALUES (5, 10);
  INSERT INTO no_oids (a, b) VALUES (20, 30);
  -- should fail
! COPY no_oids FROM stdin (OIDS);
  ERROR:  table "no_oids" does not have OIDs
! COPY no_oids TO stdout (OIDS);
  ERROR:  table "no_oids" does not have OIDs
  -- check copy out
  COPY x TO stdout;
***************
*** 146,152 **** stuff	after trigger fired
  stuff	after trigger fired
  stuff	after trigger fired
  stuff	after trigger fired
! COPY x (b, e) TO stdout WITH NULL 'I''m null';
  I'm null	before trigger fired
  21	before trigger fired
  22	before trigger fired
--- 146,152 ----
  stuff	after trigger fired
  stuff	after trigger fired
  stuff	after trigger fired
! COPY x (b, e) TO stdout (NULL 'I''m null');
  I'm null	before trigger fired
  21	before trigger fired
  22	before trigger fired
***************
*** 197,207 **** COPY y TO stdout WITH CSV FORCE QUOTE *;
  "",
  --test that we read consecutive LFs properly
  CREATE TEMP TABLE testnl (a int, b text, c int);
! COPY testnl FROM stdin CSV;
  -- test end of copy marker
  CREATE TEMP TABLE testeoc (a text);
! COPY testeoc FROM stdin CSV;
! COPY testeoc TO stdout CSV;
  a\.
  \.b
  c\.d
--- 197,207 ----
  "",
  --test that we read consecutive LFs properly
  CREATE TEMP TABLE testnl (a int, b text, c int);
! COPY testnl FROM stdin (CSV);
  -- test end of copy marker
  CREATE TEMP TABLE testeoc (a text);
! COPY testeoc FROM stdin (CSV);
! COPY testeoc TO stdout (CSV);
  a\.
  \.b
  c\.d
*** a/src/test/regress/expected/copyselect.out
--- b/src/test/regress/expected/copyselect.out
***************
*** 93,99 **** v_e
  --
  -- Test headers, CSV and quotes
  --
! copy (select t from test1 where id = 1) to stdout csv header force quote t;
  t
  "a"
  --
--- 93,99 ----
  --
  -- Test headers, CSV and quotes
  --
! copy (select t from test1 where id = 1) to stdout (csv, header, force_quote (t));
  t
  "a"
  --
*** a/src/test/regress/sql/aggregates.sql
--- b/src/test/regress/sql/aggregates.sql
***************
*** 104,110 **** SELECT
    BIT_OR(i4)  AS "?"
  FROM bitwise_test;
  
! COPY bitwise_test FROM STDIN NULL 'null';
  1	1	1	1	1	B0101
  3	3	3	null	2	B0100
  7	7	7	3	4	B1100
--- 104,110 ----
    BIT_OR(i4)  AS "?"
  FROM bitwise_test;
  
! COPY bitwise_test FROM STDIN (NULL 'null');
  1	1	1	1	1	B0101
  3	3	3	null	2	B0100
  7	7	7	3	4	B1100
***************
*** 171,177 **** SELECT
    BOOL_OR(b3)    AS "n"
  FROM bool_test;
  
! COPY bool_test FROM STDIN NULL 'null';
  TRUE	null	FALSE	null
  FALSE	TRUE	null	null
  null	TRUE	FALSE	null
--- 171,177 ----
    BOOL_OR(b3)    AS "n"
  FROM bool_test;
  
! COPY bool_test FROM STDIN (NULL 'null');
  TRUE	null	FALSE	null
  FALSE	TRUE	null	null
  null	TRUE	FALSE	null
*** a/src/test/regress/sql/copy2.sql
--- b/src/test/regress/sql/copy2.sql
***************
*** 73,89 **** COPY x from stdin;
  \.
  
  -- various COPY options: delimiters, oids, NULL string
! COPY x (b, c, d, e) from stdin with oids delimiter ',' null 'x';
  500000,x,45,80,90
  500001,x,\x,\\x,\\\x
  500002,x,\,,\\\,,\\
  \.
  
! COPY x from stdin WITH DELIMITER AS ';' NULL AS '';
  3000;;c;;
  \.
  
! COPY x from stdin WITH DELIMITER AS ':' NULL AS E'\\X';
  4000:\X:C:\X:\X
  4001:1:empty::
  4002:2:null:\X:\X
--- 73,89 ----
  \.
  
  -- various COPY options: delimiters, oids, NULL string
! COPY x (b, c, d, e) from stdin (oids, delimiter ',', null 'x');
  500000,x,45,80,90
  500001,x,\x,\\x,\\\x
  500002,x,\,,\\\,,\\
  \.
  
! COPY x from stdin (DELIMITER ';', NULL '');
  3000;;c;;
  \.
  
! COPY x from stdin (DELIMITER ':', NULL E'\\X');
  4000:\X:C:\X:\X
  4001:1:empty::
  4002:2:null:\X:\X
***************
*** 108,120 **** INSERT INTO no_oids (a, b) VALUES (5, 10);
  INSERT INTO no_oids (a, b) VALUES (20, 30);
  
  -- should fail
! COPY no_oids FROM stdin WITH OIDS;
! COPY no_oids TO stdout WITH OIDS;
  
  -- check copy out
  COPY x TO stdout;
  COPY x (c, e) TO stdout;
! COPY x (b, e) TO stdout WITH NULL 'I''m null';
  
  CREATE TEMP TABLE y (
  	col1 text,
--- 108,120 ----
  INSERT INTO no_oids (a, b) VALUES (20, 30);
  
  -- should fail
! COPY no_oids FROM stdin (OIDS);
! COPY no_oids TO stdout (OIDS);
  
  -- check copy out
  COPY x TO stdout;
  COPY x (c, e) TO stdout;
! COPY x (b, e) TO stdout (NULL 'I''m null');
  
  CREATE TEMP TABLE y (
  	col1 text,
***************
*** 134,140 **** COPY y TO stdout WITH CSV FORCE QUOTE *;
  
  CREATE TEMP TABLE testnl (a int, b text, c int);
  
! COPY testnl FROM stdin CSV;
  1,"a field with two LFs
  
  inside",2
--- 134,140 ----
  
  CREATE TEMP TABLE testnl (a int, b text, c int);
  
! COPY testnl FROM stdin (CSV);
  1,"a field with two LFs
  
  inside",2
***************
*** 143,156 **** inside",2
  -- test end of copy marker
  CREATE TEMP TABLE testeoc (a text);
  
! COPY testeoc FROM stdin CSV;
  a\.
  \.b
  c\.d
  "\."
  \.
  
! COPY testeoc TO stdout CSV;
  
  DROP TABLE x, y;
  DROP FUNCTION fn_x_before();
--- 143,156 ----
  -- test end of copy marker
  CREATE TEMP TABLE testeoc (a text);
  
! COPY testeoc FROM stdin (CSV);
  a\.
  \.b
  c\.d
  "\."
  \.
  
! COPY testeoc TO stdout (CSV);
  
  DROP TABLE x, y;
  DROP FUNCTION fn_x_before();
*** a/src/test/regress/sql/copyselect.sql
--- b/src/test/regress/sql/copyselect.sql
***************
*** 61,67 **** copy (select * from (select t from test1 where id = 1 UNION select * from v_test
  --
  -- Test headers, CSV and quotes
  --
! copy (select t from test1 where id = 1) to stdout csv header force quote t;
  --
  -- Test psql builtins, plain table
  --
--- 61,67 ----
  --
  -- Test headers, CSV and quotes
  --
! copy (select t from test1 where id = 1) to stdout (csv, header, force_quote (t));
  --
  -- Test psql builtins, plain table
  --
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to