diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml
index c1d87e601c2..cb1eaa08048 100644
--- a/doc/src/sgml/ref/copy.sgml
+++ b/doc/src/sgml/ref/copy.sgml
@@ -1,5 +1,5 @@
@@ -33,7 +33,7 @@ COPY tablename [ ( escape' ]
[ FORCE NOT NULL column [, ...] ]
-COPY tablename [ ( column [, ...] ) ]
+COPY { tablename [ ( column [, ...] ) ] | ( query ) }
TO { 'filename' | STDOUT }
[ [ WITH ]
[ BINARY ]
@@ -57,7 +57,8 @@ COPY tablename [ ( COPY TO copies the contents of a table
to> a file, while COPY FROM copies
data from> a file to a table (appending the data to
- whatever is in the table already).
+ whatever is in the table already). COPY TO
+ can also copy the results of a SELECT> query.
@@ -97,7 +98,17 @@ COPY tablename [ (
An optional list of columns to be copied. If no column list is
- specified, all columns will be used.
+ specified, all columns of the table will be copied.
+
+
+
+
+
+ query
+
+
+ A SELECT> query whose results are to be copied.
+ Note that parentheses are required around the query.
@@ -148,7 +159,8 @@ COPY tablename [ (
Specifies copying the OID for each row. (An error is raised if
OIDS is specified for a table that does not
- have OIDs.)
+ have OIDs, or in the case of copying a query.)
@@ -265,7 +277,7 @@ COPY tablename [ ( count
The count is the number
- of rows inserted into or copied from the table.
+ of rows copied.
@@ -274,7 +286,8 @@ COPY count
COPY can only be used with plain tables, not
- with views.
+ with views. However, you can write COPY (SELECT * FROM
+ viewname) TO ....
@@ -320,8 +333,8 @@ COPY count
server in the case of COPY TO, but for
COPY FROM you do have the option of reading from
a file specified by a relative path. The path will be interpreted
- relative to the working directory of the server process (somewhere below
- the data directory), not the client's working directory.
+ relative to the working directory of the server process (normally
+ the cluster's data directory), not the client's working directory.
@@ -737,14 +750,9 @@ COPY country FROM '/usr1/proj/bray/sql/country_data';
- To copy into a file just the countries whose names start with 'A'
- using a temporary table which is automatically deleted:
+ To copy into a file just the countries whose names start with 'A':
-BEGIN;
-CREATE TEMP TABLE a_list_countries AS
- SELECT * FROM country WHERE country_name LIKE 'A%';
-COPY a_list_countries TO '/usr1/proj/bray/sql/a_list_countries.copy';
-ROLLBACK;
+COPY (SELECT * FROM country WHERE country_name LIKE 'A%') TO '/usr1/proj/bray/sql/a_list_countries.copy';
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index d6528d0bc10..acac4d3daf3 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1,5 +1,5 @@
@@ -739,8 +739,7 @@ testdb=>
- \copy table
- [ ( column_list ) ]
+ \copy { table [ ( column_list ) ] | ( query ) }
{ from | to }
{ filename | stdin | stdout | pstdin | pstdout }
[ with ]
@@ -779,9 +778,7 @@ testdb=>
- \copy table from stdin | stdout
+ \copy ... from stdin | to stdout
reads/writes based on the command input and output respectively.
All rows are read from the same source that issued the command,
continuing until \. is read or the stream
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 4242c1aff1c..569d86eee2b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.268 2006/07/14 14:52:18 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.269 2006/08/30 23:34:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -31,6 +31,7 @@
#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
+#include "optimizer/planner.h"
#include "parser/parse_relation.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
@@ -99,18 +100,21 @@ typedef struct CopyStateData
/* parameters from the COPY command */
Relation rel; /* relation to copy to or from */
+ QueryDesc *queryDesc; /* executable query to copy from */
List *attnumlist; /* integer list of attnums to copy */
+ char *filename; /* filename, or NULL for STDIN/STDOUT */
bool binary; /* binary format? */
bool oids; /* include OIDs? */
bool csv_mode; /* Comma Separated Value format? */
bool header_line; /* CSV header line? */
char *null_print; /* NULL marker string (server encoding!) */
int null_print_len; /* length of same */
+ char *null_print_client; /* same converted to client encoding */
char *delim; /* column delimiter (must be 1 byte) */
char *quote; /* CSV quote char (must be 1 byte) */
char *escape; /* CSV escape char (must be 1 byte) */
- List *force_quote_atts; /* integer list of attnums to FQ */
- List *force_notnull_atts; /* integer list of attnums to FNN */
+ bool *force_quote_flags; /* per-column CSV FQ flags */
+ bool *force_notnull_flags; /* per-column CSV FNN flags */
/* these are just for error messages, see copy_in_error_callback */
const char *cur_relname; /* table name for error messages */
@@ -118,6 +122,12 @@ typedef struct CopyStateData
const char *cur_attname; /* current att for error messages */
const char *cur_attval; /* current att value for error messages */
+ /*
+ * Working state for COPY TO
+ */
+ FmgrInfo *out_functions; /* lookup info for output functions */
+ MemoryContext rowcontext; /* per-row evaluation context */
+
/*
* These variables are used to reduce overhead in textual COPY FROM.
*
@@ -153,6 +163,13 @@ typedef struct CopyStateData
typedef CopyStateData *CopyState;
+/* DestReceiver for COPY (SELECT) TO */
+typedef struct
+{
+ DestReceiver pub; /* publicly-known function pointers */
+ CopyState cstate; /* CopyStateData for the command */
+} DR_copy;
+
/*
* These macros centralize code used to process line_buf and raw_buf buffers.
@@ -225,6 +242,8 @@ static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";
/* non-export function prototypes */
static void DoCopyTo(CopyState cstate);
static void CopyTo(CopyState cstate);
+static void CopyOneRowTo(CopyState cstate, Oid tupleOid,
+ Datum *values, bool *nulls);
static void CopyFrom(CopyState cstate);
static bool CopyReadLine(CopyState cstate);
static bool CopyReadLineText(CopyState cstate);
@@ -239,7 +258,8 @@ static Datum CopyReadBinaryAttribute(CopyState cstate,
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 List *CopyGetAttnums(TupleDesc tupDesc, Relation rel,
+ List *attnamelist);
static char *limit_printout_length(const char *str);
/* Low-level communications functions */
@@ -668,7 +688,8 @@ CopyLoadRawBuf(CopyState cstate)
* DoCopy executes the SQL COPY statement.
*
* Either unload or reload contents of table , depending on .
- * ( = TRUE means we are inserting into the table.)
+ * ( = TRUE means we are inserting into the table.) In the "TO" case
+ * we also support copying the output of an arbitrary SELECT query.
*
* If is false, transfer is between the table and the file named
* . Otherwise, transfer is between the table and our regular
@@ -697,8 +718,6 @@ uint64
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;
@@ -707,6 +726,8 @@ DoCopy(const CopyStmt *stmt)
AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT);
AclResult aclresult;
ListCell *option;
+ TupleDesc tupDesc;
+ int num_phys_attrs;
uint64 processed;
/* Allocate workspace and zero all fields */
@@ -920,23 +941,7 @@ DoCopy(const CopyStmt *stmt)
(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),
- errmsg("transaction is read-only")));
-
- /* Check permissions. */
- aclresult = pg_class_aclcheck(RelationGetRelid(cstate->rel), GetUserId(),
- required_access);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, ACL_KIND_CLASS,
- RelationGetRelationName(cstate->rel));
+ /* Disallow file COPY except to superusers. */
if (!pipe && !superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -944,26 +949,137 @@ DoCopy(const CopyStmt *stmt)
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)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("table \"%s\" does not have OIDs",
- RelationGetRelationName(cstate->rel))));
+ if (stmt->relation)
+ {
+ Assert(!stmt->query);
+ cstate->queryDesc = NULL;
+
+ /* Open and lock the relation, using the appropriate lock type. */
+ cstate->rel = heap_openrv(stmt->relation,
+ (is_from ? RowExclusiveLock : AccessShareLock));
+
+ /* Check relation permissions. */
+ aclresult = pg_class_aclcheck(RelationGetRelid(cstate->rel),
+ GetUserId(),
+ required_access);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_CLASS,
+ RelationGetRelationName(cstate->rel));
+
+ /* check read-only transaction */
+ if (XactReadOnly && is_from &&
+ !isTempNamespace(RelationGetNamespace(cstate->rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
+ errmsg("transaction is read-only")));
+
+ /* Don't allow COPY w/ OIDs to or from a table without them */
+ if (cstate->oids && !cstate->rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("table \"%s\" does not have OIDs",
+ RelationGetRelationName(cstate->rel))));
+
+ tupDesc = RelationGetDescr(cstate->rel);
+ }
+ else
+ {
+ Query *query = stmt->query;
+ List *rewritten;
+ Plan *plan;
+ DestReceiver *dest;
+
+ Assert(query);
+ Assert(!is_from);
+ cstate->rel = NULL;
+
+ /* 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")));
+
+ if (query->into)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("COPY (SELECT INTO) is not supported")));
+
+ /*
+ * The query has already been through parse analysis, but not
+ * rewriting or planning. Do that now.
+ *
+ * Because the planner is not cool about not scribbling on its input,
+ * we make a preliminary copy of the source querytree. This prevents
+ * problems in the case that the COPY is in a portal or plpgsql
+ * function and is executed repeatedly. (See also the same hack in
+ * EXPLAIN, DECLARE CURSOR and PREPARE.) XXX the planner really
+ * shouldn't modify its input ... FIXME someday.
+ */
+ query = copyObject(query);
+ Assert(query->commandType == CMD_SELECT);
+
+ /*
+ * Must acquire locks in case we didn't come fresh from the parser.
+ * XXX this also scribbles on query, another reason for copyObject
+ */
+ AcquireRewriteLocks(query);
+
+ /* Rewrite through rule system */
+ rewritten = QueryRewrite(query);
+
+ /* We don't expect more or less than one result query */
+ if (list_length(rewritten) != 1)
+ elog(ERROR, "unexpected rewrite result");
+
+ query = (Query *) linitial(rewritten);
+ Assert(query->commandType == CMD_SELECT);
+
+ /* plan the query */
+ plan = planner(query, false, 0, NULL);
+
+ /*
+ * Update snapshot command ID to ensure this query sees results of any
+ * previously executed queries. (It's a bit cheesy to modify
+ * ActiveSnapshot without making a copy, but for the limited ways in
+ * which COPY can be invoked, I think it's OK, because the active
+ * snapshot shouldn't be shared with anything else anyway.)
+ */
+ ActiveSnapshot->curcid = GetCurrentCommandId();
+
+ /* Create dest receiver for COPY OUT */
+ dest = CreateDestReceiver(DestCopyOut, NULL);
+ ((DR_copy *) dest)->cstate = cstate;
+
+ /* Create a QueryDesc requesting no output */
+ cstate->queryDesc = CreateQueryDesc(query, plan,
+ ActiveSnapshot, InvalidSnapshot,
+ dest, NULL, false);
+
+ /*
+ * Call ExecutorStart to prepare the plan for execution.
+ *
+ * ExecutorStart computes a result tupdesc for us
+ */
+ ExecutorStart(cstate->queryDesc, 0);
+
+ tupDesc = cstate->queryDesc->tupDesc;
+ }
/* Generate or convert list of attributes to process */
- cstate->attnumlist = CopyGetAttnums(cstate->rel, attnamelist);
+ cstate->attnumlist = CopyGetAttnums(tupDesc, cstate->rel, attnamelist);
- /* Convert FORCE QUOTE name list to column numbers, check validity */
+ num_phys_attrs = tupDesc->natts;
+
+ /* Convert FORCE QUOTE name list to per-column flags, check validity */
+ cstate->force_quote_flags = (bool *) palloc0(num_phys_attrs * sizeof(bool));
if (force_quote)
{
- TupleDesc tupDesc = RelationGetDescr(cstate->rel);
- Form_pg_attribute *attr = tupDesc->attrs;
+ List *attnums;
ListCell *cur;
- cstate->force_quote_atts = CopyGetAttnums(cstate->rel, force_quote);
+ attnums = CopyGetAttnums(tupDesc, cstate->rel, force_quote);
- foreach(cur, cstate->force_quote_atts)
+ foreach(cur, attnums)
{
int attnum = lfirst_int(cur);
@@ -971,21 +1087,21 @@ DoCopy(const CopyStmt *stmt)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("FORCE QUOTE column \"%s\" not referenced by COPY",
- NameStr(attr[attnum - 1]->attname))));
+ NameStr(tupDesc->attrs[attnum - 1]->attname))));
+ cstate->force_quote_flags[attnum - 1] = true;
}
}
- /* Convert FORCE NOT NULL name list to column numbers, check validity */
+ /* Convert FORCE NOT NULL name list to per-column flags, check validity */
+ cstate->force_notnull_flags = (bool *) palloc0(num_phys_attrs * sizeof(bool));
if (force_notnull)
{
- TupleDesc tupDesc = RelationGetDescr(cstate->rel);
- Form_pg_attribute *attr = tupDesc->attrs;
+ List *attnums;
ListCell *cur;
- cstate->force_notnull_atts = CopyGetAttnums(cstate->rel,
- force_notnull);
+ attnums = CopyGetAttnums(tupDesc, cstate->rel, force_notnull);
- foreach(cur, cstate->force_notnull_atts)
+ foreach(cur, attnums)
{
int attnum = lfirst_int(cur);
@@ -993,7 +1109,8 @@ DoCopy(const CopyStmt *stmt)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("FORCE NOT NULL column \"%s\" not referenced by COPY",
- NameStr(attr[attnum - 1]->attname))));
+ NameStr(tupDesc->attrs[attnum - 1]->attname))));
+ cstate->force_notnull_flags[attnum - 1] = true;
}
}
@@ -1018,138 +1135,27 @@ DoCopy(const CopyStmt *stmt)
cstate->encoding_embeds_ascii = PG_ENCODING_IS_CLIENT_ONLY(cstate->client_encoding);
cstate->copy_dest = COPY_FILE; /* default */
+ cstate->filename = stmt->filename;
- if (is_from)
- { /* copy from file to database */
- if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
- {
- if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot copy to view \"%s\"",
- RelationGetRelationName(cstate->rel))));
- else if (cstate->rel->rd_rel->relkind == RELKIND_SEQUENCE)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot copy to sequence \"%s\"",
- RelationGetRelationName(cstate->rel))));
- else
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot copy to non-table relation \"%s\"",
- RelationGetRelationName(cstate->rel))));
- }
- if (pipe)
- {
- if (whereToSendOutput == DestRemote)
- ReceiveCopyBegin(cstate);
- else
- cstate->copy_file = stdin;
- }
- else
- {
- struct stat st;
-
- cstate->copy_file = AllocateFile(filename, PG_BINARY_R);
-
- if (cstate->copy_file == NULL)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\" for reading: %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)));
- }
- }
-
+ if (is_from) /* copy from file to database */
CopyFrom(cstate);
- }
- else
- { /* copy from database to file */
- if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
- {
- if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot copy from view \"%s\"",
- RelationGetRelationName(cstate->rel))));
- else if (cstate->rel->rd_rel->relkind == RELKIND_SEQUENCE)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot copy from sequence \"%s\"",
- RelationGetRelationName(cstate->rel))));
- else
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot copy from non-table relation \"%s\"",
- RelationGetRelationName(cstate->rel))));
- }
- 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)));
- }
- }
-
+ else /* copy from database to file */
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)));
- }
/*
- * Close the relation. If reading, we can release the AccessShareLock we
- * got; if writing, we should hold the lock until end of transaction to
- * ensure that updates will be committed before lock is released.
+ * Close the relation or query. If reading, we can release the
+ * AccessShareLock we 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));
+ if (cstate->rel)
+ heap_close(cstate->rel, (is_from ? NoLock : AccessShareLock));
+ else
+ {
+ /* Close down the query and free resources. */
+ ExecutorEnd(cstate->queryDesc);
+ FreeQueryDesc(cstate->queryDesc);
+ }
/* Clean up storage (probably not really necessary) */
processed = cstate->processed;
@@ -1164,12 +1170,78 @@ DoCopy(const CopyStmt *stmt)
/*
- * This intermediate routine just exists to localize the effects of setjmp
+ * This intermediate routine exists mainly to localize the effects of setjmp
* so we don't need to plaster a lot of variables with "volatile".
*/
static void
DoCopyTo(CopyState cstate)
{
+ bool pipe = (cstate->filename == NULL);
+
+ if (cstate->rel)
+ {
+ if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+ {
+ if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from view \"%s\"",
+ RelationGetRelationName(cstate->rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
+ else if (cstate->rel->rd_rel->relkind == RELKIND_SEQUENCE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from sequence \"%s\"",
+ RelationGetRelationName(cstate->rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from non-table relation \"%s\"",
+ RelationGetRelationName(cstate->rel))));
+ }
+ }
+
+ 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(cstate->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(cstate->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",
+ cstate->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", cstate->filename)));
+ }
+ }
+
PG_TRY();
{
if (cstate->fe_copy)
@@ -1191,40 +1263,41 @@ DoCopyTo(CopyState cstate)
PG_RE_THROW();
}
PG_END_TRY();
+
+ if (!pipe)
+ {
+ if (FreeFile(cstate->copy_file))
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not write to file \"%s\": %m",
+ cstate->filename)));
+ }
}
/*
- * Copy from relation TO file.
+ * Copy from relation or query 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;
+ if (cstate->rel)
+ tupDesc = RelationGetDescr(cstate->rel);
+ else
+ tupDesc = cstate->queryDesc->tupDesc;
attr = tupDesc->attrs;
num_phys_attrs = tupDesc->natts;
- attr_count = list_length(cstate->attnumlist);
- null_print_client = cstate->null_print; /* default */
+ 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. */
- out_functions = (FmgrInfo *) palloc(num_phys_attrs * sizeof(FmgrInfo));
- force_quote = (bool *) palloc(num_phys_attrs * sizeof(bool));
+ cstate->out_functions = (FmgrInfo *) palloc(num_phys_attrs * sizeof(FmgrInfo));
foreach(cur, cstate->attnumlist)
{
int attnum = lfirst_int(cur);
@@ -1239,12 +1312,7 @@ CopyTo(CopyState cstate)
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;
+ fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]);
}
/*
@@ -1253,11 +1321,11 @@ CopyTo(CopyState cstate)
* 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,
- ALLOCSET_DEFAULT_MAXSIZE);
+ cstate->rowcontext = AllocSetContextCreate(CurrentMemoryContext,
+ "COPY TO",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
if (cstate->binary)
{
@@ -1282,7 +1350,7 @@ CopyTo(CopyState cstate)
* 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_client = pg_server_to_client(cstate->null_print,
cstate->null_print_len);
/* if a header has been requested send the line */
@@ -1309,100 +1377,36 @@ CopyTo(CopyState cstate)
}
}
- scandesc = heap_beginscan(cstate->rel, ActiveSnapshot, 0, NULL);
-
- while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) != NULL)
+ if (cstate->rel)
{
- bool need_delim = false;
+ Datum *values;
+ bool *nulls;
+ HeapScanDesc scandesc;
+ HeapTuple tuple;
- CHECK_FOR_INTERRUPTS();
+ values = (Datum *) palloc(num_phys_attrs * sizeof(Datum));
+ nulls = (bool *) palloc(num_phys_attrs * sizeof(bool));
- MemoryContextReset(mycontext);
- oldcontext = MemoryContextSwitchTo(mycontext);
+ scandesc = heap_beginscan(cstate->rel, ActiveSnapshot, 0, NULL);
- if (cstate->binary)
+ while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) != NULL)
{
- /* 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);
+ CHECK_FOR_INTERRUPTS();
- /* 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;
- }
+ /* Deconstruct the tuple ... faster than repeated heap_getattr */
+ heap_deform_tuple(tuple, tupDesc, values, nulls);
+
+ /* Format and send the data */
+ CopyOneRowTo(cstate, HeapTupleGetOid(tuple), values, nulls);
}
- 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);
+ }
+ else
+ {
+ /* run the plan --- the dest receiver will send tuples */
+ ExecutorRun(cstate->queryDesc, ForwardScanDirection, 0L);
}
-
- heap_endscan(scandesc);
if (cstate->binary)
{
@@ -1412,10 +1416,100 @@ CopyTo(CopyState cstate)
CopySendEndOfRow(cstate);
}
- MemoryContextDelete(mycontext);
+ MemoryContextDelete(cstate->rowcontext);
+}
- pfree(out_functions);
- pfree(force_quote);
+/*
+ * Emit one row during CopyTo().
+ */
+static void
+CopyOneRowTo(CopyState cstate, Oid tupleOid, Datum *values, bool *nulls)
+{
+ bool need_delim = false;
+ FmgrInfo *out_functions = cstate->out_functions;
+ MemoryContext oldcontext;
+ ListCell *cur;
+ char *string;
+
+ MemoryContextReset(cstate->rowcontext);
+ oldcontext = MemoryContextSwitchTo(cstate->rowcontext);
+
+ if (cstate->binary)
+ {
+ /* Binary per-tuple header */
+ CopySendInt16(cstate, list_length(cstate->attnumlist));
+ /* Send OID if wanted --- note attnumlist doesn't include it */
+ if (cstate->oids)
+ {
+ /* Hack --- assume Oid is same size as int32 */
+ CopySendInt32(cstate, sizeof(int32));
+ CopySendInt32(cstate, tupleOid);
+ }
+ }
+ 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(tupleOid)));
+ CopySendString(cstate, string);
+ need_delim = true;
+ }
+ }
+
+ foreach(cur, cstate->attnumlist)
+ {
+ int attnum = lfirst_int(cur);
+ Datum value = values[attnum - 1];
+ bool isnull = nulls[attnum - 1];
+
+ if (!cstate->binary)
+ {
+ if (need_delim)
+ CopySendChar(cstate, cstate->delim[0]);
+ need_delim = true;
+ }
+
+ if (isnull)
+ {
+ if (!cstate->binary)
+ CopySendString(cstate, 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,
+ cstate->force_quote_flags[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++;
}
@@ -1528,6 +1622,7 @@ limit_printout_length(const char *str)
static void
CopyFrom(CopyState cstate)
{
+ bool pipe = (cstate->filename == NULL);
HeapTuple tuple;
TupleDesc tupDesc;
Form_pg_attribute *attr;
@@ -1538,7 +1633,6 @@ CopyFrom(CopyState cstate)
FmgrInfo oid_in_function;
Oid *typioparams;
Oid oid_typioparam;
- bool *force_notnull;
int attnum;
int i;
Oid in_func_oid;
@@ -1558,6 +1652,56 @@ CopyFrom(CopyState cstate)
MemoryContext oldcontext = CurrentMemoryContext;
ErrorContextCallback errcontext;
+ Assert(cstate->rel);
+
+ if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+ {
+ if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy to view \"%s\"",
+ RelationGetRelationName(cstate->rel))));
+ else if (cstate->rel->rd_rel->relkind == RELKIND_SEQUENCE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy to sequence \"%s\"",
+ RelationGetRelationName(cstate->rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy to non-table relation \"%s\"",
+ RelationGetRelationName(cstate->rel))));
+ }
+
+ if (pipe)
+ {
+ if (whereToSendOutput == DestRemote)
+ ReceiveCopyBegin(cstate);
+ else
+ cstate->copy_file = stdin;
+ }
+ else
+ {
+ struct stat st;
+
+ cstate->copy_file = AllocateFile(cstate->filename, PG_BINARY_R);
+
+ if (cstate->copy_file == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\" for reading: %m",
+ cstate->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", cstate->filename)));
+ }
+ }
+
tupDesc = RelationGetDescr(cstate->rel);
attr = tupDesc->attrs;
num_phys_attrs = tupDesc->natts;
@@ -1599,7 +1743,6 @@ CopyFrom(CopyState cstate)
typioparams = (Oid *) palloc(num_phys_attrs * sizeof(Oid));
defmap = (int *) palloc(num_phys_attrs * sizeof(int));
defexprs = (ExprState **) palloc(num_phys_attrs * sizeof(ExprState *));
- force_notnull = (bool *) palloc(num_phys_attrs * sizeof(bool));
for (attnum = 1; attnum <= num_phys_attrs; attnum++)
{
@@ -1616,11 +1759,6 @@ CopyFrom(CopyState cstate)
&in_func_oid, &typioparams[attnum - 1]);
fmgr_info(in_func_oid, &in_functions[attnum - 1]);
- if (list_member_int(cstate->force_notnull_atts, attnum))
- force_notnull[attnum - 1] = true;
- else
- force_notnull[attnum - 1] = false;
-
/* Get default info if needed */
if (!list_member_int(cstate->attnumlist, attnum))
{
@@ -1810,7 +1948,8 @@ CopyFrom(CopyState cstate)
NameStr(attr[m]->attname))));
string = field_strings[fieldno++];
- if (cstate->csv_mode && string == NULL && force_notnull[m])
+ if (cstate->csv_mode && string == NULL &&
+ cstate->force_notnull_flags[m])
{
/* Go ahead and read the NULL string */
string = cstate->null_print;
@@ -1972,13 +2111,21 @@ CopyFrom(CopyState cstate)
pfree(typioparams);
pfree(defmap);
pfree(defexprs);
- pfree(force_notnull);
ExecDropSingleTupleTableSlot(slot);
ExecCloseIndices(resultRelInfo);
FreeExecutorState(estate);
+
+ if (!pipe)
+ {
+ if (FreeFile(cstate->copy_file))
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read from file \"%s\": %m",
+ cstate->filename)));
+ }
}
@@ -3055,16 +3202,17 @@ CopyAttributeOutCSV(CopyState cstate, char *string,
* The input attnamelist is either the user-specified column list,
* or NIL if there was none (in which case we want all the non-dropped
* columns).
+ *
+ * rel can be NULL ... it's only used for error reports.
*/
static List *
-CopyGetAttnums(Relation rel, List *attnamelist)
+CopyGetAttnums(TupleDesc tupDesc, 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;
@@ -3085,15 +3233,33 @@ CopyGetAttnums(Relation rel, List *attnamelist)
{
char *name = strVal(lfirst(l));
int attnum;
+ int i;
/* Lookup column name */
- /* Note we disallow system columns here */
- attnum = attnameAttNum(rel, name, false);
+ 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,
+ {
+ if (rel != NULL)
+ ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
- name, RelationGetRelationName(rel))));
+ name, RelationGetRelationName(rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" does not exist",
+ name)));
+ }
/* Check for duplicates */
if (list_member_int(attnums, attnum))
ereport(ERROR,
@@ -3106,3 +3272,66 @@ CopyGetAttnums(Relation rel, List *attnamelist)
return attnums;
}
+
+
+/*
+ * copy_dest_startup --- executor startup
+ */
+static void
+copy_dest_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
+{
+ /* no-op */
+}
+
+/*
+ * copy_dest_receive --- receive one tuple
+ */
+static void
+copy_dest_receive(TupleTableSlot *slot, DestReceiver *self)
+{
+ DR_copy *myState = (DR_copy *) self;
+ CopyState cstate = myState->cstate;
+
+ /* Make sure the tuple is fully deconstructed */
+ slot_getallattrs(slot);
+
+ /* And send the data */
+ CopyOneRowTo(cstate, InvalidOid, slot->tts_values, slot->tts_isnull);
+}
+
+/*
+ * copy_dest_shutdown --- executor end
+ */
+static void
+copy_dest_shutdown(DestReceiver *self)
+{
+ /* no-op */
+}
+
+/*
+ * copy_dest_destroy --- release DestReceiver object
+ */
+static void
+copy_dest_destroy(DestReceiver *self)
+{
+ pfree(self);
+}
+
+/*
+ * CreateCopyDestReceiver -- create a suitable DestReceiver object
+ */
+DestReceiver *
+CreateCopyDestReceiver(void)
+{
+ DR_copy *self = (DR_copy *) palloc(sizeof(DR_copy));
+
+ self->pub.receiveSlot = copy_dest_receive;
+ self->pub.rStartup = copy_dest_startup;
+ self->pub.rShutdown = copy_dest_shutdown;
+ self->pub.rDestroy = copy_dest_destroy;
+ self->pub.mydest = DestCopyOut;
+
+ self->cstate = NULL; /* will be set later */
+
+ return (DestReceiver *) self;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 391846bee2c..fb1037f1704 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.349 2006/08/25 04:06:49 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.350 2006/08/30 23:34:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1934,6 +1934,7 @@ _copyCopyStmt(CopyStmt *from)
CopyStmt *newnode = makeNode(CopyStmt);
COPY_NODE_FIELD(relation);
+ COPY_NODE_FIELD(query);
COPY_NODE_FIELD(attlist);
COPY_SCALAR_FIELD(is_from);
COPY_STRING_FIELD(filename);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3cb4b8aee31..1912cdd319a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -18,7 +18,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.283 2006/08/25 04:06:49 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.284 2006/08/30 23:34:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -863,6 +863,7 @@ static bool
_equalCopyStmt(CopyStmt *a, CopyStmt *b)
{
COMPARE_NODE_FIELD(relation);
+ COMPARE_NODE_FIELD(query);
COMPARE_NODE_FIELD(attlist);
COMPARE_SCALAR_FIELD(is_from);
COMPARE_STRING_FIELD(filename);
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index ae3469c86c7..23e956798d6 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.348 2006/08/25 04:06:51 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.349 2006/08/30 23:34:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -341,6 +341,19 @@ transformStmt(ParseState *pstate, Node *parseTree,
}
break;
+ case T_CopyStmt:
+ {
+ CopyStmt *n = (CopyStmt *) parseTree;
+
+ result = makeNode(Query);
+ result->commandType = CMD_UTILITY;
+ if (n->query)
+ n->query = transformStmt(pstate, (Node *) n->query,
+ extras_before, extras_after);
+ result->utilityStmt = (Node *) parseTree;
+ }
+ break;
+
case T_AlterTableStmt:
result = transformAlterTableStmt(pstate,
(AlterTableStmt *) parseTree,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a77e73a43fc..4a0ce515b8a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.558 2006/08/25 04:06:51 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.559 2006/08/30 23:34:21 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -1614,11 +1614,15 @@ ClosePortalStmt:
/*****************************************************************************
*
* QUERY :
- * COPY ['(' columnList ')'] FROM/TO [WITH options]
+ * COPY relname ['(' columnList ')'] FROM/TO file [WITH options]
*
* BINARY, OIDS, and DELIMITERS kept in old locations
* for backward compatibility. 2002-06-18
*
+ * COPY ( SELECT ... ) TO file [WITH options]
+ * This form doesn't have the backwards-compatible option
+ * syntax.
+ *
*****************************************************************************/
CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids
@@ -1626,6 +1630,7 @@ CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids
{
CopyStmt *n = makeNode(CopyStmt);
n->relation = $3;
+ n->query = NULL;
n->attlist = $4;
n->is_from = $6;
n->filename = $7;
@@ -1642,6 +1647,18 @@ CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids
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->query = (Query *) $2;
+ n->attlist = NIL;
+ n->is_from = false;
+ n->filename = $4;
+ n->options = $6;
+ $$ = (Node *)n;
+ }
;
copy_from:
@@ -1652,7 +1669,7 @@ copy_from:
/*
* 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
+ * stdout. We silently correct the "typo".) - AY 9/94
*/
copy_file_name:
Sconst { $$ = $1; }
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index fe7115b5f02..8276485834e 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/dest.c,v 1.69 2006/08/12 02:52:05 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/dest.c,v 1.70 2006/08/30 23:34:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -30,6 +30,7 @@
#include "access/printtup.h"
#include "access/xact.h"
+#include "commands/copy.h"
#include "executor/executor.h"
#include "executor/tstoreReceiver.h"
#include "libpq/libpq.h"
@@ -128,6 +129,9 @@ CreateDestReceiver(CommandDest dest, Portal portal)
case DestIntoRel:
return CreateIntoRelDestReceiver();
+
+ case DestCopyOut:
+ return CreateCopyDestReceiver();
}
/* should never get here */
@@ -153,6 +157,7 @@ EndCommand(const char *commandTag, CommandDest dest)
case DestSPI:
case DestTuplestore:
case DestIntoRel:
+ case DestCopyOut:
break;
}
}
@@ -192,6 +197,7 @@ NullCommand(CommandDest dest)
case DestSPI:
case DestTuplestore:
case DestIntoRel:
+ case DestCopyOut:
break;
}
}
@@ -233,6 +239,7 @@ ReadyForQuery(CommandDest dest)
case DestSPI:
case DestTuplestore:
case DestIntoRel:
+ case DestCopyOut:
break;
}
}
diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c
index 0d7cb5b4e00..6e514c1c064 100644
--- a/src/bin/psql/copy.c
+++ b/src/bin/psql/copy.c
@@ -3,7 +3,7 @@
*
* Copyright (c) 2000-2006, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/bin/psql/copy.c,v 1.67 2006/08/29 15:19:50 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/copy.c,v 1.68 2006/08/30 23:34:22 tgl Exp $
*/
#include "postgres_fe.h"
#include "copy.h"
@@ -39,6 +39,9 @@
* \copy tablename [(columnlist)] from|to filename
* [ with ] [ binary ] [ oids ] [ delimiter [as] char ] [ null [as] string ]
*
+ * \copy ( select stmt ) to filename
+ * [ with ] [ binary ] [ delimiter [as] char ] [ null [as] string ]
+ *
* The pre-7.3 syntax was:
* \copy [ binary ] tablename [(columnlist)] [with oids] from|to filename
* [ [using] delimiters char ] [ with null as string ]
@@ -142,6 +145,26 @@ parse_slash_copy(const char *args)
result->table = pg_strdup(token);
+ /* Handle COPY (SELECT) case */
+ if (token[0] == '(')
+ {
+ int parens = 1;
+
+ while (parens > 0)
+ {
+ token = strtokx(NULL, whitespace, ".,()", "\"'",
+ nonstd_backslash, true, false, pset.encoding);
+ if (!token)
+ goto error;
+ if (token[0] == '(')
+ parens++;
+ else if (token[0] == ')')
+ parens--;
+ xstrcat(&result->table, " ");
+ xstrcat(&result->table, token);
+ }
+ }
+
token = strtokx(NULL, whitespace, ".,()", "\"",
0, false, false, pset.encoding);
if (!token)
diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h
index 60ddd8c92c2..ec328576c3f 100644
--- a/src/include/commands/copy.h
+++ b/src/include/commands/copy.h
@@ -7,7 +7,7 @@
* 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 $
+ * $PostgreSQL: pgsql/src/include/commands/copy.h,v 1.28 2006/08/30 23:34:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -15,8 +15,11 @@
#define COPY_H
#include "nodes/parsenodes.h"
+#include "tcop/dest.h"
extern uint64 DoCopy(const CopyStmt *stmt);
+extern DestReceiver *CreateCopyDestReceiver(void);
+
#endif /* COPY_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7aa7bfd38e0..9e808396734 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.325 2006/08/25 04:06:56 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.326 2006/08/30 23:34:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1012,16 +1012,22 @@ typedef struct GrantRoleStmt
/* ----------------------
* Copy Statement
+ *
+ * We support "COPY relation FROM file", "COPY relation TO file", and
+ * "COPY (query) TO file". In any given CopyStmt, exactly one of "relation"
+ * and "query" must be non-NULL. Note: "query" is a SelectStmt before
+ * parse analysis, and a Query afterwards.
* ----------------------
*/
typedef struct CopyStmt
{
NodeTag type;
RangeVar *relation; /* the relation to copy */
+ Query *query; /* the query to copy */
List *attlist; /* List of column names (as Strings), or NIL
* for all columns */
bool is_from; /* TO or FROM */
- char *filename; /* if NULL, use stdin/stdout */
+ char *filename; /* filename, or NULL for STDIN/STDOUT */
List *options; /* List of DefElem nodes */
} CopyStmt;
diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h
index 0e0c640d2ac..0903f229ddc 100644
--- a/src/include/tcop/dest.h
+++ b/src/include/tcop/dest.h
@@ -54,7 +54,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/tcop/dest.h,v 1.51 2006/08/12 02:52:06 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/tcop/dest.h,v 1.52 2006/08/30 23:34:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -85,7 +85,8 @@ typedef enum
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) */
+ DestIntoRel, /* results sent to relation (SELECT INTO) */
+ DestCopyOut /* results sent to COPY TO code */
} CommandDest;
/* ----------------
diff --git a/src/test/regress/expected/copyselect.out b/src/test/regress/expected/copyselect.out
new file mode 100644
index 00000000000..c42bad143e4
--- /dev/null
+++ b/src/test/regress/expected/copyselect.out
@@ -0,0 +1,126 @@
+--
+-- Test cases for COPY (select) TO
+--
+create table test1 (id serial, t text);
+NOTICE: CREATE TABLE will create implicit sequence "test1_id_seq" for serial column "test1.id"
+insert into test1 (t) values ('a');
+insert into test1 (t) values ('b');
+insert into test1 (t) values ('c');
+insert into test1 (t) values ('d');
+insert into test1 (t) values ('e');
+create table test2 (id serial, t text);
+NOTICE: CREATE TABLE will create implicit sequence "test2_id_seq" for serial column "test2.id"
+insert into test2 (t) values ('A');
+insert into test2 (t) values ('B');
+insert into test2 (t) values ('C');
+insert into test2 (t) values ('D');
+insert into test2 (t) values ('E');
+create view v_test1
+as select 'v_'||t from test1;
+--
+-- Test COPY table TO
+--
+copy test1 to stdout;
+1 a
+2 b
+3 c
+4 d
+5 e
+--
+-- This should fail
+--
+copy v_test1 to stdout;
+ERROR: cannot copy from view "v_test1"
+HINT: Try the COPY (SELECT ...) TO variant.
+--
+-- Test COPY (select) TO
+--
+copy (select t from test1 where id=1) to stdout;
+a
+--
+-- Test COPY (select for update) TO
+--
+copy (select t from test1 where id=3 for update) to stdout;
+c
+--
+-- This should fail
+--
+copy (select t into temp test3 from test1 where id=3) to stdout;
+ERROR: COPY (SELECT INTO) is not supported
+--
+-- This should fail
+--
+copy (select * from test1) from stdin;
+ERROR: syntax error at or near "from"
+LINE 1: copy (select * from test1) from stdin;
+ ^
+--
+-- This should fail
+--
+copy (select * from test1) (t,id) to stdout;
+ERROR: syntax error at or near "("
+LINE 1: copy (select * from test1) (t,id) to stdout;
+ ^
+--
+-- Test JOIN
+--
+copy (select * from test1 join test2 using (id)) to stdout;
+1 a A
+2 b B
+3 c C
+4 d D
+5 e E
+--
+-- Test UNION SELECT
+--
+copy (select t from test1 where id = 1 UNION select * from v_test1) to stdout;
+a
+v_a
+v_b
+v_c
+v_d
+v_e
+--
+-- Test subselect
+--
+copy (select * from (select t from test1 where id = 1 UNION select * from v_test1) t1) to stdout;
+a
+v_a
+v_b
+v_c
+v_d
+v_e
+--
+-- Test headers, CSV and quotes
+--
+copy (select t from test1 where id = 1) to stdout csv header force quote t;
+t
+"a"
+--
+-- Test psql builtins, plain table
+--
+\copy test1 to stdout
+1 a
+2 b
+3 c
+4 d
+5 e
+--
+-- This should fail
+--
+\copy v_test1 to stdout
+ERROR: cannot copy from view "v_test1"
+HINT: Try the COPY (SELECT ...) TO variant.
+\copy: ERROR: cannot copy from view "v_test1"
+HINT: Try the COPY (SELECT ...) TO variant.
+--
+-- Test \copy (select ...)
+--
+\copy (select "id",'id','id""'||t,(id + 1)*id,t,"test1"."t" from test1 where id=3) to stdout
+3 id id""c 12 c c
+--
+-- Drop everything
+--
+drop table test2;
+drop view v_test1;
+drop table test1;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d675c07ff18..f13ea4792a3 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -1,6 +1,6 @@
# ----------
# The first group of parallel test
-# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.34 2006/08/12 02:52:06 tgl Exp $
+# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.35 2006/08/30 23:34:22 tgl Exp $
# ----------
test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeric
@@ -34,7 +34,7 @@ test: create_function_2
# execute two copy tests parallel, to check that copy itself
# is concurrent safe.
# ----------
-test: copy
+test: copy copyselect
# ----------
# The third group of parallel test
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 1bb8742da6c..2d44e585d38 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -1,4 +1,4 @@
-# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.32 2006/08/12 02:52:06 tgl Exp $
+# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.33 2006/08/30 23:34:22 tgl Exp $
# This should probably be in an order similar to parallel_schedule.
test: boolean
test: char
@@ -43,6 +43,7 @@ test: create_type
test: create_table
test: create_function_2
test: copy
+test: copyselect
test: constraints
test: triggers
test: create_misc
diff --git a/src/test/regress/sql/copyselect.sql b/src/test/regress/sql/copyselect.sql
new file mode 100644
index 00000000000..c2526487c8c
--- /dev/null
+++ b/src/test/regress/sql/copyselect.sql
@@ -0,0 +1,82 @@
+--
+-- Test cases for COPY (select) TO
+--
+create table test1 (id serial, t text);
+insert into test1 (t) values ('a');
+insert into test1 (t) values ('b');
+insert into test1 (t) values ('c');
+insert into test1 (t) values ('d');
+insert into test1 (t) values ('e');
+
+create table test2 (id serial, t text);
+insert into test2 (t) values ('A');
+insert into test2 (t) values ('B');
+insert into test2 (t) values ('C');
+insert into test2 (t) values ('D');
+insert into test2 (t) values ('E');
+
+create view v_test1
+as select 'v_'||t from test1;
+
+--
+-- Test COPY table TO
+--
+copy test1 to stdout;
+--
+-- This should fail
+--
+copy v_test1 to stdout;
+--
+-- Test COPY (select) TO
+--
+copy (select t from test1 where id=1) to stdout;
+--
+-- Test COPY (select for update) TO
+--
+copy (select t from test1 where id=3 for update) to stdout;
+--
+-- This should fail
+--
+copy (select t into temp test3 from test1 where id=3) to stdout;
+--
+-- This should fail
+--
+copy (select * from test1) from stdin;
+--
+-- This should fail
+--
+copy (select * from test1) (t,id) to stdout;
+--
+-- Test JOIN
+--
+copy (select * from test1 join test2 using (id)) to stdout;
+--
+-- Test UNION SELECT
+--
+copy (select t from test1 where id = 1 UNION select * from v_test1) to stdout;
+--
+-- Test subselect
+--
+copy (select * from (select t from test1 where id = 1 UNION select * from v_test1) t1) to stdout;
+--
+-- Test headers, CSV and quotes
+--
+copy (select t from test1 where id = 1) to stdout csv header force quote t;
+--
+-- Test psql builtins, plain table
+--
+\copy test1 to stdout
+--
+-- This should fail
+--
+\copy v_test1 to stdout
+--
+-- Test \copy (select ...)
+--
+\copy (select "id",'id','id""'||t,(id + 1)*id,t,"test1"."t" from test1 where id=3) to stdout
+--
+-- Drop everything
+--
+drop table test2;
+drop view v_test1;
+drop table test1;