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"> <chapter id="plpgsql">
@ -2110,6 +2110,17 @@ END;
don't use <literal>EXCEPTION</> without need. don't use <literal>EXCEPTION</> without need.
</para> </para>
</tip> </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"> <example id="plpgsql-upsert-example">
<title>Exceptions with UPDATE/INSERT</title> <title>Exceptions with UPDATE/INSERT</title>
<para> <para>

View File

@ -42,7 +42,7 @@
* *
* *
* IDENTIFICATION * 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 * 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)); appendStringInfo(&buf, "%s: ", error_severity(edata->elevel));
if (Log_error_verbosity >= PGERROR_VERBOSE) if (Log_error_verbosity >= PGERROR_VERBOSE)
{ appendStringInfo(&buf, "%s: ", unpack_sql_state(edata->sqlerrcode));
/* 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);
}
if (edata->message) if (edata->message)
append_with_tabs(&buf, 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) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 */ /* Other exported functions */
extern void DebugFileOpen(void); extern void DebugFileOpen(void);
extern char *unpack_sql_state(int sql_state);
/* /*
* Write errors to stderr (or by equal means when stderr is * Write errors to stderr (or by equal means when stderr is

View File

@ -4,7 +4,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * 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. * 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_stmt_block *program;
PLpgSQL_condition *condition; PLpgSQL_condition *condition;
PLpgSQL_exception *exception; PLpgSQL_exception *exception;
PLpgSQL_exception_block *exception_block;
PLpgSQL_nsitem *nsitem; PLpgSQL_nsitem *nsitem;
PLpgSQL_diag_item *diagitem; 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_dynexecute stmt_getdiag
%type <stmt> stmt_open stmt_fetch stmt_close stmt_null %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 <exception> proc_exception
%type <condition> proc_conditions %type <condition> proc_conditions
@ -1495,9 +1497,38 @@ execsql_start : T_WORD
; ;
exception_sect : exception_sect :
{ $$ = NIL; } { $$ = NULL; }
| K_EXCEPTION proc_exceptions | K_EXCEPTION lno
{ $$ = $2; } {
/*
* 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 proc_exceptions : proc_exceptions proc_exception

View File

@ -3,7 +3,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * 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. * 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 || if (num_out_args > 0 || function->fn_rettype == VOIDOID ||
function->fn_retset) function->fn_retset)
{ {
if (function->action->exceptions != NIL) if (function->action->exceptions != NULL)
{ {
PLpgSQL_stmt_block *new; 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); nse = plpgsql_ns_lookup(cp[0], NULL);
if (nse != NULL) if (nse != NULL)
@ -1935,7 +1935,7 @@ plpgsql_parse_err_condition(char *condname)
/* ---------- /* ----------
* plpgsql_adddatum Add a variable, record or row * plpgsql_adddatum Add a variable, record or row
* to the compilers datum list. * to the compiler's datum list.
* ---------- * ----------
*/ */
void void

View File

@ -3,7 +3,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * 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. * 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 void exec_init_tuple_store(PLpgSQL_execstate *estate);
static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2); static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2);
static void exec_set_found(PLpgSQL_execstate *estate, bool state); 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 * 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]); PLpgSQL_var *var = (PLpgSQL_var *) (estate->datums[n]);
if (var->freeval) free_var(var);
{
pfree((void *) (var->value));
var->freeval = false;
}
if (!var->isconst || var->isnull) if (!var->isconst || var->isnull)
{ {
if (var->default_val == NULL) if (var->default_val == NULL)
@ -864,13 +859,37 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
SPI_restore_connection(); SPI_restore_connection();
/* Look for a matching exception handler */ /* Look for a matching exception handler */
foreach (e, block->exceptions) foreach (e, block->exceptions->exc_list)
{ {
PLpgSQL_exception *exception = (PLpgSQL_exception *) lfirst(e); PLpgSQL_exception *exception = (PLpgSQL_exception *) lfirst(e);
if (exception_matches_conditions(edata, exception->conditions)) 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); rc = exec_stmts(estate, exception->action);
free_var(state_var);
free_var(errm_var);
break; 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 * Store the eventually assigned cursor name in the cursor variable
* ---------- * ----------
*/ */
if (curvar->freeval) free_var(curvar);
pfree((void *) (curvar->value));
curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name)); curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name));
curvar->isnull = false; curvar->isnull = false;
curvar->freeval = true; 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 * Store the eventually assigned portal name in the cursor variable
* ---------- * ----------
*/ */
if (curvar->freeval) free_var(curvar);
pfree((void *) (curvar->value));
curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name)); curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name));
curvar->isnull = false; curvar->isnull = false;
curvar->freeval = true; curvar->freeval = true;
@ -2857,11 +2872,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
errmsg("NULL cannot be assigned to variable \"%s\" declared NOT NULL", errmsg("NULL cannot be assigned to variable \"%s\" declared NOT NULL",
var->refname))); var->refname)));
if (var->freeval) free_var(var);
{
pfree(DatumGetPointer(var->value));
var->freeval = false;
}
/* /*
* If type is by-reference, make sure we have a freshly * 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); FreeExecutorState(simple_eval_estate);
simple_eval_estate = NULL; 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 * procedural language
* *
* IDENTIFICATION * 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. * This software is copyrighted by Jan Wieck - Hamburg.
* *
@ -634,7 +634,7 @@ dump_block(PLpgSQL_stmt_block *block)
{ {
ListCell *e; ListCell *e;
foreach (e, block->exceptions) foreach (e, block->exceptions->exc_list)
{ {
PLpgSQL_exception *exc = (PLpgSQL_exception *) lfirst(e); PLpgSQL_exception *exc = (PLpgSQL_exception *) lfirst(e);
PLpgSQL_condition *cond; PLpgSQL_condition *cond;

View File

@ -3,7 +3,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * 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. * This software is copyrighted by Jan Wieck - Hamburg.
* *
@ -322,6 +322,13 @@ typedef struct PLpgSQL_condition
struct PLpgSQL_condition *next; struct PLpgSQL_condition *next;
} PLpgSQL_condition; } PLpgSQL_condition;
typedef struct
{
int sqlstate_varno;
int sqlerrm_varno;
List *exc_list; /* List of WHEN clauses */
} PLpgSQL_exception_block;
typedef struct typedef struct
{ /* One EXCEPTION ... WHEN clause */ { /* One EXCEPTION ... WHEN clause */
int lineno; int lineno;
@ -336,9 +343,9 @@ typedef struct
int lineno; int lineno;
char *label; char *label;
List *body; /* List of statements */ List *body; /* List of statements */
List *exceptions; /* List of WHEN clauses */
int n_initvars; int n_initvars;
int *initvarnos; int *initvarnos;
PLpgSQL_exception_block *exceptions;
} PLpgSQL_stmt_block; } PLpgSQL_stmt_block;

View File

@ -2415,3 +2415,57 @@ NOTICE: 10 15 20
drop table eifoo cascade; drop table eifoo cascade;
drop type eitype 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 table eifoo cascade;
drop type eitype 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();