-<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.128 2008/05/03 00:11:36 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.129 2008/05/13 22:10:29 tgl Exp $ -->
<chapter id="plpgsql">
<title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
condition name <literal>OTHERS</> matches every error type except
<literal>QUERY_CANCELED</>. (It is possible, but often unwise,
to trap <literal>QUERY_CANCELED</> by name.) Condition names are
- not case-sensitive.
+ not case-sensitive. Also, an error condition can be specified
+ by <literal>SQLSTATE</> code; for example these are equivalent:
+<programlisting>
+ WHEN division_by_zero THEN ...
+ WHEN SQLSTATE '22012' THEN ...
+</programlisting>
</para>
<para>
raise errors.
<synopsis>
-RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="parameter">format</replaceable>' <optional>, <replaceable class="parameter">expression</replaceable> <optional>, ...</optional></optional>;
+RAISE <optional> <replaceable class="parameter">level</replaceable> </optional> '<replaceable class="parameter">format</replaceable>' <optional>, <replaceable class="parameter">expression</replaceable> <optional>, ...</optional></optional> <optional> USING <replaceable class="parameter">option</replaceable> = <replaceable class="parameter">expression</replaceable> <optional>, ... </optional> </optional>;
+RAISE <optional> <replaceable class="parameter">level</replaceable> </optional> <replaceable class="parameter">condition_name</> <optional> USING <replaceable class="parameter">option</replaceable> = <replaceable class="parameter">expression</replaceable> <optional>, ... </optional> </optional>;
+RAISE <optional> <replaceable class="parameter">level</replaceable> </optional> SQLSTATE '<replaceable class="parameter">sqlstate</>' <optional> USING <replaceable class="parameter">option</replaceable> = <replaceable class="parameter">expression</replaceable> <optional>, ... </optional> </optional>;
+RAISE <optional> <replaceable class="parameter">level</replaceable> </optional> USING <replaceable class="parameter">option</replaceable> = <replaceable class="parameter">expression</replaceable> <optional>, ... </optional>;
+RAISE ;
</synopsis>
- Possible levels are <literal>DEBUG</literal>,
+ The <replaceable class="parameter">level</replaceable> option specifies
+ the error severity. Allowed levels are <literal>DEBUG</literal>,
<literal>LOG</literal>, <literal>INFO</literal>,
<literal>NOTICE</literal>, <literal>WARNING</literal>,
- and <literal>EXCEPTION</literal>.
+ and <literal>EXCEPTION</literal>, with <literal>EXCEPTION</literal>
+ being the default.
<literal>EXCEPTION</literal> raises an error (which normally aborts the
current transaction); the other levels only generate messages of different
priority levels.
</para>
<para>
+ After <replaceable class="parameter">level</replaceable> if any,
+ you can write a <replaceable class="parameter">format</replaceable>
+ (which must be a simple string literal, not an expression). The
+ format string specifies the error message text to be reported.
+ The format string can be followed
+ by optional argument expressions to be inserted into the message.
Inside the format string, <literal>%</literal> is replaced by the
- next optional argument's string representation. Write
+ string representation of the next optional argument's value. Write
<literal>%%</literal> to emit a literal <literal>%</literal>.
- Arguments can be simple variables or expressions,
- but the format must be a simple string literal.
</para>
- <!--
- This example should work, but does not:
- RAISE NOTICE 'Id number ' || key || ' not found!';
- Put it back when we allow non-string-literal formats.
- -->
-
<para>
In this example, the value of <literal>v_job_id</> will replace the
<literal>%</literal> in the string:
</para>
<para>
- This example will abort the transaction with the given error message:
+ You can attach additional information to the error report by writing
+ <literal>USING</> followed by <replaceable
+ class="parameter">option</replaceable> = <replaceable
+ class="parameter">expression</replaceable> items. The allowed
+ <replaceable class="parameter">option</replaceable> keywords are
+ <literal>MESSAGE</>, <literal>DETAIL</>, <literal>HINT</>, and
+ <literal>ERRCODE</>, while each <replaceable
+ class="parameter">expression</replaceable> can be any string-valued
+ expression.
+ <literal>MESSAGE</> sets the error message text (this option can't
+ be used in the form of <command>RAISE</> that includes a format
+ string before <literal>USING</>).
+ <literal>DETAIL</> supplies an error detail message, while
+ <literal>HINT</> supplies a hint message.
+ <literal>ERRCODE</> specifies the error code (SQLSTATE) to report,
+ either by condition name as shown in <xref linkend="errcodes-appendix">,
+ or directly as a five-character SQLSTATE code.
+ </para>
+
+ <para>
+ This example will abort the transaction with the given error message
+ and hint:
+<programlisting>
+RAISE EXCEPTION 'Nonexistent ID --> %', user_id USING HINT = 'Please check your user id';
+</programlisting>
+ </para>
+
+ <para>
+ These two examples show equivalent ways of setting the SQLSTATE:
+<programlisting>
+RAISE 'Duplicate user ID: %', user_id USING ERRCODE = 'unique_violation';
+RAISE 'Duplicate user ID: %', user_id USING ERRCODE = '23505';
+</programlisting>
+ </para>
+
+ <para>
+ There is a second <command>RAISE</> syntax in which the main argument
+ is the condition name or SQLSTATE to be reported, for example:
+<programlisting>
+RAISE division_by_zero;
+RAISE SQLSTATE '22012';
+</programlisting>
+ In this syntax, <literal>USING</> can be used to supply a custom
+ error message, detail, or hint. Another way to do the earlier
+ example is
<programlisting>
-RAISE EXCEPTION 'Nonexistent ID --> %', user_id;
+RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;
</programlisting>
</para>
+ <para>
+ Still another variant is to write <literal>RAISE USING</> or <literal>RAISE
+ <replaceable class="parameter">level</replaceable> USING</> and put
+ everything else into the <literal>USING</> list.
+ </para>
+
+ <para>
+ The last variant of <command>RAISE</> has no parameters at all.
+ This form can only be used inside a <literal>BEGIN</> block's
+ <literal>EXCEPTION</> clause;
+ it causes the error currently being handled to be re-thrown to the
+ next enclosing block.
+ </para>
+
+ <para>
+ If no condition name nor SQLSTATE is specified in a
+ <command>RAISE EXCEPTION</command> command, the default is to use
+ <literal>RAISE_EXCEPTION</> (<literal>P0001</>). If no message
+ text is specified, the default is to use the condition name or
+ SQLSTATE as message text.
+ </para>
+
+ <note>
<para>
- <command>RAISE EXCEPTION</command> presently always generates
- the same <varname>SQLSTATE</varname> code, <literal>P0001</>, no matter what message
- it is invoked with. It is possible to trap this exception with
- <literal>EXCEPTION ... WHEN RAISE_EXCEPTION THEN ...</> but there
- is no way to tell one <command>RAISE</> from another.
+ When specifying an error code by SQLSTATE code, you are not
+ limited to the predefined error codes, but can select any
+ error code consisting of five digits and/or upper-case ASCII
+ letters, other than <literal>00000</>. It is recommended that
+ you avoid throwing error codes that end in three zeroes, because
+ these are category codes and can only be trapped by trapping
+ the whole category.
</para>
+ </note>
+
</sect1>
<sect1 id="plpgsql-trigger">
<callout arearefs="co.plpgsql-porting-raise">
<para>
The syntax of <literal>RAISE</> is considerably different from
- Oracle's similar statement.
+ Oracle's statement, although the basic case <literal>RAISE</>
+ <replaceable class="parameter">exception_name</replaceable> works
+ similarly.
</para>
</callout>
<callout arearefs="co.plpgsql-porting-exception">
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
const char *end_label);
static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor,
int until, const char *expected);
+static List *read_raise_options(void);
%}
%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
%token K_CONSTANT
%token K_CONTINUE
%token K_CURSOR
-%token K_DEBUG
%token K_DECLARE
%token K_DEFAULT
%token K_DIAGNOSTICS
%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
%token K_TO
%token K_TYPE
%token K_USING
-%token K_WARNING
%token K_WHEN
%token K_WHILE
}
;
-stmt_raise : K_RAISE lno raise_level raise_msg
+stmt_raise : K_RAISE lno
{
PLpgSQL_stmt_raise *new;
int tok;
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)
+ {
+ 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)
{
- PLpgSQL_expr *expr;
+ 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");
- $$ = (PLpgSQL_stmt *)new;
- }
- ;
+ while (tok == ',')
+ {
+ PLpgSQL_expr *expr;
-raise_msg : T_STRING
- {
- $$ = plpgsql_get_string_value();
- }
- ;
+ 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");
+ }
-raise_level : K_EXCEPTION
- {
- $$ = ERROR;
- }
- | K_WARNING
- {
- $$ = WARNING;
- }
- | K_NOTICE
- {
- $$ = NOTICE;
- }
- | K_INFO
- {
- $$ = INFO;
- }
- | K_LOG
- {
- $$ = LOG;
- }
- | K_DEBUG
- {
- $$ = DEBUG1;
+ if (tok == K_USING)
+ new->options = read_raise_options();
+ }
+
+ $$ = (PLpgSQL_stmt *)new;
}
;
}
;
-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 :
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
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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),
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),
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;
}
}
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:
break;
case PLPGSQL_RC_RETURN:
- return PLPGSQL_RC_RETURN;
+ case PLPGSQL_RC_RERAISE:
+ return rc;
default:
elog(ERROR, "unrecognized rc: %d", rc);
break;
case PLPGSQL_RC_RETURN:
- return PLPGSQL_RC_RETURN;
+ case PLPGSQL_RC_RERAISE:
+ return rc;
default:
elog(ERROR, "unrecognized rc: %d", rc);
*/
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)
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;
+ err_code = plpgsql_recognize_err_condition(stmt->condname, true);
+ condname = pstrdup(stmt->condname);
+ }
+
+ if (stmt->message)
+ {
+ PLpgSQL_dstring ds;
+ ListCell *current_param;
+ char *cp;
+
+ plpgsql_dstring_init(&ds);
+ current_param = list_head(stmt->params);
- if (cp[1] == '%')
+ 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] == '%')
{
- plpgsql_dstring_append_char(&ds, cp[1]);
- cp++;
- continue;
- }
+ Oid paramtypeid;
+ Datum paramvalue;
+ bool paramisnull;
+ char *extval;
- if (current_param == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("too few parameters specified for RAISE")));
+ if (cp[1] == '%')
+ {
+ plpgsql_dstring_append_char(&ds, cp[1]);
+ cp++;
+ continue;
+ }
- paramvalue = exec_eval_expr(estate,
- (PLpgSQL_expr *) lfirst(current_param),
- ¶misnull,
- ¶mtypeid);
+ 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),
+ ¶misnull,
+ ¶mtypeid);
- if (paramisnull)
- extval = "<NULL>";
+ 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
- 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]);
}
- 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 */
}
- /*
- * 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")));
+ 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)
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;
}
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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)
{
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;
}
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
PLPGSQL_RC_OK,
PLPGSQL_RC_EXIT,
PLPGSQL_RC_RETURN,
- PLPGSQL_RC_CONTINUE
+ PLPGSQL_RC_CONTINUE,
+ PLPGSQL_RC_RERAISE
};
/* ----------
PLPGSQL_GETDIAG_RESULT_OID
};
+/* --------
+ * RAISE statement options
+ * --------
+ */
+enum
+{
+ PLPGSQL_RAISEOPTION_ERRCODE,
+ PLPGSQL_RAISEOPTION_MESSAGE,
+ PLPGSQL_RAISEOPTION_DETAIL,
+ PLPGSQL_RAISEOPTION_HINT
+};
+
/**********************************************************************
* Node and structure definitions
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 */
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);
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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; }
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; }
to { return K_TO; }
type { return K_TYPE; }
using { return K_USING; }
-warning { return K_WARNING; }
when { return K_WHEN; }
while { return K_WHILE; }
$$ 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
(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();
end;
$$ language plpgsql;
--- return query execute
+-- test RETURN QUERY EXECUTE
create or replace function return_dquery()
returns setof int as $$
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();