Re: generic copy options - Mailing list pgsql-hackers
From | Pavel Stehule |
---|---|
Subject | Re: generic copy options |
Date | |
Msg-id | 162867790909162110p6e3adfd1t96046980bae51b84@mail.gmail.com Whole thread Raw |
In response to | Re: generic copy options (Emmanuel Cecchet <manu@asterdata.com>) |
Responses |
Re: generic copy options
|
List | pgsql-hackers |
Hello I am not sure about syntax change. Isn't better solve this problem well. This is too simple solution. I thinking, so we able to add new parser for COPY statement and share this paraser between SQL and psql. regards Pavel Stehule 2009/9/17 Emmanuel Cecchet <manu@asterdata.com>: > Robert Haas wrote: >> >> I don't think the way the doc changes are formatted is consistent with >> what we've done elsewhere. I think that breaking the options out as a >> separate block could be OK (because otherwise they have to be >> duplicated between COPY TO and COPY FROM) but it should be done more >> like the way that the SELECT page is done. > > I looked at the way it is done in SELECT and there is a section per clause > (from clause, where clause, ...). So I am not sure how you want to apply > that here besides the copy parameters and the option clause. >> >> Also, you haven't >> documented the syntax 100% correctly: the boolean options work just >> like the boolean explain options - they take an optional argument >> which if omitted defaults to true, but you can also specify 0, 1, >> true, false, on, off. See defGetBoolean. So those should be >> specified as: >> >> BINARY [boolean] >> OIDS [boolean] >> CSV [boolean] >> CSV_HEADER [boolean] >> >> See how we did it in sql-explain.html. >> > > Ok, fixed. >>> >>> I changed the name of the CSV options to prefix them with csv_ to avoid >>> confusion with any future options. I also had to change the grammar to >>> allow >>> '*' as a parameter (needed for cvs_force_quote). >>> >> >> You seem to have introduced a LARGE number of unnecessary whitespace >> changes here which are not going to fly. You need to go through and >> revert all of those. It's hard to tell what you've really changed >> here, but also every whitespace change that gets committed is a >> potential merge conflict for someone else; plus pgindent will >> eventually change it back, thus creating another potential merge >> conflict for someone else. >> > > Sorry, I overlooked a format in Eclipse that formatted the whole file > instead of the block I was working on. This should be fixed now. >> >> I am not 100% sold on renaming all of the CSV-specific options to add >> "csv_". I would like to get an opinion from someone else on whether >> that is a good idea or not. I am fairly certain it is NOT a good idea >> to support BOTH the old and new option names, as you've done here. If >> you're going to rename them, you should update gram.y and change the >> makeDefElem() calls within the copy_opt_list productions to emit the >> new names. >> > > Agreed for the makeDefElem(). > For changing the names, I think that names like 'header', 'escape' and > 'quote' are too generic to not conflict with something that is not csv. If > you think of another format that could be added to copy, it is likely to > re-use the same variable names. The only thing that seems odd is that if you > use a CSV_* option, you still have to add CSV [on] to the option list which > seems kind of redundant. > >>> When we decide to drop the old syntax (in 8.6?), we will be able to clean >>> a >>> lot especially in psql. >>> >> >> Considering that we are still carrying syntax that was deprecated in >> 7.3, I don't think it's likely that we'll phase out the present syntax >> anywhere nearly that quickly. But it's reasonable to ask whether we >> should think about removing support for the pre-7.3 syntax altogether >> for 8.5. It doesn't seem to cost us much to keep that support around, >> but then again it's been deprecated for seven major releases, so it >> might be about time. >> > > While I understand the need for the server to still support the syntax, is > it necessary for newer version of psql to support the old syntax? > > I am attaching the new version of the patch with the current modifications > addressing your comments. > > Emmanuel > > -- > Emmanuel Cecchet > Aster Data Systems > Web: http://www.asterdata.com > > > ### Eclipse Workspace Patch 1.0 > #P Postgres8.5-COPY > Index: src/test/regress/sql/copy2.sql > =================================================================== > RCS file: /home/manu/cvsrepo/pgsql/src/test/regress/sql/copy2.sql,v > retrieving revision 1.18 > diff -u -r1.18 copy2.sql > --- src/test/regress/sql/copy2.sql 25 Jul 2009 00:07:14 -0000 1.18 > +++ src/test/regress/sql/copy2.sql 17 Sep 2009 03:14:48 -0000 > @@ -73,17 +73,17 @@ > \. > > -- various COPY options: delimiters, oids, NULL string > -COPY x (b, c, d, e) from stdin with oids delimiter ',' null 'x'; > +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 WITH DELIMITER AS ';' NULL AS ''; > +COPY x from stdin (DELIMITER ';', NULL ''); > 3000;;c;; > \. > > -COPY x from stdin WITH DELIMITER AS ':' NULL AS E'\\X'; > +COPY x from stdin (DELIMITER ':', NULL E'\\X'); > 4000:\X:C:\X:\X > 4001:1:empty:: > 4002:2:null:\X:\X > @@ -108,13 +108,13 @@ > 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; > +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 WITH NULL 'I''m null'; > +COPY x (b, e) TO stdout (NULL 'I''m null'); > > CREATE TEMP TABLE y ( > col1 text, > @@ -130,11 +130,23 @@ > COPY y TO stdout WITH CSV FORCE QUOTE col2 ESCAPE E'\\'; > COPY y TO stdout WITH CSV FORCE QUOTE *; > > +-- Test new 8.5 syntax > + > +COPY y TO stdout (CSV); > +COPY y TO stdout (CSV, CSV_QUOTE '''', DELIMITER '|'); > +COPY y TO stdout (CSV, CSV_FORCE_QUOTE (col2), CSV_ESCAPE E'\\'); > +COPY y TO stdout (CSV, CSV_FORCE_QUOTE *); > + > +\COPY y TO stdout (CSV) > +\COPY y TO stdout (CSV, CSV_QUOTE '''', DELIMITER '|') > +\COPY y TO stdout (CSV, CSV_FORCE_QUOTE (col2), CSV_ESCAPE E'\\') > +\COPY y TO stdout (CSV, 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; > +COPY testnl FROM stdin (CSV); > 1,"a field with two LFs > > inside",2 > @@ -143,14 +155,14 @@ > -- test end of copy marker > CREATE TEMP TABLE testeoc (a text); > > -COPY testeoc FROM stdin CSV; > +COPY testeoc FROM stdin (CSV); > a\. > \.b > c\.d > "\." > \. > > -COPY testeoc TO stdout CSV; > +COPY testeoc TO stdout (CSV); > > DROP TABLE x, y; > DROP FUNCTION fn_x_before(); > Index: src/test/regress/sql/aggregates.sql > =================================================================== > RCS file: /home/manu/cvsrepo/pgsql/src/test/regress/sql/aggregates.sql,v > retrieving revision 1.15 > diff -u -r1.15 aggregates.sql > --- src/test/regress/sql/aggregates.sql 25 Apr 2009 16:44:56 -0000 1.15 > +++ src/test/regress/sql/aggregates.sql 17 Sep 2009 03:14:48 -0000 > @@ -104,7 +104,7 @@ > BIT_OR(i4) AS "?" > FROM bitwise_test; > > -COPY bitwise_test FROM STDIN NULL 'null'; > +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,7 +171,7 @@ > BOOL_OR(b3) AS "n" > FROM bool_test; > > -COPY bool_test FROM STDIN NULL 'null'; > +COPY bool_test FROM STDIN (NULL 'null'); > TRUE null FALSE null > FALSE TRUE null null > null TRUE FALSE null > Index: src/test/regress/sql/copyselect.sql > =================================================================== > RCS file: /home/manu/cvsrepo/pgsql/src/test/regress/sql/copyselect.sql,v > retrieving revision 1.2 > diff -u -r1.2 copyselect.sql > --- src/test/regress/sql/copyselect.sql 7 Aug 2008 01:11:52 -0000 1.2 > +++ src/test/regress/sql/copyselect.sql 17 Sep 2009 03:14:48 -0000 > @@ -61,7 +61,7 @@ > -- > -- Test headers, CSV and quotes > -- > -copy (select t from test1 where id = 1) to stdout csv header force quote t; > +copy (select t from test1 where id = 1) to stdout (csv, csv_header, > csv_force_quote (t)); > -- > -- Test psql builtins, plain table > -- > Index: src/test/regress/expected/aggregates.out > =================================================================== > RCS file: > /home/manu/cvsrepo/pgsql/src/test/regress/expected/aggregates.out,v > retrieving revision 1.19 > diff -u -r1.19 aggregates.out > --- src/test/regress/expected/aggregates.out 25 Apr 2009 16:44:56 -0000 > 1.19 > +++ src/test/regress/expected/aggregates.out 17 Sep 2009 03:14:48 -0000 > @@ -326,7 +326,7 @@ > | > (1 row) > > -COPY bitwise_test FROM STDIN NULL 'null'; > +COPY bitwise_test FROM STDIN (NULL 'null'); > SELECT > BIT_AND(i2) AS "1", > BIT_AND(i4) AS "1", > @@ -401,7 +401,7 @@ > | > (1 row) > > -COPY bool_test FROM STDIN NULL 'null'; > +COPY bool_test FROM STDIN (NULL 'null'); > SELECT > BOOL_AND(b1) AS "f", > BOOL_AND(b2) AS "t", > Index: src/test/regress/expected/copyselect.out > =================================================================== > RCS file: > /home/manu/cvsrepo/pgsql/src/test/regress/expected/copyselect.out,v > retrieving revision 1.2 > diff -u -r1.2 copyselect.out > --- src/test/regress/expected/copyselect.out 7 Aug 2008 01:11:52 -0000 > 1.2 > +++ src/test/regress/expected/copyselect.out 17 Sep 2009 03:14:48 -0000 > @@ -93,7 +93,7 @@ > -- > -- Test headers, CSV and quotes > -- > -copy (select t from test1 where id = 1) to stdout csv header force quote t; > +copy (select t from test1 where id = 1) to stdout (csv, csv_header, > csv_force_quote (t)); > t > "a" > -- > Index: src/test/regress/expected/copy2.out > =================================================================== > RCS file: /home/manu/cvsrepo/pgsql/src/test/regress/expected/copy2.out,v > retrieving revision 1.27 > diff -u -r1.27 copy2.out > --- src/test/regress/expected/copy2.out 25 Jul 2009 00:07:14 -0000 1.27 > +++ src/test/regress/expected/copy2.out 17 Sep 2009 03:14:48 -0000 > @@ -47,9 +47,9 @@ > 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'; > +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,9 +89,9 @@ > 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 FROM stdin (OIDS); > ERROR: table "no_oids" does not have OIDs > -COPY no_oids TO stdout WITH OIDS; > +COPY no_oids TO stdout (OIDS); > ERROR: table "no_oids" does not have OIDs > -- check copy out > COPY x TO stdout; > @@ -146,7 +146,7 @@ > stuff after trigger fired > stuff after trigger fired > stuff after trigger fired > -COPY x (b, e) TO stdout WITH NULL 'I''m null'; > +COPY x (b, e) TO stdout (NULL 'I''m null'); > I'm null before trigger fired > 21 before trigger fired > 22 before trigger fired > @@ -195,13 +195,46 @@ > "Jackson, Sam","\h" > "It is ""perfect""."," " > "", > +-- Test new 8.5 syntax > +COPY y TO stdout (CSV); > +"Jackson, Sam",\h > +"It is ""perfect"".", > +"", > +COPY y TO stdout (CSV, CSV_QUOTE '''', DELIMITER '|'); > +Jackson, Sam|\h > +It is "perfect".| > +''| > +COPY y TO stdout (CSV, CSV_FORCE_QUOTE (col2), CSV_ESCAPE E'\\'); > +"Jackson, Sam","\\h" > +"It is \"perfect\"."," " > +"", > +COPY y TO stdout (CSV, CSV_FORCE_QUOTE *); > +"Jackson, Sam","\h" > +"It is ""perfect""."," " > +"", > +\COPY y TO stdout (CSV) > +"Jackson, Sam",\h > +"It is ""perfect"".", > +"", > +\COPY y TO stdout (CSV, CSV_QUOTE '''', DELIMITER '|') > +Jackson, Sam|\h > +It is "perfect".| > +''| > +\COPY y TO stdout (CSV, CSV_FORCE_QUOTE (col2), CSV_ESCAPE E'\\') > +"Jackson, Sam",\h > +"It is \"perfect\".", > +"", > +\COPY y TO stdout (CSV, 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; > +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; > +COPY testeoc FROM stdin (CSV); > +COPY testeoc TO stdout (CSV); > a\. > \.b > c\.d > Index: doc/src/sgml/ref/copy.sgml > =================================================================== > RCS file: /home/manu/cvsrepo/pgsql/doc/src/sgml/ref/copy.sgml,v > retrieving revision 1.87 > diff -u -r1.87 copy.sgml > --- doc/src/sgml/ref/copy.sgml 5 Sep 2009 23:58:01 -0000 1.87 > +++ doc/src/sgml/ref/copy.sgml 17 Sep 2009 03:14:48 -0000 > @@ -24,27 +24,24 @@ > <synopsis> > 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> [, ...] ] > + [ ( options [,...] ) ] > > COPY { <replaceable class="parameter">tablename</replaceable> [ ( > <replaceable class="parameter">column</replaceable> [, ...] ) ] | ( > <replaceable class="parameter">query</replaceable> ) } > 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 [ HEADER ] > - [ QUOTE [ AS ] '<replaceable > class="parameter">quote</replaceable>' ] > - [ ESCAPE [ AS ] '<replaceable > class="parameter">escape</replaceable>' ] > - [ FORCE QUOTE { <replaceable > class="parameter">column</replaceable> [, ...] | * } ] > + [ ( options [,...] ) ] > + > +Currently available options are: > + - BINARY [ <replaceable class="parameter">boolean</replaceable> ] > + - OIDS [ <replaceable class="parameter">boolean</replaceable> ] > + - DELIMITER '<replaceable class="parameter">delimiter</replaceable>' > + - NULL '<replaceable class="parameter">null string</replaceable>' > + - CSV [ <replaceable class="parameter">boolean</replaceable> ] > + - CSV_HEADER [ <replaceable class="parameter">boolean</replaceable> ] > + - CSV_QUOTE '<replaceable class="parameter">quote</replaceable>' > + - CSV_ESCAPE '<replaceable class="parameter">escape</replaceable>' > + - CSV_FORCE_QUOTE { ( <replaceable > class="parameter">column</replaceable> [, ...] ) | * } > + - CSV_FORCE_NOT_NULL ( <replaceable > class="parameter">column</replaceable> [, ...] ) > + > </synopsis> > </refsynopsisdiv> > > @@ -143,6 +140,27 @@ > </para> > </listitem> > </varlistentry> > + </variablelist> > + </refsect1> > + > + <refsect1> > + <title>Options</title> > + > + <variablelist> > + > + <varlistentry> > + <term><replaceable class="parameter">boolean</replaceable></term> > + <listitem> > + <para> > + Specifies whether the selected option should be turned on or off. > + You can write <literal>TRUE</literal>, <literal>ON</>, or > + <literal>1</literal> to enable the option, and > <literal>FALSE</literal>, > + <literal>OFF</>, or <literal>0</literal> to disable it. The > + <replaceable class="parameter">boolean</replaceable> value can also > + be omitted, in which case <literal>TRUE</literal> is assumed. > + </para> > + </listitem> > + </varlistentry> > > <varlistentry> > <term><literal>BINARY</literal></term> > @@ -168,7 +186,7 @@ > </varlistentry> > > <varlistentry> > - <term><replaceable class="parameter">delimiter</replaceable></term> > + <term><literal>DELIMITER</literal></term> > <listitem> > <para> > The single ASCII character that separates columns within each row > @@ -179,7 +197,7 @@ > </varlistentry> > > <varlistentry> > - <term><replaceable class="parameter">null string</replaceable></term> > + <term><literal>NULL</literal></term> > <listitem> > <para> > The string that represents a null value. The default is > @@ -211,7 +229,7 @@ > </varlistentry> > > <varlistentry> > - <term><literal>HEADER</literal></term> > + <term><literal>CSV_HEADER</literal></term> > <listitem> > <para> > Specifies that the file contains a header line with the names of each > @@ -222,7 +240,7 @@ > </varlistentry> > > <varlistentry> > - <term><replaceable class="parameter">quote</replaceable></term> > + <term><literal>CSV_QUOTE</literal></term> > <listitem> > <para> > Specifies the ASCII quotation character in <literal>CSV</> mode. > @@ -232,18 +250,18 @@ > </varlistentry> > > <varlistentry> > - <term><replaceable class="parameter">escape</replaceable></term> > + <term><literal>CSV_ESCAPE</literal></term> > <listitem> > <para> > Specifies the ASCII character that should appear before a > - <literal>QUOTE</> data character value in <literal>CSV</> mode. > - The default is the <literal>QUOTE</> value (usually double-quote). > + <literal>CSV_QUOTE</> data character value in <literal>CSV</> mode. > + The default is the <literal>CSV_QUOTE</> value (usually > double-quote). > </para> > </listitem> > </varlistentry> > > <varlistentry> > - <term><literal>FORCE QUOTE</></term> > + <term><literal>CSV_FORCE_QUOTE</></term> > <listitem> > <para> > In <literal>CSV</> <command>COPY TO</> mode, forces quoting to be > @@ -256,7 +274,7 @@ > </varlistentry> > > <varlistentry> > - <term><literal>FORCE NOT NULL</></term> > + <term><literal>CSV_FORCE_NOT_NULL</></term> > <listitem> > <para> > In <literal>CSV</> <command>COPY FROM</> mode, process each > @@ -295,7 +313,7 @@ > </para> > > <para> > - The <literal>BINARY</literal> key word causes all data to be > + The <literal>BINARY</literal> option causes all data to be > stored/read as binary format rather than as text. It is > somewhat faster than the normal text mode, but a binary-format > file is less portable across machine architectures and > @@ -538,12 +556,12 @@ > <para> > The values in each record are separated by the <literal>DELIMITER</> > character. If the value contains the delimiter character, the > - <literal>QUOTE</> character, the <literal>NULL</> string, a carriage > + <literal>CSV_QUOTE</> character, the <literal>NULL</> string, a > carriage > return, or line feed character, then the whole value is prefixed and > - suffixed by the <literal>QUOTE</> character, and any occurrence > - within the value of a <literal>QUOTE</> character or the > - <literal>ESCAPE</> character is preceded by the escape character. > - You can also use <literal>FORCE QUOTE</> to force quotes when > outputting > + suffixed by the <literal>CSV_QUOTE</> character, and any occurrence > + within the value of a <literal>CSV_QUOTE</> character or the > + <literal>CSV_ESCAPE</> character is preceded by the escape character. > + You can also use <literal>CSV_FORCE_QUOTE</> to force quotes when > outputting > non-<literal>NULL</> values in specific columns. > </para> > > @@ -557,7 +575,7 @@ > settings, a <literal>NULL</> is written as an unquoted empty > string, while an empty string is written with double quotes > (<literal>""</>). Reading values follows similar rules. You can > - use <literal>FORCE NOT NULL</> to prevent <literal>NULL</> input > + use <literal>CSV_FORCE_NOT_NULL</> to prevent <literal>NULL</> input > comparisons for specific columns. > </para> > > @@ -577,7 +595,7 @@ > <para> > In <literal>CSV</> mode, all characters are significant. A quoted value > surrounded by white space, or any characters other than > - <literal>DELIMITER</>, will include those characters. This can cause > + <literal>CSV_DELIMITER</>, will include those characters. This can > cause > errors if you import data from a system that pads <literal>CSV</> > lines with white space out to some fixed width. If such a situation > arises you might need to preprocess the <literal>CSV</> file to remove > @@ -759,7 +777,7 @@ > The following example copies a table to the client > using the vertical bar (<literal>|</literal>) as the field delimiter: > <programlisting> > -COPY country TO STDOUT WITH DELIMITER '|'; > +COPY country TO STDOUT (DELIMITER '|'); > </programlisting> > </para> > > @@ -809,6 +827,12 @@ > 0000200 M B A B W E 377 377 377 377 377 377 > </programlisting> > </para> > + <para> > + Multiple options are separated by a comma like: > +<programlisting> > +COPY (SELECT t FROM foo WHERE id = 1) TO STDOUT (CSV, CSV_HEADER, > CSV_FORCE_QUOTE (t)); > +</programlisting> > + </para> > </refsect1> > > <refsect1> > @@ -817,7 +841,35 @@ > <para> > There is no <command>COPY</command> statement in the SQL standard. > </para> > + <para> > + The following syntax was used before <productname>PostgreSQL</> > + version 8.5 and is still supported: > +<synopsis> > +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> [, ...] ] > > +COPY { <replaceable class="parameter">tablename</replaceable> [ ( > <replaceable class="parameter">column</replaceable> [, ...] ) ] | ( > <replaceable class="parameter">query</replaceable> ) } > + 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 [ HEADER ] > + [ QUOTE [ AS ] '<replaceable > class="parameter">quote</replaceable>' ] > + [ ESCAPE [ AS ] '<replaceable > class="parameter">escape</replaceable>' ] > + [ FORCE QUOTE { <replaceable > class="parameter">column</replaceable> [, ...] | * } ] > +</synopsis> > + </para> > <para> > The following syntax was used before <productname>PostgreSQL</> > version 7.3 and is still supported: > Index: src/backend/parser/gram.y > =================================================================== > RCS file: /home/manu/cvsrepo/pgsql/src/backend/parser/gram.y,v > retrieving revision 2.677 > diff -u -r2.677 gram.y > --- src/backend/parser/gram.y 18 Aug 2009 23:40:20 -0000 2.677 > +++ src/backend/parser/gram.y 17 Sep 2009 03:14:48 -0000 > @@ -373,6 +373,10 @@ > %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,14 +1938,19 @@ > /***************************************************************************** > * > * QUERY : > - * COPY relname ['(' columnList ')'] FROM/TO > file [WITH options] > - * > - * BINARY, OIDS, and DELIMITERS kept in old > locations > - * for backward compatibility. 2002-06-18 > + * 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] > - * 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' ] > * > *****************************************************************************/ > > @@ -2001,6 +2010,7 @@ > > copy_opt_list: > copy_opt_list copy_opt_item > { $$ = lappend($1, $2); } > + | '(' copy_generic_option_list ')' { $$ > = $2 ; } > | /* EMPTY */ > { $$ = NIL; } > ; > > @@ -2028,27 +2038,27 @@ > } > | HEADER_P > { > - $$ = makeDefElem("header", (Node > *)makeInteger(TRUE)); > + $$ = makeDefElem("csv_header", (Node > *)makeInteger(TRUE)); > } > | QUOTE opt_as Sconst > { > - $$ = makeDefElem("quote", (Node > *)makeString($3)); > + $$ = makeDefElem("csv_quote", (Node > *)makeString($3)); > } > | ESCAPE opt_as Sconst > { > - $$ = makeDefElem("escape", (Node > *)makeString($3)); > + $$ = makeDefElem("csv_escape", (Node > *)makeString($3)); > } > | FORCE QUOTE columnList > { > - $$ = makeDefElem("force_quote", > (Node *)$3); > + $$ = makeDefElem("csv_force_quote", > (Node *)$3); > } > | FORCE QUOTE '*' > { > - $$ = makeDefElem("force_quote", > (Node *)makeNode(A_Star)); > + $$ = makeDefElem("csv_force_quote", > (Node *)makeNode(A_Star)); > } > | FORCE NOT NULL_P columnList > { > - $$ = makeDefElem("force_notnull", > (Node *)$4); > + $$ = > makeDefElem("csv_force_not_null", (Node *)$4); > } > ; > > @@ -2084,6 +2094,53 @@ > | /*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; } > + | '*' > { $$ = (Node *)makeNode(A_Star); } > + | '(' ')' > { $$ = 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; > } > + ; > + > > /***************************************************************************** > * > Index: src/bin/psql/copy.c > =================================================================== > RCS file: /home/manu/cvsrepo/pgsql/src/bin/psql/copy.c,v > retrieving revision 1.82 > diff -u -r1.82 copy.c > --- src/bin/psql/copy.c 7 Aug 2009 20:16:11 -0000 1.82 > +++ src/bin/psql/copy.c 17 Sep 2009 03:14:48 -0000 > @@ -26,20 +26,27 @@ > #include "prompt.h" > #include "stringutils.h" > > - > /* > * parse_slash_copy > * -- parses \copy command line > * > * The documented syntax is: > + * Since 8.5: > + * \copy tablename [(columnlist)] from|to filename [( options )] > + * > + * options is a comma separated list of options. Currently supported > options: > + * binary, oids, delimiter, null, csv, csv_header, csv_espace, csv_quote, > + * csv_force_not_null, csv_force_quote > + * > + * Prior 8.5: > * \copy tablename [(columnlist)] from|to filename > * [ with ] [ binary ] [ oids ] [ delimiter [as] char ] [ null [as] > string ] > - * [ csv [ header ] [ quote [ AS ] string ] escape [as] string > + * [ csv [ header ] [ quote [ AS ] string ] [ escape [as] string ] > * [ force not null column [, ...] | force quote column [, ...] > | * ] ] > * > * \copy ( select stmt ) to filename > * [ with ] [ binary ] [ delimiter [as] char ] [ null [as] string ] > - * [ csv [ header ] [ quote [ AS ] string ] escape [as] string > + * [ csv [ header ] [ quote [ AS ] string ] [ escape [as] string ] > * [ force quote column [, ...] | * ] ] > * > * Force quote only applies for copy to; force not null only applies for > @@ -103,6 +110,45 @@ > *var = newvar; > } > > +/* Extract parameters of the CSV_FORCE_QUOTE or CSV_FORCE_NOT_NULL options. > + * Return true if the list was successfully extracted or false if an error > + * occurred. */ > +static bool extract_csv_force_list(char *list, char *token) > +{ > + const char *whitespace = " \t\n\r"; > + const char *separator = "(,)"; > + > + for (;;) > + { > + token = strtokx(NULL, whitespace, separator, "\"", 0, false, > false, > + pset.encoding); > + > + if (!token) > + return false; > + if (strchr(separator, token[0]) != NULL) > + continue; /* fetch next token */ > + if (!list) > + { > + list = pg_strdup(token); > + if (token[0] == '*') > + return true; > + } > + else > + { > + if (token[0] == '*') /* '*' can only be accepted as > a single argument */ > + return false; > + xstrcat(&list, token); > + } > + token = strtokx(NULL, whitespace, separator, "\"", 0, false, > false, > + pset.encoding); > + if (!token || strchr(")", token[0])) > + break; > + if (strchr(separator, token[0]) != NULL) > + continue; // fetch next token > + xstrcat(&list, token); > + } > + return true; > +} > > static struct copy_options * > parse_slash_copy(const char *args) > @@ -241,18 +287,29 @@ > expand_tilde(&result->file); > } > > - token = strtokx(NULL, whitespace, NULL, NULL, > + token = strtokx(NULL, whitespace, "(", NULL, > 0, false, false, pset.encoding); > > if (token) > { > + char *option_separator = NULL; > + > + /* New 8.5 syntax, option are between () */ > + if (token[0] == '(') > + { > + option_separator = ",)"; > + token = strtokx(NULL, whitespace, NULL, > option_separator, 0, false, false, > + pset.encoding); > + } > /* > * WITH is optional. Also, the backend will allow WITH > followed by > * nothing, so we do too. > */ > - if (pg_strcasecmp(token, "with") == 0) > - token = strtokx(NULL, whitespace, NULL, NULL, > - 0, false, false, > pset.encoding); > + else if (pg_strcasecmp(token, "with") == 0) > + { > + token = strtokx(NULL, whitespace, NULL, NULL, 0, > false, false, > + pset.encoding); > + } > > while (token) > { > @@ -266,7 +323,8 @@ > result->binary = true; > else if (pg_strcasecmp(token, "csv") == 0) > result->csv_mode = true; > - else if (pg_strcasecmp(token, "header") == 0) > + else if ((pg_strcasecmp(token, "header") == 0) || > (pg_strcasecmp( > + token, "csv_header") == 0)) > result->header = true; > else if (pg_strcasecmp(token, "delimiter") == 0) > { > @@ -296,7 +354,8 @@ > else > goto error; > } > - else if (pg_strcasecmp(token, "quote") == 0) > + else if ((pg_strcasecmp(token, "quote") == 0) || > (pg_strcasecmp( > + token, "csv_quote") == 0)) > { > if (result->quote) > goto error; > @@ -310,7 +369,8 @@ > else > goto error; > } > - else if (pg_strcasecmp(token, "escape") == 0) > + else if ((pg_strcasecmp(token, "escape") == 0) || > (pg_strcasecmp( > + token, "csv_escape") == 0)) > { > if (result->escape) > goto error; > @@ -334,22 +394,8 @@ > goto error; > /* handle column list */ > fetch_next = false; > - for (;;) > - { > - token = strtokx(NULL, > whitespace, ",", "\"", > - > 0, false, false, pset.encoding); > - if (!token || strchr(",", > token[0])) > - goto error; > - if > (!result->force_quote_list) > - > result->force_quote_list = pg_strdup(token); > - else > - > xstrcat(&result->force_quote_list, token); > - token = strtokx(NULL, > whitespace, ",", "\"", > - > 0, false, false, pset.encoding); > - if (!token || token[0] != > ',') > - break; > - > xstrcat(&result->force_quote_list, token); > - } > + if > (!extract_csv_force_list(result->force_quote_list, token)) > + goto error; > } > else if (pg_strcasecmp(token, "not") == 0) > { > @@ -361,32 +407,40 @@ > goto error; > /* handle column list */ > fetch_next = false; > - for (;;) > - { > - token = strtokx(NULL, > whitespace, ",", "\"", > - > 0, false, false, pset.encoding); > - if (!token || strchr(",", > token[0])) > - goto error; > - if > (!result->force_notnull_list) > - > result->force_notnull_list = pg_strdup(token); > - else > - > xstrcat(&result->force_notnull_list, token); > - token = strtokx(NULL, > whitespace, ",", "\"", > - > 0, false, false, pset.encoding); > - if (!token || token[0] != > ',') > - break; > - > xstrcat(&result->force_notnull_list, token); > - } > + if > (!extract_csv_force_list(result->force_notnull_list, > + token)) > + goto error; > } > else > goto error; > } > + else if (pg_strcasecmp(token, "csv_force_quote") == > 0) > + { > + if (result->force_quote_list) > + goto error; > + if > (!extract_csv_force_list(result->force_quote_list, token)) > + goto error; > + } > + else if (pg_strcasecmp(token, "csv_force_not_null") > == 0) > + { > + if (result->force_notnull_list) > + goto error; > + if > (!extract_csv_force_list(result->force_notnull_list, token)) > + goto error; > + } > else > goto error; > > if (fetch_next) > - token = strtokx(NULL, whitespace, NULL, > NULL, > - 0, false, > false, pset.encoding); > + { > + token = strtokx(NULL, whitespace, > option_separator, NULL, 0, > + false, false, > pset.encoding); > + if ((option_separator != NULL) && > (strchr(option_separator, token[0]))) > + { > + token = strtokx(NULL, whitespace, > option_separator, NULL, > + 0, false, false, > pset.encoding); > + } > + } > } > } > > Index: src/test/regress/output/copy.source > =================================================================== > RCS file: /home/manu/cvsrepo/pgsql/src/test/regress/output/copy.source,v > retrieving revision 1.13 > diff -u -r1.13 copy.source > --- src/test/regress/output/copy.source 21 Aug 2007 01:11:31 -0000 1.13 > +++ src/test/regress/output/copy.source 17 Sep 2009 03:14:48 -0000 > @@ -71,3 +71,49 @@ > c1,"col with , comma","col with "" quote" > 1,a,1 > 2,b,2 > +-- Repeat the above tests with the new 8.5 option syntax > +copy copytest to > '/home/manu/workspace/Postgres8.5-COPY/src/test/regress/results/copytest.csv' > (csv); > +truncate copytest2; > +copy copytest2 from > '/home/manu/workspace/Postgres8.5-COPY/src/test/regress/results/copytest.csv' > (csv); > +select * from copytest except select * from copytest2; > + style | test | filler > +-------+------+-------- > +(0 rows) > + > +truncate copytest2; > +copy copytest to > '/home/manu/workspace/Postgres8.5-COPY/src/test/regress/results/copytest.csv' > (csv, csv_quote '''', csv_escape E'\\'); > +copy copytest2 from > '/home/manu/workspace/Postgres8.5-COPY/src/test/regress/results/copytest.csv' > (csv, csv_quote '''', csv_escape E'\\'); > +select * from copytest except select * from copytest2; > + style | test | filler > +-------+------+-------- > +(0 rows) > + > +truncate copytest3; > +copy copytest3 from stdin (csv, csv_header); > +copy copytest3 to stdout (csv, csv_header); > +c1,"col with , comma","col with "" quote" > +1,a,1 > +2,b,2 > +-- Repeat the above tests with the new 8.5 option syntax from psql > +\copy copytest to > '/home/manu/workspace/Postgres8.5-COPY/src/test/regress/results/copytest.csv' > (csv) > +truncate copytest2; > +\copy copytest2 from > '/home/manu/workspace/Postgres8.5-COPY/src/test/regress/results/copytest.csv' > (csv) > +select * from copytest except select * from copytest2; > + style | test | filler > +-------+------+-------- > +(0 rows) > + > +truncate copytest2; > +\copy copytest to > '/home/manu/workspace/Postgres8.5-COPY/src/test/regress/results/copytest.csv' > (csv, csv_quote '''', csv_escape E'\\') > +\copy copytest2 from > '/home/manu/workspace/Postgres8.5-COPY/src/test/regress/results/copytest.csv' > (csv, csv_quote '''', csv_escape E'\\') > +select * from copytest except select * from copytest2; > + style | test | filler > +-------+------+-------- > +(0 rows) > + > +truncate copytest3; > +\copy copytest3 from stdin (csv, csv_header) > +\copy copytest3 to stdout (csv, csv_header) > +c1,"col with , comma","col with "" quote" > +1,a,1 > +2,b,2 > Index: src/test/regress/input/copy.source > =================================================================== > RCS file: /home/manu/cvsrepo/pgsql/src/test/regress/input/copy.source,v > retrieving revision 1.15 > diff -u -r1.15 copy.source > --- src/test/regress/input/copy.source 21 Aug 2007 01:11:31 -0000 1.15 > +++ src/test/regress/input/copy.source 17 Sep 2009 03:14:48 -0000 > @@ -107,3 +107,58 @@ > > copy copytest3 to stdout csv header; > > +-- Repeat the above tests with the new 8.5 option syntax > + > +copy copytest to '@abs_builddir@/results/copytest.csv' (csv); > + > +truncate copytest2; > + > +copy copytest2 from '@abs_builddir@/results/copytest.csv' (csv); > + > +select * from copytest except select * from copytest2; > + > +truncate copytest2; > + > +copy copytest to '@abs_builddir@/results/copytest.csv' (csv, csv_quote > '''', csv_escape E'\\'); > + > +copy copytest2 from '@abs_builddir@/results/copytest.csv' (csv, csv_quote > '''', csv_escape E'\\'); > + > +select * from copytest except select * from copytest2; > + > +truncate copytest3; > + > +copy copytest3 from stdin (csv, csv_header); > +this is just a line full of junk that would error out if parsed > +1,a,1 > +2,b,2 > +\. > + > +copy copytest3 to stdout (csv, csv_header); > + > +-- Repeat the above tests with the new 8.5 option syntax from psql > + > +\copy copytest to '@abs_builddir@/results/copytest.csv' (csv) > + > +truncate copytest2; > + > +\copy copytest2 from '@abs_builddir@/results/copytest.csv' (csv) > + > +select * from copytest except select * from copytest2; > + > +truncate copytest2; > + > +\copy copytest to '@abs_builddir@/results/copytest.csv' (csv, csv_quote > '''', csv_escape E'\\') > + > +\copy copytest2 from '@abs_builddir@/results/copytest.csv' (csv, csv_quote > '''', csv_escape E'\\') > + > +select * from copytest except select * from copytest2; > + > +truncate copytest3; > + > +\copy copytest3 from stdin (csv, csv_header) > +this is just a line full of junk that would error out if parsed > +1,a,1 > +2,b,2 > +\. > + > +\copy copytest3 to stdout (csv, csv_header) > Index: src/backend/commands/copy.c > =================================================================== > RCS file: /home/manu/cvsrepo/pgsql/src/backend/commands/copy.c,v > retrieving revision 1.316 > diff -u -r1.316 copy.c > --- src/backend/commands/copy.c 29 Jul 2009 20:56:18 -0000 1.316 > +++ src/backend/commands/copy.c 17 Sep 2009 03:14:48 -0000 > @@ -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" > @@ -745,7 +746,7 @@ > ereport(ERROR, > > (errcode(ERRCODE_SYNTAX_ERROR), > errmsg("conflicting or > redundant options"))); > - cstate->binary = intVal(defel->arg); > + cstate->binary = defGetBoolean(defel); > } > else if (strcmp(defel->defname, "oids") == 0) > { > @@ -753,7 +754,7 @@ > 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 +762,7 @@ > 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,7 +770,7 @@ > ereport(ERROR, > > (errcode(ERRCODE_SYNTAX_ERROR), > errmsg("conflicting or > redundant options"))); > - cstate->null_print = strVal(defel->arg); > + cstate->null_print = defGetString(defel); > } > else if (strcmp(defel->defname, "csv") == 0) > { > @@ -777,33 +778,33 @@ > ereport(ERROR, > > (errcode(ERRCODE_SYNTAX_ERROR), > errmsg("conflicting or > redundant options"))); > - cstate->csv_mode = intVal(defel->arg); > + cstate->csv_mode = defGetBoolean(defel); > } > - else if (strcmp(defel->defname, "header") == 0) > + else if (strcmp(defel->defname, "csv_header") == 0) > { > if (cstate->header_line) > 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) > + else if (strcmp(defel->defname, "csv_quote") == 0) > { > if (cstate->quote) > 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) > + else if (strcmp(defel->defname, "csv_escape") == 0) > { > if (cstate->escape) > 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) > + else if (strcmp(defel->defname, "csv_force_quote") == 0) > { > if (force_quote || force_quote_all) > ereport(ERROR, > @@ -811,20 +812,45 @@ > 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)) > + { > + 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) > + else if (strcmp(defel->defname, "csv_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", > + 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 */ > > > -- > Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) > To make changes to your subscription: > http://www.postgresql.org/mailpref/pgsql-hackers > >
pgsql-hackers by date: