diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index e8d687928f..ad6b1c8494 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -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 diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y index 5d3fd8259b..6209d2d9dd 100644 --- a/src/pl/plpgsql/src/gram.y +++ b/src/pl/plpgsql/src/gram.y @@ -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" diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index a74b0e5466..42dcad5c2f 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -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 diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index b0d49c0f32..3432b5556c 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -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;