Support RETURN QUERY EXECUTE in plpgsql.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 3 May 2008 00:11:36 +0000 (00:11 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 3 May 2008 00:11:36 +0000 (00:11 +0000)
Pavel Stehule

doc/src/sgml/plpgsql.sgml
src/pl/plpgsql/src/gram.y
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/pl_funcs.c
src/pl/plpgsql/src/plpgsql.h
src/test/regress/expected/plpgsql.out
src/test/regress/sql/plpgsql.sql

index 77302de2592eea68fb1f49a5d3961a5e92e6f647..f533e79061c13deee1195dc7062bbdd8ac4e6da6 100644 (file)
@@ -1467,6 +1467,7 @@ RETURN <replaceable>expression</replaceable>;
 <synopsis>
 RETURN NEXT <replaceable>expression</replaceable>;
 RETURN QUERY <replaceable>query</replaceable>;
+RETURN QUERY EXECUTE <replaceable class="command">command-string</replaceable> <optional> USING <replaceable>expression</replaceable> <optional>, ...</optional> </optional>;
 </synopsis>
 
      <para>
@@ -1500,6 +1501,14 @@ RETURN QUERY <replaceable>query</replaceable>;
       let control reach the end of the function).
      </para>
 
+     <para>
+      <command>RETURN QUERY</command> has a variant
+      <command>RETURN QUERY EXECUTE</command>, which specifies the
+      query to be executed dynamically.  Parameter expressions can
+      be inserted into the computed query string via <literal>USING</>,
+      in just the same way as in the <command>EXECUTE</> command.
+     </para>
+
      <para>
       If you declared the function with output parameters, write just
       <command>RETURN NEXT</command> with no expression.  On each
@@ -1544,7 +1553,6 @@ SELECT * FROM getallfoo();
       Note that functions using <command>RETURN NEXT</command> or
       <command>RETURN QUERY</command> must be called as a table source in
       a <literal>FROM</literal> clause.
-
      </para>
 
      <note>
index f6acbc6b9622b41c3484c59bfd0cdded1cbbdab3..4213d1e168c21a47f2f05711500a169d706d850a 100644 (file)
@@ -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;
 }
index f945cabd15f8774fc87c99c89581c5194b8782fc..d8cd96623015ec7e01397591f1e19edea960aceb 100644 (file)
@@ -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;
+}
index b217da80e80248c5d732505337a5df4fe1ac4a98..3bb0090fcdf66a8a1ac407758c1a4984d55923e6 100644 (file)
@@ -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
index 302fa71ad4ed4e242aba462e12434d8845061d43..26ec5ef07af056fa7c3d895d9edb584e3dff8459 100644 (file)
@@ -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
index 018c8c2b50f974ade5fc0a1f409e8d9c6b7d7667..da987b22e460218f406c4ee5982d30f3449b018a 100644 (file)
@@ -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();
index 066ccbeba639bcfebe29a4a893956e2f476cc099..b0799dcdc705d53acdd5ad99570cea7dc869a04a 100644 (file)
@@ -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();