mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-01-06 15:24:56 +08:00
Remove a couple hundred lines of ugly and tedious-to-maintain code by not
trying to parse COPY options exactly in psql's \copy support. Instead, just send the options as-is and let the backend sort it out. Emmanuel Cecchet
This commit is contained in:
parent
9dcc9c6b3b
commit
94f238cf1a
@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* Copyright (c) 2000-2009, PostgreSQL Global Development Group
|
* Copyright (c) 2000-2009, PostgreSQL Global Development Group
|
||||||
*
|
*
|
||||||
* $PostgreSQL: pgsql/src/bin/psql/copy.c,v 1.82 2009/08/07 20:16:11 tgl Exp $
|
* $PostgreSQL: pgsql/src/bin/psql/copy.c,v 1.83 2009/09/19 21:51:21 tgl Exp $
|
||||||
*/
|
*/
|
||||||
#include "postgres_fe.h"
|
#include "postgres_fe.h"
|
||||||
#include "copy.h"
|
#include "copy.h"
|
||||||
@ -32,43 +32,28 @@
|
|||||||
* -- parses \copy command line
|
* -- parses \copy command line
|
||||||
*
|
*
|
||||||
* The documented syntax is:
|
* The documented syntax is:
|
||||||
* \copy tablename [(columnlist)] from|to filename
|
* \copy tablename [(columnlist)] from|to filename [options]
|
||||||
* [ with ] [ binary ] [ oids ] [ delimiter [as] char ] [ null [as] string ]
|
* \copy ( select stmt ) to filename [options]
|
||||||
* [ csv [ header ] [ quote [ AS ] string ] escape [as] string
|
|
||||||
* [ force not null column [, ...] | force quote column [, ...] | * ] ]
|
|
||||||
*
|
*
|
||||||
* \copy ( select stmt ) to filename
|
* An undocumented fact is that you can still write BINARY before the
|
||||||
* [ with ] [ binary ] [ delimiter [as] char ] [ null [as] string ]
|
* tablename; this is a hangover from the pre-7.3 syntax. The options
|
||||||
* [ csv [ header ] [ quote [ AS ] string ] escape [as] string
|
* syntax varies across backend versions, but we avoid all that mess
|
||||||
* [ force quote column [, ...] | * ] ]
|
* by just transmitting the stuff after the filename literally.
|
||||||
*
|
|
||||||
* Force quote only applies for copy to; force not null only applies for
|
|
||||||
* copy from.
|
|
||||||
*
|
*
|
||||||
* table name can be double-quoted and can have a schema part.
|
* table name can be double-quoted and can have a schema part.
|
||||||
* column names can be double-quoted.
|
* column names can be double-quoted.
|
||||||
* filename, char, and string can be single-quoted like SQL literals.
|
* filename can be single-quoted like SQL literals.
|
||||||
*
|
*
|
||||||
* returns a malloc'ed structure with the options, or NULL on parsing error
|
* returns a malloc'ed structure with the options, or NULL on parsing error
|
||||||
*/
|
*/
|
||||||
|
|
||||||
struct copy_options
|
struct copy_options
|
||||||
{
|
{
|
||||||
char *table;
|
char *before_tofrom; /* COPY string before TO/FROM */
|
||||||
char *column_list;
|
char *after_tofrom; /* COPY string after TO/FROM filename */
|
||||||
char *file; /* NULL = stdin/stdout */
|
char *file; /* NULL = stdin/stdout */
|
||||||
bool psql_inout; /* true = use psql stdin/stdout */
|
bool psql_inout; /* true = use psql stdin/stdout */
|
||||||
bool from;
|
bool from; /* true = FROM, false = TO */
|
||||||
bool binary;
|
|
||||||
bool oids;
|
|
||||||
bool csv_mode;
|
|
||||||
bool header;
|
|
||||||
char *delim;
|
|
||||||
char *null;
|
|
||||||
char *quote;
|
|
||||||
char *escape;
|
|
||||||
char *force_quote_list;
|
|
||||||
char *force_notnull_list;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -77,15 +62,9 @@ free_copy_options(struct copy_options * ptr)
|
|||||||
{
|
{
|
||||||
if (!ptr)
|
if (!ptr)
|
||||||
return;
|
return;
|
||||||
free(ptr->table);
|
free(ptr->before_tofrom);
|
||||||
free(ptr->column_list);
|
free(ptr->after_tofrom);
|
||||||
free(ptr->file);
|
free(ptr->file);
|
||||||
free(ptr->delim);
|
|
||||||
free(ptr->null);
|
|
||||||
free(ptr->quote);
|
|
||||||
free(ptr->escape);
|
|
||||||
free(ptr->force_quote_list);
|
|
||||||
free(ptr->force_notnull_list);
|
|
||||||
free(ptr);
|
free(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,14 +87,11 @@ static struct copy_options *
|
|||||||
parse_slash_copy(const char *args)
|
parse_slash_copy(const char *args)
|
||||||
{
|
{
|
||||||
struct copy_options *result;
|
struct copy_options *result;
|
||||||
char *line;
|
|
||||||
char *token;
|
char *token;
|
||||||
const char *whitespace = " \t\n\r";
|
const char *whitespace = " \t\n\r";
|
||||||
char nonstd_backslash = standard_strings() ? 0 : '\\';
|
char nonstd_backslash = standard_strings() ? 0 : '\\';
|
||||||
|
|
||||||
if (args)
|
if (!args)
|
||||||
line = pg_strdup(args);
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
psql_error("\\copy: arguments required\n");
|
psql_error("\\copy: arguments required\n");
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -123,22 +99,23 @@ parse_slash_copy(const char *args)
|
|||||||
|
|
||||||
result = pg_calloc(1, sizeof(struct copy_options));
|
result = pg_calloc(1, sizeof(struct copy_options));
|
||||||
|
|
||||||
token = strtokx(line, whitespace, ".,()", "\"",
|
result->before_tofrom = pg_strdup(""); /* initialize for appending */
|
||||||
|
|
||||||
|
token = strtokx(args, whitespace, ".,()", "\"",
|
||||||
0, false, false, pset.encoding);
|
0, false, false, pset.encoding);
|
||||||
if (!token)
|
if (!token)
|
||||||
goto error;
|
goto error;
|
||||||
|
|
||||||
|
/* The following can be removed when we drop 7.3 syntax support */
|
||||||
if (pg_strcasecmp(token, "binary") == 0)
|
if (pg_strcasecmp(token, "binary") == 0)
|
||||||
{
|
{
|
||||||
result->binary = true;
|
xstrcat(&result->before_tofrom, token);
|
||||||
token = strtokx(NULL, whitespace, ".,()", "\"",
|
token = strtokx(NULL, whitespace, ".,()", "\"",
|
||||||
0, false, false, pset.encoding);
|
0, false, false, pset.encoding);
|
||||||
if (!token)
|
if (!token)
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
result->table = pg_strdup(token);
|
|
||||||
|
|
||||||
/* Handle COPY (SELECT) case */
|
/* Handle COPY (SELECT) case */
|
||||||
if (token[0] == '(')
|
if (token[0] == '(')
|
||||||
{
|
{
|
||||||
@ -146,7 +123,9 @@ parse_slash_copy(const char *args)
|
|||||||
|
|
||||||
while (parens > 0)
|
while (parens > 0)
|
||||||
{
|
{
|
||||||
token = strtokx(NULL, whitespace, ".,()", "\"'",
|
xstrcat(&result->before_tofrom, " ");
|
||||||
|
xstrcat(&result->before_tofrom, token);
|
||||||
|
token = strtokx(NULL, whitespace, "()", "\"'",
|
||||||
nonstd_backslash, true, false, pset.encoding);
|
nonstd_backslash, true, false, pset.encoding);
|
||||||
if (!token)
|
if (!token)
|
||||||
goto error;
|
goto error;
|
||||||
@ -154,11 +133,11 @@ parse_slash_copy(const char *args)
|
|||||||
parens++;
|
parens++;
|
||||||
else if (token[0] == ')')
|
else if (token[0] == ')')
|
||||||
parens--;
|
parens--;
|
||||||
xstrcat(&result->table, " ");
|
|
||||||
xstrcat(&result->table, token);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xstrcat(&result->before_tofrom, " ");
|
||||||
|
xstrcat(&result->before_tofrom, token);
|
||||||
token = strtokx(NULL, whitespace, ".,()", "\"",
|
token = strtokx(NULL, whitespace, ".,()", "\"",
|
||||||
0, false, false, pset.encoding);
|
0, false, false, pset.encoding);
|
||||||
if (!token)
|
if (!token)
|
||||||
@ -171,12 +150,12 @@ parse_slash_copy(const char *args)
|
|||||||
if (token[0] == '.')
|
if (token[0] == '.')
|
||||||
{
|
{
|
||||||
/* handle schema . table */
|
/* handle schema . table */
|
||||||
xstrcat(&result->table, token);
|
xstrcat(&result->before_tofrom, token);
|
||||||
token = strtokx(NULL, whitespace, ".,()", "\"",
|
token = strtokx(NULL, whitespace, ".,()", "\"",
|
||||||
0, false, false, pset.encoding);
|
0, false, false, pset.encoding);
|
||||||
if (!token)
|
if (!token)
|
||||||
goto error;
|
goto error;
|
||||||
xstrcat(&result->table, token);
|
xstrcat(&result->before_tofrom, token);
|
||||||
token = strtokx(NULL, whitespace, ".,()", "\"",
|
token = strtokx(NULL, whitespace, ".,()", "\"",
|
||||||
0, false, false, pset.encoding);
|
0, false, false, pset.encoding);
|
||||||
if (!token)
|
if (!token)
|
||||||
@ -186,24 +165,19 @@ parse_slash_copy(const char *args)
|
|||||||
if (token[0] == '(')
|
if (token[0] == '(')
|
||||||
{
|
{
|
||||||
/* handle parenthesized column list */
|
/* handle parenthesized column list */
|
||||||
result->column_list = pg_strdup(token);
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
token = strtokx(NULL, whitespace, ".,()", "\"",
|
xstrcat(&result->before_tofrom, " ");
|
||||||
0, false, false, pset.encoding);
|
xstrcat(&result->before_tofrom, token);
|
||||||
if (!token || strchr(".,()", token[0]))
|
token = strtokx(NULL, whitespace, "()", "\"",
|
||||||
goto error;
|
|
||||||
xstrcat(&result->column_list, token);
|
|
||||||
token = strtokx(NULL, whitespace, ".,()", "\"",
|
|
||||||
0, false, false, pset.encoding);
|
0, false, false, pset.encoding);
|
||||||
if (!token)
|
if (!token)
|
||||||
goto error;
|
goto error;
|
||||||
xstrcat(&result->column_list, token);
|
|
||||||
if (token[0] == ')')
|
if (token[0] == ')')
|
||||||
break;
|
break;
|
||||||
if (token[0] != ',')
|
|
||||||
goto error;
|
|
||||||
}
|
}
|
||||||
|
xstrcat(&result->before_tofrom, " ");
|
||||||
|
xstrcat(&result->before_tofrom, token);
|
||||||
token = strtokx(NULL, whitespace, ".,()", "\"",
|
token = strtokx(NULL, whitespace, ".,()", "\"",
|
||||||
0, false, false, pset.encoding);
|
0, false, false, pset.encoding);
|
||||||
if (!token)
|
if (!token)
|
||||||
@ -241,156 +215,11 @@ parse_slash_copy(const char *args)
|
|||||||
expand_tilde(&result->file);
|
expand_tilde(&result->file);
|
||||||
}
|
}
|
||||||
|
|
||||||
token = strtokx(NULL, whitespace, NULL, NULL,
|
/* Collect the rest of the line (COPY options) */
|
||||||
|
token = strtokx(NULL, "", NULL, NULL,
|
||||||
0, false, false, pset.encoding);
|
0, false, false, pset.encoding);
|
||||||
|
|
||||||
if (token)
|
if (token)
|
||||||
{
|
result->after_tofrom = pg_strdup(token);
|
||||||
/*
|
|
||||||
* WITH is optional. Also, the backend will allow WITH followed by
|
|
||||||
* nothing, so we do too.
|
|
||||||
*/
|
|
||||||
if (pg_strcasecmp(token, "with") == 0)
|
|
||||||
token = strtokx(NULL, whitespace, NULL, NULL,
|
|
||||||
0, false, false, pset.encoding);
|
|
||||||
|
|
||||||
while (token)
|
|
||||||
{
|
|
||||||
bool fetch_next;
|
|
||||||
|
|
||||||
fetch_next = true;
|
|
||||||
|
|
||||||
if (pg_strcasecmp(token, "oids") == 0)
|
|
||||||
result->oids = true;
|
|
||||||
else if (pg_strcasecmp(token, "binary") == 0)
|
|
||||||
result->binary = true;
|
|
||||||
else if (pg_strcasecmp(token, "csv") == 0)
|
|
||||||
result->csv_mode = true;
|
|
||||||
else if (pg_strcasecmp(token, "header") == 0)
|
|
||||||
result->header = true;
|
|
||||||
else if (pg_strcasecmp(token, "delimiter") == 0)
|
|
||||||
{
|
|
||||||
if (result->delim)
|
|
||||||
goto error;
|
|
||||||
token = strtokx(NULL, whitespace, NULL, "'",
|
|
||||||
nonstd_backslash, true, false, pset.encoding);
|
|
||||||
if (token && pg_strcasecmp(token, "as") == 0)
|
|
||||||
token = strtokx(NULL, whitespace, NULL, "'",
|
|
||||||
nonstd_backslash, true, false, pset.encoding);
|
|
||||||
if (token)
|
|
||||||
result->delim = pg_strdup(token);
|
|
||||||
else
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
else if (pg_strcasecmp(token, "null") == 0)
|
|
||||||
{
|
|
||||||
if (result->null)
|
|
||||||
goto error;
|
|
||||||
token = strtokx(NULL, whitespace, NULL, "'",
|
|
||||||
nonstd_backslash, true, false, pset.encoding);
|
|
||||||
if (token && pg_strcasecmp(token, "as") == 0)
|
|
||||||
token = strtokx(NULL, whitespace, NULL, "'",
|
|
||||||
nonstd_backslash, true, false, pset.encoding);
|
|
||||||
if (token)
|
|
||||||
result->null = pg_strdup(token);
|
|
||||||
else
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
else if (pg_strcasecmp(token, "quote") == 0)
|
|
||||||
{
|
|
||||||
if (result->quote)
|
|
||||||
goto error;
|
|
||||||
token = strtokx(NULL, whitespace, NULL, "'",
|
|
||||||
nonstd_backslash, true, false, pset.encoding);
|
|
||||||
if (token && pg_strcasecmp(token, "as") == 0)
|
|
||||||
token = strtokx(NULL, whitespace, NULL, "'",
|
|
||||||
nonstd_backslash, true, false, pset.encoding);
|
|
||||||
if (token)
|
|
||||||
result->quote = pg_strdup(token);
|
|
||||||
else
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
else if (pg_strcasecmp(token, "escape") == 0)
|
|
||||||
{
|
|
||||||
if (result->escape)
|
|
||||||
goto error;
|
|
||||||
token = strtokx(NULL, whitespace, NULL, "'",
|
|
||||||
nonstd_backslash, true, false, pset.encoding);
|
|
||||||
if (token && pg_strcasecmp(token, "as") == 0)
|
|
||||||
token = strtokx(NULL, whitespace, NULL, "'",
|
|
||||||
nonstd_backslash, true, false, pset.encoding);
|
|
||||||
if (token)
|
|
||||||
result->escape = pg_strdup(token);
|
|
||||||
else
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
else if (pg_strcasecmp(token, "force") == 0)
|
|
||||||
{
|
|
||||||
token = strtokx(NULL, whitespace, ",", "\"",
|
|
||||||
0, false, false, pset.encoding);
|
|
||||||
if (pg_strcasecmp(token, "quote") == 0)
|
|
||||||
{
|
|
||||||
if (result->force_quote_list)
|
|
||||||
goto error;
|
|
||||||
/* handle column list */
|
|
||||||
fetch_next = false;
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
token = strtokx(NULL, whitespace, ",", "\"",
|
|
||||||
0, false, false, pset.encoding);
|
|
||||||
if (!token || strchr(",", token[0]))
|
|
||||||
goto error;
|
|
||||||
if (!result->force_quote_list)
|
|
||||||
result->force_quote_list = pg_strdup(token);
|
|
||||||
else
|
|
||||||
xstrcat(&result->force_quote_list, token);
|
|
||||||
token = strtokx(NULL, whitespace, ",", "\"",
|
|
||||||
0, false, false, pset.encoding);
|
|
||||||
if (!token || token[0] != ',')
|
|
||||||
break;
|
|
||||||
xstrcat(&result->force_quote_list, token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (pg_strcasecmp(token, "not") == 0)
|
|
||||||
{
|
|
||||||
if (result->force_notnull_list)
|
|
||||||
goto error;
|
|
||||||
token = strtokx(NULL, whitespace, ",", "\"",
|
|
||||||
0, false, false, pset.encoding);
|
|
||||||
if (pg_strcasecmp(token, "null") != 0)
|
|
||||||
goto error;
|
|
||||||
/* handle column list */
|
|
||||||
fetch_next = false;
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
token = strtokx(NULL, whitespace, ",", "\"",
|
|
||||||
0, false, false, pset.encoding);
|
|
||||||
if (!token || strchr(",", token[0]))
|
|
||||||
goto error;
|
|
||||||
if (!result->force_notnull_list)
|
|
||||||
result->force_notnull_list = pg_strdup(token);
|
|
||||||
else
|
|
||||||
xstrcat(&result->force_notnull_list, token);
|
|
||||||
token = strtokx(NULL, whitespace, ",", "\"",
|
|
||||||
0, false, false, pset.encoding);
|
|
||||||
if (!token || token[0] != ',')
|
|
||||||
break;
|
|
||||||
xstrcat(&result->force_notnull_list, token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
if (fetch_next)
|
|
||||||
token = strtokx(NULL, whitespace, NULL, NULL,
|
|
||||||
0, false, false, pset.encoding);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(line);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
@ -400,29 +229,11 @@ error:
|
|||||||
else
|
else
|
||||||
psql_error("\\copy: parse error at end of line\n");
|
psql_error("\\copy: parse error at end of line\n");
|
||||||
free_copy_options(result);
|
free_copy_options(result);
|
||||||
free(line);
|
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Handle one of the "string" options of COPY. If the user gave a quoted
|
|
||||||
* string, pass it to the backend as-is; if it wasn't quoted then quote
|
|
||||||
* and escape it.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
emit_copy_option(PQExpBuffer query, const char *keyword, const char *option)
|
|
||||||
{
|
|
||||||
appendPQExpBufferStr(query, keyword);
|
|
||||||
if (option[0] == '\'' ||
|
|
||||||
((option[0] == 'E' || option[0] == 'e') && option[1] == '\''))
|
|
||||||
appendPQExpBufferStr(query, option);
|
|
||||||
else
|
|
||||||
appendStringLiteralConn(query, option, pset.db);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Execute a \copy command (frontend copy). We have to open a file, then
|
* Execute a \copy command (frontend copy). We have to open a file, then
|
||||||
* submit a COPY query to the backend and either feed it data from the
|
* submit a COPY query to the backend and either feed it data from the
|
||||||
@ -444,51 +255,7 @@ do_copy(const char *args)
|
|||||||
if (!options)
|
if (!options)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
initPQExpBuffer(&query);
|
/* prepare to read or write the target file */
|
||||||
|
|
||||||
printfPQExpBuffer(&query, "COPY ");
|
|
||||||
|
|
||||||
appendPQExpBuffer(&query, "%s ", options->table);
|
|
||||||
|
|
||||||
if (options->column_list)
|
|
||||||
appendPQExpBuffer(&query, "%s ", options->column_list);
|
|
||||||
|
|
||||||
if (options->from)
|
|
||||||
appendPQExpBuffer(&query, "FROM STDIN");
|
|
||||||
else
|
|
||||||
appendPQExpBuffer(&query, "TO STDOUT");
|
|
||||||
|
|
||||||
|
|
||||||
if (options->binary)
|
|
||||||
appendPQExpBuffer(&query, " BINARY ");
|
|
||||||
|
|
||||||
if (options->oids)
|
|
||||||
appendPQExpBuffer(&query, " OIDS ");
|
|
||||||
|
|
||||||
if (options->delim)
|
|
||||||
emit_copy_option(&query, " DELIMITER ", options->delim);
|
|
||||||
|
|
||||||
if (options->null)
|
|
||||||
emit_copy_option(&query, " NULL AS ", options->null);
|
|
||||||
|
|
||||||
if (options->csv_mode)
|
|
||||||
appendPQExpBuffer(&query, " CSV");
|
|
||||||
|
|
||||||
if (options->header)
|
|
||||||
appendPQExpBuffer(&query, " HEADER");
|
|
||||||
|
|
||||||
if (options->quote)
|
|
||||||
emit_copy_option(&query, " QUOTE AS ", options->quote);
|
|
||||||
|
|
||||||
if (options->escape)
|
|
||||||
emit_copy_option(&query, " ESCAPE AS ", options->escape);
|
|
||||||
|
|
||||||
if (options->force_quote_list)
|
|
||||||
appendPQExpBuffer(&query, " FORCE QUOTE %s", options->force_quote_list);
|
|
||||||
|
|
||||||
if (options->force_notnull_list)
|
|
||||||
appendPQExpBuffer(&query, " FORCE NOT NULL %s", options->force_notnull_list);
|
|
||||||
|
|
||||||
if (options->file)
|
if (options->file)
|
||||||
canonicalize_path(options->file);
|
canonicalize_path(options->file);
|
||||||
|
|
||||||
@ -504,8 +271,7 @@ do_copy(const char *args)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (options->file)
|
if (options->file)
|
||||||
copystream = fopen(options->file,
|
copystream = fopen(options->file, PG_BINARY_W);
|
||||||
options->binary ? PG_BINARY_W : "w");
|
|
||||||
else if (!options->psql_inout)
|
else if (!options->psql_inout)
|
||||||
copystream = pset.queryFout;
|
copystream = pset.queryFout;
|
||||||
else
|
else
|
||||||
@ -531,6 +297,17 @@ do_copy(const char *args)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* build the command we will send to the backend */
|
||||||
|
initPQExpBuffer(&query);
|
||||||
|
printfPQExpBuffer(&query, "COPY ");
|
||||||
|
appendPQExpBufferStr(&query, options->before_tofrom);
|
||||||
|
if (options->from)
|
||||||
|
appendPQExpBuffer(&query, " FROM STDIN ");
|
||||||
|
else
|
||||||
|
appendPQExpBuffer(&query, " TO STDOUT ");
|
||||||
|
if (options->after_tofrom)
|
||||||
|
appendPQExpBufferStr(&query, options->after_tofrom);
|
||||||
|
|
||||||
result = PSQLexec(query.data, true);
|
result = PSQLexec(query.data, true);
|
||||||
termPQExpBuffer(&query);
|
termPQExpBuffer(&query);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user