1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-27 12:41:57 +03:00

Improve plpgsql's RAISE command. It is now possible to attach DETAIL and

HINT fields to a user-thrown error message, and to specify the SQLSTATE
error code to use.  The syntax has also been tweaked so that the
Oracle-compatible case "RAISE exception_name" works (though you won't get a
very nice error message if you just write that much).  Lastly, support
the Oracle-compatible syntax "RAISE" with no parameters to re-throw
the current error from within an EXCEPTION block.

In passing, allow the syntax SQLSTATE 'nnnnn' within EXCEPTION lists,
so that there is a way to trap errors with custom SQLSTATE codes.

Pavel Stehule and Tom Lane
This commit is contained in:
Tom Lane
2008-05-13 22:10:30 +00:00
parent 72e2db86b9
commit 4107478d37
9 changed files with 867 additions and 167 deletions

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.111 2008/05/03 00:11:36 tgl Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.112 2008/05/13 22:10:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -52,6 +52,7 @@ static void check_labels(const char *start_label,
const char *end_label);
static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor,
int until, const char *expected);
static List *read_raise_options(void);
%}
@ -138,11 +139,7 @@ static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor,
%type <list> proc_exceptions
%type <exception_block> exception_sect
%type <exception> proc_exception
%type <condition> proc_conditions
%type <ival> raise_level
%type <str> raise_msg
%type <condition> proc_conditions proc_condition
%type <list> getdiag_list
%type <diagitem> getdiag_list_item
@ -164,7 +161,6 @@ static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor,
%token K_CONSTANT
%token K_CONTINUE
%token K_CURSOR
%token K_DEBUG
%token K_DECLARE
%token K_DEFAULT
%token K_DIAGNOSTICS
@ -181,16 +177,13 @@ static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor,
%token K_GET
%token K_IF
%token K_IN
%token K_INFO
%token K_INSERT
%token K_INTO
%token K_IS
%token K_LOG
%token K_LOOP
%token K_MOVE
%token K_NOSCROLL
%token K_NOT
%token K_NOTICE
%token K_NULL
%token K_OPEN
%token K_OR
@ -207,7 +200,6 @@ static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor,
%token K_TO
%token K_TYPE
%token K_USING
%token K_WARNING
%token K_WHEN
%token K_WHILE
@ -1246,7 +1238,7 @@ stmt_return : K_RETURN lno
}
;
stmt_raise : K_RAISE lno raise_level raise_msg
stmt_raise : K_RAISE lno
{
PLpgSQL_stmt_raise *new;
int tok;
@ -1255,69 +1247,133 @@ stmt_raise : K_RAISE lno raise_level raise_msg
new->cmd_type = PLPGSQL_STMT_RAISE;
new->lineno = $2;
new->elog_level = $3;
new->message = $4;
new->elog_level = ERROR; /* default */
new->condname = NULL;
new->message = NULL;
new->params = NIL;
new->options = NIL;
tok = yylex();
if (tok == 0)
yyerror("unexpected end of function definition");
/*
* We expect either a semi-colon, which
* indicates no parameters, or a comma that
* begins the list of parameter expressions
* We could have just RAISE, meaning to re-throw
* the current error.
*/
if (tok != ',' && tok != ';')
yyerror("syntax error");
if (tok == ',')
if (tok != ';')
{
do
/*
* First is an optional elog severity level.
* Most of these are not plpgsql keywords,
* so we rely on examining yytext.
*/
if (pg_strcasecmp(yytext, "exception") == 0)
{
PLpgSQL_expr *expr;
new->elog_level = ERROR;
tok = yylex();
}
else if (pg_strcasecmp(yytext, "warning") == 0)
{
new->elog_level = WARNING;
tok = yylex();
}
else if (pg_strcasecmp(yytext, "notice") == 0)
{
new->elog_level = NOTICE;
tok = yylex();
}
else if (pg_strcasecmp(yytext, "info") == 0)
{
new->elog_level = INFO;
tok = yylex();
}
else if (pg_strcasecmp(yytext, "log") == 0)
{
new->elog_level = LOG;
tok = yylex();
}
else if (pg_strcasecmp(yytext, "debug") == 0)
{
new->elog_level = DEBUG1;
tok = yylex();
}
if (tok == 0)
yyerror("unexpected end of function definition");
expr = read_sql_expression2(',', ';',
", or ;",
&tok);
new->params = lappend(new->params, expr);
} while (tok == ',');
/*
* Next we can have a condition name, or
* equivalently SQLSTATE 'xxxxx', or a string
* literal that is the old-style message format,
* or USING to start the option list immediately.
*/
if (tok == T_STRING)
{
/* old style message and parameters */
new->message = plpgsql_get_string_value();
/*
* We expect either a semi-colon, which
* indicates no parameters, or a comma that
* begins the list of parameter expressions,
* or USING to begin the options list.
*/
tok = yylex();
if (tok != ',' && tok != ';' && tok != K_USING)
yyerror("syntax error");
while (tok == ',')
{
PLpgSQL_expr *expr;
expr = read_sql_construct(',', ';', K_USING,
", or ; or USING",
"SELECT ",
true, true, &tok);
new->params = lappend(new->params, expr);
}
}
else if (tok != K_USING)
{
/* must be condition name or SQLSTATE */
if (pg_strcasecmp(yytext, "sqlstate") == 0)
{
/* next token should be a string literal */
char *sqlstatestr;
if (yylex() != T_STRING)
yyerror("syntax error");
sqlstatestr = plpgsql_get_string_value();
if (strlen(sqlstatestr) != 5)
yyerror("invalid SQLSTATE code");
if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
yyerror("invalid SQLSTATE code");
new->condname = sqlstatestr;
}
else
{
char *cname;
if (tok != T_WORD)
yyerror("syntax error");
plpgsql_convert_ident(yytext, &cname, 1);
plpgsql_recognize_err_condition(cname,
false);
new->condname = cname;
}
tok = yylex();
if (tok != ';' && tok != K_USING)
yyerror("syntax error");
}
if (tok == K_USING)
new->options = read_raise_options();
}
$$ = (PLpgSQL_stmt *)new;
}
;
raise_msg : T_STRING
{
$$ = plpgsql_get_string_value();
}
;
raise_level : K_EXCEPTION
{
$$ = ERROR;
}
| K_WARNING
{
$$ = WARNING;
}
| K_NOTICE
{
$$ = NOTICE;
}
| K_INFO
{
$$ = INFO;
}
| K_LOG
{
$$ = LOG;
}
| K_DEBUG
{
$$ = DEBUG1;
}
;
loop_body : proc_sect K_END K_LOOP opt_label ';'
{
$$.stmts = $1;
@ -1592,20 +1648,61 @@ proc_exception : K_WHEN lno proc_conditions K_THEN proc_sect
}
;
proc_conditions : proc_conditions K_OR opt_lblname
proc_conditions : proc_conditions K_OR proc_condition
{
PLpgSQL_condition *old;
for (old = $1; old->next != NULL; old = old->next)
/* skip */ ;
old->next = plpgsql_parse_err_condition($3);
old->next = $3;
$$ = $1;
}
| opt_lblname
| proc_condition
{
$$ = $1;
}
;
proc_condition : opt_lblname
{
$$ = plpgsql_parse_err_condition($1);
}
| T_SCALAR
{
/*
* Because we know the special sqlstate variable
* is at the top of the namestack (see the
* exception_sect production), the SQLSTATE
* keyword will always lex as T_SCALAR. This
* might not be true in other parsing contexts!
*/
PLpgSQL_condition *new;
char *sqlstatestr;
if (pg_strcasecmp(yytext, "sqlstate") != 0)
yyerror("syntax error");
/* next token should be a string literal */
if (yylex() != T_STRING)
yyerror("syntax error");
sqlstatestr = plpgsql_get_string_value();
if (strlen(sqlstatestr) != 5)
yyerror("invalid SQLSTATE code");
if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
yyerror("invalid SQLSTATE code");
new = palloc(sizeof(PLpgSQL_condition));
new->sqlerrstate = MAKE_SQLSTATE(sqlstatestr[0],
sqlstatestr[1],
sqlstatestr[2],
sqlstatestr[3],
sqlstatestr[4]);
new->condname = sqlstatestr;
new->next = NULL;
$$ = new;
}
;
expr_until_semi :
@ -2658,6 +2755,55 @@ read_cursor_args(PLpgSQL_var *cursor, int until, const char *expected)
return expr;
}
/*
* Parse RAISE ... USING options
*/
static List *
read_raise_options(void)
{
List *result = NIL;
for (;;)
{
PLpgSQL_raise_option *opt;
int tok;
if ((tok = yylex()) == 0)
yyerror("unexpected end of function definition");
opt = (PLpgSQL_raise_option *) palloc(sizeof(PLpgSQL_raise_option));
if (pg_strcasecmp(yytext, "errcode") == 0)
opt->opt_type = PLPGSQL_RAISEOPTION_ERRCODE;
else if (pg_strcasecmp(yytext, "message") == 0)
opt->opt_type = PLPGSQL_RAISEOPTION_MESSAGE;
else if (pg_strcasecmp(yytext, "detail") == 0)
opt->opt_type = PLPGSQL_RAISEOPTION_DETAIL;
else if (pg_strcasecmp(yytext, "hint") == 0)
opt->opt_type = PLPGSQL_RAISEOPTION_HINT;
else
{
plpgsql_error_lineno = plpgsql_scanner_lineno();
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("unrecognized RAISE statement option \"%s\"",
yytext)));
}
if (yylex() != K_ASSIGN)
yyerror("syntax error, expected \"=\"");
opt->expr = read_sql_expression2(',', ';', ", or ;", &tok);
result = lappend(result, opt);
if (tok == ';')
break;
}
return result;
}
/* Needed to avoid conflict between different prefix settings: */
#undef yylex

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.125 2008/05/12 00:00:54 alvherre Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.126 2008/05/13 22:10:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1749,6 +1749,42 @@ build_datatype(HeapTuple typeTup, int32 typmod)
return typ;
}
/*
* plpgsql_recognize_err_condition
* Check condition name and translate it to SQLSTATE.
*
* Note: there are some cases where the same condition name has multiple
* entries in the table. We arbitrarily return the first match.
*/
int
plpgsql_recognize_err_condition(const char *condname, bool allow_sqlstate)
{
int i;
if (allow_sqlstate)
{
if (strlen(condname) == 5 &&
strspn(condname, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 5)
return MAKE_SQLSTATE(condname[0],
condname[1],
condname[2],
condname[3],
condname[4]);
}
for (i = 0; exception_label_map[i].label != NULL; i++)
{
if (strcmp(condname, exception_label_map[i].label) == 0)
return exception_label_map[i].sqlerrstate;
}
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("unrecognized exception condition \"%s\"",
condname)));
return 0; /* keep compiler quiet */
}
/*
* plpgsql_parse_err_condition
* Generate PLpgSQL_condition entry(s) for an exception condition name

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.213 2008/05/12 20:02:02 alvherre Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.214 2008/05/13 22:10:30 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -316,13 +316,17 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
estate.err_text = NULL;
/*
* Provide a more helpful message if a CONTINUE has been used outside
* a loop.
* Provide a more helpful message if a CONTINUE or RAISE has been used
* outside the context it can work in.
*/
if (rc == PLPGSQL_RC_CONTINUE)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("CONTINUE cannot be used outside a loop")));
else if (rc == PLPGSQL_RC_RERAISE)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("RAISE without parameters cannot be used outside an exception handler")));
else
ereport(ERROR,
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
@ -662,13 +666,17 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
estate.err_text = NULL;
/*
* Provide a more helpful message if a CONTINUE has been used outside
* a loop.
* Provide a more helpful message if a CONTINUE or RAISE has been used
* outside the context it can work in.
*/
if (rc == PLPGSQL_RC_CONTINUE)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("CONTINUE cannot be used outside a loop")));
else if (rc == PLPGSQL_RC_RERAISE)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("RAISE without parameters cannot be used outside an exception handler")));
else
ereport(ERROR,
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
@ -1109,6 +1117,11 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
free_var(errm_var);
errm_var->value = (Datum) 0;
errm_var->isnull = true;
/* re-throw error if requested by handler */
if (rc == PLPGSQL_RC_RERAISE)
ReThrowError(edata);
break;
}
}
@ -1139,8 +1152,9 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
switch (rc)
{
case PLPGSQL_RC_OK:
case PLPGSQL_RC_CONTINUE:
case PLPGSQL_RC_RETURN:
case PLPGSQL_RC_CONTINUE:
case PLPGSQL_RC_RERAISE:
return rc;
case PLPGSQL_RC_EXIT:
@ -1469,7 +1483,8 @@ exec_stmt_loop(PLpgSQL_execstate *estate, PLpgSQL_stmt_loop *stmt)
break;
case PLPGSQL_RC_RETURN:
return PLPGSQL_RC_RETURN;
case PLPGSQL_RC_RERAISE:
return rc;
default:
elog(ERROR, "unrecognized rc: %d", rc);
@ -1532,7 +1547,8 @@ exec_stmt_while(PLpgSQL_execstate *estate, PLpgSQL_stmt_while *stmt)
break;
case PLPGSQL_RC_RETURN:
return PLPGSQL_RC_RETURN;
case PLPGSQL_RC_RERAISE:
return rc;
default:
elog(ERROR, "unrecognized rc: %d", rc);
@ -1650,8 +1666,9 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
*/
rc = exec_stmts(estate, stmt->body);
if (rc == PLPGSQL_RC_RETURN)
break; /* return from function */
if (rc == PLPGSQL_RC_RETURN ||
rc == PLPGSQL_RC_RERAISE)
break; /* break out of the loop */
else if (rc == PLPGSQL_RC_EXIT)
{
if (estate->exitlabel == NULL)
@ -2267,64 +2284,163 @@ exec_init_tuple_store(PLpgSQL_execstate *estate)
static int
exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
{
char *cp;
PLpgSQL_dstring ds;
ListCell *current_param;
int err_code = 0;
char *condname = NULL;
char *err_message = NULL;
char *err_detail = NULL;
char *err_hint = NULL;
ListCell *lc;
plpgsql_dstring_init(&ds);
current_param = list_head(stmt->params);
/* RAISE with no parameters: re-throw current exception */
if (stmt->condname == NULL && stmt->message == NULL &&
stmt->options == NIL)
return PLPGSQL_RC_RERAISE;
for (cp = stmt->message; *cp; cp++)
if (stmt->condname)
{
/*
* Occurrences of a single % are replaced by the next parameter's
* external representation. Double %'s are converted to one %.
*/
if (cp[0] == '%')
{
Oid paramtypeid;
Datum paramvalue;
bool paramisnull;
char *extval;
if (cp[1] == '%')
{
plpgsql_dstring_append_char(&ds, cp[1]);
cp++;
continue;
}
if (current_param == NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("too few parameters specified for RAISE")));
paramvalue = exec_eval_expr(estate,
(PLpgSQL_expr *) lfirst(current_param),
&paramisnull,
&paramtypeid);
if (paramisnull)
extval = "<NULL>";
else
extval = convert_value_to_string(paramvalue, paramtypeid);
plpgsql_dstring_append(&ds, extval);
current_param = lnext(current_param);
exec_eval_cleanup(estate);
continue;
}
plpgsql_dstring_append_char(&ds, cp[0]);
err_code = plpgsql_recognize_err_condition(stmt->condname, true);
condname = pstrdup(stmt->condname);
}
/*
* If more parameters were specified than were required to process the
* format string, throw an error
*/
if (current_param != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("too many parameters specified for RAISE")));
if (stmt->message)
{
PLpgSQL_dstring ds;
ListCell *current_param;
char *cp;
plpgsql_dstring_init(&ds);
current_param = list_head(stmt->params);
for (cp = stmt->message; *cp; cp++)
{
/*
* Occurrences of a single % are replaced by the next parameter's
* external representation. Double %'s are converted to one %.
*/
if (cp[0] == '%')
{
Oid paramtypeid;
Datum paramvalue;
bool paramisnull;
char *extval;
if (cp[1] == '%')
{
plpgsql_dstring_append_char(&ds, cp[1]);
cp++;
continue;
}
if (current_param == NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("too few parameters specified for RAISE")));
paramvalue = exec_eval_expr(estate,
(PLpgSQL_expr *) lfirst(current_param),
&paramisnull,
&paramtypeid);
if (paramisnull)
extval = "<NULL>";
else
extval = convert_value_to_string(paramvalue, paramtypeid);
plpgsql_dstring_append(&ds, extval);
current_param = lnext(current_param);
exec_eval_cleanup(estate);
}
else
plpgsql_dstring_append_char(&ds, cp[0]);
}
/*
* If more parameters were specified than were required to process the
* format string, throw an error
*/
if (current_param != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("too many parameters specified for RAISE")));
err_message = plpgsql_dstring_get(&ds);
/* No dstring_free here, the pfree(err_message) does it */
}
foreach(lc, stmt->options)
{
PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc);
Datum optionvalue;
bool optionisnull;
Oid optiontypeid;
char *extval;
optionvalue = exec_eval_expr(estate, opt->expr,
&optionisnull,
&optiontypeid);
if (optionisnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("RAISE statement option cannot be NULL")));
extval = convert_value_to_string(optionvalue, optiontypeid);
switch (opt->opt_type)
{
case PLPGSQL_RAISEOPTION_ERRCODE:
if (err_code)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("RAISE option already specified: %s",
"ERRCODE")));
err_code = plpgsql_recognize_err_condition(extval, true);
condname = pstrdup(extval);
break;
case PLPGSQL_RAISEOPTION_MESSAGE:
if (err_message)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("RAISE option already specified: %s",
"MESSAGE")));
err_message = pstrdup(extval);
break;
case PLPGSQL_RAISEOPTION_DETAIL:
if (err_detail)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("RAISE option already specified: %s",
"DETAIL")));
err_detail = pstrdup(extval);
break;
case PLPGSQL_RAISEOPTION_HINT:
if (err_hint)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("RAISE option already specified: %s",
"HINT")));
err_hint = pstrdup(extval);
break;
default:
elog(ERROR, "unrecognized raise option: %d", opt->opt_type);
}
exec_eval_cleanup(estate);
}
/* Default code if nothing specified */
if (err_code == 0 && stmt->elog_level >= ERROR)
err_code = ERRCODE_RAISE_EXCEPTION;
/* Default error message if nothing specified */
if (err_message == NULL)
{
if (condname)
{
err_message = condname;
condname = NULL;
}
else
err_message = pstrdup(unpack_sql_state(err_code));
}
/*
* Throw the error (may or may not come back)
@ -2332,12 +2448,21 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
estate->err_text = raise_skip_msg; /* suppress traceback of raise */
ereport(stmt->elog_level,
((stmt->elog_level >= ERROR) ? errcode(ERRCODE_RAISE_EXCEPTION) : 0,
errmsg_internal("%s", plpgsql_dstring_get(&ds))));
(err_code ? errcode(err_code) : 0,
errmsg_internal("%s", err_message),
(err_detail != NULL) ? errdetail(err_detail) : 0,
(err_hint != NULL) ? errhint(err_hint) : 0));
estate->err_text = NULL; /* un-suppress... */
plpgsql_dstring_free(&ds);
if (condname != NULL)
pfree(condname);
if (err_message != NULL)
pfree(err_message);
if (err_detail != NULL)
pfree(err_detail);
if (err_hint != NULL)
pfree(err_hint);
return PLPGSQL_RC_OK;
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.70 2008/05/03 00:11:36 tgl Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.71 2008/05/13 22:10:30 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1003,7 +1003,12 @@ dump_raise(PLpgSQL_stmt_raise *stmt)
int i = 0;
dump_ind();
printf("RAISE '%s'\n", stmt->message);
printf("RAISE level=%d", stmt->elog_level);
if (stmt->condname)
printf(" condname='%s'", stmt->condname);
if (stmt->message)
printf(" message='%s'", stmt->message);
printf("\n");
dump_indent += 2;
foreach(lc, stmt->params)
{
@ -1012,6 +1017,36 @@ dump_raise(PLpgSQL_stmt_raise *stmt)
dump_expr((PLpgSQL_expr *) lfirst(lc));
printf("\n");
}
if (stmt->options)
{
dump_ind();
printf(" USING\n");
dump_indent += 2;
foreach(lc, stmt->options)
{
PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc);
dump_ind();
switch (opt->opt_type)
{
case PLPGSQL_RAISEOPTION_ERRCODE:
printf(" ERRCODE = ");
break;
case PLPGSQL_RAISEOPTION_MESSAGE:
printf(" MESSAGE = ");
break;
case PLPGSQL_RAISEOPTION_DETAIL:
printf(" DETAIL = ");
break;
case PLPGSQL_RAISEOPTION_HINT:
printf(" HINT = ");
break;
}
dump_expr(opt->expr);
printf("\n");
}
dump_indent -= 2;
}
dump_indent -= 2;
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.98 2008/05/03 00:11:36 tgl Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.99 2008/05/13 22:10:30 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -106,7 +106,8 @@ enum
PLPGSQL_RC_OK,
PLPGSQL_RC_EXIT,
PLPGSQL_RC_RETURN,
PLPGSQL_RC_CONTINUE
PLPGSQL_RC_CONTINUE,
PLPGSQL_RC_RERAISE
};
/* ----------
@ -119,6 +120,18 @@ enum
PLPGSQL_GETDIAG_RESULT_OID
};
/* --------
* RAISE statement options
* --------
*/
enum
{
PLPGSQL_RAISEOPTION_ERRCODE,
PLPGSQL_RAISEOPTION_MESSAGE,
PLPGSQL_RAISEOPTION_DETAIL,
PLPGSQL_RAISEOPTION_HINT
};
/**********************************************************************
* Node and structure definitions
@ -539,10 +552,18 @@ typedef struct
int cmd_type;
int lineno;
int elog_level;
char *message;
List *params; /* list of expressions */
char *condname; /* condition name, SQLSTATE, or NULL */
char *message; /* old-style message format literal, or NULL */
List *params; /* list of expressions for old-style message */
List *options; /* list of PLpgSQL_raise_option */
} PLpgSQL_stmt_raise;
typedef struct
{ /* RAISE statement option */
int opt_type;
PLpgSQL_expr *expr;
} PLpgSQL_raise_option;
typedef struct
{ /* Generic SQL statement to execute */
@ -772,6 +793,8 @@ extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,
bool add2namespace);
extern PLpgSQL_rec *plpgsql_build_record(const char *refname, int lineno,
bool add2namespace);
extern int plpgsql_recognize_err_condition(const char *condname,
bool allow_sqlstate);
extern PLpgSQL_condition *plpgsql_parse_err_condition(char *condname);
extern void plpgsql_adddatum(PLpgSQL_datum *new);
extern int plpgsql_add_initdatums(int **varnos);

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.62 2008/05/09 15:36:31 petere Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.63 2008/05/13 22:10:30 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -120,7 +120,6 @@ close { return K_CLOSE; }
constant { return K_CONSTANT; }
continue { return K_CONTINUE; }
cursor { return K_CURSOR; }
debug { return K_DEBUG; }
declare { return K_DECLARE; }
default { return K_DEFAULT; }
diagnostics { return K_DIAGNOSTICS; }
@ -137,16 +136,13 @@ from { return K_FROM; }
get { return K_GET; }
if { return K_IF; }
in { return K_IN; }
info { return K_INFO; }
insert { return K_INSERT; }
into { return K_INTO; }
is { return K_IS; }
log { return K_LOG; }
loop { return K_LOOP; }
move { return K_MOVE; }
no{space}+scroll { return K_NOSCROLL; }
not { return K_NOT; }
notice { return K_NOTICE; }
null { return K_NULL; }
open { return K_OPEN; }
or { return K_OR; }
@ -163,7 +159,6 @@ then { return K_THEN; }
to { return K_TO; }
type { return K_TYPE; }
using { return K_USING; }
warning { return K_WARNING; }
when { return K_WHEN; }
while { return K_WHILE; }

View File

@ -3267,7 +3267,7 @@ end;
$$ language plpgsql;
ERROR: cursor FOR loop must use a bound cursor variable
CONTEXT: compile of PL/pgSQL function "forc_bad" near line 4
-- return query execute
-- test RETURN QUERY EXECUTE
create or replace function return_dquery()
returns setof int as $$
begin
@ -3285,3 +3285,132 @@ select * from return_dquery();
(4 rows)
drop function return_dquery();
-- Tests for 8.4's new RAISE features
create or replace function raise_test() returns void as $$
begin
raise notice '% % %', 1, 2, 3
using errcode = '55001', detail = 'some detail info', hint = 'some hint';
raise '% % %', 1, 2, 3
using errcode = 'division_by_zero', detail = 'some detail info';
end;
$$ language plpgsql;
select raise_test();
NOTICE: 1 2 3
DETAIL: some detail info
HINT: some hint
ERROR: 1 2 3
DETAIL: some detail info
-- Since we can't actually see the thrown SQLSTATE in default psql output,
-- test it like this; this also tests re-RAISE
create or replace function raise_test() returns void as $$
begin
raise 'check me'
using errcode = 'division_by_zero', detail = 'some detail info';
exception
when others then
raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
raise;
end;
$$ language plpgsql;
select raise_test();
NOTICE: SQLSTATE: 22012 SQLERRM: check me
ERROR: check me
DETAIL: some detail info
create or replace function raise_test() returns void as $$
begin
raise 'check me'
using errcode = '1234F', detail = 'some detail info';
exception
when others then
raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
raise;
end;
$$ language plpgsql;
select raise_test();
NOTICE: SQLSTATE: 1234F SQLERRM: check me
ERROR: check me
DETAIL: some detail info
-- SQLSTATE specification in WHEN
create or replace function raise_test() returns void as $$
begin
raise 'check me'
using errcode = '1234F', detail = 'some detail info';
exception
when sqlstate '1234F' then
raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
raise;
end;
$$ language plpgsql;
select raise_test();
NOTICE: SQLSTATE: 1234F SQLERRM: check me
ERROR: check me
DETAIL: some detail info
create or replace function raise_test() returns void as $$
begin
raise division_by_zero using detail = 'some detail info';
exception
when others then
raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
raise;
end;
$$ language plpgsql;
select raise_test();
NOTICE: SQLSTATE: 22012 SQLERRM: division_by_zero
ERROR: division_by_zero
DETAIL: some detail info
create or replace function raise_test() returns void as $$
begin
raise division_by_zero;
end;
$$ language plpgsql;
select raise_test();
ERROR: division_by_zero
create or replace function raise_test() returns void as $$
begin
raise sqlstate '1234F';
end;
$$ language plpgsql;
select raise_test();
ERROR: 1234F
create or replace function raise_test() returns void as $$
begin
raise division_by_zero using message = 'custom' || ' message';
end;
$$ language plpgsql;
select raise_test();
ERROR: custom message
create or replace function raise_test() returns void as $$
begin
raise using message = 'custom' || ' message', errcode = '22012';
end;
$$ language plpgsql;
select raise_test();
ERROR: custom message
-- conflict on message
create or replace function raise_test() returns void as $$
begin
raise notice 'some message' using message = 'custom' || ' message', errcode = '22012';
end;
$$ language plpgsql;
select raise_test();
ERROR: RAISE option already specified: MESSAGE
CONTEXT: PL/pgSQL function "raise_test" line 2 at RAISE
-- conflict on errcode
create or replace function raise_test() returns void as $$
begin
raise division_by_zero using message = 'custom' || ' message', errcode = '22012';
end;
$$ language plpgsql;
select raise_test();
ERROR: RAISE option already specified: ERRCODE
CONTEXT: PL/pgSQL function "raise_test" line 2 at RAISE
-- nothing to re-RAISE
create or replace function raise_test() returns void as $$
begin
raise;
end;
$$ language plpgsql;
select raise_test();
ERROR: RAISE without parameters cannot be used outside an exception handler
CONTEXT: PL/pgSQL function "raise_test"
drop function raise_test();

View File

@ -2670,7 +2670,7 @@ begin
end;
$$ language plpgsql;
-- return query execute
-- test RETURN QUERY EXECUTE
create or replace function return_dquery()
returns setof int as $$
@ -2683,3 +2683,132 @@ $$ language plpgsql;
select * from return_dquery();
drop function return_dquery();
-- Tests for 8.4's new RAISE features
create or replace function raise_test() returns void as $$
begin
raise notice '% % %', 1, 2, 3
using errcode = '55001', detail = 'some detail info', hint = 'some hint';
raise '% % %', 1, 2, 3
using errcode = 'division_by_zero', detail = 'some detail info';
end;
$$ language plpgsql;
select raise_test();
-- Since we can't actually see the thrown SQLSTATE in default psql output,
-- test it like this; this also tests re-RAISE
create or replace function raise_test() returns void as $$
begin
raise 'check me'
using errcode = 'division_by_zero', detail = 'some detail info';
exception
when others then
raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
raise;
end;
$$ language plpgsql;
select raise_test();
create or replace function raise_test() returns void as $$
begin
raise 'check me'
using errcode = '1234F', detail = 'some detail info';
exception
when others then
raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
raise;
end;
$$ language plpgsql;
select raise_test();
-- SQLSTATE specification in WHEN
create or replace function raise_test() returns void as $$
begin
raise 'check me'
using errcode = '1234F', detail = 'some detail info';
exception
when sqlstate '1234F' then
raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
raise;
end;
$$ language plpgsql;
select raise_test();
create or replace function raise_test() returns void as $$
begin
raise division_by_zero using detail = 'some detail info';
exception
when others then
raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
raise;
end;
$$ language plpgsql;
select raise_test();
create or replace function raise_test() returns void as $$
begin
raise division_by_zero;
end;
$$ language plpgsql;
select raise_test();
create or replace function raise_test() returns void as $$
begin
raise sqlstate '1234F';
end;
$$ language plpgsql;
select raise_test();
create or replace function raise_test() returns void as $$
begin
raise division_by_zero using message = 'custom' || ' message';
end;
$$ language plpgsql;
select raise_test();
create or replace function raise_test() returns void as $$
begin
raise using message = 'custom' || ' message', errcode = '22012';
end;
$$ language plpgsql;
select raise_test();
-- conflict on message
create or replace function raise_test() returns void as $$
begin
raise notice 'some message' using message = 'custom' || ' message', errcode = '22012';
end;
$$ language plpgsql;
select raise_test();
-- conflict on errcode
create or replace function raise_test() returns void as $$
begin
raise division_by_zero using message = 'custom' || ' message', errcode = '22012';
end;
$$ language plpgsql;
select raise_test();
-- nothing to re-RAISE
create or replace function raise_test() returns void as $$
begin
raise;
end;
$$ language plpgsql;
select raise_test();
drop function raise_test();