*** 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 ---- reserved + PROGRAM + non-reserved + + + + + PUBLIC non-reserved *** a/doc/src/sgml/ref/copy.sgml --- b/doc/src/sgml/ref/copy.sgml *************** *** 23,33 **** PostgreSQL documentation COPY table_name [ ( column_name [, ...] ) ] ! FROM { 'filename' | STDIN } [ [ WITH ] ( option [, ...] ) ] COPY { table_name [ ( column_name [, ...] ) ] | ( query ) } ! TO { 'filename' | STDOUT } [ [ WITH ] ( option [, ...] ) ] where option can be one of: --- 23,33 ---- COPY table_name [ ( column_name [, ...] ) ] ! FROM { 'filename' | PROGRAM 'command' | STDIN } [ [ WITH ] ( option [, ...] ) ] COPY { table_name [ ( column_name [, ...] ) ] | ( query ) } ! TO { 'filename' | PROGRAM 'command' | STDOUT } [ [ WITH ] ( option [, ...] ) ] where option can be one of: *************** *** 70,77 **** COPY { table_name [ ( COPY with a file name instructs the PostgreSQL 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 ! STDIN or STDOUT is specified, data is transmitted via the connection between the client and the server. --- 70,82 ---- COPY with a file name instructs the PostgreSQL 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. ! COPY with a command instructs the ! PostgreSQL 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 STDIN or STDOUT is specified, data is transmitted via the connection between the client and the server. *************** *** 125,130 **** COPY { table_name [ ( + command + + + 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. + + + + + STDIN *************** *** 373,378 **** COPY count --- 390,414 ---- + Commands specified in a COPY 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 PostgreSQL + user, not the client. COPY specifying a command + is only allowed to database superusers. + It is recommended that the command specified in + COPY 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. + + + + Commands specified in a COPY command might not be + executed in operating systems that implement access control for + their resources such as the SELinux operating system. + + + COPY FROM will invoke any triggers and check constraints on the destination table. However, it will not invoke rules. *** a/doc/src/sgml/ref/psql-ref.sgml --- b/doc/src/sgml/ref/psql-ref.sgml *************** *** 829,835 **** testdb=> \copy { table [ ( column_list ) ] | ( query ) } { from | to } ! { filename | stdin | stdout | pstdin | pstdout } [ [ with ] ( option [, ...] ) ] --- 829,835 ---- \copy { table [ ( column_list ) ] | ( query ) } { from | to } ! { filename | program command | stdin | stdout | pstdin | pstdout } [ [ with ] ( option [, ...] ) ] *************** *** 846,851 **** testdb=> --- 846,863 ---- + When program is specified, + command is + directly executed by psql, + not the server, and the data from or to + command 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. + + + The syntax of the command is similar to that of the SQL 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 opt_freeze opt_default opt_recheck %type opt_binary opt_oids copy_delimiter ! %type copy_from %type opt_column event cursor_options opt_hold opt_set_data %type reindex_type drop_type comment_type security_label_type --- 379,385 ---- %type opt_freeze opt_default opt_recheck %type opt_binary opt_oids copy_delimiter ! %type copy_from copy_program %type opt_column event cursor_options opt_hold opt_set_data %type 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 (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 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] == '$')