This patch makes a minor cleanup to the implementation of PERFORM in
authorBruce Momjian
Sun, 10 Nov 2002 00:35:58 +0000 (00:35 +0000)
committerBruce Momjian
Sun, 10 Nov 2002 00:35:58 +0000 (00:35 +0000)
PL/PgSQL. Previously, it had been bundled together with the assign
statement implementation, for some reason that wasn't clear to me
(they certainly don't share any code with one another). So I separated
them and made PERFORM a statement like any other. No changes in
functionality.

Along the way, I added some regression tests for PERFORM, added a
bunch more SGML tags to the PL/PgSQL docs, and removed an obsolete
comment relating to the implementation of RETURN NEXT.

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 2e7f2a9e5831150aa0194bae7e8e2868db331ac1..80764df092e8aa190767621b6c8585df6ecce665 100644 (file)
@@ -1,5 +1,5 @@
 
 
  
@@ -102,7 +102,7 @@ END;
 
     If you execute the above function, it will reference the OID for
     my_function() in the query plan produced for
-    the PERFORM statement. Later, if you
+    the PERFORM statement. Later, if you
     drop and re-create my_function(), then
     populate() will not be able to find
     my_function() anymore. You would then have to
@@ -117,17 +117,19 @@ END;
    same tables and fields on every execution; that is, you cannot use
    a parameter as the name of a table or field in a query.  To get
    around this restriction, you can construct dynamic queries using
-   the PL/pgSQL EXECUTE statement --- at
-   the price of constructing a new query plan on every execution.
+   the PL/pgSQL EXECUTE
+   statement --- at the price of constructing a new query plan on
+   every execution.
    
 
    
    
-    The PL/pgSQL EXECUTE statement is not
-    related to the EXECUTE statement supported by the
+    The PL/pgSQL
+    EXECUTE statement is not related to the
+    EXECUTE statement supported by the
     PostgreSQL backend. The backend
-    EXECUTE statement cannot be used within PL/pgSQL functions (and
-    is not needed).
+    EXECUTE statement cannot be used within
+    PL/pgSQL functions (and is not needed).
    
    
 
@@ -173,13 +175,12 @@ END;
     
 
     
-     That means that your client application must send each
-     query to the database server, wait for it to process it,
-     receive the results, do some computation, then send
-     other queries to the server. All this incurs inter-process communication
-     and may also incur network
-     overhead if your client is on a different machine than
-     the database server.
+     That means that your client application must send each query to
+     the database server, wait for it to process it, receive the
+     results, do some computation, then send other queries to the
+     server. All this incurs inter-process communication and may also
+     incur network overhead if your client is on a different machine
+     than the database server.
     
 
     
@@ -753,14 +754,14 @@ CREATE FUNCTION logfunc2 (TEXT) RETURNS TIMESTAMP AS '
 
     
      The mutable nature of record variables presents a problem in this
-     connection.  When fields of a record variable are used in expressions or
-     statements, the data types of the
-     fields must not change between calls of one and the same expression,
-     since the expression will be planned using the data type that is present
-     when the expression is first reached.
-     Keep this in mind when writing trigger procedures that handle events
-     for more than one table.  (EXECUTE can be used to get around this
-     problem when necessary.)
+     connection.  When fields of a record variable are used in
+     expressions or statements, the data types of the fields must not
+     change between calls of one and the same expression, since the
+     expression will be planned using the data type that is present
+     when the expression is first reached.  Keep this in mind when
+     writing trigger procedures that handle events for more than one
+     table.  (EXECUTE can be used to get around
+     this problem when necessary.)
     
   
 
@@ -904,10 +905,11 @@ END;
     Executing an expression or query with no result
 
     
-     Sometimes one wishes to evaluate an expression or query but discard
-     the result (typically because one is calling a function that has
-     useful side-effects but no useful result value).  To do this in
-     PL/pgSQL, use the PERFORM statement:
+     Sometimes one wishes to evaluate an expression or query but
+     discard the result (typically because one is calling a function
+     that has useful side-effects but no useful result value).  To do
+     this in PL/pgSQL, use the
+     PERFORM statement:
 
 
 PERFORM query;
@@ -922,11 +924,12 @@ PERFORM query;
     
 
     
-    
-     One might expect that SELECT with no INTO clause would accomplish
-     this result, but at present the only accepted way to do it is PERFORM.
-    
-    
+    
+     One might expect that SELECT with no INTO
+     clause would accomplish this result, but at present the only
+     accepted way to do it is PERFORM.
+    
+   
 
     
      An example:
@@ -940,13 +943,13 @@ PERFORM create_mv(''cs_session_page_requests_mv'', my_query);
     Executing dynamic queries
     
     
-     Oftentimes you will want to generate dynamic queries inside
-     your PL/pgSQL functions, that is,
-     queries that will involve different tables or different data types
-     each time they are executed.  PL/pgSQL's
+     Oftentimes you will want to generate dynamic queries inside your
+     PL/pgSQL functions, that is, queries
+     that will involve different tables or different data types each
+     time they are executed.  PL/pgSQL's
      normal attempts to cache plans for queries will not work in such
-     scenarios.  To handle this sort of problem, the EXECUTE statement
-     is provided:
+     scenarios.  To handle this sort of problem, the
+     EXECUTE statement is provided:
 
 
 EXECUTE query-string;
@@ -973,20 +976,22 @@ EXECUTE query-string;
      
     
      Unlike all other queries in PL/pgSQL, a
-     query run by an EXECUTE statement is
-     not prepared and saved just once during the life of the server.
-     Instead, the query is prepared each
-     time the statement is run. The
-     query-string can be dynamically
-     created within the procedure to perform actions on variable
-     tables and fields.
+     query run by an
+     EXECUTE statement is not prepared and saved
+     just once during the life of the server.  Instead, the
+     query is prepared each time the
+     statement is run. The query-string can
+     be dynamically created within the procedure to perform actions on
+     variable tables and fields.
     
   
     
-     The results from SELECT queries are discarded by EXECUTE, and
-     SELECT INTO is not currently supported within EXECUTE.  So, the
-     only way to extract a result from a dynamically-created SELECT is
-     to use the FOR-IN-EXECUTE form described later.
+     The results from SELECT queries are discarded
+     by EXECUTE, and SELECT INTO
+     is not currently supported within EXECUTE.
+     So, the only way to extract a result from a dynamically-created
+     SELECT is to use the FOR-IN-EXECUTE form
+     described later.
     
 
     
@@ -1017,7 +1022,8 @@ EXECUTE ''UPDATE tbl SET ''
     
 
     
-     Here is a much larger example of a dynamic query and EXECUTE:
+     Here is a much larger example of a dynamic query and
+     EXECUTE:
 
 CREATE FUNCTION cs_update_referrer_type_proc() RETURNS INTEGER AS '
 DECLARE
@@ -1159,9 +1165,9 @@ GET DIAGNOSTICS variable = item
 RETURN expression;
 
 
-     RETURN with an expression is used to return from a
-     PL/pgSQL function that does not return a set.
-     The function terminates and the value of
+     RETURN with an expression is used to return
+     from a PL/pgSQL function that does not return a
+     set.  The function terminates and the value of
      expression is returned to the caller.
     
 
@@ -1176,22 +1182,24 @@ RETURN expression;
     
 
     
-     The return value of a function cannot be left undefined. If control
-     reaches the end of the top-level block of 
-     the function without hitting a RETURN statement, a run-time error
-     will occur.
+     The return value of a function cannot be left undefined. If
+     control reaches the end of the top-level block of the function
+     without hitting a RETURN statement, a run-time
+     error will occur.
     
 
     
      When a PL/pgSQL function is declared to return
      SETOF sometype, the procedure
      to follow is slightly different.  In that case, the individual
-     items to return are specified in RETURN NEXT commands, and then a
-     final RETURN command with no arguments is used to indicate that
-     the function has finished executing.  RETURN NEXT can be used with
-     both scalar and composite data types; in the later case, an
-     entire "table" of results will be returned.  Functions that use
-     RETURN NEXT should be called in the following fashion:
+     items to return are specified in RETURN NEXT
+     commands, and then a final RETURN command with
+     no arguments is used to indicate that the function has finished
+     executing.  RETURN NEXT can be used with both
+     scalar and composite data types; in the later case, an entire
+     "table" of results will be returned.  Functions that use
+     RETURN NEXT should be called in the following
+     fashion:
 
 
 SELECT * FROM some_func();
@@ -1203,19 +1211,19 @@ SELECT * FROM some_func();
 RETURN NEXT expression;
 
 
-     RETURN NEXT does not actually return from the function; it simply
-     saves away the value of the expression (or record or row variable,
-     as appropriate for the data type being returned).
-     Execution then continues with the next statement in the
-     PL/pgSQL function.  As successive RETURN NEXT
-     commands are executed, the result set is built up.  A final
-     RETURN, which need have no argument, causes control to exit
-     the function.
+     RETURN NEXT does not actually return from the
+     function; it simply saves away the value of the expression (or
+     record or row variable, as appropriate for the data type being
+     returned).  Execution then continues with the next statement in
+     the PL/pgSQL function.  As successive
+     RETURN NEXT commands are executed, the result
+     set is built up.  A final RETURN, which need
+     have no argument, causes control to exit the function.
     
 
    
     
-     The current implementation of RETURN NEXT for
+     The current implementation of RETURN NEXT for
      PL/pgSQL stores the entire result set before
      returning from the function, as discussed above.  That means that
      if a PL/pgSQL function produces a very large result set,
@@ -1586,12 +1594,12 @@ FOR record | row IN EXECUTE text_express
     statements
 END LOOP;
 
-     This is like the previous form, except that the source SELECT
-     statement is specified as a string expression, which is evaluated
-     and re-planned on each entry to the FOR loop.  This allows the
-     programmer to choose the speed of a pre-planned query or the
-     flexibility of a dynamic query, just as with a plain EXECUTE
-     statement.
+     This is like the previous form, except that the source
+     SELECT statement is specified as a string
+     expression, which is evaluated and re-planned on each entry to
+     the FOR loop.  This allows the programmer to choose the speed of
+     a pre-planned query or the flexibility of a dynamic query, just
+     as with a plain EXECUTE statement.
     
 
     
@@ -1700,18 +1708,18 @@ OPEN curs1 FOR SELECT * FROM foo WHERE key = mykey;
     
      OPEN FOR EXECUTE
 
-       
+    
 
 OPEN unbound-cursor FOR EXECUTE query-string;
 
 
-        The cursor variable is opened and given the specified query
-   to execute.  The cursor cannot be open already, and it must
-   have been declared as an unbound cursor (that is, as a simple
-   refcursor variable).  The query is specified as a
-   string expression in the same way as in the EXECUTE command.
-   As usual, this gives flexibility so the query can vary
-   from one run to the next.
+     The cursor variable is opened and given the specified query to
+     execute.  The cursor cannot be open already, and it must have been
+     declared as an unbound cursor (that is, as a simple
+     refcursor variable).  The query is specified as a string
+     expression in the same way as in the EXECUTE
+     command.  As usual, this gives flexibility so the query can vary
+     from one run to the next.
 
 
 OPEN curs1 FOR EXECUTE ''SELECT * FROM '' || quote_ident($1);
@@ -1722,19 +1730,18 @@ OPEN curs1 FOR EXECUTE ''SELECT * FROM '' || quote_ident($1);
     
      Opening a bound cursor
 
-       
+    
 
 OPEN bound-cursor  ( argument_values ) ;
 
 
-        This form of OPEN is used to open a cursor variable whose query
-   was bound to it when it was declared.
-   The cursor cannot be open already.  A list of actual argument
-   value expressions must appear if and only if the cursor was
-   declared to take arguments.  These values will be substituted
-   in the query.
-   The query plan for a bound cursor is always considered
-   cacheable --- there is no equivalent of EXECUTE in this case.
+     This form of OPEN is used to open a cursor
+     variable whose query was bound to it when it was declared.  The
+     cursor cannot be open already.  A list of actual argument value
+     expressions must appear if and only if the cursor was declared to
+     take arguments.  These values will be substituted in the query.
+     The query plan for a bound cursor is always considered cacheable
+     --- there is no equivalent of EXECUTE in this case.
 
 
 OPEN curs2;
@@ -1771,16 +1778,17 @@ OPEN curs3(42);
     
      FETCH
 
-       
+    
 
 FETCH cursor INTO target;
 
 
-        FETCH retrieves the next row from the cursor into a target,
-   which may be a row variable, a record variable, or a comma-separated
-   list of simple variables, just like SELECT INTO.  As with
-   SELECT INTO, the special variable FOUND may be
-   checked to see whether a row was obtained or not.
+     FETCH retrieves the next row from the
+     cursor into a target, which may be a row variable, a record
+     variable, or a comma-separated list of simple variables, just like
+     SELECT INTO.  As with SELECT
+      INTO, the special variable FOUND may
+     be checked to see whether a row was obtained or not.
 
 
 FETCH curs1 INTO rowvar;
index d22e4aa0e44d3d249351060a4b49e2b0ff2d5663..f8adc55dcee568316b66c080bf48b777fb74f983 100644 (file)
@@ -4,7 +4,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.39 2002/11/01 22:52:34 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.40 2002/11/10 00:35:58 momjian Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -728,14 +728,13 @@ proc_stmt     : pl_block ';'
 
 stmt_perform   : K_PERFORM lno expr_until_semi
                    {
-                       PLpgSQL_stmt_assign *new;
+                       PLpgSQL_stmt_perform *new;
 
-                       new = malloc(sizeof(PLpgSQL_stmt_assign));
-                       memset(new, 0, sizeof(PLpgSQL_stmt_assign));
+                       new = malloc(sizeof(PLpgSQL_stmt_perform));
+                       memset(new, 0, sizeof(PLpgSQL_stmt_perform));
 
-                       new->cmd_type = PLPGSQL_STMT_ASSIGN;
+                       new->cmd_type = PLPGSQL_STMT_PERFORM;
                        new->lineno   = $2;
-                       new->varno = -1;
                        new->expr  = $3;
 
                        $$ = (PLpgSQL_stmt *)new;
index 9adf2d7a2e9de13888791943a7207b985baa2949..bbf59f2ae8dcac934c733c7c07c0971547627fa4 100644 (file)
@@ -3,7 +3,7 @@
  *           procedural language
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.65 2002/10/19 22:10:58 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.66 2002/11/10 00:35:58 momjian Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -73,6 +73,8 @@ static int exec_stmt(PLpgSQL_execstate * estate,
          PLpgSQL_stmt * stmt);
 static int exec_stmt_assign(PLpgSQL_execstate * estate,
                 PLpgSQL_stmt_assign * stmt);
+static int exec_stmt_perform(PLpgSQL_execstate * estate,
+                            PLpgSQL_stmt_perform * stmt);
 static int exec_stmt_getdiag(PLpgSQL_execstate * estate,
                  PLpgSQL_stmt_getdiag * stmt);
 static int exec_stmt_if(PLpgSQL_execstate * estate,
@@ -890,6 +892,10 @@ exec_stmt(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt)
            rc = exec_stmt_assign(estate, (PLpgSQL_stmt_assign *) stmt);
            break;
 
+       case PLPGSQL_STMT_PERFORM:
+           rc = exec_stmt_perform(estate, (PLpgSQL_stmt_perform *) stmt);
+           break;
+
        case PLPGSQL_STMT_GETDIAG:
            rc = exec_stmt_getdiag(estate, (PLpgSQL_stmt_getdiag *) stmt);
            break;
@@ -973,43 +979,43 @@ exec_stmt(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt)
 /* ----------
  * exec_stmt_assign            Evaluate an expression and
  *                 put the result into a variable.
- *
- * For no very good reason, this is also used for PERFORM statements.
  * ----------
  */
 static int
 exec_stmt_assign(PLpgSQL_execstate * estate, PLpgSQL_stmt_assign * stmt)
 {
-   PLpgSQL_expr *expr = stmt->expr;
+   Assert(stmt->varno >= 0);
 
-   if (stmt->varno >= 0)
-       exec_assign_expr(estate, estate->datums[stmt->varno], expr);
-   else
-   {
-       /*
-        * PERFORM: evaluate query and discard result (but set FOUND
-        * depending on whether at least one row was returned).
-        *
-        * This cannot share code with the assignment case since we do not
-        * wish to constrain the discarded result to be only one
-        * row/column.
-        */
-       int         rc;
+   exec_assign_expr(estate, estate->datums[stmt->varno], stmt->expr);
 
-       /*
-        * If not already done create a plan for this expression
-        */
-       if (expr->plan == NULL)
-           exec_prepare_plan(estate, expr);
+   return PLPGSQL_RC_OK;
+}
 
-       rc = exec_run_select(estate, expr, 0, NULL);
-       if (rc != SPI_OK_SELECT)
-           elog(ERROR, "query \"%s\" didn't return data", expr->query);
+/* ----------
+ * exec_stmt_perform       Evaluate query and discard result (but set
+ *                         FOUND depending on whether at least one row
+ *                         was returned).
+ * ----------
+ */
+static int
+exec_stmt_perform(PLpgSQL_execstate * estate, PLpgSQL_stmt_perform * stmt)
+{
+   PLpgSQL_expr *expr = stmt->expr;
+   int         rc;
 
-       exec_set_found(estate, (estate->eval_processed != 0));
+   /*
+    * If not already done create a plan for this expression
+    */
+   if (expr->plan == NULL)
+       exec_prepare_plan(estate, expr);
+   
+   rc = exec_run_select(estate, expr, 0, NULL);
+   if (rc != SPI_OK_SELECT)
+       elog(ERROR, "query \"%s\" didn't return data", expr->query);
 
-       exec_eval_cleanup(estate);
-   }
+   exec_set_found(estate, (estate->eval_processed != 0));
+
+   exec_eval_cleanup(estate);
 
    return PLPGSQL_RC_OK;
 }
@@ -1579,12 +1585,11 @@ exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt)
    return PLPGSQL_RC_RETURN;
 }
 
-/*
- * Notes:
- * - the tuple store must be created in a sufficiently long-lived
- *   memory context, as the same store must be used within the executor
- *   after the PL/PgSQL call returns. At present, the code uses
- *   TopTransactionContext.
+/* ----------
+ * exec_stmt_return_next       Evaluate an expression and add it to the
+ *                             list of tuples returned by the current
+ *                             SRF.
+ * ----------
  */
 static int
 exec_stmt_return_next(PLpgSQL_execstate * estate,
@@ -1732,7 +1737,6 @@ exec_init_tuple_store(PLpgSQL_execstate * estate)
    estate->rettupdesc = rsi->expectedDesc;
 }
 
-
 /* ----------
  * exec_stmt_raise         Build a message and throw it with
  *                 elog()
index cf3e1942d8a6b6a1f86523f93617e7f00fa5e4b5..2f4c10e035656426c795a5bcfbc00b7c4c3aafb4 100644 (file)
@@ -3,7 +3,7 @@
  *           procedural language
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.28 2002/09/12 00:24:09 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.29 2002/11/10 00:35:58 momjian Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -100,7 +100,8 @@ enum
    PLPGSQL_STMT_GETDIAG,
    PLPGSQL_STMT_OPEN,
    PLPGSQL_STMT_FETCH,
-   PLPGSQL_STMT_CLOSE
+   PLPGSQL_STMT_CLOSE,
+   PLPGSQL_STMT_PERFORM
 };
 
 
@@ -288,6 +289,12 @@ typedef struct
    PLpgSQL_expr *expr;
 }  PLpgSQL_stmt_assign;
 
+typedef struct
+{                              /* PERFORM statement        */
+   int         cmd_type;
+   int         lineno;
+   PLpgSQL_expr *expr;
+}   PLpgSQL_stmt_perform;
 
 typedef struct
 {                              /* Get Diagnostics item     */
index e18a1d556a6fd22c56411ffbd3d6eacdf4b3b542..b300b695d9133880dc499bff8d84bd45ae5cd178 100644 (file)
@@ -1733,3 +1733,54 @@ SELECT * FROM test_ret_rec_dyn(5) AS (a int, b numeric, c text);
  50 | 5 | xxx
 (1 row)
 
+--
+-- test PERFORM
+--
+create table perform_test (
+   a   INT,
+   b   INT
+);
+create function simple_func(int) returns boolean as '
+BEGIN
+   IF $1 < 20 THEN
+       INSERT INTO perform_test VALUES ($1, $1 + 10);
+       RETURN TRUE;
+   ELSE
+       RETURN FALSE;
+   END IF;
+END;' language 'plpgsql';
+create function perform_test_func() returns void as '
+BEGIN
+   IF FOUND then
+       INSERT INTO perform_test VALUES (100, 100);
+   END IF;
+
+   PERFORM simple_func(5);
+
+   IF FOUND then
+       INSERT INTO perform_test VALUES (100, 100);
+   END IF;
+
+   PERFORM simple_func(50);
+
+   IF FOUND then
+       INSERT INTO perform_test VALUES (100, 100);
+   END IF;
+
+   RETURN;
+END;' language 'plpgsql';
+SELECT perform_test_func();
+ perform_test_func 
+-------------------
+(1 row)
+
+SELECT * FROM perform_test;
+  a  |  b  
+-----+-----
+   5 |  15
+ 100 | 100
+ 100 | 100
+(3 rows)
+
+drop table perform_test;
index b6607442d727b6bc263e9d2a5020a7de8bc698a0..b4d0186458dc56ac7438b9916712e0399f31f439 100644 (file)
@@ -1559,3 +1559,48 @@ END;' language 'plpgsql';
 
 SELECT * FROM test_ret_rec_dyn(1500) AS (a int, b int, c int);
 SELECT * FROM test_ret_rec_dyn(5) AS (a int, b numeric, c text);
+
+--
+-- test PERFORM
+--
+
+create table perform_test (
+   a   INT,
+   b   INT
+);
+
+create function simple_func(int) returns boolean as '
+BEGIN
+   IF $1 < 20 THEN
+       INSERT INTO perform_test VALUES ($1, $1 + 10);
+       RETURN TRUE;
+   ELSE
+       RETURN FALSE;
+   END IF;
+END;' language 'plpgsql';
+
+create function perform_test_func() returns void as '
+BEGIN
+   IF FOUND then
+       INSERT INTO perform_test VALUES (100, 100);
+   END IF;
+
+   PERFORM simple_func(5);
+
+   IF FOUND then
+       INSERT INTO perform_test VALUES (100, 100);
+   END IF;
+
+   PERFORM simple_func(50);
+
+   IF FOUND then
+       INSERT INTO perform_test VALUES (100, 100);
+   END IF;
+
+   RETURN;
+END;' language 'plpgsql';
+
+SELECT perform_test_func();
+SELECT * FROM perform_test;
+
+drop table perform_test;
\ No newline at end of file