summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNeil Conway2005-07-02 08:59:48 +0000
committerNeil Conway2005-07-02 08:59:48 +0000
commitc425dcb4ec9ac9aff9e241e0adcf309d2f419ba1 (patch)
tree0f56275f049fca1f4d18e561cdfbf14288159ed5
parent784b948984a529991f86bbefd4013ef98f984996 (diff)
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.
-rw-r--r--doc/src/sgml/plpgsql.sgml35
-rw-r--r--src/pl/plpgsql/src/gram.y104
-rw-r--r--src/test/regress/expected/plpgsql.out59
-rw-r--r--src/test/regress/sql/plpgsql.sql52
4 files changed, 197 insertions, 53 deletions
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index e8d687928f8..ad6b1c84944 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -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
diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y
index 5d3fd8259b5..6209d2d9dd8 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 <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"
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index a74b0e54666..42dcad5c2fb 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); <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
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index b0d49c0f321..3432b5556cc 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 $$
+<<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;