>>>>> "Merlin" == Merlin Moncure <[email protected]> writes:
Merlin> Note, the biggest pain point I have with COPY is not being able
Merlin> to parameterize the filename argument.
Second proof of concept attached. This goes so far as to allow
statements like:
do $$
declare t text := 'bar'; f text := '/tmp/copytest.dat';
begin copy (select t, now()) to (f) csv header; end;
$$;
Also "copy foo to $1" or "copy (select * from foo where x=$1) to $2" and
so on should work from PQexecParams or in a plpgsql EXECUTE.
(I haven't tried to parameterize anything other than the filename and
query. Also, it does not accept arbitrary expressions - only $n, '...'
or a columnref. $n and '...' can have parens or not, but the columnref
must have them due to conflicts with unreserved keywords PROGRAM, STDIN,
STDOUT. This could be hacked around in other ways, I guess, if the
parens are too ugly.)
--
Andrew (irc:RhodiumToad)
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3201476..97debb7 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -37,6 +37,8 @@
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/analyze.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
#include "tcop/tcopprot.h"
@@ -279,13 +281,13 @@ 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,
+static CopyState BeginCopy(bool is_from, Relation rel, Node *raw_query, ParamListInfo params,
const char *queryString, const Oid queryRelId, List *attnamelist,
List *options);
static void EndCopy(CopyState cstate);
static void ClosePipeToProgram(CopyState cstate);
-static CopyState BeginCopyTo(Relation rel, Node *query, const char *queryString,
- const Oid queryRelId, const char *filename, bool is_program,
+static CopyState BeginCopyTo(Relation rel, Node *query, ParamListInfo params, const char *queryString,
+ const Oid queryRelId, Node *filename_expr, bool is_program,
List *attnamelist, List *options);
static void EndCopyTo(CopyState cstate);
static uint64 DoCopyTo(CopyState cstate);
@@ -767,6 +769,43 @@ CopyLoadRawBuf(CopyState cstate)
}
+static char *
+CopyEvalFilename(QueryDesc *qd, Node *expr, ParamListInfo params)
+{
+ char *filename = NULL;
+
+ if (expr)
+ {
+ EState *estate = qd ? qd->estate : CreateExecutorState();
+ MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ Assert(exprType(expr) == CSTRINGOID);
+
+ if (qd == NULL)
+ estate->es_param_list_info = params;
+
+ {
+ ExprContext *ecxt = CreateExprContext(estate);
+ ExprState *exprstate = ExecPrepareExpr(copyObject(expr), estate);
+ bool isnull;
+ Datum result = ExecEvalExprSwitchContext(exprstate, ecxt, &isnull, NULL);
+ if (!isnull)
+ filename = MemoryContextStrdup(oldcontext, DatumGetCString(result));
+ FreeExprContext(ecxt, true);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (qd == NULL)
+ FreeExecutorState(estate);
+
+ if (!filename)
+ elog(ERROR, "COPY filename expression must not return NULL");
+ }
+
+ return filename;
+}
+
/*
* DoCopy executes the SQL COPY statement
*
@@ -787,7 +826,7 @@ CopyLoadRawBuf(CopyState cstate)
* the table or the specifically requested columns.
*/
Oid
-DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
+DoCopy(const CopyStmt *stmt, const char *queryString, ParamListInfo params, uint64 *processed)
{
CopyState cstate;
bool is_from = stmt->is_from;
@@ -906,7 +945,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
select->targetList = list_make1(target);
select->fromClause = list_make1(from);
- query = (Node *) select;
+ query = (Node *) parse_analyze((Node *) select, queryString, NULL, 0);
/*
* Close the relation for now, but keep the lock on it to prevent
@@ -929,6 +968,8 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
if (is_from)
{
+ char *filename;
+
Assert(rel);
/* check read-only transaction and parallel mode */
@@ -936,15 +977,20 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
PreventCommandIfReadOnly("COPY FROM");
PreventCommandIfParallelMode("COPY FROM");
- cstate = BeginCopyFrom(rel, stmt->filename, stmt->is_program,
+ filename = CopyEvalFilename(NULL, stmt->filename, params);
+
+ cstate = BeginCopyFrom(rel, filename, stmt->is_program,
stmt->attlist, stmt->options);
cstate->range_table = range_table;
*processed = CopyFrom(cstate); /* copy from file to database */
EndCopyFrom(cstate);
+
+ if (filename)
+ pfree(filename);
}
else
{
- cstate = BeginCopyTo(rel, query, queryString, relid,
+ cstate = BeginCopyTo(rel, query, params, queryString, relid,
stmt->filename, stmt->is_program,
stmt->attlist, stmt->options);
*processed = DoCopyTo(cstate); /* copy from database to file */
@@ -1321,6 +1367,7 @@ static CopyState
BeginCopy(bool is_from,
Relation rel,
Node *raw_query,
+ ParamListInfo params,
const char *queryString,
const Oid queryRelId,
List *attnamelist,
@@ -1391,8 +1438,7 @@ BeginCopy(bool is_from,
* function and is executed repeatedly. (See also the same hack in
* DECLARE CURSOR and PREPARE.) XXX FIXME someday.
*/
- rewritten = pg_analyze_and_rewrite((Node *) copyObject(raw_query),
- queryString, NULL, 0);
+ rewritten = QueryRewrite(copyObject(raw_query));
/* check that we got back something we can work with */
if (rewritten == NIL)
@@ -1453,7 +1499,7 @@ BeginCopy(bool is_from,
}
/* plan the query */
- plan = pg_plan_query(query, 0, NULL);
+ plan = pg_plan_query(query, 0, params);
/*
* With row level security and a user using "COPY relation TO", we
@@ -1495,7 +1541,7 @@ BeginCopy(bool is_from,
cstate->queryDesc = CreateQueryDesc(plan, queryString,
GetActiveSnapshot(),
InvalidSnapshot,
- dest, NULL, 0);
+ dest, params, 0);
/*
* Call ExecutorStart to prepare the plan for execution.
@@ -1682,15 +1728,16 @@ EndCopy(CopyState cstate)
static CopyState
BeginCopyTo(Relation rel,
Node *query,
+ ParamListInfo params,
const char *queryString,
const Oid queryRelId,
- const char *filename,
+ Node *filename_expr,
bool is_program,
List *attnamelist,
List *options)
{
CopyState cstate;
- bool pipe = (filename == NULL);
+ bool pipe = (filename_expr == NULL);
MemoryContext oldcontext;
if (rel != NULL && rel->rd_rel->relkind != RELKIND_RELATION)
@@ -1725,7 +1772,7 @@ BeginCopyTo(Relation rel,
RelationGetRelationName(rel))));
}
- cstate = BeginCopy(false, rel, query, queryString, queryRelId, attnamelist,
+ cstate = BeginCopy(false, rel, query, params, queryString, queryRelId, attnamelist,
options);
oldcontext = MemoryContextSwitchTo(cstate->copycontext);
@@ -1737,7 +1784,7 @@ BeginCopyTo(Relation rel,
}
else
{
- cstate->filename = pstrdup(filename);
+ cstate->filename = CopyEvalFilename(cstate->queryDesc, filename_expr, params);
cstate->is_program = is_program;
if (is_program)
@@ -1758,7 +1805,7 @@ BeginCopyTo(Relation rel,
* Prevent write to relative path ... too easy to shoot oneself in
* the foot by overwriting a database file ...
*/
- if (!is_absolute_path(filename))
+ if (!is_absolute_path(cstate->filename))
ereport(ERROR,
(errcode(ERRCODE_INVALID_NAME),
errmsg("relative path not allowed for COPY to file")));
@@ -2670,7 +2717,7 @@ BeginCopyFrom(Relation rel,
MemoryContext oldcontext;
bool volatile_defexprs;
- cstate = BeginCopy(true, rel, NULL, NULL, InvalidOid, attnamelist, options);
+ cstate = BeginCopy(true, rel, NULL, NULL, NULL, InvalidOid, attnamelist, options);
oldcontext = MemoryContextSwitchTo(cstate->copycontext);
/* Initialize state variables */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 20e38f0..f78404e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2983,7 +2983,7 @@ _copyCopyStmt(const CopyStmt *from)
COPY_NODE_FIELD(attlist);
COPY_SCALAR_FIELD(is_from);
COPY_SCALAR_FIELD(is_program);
- COPY_STRING_FIELD(filename);
+ COPY_NODE_FIELD(filename);
COPY_NODE_FIELD(options);
return newnode;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c5ccc42..4af21cb 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1144,7 +1144,7 @@ _equalCopyStmt(const CopyStmt *a, const CopyStmt *b)
COMPARE_NODE_FIELD(attlist);
COMPARE_SCALAR_FIELD(is_from);
COMPARE_SCALAR_FIELD(is_program);
- COMPARE_STRING_FIELD(filename);
+ COMPARE_NODE_FIELD(filename);
COMPARE_NODE_FIELD(options);
return true;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 29c8c4e..f0a3f60 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -36,12 +36,14 @@
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_cte.h"
+#include "parser/parse_expr.h"
#include "parser/parse_oper.h"
#include "parser/parse_param.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
+#include "utils/builtins.h"
#include "utils/rel.h"
@@ -74,6 +76,7 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
CreateTableAsStmt *stmt);
static void transformLockingClause(ParseState *pstate, Query *qry,
LockingClause *lc, bool pushedDown);
+static Query *transformCopyStmt(ParseState *pstate, CopyStmt *stmt);
#ifdef RAW_EXPRESSION_COVERAGE_TEST
static bool test_raw_expression_coverage(Node *node, void *context);
#endif
@@ -290,6 +293,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
(CreateTableAsStmt *) parseTree);
break;
+ case T_CopyStmt:
+ result = transformCopyStmt(pstate,
+ (CopyStmt *) parseTree);
+ break;
+
default:
/*
@@ -347,6 +355,11 @@ analyze_requires_snapshot(Node *parseTree)
result = true;
break;
+ case T_CopyStmt:
+ /* maybe, because we might have a contained statement */
+ result = ((CopyStmt *)parseTree)->query != NULL;
+ break;
+
default:
/* other utility statements don't have any real parse analysis */
result = false;
@@ -356,6 +369,40 @@ analyze_requires_snapshot(Node *parseTree)
return result;
}
+static Query *
+transformCopyStmt(ParseState *pstate, CopyStmt *stmt)
+{
+ Query *result = makeNode(Query);
+
+ result->commandType = CMD_UTILITY;
+ result->utilityStmt = (Node *) stmt;
+
+ if (stmt->filename)
+ {
+ Node *expr1 = transformExpr(pstate, stmt->filename, EXPR_KIND_OTHER);
+ Node *expr2 = coerce_to_target_type(pstate, expr1, exprType(expr1),
+ CSTRINGOID, -1,
+ COERCION_EXPLICIT,
+ COERCE_IMPLICIT_CAST,
+ exprLocation(expr1));
+
+ if (!expr2)
+ ereport(ERROR,
+ (errcode(ERRCODE_CANNOT_COERCE),
+ errmsg("cannot cast type %s to %s",
+ format_type_be(exprType(expr1)),
+ format_type_be(CSTRINGOID)),
+ parser_errposition(pstate, exprLocation(expr1))));
+
+ stmt->filename = expr2;
+ }
+
+ if (stmt->query != NULL)
+ stmt->query = (Node *) transformStmt(pstate, stmt->query);
+
+ return result;
+}
+
/*
* transformDeleteStmt -
* transforms a Delete Statement
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 18ec5f0..6854255 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -313,8 +313,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <defelt> event_trigger_when_item
%type <chr> enable_trigger
-%type <str> copy_file_name
- database_name access_method_clause access_method attr_name
+%type <node> copy_file_name param_opt_indirection
+%type <str> database_name access_method_clause access_method attr_name
name cursor_name file_name
index_name opt_index_name cluster_index_specification
@@ -2651,7 +2651,11 @@ opt_program:
* stdout. We silently correct the "typo".) - AY 9/94
*/
copy_file_name:
- Sconst { $$ = $1; }
+ Sconst { $$ = makeStringConst($1,@1); }
+ | '(' Sconst ')' { $$ = makeStringConst($2,@2); }
+ | param_opt_indirection { $$ = $1; }
+ | '(' columnref ')' { $$ = $2; }
+ | '(' param_opt_indirection ')' { $$ = $2; }
| STDIN { $$ = NULL; }
| STDOUT { $$ = NULL; }
;
@@ -12049,21 +12053,7 @@ b_expr: c_expr
*/
c_expr: columnref { $$ = $1; }
| AexprConst { $$ = $1; }
- | PARAM opt_indirection
- {
- ParamRef *p = makeNode(ParamRef);
- p->number = $1;
- p->location = @1;
- if ($2)
- {
- A_Indirection *n = makeNode(A_Indirection);
- n->arg = (Node *) p;
- n->indirection = check_indirection($2, yyscanner);
- $$ = (Node *) n;
- }
- else
- $$ = (Node *) p;
- }
+ | param_opt_indirection { $$ = $1; }
| '(' a_expr ')' opt_indirection
{
if ($4)
@@ -12192,6 +12182,23 @@ c_expr: columnref { $$ = $1; }
}
;
+param_opt_indirection: PARAM opt_indirection
+ {
+ ParamRef *p = makeNode(ParamRef);
+ p->number = $1;
+ p->location = @1;
+ if ($2)
+ {
+ A_Indirection *n = makeNode(A_Indirection);
+ n->arg = (Node *) p;
+ n->indirection = check_indirection($2, yyscanner);
+ $$ = (Node *) n;
+ }
+ else
+ $$ = (Node *) p;
+ }
+ ;
+
func_application: func_name '(' ')'
{
$$ = (Node *) makeFuncCall($1, NIL, @1);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ac50c2a..dba2f5e 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -540,7 +540,7 @@ standard_ProcessUtility(Node *parsetree,
{
uint64 processed;
- DoCopy((CopyStmt *) parsetree, queryString, &processed);
+ DoCopy((CopyStmt *) parsetree, queryString, params, &processed);
if (completionTag)
snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
"COPY " UINT64_FORMAT, processed);
@@ -1769,6 +1769,16 @@ UtilityContainsQuery(Node *parsetree)
return UtilityContainsQuery(qry->utilityStmt);
return qry;
+ case T_CopyStmt:
+ qry = (Query *) ((CopyStmt *) parsetree)->query;
+ if (qry)
+ {
+ Assert(IsA(qry, Query));
+ if (qry->commandType == CMD_UTILITY)
+ return UtilityContainsQuery(qry->utilityStmt);
+ }
+ return qry;
+
case T_CreateTableAsStmt:
qry = (Query *) ((CreateTableAsStmt *) parsetree)->query;
Assert(IsA(qry, Query));
diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h
index 314d1f7..44c2c66 100644
--- a/src/include/commands/copy.h
+++ b/src/include/commands/copy.h
@@ -21,7 +21,7 @@
/* CopyStateData is private in commands/copy.c */
typedef struct CopyStateData *CopyState;
-extern Oid DoCopy(const CopyStmt *stmt, const char *queryString,
+extern Oid DoCopy(const CopyStmt *stmt, const char *queryString, ParamListInfo params,
uint64 *processed);
extern void ProcessCopyOptions(CopyState cstate, bool is_from, List *options);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 714cf15..049ac4a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1692,7 +1692,7 @@ typedef struct CopyStmt
* for all columns */
bool is_from; /* TO or FROM */
bool is_program; /* is 'filename' a program to popen? */
- char *filename; /* filename, or NULL for STDIN/STDOUT */
+ Node *filename; /* filename, or NULL for STDIN/STDOUT */
List *options; /* List of DefElem nodes */
} CopyStmt;
diff --git a/src/test/regress/expected/copyselect.out b/src/test/regress/expected/copyselect.out
index 72865fe..a02a199 100644
--- a/src/test/regress/expected/copyselect.out
+++ b/src/test/regress/expected/copyselect.out
@@ -44,7 +44,9 @@ c
-- This should fail
--
copy (select t into temp test3 from test1 where id=3) to stdout;
-ERROR: COPY (SELECT INTO) is not supported
+ERROR: SELECT ... INTO is not allowed here
+LINE 1: copy (select t into temp test3 from test1 where id=3) to std...
+ ^
--
-- This should fail
--
diff --git a/src/test/regress/expected/select_into.out b/src/test/regress/expected/select_into.out
index b577d1b..6b6482e 100644
--- a/src/test/regress/expected/select_into.out
+++ b/src/test/regress/expected/select_into.out
@@ -90,7 +90,9 @@ ERROR: SELECT ... INTO is not allowed here
LINE 1: DECLARE foo CURSOR FOR SELECT 1 INTO b;
^
COPY (SELECT 1 INTO frak UNION SELECT 2) TO 'blob';
-ERROR: COPY (SELECT INTO) is not supported
+ERROR: SELECT ... INTO is not allowed here
+LINE 1: COPY (SELECT 1 INTO frak UNION SELECT 2) TO 'blob';
+ ^
SELECT * FROM (SELECT 1 INTO f) bar;
ERROR: SELECT ... INTO is not allowed here
LINE 1: SELECT * FROM (SELECT 1 INTO f) bar;
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers