]> granicus.if.org Git - postgresql/commitdiff
In PL/PgSQL, allow a block's label to be optionally specified at the
authorNeil Conway <neilc@samurai.com>
Sat, 2 Jul 2005 08:59:48 +0000 (08:59 +0000)
committerNeil Conway <neilc@samurai.com>
Sat, 2 Jul 2005 08:59:48 +0000 (08:59 +0000)
end of the block:

<<label>>
begin
    ...
end label;

Similarly for loops. This is per PL/SQL. Update the documentation and
add regression tests. Patch from Pavel Stehule, code review by Neil
Conway.

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

index e8d687928f8183247d44e39827c24820fe9be506..ad6b1c84944f582f21c1fb67f502601ddb3c5759 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.74 2005/06/22 01:35:02 neilc Exp $
+$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.75 2005/07/02 08:59:47 neilc Exp $
 -->
 
 <chapter id="plpgsql"> 
@@ -456,7 +456,7 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
     <replaceable>declarations</replaceable> </optional>
 BEGIN
     <replaceable>statements</replaceable>
-END;
+END <optional> <replaceable>label</replaceable> </optional>;
 </synopsis>
     </para>
 
@@ -1789,18 +1789,19 @@ END IF;
      <title><literal>LOOP</></title>
 
 <synopsis>
-<optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
+<optional> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </optional>
 LOOP
     <replaceable>statements</replaceable>
-END LOOP;
+END LOOP <optional> <replaceable>label</replaceable> </optional>;
 </synopsis>
 
      <para>
-      <literal>LOOP</> defines an unconditional loop that is repeated indefinitely
-      until terminated by an <literal>EXIT</> or <command>RETURN</command>
-      statement.  The optional label can be used by <literal>EXIT</> statements in
-      nested loops to specify which level of nesting should be
-      terminated.
+      <literal>LOOP</> defines an unconditional loop that is repeated
+      indefinitely until terminated by an <literal>EXIT</> or
+      <command>RETURN</command> statement.  The optional
+      <replaceable>label</replaceable> can be used by <literal>EXIT</>
+      and <literal>CONTINUE</literal> statements in nested loops to
+      specify which loop the statement should be applied to.
      </para>
     </sect3>
 
@@ -1920,10 +1921,10 @@ END LOOP;
      </indexterm>
 
 <synopsis>
-<optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
+<optional> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </optional>
 WHILE <replaceable>expression</replaceable> LOOP
     <replaceable>statements</replaceable>
-END LOOP;
+END LOOP <optional> <replaceable>label</replaceable> </optional>;
 </synopsis>
 
        <para>
@@ -1951,10 +1952,10 @@ END LOOP;
       <title><literal>FOR</> (integer variant)</title>
 
 <synopsis>
-<optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
+<optional> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </optional>
 FOR <replaceable>name</replaceable> IN <optional> REVERSE </optional> <replaceable>expression</replaceable> .. <replaceable>expression</replaceable> LOOP
     <replaceable>statements</replaceable>
-END LOOP;
+END LOOP <optional> <replaceable>labal</replaceable> </optional>;
 </synopsis>
 
        <para>
@@ -1997,10 +1998,10 @@ END LOOP;
      the results of a query and manipulate that data
      accordingly. The syntax is:
 <synopsis>
-<optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
+<optional> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </optional>
 FOR <replaceable>record_or_row</replaceable> IN <replaceable>query</replaceable> LOOP
     <replaceable>statements</replaceable>
-END LOOP;
+END LOOP <optional> <replaceable>label</replaceable> </optional>;
 </synopsis>
      The record or row variable is successively assigned each row
      resulting from the <replaceable>query</replaceable> (which must be a
@@ -2036,10 +2037,10 @@ $$ LANGUAGE plpgsql;
      The <literal>FOR-IN-EXECUTE</> statement is another way to iterate over
      rows:
 <synopsis>
-<optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
+<optional> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </optional>
 FOR <replaceable>record_or_row</replaceable> IN EXECUTE <replaceable>text_expression</replaceable> LOOP 
     <replaceable>statements</replaceable>
-END LOOP;
+END LOOP <optional> <replaceable>label</replaceable> </optional>;
 </synopsis>
      This is like the previous form, except that the source
      <command>SELECT</command> statement is specified as a string
index 5d3fd8259b5a83256bdee4ec9612b92ebffcdc9c..6209d2d9dd8f8e1b20104c6dc3f651f00f3fc063 100644 (file)
@@ -4,7 +4,7 @@
  *                                               procedural language
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.78 2005/07/01 17:40:29 momjian Exp $
+ *       $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.79 2005/07/02 08:59:47 neilc Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -56,6 +56,8 @@ static        PLpgSQL_row             *read_into_scalar_list(const char *initial_name,
                                                                                           PLpgSQL_datum *initial_datum);
 static void                     check_sql_expr(const char *stmt);
 static void                     plpgsql_sql_error_callback(void *arg);
+static void                     check_labels(const char *start_label,
+                                                                         const char *end_label);
 
 %}
 
@@ -69,7 +71,7 @@ static        void                     plpgsql_sql_error_callback(void *arg);
                        int  lineno;
                }                                               varname;
                struct
-               {    
+               {
                        char *name;
                        int  lineno;
                        PLpgSQL_rec     *rec;
@@ -81,6 +83,11 @@ static       void                     plpgsql_sql_error_callback(void *arg);
                        int  n_initvars;
                        int  *initvarnos;
                }                                               declhdr;
+               struct
+               {
+                       char *end_label;
+                       List *stmts;
+               }                                               loop_body;
                List                                    *list;
                PLpgSQL_type                    *dtype;
                PLpgSQL_datum                   *scalar;        /* a VAR, RECFIELD, or TRIGARG */
@@ -119,11 +126,11 @@ static    void                     plpgsql_sql_error_callback(void *arg);
 %type <forvariable>    for_variable
 %type <stmt>   for_control
 
-%type <str>            opt_lblname opt_label
-%type <str>            opt_exitlabel
+%type <str>            opt_lblname opt_block_label opt_label
 %type <str>            execsql_start
 
-%type <list>   proc_sect proc_stmts stmt_else loop_body
+%type <list>   proc_sect proc_stmts stmt_else
+%type <loop_body>      loop_body
 %type <stmt>   proc_stmt pl_block
 %type <stmt>   stmt_assign stmt_if stmt_loop stmt_while stmt_exit
 %type <stmt>   stmt_return stmt_return_next stmt_raise stmt_execsql
@@ -248,7 +255,7 @@ opt_semi            :
                                | ';'
                                ;
 
-pl_block               : decl_sect K_BEGIN lno proc_sect exception_sect K_END
+pl_block               : decl_sect K_BEGIN lno proc_sect exception_sect K_END opt_label
                                        {
                                                PLpgSQL_stmt_block *new;
 
@@ -262,6 +269,7 @@ pl_block            : decl_sect K_BEGIN lno proc_sect exception_sect K_END
                                                new->body               = $4;
                                                new->exceptions = $5;
 
+                                               check_labels($1.label, $7);
                                                plpgsql_ns_pop();
 
                                                $$ = (PLpgSQL_stmt *)new;
@@ -269,7 +277,7 @@ pl_block            : decl_sect K_BEGIN lno proc_sect exception_sect K_END
                                ;
 
 
-decl_sect              : opt_label
+decl_sect              : opt_block_label
                                        {
                                                plpgsql_ns_setlocal(false);
                                                $$.label          = $1;
@@ -277,7 +285,7 @@ decl_sect           : opt_label
                                                $$.initvarnos = NULL;
                                                plpgsql_add_initdatums(NULL);
                                        }
-                               | opt_label decl_start
+                               | opt_block_label decl_start
                                        {
                                                plpgsql_ns_setlocal(false);
                                                $$.label          = $1;
@@ -285,7 +293,7 @@ decl_sect           : opt_label
                                                $$.initvarnos = NULL;
                                                plpgsql_add_initdatums(NULL);
                                        }
-                               | opt_label decl_start decl_stmts
+                               | opt_block_label decl_start decl_stmts
                                        {
                                                plpgsql_ns_setlocal(false);
                                                if ($3 != NULL)
@@ -409,7 +417,7 @@ decl_cursor_query :
                                                plpgsql_ns_setlocal(false);
                                                query = read_sql_stmt("");
                                                plpgsql_ns_setlocal(true);
-                                               
+
                                                $$ = query;
                                        }
                                ;
@@ -757,7 +765,7 @@ stmt_else           :
                                                 *       ...                                                       ...
                                                 * ELSE                                                    ELSE
                                                 *       ...                                                       ...
-                                                * END IF                                                  END IF                        
+                                                * END IF                                                  END IF
                                                 *                                                         END IF
                                                 */
                                                PLpgSQL_stmt_if *new_if;
@@ -776,11 +784,11 @@ stmt_else         :
 
                                | K_ELSE proc_sect
                                        {
-                                               $$ = $2;                                
+                                               $$ = $2;
                                        }
                                ;
 
-stmt_loop              : opt_label K_LOOP lno loop_body
+stmt_loop              : opt_block_label K_LOOP lno loop_body
                                        {
                                                PLpgSQL_stmt_loop *new;
 
@@ -788,15 +796,16 @@ stmt_loop         : opt_label K_LOOP lno loop_body
                                                new->cmd_type = PLPGSQL_STMT_LOOP;
                                                new->lineno   = $3;
                                                new->label        = $1;
-                                               new->body         = $4;
+                                               new->body         = $4.stmts;
 
+                                               check_labels($1, $4.end_label);
                                                plpgsql_ns_pop();
 
                                                $$ = (PLpgSQL_stmt *)new;
                                        }
                                ;
 
-stmt_while             : opt_label K_WHILE lno expr_until_loop loop_body
+stmt_while             : opt_block_label K_WHILE lno expr_until_loop loop_body
                                        {
                                                PLpgSQL_stmt_while *new;
 
@@ -805,15 +814,16 @@ stmt_while                : opt_label K_WHILE lno expr_until_loop loop_body
                                                new->lineno   = $3;
                                                new->label        = $1;
                                                new->cond         = $4;
-                                               new->body         = $5;
+                                               new->body         = $5.stmts;
 
+                                               check_labels($1, $5.end_label);
                                                plpgsql_ns_pop();
 
                                                $$ = (PLpgSQL_stmt *)new;
                                        }
                                ;
 
-stmt_for               : opt_label K_FOR for_control loop_body
+stmt_for               : opt_block_label K_FOR for_control loop_body
                                        {
                                                /* This runs after we've scanned the loop body */
                                                if ($3->cmd_type == PLPGSQL_STMT_FORI)
@@ -822,7 +832,7 @@ stmt_for            : opt_label K_FOR for_control loop_body
 
                                                        new = (PLpgSQL_stmt_fori *) $3;
                                                        new->label        = $1;
-                                                       new->body         = $4;
+                                                       new->body         = $4.stmts;
                                                        $$ = (PLpgSQL_stmt *) new;
                                                }
                                                else if ($3->cmd_type == PLPGSQL_STMT_FORS)
@@ -831,7 +841,7 @@ stmt_for            : opt_label K_FOR for_control loop_body
 
                                                        new = (PLpgSQL_stmt_fors *) $3;
                                                        new->label        = $1;
-                                                       new->body         = $4;
+                                                       new->body         = $4.stmts;
                                                        $$ = (PLpgSQL_stmt *) new;
                                                }
                                                else
@@ -841,10 +851,11 @@ stmt_for          : opt_label K_FOR for_control loop_body
                                                        Assert($3->cmd_type == PLPGSQL_STMT_DYNFORS);
                                                        new = (PLpgSQL_stmt_dynfors *) $3;
                                                        new->label        = $1;
-                                                       new->body         = $4;
+                                                       new->body         = $4.stmts;
                                                        $$ = (PLpgSQL_stmt *) new;
                                                }
 
+                                               check_labels($1, $4.end_label);
                                                /* close namespace started in opt_label */
                                                plpgsql_ns_pop();
                                        }
@@ -1037,7 +1048,7 @@ stmt_select               : K_SELECT lno
                                        }
                                ;
 
-stmt_exit              : exit_type lno opt_exitlabel opt_exitcond
+stmt_exit              : exit_type lno opt_label opt_exitcond
                                        {
                                                PLpgSQL_stmt_exit *new;
 
@@ -1245,8 +1256,11 @@ raise_level              : K_EXCEPTION
                                        }
                                ;
 
-loop_body              : proc_sect K_END K_LOOP ';'
-                                       { $$ = $1; }
+loop_body              : proc_sect K_END K_LOOP opt_label ';'
+                                       {
+                                               $$.stmts = $1;
+                                               $$.end_label = $4;
+                                       }
                                ;
 
 stmt_execsql   : execsql_start lno
@@ -1262,7 +1276,7 @@ stmt_execsql      : execsql_start lno
                                        }
                                ;
 
-stmt_dynexecute : K_EXECUTE lno 
+stmt_dynexecute : K_EXECUTE lno
                                        {
                                                PLpgSQL_stmt_dynexecute *new;
                                                PLpgSQL_expr *expr;
@@ -1418,7 +1432,7 @@ stmt_open         : K_OPEN lno cursor_varptr
                                                                                         errmsg("cursor \"%s\" has no arguments",
                                                                                                        $3->refname)));
                                                                }
-                                                               
+
                                                                if (tok != ';')
                                                                {
                                                                        plpgsql_error_lineno = plpgsql_scanner_lineno();
@@ -1596,7 +1610,7 @@ expr_until_loop :
                                        { $$ = plpgsql_read_expression(K_LOOP, "LOOP"); }
                                ;
 
-opt_label              :
+opt_block_label        :
                                        {
                                                plpgsql_ns_push(NULL);
                                                $$ = NULL;
@@ -1608,14 +1622,15 @@ opt_label               :
                                        }
                                ;
 
-opt_exitlabel  :
-                                       { $$ = NULL; }
+opt_label      :
+                                       {
+                                               $$ = NULL;
+                                       }
                                | T_LABEL
                                        {
-                                               char    *name;
-
-                                               plpgsql_convert_ident(yytext, &name, 1);
-                                               $$ = name;
+                                               char *label_name;
+                                               plpgsql_convert_ident(yytext, &label_name, 1);
+                                               $$ = label_name;
                                        }
                                | T_WORD
                                        {
@@ -2210,4 +2225,29 @@ plpgsql_sql_error_callback(void *arg)
        errposition(0);
 }
 
+static void
+check_labels(const char *start_label, const char *end_label)
+{
+       if (end_label)
+       {
+               if (!start_label)
+               {
+                       plpgsql_error_lineno = plpgsql_scanner_lineno();
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("end label \"%s\" specified for unlabelled block",
+                                                       end_label)));
+               }
+
+               if (strcmp(start_label, end_label) != 0)
+               {
+                       plpgsql_error_lineno = plpgsql_scanner_lineno();
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("end label \"%s\" differs from block's label \"%s\"",
+                                                       end_label, start_label)));
+               }
+       }
+}
+
 #include "pl_scan.c"
index a74b0e5466606ffa1db4f8ad4dcccb0a2e2d2e98..42dcad5c2fb63112300911e0322f9396880f10c7 100644 (file)
@@ -2491,7 +2491,7 @@ NOTICE:  {10,20,30}; 20; xyz; xyzabc; (10,aaa,,30); <NULL>
 (1 row)
 
 drop function raise_exprs();
--- continue statement 
+-- continue statement
 create table conttesttbl(idx serial, v integer);
 NOTICE:  CREATE TABLE will create implicit sequence "conttesttbl_idx_seq" for serial column "conttesttbl.idx"
 insert into conttesttbl(v) values(10);
@@ -2532,7 +2532,7 @@ begin
   for _i in 1..10 loop
     begin
       -- applies to outer loop, not the nested begin block
-      continue when _i < 5; 
+      continue when _i < 5;
       raise notice '%', _i;
     end;
   end loop;
@@ -2666,3 +2666,58 @@ drop function continue_test1();
 drop function continue_test2();
 drop function continue_test3();
 drop table conttesttbl;
+-- verbose end block and end loop
+create function end_label1() returns void as $$
+<<blbl>>
+begin
+  <<flbl1>>
+  for _i in 1 .. 10 loop
+    exit flbl1;
+  end loop flbl1;
+  <<flbl2>>
+  for _i in 1 .. 10 loop
+    exit flbl2;
+  end loop;
+end blbl;
+$$ language plpgsql;
+select end_label1();
+ end_label1 
+------------
+(1 row)
+
+drop function end_label1();
+-- should fail: undefined end label
+create function end_label2() returns void as $$
+begin
+  for _i in 1 .. 10 loop
+    exit;
+  end loop flbl1;
+end;
+$$ language plpgsql;
+ERROR:  no such label at or near "flbl1" at character 101
+LINE 5:   end loop flbl1;
+                   ^
+-- should fail: end label does not match start label
+create function end_label3() returns void as $$
+<<outer_label>>
+begin
+  <<inner_label>>
+  for _i in 1 .. 10 loop
+    exit;
+  end loop outer_label;
+end;
+$$ language plpgsql;
+ERROR:  end label "outer_label" differs from block's label "inner_label"
+CONTEXT:  compile of PL/pgSQL function "end_label3" near line 6
+-- should fail: end label on a block without a start label
+create function end_label4() returns void as $$
+<<outer_label>>
+begin
+  for _i in 1 .. 10 loop
+    exit;
+  end loop outer_label;
+end;
+$$ language plpgsql;
+ERROR:  end label "outer_label" specified for unlabelled block
+CONTEXT:  compile of PL/pgSQL function "end_label4" near line 5
index b0d49c0f321859bc553df0587346c7944e409082..3432b5556cc4b24f99f95594c6cf85d413fdb405 100644 (file)
@@ -2113,7 +2113,7 @@ end;$$ language plpgsql;
 select raise_exprs();
 drop function raise_exprs();
 
--- continue statement 
+-- continue statement
 create table conttesttbl(idx serial, v integer);
 insert into conttesttbl(v) values(10);
 insert into conttesttbl(v) values(20);
@@ -2154,7 +2154,7 @@ begin
   for _i in 1..10 loop
     begin
       -- applies to outer loop, not the nested begin block
-      continue when _i < 5; 
+      continue when _i < 5;
       raise notice '%', _i;
     end;
   end loop;
@@ -2232,3 +2232,51 @@ drop function continue_test1();
 drop function continue_test2();
 drop function continue_test3();
 drop table conttesttbl;
+
+-- verbose end block and end loop
+create function end_label1() returns void as $$
+<<blbl>>
+begin
+  <<flbl1>>
+  for _i in 1 .. 10 loop
+    exit flbl1;
+  end loop flbl1;
+  <<flbl2>>
+  for _i in 1 .. 10 loop
+    exit flbl2;
+  end loop;
+end blbl;
+$$ language plpgsql;
+
+select end_label1();
+drop function end_label1();
+
+-- should fail: undefined end label
+create function end_label2() returns void as $$
+begin
+  for _i in 1 .. 10 loop
+    exit;
+  end loop flbl1;
+end;
+$$ language plpgsql;
+
+-- should fail: end label does not match start label
+create function end_label3() returns void as $$
+<<outer_label>>
+begin
+  <<inner_label>>
+  for _i in 1 .. 10 loop
+    exit;
+  end loop outer_label;
+end;
+$$ language plpgsql;
+
+-- should fail: end label on a block without a start label
+create function end_label4() returns void as $$
+<<outer_label>>
+begin
+  for _i in 1 .. 10 loop
+    exit;
+  end loop outer_label;
+end;
+$$ language plpgsql;