mirror of
https://github.com/postgres/postgres.git
synced 2025-06-25 01:02:05 +03:00
Complete TODO item:
o -Allow dump/load of CSV format This adds new keywords to COPY and \copy: CSV - enable CSV mode (comma separated variable) QUOTE - specify quote character ESCAPE - specify escape character FORCE - force quoting of specified column LITERAL - suppress null comparison for columns Doc changes included. Regression updates coming from Andrew.
This commit is contained in:
@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
$PostgreSQL: pgsql/doc/src/sgml/ref/copy.sgml,v 1.55 2003/12/13 23:59:07 neilc Exp $
|
$PostgreSQL: pgsql/doc/src/sgml/ref/copy.sgml,v 1.56 2004/04/19 17:22:30 momjian Exp $
|
||||||
PostgreSQL documentation
|
PostgreSQL documentation
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@ -26,7 +26,10 @@ COPY <replaceable class="parameter">tablename</replaceable> [ ( <replaceable cla
|
|||||||
[ BINARY ]
|
[ BINARY ]
|
||||||
[ OIDS ]
|
[ OIDS ]
|
||||||
[ DELIMITER [ AS ] '<replaceable class="parameter">delimiter</replaceable>' ]
|
[ DELIMITER [ AS ] '<replaceable class="parameter">delimiter</replaceable>' ]
|
||||||
[ NULL [ AS ] '<replaceable class="parameter">null string</replaceable>' ] ]
|
[ NULL [ AS ] '<replaceable class="parameter">null string</replaceable>' ]
|
||||||
|
[ CSV [ QUOTE [ AS ] '<replaceable class="parameter">quote</replaceable>' ]
|
||||||
|
[ ESCAPE [ AS ] '<replaceable class="parameter">escape</replaceable>' ]
|
||||||
|
[ LITERAL <replaceable class="parameter">column</replaceable> [, ...] ]
|
||||||
|
|
||||||
COPY <replaceable class="parameter">tablename</replaceable> [ ( <replaceable class="parameter">column</replaceable> [, ...] ) ]
|
COPY <replaceable class="parameter">tablename</replaceable> [ ( <replaceable class="parameter">column</replaceable> [, ...] ) ]
|
||||||
TO { '<replaceable class="parameter">filename</replaceable>' | STDOUT }
|
TO { '<replaceable class="parameter">filename</replaceable>' | STDOUT }
|
||||||
@ -34,7 +37,10 @@ COPY <replaceable class="parameter">tablename</replaceable> [ ( <replaceable cla
|
|||||||
[ BINARY ]
|
[ BINARY ]
|
||||||
[ OIDS ]
|
[ OIDS ]
|
||||||
[ DELIMITER [ AS ] '<replaceable class="parameter">delimiter</replaceable>' ]
|
[ DELIMITER [ AS ] '<replaceable class="parameter">delimiter</replaceable>' ]
|
||||||
[ NULL [ AS ] '<replaceable class="parameter">null string</replaceable>' ] ]
|
[ NULL [ AS ] '<replaceable class="parameter">null string</replaceable>' ]
|
||||||
|
[ CSV [ QUOTE [ AS ] '<replaceable class="parameter">quote</replaceable>' ]
|
||||||
|
[ ESCAPE [ AS ] '<replaceable class="parameter">escape</replaceable>' ]
|
||||||
|
[ FORCE <replaceable class="parameter">column</replaceable> [, ...] ]
|
||||||
</synopsis>
|
</synopsis>
|
||||||
</refsynopsisdiv>
|
</refsynopsisdiv>
|
||||||
|
|
||||||
@ -146,7 +152,8 @@ COPY <replaceable class="parameter">tablename</replaceable> [ ( <replaceable cla
|
|||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
The single character that separates columns within each row
|
The single character that separates columns within each row
|
||||||
(line) of the file. The default is a tab character.
|
(line) of the file. The default is a tab character in text mode,
|
||||||
|
a comma in <literal>CSV</> mode.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -156,20 +163,86 @@ COPY <replaceable class="parameter">tablename</replaceable> [ ( <replaceable cla
|
|||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
The string that represents a null value. The default is
|
The string that represents a null value. The default is
|
||||||
<literal>\N</literal> (backslash-N). You might prefer an empty
|
<literal>\N</literal> (backslash-N) in text mode, and a empty
|
||||||
string, for example.
|
value with no quotes in <literal>CSV</> mode. You might prefer an
|
||||||
|
empty string even in text mode for cases where you don't want to
|
||||||
|
distinguish nulls from empty strings.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<note>
|
<note>
|
||||||
<para>
|
<para>
|
||||||
On a <command>COPY FROM</command>, any data item that matches
|
When using <command>COPY FROM</command>, any data item that matches
|
||||||
this string will be stored as a null value, so you should make
|
this string will be stored as a null value, so you should make
|
||||||
sure that you use the same string as you used with
|
sure that you use the same string as you used with
|
||||||
<command>COPY TO</command>.
|
<command>COPY TO</command>.
|
||||||
</para>
|
</para>
|
||||||
</note>
|
</note>
|
||||||
|
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><literal>CSV</literal></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Enables Comma Separated Variable (<literal>CSV</>) mode. (Also called
|
||||||
|
Comma Separated Value). It sets the default <literal>DELIMITER</> to
|
||||||
|
comma, and <literal>QUOTE</> and <literal>ESCAPE</> values to
|
||||||
|
double-quote.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><replaceable class="parameter">quote</replaceable></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Specifies the quotation character in <literal>CSV</> mode.
|
||||||
|
The default is double-quote.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><replaceable class="parameter">escape</replaceable></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Specifies the 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).
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><literal>FORCE</></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
In <literal>CSV</> <command>COPY TO</> mode, forces quoting
|
||||||
|
to be used for all non-<literal>NULL</> values in each specified
|
||||||
|
column. <literal>NULL</> output is never quoted.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><literal>LITERAL</></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
In <literal>CSV</> <command>COPY FROM</> mode, for each column specified,
|
||||||
|
do not do a <literal>null string</> comparison; instead load the value
|
||||||
|
literally. <literal>QUOTE</> and <literal>ESCAPE</> processing are still
|
||||||
|
performed.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
If the <literal>null string</> is <literal>''</> (the default
|
||||||
|
in <literal>CSV</> mode), a missing input value (<literal>delimiter,
|
||||||
|
delimiter</>), will load as a zero-length string. <literal>Delimiter, quote,
|
||||||
|
quote, delimiter</> is always treated as a zero-length string on input.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
</variablelist>
|
</variablelist>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
|
||||||
@ -233,6 +306,17 @@ COPY <replaceable class="parameter">tablename</replaceable> [ ( <replaceable cla
|
|||||||
constraints on the destination table. However, it will not invoke rules.
|
constraints on the destination table. However, it will not invoke rules.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<command>COPY</command> input and output is affected by
|
||||||
|
<varname>DateStyle </varname>. For portability with other
|
||||||
|
<productname>PostgreSQL</productname> installations which might use
|
||||||
|
non-default <varname>DateStyle</varname> settings,
|
||||||
|
<varname>DateStyle</varname> should be set to <literal>ISO</> before
|
||||||
|
using <command>COPY</>. In <literal>CSV</> mode, use <literal>ISO</>
|
||||||
|
or a <varname>DateStyle</varname> setting appropriate for the
|
||||||
|
external application.
|
||||||
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
<command>COPY</command> stops operation at the first error. This
|
<command>COPY</command> stops operation at the first error. This
|
||||||
should not lead to problems in the event of a <command>COPY
|
should not lead to problems in the event of a <command>COPY
|
||||||
@ -253,7 +337,8 @@ COPY <replaceable class="parameter">tablename</replaceable> [ ( <replaceable cla
|
|||||||
|
|
||||||
<para>
|
<para>
|
||||||
When <command>COPY</command> is used without the <literal>BINARY</literal> option,
|
When <command>COPY</command> is used without the <literal>BINARY</literal> option,
|
||||||
the data read or written is a text file with one line per table row.
|
the data read or written is a text file with one line per table row,
|
||||||
|
unless <literal>CSV</> mode is used.
|
||||||
Columns in a row are separated by the delimiter character.
|
Columns in a row are separated by the delimiter character.
|
||||||
The column values themselves are strings generated by the
|
The column values themselves are strings generated by the
|
||||||
output function, or acceptable to the input function, of each
|
output function, or acceptable to the input function, of each
|
||||||
@ -379,6 +464,63 @@ COPY <replaceable class="parameter">tablename</replaceable> [ ( <replaceable cla
|
|||||||
</para>
|
</para>
|
||||||
</refsect2>
|
</refsect2>
|
||||||
|
|
||||||
|
<refsect2>
|
||||||
|
<title>CSV Format</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This format is used for importing and exporting the Comma
|
||||||
|
Separated Variable (<literal>CSV</>) file format used by many other
|
||||||
|
programs, such as spreadsheets. Instead of the escaping used by
|
||||||
|
<productname>PostgreSQL</productname>'s standard text mode, it
|
||||||
|
produces and recognises the common CSV escaping mechanism.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<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
|
||||||
|
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</> to force quotes when outputting
|
||||||
|
non-<literal>NULL</> values in specific columns.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
In general, the <literal>CSV</> format has no way to distinguish a
|
||||||
|
<literal>NULL</> from an empty string.
|
||||||
|
<productname>PostgreSQL</productname>'s COPY handles this by
|
||||||
|
quoting. A <literal>NULL</> is output as the <literal>NULL</> string
|
||||||
|
and is not quoted, while a data value matching the <literal>NULL</> string
|
||||||
|
is quoted. Therefore, using the default 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>LITERAL</> to prevent <literal>NULL</>
|
||||||
|
input comparisons for specific columns.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<note>
|
||||||
|
<para>
|
||||||
|
CSV mode will both recognize and produce CSV files with quoted
|
||||||
|
values containing embedded carriage returns and line feeds. Thus
|
||||||
|
the files are not strictly one line per table row like text-mode
|
||||||
|
files.
|
||||||
|
</para>
|
||||||
|
</note>
|
||||||
|
|
||||||
|
<note>
|
||||||
|
<para>
|
||||||
|
Many programs produce strange and occasionally perverse CSV files,
|
||||||
|
so the file format is more a convention than a standard. Thus you
|
||||||
|
might encounter some files that cannot be imported using this
|
||||||
|
mechanism, and <command>COPY</> might produce files that other
|
||||||
|
programs can not process.
|
||||||
|
</para>
|
||||||
|
</note>
|
||||||
|
|
||||||
|
</refsect2>
|
||||||
|
|
||||||
<refsect2>
|
<refsect2>
|
||||||
<title>Binary Format</title>
|
<title>Binary Format</title>
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.110 2004/04/12 15:58:52 momjian Exp $
|
$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.111 2004/04/19 17:22:30 momjian Exp $
|
||||||
PostgreSQL documentation
|
PostgreSQL documentation
|
||||||
-->
|
-->
|
||||||
|
|
||||||
@ -711,6 +711,10 @@ testdb=>
|
|||||||
[ <literal>oids</literal> ]
|
[ <literal>oids</literal> ]
|
||||||
[ <literal>delimiter [as] </literal> '<replaceable class="parameter">character</replaceable>' ]
|
[ <literal>delimiter [as] </literal> '<replaceable class="parameter">character</replaceable>' ]
|
||||||
[ <literal>null [as] </literal> '<replaceable class="parameter">string</replaceable>' ]</literal>
|
[ <literal>null [as] </literal> '<replaceable class="parameter">string</replaceable>' ]</literal>
|
||||||
|
[ <literal>csv [ quote [as] </literal> '<replaceable class="parameter">character</replaceable>' ]
|
||||||
|
[ <literal>escape [as] </literal> '<replaceable class="parameter">character</replaceable>' ]
|
||||||
|
[ <literal>force</> <replaceable class="parameter">column_list</replaceable> ]
|
||||||
|
[ <literal>literal</> <replaceable class="parameter">column_list</replaceable> ] ]
|
||||||
</term>
|
</term>
|
||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.220 2004/04/15 22:36:03 momjian Exp $
|
* $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.221 2004/04/19 17:22:30 momjian Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -70,7 +70,8 @@ typedef enum CopyDest
|
|||||||
typedef enum CopyReadResult
|
typedef enum CopyReadResult
|
||||||
{
|
{
|
||||||
NORMAL_ATTR,
|
NORMAL_ATTR,
|
||||||
END_OF_LINE
|
END_OF_LINE,
|
||||||
|
UNTERMINATED_FIELD
|
||||||
} CopyReadResult;
|
} CopyReadResult;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -130,15 +131,22 @@ static bool line_buf_converted;
|
|||||||
|
|
||||||
/* non-export function prototypes */
|
/* non-export function prototypes */
|
||||||
static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
|
static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
|
||||||
char *delim, char *null_print);
|
char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
|
||||||
|
List *force_atts);
|
||||||
static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
|
static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
|
||||||
char *delim, char *null_print);
|
char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
|
||||||
|
List *literal_atts);
|
||||||
static bool CopyReadLine(void);
|
static bool CopyReadLine(void);
|
||||||
static char *CopyReadAttribute(const char *delim, const char *null_print,
|
static char *CopyReadAttribute(const char *delim, const char *null_print,
|
||||||
CopyReadResult *result, bool *isnull);
|
CopyReadResult *result, bool *isnull);
|
||||||
|
static char *CopyReadAttributeCSV(const char *delim, const char *null_print,
|
||||||
|
char *quote, char *escape,
|
||||||
|
CopyReadResult *result, bool *isnull);
|
||||||
static Datum CopyReadBinaryAttribute(int column_no, FmgrInfo *flinfo,
|
static Datum CopyReadBinaryAttribute(int column_no, FmgrInfo *flinfo,
|
||||||
Oid typelem, bool *isnull);
|
Oid typelem, bool *isnull);
|
||||||
static void CopyAttributeOut(char *string, char *delim);
|
static void CopyAttributeOut(char *string, char *delim);
|
||||||
|
static void CopyAttributeOutCSV(char *string, char *delim, char *quote,
|
||||||
|
char *escape, bool force_quote);
|
||||||
static List *CopyGetAttnums(Relation rel, List *attnamelist);
|
static List *CopyGetAttnums(Relation rel, List *attnamelist);
|
||||||
static void limit_printout_length(StringInfo buf);
|
static void limit_printout_length(StringInfo buf);
|
||||||
|
|
||||||
@ -682,8 +690,15 @@ DoCopy(const CopyStmt *stmt)
|
|||||||
List *attnumlist;
|
List *attnumlist;
|
||||||
bool binary = false;
|
bool binary = false;
|
||||||
bool oids = false;
|
bool oids = false;
|
||||||
|
bool csv_mode = false;
|
||||||
char *delim = NULL;
|
char *delim = NULL;
|
||||||
|
char *quote = NULL;
|
||||||
|
char *escape = NULL;
|
||||||
char *null_print = NULL;
|
char *null_print = NULL;
|
||||||
|
List *force = NIL;
|
||||||
|
List *literal = NIL;
|
||||||
|
List *force_atts = NIL;
|
||||||
|
List *literal_atts = NIL;
|
||||||
Relation rel;
|
Relation rel;
|
||||||
AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT);
|
AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT);
|
||||||
AclResult aclresult;
|
AclResult aclresult;
|
||||||
@ -725,6 +740,46 @@ DoCopy(const CopyStmt *stmt)
|
|||||||
errmsg("conflicting or redundant options")));
|
errmsg("conflicting or redundant options")));
|
||||||
null_print = strVal(defel->arg);
|
null_print = strVal(defel->arg);
|
||||||
}
|
}
|
||||||
|
else if (strcmp(defel->defname, "csv") == 0)
|
||||||
|
{
|
||||||
|
if (csv_mode)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
|
errmsg("conflicting or redundant options")));
|
||||||
|
csv_mode = intVal(defel->arg);
|
||||||
|
}
|
||||||
|
else if (strcmp(defel->defname, "quote") == 0)
|
||||||
|
{
|
||||||
|
if (quote)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
|
errmsg("conflicting or redundant options")));
|
||||||
|
quote = strVal(defel->arg);
|
||||||
|
}
|
||||||
|
else if (strcmp(defel->defname, "escape") == 0)
|
||||||
|
{
|
||||||
|
if (escape)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
|
errmsg("conflicting or redundant options")));
|
||||||
|
escape = strVal(defel->arg);
|
||||||
|
}
|
||||||
|
else if (strcmp(defel->defname, "force") == 0)
|
||||||
|
{
|
||||||
|
if (force)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
|
errmsg("conflicting or redundant options")));
|
||||||
|
force = (List *)defel->arg;
|
||||||
|
}
|
||||||
|
else if (strcmp(defel->defname, "literal") == 0)
|
||||||
|
{
|
||||||
|
if (literal)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
|
errmsg("conflicting or redundant options")));
|
||||||
|
literal = (List *)defel->arg;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
elog(ERROR, "option \"%s\" not recognized",
|
elog(ERROR, "option \"%s\" not recognized",
|
||||||
defel->defname);
|
defel->defname);
|
||||||
@ -735,6 +790,11 @@ DoCopy(const CopyStmt *stmt)
|
|||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
errmsg("cannot specify DELIMITER in BINARY mode")));
|
errmsg("cannot specify DELIMITER in BINARY mode")));
|
||||||
|
|
||||||
|
if (binary && csv_mode)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
|
errmsg("cannot specify CSV in BINARY mode")));
|
||||||
|
|
||||||
if (binary && null_print)
|
if (binary && null_print)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
@ -742,10 +802,92 @@ DoCopy(const CopyStmt *stmt)
|
|||||||
|
|
||||||
/* Set defaults */
|
/* Set defaults */
|
||||||
if (!delim)
|
if (!delim)
|
||||||
delim = "\t";
|
delim = csv_mode ? "," : "\t";
|
||||||
|
|
||||||
if (!null_print)
|
if (!null_print)
|
||||||
null_print = "\\N";
|
null_print = csv_mode ? "" : "\\N";
|
||||||
|
|
||||||
|
if (csv_mode)
|
||||||
|
{
|
||||||
|
if (!quote)
|
||||||
|
quote = "\"";
|
||||||
|
if (!escape)
|
||||||
|
escape = quote;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Only single-character delimiter strings are supported.
|
||||||
|
*/
|
||||||
|
if (strlen(delim) != 1)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("COPY delimiter must be a single character")));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check quote
|
||||||
|
*/
|
||||||
|
if (!csv_mode && quote != NULL)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("COPY quote available only in CSV mode")));
|
||||||
|
|
||||||
|
if (csv_mode && strlen(quote) != 1)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("COPY quote must be a single character")));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check escape
|
||||||
|
*/
|
||||||
|
if (!csv_mode && escape != NULL)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("COPY escape available only in CSV mode")));
|
||||||
|
|
||||||
|
if (csv_mode && strlen(escape) != 1)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("COPY escape must be a single character")));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check force
|
||||||
|
*/
|
||||||
|
if (!csv_mode && force != NIL)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("COPY force available only in CSV mode")));
|
||||||
|
if (force != NIL && is_from)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("COPY force only available using COPY TO")));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check literal
|
||||||
|
*/
|
||||||
|
if (!csv_mode && literal != NIL)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("COPY literal available only in CSV mode")));
|
||||||
|
if (literal != NIL && !is_from)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("COPY literal only available using COPY FROM")));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Don't allow the delimiter to appear in the null string.
|
||||||
|
*/
|
||||||
|
if (strchr(null_print, delim[0]) != NULL)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("COPY delimiter must not appear in the NULL specification")));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Don't allow the csv quote char to appear in the null string.
|
||||||
|
*/
|
||||||
|
if (csv_mode && strchr(null_print, quote[0]) != NULL)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("CSV quote character must not appear in the NULL specification")));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Open and lock the relation, using the appropriate lock type.
|
* Open and lock the relation, using the appropriate lock type.
|
||||||
@ -771,22 +913,6 @@ DoCopy(const CopyStmt *stmt)
|
|||||||
errhint("Anyone can COPY to stdout or from stdin. "
|
errhint("Anyone can COPY to stdout or from stdin. "
|
||||||
"psql's \\copy command also works for anyone.")));
|
"psql's \\copy command also works for anyone.")));
|
||||||
|
|
||||||
/*
|
|
||||||
* Presently, only single-character delimiter strings are supported.
|
|
||||||
*/
|
|
||||||
if (strlen(delim) != 1)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
||||||
errmsg("COPY delimiter must be a single character")));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Don't allow the delimiter to appear in the null string.
|
|
||||||
*/
|
|
||||||
if (strchr(null_print, delim[0]) != NULL)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
||||||
errmsg("COPY delimiter must not appear in the NULL specification")));
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Don't allow COPY w/ OIDs to or from a table without them
|
* Don't allow COPY w/ OIDs to or from a table without them
|
||||||
*/
|
*/
|
||||||
@ -801,6 +927,52 @@ DoCopy(const CopyStmt *stmt)
|
|||||||
*/
|
*/
|
||||||
attnumlist = CopyGetAttnums(rel, attnamelist);
|
attnumlist = CopyGetAttnums(rel, attnamelist);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check that FORCE references valid COPY columns
|
||||||
|
*/
|
||||||
|
if (force)
|
||||||
|
{
|
||||||
|
TupleDesc tupDesc = RelationGetDescr(rel);
|
||||||
|
Form_pg_attribute *attr = tupDesc->attrs;
|
||||||
|
List *cur;
|
||||||
|
|
||||||
|
force_atts = CopyGetAttnums(rel, force);
|
||||||
|
|
||||||
|
foreach(cur, force_atts)
|
||||||
|
{
|
||||||
|
int attnum = lfirsti(cur);
|
||||||
|
|
||||||
|
if (!intMember(attnum, attnumlist))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
|
||||||
|
errmsg("FORCE column \"%s\" not referenced by COPY",
|
||||||
|
NameStr(attr[attnum - 1]->attname))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check that LITERAL references valid COPY columns
|
||||||
|
*/
|
||||||
|
if (literal)
|
||||||
|
{
|
||||||
|
List *cur;
|
||||||
|
TupleDesc tupDesc = RelationGetDescr(rel);
|
||||||
|
Form_pg_attribute *attr = tupDesc->attrs;
|
||||||
|
|
||||||
|
literal_atts = CopyGetAttnums(rel, literal);
|
||||||
|
|
||||||
|
foreach(cur, literal_atts)
|
||||||
|
{
|
||||||
|
int attnum = lfirsti(cur);
|
||||||
|
|
||||||
|
if (!intMember(attnum, attnumlist))
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
|
||||||
|
errmsg("LITERAL column \"%s\" not referenced by COPY",
|
||||||
|
NameStr(attr[attnum - 1]->attname))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set up variables to avoid per-attribute overhead.
|
* Set up variables to avoid per-attribute overhead.
|
||||||
*/
|
*/
|
||||||
@ -864,7 +1036,8 @@ DoCopy(const CopyStmt *stmt)
|
|||||||
errmsg("\"%s\" is a directory", filename)));
|
errmsg("\"%s\" is a directory", filename)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CopyFrom(rel, attnumlist, binary, oids, delim, null_print);
|
CopyFrom(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
|
||||||
|
quote, escape, literal_atts);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ /* copy from database to file */
|
{ /* copy from database to file */
|
||||||
@ -926,7 +1099,8 @@ DoCopy(const CopyStmt *stmt)
|
|||||||
errmsg("\"%s\" is a directory", filename)));
|
errmsg("\"%s\" is a directory", filename)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CopyTo(rel, attnumlist, binary, oids, delim, null_print);
|
CopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
|
||||||
|
quote, escape, force_atts);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pipe)
|
if (!pipe)
|
||||||
@ -958,7 +1132,8 @@ DoCopy(const CopyStmt *stmt)
|
|||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
|
CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
|
||||||
char *delim, char *null_print)
|
char *delim, char *null_print, bool csv_mode, char *quote,
|
||||||
|
char *escape, List *force_atts)
|
||||||
{
|
{
|
||||||
HeapTuple tuple;
|
HeapTuple tuple;
|
||||||
TupleDesc tupDesc;
|
TupleDesc tupDesc;
|
||||||
@ -967,6 +1142,7 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
|
|||||||
int attr_count;
|
int attr_count;
|
||||||
Form_pg_attribute *attr;
|
Form_pg_attribute *attr;
|
||||||
FmgrInfo *out_functions;
|
FmgrInfo *out_functions;
|
||||||
|
bool *force_quote;
|
||||||
Oid *elements;
|
Oid *elements;
|
||||||
bool *isvarlena;
|
bool *isvarlena;
|
||||||
char *string;
|
char *string;
|
||||||
@ -988,6 +1164,7 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
|
|||||||
out_functions = (FmgrInfo *) palloc((num_phys_attrs + 1) * sizeof(FmgrInfo));
|
out_functions = (FmgrInfo *) palloc((num_phys_attrs + 1) * sizeof(FmgrInfo));
|
||||||
elements = (Oid *) palloc((num_phys_attrs + 1) * sizeof(Oid));
|
elements = (Oid *) palloc((num_phys_attrs + 1) * sizeof(Oid));
|
||||||
isvarlena = (bool *) palloc((num_phys_attrs + 1) * sizeof(bool));
|
isvarlena = (bool *) palloc((num_phys_attrs + 1) * sizeof(bool));
|
||||||
|
force_quote = (bool *) palloc((num_phys_attrs + 1) * sizeof(bool));
|
||||||
foreach(cur, attnumlist)
|
foreach(cur, attnumlist)
|
||||||
{
|
{
|
||||||
int attnum = lfirsti(cur);
|
int attnum = lfirsti(cur);
|
||||||
@ -1002,6 +1179,11 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
|
|||||||
&out_func_oid, &elements[attnum - 1],
|
&out_func_oid, &elements[attnum - 1],
|
||||||
&isvarlena[attnum - 1]);
|
&isvarlena[attnum - 1]);
|
||||||
fmgr_info(out_func_oid, &out_functions[attnum - 1]);
|
fmgr_info(out_func_oid, &out_functions[attnum - 1]);
|
||||||
|
|
||||||
|
if (intMember(attnum, force_atts))
|
||||||
|
force_quote[attnum - 1] = true;
|
||||||
|
else
|
||||||
|
force_quote[attnum - 1] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1051,7 +1233,6 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
|
|||||||
while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) != NULL)
|
while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) != NULL)
|
||||||
{
|
{
|
||||||
bool need_delim = false;
|
bool need_delim = false;
|
||||||
|
|
||||||
CHECK_FOR_INTERRUPTS();
|
CHECK_FOR_INTERRUPTS();
|
||||||
|
|
||||||
MemoryContextReset(mycontext);
|
MemoryContextReset(mycontext);
|
||||||
@ -1113,7 +1294,15 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
|
|||||||
value,
|
value,
|
||||||
ObjectIdGetDatum(elements[attnum - 1]),
|
ObjectIdGetDatum(elements[attnum - 1]),
|
||||||
Int32GetDatum(attr[attnum - 1]->atttypmod)));
|
Int32GetDatum(attr[attnum - 1]->atttypmod)));
|
||||||
|
if (csv_mode)
|
||||||
|
{
|
||||||
|
CopyAttributeOutCSV(string, delim, quote, escape,
|
||||||
|
(strcmp(string, null_print) == 0 ||
|
||||||
|
force_quote[attnum - 1]));
|
||||||
|
}
|
||||||
|
else
|
||||||
CopyAttributeOut(string, delim);
|
CopyAttributeOut(string, delim);
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1148,6 +1337,7 @@ CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
|
|||||||
pfree(out_functions);
|
pfree(out_functions);
|
||||||
pfree(elements);
|
pfree(elements);
|
||||||
pfree(isvarlena);
|
pfree(isvarlena);
|
||||||
|
pfree(force_quote);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1243,7 +1433,8 @@ limit_printout_length(StringInfo buf)
|
|||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
|
CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
|
||||||
char *delim, char *null_print)
|
char *delim, char *null_print, bool csv_mode, char *quote,
|
||||||
|
char *escape, List *literal_atts)
|
||||||
{
|
{
|
||||||
HeapTuple tuple;
|
HeapTuple tuple;
|
||||||
TupleDesc tupDesc;
|
TupleDesc tupDesc;
|
||||||
@ -1256,9 +1447,10 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
|
|||||||
Oid *elements;
|
Oid *elements;
|
||||||
Oid oid_in_element;
|
Oid oid_in_element;
|
||||||
ExprState **constraintexprs;
|
ExprState **constraintexprs;
|
||||||
|
bool *literal_nullstr;
|
||||||
bool hasConstraints = false;
|
bool hasConstraints = false;
|
||||||
int i;
|
|
||||||
int attnum;
|
int attnum;
|
||||||
|
int i;
|
||||||
List *cur;
|
List *cur;
|
||||||
Oid in_func_oid;
|
Oid in_func_oid;
|
||||||
Datum *values;
|
Datum *values;
|
||||||
@ -1317,6 +1509,7 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
|
|||||||
defmap = (int *) palloc((num_phys_attrs + 1) * sizeof(int));
|
defmap = (int *) palloc((num_phys_attrs + 1) * sizeof(int));
|
||||||
defexprs = (ExprState **) palloc((num_phys_attrs + 1) * sizeof(ExprState *));
|
defexprs = (ExprState **) palloc((num_phys_attrs + 1) * sizeof(ExprState *));
|
||||||
constraintexprs = (ExprState **) palloc0((num_phys_attrs + 1) * sizeof(ExprState *));
|
constraintexprs = (ExprState **) palloc0((num_phys_attrs + 1) * sizeof(ExprState *));
|
||||||
|
literal_nullstr = (bool *) palloc((num_phys_attrs + 1) * sizeof(bool));
|
||||||
|
|
||||||
for (attnum = 1; attnum <= num_phys_attrs; attnum++)
|
for (attnum = 1; attnum <= num_phys_attrs; attnum++)
|
||||||
{
|
{
|
||||||
@ -1333,6 +1526,11 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
|
|||||||
&in_func_oid, &elements[attnum - 1]);
|
&in_func_oid, &elements[attnum - 1]);
|
||||||
fmgr_info(in_func_oid, &in_functions[attnum - 1]);
|
fmgr_info(in_func_oid, &in_functions[attnum - 1]);
|
||||||
|
|
||||||
|
if (intMember(attnum, literal_atts))
|
||||||
|
literal_nullstr[attnum - 1] = true;
|
||||||
|
else
|
||||||
|
literal_nullstr[attnum - 1] = false;
|
||||||
|
|
||||||
/* Get default info if needed */
|
/* Get default info if needed */
|
||||||
if (!intMember(attnum, attnumlist))
|
if (!intMember(attnum, attnumlist))
|
||||||
{
|
{
|
||||||
@ -1389,9 +1587,7 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
|
|||||||
ExecBSInsertTriggers(estate, resultRelInfo);
|
ExecBSInsertTriggers(estate, resultRelInfo);
|
||||||
|
|
||||||
if (!binary)
|
if (!binary)
|
||||||
{
|
|
||||||
file_has_oids = oids; /* must rely on user to tell us this... */
|
file_has_oids = oids; /* must rely on user to tell us this... */
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Read and verify binary header */
|
/* Read and verify binary header */
|
||||||
@ -1500,6 +1696,7 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
|
|||||||
|
|
||||||
if (file_has_oids)
|
if (file_has_oids)
|
||||||
{
|
{
|
||||||
|
/* can't be in CSV mode here */
|
||||||
string = CopyReadAttribute(delim, null_print,
|
string = CopyReadAttribute(delim, null_print,
|
||||||
&result, &isnull);
|
&result, &isnull);
|
||||||
|
|
||||||
@ -1538,14 +1735,27 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
|
|||||||
errmsg("missing data for column \"%s\"",
|
errmsg("missing data for column \"%s\"",
|
||||||
NameStr(attr[m]->attname))));
|
NameStr(attr[m]->attname))));
|
||||||
|
|
||||||
|
if (csv_mode)
|
||||||
|
{
|
||||||
|
string = CopyReadAttributeCSV(delim, null_print, quote,
|
||||||
|
escape, &result, &isnull);
|
||||||
|
if (result == UNTERMINATED_FIELD)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
|
||||||
|
errmsg("unterminated CSV quoted field")));
|
||||||
|
}
|
||||||
|
else
|
||||||
string = CopyReadAttribute(delim, null_print,
|
string = CopyReadAttribute(delim, null_print,
|
||||||
&result, &isnull);
|
&result, &isnull);
|
||||||
|
|
||||||
if (isnull)
|
if (csv_mode && isnull && literal_nullstr[m])
|
||||||
{
|
{
|
||||||
/* we read an SQL NULL, no need to do anything */
|
string = null_print; /* set to NULL string */
|
||||||
|
isnull = false;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
/* we read an SQL NULL, no need to do anything */
|
||||||
|
if (!isnull)
|
||||||
{
|
{
|
||||||
copy_attname = NameStr(attr[m]->attname);
|
copy_attname = NameStr(attr[m]->attname);
|
||||||
values[m] = FunctionCall3(&in_functions[m],
|
values[m] = FunctionCall3(&in_functions[m],
|
||||||
@ -1732,11 +1942,12 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
|
|||||||
pfree(values);
|
pfree(values);
|
||||||
pfree(nulls);
|
pfree(nulls);
|
||||||
|
|
||||||
if (!binary)
|
|
||||||
{
|
|
||||||
pfree(in_functions);
|
pfree(in_functions);
|
||||||
pfree(elements);
|
pfree(elements);
|
||||||
}
|
pfree(defmap);
|
||||||
|
pfree(defexprs);
|
||||||
|
pfree(constraintexprs);
|
||||||
|
pfree(literal_nullstr);
|
||||||
|
|
||||||
ExecDropTupleTable(tupleTable, true);
|
ExecDropTupleTable(tupleTable, true);
|
||||||
|
|
||||||
@ -2070,6 +2281,152 @@ CopyReadAttribute(const char *delim, const char *null_print,
|
|||||||
return attribute_buf.data;
|
return attribute_buf.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read the value of a single attribute in CSV mode,
|
||||||
|
* performing de-escaping as needed. Escaping does not follow the normal
|
||||||
|
* PostgreSQL text mode, but instead "standard" (i.e. common) CSV usage.
|
||||||
|
*
|
||||||
|
* Quoted fields can span lines, in which case the line end is embedded
|
||||||
|
* in the returned string.
|
||||||
|
*
|
||||||
|
* null_print is the null marker string. Note that this is compared to
|
||||||
|
* the pre-de-escaped input string (thus if it is quoted it is not a NULL).
|
||||||
|
*
|
||||||
|
* *result is set to indicate what terminated the read:
|
||||||
|
* NORMAL_ATTR: column delimiter
|
||||||
|
* END_OF_LINE: end of line
|
||||||
|
* UNTERMINATED_FIELD no quote detected at end of a quoted field
|
||||||
|
*
|
||||||
|
* In any case, the string read up to the terminator (or end of file)
|
||||||
|
* is returned.
|
||||||
|
*
|
||||||
|
* *isnull is set true or false depending on whether the input matched
|
||||||
|
* the null marker. Note that the caller cannot check this since the
|
||||||
|
* returned string will be the post-de-escaping equivalent, which may
|
||||||
|
* look the same as some valid data string.
|
||||||
|
*----------
|
||||||
|
*/
|
||||||
|
|
||||||
|
static char *
|
||||||
|
CopyReadAttributeCSV(const char *delim, const char *null_print, char *quote,
|
||||||
|
char *escape, CopyReadResult *result, bool *isnull)
|
||||||
|
{
|
||||||
|
char delimc = delim[0];
|
||||||
|
char quotec = quote[0];
|
||||||
|
char escapec = escape[0];
|
||||||
|
char c;
|
||||||
|
int start_cursor = line_buf.cursor;
|
||||||
|
int end_cursor = start_cursor;
|
||||||
|
int input_len;
|
||||||
|
bool in_quote = false;
|
||||||
|
bool saw_quote = false;
|
||||||
|
|
||||||
|
/* reset attribute_buf to empty */
|
||||||
|
attribute_buf.len = 0;
|
||||||
|
attribute_buf.data[0] = '\0';
|
||||||
|
|
||||||
|
/* set default status */
|
||||||
|
*result = END_OF_LINE;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
/* handle multiline quoted fields */
|
||||||
|
if (in_quote && line_buf.cursor >= line_buf.len)
|
||||||
|
{
|
||||||
|
bool done;
|
||||||
|
|
||||||
|
switch(eol_type)
|
||||||
|
{
|
||||||
|
case EOL_NL:
|
||||||
|
appendStringInfoString(&attribute_buf,"\n");
|
||||||
|
break;
|
||||||
|
case EOL_CR:
|
||||||
|
appendStringInfoString(&attribute_buf,"\r");
|
||||||
|
break;
|
||||||
|
case EOL_CRNL:
|
||||||
|
appendStringInfoString(&attribute_buf,"\r\n");
|
||||||
|
break;
|
||||||
|
case EOL_UNKNOWN:
|
||||||
|
/* shouldn't happen - just keep going */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_lineno++;
|
||||||
|
done = CopyReadLine();
|
||||||
|
if (done && line_buf.len == 0)
|
||||||
|
break;
|
||||||
|
start_cursor = line_buf.cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
end_cursor = line_buf.cursor;
|
||||||
|
if (line_buf.cursor >= line_buf.len)
|
||||||
|
break;
|
||||||
|
c = line_buf.data[line_buf.cursor++];
|
||||||
|
/*
|
||||||
|
* unquoted field delimiter
|
||||||
|
*/
|
||||||
|
if (!in_quote && c == delimc)
|
||||||
|
{
|
||||||
|
*result = NORMAL_ATTR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* start of quoted field (or part of field)
|
||||||
|
*/
|
||||||
|
if (!in_quote && c == quotec)
|
||||||
|
{
|
||||||
|
saw_quote = true;
|
||||||
|
in_quote = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* escape within a quoted field
|
||||||
|
*/
|
||||||
|
if (in_quote && c == escapec)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* peek at the next char if available, and escape it if it
|
||||||
|
* is an escape char or a quote char
|
||||||
|
*/
|
||||||
|
if (line_buf.cursor <= line_buf.len)
|
||||||
|
{
|
||||||
|
char nextc = line_buf.data[line_buf.cursor];
|
||||||
|
if (nextc == escapec || nextc == quotec)
|
||||||
|
{
|
||||||
|
appendStringInfoCharMacro(&attribute_buf, nextc);
|
||||||
|
line_buf.cursor++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* end of quoted field.
|
||||||
|
* Must do this test after testing for escape in case quote char
|
||||||
|
* and escape char are the same (which is the common case).
|
||||||
|
*/
|
||||||
|
if (in_quote && c == quotec)
|
||||||
|
{
|
||||||
|
in_quote = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
appendStringInfoCharMacro(&attribute_buf, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_quote)
|
||||||
|
*result = UNTERMINATED_FIELD;
|
||||||
|
|
||||||
|
/* check whether raw input matched null marker */
|
||||||
|
input_len = end_cursor - start_cursor;
|
||||||
|
if (!saw_quote && input_len == strlen(null_print) &&
|
||||||
|
strncmp(&line_buf.data[start_cursor], null_print, input_len) == 0)
|
||||||
|
*isnull = true;
|
||||||
|
else
|
||||||
|
*isnull = false;
|
||||||
|
|
||||||
|
return attribute_buf.data;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read a binary attribute
|
* Read a binary attribute
|
||||||
*/
|
*/
|
||||||
@ -2195,6 +2552,73 @@ CopyAttributeOut(char *server_string, char *delim)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send CSV representation of one attribute, with conversion and
|
||||||
|
* CSV type escaping
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
CopyAttributeOutCSV(char *server_string, char *delim, char *quote,
|
||||||
|
char *escape, bool force_quote)
|
||||||
|
{
|
||||||
|
char *string;
|
||||||
|
char c;
|
||||||
|
char delimc = delim[0];
|
||||||
|
char quotec = quote[0];
|
||||||
|
char escapec = escape[0];
|
||||||
|
bool need_quote = force_quote;
|
||||||
|
char *test_string;
|
||||||
|
bool same_encoding;
|
||||||
|
int mblen;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
same_encoding = (server_encoding == client_encoding);
|
||||||
|
if (!same_encoding)
|
||||||
|
string = (char *) pg_server_to_client((unsigned char *) server_string,
|
||||||
|
strlen(server_string));
|
||||||
|
else
|
||||||
|
string = server_string;
|
||||||
|
|
||||||
|
/* have to run through the string twice,
|
||||||
|
* first time to see if it needs quoting, second to actually send it
|
||||||
|
*/
|
||||||
|
|
||||||
|
for(test_string = string;
|
||||||
|
!need_quote && (c = *test_string) != '\0';
|
||||||
|
test_string += mblen)
|
||||||
|
{
|
||||||
|
if (c == delimc || c == quotec || c == '\n' || c == '\r')
|
||||||
|
need_quote = true;
|
||||||
|
if (!same_encoding)
|
||||||
|
mblen = pg_encoding_mblen(client_encoding, test_string);
|
||||||
|
else
|
||||||
|
mblen = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (need_quote)
|
||||||
|
CopySendChar(quotec);
|
||||||
|
|
||||||
|
for (; (c = *string) != '\0'; string += mblen)
|
||||||
|
{
|
||||||
|
if (c == quotec || c == escapec)
|
||||||
|
CopySendChar(escapec);
|
||||||
|
|
||||||
|
CopySendChar(c);
|
||||||
|
|
||||||
|
if (!same_encoding)
|
||||||
|
{
|
||||||
|
/* send additional bytes of the char, if any */
|
||||||
|
mblen = pg_encoding_mblen(client_encoding, string);
|
||||||
|
for (i = 1; i < mblen; i++)
|
||||||
|
CopySendChar(string[i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
mblen = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (need_quote)
|
||||||
|
CopySendChar(quotec);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* CopyGetAttnums - build an integer list of attnums to be copied
|
* CopyGetAttnums - build an integer list of attnums to be copied
|
||||||
*
|
*
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.450 2004/04/05 03:07:26 momjian Exp $
|
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.451 2004/04/19 17:22:30 momjian Exp $
|
||||||
*
|
*
|
||||||
* HISTORY
|
* HISTORY
|
||||||
* AUTHOR DATE MAJOR EVENT
|
* AUTHOR DATE MAJOR EVENT
|
||||||
@ -343,7 +343,7 @@ static void doNegateFloat(Value *v);
|
|||||||
CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
|
CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
|
||||||
CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT
|
CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT
|
||||||
COMMITTED CONSTRAINT CONSTRAINTS CONVERSION_P CONVERT COPY CREATE CREATEDB
|
COMMITTED CONSTRAINT CONSTRAINTS CONVERSION_P CONVERT COPY CREATE CREATEDB
|
||||||
CREATEUSER CROSS CURRENT_DATE CURRENT_TIME
|
CREATEUSER CROSS CSV CURRENT_DATE CURRENT_TIME
|
||||||
CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
|
CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
|
||||||
|
|
||||||
DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
|
DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
|
||||||
@ -370,7 +370,7 @@ static void doNegateFloat(Value *v);
|
|||||||
KEY
|
KEY
|
||||||
|
|
||||||
LANCOMPILER LANGUAGE LARGE_P LAST_P LEADING LEFT LEVEL LIKE LIMIT
|
LANCOMPILER LANGUAGE LARGE_P LAST_P LEADING LEFT LEVEL LIKE LIMIT
|
||||||
LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
|
LISTEN LITERAL LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
|
||||||
LOCK_P
|
LOCK_P
|
||||||
|
|
||||||
MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
|
MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
|
||||||
@ -386,6 +386,8 @@ static void doNegateFloat(Value *v);
|
|||||||
PRECISION PRESERVE PREPARE PRIMARY
|
PRECISION PRESERVE PREPARE PRIMARY
|
||||||
PRIOR PRIVILEGES PROCEDURAL PROCEDURE
|
PRIOR PRIVILEGES PROCEDURAL PROCEDURE
|
||||||
|
|
||||||
|
QUOTE
|
||||||
|
|
||||||
READ REAL RECHECK REFERENCES REINDEX RELATIVE_P RENAME REPEATABLE REPLACE
|
READ REAL RECHECK REFERENCES REINDEX RELATIVE_P RENAME REPEATABLE REPLACE
|
||||||
RESET RESTART RESTRICT RETURNS REVOKE RIGHT ROLLBACK ROW ROWS
|
RESET RESTART RESTRICT RETURNS REVOKE RIGHT ROLLBACK ROW ROWS
|
||||||
RULE
|
RULE
|
||||||
@ -1360,6 +1362,26 @@ copy_opt_item:
|
|||||||
{
|
{
|
||||||
$$ = makeDefElem("null", (Node *)makeString($3));
|
$$ = makeDefElem("null", (Node *)makeString($3));
|
||||||
}
|
}
|
||||||
|
| CSV
|
||||||
|
{
|
||||||
|
$$ = makeDefElem("csv", (Node *)makeInteger(TRUE));
|
||||||
|
}
|
||||||
|
| QUOTE opt_as Sconst
|
||||||
|
{
|
||||||
|
$$ = makeDefElem("quote", (Node *)makeString($3));
|
||||||
|
}
|
||||||
|
| ESCAPE opt_as Sconst
|
||||||
|
{
|
||||||
|
$$ = makeDefElem("escape", (Node *)makeString($3));
|
||||||
|
}
|
||||||
|
| FORCE columnList
|
||||||
|
{
|
||||||
|
$$ = makeDefElem("force", (Node *)$2);
|
||||||
|
}
|
||||||
|
| LITERAL columnList
|
||||||
|
{
|
||||||
|
$$ = makeDefElem("literal", (Node *)$2);
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
/* The following exist for backward compatibility */
|
/* The following exist for backward compatibility */
|
||||||
@ -7420,6 +7442,7 @@ unreserved_keyword:
|
|||||||
| COPY
|
| COPY
|
||||||
| CREATEDB
|
| CREATEDB
|
||||||
| CREATEUSER
|
| CREATEUSER
|
||||||
|
| CSV
|
||||||
| CURSOR
|
| CURSOR
|
||||||
| CYCLE
|
| CYCLE
|
||||||
| DATABASE
|
| DATABASE
|
||||||
@ -7473,6 +7496,7 @@ unreserved_keyword:
|
|||||||
| LAST_P
|
| LAST_P
|
||||||
| LEVEL
|
| LEVEL
|
||||||
| LISTEN
|
| LISTEN
|
||||||
|
| LITERAL
|
||||||
| LOAD
|
| LOAD
|
||||||
| LOCAL
|
| LOCAL
|
||||||
| LOCATION
|
| LOCATION
|
||||||
@ -7507,6 +7531,7 @@ unreserved_keyword:
|
|||||||
| PRIVILEGES
|
| PRIVILEGES
|
||||||
| PROCEDURAL
|
| PROCEDURAL
|
||||||
| PROCEDURE
|
| PROCEDURE
|
||||||
|
| QUOTE
|
||||||
| READ
|
| READ
|
||||||
| RECHECK
|
| RECHECK
|
||||||
| REINDEX
|
| REINDEX
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.147 2004/03/11 01:47:40 ishii Exp $
|
* $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.148 2004/04/19 17:22:31 momjian Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -90,6 +90,7 @@ static const ScanKeyword ScanKeywords[] = {
|
|||||||
{"createdb", CREATEDB},
|
{"createdb", CREATEDB},
|
||||||
{"createuser", CREATEUSER},
|
{"createuser", CREATEUSER},
|
||||||
{"cross", CROSS},
|
{"cross", CROSS},
|
||||||
|
{"csv", CSV},
|
||||||
{"current_date", CURRENT_DATE},
|
{"current_date", CURRENT_DATE},
|
||||||
{"current_time", CURRENT_TIME},
|
{"current_time", CURRENT_TIME},
|
||||||
{"current_timestamp", CURRENT_TIMESTAMP},
|
{"current_timestamp", CURRENT_TIMESTAMP},
|
||||||
@ -186,6 +187,7 @@ static const ScanKeyword ScanKeywords[] = {
|
|||||||
{"like", LIKE},
|
{"like", LIKE},
|
||||||
{"limit", LIMIT},
|
{"limit", LIMIT},
|
||||||
{"listen", LISTEN},
|
{"listen", LISTEN},
|
||||||
|
{"literal", LITERAL},
|
||||||
{"load", LOAD},
|
{"load", LOAD},
|
||||||
{"local", LOCAL},
|
{"local", LOCAL},
|
||||||
{"localtime", LOCALTIME},
|
{"localtime", LOCALTIME},
|
||||||
@ -248,6 +250,7 @@ static const ScanKeyword ScanKeywords[] = {
|
|||||||
{"privileges", PRIVILEGES},
|
{"privileges", PRIVILEGES},
|
||||||
{"procedural", PROCEDURAL},
|
{"procedural", PROCEDURAL},
|
||||||
{"procedure", PROCEDURE},
|
{"procedure", PROCEDURE},
|
||||||
|
{"quote", QUOTE},
|
||||||
{"read", READ},
|
{"read", READ},
|
||||||
{"real", REAL},
|
{"real", REAL},
|
||||||
{"recheck", RECHECK},
|
{"recheck", RECHECK},
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/backend/tcop/fastpath.c,v 1.71 2004/01/07 18:56:27 neilc Exp $
|
* $PostgreSQL: pgsql/src/backend/tcop/fastpath.c,v 1.72 2004/04/19 17:22:31 momjian Exp $
|
||||||
*
|
*
|
||||||
* NOTES
|
* NOTES
|
||||||
* This cruft is the server side of PQfn.
|
* This cruft is the server side of PQfn.
|
||||||
@ -154,8 +154,7 @@ SendFunctionResult(Datum retval, bool isnull, Oid rettype, int16 format)
|
|||||||
bool typisvarlena;
|
bool typisvarlena;
|
||||||
char *outputstr;
|
char *outputstr;
|
||||||
|
|
||||||
getTypeOutputInfo(rettype,
|
getTypeOutputInfo(rettype, &typoutput, &typelem, &typisvarlena);
|
||||||
&typoutput, &typelem, &typisvarlena);
|
|
||||||
outputstr = DatumGetCString(OidFunctionCall3(typoutput,
|
outputstr = DatumGetCString(OidFunctionCall3(typoutput,
|
||||||
retval,
|
retval,
|
||||||
ObjectIdGetDatum(typelem),
|
ObjectIdGetDatum(typelem),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* Copyright (c) 2000-2003, PostgreSQL Global Development Group
|
* Copyright (c) 2000-2003, PostgreSQL Global Development Group
|
||||||
*
|
*
|
||||||
* $PostgreSQL: pgsql/src/bin/psql/copy.c,v 1.43 2004/04/12 15:58:52 momjian Exp $
|
* $PostgreSQL: pgsql/src/bin/psql/copy.c,v 1.44 2004/04/19 17:22:31 momjian Exp $
|
||||||
*/
|
*/
|
||||||
#include "postgres_fe.h"
|
#include "postgres_fe.h"
|
||||||
#include "copy.h"
|
#include "copy.h"
|
||||||
@ -66,8 +66,13 @@ struct copy_options
|
|||||||
bool from;
|
bool from;
|
||||||
bool binary;
|
bool binary;
|
||||||
bool oids;
|
bool oids;
|
||||||
|
bool csv_mode;
|
||||||
char *delim;
|
char *delim;
|
||||||
char *null;
|
char *null;
|
||||||
|
char *quote;
|
||||||
|
char *escape;
|
||||||
|
char *force_list;
|
||||||
|
char *literal_list;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -81,6 +86,10 @@ free_copy_options(struct copy_options * ptr)
|
|||||||
free(ptr->file);
|
free(ptr->file);
|
||||||
free(ptr->delim);
|
free(ptr->delim);
|
||||||
free(ptr->null);
|
free(ptr->null);
|
||||||
|
free(ptr->quote);
|
||||||
|
free(ptr->escape);
|
||||||
|
free(ptr->force_list);
|
||||||
|
free(ptr->literal_list);
|
||||||
free(ptr);
|
free(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,11 +281,19 @@ parse_slash_copy(const char *args)
|
|||||||
|
|
||||||
while (token)
|
while (token)
|
||||||
{
|
{
|
||||||
|
bool fetch_next;
|
||||||
|
|
||||||
|
fetch_next = true;
|
||||||
|
|
||||||
/* someday allow BINARY here */
|
/* someday allow BINARY here */
|
||||||
if (strcasecmp(token, "oids") == 0)
|
if (strcasecmp(token, "oids") == 0)
|
||||||
{
|
{
|
||||||
result->oids = true;
|
result->oids = true;
|
||||||
}
|
}
|
||||||
|
else if (strcasecmp(token, "csv") == 0)
|
||||||
|
{
|
||||||
|
result->csv_mode = true;
|
||||||
|
}
|
||||||
else if (strcasecmp(token, "delimiter") == 0)
|
else if (strcasecmp(token, "delimiter") == 0)
|
||||||
{
|
{
|
||||||
token = strtokx(NULL, whitespace, NULL, "'",
|
token = strtokx(NULL, whitespace, NULL, "'",
|
||||||
@ -301,9 +318,76 @@ parse_slash_copy(const char *args)
|
|||||||
else
|
else
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
else if (strcasecmp(token, "quote") == 0)
|
||||||
|
{
|
||||||
|
token = strtokx(NULL, whitespace, NULL, "'",
|
||||||
|
'\\', false, pset.encoding);
|
||||||
|
if (token && strcasecmp(token, "as") == 0)
|
||||||
|
token = strtokx(NULL, whitespace, NULL, "'",
|
||||||
|
'\\', false, pset.encoding);
|
||||||
|
if (token)
|
||||||
|
result->quote = pg_strdup(token);
|
||||||
|
else
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
else if (strcasecmp(token, "escape") == 0)
|
||||||
|
{
|
||||||
|
token = strtokx(NULL, whitespace, NULL, "'",
|
||||||
|
'\\', false, pset.encoding);
|
||||||
|
if (token && strcasecmp(token, "as") == 0)
|
||||||
|
token = strtokx(NULL, whitespace, NULL, "'",
|
||||||
|
'\\', false, pset.encoding);
|
||||||
|
if (token)
|
||||||
|
result->escape = pg_strdup(token);
|
||||||
|
else
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
else if (strcasecmp(token, "force") == 0)
|
||||||
|
{
|
||||||
|
/* handle column list */
|
||||||
|
fetch_next = false;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
token = strtokx(NULL, whitespace, ",", "\"",
|
||||||
|
0, false, pset.encoding);
|
||||||
|
if (!token || strchr(",", token[0]))
|
||||||
|
goto error;
|
||||||
|
if (!result->force_list)
|
||||||
|
result->force_list = pg_strdup(token);
|
||||||
|
else
|
||||||
|
xstrcat(&result->force_list, token);
|
||||||
|
token = strtokx(NULL, whitespace, ",", "\"",
|
||||||
|
0, false, pset.encoding);
|
||||||
|
if (!token || token[0] != ',')
|
||||||
|
break;
|
||||||
|
xstrcat(&result->force_list, token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcasecmp(token, "literal") == 0)
|
||||||
|
{
|
||||||
|
/* handle column list */
|
||||||
|
fetch_next = false;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
token = strtokx(NULL, whitespace, ",", "\"",
|
||||||
|
0, false, pset.encoding);
|
||||||
|
if (!token || strchr(",", token[0]))
|
||||||
|
goto error;
|
||||||
|
if (!result->literal_list)
|
||||||
|
result->literal_list = pg_strdup(token);
|
||||||
|
else
|
||||||
|
xstrcat(&result->literal_list, token);
|
||||||
|
token = strtokx(NULL, whitespace, ",", "\"",
|
||||||
|
0, false, pset.encoding);
|
||||||
|
if (!token || token[0] != ',')
|
||||||
|
break;
|
||||||
|
xstrcat(&result->literal_list, token);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
if (fetch_next)
|
||||||
token = strtokx(NULL, whitespace, NULL, NULL,
|
token = strtokx(NULL, whitespace, NULL, NULL,
|
||||||
0, false, pset.encoding);
|
0, false, pset.encoding);
|
||||||
}
|
}
|
||||||
@ -379,6 +463,7 @@ do_copy(const char *args)
|
|||||||
options->delim);
|
options->delim);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* There is no backward-compatible CSV syntax */
|
||||||
if (options->null)
|
if (options->null)
|
||||||
{
|
{
|
||||||
if (options->null[0] == '\'')
|
if (options->null[0] == '\'')
|
||||||
@ -387,6 +472,37 @@ do_copy(const char *args)
|
|||||||
appendPQExpBuffer(&query, " WITH NULL AS '%s'", options->null);
|
appendPQExpBuffer(&query, " WITH NULL AS '%s'", options->null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options->csv_mode)
|
||||||
|
{
|
||||||
|
appendPQExpBuffer(&query, " CSV");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options->quote)
|
||||||
|
{
|
||||||
|
if (options->quote[0] == '\'')
|
||||||
|
appendPQExpBuffer(&query, " QUOTE AS %s", options->quote);
|
||||||
|
else
|
||||||
|
appendPQExpBuffer(&query, " QUOTE AS '%s'", options->quote);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options->escape)
|
||||||
|
{
|
||||||
|
if (options->escape[0] == '\'')
|
||||||
|
appendPQExpBuffer(&query, " ESCAPE AS %s", options->escape);
|
||||||
|
else
|
||||||
|
appendPQExpBuffer(&query, " ESCAPE AS '%s'", options->escape);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options->force_list)
|
||||||
|
{
|
||||||
|
appendPQExpBuffer(&query, " FORCE %s", options->force_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options->literal_list)
|
||||||
|
{
|
||||||
|
appendPQExpBuffer(&query, " LITERAL %s", options->literal_list);
|
||||||
|
}
|
||||||
|
|
||||||
if (options->from)
|
if (options->from)
|
||||||
{
|
{
|
||||||
if (options->file)
|
if (options->file)
|
||||||
|
Reference in New Issue
Block a user