diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index d96c123e21c0ec87c0de013c7e3b254f61539a00..c043c78fea3efb3ddf8ded119967a3be8b28067f 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.70 2005/06/07 02:47:15 neilc Exp $ +$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.71 2005/06/10 16:23:09 neilc Exp $ --> <chapter id="plpgsql"> @@ -2110,6 +2110,17 @@ END; don't use <literal>EXCEPTION</> without need. </para> </tip> + + <para> + Within an exception handler, the <varname>SQLSTATE</varname> + variable contains the error code that corresponds to the + exception that was raised (refer to <xref + linkend="errcodes-table"> for a list of possible error + codes). The <varname>SQLERRM</varname> variable contains the + error message associated with the exception. These variables are + undefined outside exception handlers. + </para> + <example id="plpgsql-upsert-example"> <title>Exceptions with UPDATE/INSERT</title> <para> diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 122a0a92a55df74d5ea12d4c7a60ce24e4496991..3e883887ee811795002b9d50cd9d908e9cf45b30 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -42,7 +42,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.159 2005/06/09 22:29:52 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.160 2005/06/10 16:23:10 neilc Exp $ * *------------------------------------------------------------------------- */ @@ -1482,6 +1482,26 @@ log_line_prefix(StringInfo buf) } } +/* + * Unpack MAKE_SQLSTATE code. Note that this returns a pointer to a + * static buffer. + */ +char * +unpack_sql_state(int sql_state) +{ + static char buf[12]; + int i; + + for (i = 0; i < 5; i++) + { + buf[i] = PGUNSIXBIT(sql_state); + sql_state >>= 6; + } + + buf[i] = '\0'; + return buf; +} + /* * Write error report to server's log @@ -1497,21 +1517,7 @@ send_message_to_server_log(ErrorData *edata) appendStringInfo(&buf, "%s: ", error_severity(edata->elevel)); if (Log_error_verbosity >= PGERROR_VERBOSE) - { - /* unpack MAKE_SQLSTATE code */ - char tbuf[12]; - int ssval; - int i; - - ssval = edata->sqlerrcode; - for (i = 0; i < 5; i++) - { - tbuf[i] = PGUNSIXBIT(ssval); - ssval >>= 6; - } - tbuf[i] = '\0'; - appendStringInfo(&buf, "%s: ", tbuf); - } + appendStringInfo(&buf, "%s: ", unpack_sql_state(edata->sqlerrcode)); if (edata->message) append_with_tabs(&buf, edata->message); diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h index 0b7d3f17ebb40d73e9b344a5e562858ee637f066..f226f772911a04b024b82de54742ee381c19b2e7 100644 --- a/src/include/utils/elog.h +++ b/src/include/utils/elog.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/elog.h,v 1.78 2004/12/31 22:03:46 pgsql Exp $ + * $PostgreSQL: pgsql/src/include/utils/elog.h,v 1.79 2005/06/10 16:23:10 neilc Exp $ * *------------------------------------------------------------------------- */ @@ -282,6 +282,7 @@ extern int Log_destination; /* Other exported functions */ extern void DebugFileOpen(void); +extern char *unpack_sql_state(int sql_state); /* * Write errors to stderr (or by equal means when stderr is diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y index e1ba564ca900b42cd542e0743bc9b5067bcc4092..48164d983b5586059cae992763e27c3d2e28638a 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.74 2005/06/08 00:49:36 neilc Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.75 2005/06/10 16:23:11 neilc Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -92,6 +92,7 @@ static void plpgsql_sql_error_callback(void *arg); PLpgSQL_stmt_block *program; PLpgSQL_condition *condition; PLpgSQL_exception *exception; + PLpgSQL_exception_block *exception_block; PLpgSQL_nsitem *nsitem; PLpgSQL_diag_item *diagitem; } @@ -129,7 +130,8 @@ static void plpgsql_sql_error_callback(void *arg); %type <stmt> stmt_dynexecute stmt_getdiag %type <stmt> stmt_open stmt_fetch stmt_close stmt_null -%type <list> exception_sect proc_exceptions +%type <list> proc_exceptions +%type <exception_block> exception_sect %type <exception> proc_exception %type <condition> proc_conditions @@ -1495,9 +1497,38 @@ execsql_start : T_WORD ; exception_sect : - { $$ = NIL; } - | K_EXCEPTION proc_exceptions - { $$ = $2; } + { $$ = NULL; } + | K_EXCEPTION lno + { + /* + * We use a mid-rule action to add these + * special variables to the namespace before + * parsing the WHEN clauses themselves. + */ + PLpgSQL_exception_block *new = palloc(sizeof(PLpgSQL_exception_block)); + PLpgSQL_variable *var; + + var = plpgsql_build_variable("sqlstate", $2, + plpgsql_build_datatype(TEXTOID, -1), + true); + ((PLpgSQL_var *) var)->isconst = true; + new->sqlstate_varno = var->dno; + + var = plpgsql_build_variable("sqlerrm", $2, + plpgsql_build_datatype(TEXTOID, -1), + true); + ((PLpgSQL_var *) var)->isconst = true; + new->sqlerrm_varno = var->dno; + + $<exception_block>$ = new; + } + proc_exceptions + { + PLpgSQL_exception_block *new = $<exception_block>3; + new->exc_list = $4; + + $$ = new; + } ; proc_exceptions : proc_exceptions proc_exception diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index b746a8374bda03603ff92feebc6de69fb7fef602..38b3d077de21221011559bd737572695be84c060 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.90 2005/05/29 04:23:06 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.91 2005/06/10 16:23:11 neilc Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -656,7 +656,7 @@ do_compile(FunctionCallInfo fcinfo, if (num_out_args > 0 || function->fn_rettype == VOIDOID || function->fn_retset) { - if (function->action->exceptions != NIL) + if (function->action->exceptions != NULL) { PLpgSQL_stmt_block *new; @@ -882,7 +882,7 @@ plpgsql_parse_word(char *word) } /* - * Do a lookup on the compilers namestack + * Do a lookup on the compiler's namestack */ nse = plpgsql_ns_lookup(cp[0], NULL); if (nse != NULL) @@ -1935,7 +1935,7 @@ plpgsql_parse_err_condition(char *condname) /* ---------- * plpgsql_adddatum Add a variable, record or row - * to the compilers datum list. + * to the compiler's datum list. * ---------- */ void diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 1fe1ed4cbedf38612d3b40b39a1a0b5339849b50..ae5f7473146c1bfefcb5663a6289bc268d4a845d 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.142 2005/06/07 02:47:17 neilc Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.143 2005/06/10 16:23:11 neilc Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -180,7 +180,7 @@ static Datum exec_simple_cast_value(Datum value, Oid valtype, static void exec_init_tuple_store(PLpgSQL_execstate *estate); static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2); static void exec_set_found(PLpgSQL_execstate *estate, bool state); - +static void free_var(PLpgSQL_var *var); /* ---------- * plpgsql_exec_function Called by the call handler for @@ -760,12 +760,7 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block) { PLpgSQL_var *var = (PLpgSQL_var *) (estate->datums[n]); - if (var->freeval) - { - pfree((void *) (var->value)); - var->freeval = false; - } - + free_var(var); if (!var->isconst || var->isnull) { if (var->default_val == NULL) @@ -864,13 +859,37 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block) SPI_restore_connection(); /* Look for a matching exception handler */ - foreach (e, block->exceptions) + foreach (e, block->exceptions->exc_list) { PLpgSQL_exception *exception = (PLpgSQL_exception *) lfirst(e); if (exception_matches_conditions(edata, exception->conditions)) { + /* + * Initialize the magic SQLSTATE and SQLERRM + * variables for the exception block. We needn't + * do this until we have found a matching + * exception. + */ + PLpgSQL_var *state_var; + PLpgSQL_var *errm_var; + + state_var = (PLpgSQL_var *) (estate->datums[block->exceptions->sqlstate_varno]); + state_var->value = DirectFunctionCall1(textin, + CStringGetDatum(unpack_sql_state(edata->sqlerrcode))); + state_var->freeval = true; + state_var->isnull = false; + + errm_var = (PLpgSQL_var *) (estate->datums[block->exceptions->sqlerrm_varno]); + errm_var->value = DirectFunctionCall1(textin, + CStringGetDatum(edata->message)); + errm_var->freeval = true; + errm_var->isnull = false; + rc = exec_stmts(estate, exception->action); + + free_var(state_var); + free_var(errm_var); break; } } @@ -2586,9 +2605,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt) * Store the eventually assigned cursor name in the cursor variable * ---------- */ - if (curvar->freeval) - pfree((void *) (curvar->value)); - + free_var(curvar); curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name)); curvar->isnull = false; curvar->freeval = true; @@ -2684,9 +2701,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt) * Store the eventually assigned portal name in the cursor variable * ---------- */ - if (curvar->freeval) - pfree((void *) (curvar->value)); - + free_var(curvar); curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name)); curvar->isnull = false; curvar->freeval = true; @@ -2857,11 +2872,7 @@ exec_assign_value(PLpgSQL_execstate *estate, errmsg("NULL cannot be assigned to variable \"%s\" declared NOT NULL", var->refname))); - if (var->freeval) - { - pfree(DatumGetPointer(var->value)); - var->freeval = false; - } + free_var(var); /* * If type is by-reference, make sure we have a freshly @@ -4343,3 +4354,13 @@ plpgsql_xact_cb(XactEvent event, void *arg) FreeExecutorState(simple_eval_estate); simple_eval_estate = NULL; } + +static void +free_var(PLpgSQL_var *var) +{ + if (var->freeval) + { + pfree(DatumGetPointer(var->value)); + var->freeval = false; + } +} diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index 475d2224bffcfc6d13360540b96fb7d3c692744f..9e2ef4902c2b246d99077390da7cbf6ba4a4897e 100644 --- a/src/pl/plpgsql/src/pl_funcs.c +++ b/src/pl/plpgsql/src/pl_funcs.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.40 2005/04/05 06:22:16 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.41 2005/06/10 16:23:11 neilc Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -634,7 +634,7 @@ dump_block(PLpgSQL_stmt_block *block) { ListCell *e; - foreach (e, block->exceptions) + foreach (e, block->exceptions->exc_list) { PLpgSQL_exception *exc = (PLpgSQL_exception *) lfirst(e); PLpgSQL_condition *cond; diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index a70553ac2c8a2e62f1de10e33405af990cbf457d..580439c88c5682720e2c364ee022c478f93d997d 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.61 2005/06/07 02:47:18 neilc Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.62 2005/06/10 16:23:11 neilc Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -322,6 +322,13 @@ typedef struct PLpgSQL_condition struct PLpgSQL_condition *next; } PLpgSQL_condition; +typedef struct +{ + int sqlstate_varno; + int sqlerrm_varno; + List *exc_list; /* List of WHEN clauses */ +} PLpgSQL_exception_block; + typedef struct { /* One EXCEPTION ... WHEN clause */ int lineno; @@ -336,9 +343,9 @@ typedef struct int lineno; char *label; List *body; /* List of statements */ - List *exceptions; /* List of WHEN clauses */ int n_initvars; int *initvarnos; + PLpgSQL_exception_block *exceptions; } PLpgSQL_stmt_block; diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index 39e61e09cf26d94a5e92a85f0c48dcb4b2658c68..2650cbb908a55dbd22d13c03b65b21ec70a89742 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -2415,3 +2415,57 @@ NOTICE: 10 15 20 drop table eifoo cascade; drop type eitype cascade; +-- +-- SQLSTATE and SQLERRM test +-- +-- should fail: SQLSTATE and SQLERRM are only in defined EXCEPTION +-- blocks +create function excpt_test() returns void as $$ +begin + raise notice '% %', sqlstate, sqlerrm; +end; $$ language plpgsql; +ERROR: syntax error at or near "sqlstate" at character 79 +LINE 3: raise notice '% %', sqlstate, sqlerrm; + ^ +-- should fail +create function excpt_test() returns void as $$ +begin + begin + begin + raise notice '% %', sqlstate, sqlerrm; + end; + end; +end; $$ language plpgsql; +ERROR: syntax error at or near "sqlstate" at character 108 +LINE 5: raise notice '% %', sqlstate, sqlerrm; + ^ +create function excpt_test() returns void as $$ +begin + begin + raise exception 'user exception'; + exception when others then + raise notice 'caught exception % %', sqlstate, sqlerrm; + begin + raise notice '% %', sqlstate, sqlerrm; + perform 10/0; + exception + when substring_error then + -- this exception handler shouldn't be invoked + raise notice 'unexpected exception: % %', sqlstate, sqlerrm; + when division_by_zero then + raise notice 'caught exception % %', sqlstate, sqlerrm; + end; + raise notice '% %', sqlstate, sqlerrm; + end; +end; $$ language plpgsql; +select excpt_test(); +NOTICE: caught exception P0001 user exception +NOTICE: P0001 user exception +NOTICE: caught exception 22012 division by zero +NOTICE: P0001 user exception + excpt_test +------------ + +(1 row) + +drop function excpt_test(); diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index 314f69915fc61dfecc5963666cf7c7c7083773d5..9dc00f2f1e5b7b15103d0d4b4bb3a69d62b0b39b 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -2050,3 +2050,47 @@ select execute_into_test('eifoo'); drop table eifoo cascade; drop type eitype cascade; + +-- +-- SQLSTATE and SQLERRM test +-- + +-- should fail: SQLSTATE and SQLERRM are only in defined EXCEPTION +-- blocks +create function excpt_test() returns void as $$ +begin + raise notice '% %', sqlstate, sqlerrm; +end; $$ language plpgsql; + +-- should fail +create function excpt_test() returns void as $$ +begin + begin + begin + raise notice '% %', sqlstate, sqlerrm; + end; + end; +end; $$ language plpgsql; + +create function excpt_test() returns void as $$ +begin + begin + raise exception 'user exception'; + exception when others then + raise notice 'caught exception % %', sqlstate, sqlerrm; + begin + raise notice '% %', sqlstate, sqlerrm; + perform 10/0; + exception + when substring_error then + -- this exception handler shouldn't be invoked + raise notice 'unexpected exception: % %', sqlstate, sqlerrm; + when division_by_zero then + raise notice 'caught exception % %', sqlstate, sqlerrm; + end; + raise notice '% %', sqlstate, sqlerrm; + end; +end; $$ language plpgsql; + +select excpt_test(); +drop function excpt_test();