In PL/PgSQL, allow a block's label to be optionally specified at the
authorNeil Conway
Sat, 2 Jul 2005 08:59:48 +0000 (08:59 +0000)
committerNeil Conway
Sat, 2 Jul 2005 08:59:48 +0000 (08:59 +0000)
end of the block:

<
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 @@
 
 
  
@@ -456,7 +456,7 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
     declarations 
 BEGIN
     statements
-END;
+END  label ;
 
     
 
@@ -1789,18 +1789,19 @@ END IF;
      <literal>LOOP</>
 
 
-<<label>>
+ <<label>> 
 LOOP
     statements
-END LOOP;
+END LOOP  label ;
 
 
      
-      LOOP defines an unconditional loop that is repeated indefinitely
-      until terminated by an EXIT or RETURN
-      statement.  The optional label can be used by EXIT statements in
-      nested loops to specify which level of nesting should be
-      terminated.
+      LOOP defines an unconditional loop that is repeated
+      indefinitely until terminated by an EXIT or
+      RETURN statement.  The optional
+      label can be used by EXIT
+      and CONTINUE statements in nested loops to
+      specify which loop the statement should be applied to.
      
     
 
@@ -1920,10 +1921,10 @@ END LOOP;
      
 
 
-<<label>>
+ <<label>> 
 WHILE expression LOOP
     statements
-END LOOP;
+END LOOP  label ;
 
 
        
@@ -1951,10 +1952,10 @@ END LOOP;
       <literal>FOR</> (integer variant)
 
 
-<<label>>
+ <<label>> 
 FOR name IN  REVERSE  expression .. expression LOOP
     statements
-END LOOP;
+END LOOP  labal ;
 
 
        
@@ -1997,10 +1998,10 @@ END LOOP;
      the results of a query and manipulate that data
      accordingly. The syntax is:
 
-<<label>>
+ <<label>> 
 FOR record_or_row IN query LOOP
     statements
-END LOOP;
+END LOOP  label ;
 
      The record or row variable is successively assigned each row
      resulting from the query (which must be a
@@ -2036,10 +2037,10 @@ $$ LANGUAGE plpgsql;
      The FOR-IN-EXECUTE statement is another way to iterate over
      rows:
 
-<<label>>
+ <<label>> 
 FOR record_or_row IN EXECUTE text_expression LOOP 
     statements
-END LOOP;
+END LOOP  label ;
 
      This is like the previous form, except that the source
      SELECT 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     for_variable
 %type    for_control
 
-%type         opt_lblname opt_label
-%type         opt_exitlabel
+%type         opt_lblname opt_block_label opt_label
 %type         execsql_start
 
-%type    proc_sect proc_stmts stmt_else loop_body
+%type    proc_sect proc_stmts stmt_else
+%type   loop_body
 %type    proc_stmt pl_block
 %type    stmt_assign stmt_if stmt_loop stmt_while stmt_exit
 %type    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); 
 (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 $$
+<>
+begin
+  <>
+  for _i in 1 .. 10 loop
+    exit flbl1;
+  end loop flbl1;
+  <>
+  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 $$
+<>
+begin
+  <>
+  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 $$
+<>
+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 $$
+<>
+begin
+  <>
+  for _i in 1 .. 10 loop
+    exit flbl1;
+  end loop flbl1;
+  <>
+  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 $$
+<>
+begin
+  <>
+  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 $$
+<>
+begin
+  for _i in 1 .. 10 loop
+    exit;
+  end loop outer_label;
+end;
+$$ language plpgsql;