mirror of https://github.com/postgres/postgres
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:
parent
1a61896189
commit
d46bc444ac
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue