Implement two new special variables in PL/PgSQL: SQLSTATE and SQLERRM.

These contain the SQLSTATE and error message of the current exception,
respectively. They are scope-local variables that are only defined
in exception handlers (so attempting to reference them outside an
exception handler is an error). Update the regression tests and the
documentation.

Also, do some minor related cleanup: export an unpack_sql_state()
function from the backend and use it to unpack a SQLSTATE into a
string, and add a free_var() function to pl_exec.c

Original patch from Pavel Stehule, review by Neil Conway.
This commit is contained in:
Neil Conway 2005-06-10 16:23:11 +00:00
parent 1a61896189
commit d46bc444ac
10 changed files with 226 additions and 51 deletions

View File

@ -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>

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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();