Add support for an optional INTO clause to PL/PgSQL's EXECUTE command.
authorNeil Conway
Tue, 7 Jun 2005 02:47:23 +0000 (02:47 +0000)
committerNeil Conway
Tue, 7 Jun 2005 02:47:23 +0000 (02:47 +0000)
This allows the result of executing a SELECT to be assigned to a row
variable, record variable, or list of scalars. Docs and regression tests
updated. Per Pavel Stehule, improvements and cleanup by Neil Conway.

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

index 7d2b37e41d7b801b20e2e5c41a0544bef14df2ed..d96c123e21c0ec87c0de013c7e3b254f61539a00 100644 (file)
@@ -1,5 +1,5 @@
 
 
  
@@ -1251,13 +1251,14 @@ NULL;
      EXECUTE statement is provided:
 
 
-EXECUTE command-string;
+EXECUTE command-string [ INTO target ];
 
 
      where command-string is an expression
-     yielding a string (of type
-     text) containing the command
-     to be executed.  This string is fed literally to the SQL engine.
+     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.
     
 
     
@@ -1276,16 +1277,22 @@ EXECUTE command-string;
     
 
     
-     The results from SELECT commands are discarded
-     by EXECUTE, and SELECT INTO
-     is not currently supported within EXECUTE.
-     So there is no way to extract a result from a dynamically-created
-     SELECT using the plain EXECUTE
-     command.  There are two other ways to do it, however: one is to use the
-     FOR-IN-EXECUTE
-     loop form described in ,
-     and the other is to use a cursor with OPEN-FOR-EXECUTE, as
-     described in .
+     The INTO clause specifies where the results of
+     a SELECT command should be assigned. If a row
+     or variable list is provided, it must exactly match the structure
+     of the results produced by the SELECT (when a
+     record variable is used, it will configure itself to match the
+     result's structure automatically). If multiple rows are returned,
+     only the first will be assigned to the INTO
+     variable. If no rows are returned, NULL is assigned to the
+     INTO variable. If no INTO
+     clause is specified, the results of a SELECT
+     command are discarded.
+    
+
+    
+     SELECT INTO is not currently supported within
+     EXECUTE.
     
 
     
@@ -1364,7 +1371,7 @@ EXECUTE 'UPDATE tbl SET '
      command, which has the form:
 
 
-GET DIAGNOSTICS variable = item  , ...  ;
+GET DIAGNOSTICS variable = item  , ... ;
 
 
      This command allows retrieval of system status indicators.  Each
@@ -2173,7 +2180,7 @@ SELECT merge_db (1, 'dennis');
      Another way is to use the cursor declaration syntax,
      which in general is:
 
-name CURSOR  ( arguments )  FOR query ;
+name CURSOR  ( arguments )  FOR query;
 
      (FOR may be replaced by IS for
      Oracle compatibility.)
@@ -2218,7 +2225,7 @@ DECLARE
      <command>OPEN FOR</command> <replaceable>query</replaceable>
 
 
-OPEN unbound_cursor FOR query ;
+OPEN unbound_cursor FOR query;
 
 
        
@@ -3188,7 +3195,7 @@ DECLARE
     func_body text;
     func_cmd text;
 BEGIN 
-    func_body := 'BEGIN' ;
+    func_body := 'BEGIN';
 
     -- Notice how we scan through the results of a query in a FOR loop
     -- using the FOR <record> construct.
index 028323192fa394a3987afc08a182d4428afee8de..d1823d7a091ad2ce614a1dad5641903fc8c1bac1 100644 (file)
@@ -4,7 +4,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.72 2005/05/26 04:08:31 momjian Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.73 2005/06/07 02:47:16 neilc Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -1250,19 +1250,62 @@ stmt_execsql    : execsql_start lno
                    }
                ;
 
-stmt_dynexecute : K_EXECUTE lno expr_until_semi
+stmt_dynexecute : K_EXECUTE lno 
                    {
                        PLpgSQL_stmt_dynexecute *new;
+                       PLpgSQL_expr *expr;
+                       int endtoken;
+
+                       expr = read_sql_construct(K_INTO, ';', "INTO|;", "SELECT ",
+                                                 true, true, &endtoken);
 
                        new = palloc(sizeof(PLpgSQL_stmt_dynexecute));
                        new->cmd_type = PLPGSQL_STMT_DYNEXECUTE;
                        new->lineno   = $2;
-                       new->query    = $3;
+                       new->query    = expr;
+
+                       new->rec = NULL;
+                       new->row = NULL;
+
+                       /*
+                        * If we saw "INTO", look for an additional
+                        * row or record var.
+                        */
+                       if (endtoken == K_INTO)
+                       {
+                           switch (yylex())
+                           {
+                               case T_ROW:
+                                   check_assignable((PLpgSQL_datum *) yylval.row);
+                                   new->row = yylval.row;
+                                   break;
+
+                               case T_RECORD:
+                                   check_assignable((PLpgSQL_datum *) yylval.row);
+                                   new->rec = yylval.rec;
+                                   break;
+
+                               case T_SCALAR:
+                                   new->row = read_into_scalar_list(yytext, yylval.scalar);
+                                   break;
+
+                               default:
+                                   plpgsql_error_lineno = $2;
+                                   ereport(ERROR,
+                                           (errcode(ERRCODE_SYNTAX_ERROR),
+                                            errmsg("syntax error at \"%s\"",
+                                                   yytext),
+                                            errdetail("Expected record or row variable.")));
+                           }
+                           if (yylex() != ';')
+                               yyerror("syntax error");
+                       }
 
                        $$ = (PLpgSQL_stmt *)new;
                    }
                ;
 
+
 stmt_open      : K_OPEN lno cursor_varptr
                    {
                        PLpgSQL_stmt_open *new;
index 3c6216a3e56c797ad197030bd0cd0cce618f3428..1fe1ed4cbedf38612d3b40b39a1a0b5339849b50 100644 (file)
@@ -3,7 +3,7 @@
  *           procedural language
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.141 2005/05/26 04:08:31 momjian Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.142 2005/06/07 02:47:17 neilc Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -2202,6 +2202,13 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
    Oid         restype;
    char       *querystr;
    int         exec_res;
+   PLpgSQL_rec *rec = NULL;
+   PLpgSQL_row *row = NULL;
+
+   if (stmt->rec != NULL)
+       rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
+   else if (stmt->row != NULL)
+       row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
 
    /*
     * First we evaluate the string expression after the EXECUTE keyword.
@@ -2221,9 +2228,27 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
    /*
     * Call SPI_execute() without preparing a saved plan. The returncode can
     * be any standard OK.  Note that while a SELECT is allowed, its
-    * results will be discarded.
+    * results will be discarded unless an INTO clause is specified.
     */
    exec_res = SPI_execute(querystr, estate->readonly_func, 0);
+
+   /* Assign to INTO variable */
+   if (rec || row)
+   {
+       if (exec_res != SPI_OK_SELECT)
+           ereport(ERROR,
+                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                    errmsg("EXECUTE ... INTO is only for SELECT")));
+       else
+       {
+           if (SPI_processed == 0)
+               exec_move_row(estate, rec, row, NULL, SPI_tuptable->tupdesc);
+           else
+               exec_move_row(estate, rec, row,
+                             SPI_tuptable->vals[0], SPI_tuptable->tupdesc);
+       }
+   }
+
    switch (exec_res)
    {
        case SPI_OK_SELECT:
index 17a466e01e8cd5b4b48de63c7c21f4dba8fd78c5..a70553ac2c8a2e62f1de10e33405af990cbf457d 100644 (file)
@@ -3,7 +3,7 @@
  *           procedural language
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.60 2005/05/26 04:08:31 momjian Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.61 2005/06/07 02:47:18 neilc Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -524,6 +524,8 @@ typedef struct
 {                              /* Dynamic SQL string to execute */
    int         cmd_type;
    int         lineno;
+   PLpgSQL_rec *rec;                   /* INTO record or row variable */
+   PLpgSQL_row *row;
    PLpgSQL_expr *query;
 } PLpgSQL_stmt_dynexecute;
 
index 08fbe46b3a240f4932ff382d3370ed20f44e580d..39e61e09cf26d94a5e92a85f0c48dcb4b2658c68 100644 (file)
@@ -2380,3 +2380,38 @@ ERROR:  control reached end of function without RETURN
 CONTEXT:  PL/pgSQL function "missing_return_expr"
 drop function void_return_expr();
 drop function missing_return_expr();
+--
+-- EXECUTE ... INTO test
+--
+create table eifoo (i integer, y integer);
+create type eitype as (i integer, y integer);
+create or replace function execute_into_test(varchar) returns record as $$
+declare
+    _r record;
+    _rt eifoo%rowtype;
+    _v eitype;
+    i int;
+    j int;
+    k int;
+begin
+    execute 'insert into '||$1||' values(10,15)';
+    execute 'select (row).* from (select row(10,1)::eifoo) s' into _r;
+    raise notice '% %', _r.i, _r.y;
+    execute 'select * from '||$1||' limit 1' into _rt;
+    raise notice '% %', _rt.i, _rt.y;
+    execute 'select *, 20 from '||$1||' limit 1' into i, j, k;
+    raise notice '% % %', i, j, k;
+    execute 'select 1,2' into _v;
+    return _v;
+end; $$ language plpgsql;
+select execute_into_test('eifoo');
+NOTICE:  10 1
+NOTICE:  10 15
+NOTICE:  10 15 20
+ execute_into_test 
+-------------------
+ (1,2)
+(1 row)
+
+drop table eifoo cascade;
+drop type eitype cascade;
index 7ea7c8c6e0c558243f82b891059b0a77a848debf..314f69915fc61dfecc5963666cf7c7c7083773d5 100644 (file)
@@ -2018,3 +2018,35 @@ select missing_return_expr();
 
 drop function void_return_expr();
 drop function missing_return_expr();
+
+--
+-- EXECUTE ... INTO test
+--
+
+create table eifoo (i integer, y integer);
+create type eitype as (i integer, y integer);
+
+create or replace function execute_into_test(varchar) returns record as $$
+declare
+    _r record;
+    _rt eifoo%rowtype;
+    _v eitype;
+    i int;
+    j int;
+    k int;
+begin
+    execute 'insert into '||$1||' values(10,15)';
+    execute 'select (row).* from (select row(10,1)::eifoo) s' into _r;
+    raise notice '% %', _r.i, _r.y;
+    execute 'select * from '||$1||' limit 1' into _rt;
+    raise notice '% %', _rt.i, _rt.y;
+    execute 'select *, 20 from '||$1||' limit 1' into i, j, k;
+    raise notice '% % %', i, j, k;
+    execute 'select 1,2' into _v;
+    return _v;
+end; $$ language plpgsql;
+
+select execute_into_test('eifoo');
+
+drop table eifoo cascade;
+drop type eitype cascade;