mirror of https://github.com/postgres/postgres
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.
This commit is contained in:
parent
784b948984
commit
c425dcb4ec
|
@ -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><<<replaceable>label</replaceable>>></optional>
|
||||
<optional> <<<replaceable>label</replaceable>>> </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><<<replaceable>label</replaceable>>></optional>
|
||||
<optional> <<<replaceable>label</replaceable>>> </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><<<replaceable>label</replaceable>>></optional>
|
||||
<optional> <<<replaceable>label</replaceable>>> </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><<<replaceable>label</replaceable>>></optional>
|
||||
<optional> <<<replaceable>label</replaceable>>> </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><<<replaceable>label</replaceable>>></optional>
|
||||
<optional> <<<replaceable>label</replaceable>>> </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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue