Index: doc/src/sgml/plpgsql.sgml =================================================================== RCS file: /Users/neilc/local/cvs/pgsql/doc/src/sgml/plpgsql.sgml,v retrieving revision 1.74 diff -c -r1.74 plpgsql.sgml *** doc/src/sgml/plpgsql.sgml 22 Jun 2005 01:35:02 -0000 1.74 --- doc/src/sgml/plpgsql.sgml 1 Jul 2005 11:43:36 -0000 *************** *** 456,462 **** declarations BEGIN statements ! END; --- 456,462 ---- declarations BEGIN statements ! END label ; *************** *** 1789,1806 **** <literal>LOOP</> ! <<label>> LOOP statements ! END LOOP; ! 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. --- 1789,1807 ---- <literal>LOOP</> ! <<label>> LOOP statements ! 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 ! and CONTINUE statements in nested loops to ! specify which loop the statement should be applied to. *************** *** 1920,1929 **** ! <<label>> WHILE expression LOOP statements ! END LOOP; --- 1921,1930 ---- ! <<label>> WHILE expression LOOP statements ! END LOOP label ; *************** *** 1951,1960 **** <literal>FOR</> (integer variant) ! <<label>> FOR name IN REVERSE expression .. expression LOOP statements ! END LOOP; --- 1952,1961 ---- <literal>FOR</> (integer variant) ! <<label>> FOR name IN REVERSE expression .. expression LOOP statements ! END LOOP labal ; *************** *** 1997,2006 **** the results of a query and manipulate that data accordingly. The syntax is: ! <<label>> FOR record_or_row IN query LOOP statements ! END LOOP; The record or row variable is successively assigned each row resulting from the query (which must be a --- 1998,2007 ---- the results of a query and manipulate that data accordingly. The syntax is: ! <<label>> FOR record_or_row IN query LOOP statements ! END LOOP label ; The record or row variable is successively assigned each row resulting from the query (which must be a *************** *** 2036,2045 **** The FOR-IN-EXECUTE statement is another way to iterate over rows: ! <<label>> FOR record_or_row IN EXECUTE text_expression LOOP statements ! END LOOP; This is like the previous form, except that the source SELECT statement is specified as a string --- 2037,2046 ---- The FOR-IN-EXECUTE statement is another way to iterate over rows: ! <<label>> FOR record_or_row IN EXECUTE text_expression LOOP statements ! END LOOP label ; This is like the previous form, except that the source SELECT statement is specified as a string Index: src/pl/plpgsql/src/gram.y =================================================================== RCS file: /Users/neilc/local/cvs/pgsql/src/pl/plpgsql/src/gram.y,v retrieving revision 1.77 diff -c -r1.77 gram.y *** src/pl/plpgsql/src/gram.y 22 Jun 2005 01:35:02 -0000 1.77 --- src/pl/plpgsql/src/gram.y 1 Jul 2005 12:08:45 -0000 *************** *** 56,61 **** --- 56,63 ---- 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,75 **** int lineno; } varname; struct ! { char *name; int lineno; PLpgSQL_rec *rec; --- 71,77 ---- int lineno; } varname; struct ! { char *name; int lineno; PLpgSQL_rec *rec; *************** *** 81,86 **** --- 83,93 ---- 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,129 **** %type for_variable %type for_control ! %type opt_lblname opt_label ! %type opt_exitlabel %type execsql_start ! %type proc_sect proc_stmts stmt_else 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 --- 126,136 ---- %type for_variable %type for_control ! %type opt_lblname opt_block_label opt_label %type execsql_start ! %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,257 **** | ';' ; ! pl_block : decl_sect K_BEGIN lno proc_sect exception_sect K_END { PLpgSQL_stmt_block *new; new = palloc0(sizeof(PLpgSQL_stmt_block)); new->cmd_type = PLPGSQL_STMT_BLOCK; --- 255,266 ---- | ';' ; ! pl_block : decl_sect K_BEGIN lno proc_sect exception_sect K_END opt_label { PLpgSQL_stmt_block *new; + check_labels($1.label, $7); + new = palloc0(sizeof(PLpgSQL_stmt_block)); new->cmd_type = PLPGSQL_STMT_BLOCK; *************** *** 269,275 **** ; ! decl_sect : opt_label { plpgsql_ns_setlocal(false); $$.label = $1; --- 278,284 ---- ; ! decl_sect : opt_block_label { plpgsql_ns_setlocal(false); $$.label = $1; *************** *** 277,283 **** $$.initvarnos = NULL; plpgsql_add_initdatums(NULL); } ! | opt_label decl_start { plpgsql_ns_setlocal(false); $$.label = $1; --- 286,292 ---- $$.initvarnos = NULL; plpgsql_add_initdatums(NULL); } ! | opt_block_label decl_start { plpgsql_ns_setlocal(false); $$.label = $1; *************** *** 285,291 **** $$.initvarnos = NULL; plpgsql_add_initdatums(NULL); } ! | opt_label decl_start decl_stmts { plpgsql_ns_setlocal(false); if ($3 != NULL) --- 294,300 ---- $$.initvarnos = NULL; plpgsql_add_initdatums(NULL); } ! | opt_block_label decl_start decl_stmts { plpgsql_ns_setlocal(false); if ($3 != NULL) *************** *** 409,415 **** plpgsql_ns_setlocal(false); query = read_sql_stmt(""); plpgsql_ns_setlocal(true); ! $$ = query; } ; --- 418,424 ---- plpgsql_ns_setlocal(false); query = read_sql_stmt(""); plpgsql_ns_setlocal(true); ! $$ = query; } ; *************** *** 757,763 **** * ... ... * ELSE ELSE * ... ... ! * END IF END IF * END IF */ PLpgSQL_stmt_if *new_if; --- 766,772 ---- * ... ... * ELSE ELSE * ... ... ! * END IF END IF * END IF */ PLpgSQL_stmt_if *new_if; *************** *** 776,786 **** | K_ELSE proc_sect { ! $$ = $2; } ; ! stmt_loop : opt_label K_LOOP lno loop_body { PLpgSQL_stmt_loop *new; --- 785,795 ---- | K_ELSE proc_sect { ! $$ = $2; } ; ! stmt_loop : opt_block_label K_LOOP lno loop_body { PLpgSQL_stmt_loop *new; *************** *** 788,802 **** new->cmd_type = PLPGSQL_STMT_LOOP; new->lineno = $3; new->label = $1; ! new->body = $4; plpgsql_ns_pop(); $$ = (PLpgSQL_stmt *)new; } ; ! stmt_while : opt_label K_WHILE lno expr_until_loop loop_body { PLpgSQL_stmt_while *new; --- 797,812 ---- new->cmd_type = PLPGSQL_STMT_LOOP; new->lineno = $3; new->label = $1; ! new->body = $4.stmts; + check_labels($1, $4.end_label); plpgsql_ns_pop(); $$ = (PLpgSQL_stmt *)new; } ; ! stmt_while : opt_block_label K_WHILE lno expr_until_loop loop_body { PLpgSQL_stmt_while *new; *************** *** 805,819 **** new->lineno = $3; new->label = $1; new->cond = $4; ! new->body = $5; plpgsql_ns_pop(); $$ = (PLpgSQL_stmt *)new; } ; ! stmt_for : opt_label K_FOR for_control loop_body { /* This runs after we've scanned the loop body */ if ($3->cmd_type == PLPGSQL_STMT_FORI) --- 815,830 ---- new->lineno = $3; new->label = $1; new->cond = $4; ! new->body = $5.stmts; + check_labels($1, $5.end_label); plpgsql_ns_pop(); $$ = (PLpgSQL_stmt *)new; } ; ! 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,828 **** new = (PLpgSQL_stmt_fori *) $3; new->label = $1; ! new->body = $4; $$ = (PLpgSQL_stmt *) new; } else if ($3->cmd_type == PLPGSQL_STMT_FORS) --- 833,839 ---- new = (PLpgSQL_stmt_fori *) $3; new->label = $1; ! new->body = $4.stmts; $$ = (PLpgSQL_stmt *) new; } else if ($3->cmd_type == PLPGSQL_STMT_FORS) *************** *** 831,837 **** new = (PLpgSQL_stmt_fors *) $3; new->label = $1; ! new->body = $4; $$ = (PLpgSQL_stmt *) new; } else --- 842,848 ---- new = (PLpgSQL_stmt_fors *) $3; new->label = $1; ! new->body = $4.stmts; $$ = (PLpgSQL_stmt *) new; } else *************** *** 841,850 **** Assert($3->cmd_type == PLPGSQL_STMT_DYNFORS); new = (PLpgSQL_stmt_dynfors *) $3; new->label = $1; ! new->body = $4; $$ = (PLpgSQL_stmt *) new; } /* close namespace started in opt_label */ plpgsql_ns_pop(); } --- 852,862 ---- Assert($3->cmd_type == PLPGSQL_STMT_DYNFORS); new = (PLpgSQL_stmt_dynfors *) $3; new->label = $1; ! new->body = $4.stmts; $$ = (PLpgSQL_stmt *) new; } + check_labels($1, $4.end_label); /* close namespace started in opt_label */ plpgsql_ns_pop(); } *************** *** 1037,1043 **** } ; ! stmt_exit : exit_type lno opt_exitlabel opt_exitcond { PLpgSQL_stmt_exit *new; --- 1049,1055 ---- } ; ! stmt_exit : exit_type lno opt_label opt_exitcond { PLpgSQL_stmt_exit *new; *************** *** 1245,1252 **** } ; ! loop_body : proc_sect K_END K_LOOP ';' ! { $$ = $1; } ; stmt_execsql : execsql_start lno --- 1257,1267 ---- } ; ! loop_body : proc_sect K_END K_LOOP opt_label ';' ! { ! $$.stmts = $1; ! $$.end_label = $4; ! } ; stmt_execsql : execsql_start lno *************** *** 1262,1268 **** } ; ! stmt_dynexecute : K_EXECUTE lno { PLpgSQL_stmt_dynexecute *new; PLpgSQL_expr *expr; --- 1277,1283 ---- } ; ! stmt_dynexecute : K_EXECUTE lno { PLpgSQL_stmt_dynexecute *new; PLpgSQL_expr *expr; *************** *** 1418,1424 **** errmsg("cursor \"%s\" has no arguments", $3->refname))); } ! if (tok != ';') { plpgsql_error_lineno = plpgsql_scanner_lineno(); --- 1433,1439 ---- errmsg("cursor \"%s\" has no arguments", $3->refname))); } ! if (tok != ';') { plpgsql_error_lineno = plpgsql_scanner_lineno(); *************** *** 1596,1602 **** { $$ = plpgsql_read_expression(K_LOOP, "LOOP"); } ; ! opt_label : { plpgsql_ns_push(NULL); $$ = NULL; --- 1611,1617 ---- { $$ = plpgsql_read_expression(K_LOOP, "LOOP"); } ; ! opt_block_label : { plpgsql_ns_push(NULL); $$ = NULL; *************** *** 1608,1621 **** } ; ! opt_exitlabel : ! { $$ = NULL; } | T_LABEL { ! char *name; ! ! plpgsql_convert_ident(yytext, &name, 1); ! $$ = name; } | T_WORD { --- 1623,1637 ---- } ; ! opt_label : ! { ! $$ = NULL; ! } | T_LABEL { ! char *label_name; ! plpgsql_convert_ident(yytext, &label_name, 1); ! $$ = label_name; } | T_WORD { *************** *** 2210,2213 **** --- 2226,2254 ---- 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: src/test/regress/expected/plpgsql.out =================================================================== RCS file: /Users/neilc/local/cvs/pgsql/src/test/regress/expected/plpgsql.out,v retrieving revision 1.36 diff -c -r1.36 plpgsql.out *** src/test/regress/expected/plpgsql.out 22 Jun 2005 07:28:47 -0000 1.36 --- src/test/regress/expected/plpgsql.out 1 Jul 2005 11:53:55 -0000 *************** *** 2491,2497 **** (1 row) drop function raise_exprs(); ! -- 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); --- 2491,2497 ---- (1 row) drop function raise_exprs(); ! -- 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,2538 **** for _i in 1..10 loop begin -- applies to outer loop, not the nested begin block ! continue when _i < 5; raise notice '%', _i; end; end loop; --- 2532,2538 ---- for _i in 1..10 loop begin -- applies to outer loop, not the nested begin block ! continue when _i < 5; raise notice '%', _i; end; end loop; *************** *** 2666,2668 **** --- 2666,2723 ---- 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: src/test/regress/sql/plpgsql.sql =================================================================== RCS file: /Users/neilc/local/cvs/pgsql/src/test/regress/sql/plpgsql.sql,v retrieving revision 1.31 diff -c -r1.31 plpgsql.sql *** src/test/regress/sql/plpgsql.sql 22 Jun 2005 07:28:47 -0000 1.31 --- src/test/regress/sql/plpgsql.sql 1 Jul 2005 11:43:36 -0000 *************** *** 2113,2119 **** select raise_exprs(); drop function raise_exprs(); ! -- continue statement create table conttesttbl(idx serial, v integer); insert into conttesttbl(v) values(10); insert into conttesttbl(v) values(20); --- 2113,2119 ---- select raise_exprs(); drop function raise_exprs(); ! -- continue statement create table conttesttbl(idx serial, v integer); insert into conttesttbl(v) values(10); insert into conttesttbl(v) values(20); *************** *** 2154,2160 **** for _i in 1..10 loop begin -- applies to outer loop, not the nested begin block ! continue when _i < 5; raise notice '%', _i; end; end loop; --- 2154,2160 ---- for _i in 1..10 loop begin -- applies to outer loop, not the nested begin block ! continue when _i < 5; raise notice '%', _i; end; end loop; *************** *** 2232,2234 **** --- 2232,2282 ---- 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;