Removed Cc: to pgsql-hackers.
Zoltán,
Zoltan Boszormenyi wrote:
> >Your patch has been added to the PostgreSQL unapplied patches list at:
> >
> > http://momjian.postgresql.org/cgi-bin/pgpatches
> >
> >It will be applied as soon as one of the PostgreSQL committers reviews
> >and approves it.
>
> Thanks. Would you please add this instead?
> psql built-in \copy (select ...) now also work.
Please check this one out. I took the version you posted here and
changed the stuff in the parser that I didn't like, and removed the ugly
"SELECT * FROM" stuff that was bothering me. I also removed the
transformCopyStmt stuff as it seems unnecessary to me. I did all that
stuff in a cleaner way (IMO).
I also cleaned up the grammar -- basically added a separate case from
the regular COPY. I took the opportunity to remove the
backwards-compatible options from there. I didn't check that stuff very
much but it should continue to work ...
I noticed that this works:
alvherre=# copy (values (1, 'uno'), (2, 'dos'), (3, 'tr;es'), (4, NULL)) to
stdout with delimiter ';' null 'NUL' csv quote as '"';
1;uno
2;dos
3;"tr;es"
4;NUL
which is nice.
With this patch, the COPY view FROM stdout path now throws an error --
in your version it worked (because of that "COPY * FROM" stuff), and
from previous discussion it seems reasonable to behave differently for
views than for plain tables (i.e. it's reasonable that we fail for
views).
I also broke the check for a FOR UPDATE clause. Not sure where but it
must be easy to fix :-) I'd do it myself but I'm heading to bed right
now.
I also wanted to check these hunks in your patch, which I didn't like
very much:
-ERROR: column "a" of relation "test" does not exist
+ERROR: column "a" does not exist
but didn't got around to it.
I also noticed that the new copyselect regression test is not added to
the serial schedule.
I'll repost a reworked version at some point, if no one beats me to it.
--
Alvaro Herrera http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.
Index: doc/src/sgml/ref/copy.sgml
===================================================================
RCS file: /home/alvherre/cvs/pgsql/doc/src/sgml/ref/copy.sgml,v
retrieving revision 1.74
diff -c -p -r1.74 copy.sgml
*** doc/src/sgml/ref/copy.sgml 22 Apr 2006 03:03:11 -0000 1.74
--- doc/src/sgml/ref/copy.sgml 27 Aug 2006 04:53:19 -0000
***************
*** 1,5 ****
<!--
! $PostgreSQL: pgsql/doc/src/sgml/ref/copy.sgml,v 1.73 2006/03/03 19:54:10 tgl
Exp $
PostgreSQL documentation
-->
--- 1,5 ----
<!--
! $PostgreSQL: pgsql/doc/src/sgml/ref/copy.sgml,v 1.74 2006-04-22 03:03:11
momjian Exp $
PostgreSQL documentation
-->
*************** COPY <replaceable class="parameter">tabl
*** 33,39 ****
[ 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> [, ...] ) ]
TO { '<replaceable class="parameter">filename</replaceable>' | STDOUT }
[ [ WITH ]
[ BINARY ]
--- 33,39 ----
[ ESCAPE [ AS ] '<replaceable
class="parameter">escape</replaceable>' ]
[ FORCE NOT NULL <replaceable
class="parameter">column</replaceable> [, ...] ]
! COPY { <replaceable class="parameter">tablename</replaceable> | <replaceable
class="parameter">viewname</replaceable> | ( select_statement ) } [ (
<replaceable class="parameter">column</replaceable> [, ...] ) ]
TO { '<replaceable class="parameter">filename</replaceable>' | STDOUT }
[ [ WITH ]
[ BINARY ]
*************** COPY <replaceable class="parameter">tabl
*** 55,61 ****
<command>COPY</command> moves data between
<productname>PostgreSQL</productname> tables and standard file-system
files. <command>COPY TO</command> copies the contents of a table
! <emphasis>to</> a file, while <command>COPY FROM</command> copies
data <emphasis>from</> a file to a table (appending the data to
whatever is in the table already).
</para>
--- 55,63 ----
<command>COPY</command> moves data between
<productname>PostgreSQL</productname> tables and standard file-system
files. <command>COPY TO</command> copies the contents of a table
! <emphasis>to</> a file, which also work on views and arbitrary
! SELECT statements. (Internally, the view case is rewitten as
! <command>SELECT * FROM viewname</command>.) <command>COPY FROM</command>
copies
data <emphasis>from</> a file to a table (appending the data to
whatever is in the table already).
</para>
*************** COPY <replaceable class="parameter">tabl
*** 65,71 ****
only copy the data in the specified columns to or from the file.
If there are any columns in the table that are not in the column list,
<command>COPY FROM</command> will insert the default values for
! those columns.
</para>
<para>
--- 67,76 ----
only copy the data in the specified columns to or from the file.
If there are any columns in the table that are not in the column list,
<command>COPY FROM</command> will insert the default values for
! those columns. <command>COPY TO</command> also accepts the list of
! columns, which can be useful for exporting only a subset of the
! columns that are in the table, view or select statement, or
! reordering them in the export.
</para>
<para>
*************** COPY <replaceable class="parameter">tabl
*** 148,154 ****
<para>
Specifies copying the OID for each row. (An error is raised if
<literal>OIDS</literal> is specified for a table that does not
! have OIDs.)
</para>
</listitem>
</varlistentry>
--- 153,159 ----
<para>
Specifies copying the OID for each row. (An error is raised if
<literal>OIDS</literal> is specified for a table that does not
! have OIDs, or in the case of COPY (SELECT) TO.)
</para>
</listitem>
</varlistentry>
Index: src/backend/commands/copy.c
===================================================================
RCS file: /home/alvherre/cvs/pgsql/src/backend/commands/copy.c,v
retrieving revision 1.268
diff -c -p -r1.268 copy.c
*** src/backend/commands/copy.c 14 Jul 2006 14:52:18 -0000 1.268
--- src/backend/commands/copy.c 28 Aug 2006 03:12:07 -0000
***************
*** 31,38 ****
#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
! #include "parser/parse_relation.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
--- 31,39 ----
#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
! #include "parser/analyze.h"
#include "rewrite/rewriteHandler.h"
+ #include "optimizer/planner.h"
#include "storage/fd.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
*************** typedef struct CopyStateData
*** 96,104 ****
bool need_transcoding; /* client encoding diff
from server? */
bool encoding_embeds_ascii; /* ASCII can be non-first byte?
*/
uint64 processed; /* # of tuples processed */
/* parameters from the COPY command */
! Relation rel; /* relation to copy to or from
*/
List *attnumlist; /* integer list of attnums to copy */
bool binary; /* binary format? */
bool oids; /* include OIDs? */
--- 97,125 ----
bool need_transcoding; /* client encoding diff
from server? */
bool encoding_embeds_ascii; /* ASCII can be non-first byte?
*/
uint64 processed; /* # of tuples processed */
+ FmgrInfo *out_functions; /* array of output functions
(COPY TO) */
+ MemoryContext rowcxt; /* memory for output formatting
*/
+ List *force_quote_orig; /* quote attribute names */
+ List *force_notnull;
+ bool *force_quote; /* quote attribute? */
+ char *null_print_client; /* final version of null_print
*/
+ Datum *values; /* row data (COPY TO) */
+ bool *isnull; /* row 'isnull' flags (COPY TO)
*/
+ Oid out_func_oid;
+ bool isvarlena; /* tuple should be de-toasted */
+ TupleDesc attrinfo;
+ int natts;
/* parameters from the COPY command */
! /* relation to copy to or from, in the "COPY table" case */
! Relation rel;
!
! /* The following are valid in the "COPY (SELECT)" case: */
! DestReceiver *dest; /* DestReceiver for the COPY (SELECT)
case */
! QueryDesc *desc; /* descriptor for SELECT query
*/
! Plan *plan; /* plan for SELECT */
!
! /* These are valid in both cases */
List *attnumlist; /* integer list of attnums to copy */
bool binary; /* binary format? */
bool oids; /* include OIDs? */
*************** typedef struct CopyStateData
*** 153,158 ****
--- 174,184 ----
typedef CopyStateData *CopyState;
+ typedef struct
+ {
+ DestReceiver pub; /* publicly-known function pointers */
+ void *cstate; /* CopyStateData we are working with */
+ } DR_copy;
/*
* These macros centralize code used to process line_buf and raw_buf buffers.
*************** static const char BinarySignature[11] =
*** 223,230 ****
--- 249,259 ----
/* non-export function prototypes */
+ static uint64 DoCopyRelation(const CopyStmt *stmt, CopyState cstate);
+ static uint64 DoCopySelect(const CopyStmt *stmt, CopyState cstate);
static void DoCopyTo(CopyState cstate);
static void CopyTo(CopyState cstate);
+ static void CopyValuesTo(CopyState cstate, Oid oid, Datum *values, bool
*isnull);
static void CopyFrom(CopyState cstate);
static bool CopyReadLine(CopyState cstate);
static bool CopyReadLineText(CopyState cstate);
*************** static Datum CopyReadBinaryAttribute(Cop
*** 239,246 ****
static void CopyAttributeOutText(CopyState cstate, char *string);
static void CopyAttributeOutCSV(CopyState cstate, char *string,
bool use_quote, bool single_attr);
! static List *CopyGetAttnums(Relation rel, List *attnamelist);
static char *limit_printout_length(const char *str);
/* Low-level communications functions */
static void SendCopyBegin(CopyState cstate);
--- 268,276 ----
static void CopyAttributeOutText(CopyState cstate, char *string);
static void CopyAttributeOutCSV(CopyState cstate, char *string,
bool use_quote, bool single_attr);
! static List *CopyGetAttnums(TupleDesc tupDesc, List *attnamelist);
static char *limit_printout_length(const char *str);
+ static void prepare_cstate_info(CopyState cstate, TupleDesc typeinfo, int
numAttrs);
/* Low-level communications functions */
static void SendCopyBegin(CopyState cstate);
*************** uint64
*** 697,713 ****
DoCopy(const CopyStmt *stmt)
{
CopyState cstate;
- RangeVar *relation = stmt->relation;
- char *filename = stmt->filename;
- bool is_from = stmt->is_from;
- bool pipe = (stmt->filename == NULL);
- List *attnamelist = stmt->attlist;
- List *force_quote = NIL;
- List *force_notnull = NIL;
- AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT);
- AclResult aclresult;
ListCell *option;
- uint64 processed;
/* Allocate workspace and zero all fields */
cstate = (CopyStateData *) palloc0(sizeof(CopyStateData));
--- 727,733 ----
*************** DoCopy(const CopyStmt *stmt)
*** 783,801 ****
}
else if (strcmp(defel->defname, "force_quote") == 0)
{
! if (force_quote)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or
redundant options")));
! force_quote = (List *) defel->arg;
}
else if (strcmp(defel->defname, "force_notnull") == 0)
{
! if (force_notnull)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or
redundant options")));
! force_notnull = (List *) defel->arg;
}
else
elog(ERROR, "option \"%s\" not recognized",
--- 803,821 ----
}
else if (strcmp(defel->defname, "force_quote") == 0)
{
! if (cstate->force_quote_orig)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or
redundant options")));
! cstate->force_quote_orig = (List *) defel->arg;
}
else if (strcmp(defel->defname, "force_notnull") == 0)
{
! if (cstate->force_notnull)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or
redundant options")));
! cstate->force_notnull = (List *) defel->arg;
}
else
elog(ERROR, "option \"%s\" not recognized",
*************** DoCopy(const CopyStmt *stmt)
*** 888,908 ****
errmsg("COPY escape must be a single
character")));
/* Check force_quote */
! if (!cstate->csv_mode && force_quote != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY force quote available only in CSV
mode")));
! if (force_quote != NIL && is_from)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY force quote only available using
COPY TO")));
/* Check force_notnull */
! if (!cstate->csv_mode && force_notnull != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY force not null available only in
CSV mode")));
! if (force_notnull != NIL && !is_from)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY force not null only available using COPY
FROM")));
--- 908,928 ----
errmsg("COPY escape must be a single
character")));
/* Check force_quote */
! if (!cstate->csv_mode && cstate->force_quote != NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY force quote available only in CSV
mode")));
! if (cstate->force_quote != NULL && stmt->is_from)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY force quote only available using
COPY TO")));
/* Check force_notnull */
! if (!cstate->csv_mode && cstate->force_notnull != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY force not null available only in
CSV mode")));
! if (cstate->force_notnull != NIL && !stmt->is_from)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY force not null only available using COPY
FROM")));
*************** DoCopy(const CopyStmt *stmt)
*** 920,931 ****
(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. */
cstate->rel = heap_openrv(relation,
! (is_from ?
RowExclusiveLock : AccessShareLock));
/* check read-only transaction */
! if (XactReadOnly && is_from &&
!isTempNamespace(RelationGetNamespace(cstate->rel)))
ereport(ERROR,
(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
--- 940,975 ----
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("CSV quote character must not appear in
the NULL specification")));
+ if (stmt->filename != NULL && !superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to COPY to or from a
file"),
+ errhint("Anyone can COPY to stdout or from
stdin. "
+ "psql's \\copy command also
works for anyone.")));
+
+ if (stmt->relation)
+ return DoCopyRelation(stmt, cstate);
+ else
+ return DoCopySelect(stmt, cstate);
+ }
+
+ static uint64
+ DoCopyRelation(const CopyStmt *stmt, CopyState cstate)
+ {
+ RangeVar *relation = stmt->relation;
+ char *filename = stmt->filename;
+ bool pipe = (stmt->filename == NULL);
+ List *attnamelist = stmt->attlist;
+ AclMode required_access = (stmt->is_from ? ACL_INSERT :
ACL_SELECT);
+ AclResult aclresult;
+ uint64 processed;
+
/* Open and lock the relation, using the appropriate lock type. */
cstate->rel = heap_openrv(relation,
! (stmt->is_from ?
RowExclusiveLock : AccessShareLock));
/* check read-only transaction */
! if (XactReadOnly && stmt->is_from &&
!isTempNamespace(RelationGetNamespace(cstate->rel)))
ereport(ERROR,
(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
*************** DoCopy(const CopyStmt *stmt)
*** 937,948 ****
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_CLASS,
RelationGetRelationName(cstate->rel));
- if (!pipe && !superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to COPY to or from a
file"),
- errhint("Anyone can COPY to stdout or from
stdin. "
- "psql's \\copy command also
works for anyone.")));
/* Don't allow COPY w/ OIDs to or from a table without them */
if (cstate->oids && !cstate->rel->rd_rel->relhasoids)
--- 981,986 ----
*************** DoCopy(const CopyStmt *stmt)
*** 952,967 ****
RelationGetRelationName(cstate->rel))));
/* Generate or convert list of attributes to process */
! cstate->attnumlist = CopyGetAttnums(cstate->rel, attnamelist);
/* Convert FORCE QUOTE name list to column numbers, check validity */
! if (force_quote)
{
TupleDesc tupDesc = RelationGetDescr(cstate->rel);
Form_pg_attribute *attr = tupDesc->attrs;
ListCell *cur;
! cstate->force_quote_atts = CopyGetAttnums(cstate->rel,
force_quote);
foreach(cur, cstate->force_quote_atts)
{
--- 990,1005 ----
RelationGetRelationName(cstate->rel))));
/* Generate or convert list of attributes to process */
! cstate->attnumlist = CopyGetAttnums(RelationGetDescr(cstate->rel),
attnamelist);
/* Convert FORCE QUOTE name list to column numbers, check validity */
! if (cstate->force_quote_orig)
{
TupleDesc tupDesc = RelationGetDescr(cstate->rel);
Form_pg_attribute *attr = tupDesc->attrs;
ListCell *cur;
! cstate->force_quote_atts = CopyGetAttnums(tupDesc,
cstate->force_quote_orig);
foreach(cur, cstate->force_quote_atts)
{
*************** DoCopy(const CopyStmt *stmt)
*** 976,989 ****
}
/* Convert FORCE NOT NULL name list to column numbers, check validity */
! if (force_notnull)
{
TupleDesc tupDesc = RelationGetDescr(cstate->rel);
Form_pg_attribute *attr = tupDesc->attrs;
ListCell *cur;
! cstate->force_notnull_atts = CopyGetAttnums(cstate->rel,
!
force_notnull);
foreach(cur, cstate->force_notnull_atts)
{
--- 1014,1026 ----
}
/* Convert FORCE NOT NULL name list to column numbers, check validity */
! if (cstate->force_notnull)
{
TupleDesc tupDesc = RelationGetDescr(cstate->rel);
Form_pg_attribute *attr = tupDesc->attrs;
ListCell *cur;
! cstate->force_notnull_atts = CopyGetAttnums(tupDesc,
cstate->force_notnull);
foreach(cur, cstate->force_notnull_atts)
{
*************** DoCopy(const CopyStmt *stmt)
*** 1019,1025 ****
cstate->copy_dest = COPY_FILE; /* default */
! if (is_from)
{ /* copy from
file to database */
if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
{
--- 1056,1062 ----
cstate->copy_dest = COPY_FILE; /* default */
! if (stmt->is_from)
{ /* copy from
file to database */
if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
{
*************** DoCopy(const CopyStmt *stmt)
*** 1149,1159 ****
* got; if writing, we should hold the lock until end of transaction to
* ensure that updates will be committed before lock is released.
*/
! heap_close(cstate->rel, (is_from ? NoLock : AccessShareLock));
/* Clean up storage (probably not really necessary) */
processed = cstate->processed;
pfree(cstate->attribute_buf.data);
pfree(cstate->line_buf.data);
pfree(cstate->raw_buf);
--- 1186,1372 ----
* got; if writing, we should hold the lock until end of transaction to
* ensure that updates will be committed before lock is released.
*/
! heap_close(cstate->rel, (stmt->is_from ? NoLock : AccessShareLock));
!
! /* Clean up storage (probably not really necessary) */
! processed = cstate->processed;
!
! pfree(cstate->attribute_buf.data);
! pfree(cstate->line_buf.data);
! pfree(cstate->raw_buf);
! pfree(cstate);
!
! return processed;
! }
!
! static uint64
! DoCopySelect(const CopyStmt *stmt, CopyState cstate)
! {
! char *filename = stmt->filename;
! bool pipe = (stmt->filename == NULL);
! List *attnamelist = stmt->attlist;
! uint64 processed = 0;
! List *queries;
! Query *query;
!
! /* Don't allow COPY w/ OIDs from a select */
! if (cstate->oids)
! ereport(ERROR,
! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! errmsg("COPY (SELECT) WITH OIDS is not
supported")));
!
! queries = parse_analyze((Node *) stmt->selectstmt, NULL, NULL, 0);
!
! /* shouldn't happen */
! if (list_length(queries) != 1)
! elog(ERROR, "COPY (SELECT) returns more than one Query");
! query = linitial(queries);
!
! /* pass the query through the rewriter */
! queries = QueryRewrite(query);
!
! /* shouldn't happen, or could it? */
! if (list_length(queries) != 1)
! elog(ERROR, "COPY (SELECT) returns more than one Query after
rewrite");
! query = linitial(queries);
!
! cstate->plan = planner(query, true, 0, NULL);
!
! cstate->dest = CreateDestReceiver(DestCopyDR, NULL);
! ((DR_copy *) cstate->dest)->cstate = cstate;
! cstate->desc = CreateQueryDesc(query, cstate->plan,
!
ActiveSnapshot, InvalidSnapshot,
!
cstate->dest, NULL, false);
! /* Execute query */
! ExecutorStart(cstate->desc, false);
!
! /* Generate or convert list of attributes to process */
! cstate->attnumlist = CopyGetAttnums(cstate->desc->tupDesc, attnamelist);
!
! /* Convert FORCE QUOTE name list to column numbers, check validity */
! if (cstate->force_quote_orig)
! {
! TupleDesc tupDesc = cstate->desc->tupDesc;
! Form_pg_attribute *attr = tupDesc->attrs;
! ListCell *cur;
!
! cstate->force_quote_atts = CopyGetAttnums(tupDesc,
cstate->force_quote_orig);
!
! foreach(cur, cstate->force_quote_atts)
! {
! int attnum = lfirst_int(cur);
!
! if (!list_member_int(cstate->attnumlist, attnum))
! ereport(ERROR,
!
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
! errmsg("FORCE QUOTE column \"%s\" not
referenced by COPY",
! NameStr(attr[attnum -
1]->attname))));
! }
! }
!
! /* Convert FORCE NOT NULL name list to column numbers, check validity */
! if (cstate->force_notnull)
! {
! TupleDesc tupDesc = cstate->desc->tupDesc;
! Form_pg_attribute *attr = tupDesc->attrs;
! ListCell *cur;
!
! cstate->force_notnull_atts = CopyGetAttnums(tupDesc,
cstate->force_notnull);
!
! foreach(cur, cstate->force_notnull_atts)
! {
! int attnum = lfirst_int(cur);
!
! if (!list_member_int(cstate->attnumlist, attnum))
! ereport(ERROR,
!
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
! errmsg("FORCE NOT NULL column \"%s\" not
referenced by COPY",
! NameStr(attr[attnum -
1]->attname))));
! }
! }
!
! /* Set up variables to avoid per-attribute overhead. */
! initStringInfo(&cstate->attribute_buf);
! initStringInfo(&cstate->line_buf);
! cstate->line_buf_converted = false;
! cstate->raw_buf = (char *) palloc(RAW_BUF_SIZE + 1);
! cstate->raw_buf_index = cstate->raw_buf_len = 0;
! cstate->processed = 0;
!
! /*
! * Set up encoding conversion info. Even if the client and server
! * encodings are the same, we must apply pg_client_to_server() to
! * validate data in multibyte encodings.
! */
! cstate->client_encoding = pg_get_client_encoding();
! cstate->need_transcoding =
! (cstate->client_encoding != GetDatabaseEncoding() ||
! pg_database_encoding_max_length() > 1);
! /* See Multibyte encoding comment above */
! cstate->encoding_embeds_ascii =
PG_ENCODING_IS_CLIENT_ONLY(cstate->client_encoding);
!
! cstate->copy_dest = COPY_FILE; /* default */
!
! /* copy from database to file */
! if (pipe)
! {
! if (whereToSendOutput == DestRemote)
! cstate->fe_copy = true;
! else
! cstate->copy_file = stdout;
! }
! else
! {
! mode_t oumask; /* Pre-existing umask value */
! struct stat st;
!
! /*
! * Prevent write to relative path ... too easy to shoot oneself
in
! * the foot by overwriting a database file ...
! */
! if (!is_absolute_path(filename))
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_NAME),
! errmsg("relative path not allowed for
COPY to file")));
!
! oumask = umask((mode_t) 022);
! cstate->copy_file = AllocateFile(filename, PG_BINARY_W);
! umask(oumask);
!
! if (cstate->copy_file == NULL)
! ereport(ERROR,
! (errcode_for_file_access(),
! errmsg("could not open file \"%s\" for
writing: %m",
! filename)));
!
! fstat(fileno(cstate->copy_file), &st);
! if (S_ISDIR(st.st_mode))
! {
! FreeFile(cstate->copy_file);
! ereport(ERROR,
! (errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is a directory",
filename)));
! }
! }
!
! DoCopyTo(cstate);
!
! if (!pipe)
! {
! /* we assume only the write case could fail here */
! if (FreeFile(cstate->copy_file))
! ereport(ERROR,
! (errcode_for_file_access(),
! errmsg("could not write to file
\"%s\": %m",
! filename)));
! }
/* Clean up storage (probably not really necessary) */
processed = cstate->processed;
+ ExecutorEnd(cstate->desc);
+ FreeQueryDesc(cstate->desc);
+
pfree(cstate->attribute_buf.data);
pfree(cstate->line_buf.data);
pfree(cstate->raw_buf);
*************** DoCopyTo(CopyState cstate)
*** 1193,1250 ****
PG_END_TRY();
}
/*
! * Copy from relation TO file.
*/
static void
CopyTo(CopyState cstate)
{
- HeapTuple tuple;
TupleDesc tupDesc;
- HeapScanDesc scandesc;
int num_phys_attrs;
- int attr_count;
Form_pg_attribute *attr;
- FmgrInfo *out_functions;
- bool *force_quote;
- char *string;
- char *null_print_client;
ListCell *cur;
- MemoryContext oldcontext;
- MemoryContext mycontext;
! tupDesc = cstate->rel->rd_att;
attr = tupDesc->attrs;
num_phys_attrs = tupDesc->natts;
! attr_count = list_length(cstate->attnumlist);
! null_print_client = cstate->null_print; /* default */
/* We use fe_msgbuf as a per-row buffer regardless of copy_dest */
cstate->fe_msgbuf = makeStringInfo();
/* Get info about the columns we need to process. */
! out_functions = (FmgrInfo *) palloc(num_phys_attrs * sizeof(FmgrInfo));
! force_quote = (bool *) palloc(num_phys_attrs * sizeof(bool));
foreach(cur, cstate->attnumlist)
{
int attnum = lfirst_int(cur);
- Oid out_func_oid;
- bool isvarlena;
-
- if (cstate->binary)
- getTypeBinaryOutputInfo(attr[attnum - 1]->atttypid,
-
&out_func_oid,
-
&isvarlena);
- else
- getTypeOutputInfo(attr[attnum - 1]->atttypid,
- &out_func_oid,
- &isvarlena);
- fmgr_info(out_func_oid, &out_functions[attnum - 1]);
if (list_member_int(cstate->force_quote_atts, attnum))
! force_quote[attnum - 1] = true;
else
! force_quote[attnum - 1] = false;
}
/*
--- 1406,1479 ----
PG_END_TRY();
}
+ static void
+ prepare_cstate_info(CopyState cstate, TupleDesc typeinfo, int numAttrs)
+ {
+ Form_pg_attribute *attr;
+ ListCell *cur;
+
+ /* get rid of any old data */
+ attr = typeinfo->attrs;
+ cstate->attrinfo = typeinfo;
+ cstate->natts = numAttrs;
+ if (numAttrs <= 0)
+ return;
+
+ foreach(cur, cstate->attnumlist)
+ {
+ int attnum = lfirst_int(cur);
+
+ if (cstate->binary)
+ getTypeBinaryOutputInfo(attr[attnum - 1]->atttypid,
+
&cstate->out_func_oid,
+
&cstate->isvarlena);
+ else
+ getTypeOutputInfo(attr[attnum - 1]->atttypid,
+ &cstate->out_func_oid,
+ &cstate->isvarlena);
+ fmgr_info(cstate->out_func_oid, &cstate->out_functions[attnum -
1]);
+ }
+ }
+
/*
! * Copy from select or relation TO file.
*/
static void
CopyTo(CopyState cstate)
{
TupleDesc tupDesc;
int num_phys_attrs;
Form_pg_attribute *attr;
ListCell *cur;
! if (cstate->rel)
! tupDesc = cstate->rel->rd_att;
! else
! tupDesc = cstate->desc->tupDesc;
attr = tupDesc->attrs;
num_phys_attrs = tupDesc->natts;
! cstate->null_print_client = cstate->null_print; /* default */
/* We use fe_msgbuf as a per-row buffer regardless of copy_dest */
cstate->fe_msgbuf = makeStringInfo();
/* Get info about the columns we need to process. */
! cstate->out_functions = (FmgrInfo *) palloc(num_phys_attrs *
sizeof(FmgrInfo));
! cstate->force_quote = (bool *) palloc(num_phys_attrs * sizeof(bool));
!
! cstate->isnull = (bool *) palloc(num_phys_attrs * sizeof(bool));
! cstate->values = (Datum *) palloc(num_phys_attrs * sizeof(Datum));
!
! prepare_cstate_info(cstate, tupDesc, tupDesc->natts);
!
foreach(cur, cstate->attnumlist)
{
int attnum = lfirst_int(cur);
if (list_member_int(cstate->force_quote_atts, attnum))
! cstate->force_quote[attnum - 1] = true;
else
! cstate->force_quote[attnum - 1] = false;
}
/*
*************** CopyTo(CopyState cstate)
*** 1253,1259 ****
* datatype output routines, and should be faster than retail pfree's
* anyway. (We don't need a whole econtext as CopyFrom does.)
*/
! mycontext = AllocSetContextCreate(CurrentMemoryContext,
"COPY
TO",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
--- 1482,1488 ----
* datatype output routines, and should be faster than retail pfree's
* anyway. (We don't need a whole econtext as CopyFrom does.)
*/
! cstate->rowcxt = AllocSetContextCreate(CurrentMemoryContext,
"COPY
TO",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
*************** CopyTo(CopyState cstate)
*** 1282,1288 ****
* encoding, because it will be sent directly with
CopySendString.
*/
if (cstate->need_transcoding)
! null_print_client =
pg_server_to_client(cstate->null_print,
cstate->null_print_len);
/* if a header has been requested send the line */
--- 1511,1517 ----
* encoding, because it will be sent directly with
CopySendString.
*/
if (cstate->need_transcoding)
! cstate->null_print_client =
pg_server_to_client(cstate->null_print,
cstate->null_print_len);
/* if a header has been requested send the line */
*************** CopyTo(CopyState cstate)
*** 1309,1408 ****
}
}
! scandesc = heap_beginscan(cstate->rel, ActiveSnapshot, 0, NULL);
!
! while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) != NULL)
{
! bool need_delim = false;
!
! CHECK_FOR_INTERRUPTS();
! MemoryContextReset(mycontext);
! oldcontext = MemoryContextSwitchTo(mycontext);
!
! if (cstate->binary)
{
! /* Binary per-tuple header */
! CopySendInt16(cstate, attr_count);
! /* Send OID if wanted --- note attr_count doesn't
include it */
! if (cstate->oids)
! {
! Oid oid =
HeapTupleGetOid(tuple);
! /* Hack --- assume Oid is same size as int32 */
! CopySendInt32(cstate, sizeof(int32));
! CopySendInt32(cstate, oid);
! }
! }
! else
! {
! /* Text format has no per-tuple header, but send OID if
wanted */
! /* Assume digits don't need any quoting or encoding
conversion */
if (cstate->oids)
! {
! string =
DatumGetCString(DirectFunctionCall1(oidout,
!
ObjectIdGetDatum(HeapTupleGetOid(tuple))));
! CopySendString(cstate, string);
! need_delim = true;
! }
! }
!
! foreach(cur, cstate->attnumlist)
! {
! int attnum = lfirst_int(cur);
! Datum value;
! bool isnull;
!
! value = heap_getattr(tuple, attnum, tupDesc, &isnull);
!
! if (!cstate->binary)
! {
! if (need_delim)
! CopySendChar(cstate, cstate->delim[0]);
! need_delim = true;
! }
! if (isnull)
! {
! if (!cstate->binary)
! CopySendString(cstate,
null_print_client);
! else
! CopySendInt32(cstate, -1);
! }
! else
! {
! if (!cstate->binary)
! {
! string =
OutputFunctionCall(&out_functions[attnum - 1],
!
value);
! if (cstate->csv_mode)
! CopyAttributeOutCSV(cstate,
string,
!
force_quote[attnum - 1],
!
list_length(cstate->attnumlist) == 1);
! else
! CopyAttributeOutText(cstate,
string);
! }
! else
! {
! bytea *outputbytes;
!
! outputbytes =
SendFunctionCall(&out_functions[attnum - 1],
!
value);
! CopySendInt32(cstate,
VARSIZE(outputbytes) - VARHDRSZ);
! CopySendData(cstate,
VARDATA(outputbytes),
!
VARSIZE(outputbytes) - VARHDRSZ);
! }
! }
}
! CopySendEndOfRow(cstate);
!
! MemoryContextSwitchTo(oldcontext);
!
! cstate->processed++;
}
!
! heap_endscan(scandesc);
if (cstate->binary)
{
--- 1538,1567 ----
}
}
! if (cstate->rel)
{
! HeapScanDesc scandesc;
! HeapTuple tuple;
!
! scandesc = heap_beginscan(cstate->rel, ActiveSnapshot, 0, NULL);
! while ((tuple = heap_getnext(scandesc, ForwardScanDirection))
!= NULL)
{
! Oid oid = InvalidOid;
! CHECK_FOR_INTERRUPTS();
!
if (cstate->oids)
! oid = HeapTupleGetOid(tuple);
! heap_deform_tuple(tuple, tupDesc, cstate->values,
cstate->isnull);
! CopyValuesTo(cstate, oid, cstate->values,
cstate->isnull);
}
! heap_endscan(scandesc);
}
! else
! ExecutorRun(cstate->desc, ForwardScanDirection, 0);
if (cstate->binary)
{
*************** CopyTo(CopyState cstate)
*** 1412,1421 ****
CopySendEndOfRow(cstate);
}
! MemoryContextDelete(mycontext);
! pfree(out_functions);
! pfree(force_quote);
}
--- 1571,1582 ----
CopySendEndOfRow(cstate);
}
! MemoryContextDelete(cstate->rowcxt);
! pfree(cstate->out_functions);
! pfree(cstate->force_quote);
! pfree(cstate->values);
! pfree(cstate->isnull);
}
*************** CopyAttributeOutCSV(CopyState cstate, ch
*** 3057,3070 ****
* columns).
*/
static List *
! CopyGetAttnums(Relation rel, List *attnamelist)
{
List *attnums = NIL;
if (attnamelist == NIL)
{
/* Generate default column list */
- TupleDesc tupDesc = RelationGetDescr(rel);
Form_pg_attribute *attr = tupDesc->attrs;
int attr_count = tupDesc->natts;
int i;
--- 3218,3230 ----
* columns).
*/
static List *
! CopyGetAttnums(TupleDesc tupDesc, List *attnamelist)
{
List *attnums = NIL;
if (attnamelist == NIL)
{
/* Generate default column list */
Form_pg_attribute *attr = tupDesc->attrs;
int attr_count = tupDesc->natts;
int i;
*************** CopyGetAttnums(Relation rel, List *attna
*** 3085,3099 ****
{
char *name = strVal(lfirst(l));
int attnum;
/* Lookup column name */
! /* Note we disallow system columns here */
! attnum = attnameAttNum(rel, name, false);
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
! errmsg("column \"%s\" of
relation \"%s\" does not exist",
! name,
RelationGetRelationName(rel))));
/* Check for duplicates */
if (list_member_int(attnums, attnum))
ereport(ERROR,
--- 3245,3269 ----
{
char *name = strVal(lfirst(l));
int attnum;
+ int i;
/* Lookup column name */
! attnum = InvalidAttrNumber;
! for (i = 0; i < tupDesc->natts; i++)
! {
! if (tupDesc->attrs[i]->attisdropped)
! continue;
! if (namestrcmp(&(tupDesc->attrs[i]->attname),
name) == 0)
! {
! attnum = tupDesc->attrs[i]->attnum;
! break;
! }
! }
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
! errmsg("column \"%s\" does not
exist",
! name)));
/* Check for duplicates */
if (list_member_int(attnums, attnum))
ereport(ERROR,
*************** CopyGetAttnums(Relation rel, List *attna
*** 3106,3108 ****
--- 3276,3438 ----
return attnums;
}
+
+ /*
+ * Copies data from 'values' array
+ */
+ static void
+ CopyValuesTo(CopyState cstate, Oid oid, Datum *values, bool *isnull)
+ {
+ int attr_count;
+ char *string;
+ ListCell *cur;
+ MemoryContext oldcontext;
+ bool need_delim = false;
+
+ Assert(cstate);
+ Assert(values);
+ Assert(isnull);
+
+ attr_count = list_length(cstate->attnumlist);
+
+ MemoryContextReset(cstate->rowcxt);
+ oldcontext = MemoryContextSwitchTo(cstate->rowcxt);
+
+ if (cstate->binary)
+ {
+ /* Binary per-tuple header */
+ CopySendInt16(cstate, attr_count);
+ /* Send OID if wanted --- note attr_count doesn't include it */
+ if (cstate->oids)
+ {
+ Assert(oid);
+ /* Hack --- assume Oid is same size as int32 */
+ CopySendInt32(cstate, sizeof(int32));
+ CopySendInt32(cstate, oid);
+ }
+ }
+ else
+ {
+ /* Text format has no per-tuple header, but send OID if wanted
*/
+ /* Assume digits don't need any quoting or encoding conversion
*/
+ if (cstate->oids)
+ {
+ Assert(oid);
+ string = DatumGetCString(DirectFunctionCall1(oidout,
+ ObjectIdGetDatum(oid)));
+ CopySendString(cstate, string);
+ need_delim = true;
+ }
+ }
+
+ foreach(cur, cstate->attnumlist)
+ {
+ int attnum = lfirst_int(cur);
+
+ if (!cstate->binary)
+ {
+ if (need_delim)
+ CopySendChar(cstate, cstate->delim[0]);
+ need_delim = true;
+ }
+ if (isnull[attnum-1])
+ {
+ if (!cstate->binary)
+ CopySendString(cstate,
cstate->null_print_client);
+ else
+ CopySendInt32(cstate, -1);
+ }
+ else
+ {
+ if (!cstate->binary)
+ {
+ string = DatumGetCString(FunctionCall1(
+
&cstate->out_functions[attnum - 1],
+ values[attnum - 1]));
+ if (cstate->csv_mode)
+ CopyAttributeOutCSV(cstate, string,
+
cstate->force_quote[attnum - 1],
+
list_length(cstate->attnumlist) == 1);
+ else
+ CopyAttributeOutText(cstate, string);
+ }
+ else
+ {
+ bytea *outputbytes;
+
+ outputbytes =
SendFunctionCall(&cstate->out_functions[attnum - 1],
+
values[attnum - 1]);
+
+ /* We assume the result will not have been
toasted */
+ CopySendInt32(cstate, VARSIZE(outputbytes) -
VARHDRSZ);
+ CopySendData(cstate, VARDATA(outputbytes),
+ VARSIZE(outputbytes) -
VARHDRSZ);
+ }
+ }
+ }
+ CopySendEndOfRow(cstate);
+ MemoryContextSwitchTo(oldcontext);
+
+ cstate->processed++;
+ }
+
+
+ /*
+ * Callback for executor destination receiver (COPY view TO)
+ */
+ static void
+ copy_dest_printtup(TupleTableSlot *slot, DestReceiver *self)
+ {
+ TupleDesc typeinfo = slot->tts_tupleDescriptor;
+ DR_copy *copyDR = (DR_copy *)self;
+ CopyState cstate = copyDR->cstate;
+ int natts = typeinfo->natts;
+
+ if (cstate->attrinfo != typeinfo ||cstate->natts != natts)
+ prepare_cstate_info(cstate, typeinfo, natts);
+
+ /* Make sure the tuple is fully deconstructed */
+ slot_getallattrs(slot);
+
+ if (slot->tts_tuple)
+ {
+ /* Executor returns standard tuple */
+ heap_deform_tuple(slot->tts_tuple, typeinfo, cstate->values,
cstate->isnull);
+ CopyValuesTo(cstate, InvalidOid, cstate->values,
cstate->isnull);
+ }
+ else
+ /* Executor returns "virtual" tuple */
+ CopyValuesTo(cstate, InvalidOid, slot->tts_values,
slot->tts_isnull);
+
+ }
+
+ static void
+ copy_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
+ {
+ /* no-op */
+ }
+
+ static void
+ copy_shutdown(DestReceiver *self)
+ {
+ /* no-op */
+ }
+
+ static void
+ copy_destroy(DestReceiver *self)
+ {
+ /* no-op */
+ }
+
+ DestReceiver *
+ CreateCopyDestReceiver(void)
+ {
+ DR_copy *self = (DR_copy *) palloc(sizeof(DR_copy));
+
+ self->pub.receiveSlot = copy_dest_printtup;
+ self->pub.rStartup = copy_startup;
+ self->pub.rShutdown = copy_shutdown;
+ self->pub.rDestroy = copy_destroy;
+ self->pub.mydest = DestCopyDR;
+ return (DestReceiver *)self;
+ }
Index: src/backend/parser/gram.y
===================================================================
RCS file: /home/alvherre/cvs/pgsql/src/backend/parser/gram.y,v
retrieving revision 2.558
diff -c -p -r2.558 gram.y
*** src/backend/parser/gram.y 25 Aug 2006 04:06:51 -0000 2.558
--- src/backend/parser/gram.y 27 Aug 2006 05:33:07 -0000
*************** ClosePortalStmt:
*** 1614,1624 ****
/*****************************************************************************
*
* QUERY :
! * COPY <relname> ['(' columnList ')'] FROM/TO
[WITH options]
*
* BINARY, OIDS, and DELIMITERS kept in old
locations
* for backward compatibility. 2002-06-18
*
*****************************************************************************/
CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids
--- 1614,1627 ----
/*****************************************************************************
*
* QUERY :
! * COPY <relname> ['(' columnList ')'] FROM/TO
<file> [WITH options]
*
* BINARY, OIDS, and DELIMITERS kept in old
locations
* for backward compatibility. 2002-06-18
*
+ * COPY ( SELECT ... ) TO <file> [WITH options]
+ * This form doesn't have the backwards-compatible
locations for options.
+ *
*****************************************************************************/
CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids
*************** CopyStmt: COPY opt_binary qualified_name
*** 1626,1631 ****
--- 1629,1635 ----
{
CopyStmt *n = makeNode(CopyStmt);
n->relation = $3;
+ n->selectstmt = NULL;
n->attlist = $4;
n->is_from = $6;
n->filename = $7;
*************** CopyStmt: COPY opt_binary qualified_name
*** 1642,1647 ****
--- 1646,1664 ----
n->options =
list_concat(n->options, $10);
$$ = (Node *)n;
}
+ | COPY select_with_parens TO copy_file_name opt_with
+ copy_opt_list
+ {
+ CopyStmt *n = makeNode(CopyStmt);
+ n->relation = NULL;
+ n->selectstmt = (SelectStmt *) $2;
+ n->attlist = NIL;
+ n->is_from = false;
+ n->filename = $4;
+
+ n->options = $6;
+ $$ = (Node *)n;
+ }
;
copy_from:
*************** copy_from:
*** 1652,1658 ****
/*
* copy_file_name NULL indicates stdio is used. Whether stdin or stdout is
* used depends on the direction. (It really doesn't make sense to copy from
! * stdout. We silently correct the "typo". - AY 9/94
*/
copy_file_name:
Sconst
{ $$ = $1; }
--- 1669,1675 ----
/*
* copy_file_name NULL indicates stdio is used. Whether stdin or stdout is
* used depends on the direction. (It really doesn't make sense to copy from
! * stdout. We silently correct the "typo".) - AY 9/94
*/
copy_file_name:
Sconst
{ $$ = $1; }
Index: src/backend/tcop/dest.c
===================================================================
RCS file: /home/alvherre/cvs/pgsql/src/backend/tcop/dest.c,v
retrieving revision 1.69
diff -c -p -r1.69 dest.c
*** src/backend/tcop/dest.c 12 Aug 2006 02:52:05 -0000 1.69
--- src/backend/tcop/dest.c 27 Aug 2006 04:53:19 -0000
***************
*** 30,35 ****
--- 30,36 ----
#include "access/printtup.h"
#include "access/xact.h"
+ #include "commands/copy.h"
#include "executor/executor.h"
#include "executor/tstoreReceiver.h"
#include "libpq/libpq.h"
*************** CreateDestReceiver(CommandDest dest, Por
*** 117,122 ****
--- 118,126 ----
case DestSPI:
return &spi_printtupDR;
+ case DestCopyDR:
+ return CreateCopyDestReceiver();
+
case DestTuplestore:
if (portal == NULL)
elog(ERROR, "no portal specified for
DestTuplestore receiver");
*************** EndCommand(const char *commandTag, Comma
*** 153,158 ****
--- 157,163 ----
case DestSPI:
case DestTuplestore:
case DestIntoRel:
+ case DestCopyDR:
break;
}
}
*************** NullCommand(CommandDest dest)
*** 192,197 ****
--- 197,203 ----
case DestSPI:
case DestTuplestore:
case DestIntoRel:
+ case DestCopyDR:
break;
}
}
*************** ReadyForQuery(CommandDest dest)
*** 233,238 ****
--- 239,245 ----
case DestSPI:
case DestTuplestore:
case DestIntoRel:
+ case DestCopyDR:
break;
}
}
Index: src/bin/psql/copy.c
===================================================================
RCS file: /home/alvherre/cvs/pgsql/src/bin/psql/copy.c,v
retrieving revision 1.66
diff -c -p -r1.66 copy.c
*** src/bin/psql/copy.c 14 Jun 2006 16:49:02 -0000 1.66
--- src/bin/psql/copy.c 27 Aug 2006 04:53:19 -0000
***************
*** 36,42 ****
* -- parses \copy command line
*
* The documented preferred syntax is:
! * \copy tablename [(columnlist)] from|to filename
* [ with ] [ binary ] [ oids ] [ delimiter [as] char ] [ null [as]
string ]
*
* The pre-7.3 syntax was:
--- 36,42 ----
* -- parses \copy command line
*
* The documented preferred syntax is:
! * \copy { tablename | viewname | ( select stmt ) } [(columnlist)] from|to
filename
* [ with ] [ binary ] [ oids ] [ delimiter [as] char ] [ null [as]
string ]
*
* The pre-7.3 syntax was:
*************** parse_slash_copy(const char *args)
*** 142,147 ****
--- 142,168 ----
result->table = pg_strdup(token);
+ /* Handle COPY (SELECT) case */
+ if (token[0] == '(')
+ {
+ char *selectstmt;
+ int brackets = 1;
+
+ while (brackets > 0)
+ {
+ token = strtokx(NULL, whitespace, ".,()", "\"'",
+ 0, false, false, pset.encoding);
+ if (!token)
+ goto error;
+ if (token[0] == '(')
+ brackets++;
+ else if (token[0] == ')')
+ brackets--;
+ xstrcat(&result->table, " ");
+ xstrcat(&result->table, token);
+ }
+ }
+
token = strtokx(NULL, whitespace, ".,()", "\"",
0, false, false, pset.encoding);
if (!token)
Index: src/include/commands/copy.h
===================================================================
RCS file: /home/alvherre/cvs/pgsql/src/include/commands/copy.h,v
retrieving revision 1.27
diff -c -p -r1.27 copy.h
*** src/include/commands/copy.h 5 Mar 2006 15:58:55 -0000 1.27
--- src/include/commands/copy.h 27 Aug 2006 04:53:19 -0000
***************
*** 7,13 ****
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
! * $PostgreSQL: pgsql/src/include/commands/copy.h,v 1.26 2006/03/03 19:54:10
tgl Exp $
*
*-------------------------------------------------------------------------
*/
--- 7,13 ----
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
! * $PostgreSQL: pgsql/src/include/commands/copy.h,v 1.27 2006-03-05 15:58:55
momjian Exp $
*
*-------------------------------------------------------------------------
*/
***************
*** 15,22 ****
--- 15,24 ----
#define COPY_H
#include "nodes/parsenodes.h"
+ #include "tcop/dest.h"
+ extern DestReceiver *CreateCopyDestReceiver(void);
extern uint64 DoCopy(const CopyStmt *stmt);
#endif /* COPY_H */
Index: src/include/nodes/parsenodes.h
===================================================================
RCS file: /home/alvherre/cvs/pgsql/src/include/nodes/parsenodes.h,v
retrieving revision 1.325
diff -c -p -r1.325 parsenodes.h
*** src/include/nodes/parsenodes.h 25 Aug 2006 04:06:56 -0000 1.325
--- src/include/nodes/parsenodes.h 27 Aug 2006 05:26:28 -0000
*************** typedef struct GrantRoleStmt
*** 1017,1023 ****
--- 1017,1025 ----
typedef struct CopyStmt
{
NodeTag type;
+ /* one of the two following must be NULL and the other must not be: */
RangeVar *relation; /* the relation to copy */
+ SelectStmt *selectstmt; /* the SELECT to copy */
List *attlist; /* List of column names (as Strings),
or NIL
* for all
columns */
bool is_from; /* TO or FROM */
Index: src/include/tcop/dest.h
===================================================================
RCS file: /home/alvherre/cvs/pgsql/src/include/tcop/dest.h,v
retrieving revision 1.51
diff -c -p -r1.51 dest.h
*** src/include/tcop/dest.h 12 Aug 2006 02:52:06 -0000 1.51
--- src/include/tcop/dest.h 27 Aug 2006 04:53:19 -0000
*************** typedef enum
*** 85,91 ****
DestRemoteExecute, /* sent to frontend, in Execute
command */
DestSPI, /* results sent to SPI
manager */
DestTuplestore, /* results sent to Tuplestore */
! DestIntoRel /* results sent to
relation (SELECT INTO) */
} CommandDest;
/* ----------------
--- 85,92 ----
DestRemoteExecute, /* sent to frontend, in Execute
command */
DestSPI, /* results sent to SPI
manager */
DestTuplestore, /* results sent to Tuplestore */
! DestIntoRel, /* results sent to
relation (SELECT INTO) */
! DestCopyDR /* results sent to file
*/
} CommandDest;
/* ----------------
Index: src/interfaces/ecpg/test/Makefile.regress
===================================================================
RCS file: /home/alvherre/cvs/pgsql/src/interfaces/ecpg/test/Makefile.regress,v
retrieving revision 1.3
diff -c -p -r1.3 Makefile.regress
*** src/interfaces/ecpg/test/Makefile.regress 9 Aug 2006 22:48:17 -0000
1.3
--- src/interfaces/ecpg/test/Makefile.regress 28 Aug 2006 02:50:19 -0000
***************
*** 1,4 ****
! override CPPFLAGS := -I$(srcdir)/../../include -I$(libpq_srcdir) $(CPPFLAGS)
override CFLAGS += $(PTHREAD_CFLAGS)
override LDFLAGS := -L../../ecpglib -L../../pgtypeslib -L../../../libpq
$(LDFLAGS)
--- 1,4 ----
! override CPPFLAGS := -I$(srcdir)/../../include
-I$(top_builddir)/$(subdir)/../../include -I$(libpq_srcdir) $(CPPFLAGS)
override CFLAGS += $(PTHREAD_CFLAGS)
override LDFLAGS := -L../../ecpglib -L../../pgtypeslib -L../../../libpq
$(LDFLAGS)
Index: src/test/regress/parallel_schedule
===================================================================
RCS file: /home/alvherre/cvs/pgsql/src/test/regress/parallel_schedule,v
retrieving revision 1.34
diff -c -p -r1.34 parallel_schedule
*** src/test/regress/parallel_schedule 12 Aug 2006 02:52:06 -0000 1.34
--- src/test/regress/parallel_schedule 27 Aug 2006 04:53:19 -0000
*************** test: create_function_2
*** 34,40 ****
# execute two copy tests parallel, to check that copy itself
# is concurrent safe.
# ----------
! test: copy
# ----------
# The third group of parallel test
--- 34,40 ----
# execute two copy tests parallel, to check that copy itself
# is concurrent safe.
# ----------
! test: copy copyselect
# ----------
# The third group of parallel test
Index: src/test/regress/expected/alter_table.out
===================================================================
RCS file: /home/alvherre/cvs/pgsql/src/test/regress/expected/alter_table.out,v
retrieving revision 1.97
diff -c -p -r1.97 alter_table.out
*** src/test/regress/expected/alter_table.out 21 Aug 2006 00:57:26 -0000
1.97
--- src/test/regress/expected/alter_table.out 27 Aug 2006 04:53:19 -0000
*************** alter table test drop a;
*** 947,955 ****
copy test to stdout;
2 3
copy test(a) to stdout;
! ERROR: column "a" of relation "test" does not exist
copy test("........pg.dropped.1........") to stdout;
! ERROR: column "........pg.dropped.1........" of relation "test" does not
exist
copy test from stdin;
ERROR: extra data after last expected column
CONTEXT: COPY test, line 1: "10 11 12"
--- 947,955 ----
copy test to stdout;
2 3
copy test(a) to stdout;
! ERROR: column "a" does not exist
copy test("........pg.dropped.1........") to stdout;
! ERROR: column "........pg.dropped.1........" does not exist
copy test from stdin;
ERROR: extra data after last expected column
CONTEXT: COPY test, line 1: "10 11 12"
*************** select * from test;
*** 968,976 ****
(2 rows)
copy test(a) from stdin;
! ERROR: column "a" of relation "test" does not exist
copy test("........pg.dropped.1........") from stdin;
! ERROR: column "........pg.dropped.1........" of relation "test" does not
exist
copy test(b,c) from stdin;
select * from test;
b | c
--- 968,976 ----
(2 rows)
copy test(a) from stdin;
! ERROR: column "a" does not exist
copy test("........pg.dropped.1........") from stdin;
! ERROR: column "........pg.dropped.1........" does not exist
copy test(b,c) from stdin;
select * from test;
b | c
Index: src/test/regress/expected/copy2.out
===================================================================
RCS file: /home/alvherre/cvs/pgsql/src/test/regress/expected/copy2.out,v
retrieving revision 1.25
diff -c -p -r1.25 copy2.out
*** src/test/regress/expected/copy2.out 27 Feb 2006 16:09:50 -0000 1.25
--- src/test/regress/expected/copy2.out 27 Aug 2006 04:53:19 -0000
*************** COPY x (b, d) from stdin;
*** 28,34 ****
COPY x (a, b, c, d, e) from stdin;
-- non-existent column in column list: should fail
COPY x (xyz) from stdin;
! ERROR: column "xyz" of relation "x" does not exist
-- too many columns in column list: should fail
COPY x (a, b, c, d, e, d, c) from stdin;
ERROR: column "d" specified more than once
--- 28,34 ----
COPY x (a, b, c, d, e) from stdin;
-- non-existent column in column list: should fail
COPY x (xyz) from stdin;
! ERROR: column "xyz" does not exist
-- too many columns in column list: should fail
COPY x (a, b, c, d, e, d, c) from stdin;
ERROR: column "d" specified more than once
---------------------------(end of broadcast)---------------------------
TIP 1: if posting/reading through Usenet, please send an appropriate
subscribe-nomail command to [EMAIL PROTECTED] so that your
message can get through to the mailing list cleanly