Skip to content

Commit 2e35d4f

Browse files
committed
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
1 parent 4dfc457 commit 2e35d4f

File tree

5 files changed

+101
-28
lines changed

5 files changed

+101
-28
lines changed

doc/src/sgml/plpgsql.sgml

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.156 2010/07/29 19:34:40 petere Exp $ -->
1+
<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.157 2010/08/09 02:25:05 tgl Exp $ -->
22

33
<chapter id="plpgsql">
44
<title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
@@ -2160,7 +2160,7 @@ BEGIN
21602160
|| quote_ident(mviews.mv_name) || ' ...');
21612161
EXECUTE 'TRUNCATE TABLE ' || quote_ident(mviews.mv_name);
21622162
EXECUTE 'INSERT INTO '
2163-
|| quote_ident(mviews.mv_name) || ' '
2163+
|| quote_ident(mviews.mv_name) || ' '
21642164
|| mviews.mv_query;
21652165
END LOOP;
21662166

@@ -2523,7 +2523,7 @@ OPEN <replaceable>unbound_cursorvar</replaceable> <optional> <optional> NO </opt
25232523
<para>
25242524
An example:
25252525
<programlisting>
2526-
OPEN curs1 FOR EXECUTE 'SELECT * FROM ' || quote_ident(tabname)
2526+
OPEN curs1 FOR EXECUTE 'SELECT * FROM ' || quote_ident(tabname)
25272527
|| ' WHERE col1 = $1' USING keyvalue;
25282528
</programlisting>
25292529
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;
30123012
The last variant of <command>RAISE</> has no parameters at all.
30133013
This form can only be used inside a <literal>BEGIN</> block's
30143014
<literal>EXCEPTION</> clause;
3015-
it causes the error currently being handled to be re-thrown to the
3016-
next enclosing block.
3015+
it causes the error currently being handled to be re-thrown.
30173016
</para>
30183017

3018+
<note>
3019+
<para>
3020+
Before <productname>PostgreSQL</> 9.1, <command>RAISE</> without
3021+
parameters was interpreted as re-throwing the error from the block
3022+
containing the active exception handler. Thus an <literal>EXCEPTION</>
3023+
clause nested within that handler could not catch it, even if the
3024+
<command>RAISE</> was within the nested <literal>EXCEPTION</> clause's
3025+
block. This was deemed surprising as well as being incompatible with
3026+
Oracle's PL/SQL.
3027+
</para>
3028+
</note>
3029+
30193030
<para>
30203031
If no condition name nor SQLSTATE is specified in a
30213032
<command>RAISE EXCEPTION</command> command, the default is to use

src/pl/plpgsql/src/pl_exec.c

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.261 2010/07/06 19:19:01 momjian Exp $
11+
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.262 2010/08/09 02:25:05 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -327,10 +327,6 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
327327
ereport(ERROR,
328328
(errcode(ERRCODE_SYNTAX_ERROR),
329329
errmsg("CONTINUE cannot be used outside a loop")));
330-
else if (rc == PLPGSQL_RC_RERAISE)
331-
ereport(ERROR,
332-
(errcode(ERRCODE_SYNTAX_ERROR),
333-
errmsg("RAISE without parameters cannot be used outside an exception handler")));
334330
else
335331
ereport(ERROR,
336332
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
@@ -695,10 +691,6 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
695691
ereport(ERROR,
696692
(errcode(ERRCODE_SYNTAX_ERROR),
697693
errmsg("CONTINUE cannot be used outside a loop")));
698-
else if (rc == PLPGSQL_RC_RERAISE)
699-
ereport(ERROR,
700-
(errcode(ERRCODE_SYNTAX_ERROR),
701-
errmsg("RAISE without parameters cannot be used outside an exception handler")));
702694
else
703695
ereport(ERROR,
704696
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
@@ -1019,6 +1011,7 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
10191011
MemoryContext oldcontext = CurrentMemoryContext;
10201012
ResourceOwner oldowner = CurrentResourceOwner;
10211013
ExprContext *old_eval_econtext = estate->eval_econtext;
1014+
ErrorData *save_cur_error = estate->cur_error;
10221015

10231016
estate->err_text = gettext_noop("during statement block entry");
10241017

@@ -1130,6 +1123,12 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
11301123
unpack_sql_state(edata->sqlerrcode));
11311124
assign_text_var(errm_var, edata->message);
11321125

1126+
/*
1127+
* Also set up cur_error so the error data is accessible
1128+
* inside the handler.
1129+
*/
1130+
estate->cur_error = edata;
1131+
11331132
estate->err_text = NULL;
11341133

11351134
rc = exec_stmts(estate, exception->action);
@@ -1141,21 +1140,26 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
11411140
errm_var->value = (Datum) 0;
11421141
errm_var->isnull = true;
11431142

1144-
/* re-throw error if requested by handler */
1145-
if (rc == PLPGSQL_RC_RERAISE)
1146-
ReThrowError(edata);
1147-
11481143
break;
11491144
}
11501145
}
11511146

1147+
/*
1148+
* Restore previous state of cur_error, whether or not we executed
1149+
* a handler. This is needed in case an error got thrown from
1150+
* some inner block's exception handler.
1151+
*/
1152+
estate->cur_error = save_cur_error;
1153+
11521154
/* If no match found, re-throw the error */
11531155
if (e == NULL)
11541156
ReThrowError(edata);
11551157
else
11561158
FreeErrorData(edata);
11571159
}
11581160
PG_END_TRY();
1161+
1162+
Assert(save_cur_error == estate->cur_error);
11591163
}
11601164
else
11611165
{
@@ -1177,7 +1181,6 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
11771181
case PLPGSQL_RC_OK:
11781182
case PLPGSQL_RC_RETURN:
11791183
case PLPGSQL_RC_CONTINUE:
1180-
case PLPGSQL_RC_RERAISE:
11811184
return rc;
11821185

11831186
case PLPGSQL_RC_EXIT:
@@ -1599,7 +1602,6 @@ exec_stmt_loop(PLpgSQL_execstate *estate, PLpgSQL_stmt_loop *stmt)
15991602
break;
16001603

16011604
case PLPGSQL_RC_RETURN:
1602-
case PLPGSQL_RC_RERAISE:
16031605
return rc;
16041606

16051607
default:
@@ -1663,7 +1665,6 @@ exec_stmt_while(PLpgSQL_execstate *estate, PLpgSQL_stmt_while *stmt)
16631665
break;
16641666

16651667
case PLPGSQL_RC_RETURN:
1666-
case PLPGSQL_RC_RERAISE:
16671668
return rc;
16681669

16691670
default:
@@ -1782,8 +1783,7 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
17821783
*/
17831784
rc = exec_stmts(estate, stmt->body);
17841785

1785-
if (rc == PLPGSQL_RC_RETURN ||
1786-
rc == PLPGSQL_RC_RERAISE)
1786+
if (rc == PLPGSQL_RC_RETURN)
17871787
break; /* break out of the loop */
17881788
else if (rc == PLPGSQL_RC_EXIT)
17891789
{
@@ -2437,7 +2437,14 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
24372437
/* RAISE with no parameters: re-throw current exception */
24382438
if (stmt->condname == NULL && stmt->message == NULL &&
24392439
stmt->options == NIL)
2440-
return PLPGSQL_RC_RERAISE;
2440+
{
2441+
if (estate->cur_error != NULL)
2442+
ReThrowError(estate->cur_error);
2443+
/* oops, we're not inside a handler */
2444+
ereport(ERROR,
2445+
(errcode(ERRCODE_SYNTAX_ERROR),
2446+
errmsg("RAISE without parameters cannot be used outside an exception handler")));
2447+
}
24412448

24422449
if (stmt->condname)
24432450
{
@@ -2637,6 +2644,7 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
26372644

26382645
estate->rettupdesc = NULL;
26392646
estate->exitlabel = NULL;
2647+
estate->cur_error = NULL;
26402648

26412649
estate->tuple_store = NULL;
26422650
if (rsi)

src/pl/plpgsql/src/plpgsql.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.130 2010/02/26 02:01:35 momjian Exp $
11+
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.131 2010/08/09 02:25:05 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -115,8 +115,7 @@ enum
115115
PLPGSQL_RC_OK,
116116
PLPGSQL_RC_EXIT,
117117
PLPGSQL_RC_RETURN,
118-
PLPGSQL_RC_CONTINUE,
119-
PLPGSQL_RC_RERAISE
118+
PLPGSQL_RC_CONTINUE
120119
};
121120

122121
/* ----------
@@ -699,6 +698,7 @@ typedef struct PLpgSQL_execstate
699698
TupleDesc rettupdesc;
700699
char *exitlabel; /* the "target" label of the current EXIT or
701700
* CONTINUE stmt, if any */
701+
ErrorData *cur_error; /* current exception handler's error */
702702

703703
Tuplestorestate *tuple_store; /* SRFs accumulate results here */
704704
MemoryContext tuple_store_cxt;

src/test/regress/expected/plpgsql.out

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2312,6 +2312,35 @@ $$ language plpgsql;
23122312
select raise_test2(10);
23132313
ERROR: too few parameters specified for RAISE
23142314
CONTEXT: PL/pgSQL function "raise_test2" line 3 at RAISE
2315+
-- Test re-RAISE inside a nested exception block. This case is allowed
2316+
-- by Oracle's PL/SQL but was handled differently by PG before 9.1.
2317+
CREATE FUNCTION reraise_test() RETURNS void AS $$
2318+
BEGIN
2319+
BEGIN
2320+
RAISE syntax_error;
2321+
EXCEPTION
2322+
WHEN syntax_error THEN
2323+
BEGIN
2324+
raise notice 'exception % thrown in inner block, reraising', sqlerrm;
2325+
RAISE;
2326+
EXCEPTION
2327+
WHEN OTHERS THEN
2328+
raise notice 'RIGHT - exception % caught in inner block', sqlerrm;
2329+
END;
2330+
END;
2331+
EXCEPTION
2332+
WHEN OTHERS THEN
2333+
raise notice 'WRONG - exception % caught in outer block', sqlerrm;
2334+
END;
2335+
$$ LANGUAGE plpgsql;
2336+
SELECT reraise_test();
2337+
NOTICE: exception syntax_error thrown in inner block, reraising
2338+
NOTICE: RIGHT - exception syntax_error caught in inner block
2339+
reraise_test
2340+
--------------
2341+
2342+
(1 row)
2343+
23152344
--
23162345
-- reject function definitions that contain malformed SQL queries at
23172346
-- compile-time, where possible
@@ -3577,7 +3606,7 @@ end;
35773606
$$ language plpgsql;
35783607
select raise_test();
35793608
ERROR: RAISE without parameters cannot be used outside an exception handler
3580-
CONTEXT: PL/pgSQL function "raise_test"
3609+
CONTEXT: PL/pgSQL function "raise_test" line 3 at RAISE
35813610
-- check cases where implicit SQLSTATE variable could be confused with
35823611
-- SQLSTATE as a keyword, cf bug #5524
35833612
create or replace function raise_test() returns void as $$

src/test/regress/sql/plpgsql.sql

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1966,6 +1966,31 @@ $$ language plpgsql;
19661966

19671967
select raise_test2(10);
19681968

1969+
-- Test re-RAISE inside a nested exception block. This case is allowed
1970+
-- by Oracle's PL/SQL but was handled differently by PG before 9.1.
1971+
1972+
CREATE FUNCTION reraise_test() RETURNS void AS $$
1973+
BEGIN
1974+
BEGIN
1975+
RAISE syntax_error;
1976+
EXCEPTION
1977+
WHEN syntax_error THEN
1978+
BEGIN
1979+
raise notice 'exception % thrown in inner block, reraising', sqlerrm;
1980+
RAISE;
1981+
EXCEPTION
1982+
WHEN OTHERS THEN
1983+
raise notice 'RIGHT - exception % caught in inner block', sqlerrm;
1984+
END;
1985+
END;
1986+
EXCEPTION
1987+
WHEN OTHERS THEN
1988+
raise notice 'WRONG - exception % caught in outer block', sqlerrm;
1989+
END;
1990+
$$ LANGUAGE plpgsql;
1991+
1992+
SELECT reraise_test();
1993+
19691994
--
19701995
-- reject function definitions that contain malformed SQL queries at
19711996
-- compile-time, where possible

0 commit comments

Comments
 (0)