copy-popen-20121126.patch
application/octet-stream
Filename: copy-popen-20121126.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: context
| File | + | − |
|---|---|---|
| contrib/file_fdw/file_fdw.c | 3 | 0 |
| doc/src/sgml/keywords.sgml | 7 | 0 |
| doc/src/sgml/ref/copy.sgml | 40 | 0 |
| doc/src/sgml/ref/psql-ref.sgml | 13 | 0 |
| src/backend/commands/copy.c | 94 | 0 |
| src/backend/nodes/copyfuncs.c | 1 | 0 |
| src/backend/nodes/equalfuncs.c | 1 | 0 |
| src/backend/parser/gram.y | 21 | 0 |
| src/backend/storage/file/fd.c | 75 | 0 |
| src/bin/psql/copy.c | 81 | 0 |
| src/bin/psql/stringutils.c | 1 | 3 |
| src/bin/psql/stringutils.h | 2 | 0 |
| src/include/commands/copy.h | 1 | 0 |
| src/include/nodes/parsenodes.h | 1 | 0 |
| src/include/parser/kwlist.h | 1 | 0 |
| src/include/storage/fd.h | 4 | 0 |
| src/interfaces/ecpg/preproc/ecpg.addons | 14 | 0 |
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
***************
*** 588,593 **** fileBeginForeignScan(ForeignScanState *node, int eflags)
--- 588,594 ----
*/
cstate = BeginCopyFrom(node->ss.ss_currentRelation,
filename,
+ false,
NIL,
options);
***************
*** 660,665 **** fileReScanForeignScan(ForeignScanState *node)
--- 661,667 ----
festate->cstate = BeginCopyFrom(node->ss.ss_currentRelation,
festate->filename,
+ false,
NIL,
festate->options);
}
***************
*** 993,999 **** file_acquire_sample_rows(Relation onerel, int elevel,
/*
* Create CopyState from FDW options.
*/
! cstate = BeginCopyFrom(onerel, filename, NIL, options);
/*
* Use per-tuple memory context to prevent leak of memory used to read
--- 995,1001 ----
/*
* Create CopyState from FDW options.
*/
! cstate = BeginCopyFrom(onerel, filename, false, NIL, options);
/*
* Use per-tuple memory context to prevent leak of memory used to read
*** a/doc/src/sgml/keywords.sgml
--- b/doc/src/sgml/keywords.sgml
***************
*** 3514,3519 ****
--- 3514,3526 ----
<entry>reserved</entry>
</row>
<row>
+ <entry><token>PROGRAM</token></entry>
+ <entry>non-reserved</entry>
+ <entry></entry>
+ <entry></entry>
+ <entry></entry>
+ </row>
+ <row>
<entry><token>PUBLIC</token></entry>
<entry></entry>
<entry>non-reserved</entry>
*** a/doc/src/sgml/ref/copy.sgml
--- b/doc/src/sgml/ref/copy.sgml
***************
*** 23,33 **** PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
COPY <replaceable class="parameter">table_name</replaceable> [ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ]
! FROM { '<replaceable class="parameter">filename</replaceable>' | STDIN }
[ [ WITH ] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ] | ( <replaceable class="parameter">query</replaceable> ) }
! TO { '<replaceable class="parameter">filename</replaceable>' | STDOUT }
[ [ WITH ] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
--- 23,33 ----
<refsynopsisdiv>
<synopsis>
COPY <replaceable class="parameter">table_name</replaceable> [ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ]
! FROM { '<replaceable class="parameter">filename</replaceable>' | PROGRAM '<replaceable class="parameter">command</replaceable>' | STDIN }
[ [ WITH ] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable class="parameter">column_name</replaceable> [, ...] ) ] | ( <replaceable class="parameter">query</replaceable> ) }
! TO { '<replaceable class="parameter">filename</replaceable>' | PROGRAM '<replaceable class="parameter">command</replaceable>' | STDOUT }
[ [ WITH ] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
***************
*** 70,77 **** COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable
<command>COPY</command> with a file name instructs the
<productname>PostgreSQL</productname> server to directly read from
or write to a file. The file must be accessible to the server and
! the name must be specified from the viewpoint of the server. When
! <literal>STDIN</literal> or <literal>STDOUT</literal> is
specified, data is transmitted via the connection between the
client and the server.
</para>
--- 70,82 ----
<command>COPY</command> with a file name instructs the
<productname>PostgreSQL</productname> server to directly read from
or write to a file. The file must be accessible to the server and
! the name must be specified from the viewpoint of the server.
! <command>COPY</command> with a command instructs the
! <productname>PostgreSQL</productname> server to directly execute
! a command that input comes from or that output goes to.
! The command must be executable by the server and specified from
! the viewpoint of the server.
! When <literal>STDIN</literal> or <literal>STDOUT</literal> is
specified, data is transmitted via the connection between the
client and the server.
</para>
***************
*** 125,130 **** COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable
--- 130,147 ----
</varlistentry>
<varlistentry>
+ <term><replaceable class="parameter">command</replaceable></term>
+ <listitem>
+ <para>
+ The command that input comes from or that output goes to.
+ A command for COPY FROM, which input comes from, must write its output
+ to standard output, while that for COPY TO, which output goes to, must
+ read its input from standard input.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>STDIN</literal></term>
<listitem>
<para>
***************
*** 373,378 **** COPY <replaceable class="parameter">count</replaceable>
--- 390,414 ----
</para>
<para>
+ Commands specified in a <command>COPY</command> command are executed
+ directly by the server, not by the client application. Therefore,
+ they must be executable at the database server machine, not the client.
+ They must be executable by the <productname>PostgreSQL</productname>
+ user, not the client. <command>COPY</command> specifying a command
+ is only allowed to database superusers.
+ It is recommended that the command specified in
+ <command>COPY</command> always be set using an absolute paths.
+ However, a relative path can be specified, which will be interpreted
+ relative to the working directory of the server process.
+ </para>
+
+ <para>
+ Commands specified in a <command>COPY</command> command might not be
+ executed in operating systems that implement access control for
+ their resources such as the SELinux operating system.
+ </para>
+
+ <para>
<command>COPY FROM</command> will invoke any triggers and check
constraints on the destination table. However, it will not invoke rules.
</para>
*** a/doc/src/sgml/ref/psql-ref.sgml
--- b/doc/src/sgml/ref/psql-ref.sgml
***************
*** 829,835 **** testdb=>
<varlistentry id="APP-PSQL-meta-commands-copy">
<term><literal>\copy { <replaceable class="parameter">table</replaceable> [ ( <replaceable class="parameter">column_list</replaceable> ) ] | ( <replaceable class="parameter">query</replaceable> ) }
{ <literal>from</literal> | <literal>to</literal> }
! { <replaceable class="parameter">filename</replaceable> | stdin | stdout | pstdin | pstdout }
[ [ with ] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]</literal></term>
<listitem>
--- 829,835 ----
<varlistentry id="APP-PSQL-meta-commands-copy">
<term><literal>\copy { <replaceable class="parameter">table</replaceable> [ ( <replaceable class="parameter">column_list</replaceable> ) ] | ( <replaceable class="parameter">query</replaceable> ) }
{ <literal>from</literal> | <literal>to</literal> }
! { <replaceable class="parameter">filename</replaceable> | program <replaceable class="parameter">command</replaceable> | stdin | stdout | pstdin | pstdout }
[ [ with ] ( <replaceable class="parameter">option</replaceable> [, ...] ) ]</literal></term>
<listitem>
***************
*** 846,851 **** testdb=>
--- 846,863 ----
</para>
<para>
+ When <literal>program</> is specified,
+ <replaceable class="parameter">command</replaceable> is
+ directly executed by <application>psql</application>,
+ not the server, and the data from or to
+ <replaceable class="parameter">command</replaceable> is
+ routed between the server and the client.
+ This means that the exectution privileges are those of
+ the local user, not the server, and no SQL superuser
+ privileges are required.
+ </para>
+
+ <para>
The syntax of the command is similar to that of the
<acronym>SQL</acronym> <xref linkend="sql-copy">
command, and
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 107,112 **** typedef struct CopyStateData
--- 107,113 ----
QueryDesc *queryDesc; /* executable query to copy from */
List *attnumlist; /* integer list of attnums to copy */
char *filename; /* filename, or NULL for STDIN/STDOUT */
+ bool is_program; /* do I execute popen/pclose? */
bool binary; /* binary format? */
bool oids; /* include OIDs? */
bool csv_mode; /* Comma Separated Value format? */
***************
*** 276,282 **** static CopyState BeginCopy(bool is_from, Relation rel, Node *raw_query,
const char *queryString, List *attnamelist, List *options);
static void EndCopy(CopyState cstate);
static CopyState BeginCopyTo(Relation rel, Node *query, const char *queryString,
! const char *filename, List *attnamelist, List *options);
static void EndCopyTo(CopyState cstate);
static uint64 DoCopyTo(CopyState cstate);
static uint64 CopyTo(CopyState cstate);
--- 277,284 ----
const char *queryString, List *attnamelist, List *options);
static void EndCopy(CopyState cstate);
static CopyState BeginCopyTo(Relation rel, Node *query, const char *queryString,
! const char *filename, bool is_program,
! List *attnamelist, List *options);
static void EndCopyTo(CopyState cstate);
static uint64 DoCopyTo(CopyState cstate);
static uint64 CopyTo(CopyState cstate);
***************
*** 807,820 **** DoCopy(const CopyStmt *stmt, const char *queryString)
if (XactReadOnly && rel->rd_backend != MyBackendId)
PreventCommandIfReadOnly("COPY FROM");
! cstate = BeginCopyFrom(rel, stmt->filename,
stmt->attlist, stmt->options);
processed = CopyFrom(cstate); /* copy from file to database */
EndCopyFrom(cstate);
}
else
{
! cstate = BeginCopyTo(rel, stmt->query, queryString, stmt->filename,
stmt->attlist, stmt->options);
processed = DoCopyTo(cstate); /* copy from database to file */
EndCopyTo(cstate);
--- 809,823 ----
if (XactReadOnly && rel->rd_backend != MyBackendId)
PreventCommandIfReadOnly("COPY FROM");
! cstate = BeginCopyFrom(rel, stmt->filename, stmt->is_program,
stmt->attlist, stmt->options);
processed = CopyFrom(cstate); /* copy from file to database */
EndCopyFrom(cstate);
}
else
{
! cstate = BeginCopyTo(rel, stmt->query, queryString,
! stmt->filename, stmt->is_program,
stmt->attlist, stmt->options);
processed = DoCopyTo(cstate); /* copy from database to file */
EndCopyTo(cstate);
***************
*** 1382,1392 **** BeginCopy(bool is_from,
static void
EndCopy(CopyState cstate)
{
! if (cstate->filename != NULL && FreeFile(cstate->copy_file))
! ereport(ERROR,
! (errcode_for_file_access(),
! errmsg("could not close file \"%s\": %m",
! cstate->filename)));
MemoryContextDelete(cstate->copycontext);
pfree(cstate);
--- 1385,1405 ----
static void
EndCopy(CopyState cstate)
{
! if (cstate->is_program)
! {
! if (ClosePipeStream(cstate->copy_file) == -1)
! ereport(ERROR,
! (errmsg("could not execute command \"%s\"",
! cstate->filename)));
! }
! else
! {
! if (cstate->filename != NULL && FreeFile(cstate->copy_file))
! ereport(ERROR,
! (errcode_for_file_access(),
! errmsg("could not close file \"%s\": %m",
! cstate->filename)));
! }
MemoryContextDelete(cstate->copycontext);
pfree(cstate);
***************
*** 1400,1405 **** BeginCopyTo(Relation rel,
--- 1413,1419 ----
Node *query,
const char *queryString,
const char *filename,
+ bool is_program,
List *attnamelist,
List *options)
{
***************
*** 1436,1441 **** BeginCopyTo(Relation rel,
--- 1450,1461 ----
cstate = BeginCopy(false, rel, query, queryString, attnamelist, options);
oldcontext = MemoryContextSwitchTo(cstate->copycontext);
+ cstate->is_program = is_program;
+ if (pipe && cstate->is_program)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PROGRAM is not supported to stdout or from stdin")));
+
if (pipe)
{
if (whereToSendOutput != DestRemote)
***************
*** 1443,1476 **** BeginCopyTo(Relation rel,
}
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")));
! cstate->filename = pstrdup(filename);
! oumask = umask(S_IWGRP | S_IWOTH);
! 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))
! ereport(ERROR,
! (errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is a directory", cstate->filename)));
}
MemoryContextSwitchTo(oldcontext);
--- 1463,1507 ----
}
else
{
! cstate->filename = pstrdup(filename);
! if (cstate->is_program)
! {
! cstate->copy_file = OpenPipeStream(cstate->filename, PG_BINARY_W);
! if (cstate->copy_file == NULL)
! ereport(ERROR,
! (errmsg("could not execute command \"%s\": %m",
! cstate->filename)));
! }
! 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(S_IWGRP | S_IWOTH);
! 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))
! ereport(ERROR,
! (errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is a directory", cstate->filename)));
! }
}
MemoryContextSwitchTo(oldcontext);
***************
*** 2270,2275 **** CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
--- 2301,2307 ----
CopyState
BeginCopyFrom(Relation rel,
const char *filename,
+ bool is_program,
List *attnamelist,
List *options)
{
***************
*** 2367,2372 **** BeginCopyFrom(Relation rel,
--- 2399,2410 ----
cstate->volatile_defexprs = volatile_defexprs;
cstate->num_defaults = num_defaults;
+ cstate->is_program = is_program;
+ if (pipe && cstate->is_program)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PROGRAM is not supported to stdout or from stdin")));
+
if (pipe)
{
if (whereToSendOutput == DestRemote)
***************
*** 2376,2397 **** BeginCopyFrom(Relation rel,
}
else
{
- struct stat st;
-
cstate->filename = pstrdup(filename);
- 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))
! ereport(ERROR,
! (errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is a directory", cstate->filename)));
}
if (!cstate->binary)
--- 2414,2446 ----
}
else
{
cstate->filename = pstrdup(filename);
! if (cstate->is_program)
! {
! cstate->copy_file = OpenPipeStream(cstate->filename, PG_BINARY_R);
! if (cstate->copy_file == NULL)
! ereport(ERROR,
! (errmsg("could not execute command \"%s\": %m",
! cstate->filename)));
! }
! 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))
! ereport(ERROR,
! (errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is a directory", cstate->filename)));
! }
}
if (!cstate->binary)
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2702,2707 **** _copyCopyStmt(const CopyStmt *from)
--- 2702,2708 ----
COPY_NODE_FIELD(query);
COPY_NODE_FIELD(attlist);
COPY_SCALAR_FIELD(is_from);
+ COPY_SCALAR_FIELD(is_program);
COPY_STRING_FIELD(filename);
COPY_NODE_FIELD(options);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1089,1094 **** _equalCopyStmt(const CopyStmt *a, const CopyStmt *b)
--- 1089,1095 ----
COMPARE_NODE_FIELD(query);
COMPARE_NODE_FIELD(attlist);
COMPARE_SCALAR_FIELD(is_from);
+ COMPARE_SCALAR_FIELD(is_program);
COMPARE_STRING_FIELD(filename);
COMPARE_NODE_FIELD(options);
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 379,385 **** static void processCASbits(int cas_bits, int location, const char *constrType,
%type <boolean> opt_freeze opt_default opt_recheck
%type <defelt> opt_binary opt_oids copy_delimiter
! %type <boolean> copy_from
%type <ival> opt_column event cursor_options opt_hold opt_set_data
%type <objtype> reindex_type drop_type comment_type security_label_type
--- 379,385 ----
%type <boolean> opt_freeze opt_default opt_recheck
%type <defelt> opt_binary opt_oids copy_delimiter
! %type <boolean> copy_from copy_program
%type <ival> opt_column event cursor_options opt_hold opt_set_data
%type <objtype> reindex_type drop_type comment_type security_label_type
***************
*** 566,572 **** static void processCASbits(int cas_bits, int location, const char *constrType,
PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
! PRIOR PRIVILEGES PROCEDURAL PROCEDURE
QUOTE
--- 566,572 ----
PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
! PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
QUOTE
***************
*** 2299,2305 **** ClosePortalStmt:
--- 2299,2307 ----
*
* QUERY :
* COPY relname [(columnList)] FROM/TO file [WITH] [(options)]
+ * COPY relname [(columnList)] FROM/TO PROGRAM file [WITH] [(options)]
* COPY ( SELECT ... ) TO file [WITH] [(options)]
+ * COPY ( SELECT ... ) TO PROGRAM file [WITH] [(options)]
*
* In the preferred syntax the options are comma-separated
* and use generic identifiers instead of keywords. The pre-9.0
***************
*** 2314,2327 **** ClosePortalStmt:
*****************************************************************************/
CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids
! copy_from copy_file_name copy_delimiter opt_with copy_options
{
CopyStmt *n = makeNode(CopyStmt);
n->relation = $3;
n->query = NULL;
n->attlist = $4;
n->is_from = $6;
! n->filename = $7;
n->options = NIL;
/* Concatenate user-supplied flags */
--- 2316,2330 ----
*****************************************************************************/
CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids
! copy_from copy_program copy_file_name copy_delimiter opt_with copy_options
{
CopyStmt *n = makeNode(CopyStmt);
n->relation = $3;
n->query = NULL;
n->attlist = $4;
n->is_from = $6;
! n->is_program = $7;
! n->filename = $8;
n->options = NIL;
/* Concatenate user-supplied flags */
***************
*** 2329,2349 **** CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids
n->options = lappend(n->options, $2);
if ($5)
n->options = lappend(n->options, $5);
! if ($8)
! n->options = lappend(n->options, $8);
! if ($10)
! n->options = list_concat(n->options, $10);
$$ = (Node *)n;
}
! | COPY select_with_parens TO copy_file_name opt_with copy_options
{
CopyStmt *n = makeNode(CopyStmt);
n->relation = NULL;
n->query = $2;
n->attlist = NIL;
n->is_from = false;
! n->filename = $4;
! n->options = $6;
$$ = (Node *)n;
}
;
--- 2332,2353 ----
n->options = lappend(n->options, $2);
if ($5)
n->options = lappend(n->options, $5);
! if ($9)
! n->options = lappend(n->options, $9);
! if ($11)
! n->options = list_concat(n->options, $11);
$$ = (Node *)n;
}
! | COPY select_with_parens TO copy_program copy_file_name opt_with copy_options
{
CopyStmt *n = makeNode(CopyStmt);
n->relation = NULL;
n->query = $2;
n->attlist = NIL;
n->is_from = false;
! n->is_program = $4;
! n->filename = $5;
! n->options = $7;
$$ = (Node *)n;
}
;
***************
*** 2353,2358 **** copy_from:
--- 2357,2367 ----
| TO { $$ = FALSE; }
;
+ copy_program:
+ PROGRAM { $$ = TRUE; }
+ | /* EMPTY */ { $$ = FALSE; }
+ ;
+
/*
* 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
***************
*** 12595,12600 **** unreserved_keyword:
--- 12604,12610 ----
| PRIVILEGES
| PROCEDURAL
| PROCEDURE
+ | PROGRAM
| QUOTE
| RANGE
| READ
*** a/src/backend/storage/file/fd.c
--- b/src/backend/storage/file/fd.c
***************
*** 183,188 **** static uint64 temporary_files_size = 0;
--- 183,189 ----
typedef enum
{
AllocateDescFile,
+ AllocateDescPipe,
AllocateDescDir
} AllocateDescKind;
***************
*** 1539,1544 **** FreeDesc(AllocateDesc *desc)
--- 1540,1548 ----
case AllocateDescFile:
result = fclose(desc->desc.file);
break;
+ case AllocateDescPipe:
+ result = pclose_check(desc->desc.file);
+ break;
case AllocateDescDir:
result = closedir(desc->desc.dir);
break;
***************
*** 1583,1588 **** FreeFile(FILE *file)
--- 1587,1663 ----
return fclose(file);
}
+ FILE *
+ OpenPipeStream(const char *command, const char *mode)
+ {
+ FILE *file;
+
+ DO_DB(elog(LOG, "OpenPipeStream: Allocated %d (%s)",
+ numAllocatedDescs, command));
+
+ /*
+ * The test against MAX_ALLOCATED_DESCS prevents us from overflowing
+ * allocatedFiles[]; the test against max_safe_fds prevents AllocateFile
+ * from hogging every one of the available FDs, which'd lead to infinite
+ * looping.
+ */
+ if (numAllocatedDescs >= MAX_ALLOCATED_DESCS ||
+ numAllocatedDescs >= max_safe_fds - 1)
+ elog(ERROR, "exceeded MAX_ALLOCATED_DESCS while trying to execute command \"%s\"",
+ command);
+
+ TryAgain:
+ fflush(stdout);
+ fflush(stderr);
+ errno = 0;
+ if ((file = popen(command, mode)) != NULL)
+ {
+ AllocateDesc *desc = &allocatedDescs[numAllocatedDescs];
+
+ desc->kind = AllocateDescPipe;
+ desc->desc.file = file;
+ desc->create_subid = GetCurrentSubTransactionId();
+ numAllocatedDescs++;
+ return desc->desc.file;
+ }
+
+ if (errno == EMFILE || errno == ENFILE)
+ {
+ int save_errno = errno;
+
+ ereport(LOG,
+ (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
+ errmsg("out of file descriptors: %m; release and retry")));
+ errno = 0;
+ if (ReleaseLruFile())
+ goto TryAgain;
+ errno = save_errno;
+ }
+
+ return NULL;
+ }
+
+ int
+ ClosePipeStream(FILE *file)
+ {
+ int i;
+
+ DO_DB(elog(LOG, "ClosePipeStream: Allocated %d", numAllocatedDescs));
+
+ /* Remove file from list of allocated files, if it's present */
+ for (i = numAllocatedDescs; --i >= 0;)
+ {
+ AllocateDesc *desc = &allocatedDescs[i];
+
+ if (desc->kind == AllocateDescPipe && desc->desc.file == file)
+ return FreeDesc(desc);
+ }
+
+ /* Only get here if someone passes us a file not in allocatedDescs */
+ elog(WARNING, "file passed to ClosePipeStream was not obtained from OpenPipeStream");
+
+ return pclose_check(file);
+ }
/*
* Routines that want to use <dirent.h> (ie, DIR*) should use AllocateDir
*** a/src/bin/psql/copy.c
--- b/src/bin/psql/copy.c
***************
*** 52,57 **** struct copy_options
--- 52,58 ----
char *before_tofrom; /* COPY string before TO/FROM */
char *after_tofrom; /* COPY string after TO/FROM filename */
char *file; /* NULL = stdin/stdout */
+ bool program; /* do I execute popen/pclose? */
bool psql_inout; /* true = use psql stdin/stdout */
bool from; /* true = FROM, false = TO */
};
***************
*** 191,201 **** parse_slash_copy(const char *args)
else
goto error;
! token = strtokx(NULL, whitespace, NULL, "'",
! 0, false, true, pset.encoding);
if (!token)
goto error;
if (pg_strcasecmp(token, "stdin") == 0 ||
pg_strcasecmp(token, "stdout") == 0)
{
--- 192,216 ----
else
goto error;
! token = strtokx(NULL, whitespace, NULL, NULL,
! 0, false, false, pset.encoding);
if (!token)
goto error;
+ if (pg_strcasecmp(token, "program") == 0)
+ {
+ result->program = true;
+ token = strtokx(NULL, whitespace, NULL, "'",
+ 0, false, true, pset.encoding);
+ if (!token)
+ goto error;
+ }
+ else
+ {
+ result->program = false;
+ strip_quotes(token, '\'', 0, pset.encoding);
+ }
+
if (pg_strcasecmp(token, "stdin") == 0 ||
pg_strcasecmp(token, "stdout") == 0)
{
***************
*** 212,218 **** parse_slash_copy(const char *args)
{
result->psql_inout = false;
result->file = pg_strdup(token);
! expand_tilde(&result->file);
}
/* Collect the rest of the line (COPY options) */
--- 227,234 ----
{
result->psql_inout = false;
result->file = pg_strdup(token);
! if (!result->program)
! expand_tilde(&result->file);
}
/* Collect the rest of the line (COPY options) */
***************
*** 256,263 **** do_copy(const char *args)
if (!options)
return false;
! /* prepare to read or write the target file */
! if (options->file)
canonicalize_path(options->file);
if (options->from)
--- 272,285 ----
if (!options)
return false;
! if (options->file == NULL && options->program)
! {
! psql_error("program is not supported to stdout/pstdout or from stdin/pstdin\n");
! return false;
! }
!
! /* prepare to read or write the target file or command */
! if (options->file && !options->program)
canonicalize_path(options->file);
if (options->from)
***************
*** 265,271 **** do_copy(const char *args)
override_file = &pset.cur_cmd_source;
if (options->file)
! copystream = fopen(options->file, PG_BINARY_R);
else if (!options->psql_inout)
copystream = pset.cur_cmd_source;
else
--- 287,303 ----
override_file = &pset.cur_cmd_source;
if (options->file)
! {
! if (options->program)
! {
! fflush(stdout);
! fflush(stderr);
! errno = 0;
! copystream = popen(options->file, PG_BINARY_R);
! }
! else
! copystream = fopen(options->file, PG_BINARY_R);
! }
else if (!options->psql_inout)
copystream = pset.cur_cmd_source;
else
***************
*** 276,282 **** do_copy(const char *args)
override_file = &pset.queryFout;
if (options->file)
! copystream = fopen(options->file, PG_BINARY_W);
else if (!options->psql_inout)
copystream = pset.queryFout;
else
--- 308,324 ----
override_file = &pset.queryFout;
if (options->file)
! {
! if (options->program)
! {
! fflush(stdout);
! fflush(stderr);
! errno = 0;
! copystream = popen(options->file, PG_BINARY_W);
! }
! else
! copystream = fopen(options->file, PG_BINARY_W);
! }
else if (!options->psql_inout)
copystream = pset.queryFout;
else
***************
*** 285,305 **** do_copy(const char *args)
if (!copystream)
{
! psql_error("%s: %s\n",
! options->file, strerror(errno));
free_copy_options(options);
return false;
}
! /* make sure the specified file is not a directory */
! fstat(fileno(copystream), &st);
! if (S_ISDIR(st.st_mode))
{
! fclose(copystream);
! psql_error("%s: cannot copy from/to a directory\n",
! options->file);
! free_copy_options(options);
! return false;
}
/* build the command we will send to the backend */
--- 327,354 ----
if (!copystream)
{
! if (options->program)
! psql_error("could not execute command \"%s\": %s\n",
! options->file, strerror(errno));
! else
! psql_error("%s: %s\n",
! options->file, strerror(errno));
free_copy_options(options);
return false;
}
! if (!options->program)
{
! /* make sure the specified file is not a directory */
! fstat(fileno(copystream), &st);
! if (S_ISDIR(st.st_mode))
! {
! fclose(copystream);
! psql_error("%s: cannot copy from/to a directory\n",
! options->file);
! free_copy_options(options);
! return false;
! }
}
/* build the command we will send to the backend */
***************
*** 322,331 **** do_copy(const char *args)
if (options->file != NULL)
{
! if (fclose(copystream) != 0)
{
! psql_error("%s: %s\n", options->file, strerror(errno));
! success = false;
}
}
free_copy_options(options);
--- 371,392 ----
if (options->file != NULL)
{
! if (options->program)
! {
! if (pclose_check(copystream) == -1)
! {
! psql_error("could not execute command \"%s\"\n",
! options->file);
! success = false;
! }
! }
! else
{
! if (fclose(copystream) != 0)
! {
! psql_error("%s: %s\n", options->file, strerror(errno));
! success = false;
! }
}
}
free_copy_options(options);
*** a/src/bin/psql/stringutils.c
--- b/src/bin/psql/stringutils.c
***************
*** 13,21 ****
#include "stringutils.h"
- static void strip_quotes(char *source, char quote, char escape, int encoding);
-
-
/*
* Replacement for strtok() (a.k.a. poor man's flex)
*
--- 13,18 ----
***************
*** 239,245 **** strtokx(const char *s,
*
* Note that the source string is overwritten in-place.
*/
! static void
strip_quotes(char *source, char quote, char escape, int encoding)
{
char *src;
--- 236,242 ----
*
* Note that the source string is overwritten in-place.
*/
! void
strip_quotes(char *source, char quote, char escape, int encoding)
{
char *src;
*** a/src/bin/psql/stringutils.h
--- b/src/bin/psql/stringutils.h
***************
*** 19,24 **** extern char *strtokx(const char *s,
--- 19,26 ----
bool del_quotes,
int encoding);
+ extern void strip_quotes(char *source, char quote, char escape, int encoding);
+
extern char *quote_if_needed(const char *source, const char *entails_quote,
char quote, char escape, int encoding);
*** a/src/include/commands/copy.h
--- b/src/include/commands/copy.h
***************
*** 25,31 **** extern uint64 DoCopy(const CopyStmt *stmt, const char *queryString);
extern void ProcessCopyOptions(CopyState cstate, bool is_from, List *options);
extern CopyState BeginCopyFrom(Relation rel, const char *filename,
! List *attnamelist, List *options);
extern void EndCopyFrom(CopyState cstate);
extern bool NextCopyFrom(CopyState cstate, ExprContext *econtext,
Datum *values, bool *nulls, Oid *tupleOid);
--- 25,31 ----
extern void ProcessCopyOptions(CopyState cstate, bool is_from, List *options);
extern CopyState BeginCopyFrom(Relation rel, const char *filename,
! bool is_program, List *attnamelist, List *options);
extern void EndCopyFrom(CopyState cstate);
extern bool NextCopyFrom(CopyState cstate, ExprContext *econtext,
Datum *values, bool *nulls, Oid *tupleOid);
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1397,1402 **** typedef struct CopyStmt
--- 1397,1403 ----
List *attlist; /* List of column names (as Strings), or NIL
* for all columns */
bool is_from; /* TO or FROM */
+ bool is_program; /* do I execute popen/pclose? */
char *filename; /* filename, or NULL for STDIN/STDOUT */
List *options; /* List of DefElem nodes */
} CopyStmt;
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 292,297 **** PG_KEYWORD("prior", PRIOR, UNRESERVED_KEYWORD)
--- 292,298 ----
PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD)
PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD)
PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD)
PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD)
PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD)
PG_KEYWORD("read", READ, UNRESERVED_KEYWORD)
*** a/src/include/storage/fd.h
--- b/src/include/storage/fd.h
***************
*** 79,84 **** extern char *FilePathName(File file);
--- 79,88 ----
extern FILE *AllocateFile(const char *name, const char *mode);
extern int FreeFile(FILE *file);
+ /* Operations that allow use of pipe stream */
+ extern FILE *OpenPipeStream(const char *command, const char *mode);
+ extern int ClosePipeStream(FILE *file);
+
/* Operations to allow use of the <dirent.h> library routines */
extern DIR *AllocateDir(const char *dirname);
extern struct dirent *ReadDir(DIR *dir, const char *dirname);
*** a/src/interfaces/ecpg/preproc/ecpg.addons
--- b/src/interfaces/ecpg/preproc/ecpg.addons
***************
*** 192,206 **** ECPG: where_or_current_clauseWHERECURRENT_POFcursor_name block
char *cursor_marker = $4[0] == ':' ? mm_strdup("$0") : $4;
$$ = cat_str(2,mm_strdup("where current of"), cursor_marker);
}
! ECPG: CopyStmtCOPYopt_binaryqualified_nameopt_column_listopt_oidscopy_fromcopy_file_namecopy_delimiteropt_withcopy_options addon
! if (strcmp($6, "to") == 0 && strcmp($7, "stdin") == 0)
mmerror(PARSE_ERROR, ET_ERROR, "COPY TO STDIN is not possible");
! else if (strcmp($6, "from") == 0 && strcmp($7, "stdout") == 0)
mmerror(PARSE_ERROR, ET_ERROR, "COPY FROM STDOUT is not possible");
! else if (strcmp($6, "from") == 0 && strcmp($7, "stdin") == 0)
mmerror(PARSE_ERROR, ET_WARNING, "COPY FROM STDIN is not implemented");
! ECPG: CopyStmtCOPYselect_with_parensTOcopy_file_nameopt_withcopy_options addon
! if (strcmp($4, "stdin") == 0)
mmerror(PARSE_ERROR, ET_ERROR, "COPY TO STDIN is not possible");
ECPG: var_valueNumericOnly addon
if ($1[0] == '$')
--- 192,214 ----
char *cursor_marker = $4[0] == ':' ? mm_strdup("$0") : $4;
$$ = cat_str(2,mm_strdup("where current of"), cursor_marker);
}
! ECPG: CopyStmtCOPYopt_binaryqualified_nameopt_column_listopt_oidscopy_fromcopy_programcopy_file_namecopy_delimiteropt_withcopy_options addon
! if (strcmp($7, "program") == 0 && strcmp($8, "stdin") == 0)
! mmerror(PARSE_ERROR, ET_ERROR, "PROGRAM STDIN is not possible");
! else if (strcmp($7, "program") == 0 && strcmp($8, "stdout") == 0)
! mmerror(PARSE_ERROR, ET_ERROR, "PROGRAM STDOUT is not possible");
! else if (strcmp($6, "to") == 0 && strcmp($8, "stdin") == 0)
mmerror(PARSE_ERROR, ET_ERROR, "COPY TO STDIN is not possible");
! else if (strcmp($6, "from") == 0 && strcmp($8, "stdout") == 0)
mmerror(PARSE_ERROR, ET_ERROR, "COPY FROM STDOUT is not possible");
! else if (strcmp($6, "from") == 0 && strcmp($8, "stdin") == 0)
mmerror(PARSE_ERROR, ET_WARNING, "COPY FROM STDIN is not implemented");
! ECPG: CopyStmtCOPYselect_with_parensTOcopy_programcopy_file_nameopt_withcopy_options addon
! if (strcmp($4, "program") == 0 && strcmp($5, "stdin") == 0)
! mmerror(PARSE_ERROR, ET_ERROR, "PROGRAM STDIN is not possible");
! else if (strcmp($4, "program") == 0 && strcmp($5, "stdout") == 0)
! mmerror(PARSE_ERROR, ET_ERROR, "PROGRAM STDOUT is not possible");
! else if (strcmp($5, "stdin") == 0)
mmerror(PARSE_ERROR, ET_ERROR, "COPY TO STDIN is not possible");
ECPG: var_valueNumericOnly addon
if ($1[0] == '$')