From 7c5bc30a02ec34646c8e49af1499fe4113bc9e5e Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Thu, 31 Oct 2019 09:54:07 +0100 Subject: [PATCH] Refactor parse analysis of EXECUTE command Move the parse analysis component of ExecuteQuery() and EvaluateParams() into a new transformExecuteStmt() that is called from transformStmt(). This makes EXECUTE behave more like other utility commands. It also allows error messages to have position information, and it allows using external parameters in the arguments of the EXECUTE command. --- src/backend/commands/createas.c | 2 +- src/backend/commands/prepare.c | 72 ++-------------------- src/backend/parser/analyze.c | 89 +++++++++++++++++++++++++++ src/backend/tcop/utility.c | 2 +- src/include/commands/prepare.h | 2 +- src/test/regress/expected/prepare.out | 25 ++++++++ src/test/regress/sql/prepare.sql | 21 +++++++ 7 files changed, 143 insertions(+), 70 deletions(-) diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index b7d220699f..e4244f84e2 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -271,7 +271,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, ExecuteStmt *estmt = castNode(ExecuteStmt, query->utilityStmt); Assert(!is_matview); /* excluded by syntax */ - ExecuteQuery(estmt, into, queryString, params, dest, completionTag); + ExecuteQuery(estmt, into, params, dest, completionTag); /* get object address that intorel_startup saved for us */ address = ((DR_intorel *) dest)->reladdr; diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 7e0a041fab..0aba6a7b00 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -47,7 +47,7 @@ static HTAB *prepared_queries = NULL; static void InitQueryHashTable(void); static ParamListInfo EvaluateParams(PreparedStatement *pstmt, List *params, - const char *queryString, EState *estate); + EState *estate); static Datum build_regtype_array(Oid *param_types, int num_params); /* @@ -189,16 +189,10 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString, * indicated by passing a non-null intoClause. The DestReceiver is already * set up correctly for CREATE TABLE AS, but we still have to make a few * other adjustments here. - * - * Note: this is one of very few places in the code that needs to deal with - * two query strings at once. The passed-in queryString is that of the - * EXECUTE, which we might need for error reporting while processing the - * parameter expressions. The query_string that we copy from the plan - * source is that of the original PREPARE. */ void ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause, - const char *queryString, ParamListInfo params, + ParamListInfo params, DestReceiver *dest, char *completionTag) { PreparedStatement *entry; @@ -229,8 +223,7 @@ ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause, */ estate = CreateExecutorState(); estate->es_param_list_info = params; - paramLI = EvaluateParams(entry, stmt->params, - queryString, estate); + paramLI = EvaluateParams(entry, stmt->params, estate); } /* Create a new portal to run the query in */ @@ -316,7 +309,6 @@ ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause, * * pstmt: statement we are getting parameters for. * params: list of given parameter expressions (raw parser output!) - * queryString: source text for error messages. * estate: executor state to use. * * Returns a filled-in ParamListInfo -- this can later be passed to @@ -324,72 +316,19 @@ ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause, * during query execution. */ static ParamListInfo -EvaluateParams(PreparedStatement *pstmt, List *params, - const char *queryString, EState *estate) +EvaluateParams(PreparedStatement *pstmt, List *params, EState *estate) { Oid *param_types = pstmt->plansource->param_types; int num_params = pstmt->plansource->num_params; - int nparams = list_length(params); - ParseState *pstate; ParamListInfo paramLI; List *exprstates; ListCell *l; int i; - if (nparams != num_params) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("wrong number of parameters for prepared statement \"%s\"", - pstmt->stmt_name), - errdetail("Expected %d parameters but got %d.", - num_params, nparams))); - /* Quick exit if no parameters */ if (num_params == 0) return NULL; - /* - * We have to run parse analysis for the expressions. Since the parser is - * not cool about scribbling on its input, copy first. - */ - params = copyObject(params); - - pstate = make_parsestate(NULL); - pstate->p_sourcetext = queryString; - - i = 0; - foreach(l, params) - { - Node *expr = lfirst(l); - Oid expected_type_id = param_types[i]; - Oid given_type_id; - - expr = transformExpr(pstate, expr, EXPR_KIND_EXECUTE_PARAMETER); - - given_type_id = exprType(expr); - - expr = coerce_to_target_type(pstate, expr, given_type_id, - expected_type_id, -1, - COERCION_ASSIGNMENT, - COERCE_IMPLICIT_CAST, - -1); - - if (expr == NULL) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("parameter $%d of type %s cannot be coerced to the expected type %s", - i + 1, - format_type_be(given_type_id), - format_type_be(expected_type_id)), - errhint("You will need to rewrite or cast the expression."))); - - /* Take care of collations in the finished expression. */ - assign_expr_collations(pstate, expr); - - lfirst(l) = expr; - i++; - } - /* Prepare the expressions for execution */ exprstates = ExecPrepareExprList(params, estate); @@ -655,8 +594,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es, */ estate = CreateExecutorState(); estate->es_param_list_info = params; - paramLI = EvaluateParams(entry, execstmt->params, - queryString, estate); + paramLI = EvaluateParams(entry, execstmt->params, estate); } /* Replan if needed, and acquire a transient refcount */ diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 85d7a96406..943e1764bd 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -26,6 +26,7 @@ #include "access/sysattr.h" #include "catalog/pg_type.h" +#include "commands/prepare.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" @@ -44,6 +45,7 @@ #include "parser/parse_target.h" #include "parser/parsetree.h" #include "rewrite/rewriteManip.h" +#include "utils/builtins.h" #include "utils/rel.h" @@ -72,6 +74,7 @@ static List *transformUpdateTargetList(ParseState *pstate, List *targetList); static Query *transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt); +static Query *transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt); static Query *transformExplainStmt(ParseState *pstate, ExplainStmt *stmt); static Query *transformCreateTableAsStmt(ParseState *pstate, @@ -312,6 +315,11 @@ transformStmt(ParseState *pstate, Node *parseTree) (DeclareCursorStmt *) parseTree); break; + case T_ExecuteStmt: + result = transformExecuteStmt(pstate, + (ExecuteStmt *) parseTree); + break; + case T_ExplainStmt: result = transformExplainStmt(pstate, (ExplainStmt *) parseTree); @@ -2511,6 +2519,87 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt) return result; } +/* + * transformExexecuteStmt - + * transform an EXECUTE Statement + * + * This checks that the number of parameters passed to EXECUTE matches the + * prepared statement, and it transforms the passed parameter expressions. + */ +static Query * +transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt) +{ + PreparedStatement *pstmt; + Oid *param_types; + int num_params; + int nparams; + List *params; + ListCell *l; + int i; + Query *result; + + pstmt = FetchPreparedStatement(stmt->name, true); + + param_types = pstmt->plansource->param_types; + num_params = pstmt->plansource->num_params; + nparams = list_length(stmt->params); + + if (nparams != num_params) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("wrong number of parameters for prepared statement \"%s\"", + pstmt->stmt_name), + errdetail("Expected %d parameters but got %d.", + num_params, nparams))); + + /* + * We have to run parse analysis for the expressions. Since the parser is + * not cool about scribbling on its input, copy first. + */ + params = copyObject(stmt->params); + i = 0; + foreach(l, params) + { + Node *expr = lfirst(l); + Oid expected_type_id = param_types[i]; + Oid given_type_id; + + expr = transformExpr(pstate, expr, EXPR_KIND_EXECUTE_PARAMETER); + + given_type_id = exprType(expr); + + expr = coerce_to_target_type(pstate, expr, given_type_id, + expected_type_id, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + + if (expr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("parameter $%d of type %s cannot be coerced to the expected type %s", + i + 1, + format_type_be(given_type_id), + format_type_be(expected_type_id)), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(lfirst(l))))); + + /* Take care of collations in the finished expression. */ + assign_expr_collations(pstate, expr); + + lfirst(l) = expr; + i++; + } + + stmt->params = params; + + result = makeNode(Query); + result->commandType = CMD_UTILITY; + result->utilityStmt = (Node *) stmt; + + return result; +} + /* * transformExplainStmt - diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index c6faa6619d..f2d00b0084 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -565,7 +565,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, case T_ExecuteStmt: ExecuteQuery((ExecuteStmt *) parsetree, NULL, - queryString, params, + params, dest, completionTag); break; diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h index 2ce832419f..ff24797fce 100644 --- a/src/include/commands/prepare.h +++ b/src/include/commands/prepare.h @@ -38,7 +38,7 @@ typedef struct extern void PrepareQuery(PrepareStmt *stmt, const char *queryString, int stmt_location, int stmt_len); extern void ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause, - const char *queryString, ParamListInfo params, + ParamListInfo params, DestReceiver *dest, char *completionTag); extern void DeallocateQuery(DeallocateStmt *stmt); extern void ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, diff --git a/src/test/regress/expected/prepare.out b/src/test/regress/expected/prepare.out index 717732300d..a0fec13c6b 100644 --- a/src/test/regress/expected/prepare.out +++ b/src/test/regress/expected/prepare.out @@ -113,6 +113,8 @@ DETAIL: Expected 5 parameters but got 6. -- wrong param types EXECUTE q3(5::smallint, 10.5::float, false, 4::bigint, 'bytea'); ERROR: parameter $3 of type boolean cannot be coerced to the expected type double precision +LINE 1: EXECUTE q3(5::smallint, 10.5::float, false, 4::bigint, 'byte... + ^ HINT: You will need to rewrite or cast the expression. -- invalid type PREPARE q4(nonexistenttype) AS SELECT $1; @@ -185,3 +187,26 @@ SELECT name, statement, parameter_types FROM pg_prepared_statements ------+-----------+----------------- (0 rows) +-- check parameter handling +CREATE TABLE t1 (a int); +PREPARE p1 AS INSERT INTO t1 (a) VALUES ($1); +CREATE FUNCTION f1(x int) RETURNS int +LANGUAGE SQL +AS $$ +EXECUTE p1($1); +SELECT null::int; +$$; +SELECT f1(2); + f1 +---- + +(1 row) + +SELECT * FROM t1; + a +--- + 2 +(1 row) + +DROP FUNCTION f1(int); +DROP TABLE t1; diff --git a/src/test/regress/sql/prepare.sql b/src/test/regress/sql/prepare.sql index 985d0f05c9..6a16858482 100644 --- a/src/test/regress/sql/prepare.sql +++ b/src/test/regress/sql/prepare.sql @@ -78,3 +78,24 @@ CREATE TEMPORARY TABLE q5_prep_nodata AS EXECUTE q5(200, 'DTAAAA') DEALLOCATE ALL; SELECT name, statement, parameter_types FROM pg_prepared_statements ORDER BY name; + + +-- check parameter handling + +CREATE TABLE t1 (a int); + +PREPARE p1 AS INSERT INTO t1 (a) VALUES ($1); + +CREATE FUNCTION f1(x int) RETURNS int +LANGUAGE SQL +AS $$ +EXECUTE p1($1); +SELECT null::int; +$$; + +SELECT f1(2); + +SELECT * FROM t1; + +DROP FUNCTION f1(int); +DROP TABLE t1; -- 2.23.0