Commit c425dcb4 authored by Neil Conway's avatar Neil Conway

In PL/PgSQL, allow a block's label to be optionally specified at the

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.
parent 784b9489
<!-- <!--
$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"> <chapter id="plpgsql">
...@@ -456,7 +456,7 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$ ...@@ -456,7 +456,7 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
<replaceable>declarations</replaceable> </optional> <replaceable>declarations</replaceable> </optional>
BEGIN BEGIN
<replaceable>statements</replaceable> <replaceable>statements</replaceable>
END; END <optional> <replaceable>label</replaceable> </optional>;
</synopsis> </synopsis>
</para> </para>
...@@ -1789,18 +1789,19 @@ END IF; ...@@ -1789,18 +1789,19 @@ END IF;
<title><literal>LOOP</></title> <title><literal>LOOP</></title>
<synopsis> <synopsis>
<optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional> <optional> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </optional>
LOOP LOOP
<replaceable>statements</replaceable> <replaceable>statements</replaceable>
END LOOP; END LOOP <optional> <replaceable>label</replaceable> </optional>;
</synopsis> </synopsis>
<para> <para>
<literal>LOOP</> defines an unconditional loop that is repeated indefinitely <literal>LOOP</> defines an unconditional loop that is repeated
until terminated by an <literal>EXIT</> or <command>RETURN</command> indefinitely until terminated by an <literal>EXIT</> or
statement. The optional label can be used by <literal>EXIT</> statements in <command>RETURN</command> statement. The optional
nested loops to specify which level of nesting should be <replaceable>label</replaceable> can be used by <literal>EXIT</>
terminated. and <literal>CONTINUE</literal> statements in nested loops to
specify which loop the statement should be applied to.
</para> </para>
</sect3> </sect3>
...@@ -1920,10 +1921,10 @@ END LOOP; ...@@ -1920,10 +1921,10 @@ END LOOP;
</indexterm> </indexterm>
<synopsis> <synopsis>
<optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional> <optional> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </optional>
WHILE <replaceable>expression</replaceable> LOOP WHILE <replaceable>expression</replaceable> LOOP
<replaceable>statements</replaceable> <replaceable>statements</replaceable>
END LOOP; END LOOP <optional> <replaceable>label</replaceable> </optional>;
</synopsis> </synopsis>
<para> <para>
...@@ -1951,10 +1952,10 @@ END LOOP; ...@@ -1951,10 +1952,10 @@ END LOOP;
<title><literal>FOR</> (integer variant)</title> <title><literal>FOR</> (integer variant)</title>
<synopsis> <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 FOR <replaceable>name</replaceable> IN <optional> REVERSE </optional> <replaceable>expression</replaceable> .. <replaceable>expression</replaceable> LOOP
<replaceable>statements</replaceable> <replaceable>statements</replaceable>
END LOOP; END LOOP <optional> <replaceable>labal</replaceable> </optional>;
</synopsis> </synopsis>
<para> <para>
...@@ -1997,10 +1998,10 @@ END LOOP; ...@@ -1997,10 +1998,10 @@ END LOOP;
the results of a query and manipulate that data the results of a query and manipulate that data
accordingly. The syntax is: accordingly. The syntax is:
<synopsis> <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 FOR <replaceable>record_or_row</replaceable> IN <replaceable>query</replaceable> LOOP
<replaceable>statements</replaceable> <replaceable>statements</replaceable>
END LOOP; END LOOP <optional> <replaceable>label</replaceable> </optional>;
</synopsis> </synopsis>
The record or row variable is successively assigned each row The record or row variable is successively assigned each row
resulting from the <replaceable>query</replaceable> (which must be a resulting from the <replaceable>query</replaceable> (which must be a
...@@ -2036,10 +2037,10 @@ $$ LANGUAGE plpgsql; ...@@ -2036,10 +2037,10 @@ $$ LANGUAGE plpgsql;
The <literal>FOR-IN-EXECUTE</> statement is another way to iterate over The <literal>FOR-IN-EXECUTE</> statement is another way to iterate over
rows: rows:
<synopsis> <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 FOR <replaceable>record_or_row</replaceable> IN EXECUTE <replaceable>text_expression</replaceable> LOOP
<replaceable>statements</replaceable> <replaceable>statements</replaceable>
END LOOP; END LOOP <optional> <replaceable>label</replaceable> </optional>;
</synopsis> </synopsis>
This is like the previous form, except that the source This is like the previous form, except that the source
<command>SELECT</command> statement is specified as a string <command>SELECT</command> statement is specified as a string
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * 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. * This software is copyrighted by Jan Wieck - Hamburg.
* *
...@@ -56,6 +56,8 @@ static PLpgSQL_row *read_into_scalar_list(const char *initial_name, ...@@ -56,6 +56,8 @@ static PLpgSQL_row *read_into_scalar_list(const char *initial_name,
PLpgSQL_datum *initial_datum); PLpgSQL_datum *initial_datum);
static void check_sql_expr(const char *stmt); static void check_sql_expr(const char *stmt);
static void plpgsql_sql_error_callback(void *arg); static void plpgsql_sql_error_callback(void *arg);
static void check_labels(const char *start_label,
const char *end_label);
%} %}
...@@ -81,6 +83,11 @@ static void plpgsql_sql_error_callback(void *arg); ...@@ -81,6 +83,11 @@ static void plpgsql_sql_error_callback(void *arg);
int n_initvars; int n_initvars;
int *initvarnos; int *initvarnos;
} declhdr; } declhdr;
struct
{
char *end_label;
List *stmts;
} loop_body;
List *list; List *list;
PLpgSQL_type *dtype; PLpgSQL_type *dtype;
PLpgSQL_datum *scalar; /* a VAR, RECFIELD, or TRIGARG */ PLpgSQL_datum *scalar; /* a VAR, RECFIELD, or TRIGARG */
...@@ -119,11 +126,11 @@ static void plpgsql_sql_error_callback(void *arg); ...@@ -119,11 +126,11 @@ static void plpgsql_sql_error_callback(void *arg);
%type <forvariable> for_variable %type <forvariable> for_variable
%type <stmt> for_control %type <stmt> for_control
%type <str> opt_lblname opt_label %type <str> opt_lblname opt_block_label opt_label
%type <str> opt_exitlabel
%type <str> execsql_start %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> proc_stmt pl_block
%type <stmt> stmt_assign stmt_if stmt_loop stmt_while stmt_exit %type <stmt> stmt_assign stmt_if stmt_loop stmt_while stmt_exit
%type <stmt> stmt_return stmt_return_next stmt_raise stmt_execsql %type <stmt> stmt_return stmt_return_next stmt_raise stmt_execsql
...@@ -248,7 +255,7 @@ opt_semi : ...@@ -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; PLpgSQL_stmt_block *new;
...@@ -262,6 +269,7 @@ pl_block : decl_sect K_BEGIN lno proc_sect exception_sect K_END ...@@ -262,6 +269,7 @@ pl_block : decl_sect K_BEGIN lno proc_sect exception_sect K_END
new->body = $4; new->body = $4;
new->exceptions = $5; new->exceptions = $5;
check_labels($1.label, $7);
plpgsql_ns_pop(); plpgsql_ns_pop();
$$ = (PLpgSQL_stmt *)new; $$ = (PLpgSQL_stmt *)new;
...@@ -269,7 +277,7 @@ pl_block : decl_sect K_BEGIN lno proc_sect exception_sect K_END ...@@ -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); plpgsql_ns_setlocal(false);
$$.label = $1; $$.label = $1;
...@@ -277,7 +285,7 @@ decl_sect : opt_label ...@@ -277,7 +285,7 @@ decl_sect : opt_label
$$.initvarnos = NULL; $$.initvarnos = NULL;
plpgsql_add_initdatums(NULL); plpgsql_add_initdatums(NULL);
} }
| opt_label decl_start | opt_block_label decl_start
{ {
plpgsql_ns_setlocal(false); plpgsql_ns_setlocal(false);
$$.label = $1; $$.label = $1;
...@@ -285,7 +293,7 @@ decl_sect : opt_label ...@@ -285,7 +293,7 @@ decl_sect : opt_label
$$.initvarnos = NULL; $$.initvarnos = NULL;
plpgsql_add_initdatums(NULL); plpgsql_add_initdatums(NULL);
} }
| opt_label decl_start decl_stmts | opt_block_label decl_start decl_stmts
{ {
plpgsql_ns_setlocal(false); plpgsql_ns_setlocal(false);
if ($3 != NULL) if ($3 != NULL)
...@@ -780,7 +788,7 @@ stmt_else : ...@@ -780,7 +788,7 @@ stmt_else :
} }
; ;
stmt_loop : opt_label K_LOOP lno loop_body stmt_loop : opt_block_label K_LOOP lno loop_body
{ {
PLpgSQL_stmt_loop *new; PLpgSQL_stmt_loop *new;
...@@ -788,15 +796,16 @@ stmt_loop : opt_label K_LOOP lno loop_body ...@@ -788,15 +796,16 @@ stmt_loop : opt_label K_LOOP lno loop_body
new->cmd_type = PLPGSQL_STMT_LOOP; new->cmd_type = PLPGSQL_STMT_LOOP;
new->lineno = $3; new->lineno = $3;
new->label = $1; new->label = $1;
new->body = $4; new->body = $4.stmts;
check_labels($1, $4.end_label);
plpgsql_ns_pop(); plpgsql_ns_pop();
$$ = (PLpgSQL_stmt *)new; $$ = (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; PLpgSQL_stmt_while *new;
...@@ -805,15 +814,16 @@ stmt_while : opt_label K_WHILE lno expr_until_loop loop_body ...@@ -805,15 +814,16 @@ stmt_while : opt_label K_WHILE lno expr_until_loop loop_body
new->lineno = $3; new->lineno = $3;
new->label = $1; new->label = $1;
new->cond = $4; new->cond = $4;
new->body = $5; new->body = $5.stmts;
check_labels($1, $5.end_label);
plpgsql_ns_pop(); plpgsql_ns_pop();
$$ = (PLpgSQL_stmt *)new; $$ = (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 */ /* This runs after we've scanned the loop body */
if ($3->cmd_type == PLPGSQL_STMT_FORI) if ($3->cmd_type == PLPGSQL_STMT_FORI)
...@@ -822,7 +832,7 @@ stmt_for : opt_label K_FOR for_control loop_body ...@@ -822,7 +832,7 @@ stmt_for : opt_label K_FOR for_control loop_body
new = (PLpgSQL_stmt_fori *) $3; new = (PLpgSQL_stmt_fori *) $3;
new->label = $1; new->label = $1;
new->body = $4; new->body = $4.stmts;
$$ = (PLpgSQL_stmt *) new; $$ = (PLpgSQL_stmt *) new;
} }
else if ($3->cmd_type == PLPGSQL_STMT_FORS) else if ($3->cmd_type == PLPGSQL_STMT_FORS)
...@@ -831,7 +841,7 @@ stmt_for : opt_label K_FOR for_control loop_body ...@@ -831,7 +841,7 @@ stmt_for : opt_label K_FOR for_control loop_body
new = (PLpgSQL_stmt_fors *) $3; new = (PLpgSQL_stmt_fors *) $3;
new->label = $1; new->label = $1;
new->body = $4; new->body = $4.stmts;
$$ = (PLpgSQL_stmt *) new; $$ = (PLpgSQL_stmt *) new;
} }
else else
...@@ -841,10 +851,11 @@ stmt_for : opt_label K_FOR for_control loop_body ...@@ -841,10 +851,11 @@ stmt_for : opt_label K_FOR for_control loop_body
Assert($3->cmd_type == PLPGSQL_STMT_DYNFORS); Assert($3->cmd_type == PLPGSQL_STMT_DYNFORS);
new = (PLpgSQL_stmt_dynfors *) $3; new = (PLpgSQL_stmt_dynfors *) $3;
new->label = $1; new->label = $1;
new->body = $4; new->body = $4.stmts;
$$ = (PLpgSQL_stmt *) new; $$ = (PLpgSQL_stmt *) new;
} }
check_labels($1, $4.end_label);
/* close namespace started in opt_label */ /* close namespace started in opt_label */
plpgsql_ns_pop(); plpgsql_ns_pop();
} }
...@@ -1037,7 +1048,7 @@ stmt_select : K_SELECT lno ...@@ -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; PLpgSQL_stmt_exit *new;
...@@ -1245,8 +1256,11 @@ raise_level : K_EXCEPTION ...@@ -1245,8 +1256,11 @@ raise_level : K_EXCEPTION
} }
; ;
loop_body : proc_sect K_END K_LOOP ';' loop_body : proc_sect K_END K_LOOP opt_label ';'
{ $$ = $1; } {
$$.stmts = $1;
$$.end_label = $4;
}
; ;
stmt_execsql : execsql_start lno stmt_execsql : execsql_start lno
...@@ -1596,7 +1610,7 @@ expr_until_loop : ...@@ -1596,7 +1610,7 @@ expr_until_loop :
{ $$ = plpgsql_read_expression(K_LOOP, "LOOP"); } { $$ = plpgsql_read_expression(K_LOOP, "LOOP"); }
; ;
opt_label : opt_block_label :
{ {
plpgsql_ns_push(NULL); plpgsql_ns_push(NULL);
$$ = NULL; $$ = NULL;
...@@ -1608,14 +1622,15 @@ opt_label : ...@@ -1608,14 +1622,15 @@ opt_label :
} }
; ;
opt_exitlabel : opt_label :
{ $$ = NULL; } {
$$ = NULL;
}
| T_LABEL | T_LABEL
{ {
char *name; char *label_name;
plpgsql_convert_ident(yytext, &label_name, 1);
plpgsql_convert_ident(yytext, &name, 1); $$ = label_name;
$$ = name;
} }
| T_WORD | T_WORD
{ {
...@@ -2210,4 +2225,29 @@ plpgsql_sql_error_callback(void *arg) ...@@ -2210,4 +2225,29 @@ plpgsql_sql_error_callback(void *arg)
errposition(0); 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" #include "pl_scan.c"
...@@ -2666,3 +2666,58 @@ drop function continue_test1(); ...@@ -2666,3 +2666,58 @@ drop function continue_test1();
drop function continue_test2(); drop function continue_test2();
drop function continue_test3(); drop function continue_test3();
drop table conttesttbl; 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
...@@ -2232,3 +2232,51 @@ drop function continue_test1(); ...@@ -2232,3 +2232,51 @@ drop function continue_test1();
drop function continue_test2(); drop function continue_test2();
drop function continue_test3(); drop function continue_test3();
drop table conttesttbl; 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;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment