copy_options.patch
application/octet-stream
Filename: copy_options.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: unified
| File | + | − |
|---|---|---|
| contrib/file_fdw/file_fdw.c | 1 | 1 |
| src/backend/commands/copy.c | 130 | 25 |
| src/backend/parser/gram.y | 12 | 2 |
| src/backend/storage/file/fd.c | 76 | 0 |
| src/bin/psql/copy.c | 146 | 10 |
| src/include/commands/copy.h | 2 | 1 |
| src/include/parser/kwlist.h | 2 | 0 |
| src/include/storage/fd.h | 4 | 0 |
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 81fc4e2..af1959b 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -272,7 +272,7 @@ file_fdw_validator(PG_FUNCTION_ARGS)
/*
* Now apply the core COPY code's validation logic for more checks.
*/
- ProcessCopyOptions(NULL, true, other_options);
+ ProcessCopyOptions(NULL, true, false, other_options);
/*
* Filename option is required for file_fdw foreign tables.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 0567ab0..252fc6e 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -100,6 +100,8 @@ typedef struct CopyStateData
EolType eol_type; /* EOL type of input */
int file_encoding; /* file or remote side's character encoding */
bool need_transcoding; /* file encoding diff from server? */
+ StringInfo cmdbuf; /* used to hold the whole command string
+ * if either preproc or postproc is set */
bool encoding_embeds_ascii; /* ASCII can be non-first byte? */
/* parameters from the COPY command */
@@ -124,6 +126,8 @@ typedef struct CopyStateData
bool *force_notnull_flags; /* per-column CSV FNN flags */
bool convert_selectively; /* do selective binary conversion? */
List *convert_select; /* list of column names (can be NIL) */
+ char *preproc; /* preprocessor for COPY FROM */
+ char *postproc; /* postprocessor for COPY TO */
bool *convert_select_flags; /* per-column CSV/TEXT CS flags */
/* these are just for error messages, see CopyFromErrorCallback */
@@ -272,8 +276,9 @@ static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";
/* non-export function prototypes */
-static CopyState BeginCopy(bool is_from, Relation rel, Node *raw_query,
- const char *queryString, List *attnamelist, List *options);
+static CopyState BeginCopy(bool is_from, Relation rel,
+ Node *raw_query, const char *queryString,
+ bool is_pipe, 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);
@@ -851,6 +856,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
void
ProcessCopyOptions(CopyState cstate,
bool is_from,
+ bool is_pipe,
List *options)
{
bool format_specified = false;
@@ -998,6 +1004,22 @@ ProcessCopyOptions(CopyState cstate,
errmsg("argument to option \"%s\" must be a valid encoding name",
defel->defname)));
}
+ else if (strcmp(defel->defname, "preprocessor") == 0)
+ {
+ if (cstate->preproc)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ cstate->preproc = defGetString(defel);
+ }
+ else if (strcmp(defel->defname, "postprocessor") == 0)
+ {
+ if (cstate->postproc)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ cstate->postproc = defGetString(defel);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -1136,6 +1158,26 @@ ProcessCopyOptions(CopyState cstate,
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("CSV quote character must not appear in the NULL specification")));
+
+ /* Check preproc */
+ if (cstate->preproc && !is_from)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot specify PREPROCESSOR in COPY TO")));
+ if (cstate->preproc && is_pipe)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot specify PREPROCESSOR in COPY FROM STDIN")));
+
+ /* Check postproc */
+ if (cstate->postproc && is_from)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot specify POSTPROCESSOR in COPY FROM")));
+ if (cstate->postproc && is_pipe)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot specify POSTPROCESSOR in COPY TO STDOUT")));
}
/*
@@ -1158,6 +1200,7 @@ BeginCopy(bool is_from,
Relation rel,
Node *raw_query,
const char *queryString,
+ bool is_pipe,
List *attnamelist,
List *options)
{
@@ -1182,7 +1225,7 @@ BeginCopy(bool is_from,
oldcontext = MemoryContextSwitchTo(cstate->copycontext);
/* Extract options from the statement node tree */
- ProcessCopyOptions(cstate, is_from, options);
+ ProcessCopyOptions(cstate, is_from, is_pipe, options);
/* Process the source/target relation or query */
if (rel)
@@ -1382,11 +1425,21 @@ 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)));
+ if (cstate->preproc != NULL || cstate->postproc != NULL)
+ {
+ if (ClosePipeStream(cstate->copy_file) == -1)
+ ereport(ERROR,
+ (errmsg("could not execute command \"%s\"",
+ cstate->cmdbuf->data)));
+ }
+ 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);
@@ -1433,7 +1486,8 @@ BeginCopyTo(Relation rel,
RelationGetRelationName(rel))));
}
- cstate = BeginCopy(false, rel, query, queryString, attnamelist, options);
+ cstate = BeginCopy(false, rel, query, queryString,
+ pipe, attnamelist, options);
oldcontext = MemoryContextSwitchTo(cstate->copycontext);
if (pipe)
@@ -1443,7 +1497,6 @@ BeginCopyTo(Relation rel,
}
else
{
- mode_t oumask; /* Pre-existing umask value */
struct stat st;
/*
@@ -1456,15 +1509,42 @@ BeginCopyTo(Relation rel,
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)));
+ if (cstate->postproc != NULL)
+ {
+ char my_exec_path[MAXPGPATH];
+
+ if (find_my_exec(cstate->postproc, my_exec_path) < 0)
+ elog(ERROR, "could not locate postprocessor \"%s\"",
+ cstate->postproc);
+
+ cstate->cmdbuf = makeStringInfo();
+ appendStringInfoString(cstate->cmdbuf, my_exec_path);
+ appendStringInfoChar(cstate->cmdbuf, ' ');
+ appendStringInfoString(cstate->cmdbuf, cstate->filename);
+
+ cstate->copy_file = OpenPipeStream(cstate->cmdbuf->data,
+ PG_BINARY_W);
+
+ if (cstate->copy_file == NULL)
+ ereport(ERROR,
+ (errmsg("could not execute command \"%s\": %m",
+ cstate->cmdbuf->data)));
+ }
+ else
+ {
+ mode_t oumask; /* Pre-existing umask value */
+
+ 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))
@@ -2288,7 +2368,7 @@ BeginCopyFrom(Relation rel,
MemoryContext oldcontext;
bool volatile_defexprs;
- cstate = BeginCopy(true, rel, NULL, NULL, attnamelist, options);
+ cstate = BeginCopy(true, rel, NULL, NULL, pipe, attnamelist, options);
oldcontext = MemoryContextSwitchTo(cstate->copycontext);
/* Initialize state variables */
@@ -2379,13 +2459,38 @@ 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)));
+ if (cstate->preproc != NULL)
+ {
+ char my_exec_path[MAXPGPATH];
+
+ if (find_my_exec(cstate->preproc, my_exec_path) < 0)
+ elog(ERROR, "could not locate preprocessor \"%s\"",
+ cstate->preproc);
+
+ cstate->cmdbuf = makeStringInfo();
+ appendStringInfoString(cstate->cmdbuf, my_exec_path);
+ appendStringInfoChar(cstate->cmdbuf, ' ');
+ appendStringInfoString(cstate->cmdbuf, cstate->filename);
+
+ cstate->copy_file = OpenPipeStream(cstate->cmdbuf->data,
+ PG_BINARY_R);
+
+ if (cstate->copy_file == NULL)
+ ereport(ERROR,
+ (errmsg("could not execute command \"%s\": %m",
+ cstate->cmdbuf->data)));
+ }
+ 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))
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5894cb0..09796f4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -547,8 +547,8 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
ORDER OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
- PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
- PRIOR PRIVILEGES PROCEDURAL PROCEDURE
+ POSTPROCESSOR PRECEDING PRECISION PREPROCESSOR PRESERVE PREPARE PREPARED
+ PRIMARY PRIOR PRIVILEGES PROCEDURAL PROCEDURE
QUOTE
@@ -2360,6 +2360,14 @@ copy_opt_item:
{
$$ = makeDefElem("force_not_null", (Node *)$4);
}
+ | PREPROCESSOR Sconst
+ {
+ $$ = makeDefElem("preprocessor", (Node *)makeString($2));
+ }
+ | POSTPROCESSOR Sconst
+ {
+ $$ = makeDefElem("postprocessor", (Node *)makeString($2));
+ }
| ENCODING Sconst
{
$$ = makeDefElem("encoding", (Node *)makeString($2));
@@ -12520,9 +12528,11 @@ unreserved_keyword:
| PASSING
| PASSWORD
| PLANS
+ | POSTPROCESSOR
| PRECEDING
| PREPARE
| PREPARED
+ | PREPROCESSOR
| PRESERVE
| PRIOR
| PRIVILEGES
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index fed25fd..eaa594a 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -182,6 +182,7 @@ static uint64 temporary_files_size = 0;
typedef enum
{
AllocateDescFile,
+ AllocateDescPipe,
AllocateDescDir
} AllocateDescKind;
@@ -1542,6 +1543,56 @@ TryAgain:
return NULL;
}
+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;
+}
+
/*
* Free an AllocateDesc of either type.
*
@@ -1558,6 +1609,9 @@ FreeDesc(AllocateDesc *desc)
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;
@@ -1602,6 +1656,28 @@ FreeFile(FILE *file)
return fclose(file);
}
+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
diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c
index 22fcc59..38a6c52 100644
--- a/src/bin/psql/copy.c
+++ b/src/bin/psql/copy.c
@@ -54,6 +54,9 @@ struct copy_options
char *file; /* NULL = stdin/stdout */
bool psql_inout; /* true = use psql stdin/stdout */
bool from; /* true = FROM, false = TO */
+ char *proc; /* preprocessor/postprocessor, if any */
+ char *command; /* used to hold the whole command string
+ * if preprocessor/postprocessor is set */
};
@@ -65,6 +68,8 @@ free_copy_options(struct copy_options * ptr)
free(ptr->before_tofrom);
free(ptr->after_tofrom);
free(ptr->file);
+ free(ptr->proc);
+ free(ptr->command);
free(ptr);
}
@@ -88,6 +93,8 @@ parse_slash_copy(const char *args)
{
struct copy_options *result;
char *token;
+ char *prev_token;
+ char *rest;
const char *whitespace = " \t\n\r";
char nonstd_backslash = standard_strings() ? 0 : '\\';
@@ -216,10 +223,76 @@ parse_slash_copy(const char *args)
}
/* Collect the rest of the line (COPY options) */
- token = strtokx(NULL, "", NULL, NULL,
+ rest = strtokx(NULL, "", NULL, NULL,
+ 0, false, false, pset.encoding);
+ if (!rest ||
+ (!strstr(rest, "preprocessor") && !strstr(rest, "postprocessor")))
+ {
+ if (rest)
+ result->after_tofrom = pg_strdup(rest);
+ return result;
+ }
+ result->after_tofrom = pg_strdup("");
+ token = strtokx(rest, whitespace, ",()", "\"'",
0, false, false, pset.encoding);
- if (token)
- result->after_tofrom = pg_strdup(token);
+ while (token)
+ {
+ if (pg_strcasecmp(token, "preprocessor") == 0 ||
+ pg_strcasecmp(token, "postprocessor") == 0)
+ {
+ if ( prev_token[0] != ',' && prev_token[0] != '(')
+ goto error;
+
+ if (result->proc != NULL)
+ goto error;
+ if (result->file == NULL)
+ goto error;
+ if (!result->from && pg_strcasecmp(token, "preprocessor") == 0)
+ goto error;
+ if (result->from && pg_strcasecmp(token, "postprocessor") == 0)
+ goto error;
+
+ token = strtokx(NULL, whitespace, NULL, "'",
+ 0, false, true, pset.encoding);
+ if (!token)
+ goto error;
+ result->proc = pg_strdup(token);
+
+ token = strtokx(NULL, whitespace, ",()", "\"'",
+ 0, false, false, pset.encoding);
+ if (!token)
+ goto error;
+ if (token[0] != ',' && token[0] != ')')
+ goto error;
+ if (token[0] == ')')
+ {
+ int len;
+
+ token = strtokx(NULL, whitespace, ",()", "\"'",
+ 0, false, false, pset.encoding);
+ if (token)
+ goto error;
+ if (prev_token[0] == ',')
+ {
+ len = strlen(result->after_tofrom);
+ result->after_tofrom[len - 1] = ')';
+ }
+ if (prev_token[0] == '(')
+ result->after_tofrom = pg_strdup("");
+ break;
+ }
+
+ token = strtokx(NULL, whitespace, NULL, "\"'",
+ 0, false, false, pset.encoding);
+ if (!token)
+ goto error;
+ }
+ xstrcat(&result->after_tofrom, " ");
+ xstrcat(&result->after_tofrom, token);
+ prev_token = token;
+ token = strtokx(NULL, whitespace, ".,()", "\"'",
+ 0, false, false, pset.encoding);
+ }
return result;
@@ -260,12 +333,48 @@ do_copy(const char *args)
if (options->file)
canonicalize_path(options->file);
+ if (options->proc)
+ {
+ PQExpBufferData cmdbuf;
+ char full_path[MAXPGPATH];
+ int ret;
+
+ ret = find_my_exec(options->proc, full_path);
+ if (ret)
+ {
+ if (options->from)
+ psql_error("could not locate preprocessor: %s\n",
+ options->proc);
+ else
+ psql_error("could not locate postprocessor: %s\n",
+ options->proc);
+ free_copy_options(options);
+ return false;
+ }
+ initPQExpBuffer(&cmdbuf);
+ appendPQExpBufferStr(&cmdbuf, full_path);
+ appendPQExpBuffer(&cmdbuf, " ");
+ appendPQExpBufferStr(&cmdbuf, options->file);
+ options->command = pg_strdup(cmdbuf.data);
+ termPQExpBuffer(&cmdbuf);
+ }
+
if (options->from)
{
override_file = &pset.cur_cmd_source;
if (options->file)
- copystream = fopen(options->file, PG_BINARY_R);
+ {
+ if (!options->proc)
+ copystream = fopen(options->file, PG_BINARY_R);
+ else
+ {
+ fflush(stdout);
+ fflush(stderr);
+ errno = 0;
+ copystream = popen(options->command, PG_BINARY_R);
+ }
+ }
else if (!options->psql_inout)
copystream = pset.cur_cmd_source;
else
@@ -276,7 +385,17 @@ do_copy(const char *args)
override_file = &pset.queryFout;
if (options->file)
- copystream = fopen(options->file, PG_BINARY_W);
+ {
+ if (!options->proc)
+ copystream = fopen(options->file, PG_BINARY_W);
+ else
+ {
+ fflush(stdout);
+ fflush(stderr);
+ errno = 0;
+ copystream = popen(options->command, PG_BINARY_W);
+ }
+ }
else if (!options->psql_inout)
copystream = pset.queryFout;
else
@@ -285,8 +404,12 @@ do_copy(const char *args)
if (!copystream)
{
- psql_error("%s: %s\n",
- options->file, strerror(errno));
+ if (!options->proc)
+ psql_error("%s: %s\n",
+ options->file, strerror(errno));
+ else
+ psql_error("could not execute command \"%s\": %s\n",
+ options->command, strerror(errno));
free_copy_options(options);
return false;
}
@@ -322,10 +445,23 @@ do_copy(const char *args)
if (options->file != NULL)
{
- if (fclose(copystream) != 0)
+ if (!options->proc)
{
- psql_error("%s: %s\n", options->file, strerror(errno));
- success = false;
+ if (fclose(copystream) != 0)
+ {
+ psql_error("%s: %s\n",
+ options->file, strerror(errno));
+ success = false;
+ }
+ }
+ else
+ {
+ if (pclose_check(copystream) == -1)
+ {
+ psql_error("could not execute command \"%s\"\n",
+ options->command);
+ success = false;
+ }
}
}
free_copy_options(options);
diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h
index 8680ac3..b6ab6a1 100644
--- a/src/include/commands/copy.h
+++ b/src/include/commands/copy.h
@@ -23,7 +23,8 @@ typedef struct CopyStateData *CopyState;
extern uint64 DoCopy(const CopyStmt *stmt, const char *queryString);
-extern void ProcessCopyOptions(CopyState cstate, bool is_from, List *options);
+extern void ProcessCopyOptions(CopyState cstate, bool is_from,
+ bool is_pipe, List *options);
extern CopyState BeginCopyFrom(Relation rel, const char *filename,
List *attnamelist, List *options);
extern void EndCopyFrom(CopyState cstate);
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index af60dac..f0cf0a4 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -282,10 +282,12 @@ PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD)
+PG_KEYWORD("postprocessor", POSTPROCESSOR, UNRESERVED_KEYWORD)
PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD)
PG_KEYWORD("precision", PRECISION, COL_NAME_KEYWORD)
PG_KEYWORD("prepare", PREPARE, UNRESERVED_KEYWORD)
PG_KEYWORD("prepared", PREPARED, UNRESERVED_KEYWORD)
+PG_KEYWORD("preprocessor", PREPROCESSOR, UNRESERVED_KEYWORD)
PG_KEYWORD("preserve", PRESERVE, UNRESERVED_KEYWORD)
PG_KEYWORD("primary", PRIMARY, RESERVED_KEYWORD)
PG_KEYWORD("prior", PRIOR, UNRESERVED_KEYWORD)
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index bad9f10..69d5388 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -80,6 +80,10 @@ extern char *FilePathName(File file);
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);