*** a/doc/src/sgml/plpgsql.sgml
--- b/doc/src/sgml/plpgsql.sgml
***************
*** 2107,2113 **** END LOOP label ;
! EXIT label WHEN boolean-expression ;
--- 2107,2113 ----
! EXIT label USING ROLLBACK WHEN boolean-expression ;
***************
*** 2121,2126 **** EXIT label WHEN
+ If USING ROLLBACK> is specified, instead of persisting the
+ changes made inside the escaped BEGIN> blocks, they are
+ rolled back. The label> must be the label of the
+ current or an outer level BEGIN> block with an
+ EXCEPTION> block.
+
+
+
If WHEN> is specified, the loop exit occurs only if
boolean-expression> is true. Otherwise, control passes
to the statement after EXIT>.
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
***************
*** 1181,1188 **** exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
resTypByVal, resTypLen);
}
! /* Commit the inner transaction, return to outer xact context */
! ReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
--- 1181,1192 ----
resTypByVal, resTypLen);
}
! if (rc == PLPGSQL_RC_EXIT && estate->exitrollback)
! RollbackAndReleaseCurrentSubTransaction();
! else
! /* Commit the inner transaction, return to outer xact context */
! ReleaseCurrentSubTransaction();
!
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
***************
*** 1330,1335 **** exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
--- 1334,1347 ----
return PLPGSQL_RC_EXIT;
if (strcmp(block->label, estate->exitlabel) != 0)
return PLPGSQL_RC_EXIT;
+ if (estate->exitrollback)
+ {
+ if (!block->exceptions)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("the BEGIN block targeted by EXIT USING ROLLBACK must have an EXCEPTION clause")));
+ estate->exitrollback = false;
+ }
estate->exitlabel = NULL;
return PLPGSQL_RC_OK;
***************
*** 1789,1794 **** exec_stmt_loop(PLpgSQL_execstate *estate, PLpgSQL_stmt_loop *stmt)
--- 1801,1810 ----
return PLPGSQL_RC_EXIT;
if (strcmp(stmt->label, estate->exitlabel) != 0)
return PLPGSQL_RC_EXIT;
+ if (estate->exitrollback)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("the target of EXIT USING ROLLBACK must be a BEGIN block")));
estate->exitlabel = NULL;
return PLPGSQL_RC_OK;
***************
*** 1850,1855 **** exec_stmt_while(PLpgSQL_execstate *estate, PLpgSQL_stmt_while *stmt)
--- 1866,1875 ----
return PLPGSQL_RC_EXIT;
if (strcmp(stmt->label, estate->exitlabel) != 0)
return PLPGSQL_RC_EXIT;
+ if (estate->exitrollback)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("the target of EXIT USING ROLLBACK must be a BEGIN block")));
estate->exitlabel = NULL;
return PLPGSQL_RC_OK;
***************
*** 1996,2001 **** exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
--- 2016,2025 ----
strcmp(stmt->label, estate->exitlabel) == 0)
{
/* labelled exit, matches the current stmt's label */
+ if (estate->exitrollback)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("the target of EXIT USING ROLLBACK must be a BEGIN block")));
estate->exitlabel = NULL;
rc = PLPGSQL_RC_OK;
}
***************
*** 2348,2353 **** exec_stmt_foreach_a(PLpgSQL_execstate *estate, PLpgSQL_stmt_foreach_a *stmt)
--- 2372,2381 ----
strcmp(stmt->label, estate->exitlabel) == 0)
{
/* labelled exit, matches the current stmt's label */
+ if (estate->exitrollback)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("the target of EXIT USING ROLLBACK must be a BEGIN block")));
estate->exitlabel = NULL;
rc = PLPGSQL_RC_OK;
}
***************
*** 2423,2428 **** exec_stmt_exit(PLpgSQL_execstate *estate, PLpgSQL_stmt_exit *stmt)
--- 2451,2457 ----
}
estate->exitlabel = stmt->label;
+ estate->exitrollback = stmt->rollback;
if (stmt->is_exit)
return PLPGSQL_RC_EXIT;
else
***************
*** 3125,3130 **** plpgsql_estate_setup(PLpgSQL_execstate *estate,
--- 3154,3160 ----
estate->rettupdesc = NULL;
estate->exitlabel = NULL;
+ estate->exitrollback = false;
estate->cur_error = NULL;
estate->tuple_store = NULL;
***************
*** 4995,5000 **** exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
--- 5025,5034 ----
strcmp(stmt->label, estate->exitlabel) == 0)
{
/* label matches this loop, so exit loop */
+ if (estate->exitrollback)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("the target of EXIT USING ROLLBACK must be a BEGIN block")));
estate->exitlabel = NULL;
rc = PLPGSQL_RC_OK;
}
*** a/src/pl/plpgsql/src/pl_funcs.c
--- b/src/pl/plpgsql/src/pl_funcs.c
***************
*** 1209,1214 **** dump_exit(PLpgSQL_stmt_exit *stmt)
--- 1209,1216 ----
{
dump_ind();
printf("%s", stmt->is_exit ? "EXIT" : "CONTINUE");
+ if (stmt->rollback)
+ printf(" USING ROLLBACK");
if (stmt->label != NULL)
printf(" label='%s'", stmt->label);
if (stmt->cond != NULL)
*** a/src/pl/plpgsql/src/pl_gram.y
--- b/src/pl/plpgsql/src/pl_gram.y
***************
*** 178,183 **** static List *read_raise_options(void);
--- 178,184 ----
%type expr_until_semi expr_until_rightbracket
%type expr_until_then expr_until_loop opt_expr_until_when
%type opt_exitcond
+ %type opt_using_rollback
%type assign_var foreach_slice
%type cursor_variable
***************
*** 1660,1675 **** foreach_slice :
}
;
! stmt_exit : exit_type opt_label opt_exitcond
{
PLpgSQL_stmt_exit *new;
new = palloc0(sizeof(PLpgSQL_stmt_exit));
new->cmd_type = PLPGSQL_STMT_EXIT;
new->is_exit = $1;
new->lineno = plpgsql_location_to_lineno(@1);
! new->label = $2;
! new->cond = $3;
$$ = (PLpgSQL_stmt *)new;
}
--- 1661,1688 ----
}
;
! stmt_exit : exit_type opt_using_rollback opt_label opt_exitcond
{
PLpgSQL_stmt_exit *new;
new = palloc0(sizeof(PLpgSQL_stmt_exit));
new->cmd_type = PLPGSQL_STMT_EXIT;
new->is_exit = $1;
+ new->rollback = $2;
new->lineno = plpgsql_location_to_lineno(@1);
! new->label = $3;
! new->cond = $4;
!
! if (new->rollback && !new->is_exit)
! ereport(ERROR,
! (errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("USING ROLLBACK is only supported with EXIT"),
! parser_errposition(@3)));
! else if (new->rollback && new->label == NULL)
! ereport(ERROR,
! (errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("EXIT USING ROLLBACK requires a label"),
! parser_errposition(@3)));
$$ = (PLpgSQL_stmt *)new;
}
***************
*** 1685,1690 **** exit_type : K_EXIT
--- 1698,1716 ----
}
;
+ opt_using_rollback :
+ {
+ $$ = false;
+ }
+ |
+ K_USING T_WORD
+ {
+ if (strcmp($2.ident, "rollback") != 0)
+ yyerror("syntax error");
+ $$ = true;
+ }
+ ;
+
stmt_return : K_RETURN
{
int tok;
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
***************
*** 582,587 **** typedef struct
--- 582,588 ----
int cmd_type;
int lineno;
bool is_exit; /* Is this an exit or a continue? */
+ bool rollback; /* If this is an exit, true if we should roll back the current subtxn */
char *label; /* NULL if it's an unlabelled EXIT/CONTINUE */
PLpgSQL_expr *cond;
} PLpgSQL_stmt_exit;
***************
*** 770,775 **** typedef struct PLpgSQL_execstate
--- 771,778 ----
TupleDesc rettupdesc;
char *exitlabel; /* the "target" label of the current EXIT or
* CONTINUE stmt, if any */
+ bool exitrollback; /* true if we should also roll back when exiting
+ the BEGIN/END block matching "exitlabel" */
ErrorData *cur_error; /* current exception handler's error */
Tuplestorestate *tuple_store; /* SRFs accumulate results here */
*** a/src/test/regress/expected/plpgsql.out
--- b/src/test/regress/expected/plpgsql.out
***************
*** 2896,2901 **** $$ language plpgsql;
--- 2896,3026 ----
ERROR: end label "outer_label" specified for unlabelled block
LINE 6: end loop outer_label;
^
+ -- exit using rollback
+ create temporary table foo(a text not null);
+ create function exit_using_rollback() returns void as $$
+ begin
+ <>
+ begin
+ insert into foo values ('not ok');
+ exit using rollback subtxn;
+ exception when others then
+ raise;
+ end;
+ insert into foo values ('ok');
+ end;
+ $$ language plpgsql;
+ select exit_using_rollback();
+ exit_using_rollback
+ ---------------------
+
+ (1 row)
+
+ select * from foo;
+ a
+ ----
+ ok
+ (1 row)
+
+ -- roll back two blocks at once
+ create or replace function exit_using_rollback() returns void as $$
+ begin
+ <>
+ begin
+ insert into foo values ('not ok');
+ begin
+ insert into foo values ('not ok');
+ exit using rollback subtxn;
+ end;
+ exception when others then
+ raise;
+ end;
+ end;
+ $$ language plpgsql;
+ select exit_using_rollback();
+ exit_using_rollback
+ ---------------------
+
+ (1 row)
+
+ select * from foo;
+ a
+ ----
+ ok
+ (1 row)
+
+ -- roll back two blocks at once, both with EXCEPTION blocks
+ create or replace function exit_using_rollback() returns void as $$
+ begin
+ <>
+ begin
+ insert into foo values ('not ok');
+ begin
+ insert into foo values ('not ok');
+ exit using rollback subtxn;
+ exception when others then
+ raise;
+ end;
+ exception when others then
+ raise;
+ end;
+ end;
+ $$ language plpgsql;
+ select exit_using_rollback();
+ exit_using_rollback
+ ---------------------
+
+ (1 row)
+
+ select * from foo;
+ a
+ ----
+ ok
+ (1 row)
+
+ drop table foo;
+ -- should fail: not allowed to target LOOP label (runtime failure)
+ create or replace function exit_using_rollback() returns void as $$
+ begin
+ <>
+ loop
+ exit using rollback invalid;
+ end loop;
+ end;
+ $$ language plpgsql;
+ select exit_using_rollback();
+ ERROR: the target of EXIT USING ROLLBACK must be a BEGIN block
+ CONTEXT: PL/pgSQL function exit_using_rollback() line 4 at LOOP
+ -- should fail: invalid syntax
+ create or replace function exit_using_rollback() returns void as $$
+ begin
+ continue using rollback;
+ end;
+ $$ language plpgsql;
+ ERROR: USING ROLLBACK is only supported with EXIT
+ LINE 3: continue using rollback;
+ ^
+ -- should fail: requires label
+ create or replace function exit_using_rollback() returns void as $$
+ begin
+ exit using rollback;
+ end;
+ $$ language plpgsql;
+ ERROR: EXIT USING ROLLBACK requires a label
+ LINE 3: exit using rollback;
+ ^
+ -- should fail: requires exception handler (runtime failure)
+ create or replace function exit_using_rollback() returns void as $$
+ begin
+ <>
+ begin
+ exit using rollback nosubtxn;
+ end;
+ end;
+ $$ language plpgsql;
+ select exit_using_rollback();
+ ERROR: the BEGIN block targeted by EXIT USING ROLLBACK must have an EXCEPTION clause
+ CONTEXT: PL/pgSQL function exit_using_rollback() line 4 at statement block
-- using list of scalars in fori and fore stmts
create function for_vect() returns void as $proc$
<>declare a integer; b varchar; c varchar; r record;
*** a/src/test/regress/sql/plpgsql.sql
--- b/src/test/regress/sql/plpgsql.sql
***************
*** 2429,2434 **** begin
--- 2429,2532 ----
end;
$$ language plpgsql;
+ -- exit using rollback
+ create temporary table foo(a text not null);
+ create function exit_using_rollback() returns void as $$
+ begin
+ <>
+ begin
+ insert into foo values ('not ok');
+ exit using rollback subtxn;
+ exception when others then
+ raise;
+ end;
+ insert into foo values ('ok');
+ end;
+ $$ language plpgsql;
+
+ select exit_using_rollback();
+ select * from foo;
+
+ -- roll back two blocks at once
+ create or replace function exit_using_rollback() returns void as $$
+ begin
+ <>
+ begin
+ insert into foo values ('not ok');
+ begin
+ insert into foo values ('not ok');
+ exit using rollback subtxn;
+ end;
+ exception when others then
+ raise;
+ end;
+ end;
+ $$ language plpgsql;
+
+ select exit_using_rollback();
+ select * from foo;
+
+ -- roll back two blocks at once, both with EXCEPTION blocks
+ create or replace function exit_using_rollback() returns void as $$
+ begin
+ <>
+ begin
+ insert into foo values ('not ok');
+ begin
+ insert into foo values ('not ok');
+ exit using rollback subtxn;
+ exception when others then
+ raise;
+ end;
+ exception when others then
+ raise;
+ end;
+ end;
+ $$ language plpgsql;
+
+ select exit_using_rollback();
+ select * from foo;
+
+ drop table foo;
+
+ -- should fail: not allowed to target LOOP label (runtime failure)
+ create or replace function exit_using_rollback() returns void as $$
+ begin
+ <>
+ loop
+ exit using rollback invalid;
+ end loop;
+ end;
+ $$ language plpgsql;
+
+ select exit_using_rollback();
+
+ -- should fail: invalid syntax
+ create or replace function exit_using_rollback() returns void as $$
+ begin
+ continue using rollback;
+ end;
+ $$ language plpgsql;
+
+ -- should fail: requires label
+ create or replace function exit_using_rollback() returns void as $$
+ begin
+ exit using rollback;
+ end;
+ $$ language plpgsql;
+
+ -- should fail: requires exception handler (runtime failure)
+ create or replace function exit_using_rollback() returns void as $$
+ begin
+ <>
+ begin
+ exit using rollback nosubtxn;
+ end;
+ end;
+ $$ language plpgsql;
+
+ select exit_using_rollback();
+
-- using list of scalars in fori and fore stmts
create function for_vect() returns void as $proc$
<>declare a integer; b varchar; c varchar; r record;