From e2a8804330a1d3a043fecc49833b47d0937bf7b6 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 1 Apr 2008 03:51:09 +0000 Subject: [PATCH] Support EXECUTE USING in plpgsql. Pavel Stehule, with some improvements by myself. --- doc/src/sgml/plpgsql.sgml | 64 ++++++++++-- src/pl/plpgsql/src/gram.y | 115 +++++++++++++++------ src/pl/plpgsql/src/pl_exec.c | 139 +++++++++++++++++++++++--- src/pl/plpgsql/src/pl_funcs.c | 42 +++++++- src/pl/plpgsql/src/plpgsql.h | 4 +- src/pl/plpgsql/src/scan.l | 7 +- src/test/regress/expected/plpgsql.out | 23 +++++ src/test/regress/sql/plpgsql.sql | 16 ++- 8 files changed, 350 insertions(+), 60 deletions(-) diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index f7b94798d8..512cb7657c 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -1,4 +1,4 @@ - + <application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language @@ -1005,20 +1005,23 @@ END; EXECUTE statement is provided: -EXECUTE command-string INTO STRICT target ; +EXECUTE command-string INTO STRICT target USING expression , ... ; where command-string is an expression yielding a string (of type text) containing the - command to be executed and target is a - record variable, row variable, or a comma-separated list of - simple variables and record/row fields. + command to be executed. The optional target + is a record variable, a row variable, or a comma-separated list of + simple variables and record/row fields, into which the results of + the command will be stored. The optional USING expressions + supply values to be inserted into the command. No substitution of PL/pgSQL variables is done on the computed command string. Any required variable values must be inserted - in the command string as it is constructed. + in the command string as it is constructed; or you can use parameters + as described below. @@ -1046,6 +1049,51 @@ EXECUTE command-string INT If the STRICT option is given, an error is reported unless the query produces exactly one row. + + + The command string can use parameter values, which are referenced + in the command as $1, $2, etc. + These symbols refer to values supplied in the USING + clause. This method is often preferable to inserting data values + into the command string as text: it avoids run-time overhead of + converting the values to text and back, and it is much less prone + to SQL-injection attacks since there is no need for quoting or escaping. + An example is: + +EXECUTE 'SELECT count(*) FROM mytable WHERE inserted_by = $1 AND inserted <= $2' + INTO c + USING checked_user, checked_date; + + + Note that parameter symbols can only be used for data values + — if you want to use dynamically determined table or column + names, you must insert them into the command string textually. + For example, if the preceding query needed to be done against a + dynamically selected table, you could do this: + +EXECUTE 'SELECT count(*) FROM ' + || tabname::regclass + || ' WHERE inserted_by = $1 AND inserted <= $2' + INTO c + USING checked_user, checked_date; + + + + + An EXECUTE with a simple constant command string and some + USING parameters, as in the first example above, is + functionally equivalent to just writing the command directly in + PL/pgSQL and allowing replacement of + PL/pgSQL variables to happen automatically. + The important difference is that EXECUTE will re-plan + the command on each execution, generating a plan that is specific + to the current parameter values; whereas + PL/pgSQL normally creates a generic plan + and caches it for re-use. In situations where the best plan depends + strongly on the parameter values, EXECUTE can be + significantly faster; while when the plan is not sensitive to parameter + values, re-planning will be a waste. + SELECT INTO is not currently supported within @@ -1997,7 +2045,7 @@ $$ LANGUAGE plpgsql; rows: <<label>> -FOR target IN EXECUTE text_expression LOOP +FOR target IN EXECUTE text_expression USING expression , ... LOOP statements END LOOP label ; @@ -2006,6 +2054,8 @@ END LOOP label ; on each entry to the FOR loop. This allows the programmer to choose the speed of a preplanned query or the flexibility of a dynamic query, just as with a plain EXECUTE statement. + As with EXECUTE, parameter values can be inserted + into the dynamic command via USING. diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y index c9ffb8f18b..f8b7dd4291 100644 --- a/src/pl/plpgsql/src/gram.y +++ b/src/pl/plpgsql/src/gram.y @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.108 2008/01/01 19:46:00 momjian Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.109 2008/04/01 03:51:09 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -21,11 +21,15 @@ static PLpgSQL_expr *read_sql_construct(int until, int until2, + int until3, const char *expected, const char *sqlstart, bool isexpression, bool valid_sql, int *endtoken); +static PLpgSQL_expr *read_sql_expression2(int until, int until2, + const char *expected, + int *endtoken); static PLpgSQL_expr *read_sql_stmt(const char *sqlstart); static PLpgSQL_type *read_datatype(int tok); static PLpgSQL_stmt *make_execsql_stmt(const char *sqlstart, int lineno); @@ -200,6 +204,7 @@ static void check_labels(const char *start_label, %token K_THEN %token K_TO %token K_TYPE +%token K_USING %token K_WARNING %token K_WHEN %token K_WHILE @@ -892,8 +897,11 @@ for_control : { PLpgSQL_stmt_dynfors *new; PLpgSQL_expr *expr; + int term; - expr = plpgsql_read_expression(K_LOOP, "LOOP"); + expr = read_sql_expression2(K_LOOP, K_USING, + "LOOP or USING", + &term); new = palloc0(sizeof(PLpgSQL_stmt_dynfors)); new->cmd_type = PLPGSQL_STMT_DYNFORS; @@ -921,6 +929,17 @@ for_control : } new->query = expr; + if (term == K_USING) + { + do + { + expr = read_sql_expression2(',', K_LOOP, + ", or LOOP", + &term); + new->params = lappend(new->params, expr); + } while (term == ','); + } + $$ = (PLpgSQL_stmt *) new; } else @@ -954,6 +973,7 @@ for_control : */ expr1 = read_sql_construct(K_DOTDOT, K_LOOP, + 0, "LOOP", "SELECT ", true, @@ -973,17 +993,14 @@ for_control : check_sql_expr(expr1->query); /* Read and check the second one */ - expr2 = read_sql_construct(K_LOOP, - K_BY, - "LOOP", - "SELECT ", - true, - true, - &tok); + expr2 = read_sql_expression2(K_LOOP, K_BY, + "LOOP", + &tok); /* Get the BY clause if any */ if (tok == K_BY) - expr_by = plpgsql_read_expression(K_LOOP, "LOOP"); + expr_by = plpgsql_read_expression(K_LOOP, + "LOOP"); else expr_by = NULL; @@ -1217,18 +1234,15 @@ stmt_raise : K_RAISE lno raise_level raise_msg if (tok == ',') { - PLpgSQL_expr *expr; - int term; - - for (;;) + do { - expr = read_sql_construct(',', ';', ", or ;", - "SELECT ", - true, true, &term); + PLpgSQL_expr *expr; + + expr = read_sql_expression2(',', ';', + ", or ;", + &tok); new->params = lappend(new->params, expr); - if (term == ';') - break; - } + } while (tok == ','); } $$ = (PLpgSQL_stmt *)new; @@ -1307,7 +1321,8 @@ stmt_dynexecute : K_EXECUTE lno PLpgSQL_expr *expr; int endtoken; - expr = read_sql_construct(K_INTO, ';', "INTO|;", + expr = read_sql_construct(K_INTO, K_USING, ';', + "INTO or USING or ;", "SELECT ", true, true, &endtoken); @@ -1319,16 +1334,30 @@ stmt_dynexecute : K_EXECUTE lno new->strict = false; new->rec = NULL; new->row = NULL; + new->params = NIL; /* If we found "INTO", collect the argument */ if (endtoken == K_INTO) { new->into = true; read_into_target(&new->rec, &new->row, &new->strict); - if (yylex() != ';') + endtoken = yylex(); + if (endtoken != ';' && endtoken != K_USING) yyerror("syntax error"); } + /* If we found "USING", collect the argument(s) */ + if (endtoken == K_USING) + { + do + { + expr = read_sql_expression2(',', ';', + ", or ;", + &endtoken); + new->params = lappend(new->params, expr); + } while (endtoken == ','); + } + $$ = (PLpgSQL_stmt *)new; } ; @@ -1485,7 +1514,7 @@ stmt_fetch : K_FETCH lno opt_fetch_direction cursor_variable K_INTO $$ = (PLpgSQL_stmt *)fetch; } ; - + stmt_move : K_MOVE lno opt_fetch_direction cursor_variable ';' { PLpgSQL_stmt_fetch *fetch = $3; @@ -1730,16 +1759,29 @@ assign_expr_param(int dno, int *params, int *nparams) } +/* Convenience routine to read an expression with one possible terminator */ PLpgSQL_expr * plpgsql_read_expression(int until, const char *expected) { - return read_sql_construct(until, 0, expected, "SELECT ", true, true, NULL); + return read_sql_construct(until, 0, 0, expected, + "SELECT ", true, true, NULL); } +/* Convenience routine to read an expression with two possible terminators */ +static PLpgSQL_expr * +read_sql_expression2(int until, int until2, const char *expected, + int *endtoken) +{ + return read_sql_construct(until, until2, 0, expected, + "SELECT ", true, true, endtoken); +} + +/* Convenience routine to read a SQL statement that must end with ';' */ static PLpgSQL_expr * read_sql_stmt(const char *sqlstart) { - return read_sql_construct(';', 0, ";", sqlstart, false, true, NULL); + return read_sql_construct(';', 0, 0, ";", + sqlstart, false, true, NULL); } /* @@ -1747,16 +1789,18 @@ read_sql_stmt(const char *sqlstart) * * until: token code for expected terminator * until2: token code for alternate terminator (pass 0 if none) + * until3: token code for another alternate terminator (pass 0 if none) * expected: text to use in complaining that terminator was not found * sqlstart: text to prefix to the accumulated SQL text * isexpression: whether to say we're reading an "expression" or a "statement" * valid_sql: whether to check the syntax of the expr (prefixed with sqlstart) * endtoken: if not NULL, ending token is stored at *endtoken - * (this is only interesting if until2 isn't zero) + * (this is only interesting if until2 or until3 isn't zero) */ static PLpgSQL_expr * read_sql_construct(int until, int until2, + int until3, const char *expected, const char *sqlstart, bool isexpression, @@ -1783,6 +1827,8 @@ read_sql_construct(int until, break; if (tok == until2 && parenlevel == 0) break; + if (tok == until3 && parenlevel == 0) + break; if (tok == '(' || tok == '[') parenlevel++; else if (tok == ')' || tok == ']') @@ -2066,15 +2112,17 @@ read_fetch_direction(void) else if (pg_strcasecmp(yytext, "absolute") == 0) { fetch->direction = FETCH_ABSOLUTE; - fetch->expr = read_sql_construct(K_FROM, K_IN, "FROM or IN", - "SELECT ", true, true, NULL); + fetch->expr = read_sql_expression2(K_FROM, K_IN, + "FROM or IN", + NULL); check_FROM = false; } else if (pg_strcasecmp(yytext, "relative") == 0) { fetch->direction = FETCH_RELATIVE; - fetch->expr = read_sql_construct(K_FROM, K_IN, "FROM or IN", - "SELECT ", true, true, NULL); + fetch->expr = read_sql_expression2(K_FROM, K_IN, + "FROM or IN", + NULL); check_FROM = false; } else if (pg_strcasecmp(yytext, "forward") == 0) @@ -2088,8 +2136,9 @@ read_fetch_direction(void) else if (tok != T_SCALAR) { plpgsql_push_back_token(tok); - fetch->expr = read_sql_construct(K_FROM, K_IN, "FROM or IN", - "SELECT ", true, true, NULL); + fetch->expr = read_sql_expression2(K_FROM, K_IN, + "FROM or IN", + NULL); check_FROM = false; } else @@ -2233,7 +2282,7 @@ make_return_query_stmt(int lineno) new = palloc0(sizeof(PLpgSQL_stmt_return_query)); new->cmd_type = PLPGSQL_STMT_RETURN_QUERY; new->lineno = lineno; - new->query = read_sql_construct(';', 0, ")", "", false, true, NULL); + new->query = read_sql_stmt(""); return (PLpgSQL_stmt *) new; } diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 931e17d26d..e331b732d3 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.207 2008/03/28 00:21:56 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.208 2008/04/01 03:51:09 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -38,6 +38,15 @@ static const char *const raise_skip_msg = "RAISE"; +typedef struct +{ + int nargs; /* number of arguments */ + Oid *types; /* types of arguments */ + Datum *values; /* evaluated argument values */ + char *nulls; /* null markers (' '/'n' style) */ + bool *freevals; /* which arguments are pfree-able */ +} PreparedParamsData; + /* * All plpgsql function executions within a single transaction share the same * executor EState for evaluating "simple" expressions. Each function call @@ -178,6 +187,9 @@ static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2); static void exec_set_found(PLpgSQL_execstate *estate, bool state); static void plpgsql_create_econtext(PLpgSQL_execstate *estate); static void free_var(PLpgSQL_var *var); +static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate, + List *params); +static void free_params_data(PreparedParamsData *ppd); /* ---------- @@ -2676,9 +2688,21 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate, exec_eval_cleanup(estate); /* - * Call SPI_execute() without preparing a saved plan. + * Execute the query without preparing a saved plan. */ - exec_res = SPI_execute(querystr, estate->readonly_func, 0); + if (stmt->params) + { + PreparedParamsData *ppd; + + ppd = exec_eval_using_params(estate, stmt->params); + exec_res = SPI_execute_with_args(querystr, + ppd->nargs, ppd->types, + ppd->values, ppd->nulls, + estate->readonly_func, 0); + free_params_data(ppd); + } + else + exec_res = SPI_execute(querystr, estate->readonly_func, 0); switch (exec_res) { @@ -2826,7 +2850,6 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt) PLpgSQL_row *row = NULL; SPITupleTable *tuptab; int n; - SPIPlanPtr plan; Portal portal; bool found = false; @@ -2856,19 +2879,35 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt) exec_eval_cleanup(estate); /* - * Prepare a plan and open an implicit cursor for the query + * Open an implicit cursor for the query. We use SPI_cursor_open_with_args + * even when there are no params, because this avoids making and freeing + * one copy of the plan. */ - plan = SPI_prepare(querystr, 0, NULL); - if (plan == NULL) - elog(ERROR, "SPI_prepare failed for \"%s\": %s", - querystr, SPI_result_code_string(SPI_result)); - portal = SPI_cursor_open(NULL, plan, NULL, NULL, - estate->readonly_func); + if (stmt->params) + { + PreparedParamsData *ppd; + + ppd = exec_eval_using_params(estate, stmt->params); + portal = SPI_cursor_open_with_args(NULL, + querystr, + ppd->nargs, ppd->types, + ppd->values, ppd->nulls, + estate->readonly_func, 0); + free_params_data(ppd); + } + else + { + portal = SPI_cursor_open_with_args(NULL, + querystr, + 0, NULL, + NULL, NULL, + estate->readonly_func, 0); + } + if (portal == NULL) elog(ERROR, "could not open implicit cursor for query \"%s\": %s", querystr, SPI_result_code_string(SPI_result)); pfree(querystr); - SPI_freeplan(plan); /* * Fetch the initial 10 tuples @@ -5069,3 +5108,79 @@ free_var(PLpgSQL_var *var) var->freeval = false; } } + +/* + * exec_eval_using_params --- evaluate params of USING clause + */ +static PreparedParamsData * +exec_eval_using_params(PLpgSQL_execstate *estate, List *params) +{ + PreparedParamsData *ppd; + int nargs; + int i; + ListCell *lc; + + ppd = (PreparedParamsData *) palloc(sizeof(PreparedParamsData)); + nargs = list_length(params); + + ppd->nargs = nargs; + ppd->types = (Oid *) palloc(nargs * sizeof(Oid)); + ppd->values = (Datum *) palloc(nargs * sizeof(Datum)); + ppd->nulls = (char *) palloc(nargs * sizeof(char)); + ppd->freevals = (bool *) palloc(nargs * sizeof(bool)); + + i = 0; + foreach(lc, params) + { + PLpgSQL_expr *param = (PLpgSQL_expr *) lfirst(lc); + bool isnull; + + ppd->values[i] = exec_eval_expr(estate, param, + &isnull, + &ppd->types[i]); + ppd->nulls[i] = isnull ? 'n' : ' '; + ppd->freevals[i] = false; + + /* pass-by-ref non null values must be copied into plpgsql context */ + if (!isnull) + { + int16 typLen; + bool typByVal; + + get_typlenbyval(ppd->types[i], &typLen, &typByVal); + if (!typByVal) + { + ppd->values[i] = datumCopy(ppd->values[i], typByVal, typLen); + ppd->freevals[i] = true; + } + } + + exec_eval_cleanup(estate); + + i++; + } + + return ppd; +} + +/* + * free_params_data --- pfree all pass-by-reference values used in USING clause + */ +static void +free_params_data(PreparedParamsData *ppd) +{ + int i; + + for (i = 0; i < ppd->nargs; i++) + { + if (ppd->freevals[i]) + pfree(DatumGetPointer(ppd->values[i])); + } + + pfree(ppd->types); + pfree(ppd->values); + pfree(ppd->nulls); + pfree(ppd->freevals); + + pfree(ppd); +} diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index dd2841e10e..be57154e40 100644 --- a/src/pl/plpgsql/src/pl_funcs.c +++ b/src/pl/plpgsql/src/pl_funcs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.67 2008/01/01 19:46:00 momjian Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.68 2008/04/01 03:51:09 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1007,6 +1007,24 @@ dump_dynexecute(PLpgSQL_stmt_dynexecute *stmt) stmt->strict ? " STRICT" : "", stmt->row->rowno, stmt->row->refname); } + if (stmt->params != NIL) + { + ListCell *lc; + int i; + + dump_ind(); + printf(" USING\n"); + dump_indent += 2; + i = 1; + foreach(lc, stmt->params) + { + dump_ind(); + printf(" parameter %d: ", i++); + dump_expr((PLpgSQL_expr *) lfirst(lc)); + printf("\n"); + } + dump_indent -= 2; + } dump_indent -= 2; } @@ -1014,12 +1032,30 @@ static void dump_dynfors(PLpgSQL_stmt_dynfors *stmt) { dump_ind(); - printf("FORS %s EXECUTE ", (stmt->rec != NULL) ? stmt->rec->refname : stmt->row->refname); + printf("FORS %s EXECUTE ", + (stmt->rec != NULL) ? stmt->rec->refname : stmt->row->refname); dump_expr(stmt->query); printf("\n"); + if (stmt->params != NIL) + { + ListCell *lc; + int i; + dump_indent += 2; + dump_ind(); + printf(" USING\n"); + dump_indent += 2; + i = 1; + foreach(lc, stmt->params) + { + dump_ind(); + printf(" parameter $%d: ", i++); + dump_expr((PLpgSQL_expr *) lfirst(lc)); + printf("\n"); + } + dump_indent -= 4; + } dump_stmts(stmt->body); - dump_ind(); printf(" ENDFORS\n"); } diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 36e5b07586..66588b343f 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.95 2008/01/01 19:46:00 momjian Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.96 2008/04/01 03:51:09 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -430,6 +430,7 @@ typedef struct PLpgSQL_row *row; PLpgSQL_expr *query; List *body; /* List of statements */ + List *params; /* USING expressions */ } PLpgSQL_stmt_dynfors; @@ -534,6 +535,7 @@ typedef struct bool strict; /* INTO STRICT flag */ PLpgSQL_rec *rec; /* INTO target, if record */ PLpgSQL_row *row; /* INTO target, if row */ + List *params; /* USING expressions */ } PLpgSQL_stmt_dynexecute; diff --git a/src/pl/plpgsql/src/scan.l b/src/pl/plpgsql/src/scan.l index 12d5b44bcf..8de29117c5 100644 --- a/src/pl/plpgsql/src/scan.l +++ b/src/pl/plpgsql/src/scan.l @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.60 2008/01/01 19:46:00 momjian Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.61 2008/04/01 03:51:09 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -161,6 +161,7 @@ strict { return K_STRICT; } then { return K_THEN; } to { return K_TO; } type { return K_TYPE; } +using { return K_USING; } warning { return K_WARNING; } when { return K_WHEN; } while { return K_WHILE; } @@ -328,7 +329,7 @@ dump { return O_DUMP; } } {dolqinside} { } . { /* needed for $ inside the quoted text */ } -<> { +<> { plpgsql_error_lineno = start_lineno; ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), @@ -502,7 +503,7 @@ plpgsql_scanner_finish(void) * scenarios there's no need to get the decoded value.) * * Note: we expect the literal to be the most recently lexed token. This - * would not work well if we supported multiple-token pushback or if + * would not work well if we supported multiple-token pushback or if * plpgsql_yylex() wanted to read ahead beyond a T_STRING token. */ char * diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index 03204b66e6..bcf974483e 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -3128,3 +3128,26 @@ select * from ret_query2(8); c9f0f895fb98ab9159f51fd0297e236d | 8 | t (9 rows) +-- test EXECUTE USING +create function exc_using(int, text) returns int as $$ +declare i int; +begin + for i in execute 'select * from generate_series(1,$1)' using $1+1 loop + raise notice '%', i; + end loop; + execute 'select $2 + $2*3 + length($1)' into i using $2,$1; + return i; +end +$$ language plpgsql; +select exc_using(5, 'foobar'); +NOTICE: 1 +NOTICE: 2 +NOTICE: 3 +NOTICE: 4 +NOTICE: 5 +NOTICE: 6 + exc_using +----------- + 26 +(1 row) + diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index 3c7459b2b7..2a93ffc611 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -2580,4 +2580,18 @@ begin end; $$ language plpgsql; -select * from ret_query2(8); \ No newline at end of file +select * from ret_query2(8); + +-- test EXECUTE USING +create function exc_using(int, text) returns int as $$ +declare i int; +begin + for i in execute 'select * from generate_series(1,$1)' using $1+1 loop + raise notice '%', i; + end loop; + execute 'select $2 + $2*3 + length($1)' into i using $2,$1; + return i; +end +$$ language plpgsql; + +select exc_using(5, 'foobar');