*** 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,413 ----
     </para>
  
     <para>
+     Likewise, commands specified in a <command>COPY</command> command 
+     are executed directly by the server. Therefore, they must be
+     executable at the database server machine by the <productname>
+     PostgreSQL</productname> user. <command>COPY</command> specifying
+     a command is only allowed to database superusers.
+     It is also 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 also 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=&gt;
        <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>
*** 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 (cstate->is_program && pipe)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("PROGRAM is not supported to stdout or from stdin")));
+ 
  	if (pipe)
  	{
  		if (whereToSendOutput != DestRemote)
***************
*** 1443,1470 **** 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))
--- 1463,1502 ----
  	}
  	else
  	{
  		struct stat st;
  
  		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 */
! 
! 			/*
! 			 * 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))
***************
*** 2270,2275 **** CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
--- 2302,2308 ----
  CopyState
  BeginCopyFrom(Relation rel,
  			  const char *filename,
+ 			  bool	is_program,
  			  List *attnamelist,
  			  List *options)
  {
***************
*** 2367,2372 **** BeginCopyFrom(Relation rel,
--- 2400,2411 ----
  	cstate->volatile_defexprs = volatile_defexprs;
  	cstate->num_defaults = num_defaults;
  
+ 	cstate->is_program = is_program;
+ 	if (cstate->is_program && pipe)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("PROGRAM is not supported to stdout or from stdin")));
+ 
  	if (pipe)
  	{
  		if (whereToSendOutput == DestRemote)
***************
*** 2379,2391 **** BeginCopyFrom(Relation rel,
  		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))
--- 2418,2441 ----
  		struct stat st;
  
  		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
! 		{
! 			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))
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2703,2708 **** _copyCopyStmt(const CopyStmt *from)
--- 2703,2709 ----
  	COPY_NODE_FIELD(attlist);
  	COPY_SCALAR_FIELD(is_from);
  	COPY_STRING_FIELD(filename);
+ 	COPY_SCALAR_FIELD(is_program);
  	COPY_NODE_FIELD(options);
  
  	return newnode;
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1090,1095 **** _equalCopyStmt(const CopyStmt *a, const CopyStmt *b)
--- 1090,1096 ----
  	COMPARE_NODE_FIELD(attlist);
  	COMPARE_SCALAR_FIELD(is_from);
  	COMPARE_STRING_FIELD(filename);
+ 	COMPARE_SCALAR_FIELD(is_program);
  	COMPARE_NODE_FIELD(options);
  
  	return true;
*** 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
  
***************
*** 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 */
--- 2314,2328 ----
   *****************************************************************************/
  
  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;
  				}
  		;
--- 2330,2351 ----
  						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:
--- 2355,2365 ----
  			| 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:
--- 12602,12608 ----
  			| 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,218 ----
  	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 (!token)
+ 			goto error;
+ 	}
+ 
  	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) */
--- 229,236 ----
  	{
  		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)
--- 274,288 ----
  	if (!options)
  		return false;
  
+ 	/* program is not supported to stdout or from stdin */
+ 	if (options->program && options->file == NULL)
+ 	{
+ 		psql_error("program is not supported to stdout/pstdout or from stdin/pstdin\n");
+ 		return false;
+ 	}
+ 
  	/* prepare to read or write the target file */
! 	if (!options->program && options->file)
  		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
--- 290,306 ----
  		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
--- 311,327 ----
  		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,292 **** do_copy(const char *args)
  
  	if (!copystream)
  	{
! 		psql_error("%s: %s\n",
! 				   options->file, strerror(errno));
  		free_copy_options(options);
  		return false;
  	}
--- 330,341 ----
  
  	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;
  	}
***************
*** 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\": %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 ----
  								 * for all columns */
  	bool		is_from;		/* TO or FROM */
  	char	   *filename;		/* filename, or NULL for STDIN/STDOUT */
+ 	bool		is_program;		/* do I execute popen/pclose? */
  	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,205 **** 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
--- 192,205 ----
  		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($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_programcopy_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
