Commit 2e35d4f3 authored by Tom Lane's avatar Tom Lane

Modify the handling of RAISE without parameters so that the error it throws

can be caught in the same places that could catch an ordinary RAISE ERROR
in the same location.  The previous coding insisted on throwing the error
from the block containing the active exception handler; which is arguably
more surprising, and definitely unlike Oracle's behavior.

Not back-patching, since this is a pretty obscure corner case.  The risk
of breaking somebody's code in a minor version update seems to outweigh
any possible benefit.

Piyush Newe, reviewed by David Fetter
parent 4dfc4578
<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.156 2010/07/29 19:34:40 petere Exp $ --> <!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.157 2010/08/09 02:25:05 tgl Exp $ -->
<chapter id="plpgsql"> <chapter id="plpgsql">
<title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title> <title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
...@@ -2160,7 +2160,7 @@ BEGIN ...@@ -2160,7 +2160,7 @@ BEGIN
|| quote_ident(mviews.mv_name) || ' ...'); || quote_ident(mviews.mv_name) || ' ...');
EXECUTE 'TRUNCATE TABLE ' || quote_ident(mviews.mv_name); EXECUTE 'TRUNCATE TABLE ' || quote_ident(mviews.mv_name);
EXECUTE 'INSERT INTO ' EXECUTE 'INSERT INTO '
|| quote_ident(mviews.mv_name) || ' ' || quote_ident(mviews.mv_name) || ' '
|| mviews.mv_query; || mviews.mv_query;
END LOOP; END LOOP;
...@@ -2523,7 +2523,7 @@ OPEN <replaceable>unbound_cursorvar</replaceable> <optional> <optional> NO </opt ...@@ -2523,7 +2523,7 @@ OPEN <replaceable>unbound_cursorvar</replaceable> <optional> <optional> NO </opt
<para> <para>
An example: An example:
<programlisting> <programlisting>
OPEN curs1 FOR EXECUTE 'SELECT * FROM ' || quote_ident(tabname) OPEN curs1 FOR EXECUTE 'SELECT * FROM ' || quote_ident(tabname)
|| ' WHERE col1 = $1' USING keyvalue; || ' WHERE col1 = $1' USING keyvalue;
</programlisting> </programlisting>
In this example, the table name is inserted into the query textually, In this example, the table name is inserted into the query textually,
...@@ -3012,10 +3012,21 @@ RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id; ...@@ -3012,10 +3012,21 @@ RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;
The last variant of <command>RAISE</> has no parameters at all. The last variant of <command>RAISE</> has no parameters at all.
This form can only be used inside a <literal>BEGIN</> block's This form can only be used inside a <literal>BEGIN</> block's
<literal>EXCEPTION</> clause; <literal>EXCEPTION</> clause;
it causes the error currently being handled to be re-thrown to the it causes the error currently being handled to be re-thrown.
next enclosing block.
</para> </para>
<note>
<para>
Before <productname>PostgreSQL</> 9.1, <command>RAISE</> without
parameters was interpreted as re-throwing the error from the block
containing the active exception handler. Thus an <literal>EXCEPTION</>
clause nested within that handler could not catch it, even if the
<command>RAISE</> was within the nested <literal>EXCEPTION</> clause's
block. This was deemed surprising as well as being incompatible with
Oracle's PL/SQL.
</para>
</note>
<para> <para>
If no condition name nor SQLSTATE is specified in a If no condition name nor SQLSTATE is specified in a
<command>RAISE EXCEPTION</command> command, the default is to use <command>RAISE EXCEPTION</command> command, the default is to use
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.261 2010/07/06 19:19:01 momjian Exp $ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.262 2010/08/09 02:25:05 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -327,10 +327,6 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo) ...@@ -327,10 +327,6 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("CONTINUE cannot be used outside a loop"))); errmsg("CONTINUE cannot be used outside a loop")));
else if (rc == PLPGSQL_RC_RERAISE)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("RAISE without parameters cannot be used outside an exception handler")));
else else
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT), (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
...@@ -695,10 +691,6 @@ plpgsql_exec_trigger(PLpgSQL_function *func, ...@@ -695,10 +691,6 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("CONTINUE cannot be used outside a loop"))); errmsg("CONTINUE cannot be used outside a loop")));
else if (rc == PLPGSQL_RC_RERAISE)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("RAISE without parameters cannot be used outside an exception handler")));
else else
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT), (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
...@@ -1019,6 +1011,7 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block) ...@@ -1019,6 +1011,7 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
MemoryContext oldcontext = CurrentMemoryContext; MemoryContext oldcontext = CurrentMemoryContext;
ResourceOwner oldowner = CurrentResourceOwner; ResourceOwner oldowner = CurrentResourceOwner;
ExprContext *old_eval_econtext = estate->eval_econtext; ExprContext *old_eval_econtext = estate->eval_econtext;
ErrorData *save_cur_error = estate->cur_error;
estate->err_text = gettext_noop("during statement block entry"); estate->err_text = gettext_noop("during statement block entry");
...@@ -1130,6 +1123,12 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block) ...@@ -1130,6 +1123,12 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
unpack_sql_state(edata->sqlerrcode)); unpack_sql_state(edata->sqlerrcode));
assign_text_var(errm_var, edata->message); assign_text_var(errm_var, edata->message);
/*
* Also set up cur_error so the error data is accessible
* inside the handler.
*/
estate->cur_error = edata;
estate->err_text = NULL; estate->err_text = NULL;
rc = exec_stmts(estate, exception->action); rc = exec_stmts(estate, exception->action);
...@@ -1141,14 +1140,17 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block) ...@@ -1141,14 +1140,17 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
errm_var->value = (Datum) 0; errm_var->value = (Datum) 0;
errm_var->isnull = true; errm_var->isnull = true;
/* re-throw error if requested by handler */
if (rc == PLPGSQL_RC_RERAISE)
ReThrowError(edata);
break; break;
} }
} }
/*
* Restore previous state of cur_error, whether or not we executed
* a handler. This is needed in case an error got thrown from
* some inner block's exception handler.
*/
estate->cur_error = save_cur_error;
/* If no match found, re-throw the error */ /* If no match found, re-throw the error */
if (e == NULL) if (e == NULL)
ReThrowError(edata); ReThrowError(edata);
...@@ -1156,6 +1158,8 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block) ...@@ -1156,6 +1158,8 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
FreeErrorData(edata); FreeErrorData(edata);
} }
PG_END_TRY(); PG_END_TRY();
Assert(save_cur_error == estate->cur_error);
} }
else else
{ {
...@@ -1177,7 +1181,6 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block) ...@@ -1177,7 +1181,6 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
case PLPGSQL_RC_OK: case PLPGSQL_RC_OK:
case PLPGSQL_RC_RETURN: case PLPGSQL_RC_RETURN:
case PLPGSQL_RC_CONTINUE: case PLPGSQL_RC_CONTINUE:
case PLPGSQL_RC_RERAISE:
return rc; return rc;
case PLPGSQL_RC_EXIT: case PLPGSQL_RC_EXIT:
...@@ -1599,7 +1602,6 @@ exec_stmt_loop(PLpgSQL_execstate *estate, PLpgSQL_stmt_loop *stmt) ...@@ -1599,7 +1602,6 @@ exec_stmt_loop(PLpgSQL_execstate *estate, PLpgSQL_stmt_loop *stmt)
break; break;
case PLPGSQL_RC_RETURN: case PLPGSQL_RC_RETURN:
case PLPGSQL_RC_RERAISE:
return rc; return rc;
default: default:
...@@ -1663,7 +1665,6 @@ exec_stmt_while(PLpgSQL_execstate *estate, PLpgSQL_stmt_while *stmt) ...@@ -1663,7 +1665,6 @@ exec_stmt_while(PLpgSQL_execstate *estate, PLpgSQL_stmt_while *stmt)
break; break;
case PLPGSQL_RC_RETURN: case PLPGSQL_RC_RETURN:
case PLPGSQL_RC_RERAISE:
return rc; return rc;
default: default:
...@@ -1782,8 +1783,7 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt) ...@@ -1782,8 +1783,7 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
*/ */
rc = exec_stmts(estate, stmt->body); rc = exec_stmts(estate, stmt->body);
if (rc == PLPGSQL_RC_RETURN || if (rc == PLPGSQL_RC_RETURN)
rc == PLPGSQL_RC_RERAISE)
break; /* break out of the loop */ break; /* break out of the loop */
else if (rc == PLPGSQL_RC_EXIT) else if (rc == PLPGSQL_RC_EXIT)
{ {
...@@ -2437,7 +2437,14 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt) ...@@ -2437,7 +2437,14 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
/* RAISE with no parameters: re-throw current exception */ /* RAISE with no parameters: re-throw current exception */
if (stmt->condname == NULL && stmt->message == NULL && if (stmt->condname == NULL && stmt->message == NULL &&
stmt->options == NIL) stmt->options == NIL)
return PLPGSQL_RC_RERAISE; {
if (estate->cur_error != NULL)
ReThrowError(estate->cur_error);
/* oops, we're not inside a handler */
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("RAISE without parameters cannot be used outside an exception handler")));
}
if (stmt->condname) if (stmt->condname)
{ {
...@@ -2637,6 +2644,7 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate, ...@@ -2637,6 +2644,7 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
estate->rettupdesc = NULL; estate->rettupdesc = NULL;
estate->exitlabel = NULL; estate->exitlabel = NULL;
estate->cur_error = NULL;
estate->tuple_store = NULL; estate->tuple_store = NULL;
if (rsi) if (rsi)
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.130 2010/02/26 02:01:35 momjian Exp $ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.131 2010/08/09 02:25:05 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -115,8 +115,7 @@ enum ...@@ -115,8 +115,7 @@ enum
PLPGSQL_RC_OK, PLPGSQL_RC_OK,
PLPGSQL_RC_EXIT, PLPGSQL_RC_EXIT,
PLPGSQL_RC_RETURN, PLPGSQL_RC_RETURN,
PLPGSQL_RC_CONTINUE, PLPGSQL_RC_CONTINUE
PLPGSQL_RC_RERAISE
}; };
/* ---------- /* ----------
...@@ -699,6 +698,7 @@ typedef struct PLpgSQL_execstate ...@@ -699,6 +698,7 @@ typedef struct PLpgSQL_execstate
TupleDesc rettupdesc; TupleDesc rettupdesc;
char *exitlabel; /* the "target" label of the current EXIT or char *exitlabel; /* the "target" label of the current EXIT or
* CONTINUE stmt, if any */ * CONTINUE stmt, if any */
ErrorData *cur_error; /* current exception handler's error */
Tuplestorestate *tuple_store; /* SRFs accumulate results here */ Tuplestorestate *tuple_store; /* SRFs accumulate results here */
MemoryContext tuple_store_cxt; MemoryContext tuple_store_cxt;
......
...@@ -2312,6 +2312,35 @@ $$ language plpgsql; ...@@ -2312,6 +2312,35 @@ $$ language plpgsql;
select raise_test2(10); select raise_test2(10);
ERROR: too few parameters specified for RAISE ERROR: too few parameters specified for RAISE
CONTEXT: PL/pgSQL function "raise_test2" line 3 at RAISE CONTEXT: PL/pgSQL function "raise_test2" line 3 at RAISE
-- Test re-RAISE inside a nested exception block. This case is allowed
-- by Oracle's PL/SQL but was handled differently by PG before 9.1.
CREATE FUNCTION reraise_test() RETURNS void AS $$
BEGIN
BEGIN
RAISE syntax_error;
EXCEPTION
WHEN syntax_error THEN
BEGIN
raise notice 'exception % thrown in inner block, reraising', sqlerrm;
RAISE;
EXCEPTION
WHEN OTHERS THEN
raise notice 'RIGHT - exception % caught in inner block', sqlerrm;
END;
END;
EXCEPTION
WHEN OTHERS THEN
raise notice 'WRONG - exception % caught in outer block', sqlerrm;
END;
$$ LANGUAGE plpgsql;
SELECT reraise_test();
NOTICE: exception syntax_error thrown in inner block, reraising
NOTICE: RIGHT - exception syntax_error caught in inner block
reraise_test
--------------
(1 row)
-- --
-- reject function definitions that contain malformed SQL queries at -- reject function definitions that contain malformed SQL queries at
-- compile-time, where possible -- compile-time, where possible
...@@ -3577,7 +3606,7 @@ end; ...@@ -3577,7 +3606,7 @@ end;
$$ language plpgsql; $$ language plpgsql;
select raise_test(); select raise_test();
ERROR: RAISE without parameters cannot be used outside an exception handler ERROR: RAISE without parameters cannot be used outside an exception handler
CONTEXT: PL/pgSQL function "raise_test" CONTEXT: PL/pgSQL function "raise_test" line 3 at RAISE
-- check cases where implicit SQLSTATE variable could be confused with -- check cases where implicit SQLSTATE variable could be confused with
-- SQLSTATE as a keyword, cf bug #5524 -- SQLSTATE as a keyword, cf bug #5524
create or replace function raise_test() returns void as $$ create or replace function raise_test() returns void as $$
......
...@@ -1966,6 +1966,31 @@ $$ language plpgsql; ...@@ -1966,6 +1966,31 @@ $$ language plpgsql;
select raise_test2(10); select raise_test2(10);
-- Test re-RAISE inside a nested exception block. This case is allowed
-- by Oracle's PL/SQL but was handled differently by PG before 9.1.
CREATE FUNCTION reraise_test() RETURNS void AS $$
BEGIN
BEGIN
RAISE syntax_error;
EXCEPTION
WHEN syntax_error THEN
BEGIN
raise notice 'exception % thrown in inner block, reraising', sqlerrm;
RAISE;
EXCEPTION
WHEN OTHERS THEN
raise notice 'RIGHT - exception % caught in inner block', sqlerrm;
END;
END;
EXCEPTION
WHEN OTHERS THEN
raise notice 'WRONG - exception % caught in outer block', sqlerrm;
END;
$$ LANGUAGE plpgsql;
SELECT reraise_test();
-- --
-- reject function definitions that contain malformed SQL queries at -- reject function definitions that contain malformed SQL queries at
-- compile-time, where possible -- compile-time, where possible
......
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