diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index 8eac671fab..1065eddc74 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 @@ -1467,6 +1467,7 @@ RETURN expression; RETURN NEXT expression; RETURN QUERY query; +RETURN QUERY EXECUTE command-string USING expression , ... ; @@ -1500,6 +1501,14 @@ RETURN QUERY query; let control reach the end of the function). + + RETURN QUERY has a variant + RETURN QUERY EXECUTE, which specifies the + query to be executed dynamically. Parameter expressions can + be inserted into the computed query string via USING, + in just the same way as in the EXECUTE command. + + If you declared the function with output parameters, write just RETURN NEXT with no expression. On each @@ -1544,7 +1553,6 @@ SELECT * FROM getallfoo(); Note that functions using RETURN NEXT or RETURN QUERY must be called as a table source in a FROM clause. - diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y index 495b625a45..979ebee3ca 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.110 2008/04/06 23:43:29 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.111 2008/05/03 00:11:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2239,6 +2239,7 @@ static PLpgSQL_stmt * make_return_query_stmt(int lineno) { PLpgSQL_stmt_return_query *new; + int tok; if (!plpgsql_curr_compile->fn_retset) yyerror("cannot use RETURN QUERY in a non-SETOF function"); @@ -2246,7 +2247,32 @@ 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_stmt(""); + + /* check for RETURN QUERY EXECUTE */ + if ((tok = yylex()) != K_EXECUTE) + { + /* ordinary static query */ + plpgsql_push_back_token(tok); + new->query = read_sql_stmt(""); + } + else + { + /* dynamic SQL */ + int term; + + new->dynquery = read_sql_expression2(';', K_USING, "; or USING", + &term); + if (term == K_USING) + { + do + { + PLpgSQL_expr *expr; + + expr = read_sql_expression2(',', ';', ", or ;", &term); + new->params = lappend(new->params, expr); + } while (term == ','); + } + } return (PLpgSQL_stmt *) new; } diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index a1774bf503..64845f2965 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.210 2008/04/17 21:37:28 alvherre Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.211 2008/05/03 00:11:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -197,6 +197,8 @@ static void assign_text_var(PLpgSQL_var *var, const char *str); static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate, List *params); static void free_params_data(PreparedParamsData *ppd); +static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate, + PLpgSQL_expr *query, List *params); /* ---------- @@ -1968,7 +1970,7 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt) PLpgSQL_row *row = (PLpgSQL_row *) retvar; Assert(row->rowtupdesc); - estate->retval = + estate->retval = PointerGetDatum(make_tuple_from_row(estate, row, row->rowtupdesc)); if (DatumGetPointer(estate->retval) == NULL) /* should not happen */ @@ -2189,7 +2191,18 @@ exec_stmt_return_query(PLpgSQL_execstate *estate, if (estate->tuple_store == NULL) exec_init_tuple_store(estate); - exec_run_select(estate, stmt->query, 0, &portal); + if (stmt->query != NULL) + { + /* static query */ + exec_run_select(estate, stmt->query, 0, &portal); + } + else + { + /* RETURN QUERY EXECUTE */ + Assert(stmt->dynquery != NULL); + portal = exec_dynquery_with_params(estate, stmt->dynquery, + stmt->params); + } if (!compatible_tupdesc(estate->rettupdesc, portal->tupDesc)) ereport(ERROR, @@ -2841,58 +2854,10 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate, static int exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt) { - Datum query; - bool isnull; - Oid restype; - char *querystr; Portal portal; int rc; - /* - * Evaluate the string expression after the EXECUTE keyword. It's result - * is the querystring we have to execute. - */ - query = exec_eval_expr(estate, stmt->query, &isnull, &restype); - if (isnull) - ereport(ERROR, - (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("cannot EXECUTE a null querystring"))); - - /* Get the C-String representation */ - querystr = convert_value_to_string(query, restype); - - exec_eval_cleanup(estate); - - /* - * 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. - */ - 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); + portal = exec_dynquery_with_params(estate, stmt->query, stmt->params); /* * Execute the loop @@ -5208,3 +5173,65 @@ free_params_data(PreparedParamsData *ppd) pfree(ppd); } + +/* + * Open portal for dynamic query + */ +static Portal +exec_dynquery_with_params(PLpgSQL_execstate *estate, PLpgSQL_expr *dynquery, + List *params) +{ + Portal portal; + Datum query; + bool isnull; + Oid restype; + char *querystr; + + /* + * Evaluate the string expression after the EXECUTE keyword. Its result + * is the querystring we have to execute. + */ + query = exec_eval_expr(estate, dynquery, &isnull, &restype); + if (isnull) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("cannot EXECUTE a null querystring"))); + + /* Get the C-String representation */ + querystr = convert_value_to_string(query, restype); + + exec_eval_cleanup(estate); + + /* + * 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. + */ + if (params) + { + PreparedParamsData *ppd; + + ppd = exec_eval_using_params(estate, 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); + + return portal; +} diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index 1a68c9339f..f7624a6c33 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.69 2008/04/06 23:43:29 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.70 2008/05/03 00:11:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -963,9 +963,37 @@ static void dump_return_query(PLpgSQL_stmt_return_query *stmt) { dump_ind(); - printf("RETURN QUERY "); - dump_expr(stmt->query); - printf("\n"); + if (stmt->query) + { + printf("RETURN QUERY "); + dump_expr(stmt->query); + printf("\n"); + } + else + { + printf("RETURN QUERY EXECUTE "); + dump_expr(stmt->dynquery); + 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; + } + } } static void diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index a50d5721f4..f1206932bb 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.97 2008/04/06 23:43:29 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.98 2008/05/03 00:11:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -529,7 +529,9 @@ typedef struct { /* RETURN QUERY statement */ int cmd_type; int lineno; - PLpgSQL_expr *query; + PLpgSQL_expr *query; /* if static query */ + PLpgSQL_expr *dynquery; /* if dynamic query (RETURN QUERY EXECUTE) */ + List *params; /* USING arguments for dynamic query */ } PLpgSQL_stmt_return_query; typedef struct diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index 018c8c2b50..da987b22e4 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -3267,3 +3267,21 @@ end; $$ language plpgsql; ERROR: cursor FOR loop must use a bound cursor variable CONTEXT: compile of PL/pgSQL function "forc_bad" near line 4 +-- return query execute +create or replace function return_dquery() +returns setof int as $$ +begin + return query execute 'select * from (values(10),(20)) f'; + return query execute 'select * from (values($1),($2)) f' using 40,50; +end; +$$ language plpgsql; +select * from return_dquery(); + return_dquery +--------------- + 10 + 20 + 40 + 50 +(4 rows) + +drop function return_dquery(); diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index 066ccbeba6..b0799dcdc7 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -2669,3 +2669,17 @@ begin end loop; end; $$ language plpgsql; + +-- return query execute + +create or replace function return_dquery() +returns setof int as $$ +begin + return query execute 'select * from (values(10),(20)) f'; + return query execute 'select * from (values($1),($2)) f' using 40,50; +end; +$$ language plpgsql; + +select * from return_dquery(); + +drop function return_dquery();