diff -c -r --new-file pgsql.01/doc/src/sgml/plpgsql.sgml pgsql.02/doc/src/sgml/plpgsql.sgml *** pgsql.01/doc/src/sgml/plpgsql.sgml 2005-06-25 15:29:27.000000000 +0200 --- pgsql.02/doc/src/sgml/plpgsql.sgml 2005-06-25 21:56:24.000000000 +0200 *************** *** 2116,2122 **** The condition names can be any of those shown in . A category name matches ! any error within its category. The special condition name OTHERS matches every error type except QUERY_CANCELED. (It is possible, but often unwise, to trap --- 2116,2124 ---- The condition names can be any of those shown in . A category name matches ! any error within its category. You can use exception variable as ! condition name. Exception variable is declared with type ! EXCEPTION The special condition name OTHERS matches every error type except QUERY_CANCELED. (It is possible, but often unwise, to trap *************** *** 2570,2576 **** raise errors. ! RAISE level 'format' , expression , ...; Possible levels are DEBUG, --- 2572,2579 ---- raise errors. ! RAISE level ! system exception|exception variable 'format' , expression , ...; Possible levels are DEBUG, *************** *** 2587,2592 **** --- 2590,2599 ---- variables. See for more information. + + + You can specify any system exception or any user exception. + Inside the format string, % is replaced by the diff -c -r --new-file pgsql.01/src/pl/plpgsql/src/gram.y pgsql.02/src/pl/plpgsql/src/gram.y *** pgsql.01/src/pl/plpgsql/src/gram.y 2005-06-25 15:21:22.000000000 +0200 --- pgsql.02/src/pl/plpgsql/src/gram.y 2005-06-25 19:57:42.000000000 +0200 *************** *** 39,44 **** --- 39,45 ---- #include "plpgsql.h" #include "parser/parser.h" + #include "utils/elog.h" static PLpgSQL_expr *read_sql_construct(int until, int until2, *************** *** 78,83 **** --- 79,90 ---- } forvariable; struct { + bool nospec; + int sqlstate; + char *refname; + } opt_excptspec; + struct + { char *label; int n_initvars; int *initvarnos; *************** *** 103,108 **** --- 110,116 ---- PLpgSQL_exception_block *exception_block; PLpgSQL_nsitem *nsitem; PLpgSQL_diag_item *diagitem; + PLpgSQL_usrexcpt *usrexcpt; } %type decl_sect *************** *** 115,125 **** --- 123,135 ---- %type decl_cursor_arglist %type decl_aliasitem %type decl_stmts decl_stmt + %type decl_defsqlstate %type expr_until_semi expr_until_rightbracket %type expr_until_then expr_until_loop %type opt_exitcond + %type assign_var cursor_variable %type cursor_varptr %type decl_cursor_arg *************** *** 144,150 **** %type exception_sect %type proc_exception %type proc_conditions ! %type raise_level %type raise_msg --- 154,161 ---- %type exception_sect %type proc_exception %type proc_conditions ! %type opt_excptspec ! %type excpt_name %type raise_level %type raise_msg *************** *** 223,228 **** --- 234,240 ---- %token T_LABEL %token T_WORD %token T_ERROR + %token T_EXCEPTION %token O_OPTION %token O_DUMP *************** *** 332,338 **** PLpgSQL_variable *var; var = plpgsql_build_variable($1.name, $1.lineno, ! $3, true); if ($2) { if (var->dtype == PLPGSQL_DTYPE_VAR) --- 344,351 ---- PLpgSQL_variable *var; var = plpgsql_build_variable($1.name, $1.lineno, ! ! $3, true); if ($2) { if (var->dtype == PLPGSQL_DTYPE_VAR) *************** *** 361,366 **** --- 374,408 ---- errmsg("default value for row or record variable is not supported"))); } } + | decl_varname K_EXCEPTION decl_defsqlstate + { + PLpgSQL_usrexcpt *ue; + PLpgSQL_type *dtype; + + dtype = (PLpgSQL_type *) palloc(sizeof(PLpgSQL_type)); + dtype->typname = "exception"; + dtype->ttype = PLPGSQL_TTYPE_EXCEPTION; + + + ue = (PLpgSQL_usrexcpt *) plpgsql_build_variable($1.name, $1.lineno, + dtype, true); + if ($3) + { + if (strlen($3) != 5) + yyerror("Sqlstate has five chars"); + + if (strncmp($3,"U0",2) == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid class for SQLSTATE value '%s' for user's exception.", $3), + errhint("Class 'U0' is reserved for default values user's exceptions."))); + + ue->sqlstate = MAKE_SQLSTATE($3[0],$3[1],$3[2],$3[3],$3[4]); + } else + ue->sqlstate = plpgsql_newUsrExceptions(); + + pfree(dtype); + } | decl_varname K_ALIAS K_FOR decl_aliasitem ';' { plpgsql_ns_additem($4->itemtype, *************** *** 563,568 **** --- 605,626 ---- | K_DEFAULT ; + decl_defsqlstate : ';' + { $$ = NULL; } + | decl_defkey + { + if (yylex() != T_STRING) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Default value for exception type have to be string constant"))); + $$ = plpgsql_get_string_value(); + if (yylex() != ';') + yyerror("syntax error"); + + } + ; + + proc_sect : { $$ = NIL; *************** *** 1185,1191 **** } ; ! stmt_raise : K_RAISE lno raise_level raise_msg { PLpgSQL_stmt_raise *new; int tok; --- 1243,1249 ---- } ; ! stmt_raise : K_RAISE lno raise_level opt_excptspec raise_msg { PLpgSQL_stmt_raise *new; int tok; *************** *** 1195,1203 **** new->cmd_type = PLPGSQL_STMT_RAISE; new->lineno = $2; new->elog_level = $3; ! new->message = $4; new->params = NIL; tok = yylex(); /* --- 1253,1273 ---- new->cmd_type = PLPGSQL_STMT_RAISE; new->lineno = $2; new->elog_level = $3; ! new->message = $5; new->params = NIL; + if ($4.nospec == false) + { + new->sqlstate = $4.sqlstate; + new->refname = $4.refname; + } + else + { + new->sqlstate = (new->elog_level >= ERROR)?ERRCODE_RAISE_EXCEPTION:0; + new->refname = NULL; + } + + tok = yylex(); /* *************** *** 1260,1265 **** --- 1330,1358 ---- } ; + opt_excptspec : T_EXCEPTION + { + $$.nospec = false; + $$.sqlstate = yylval.usrexcpt->sqlstate; + $$.refname = yylval.usrexcpt->refname; + } + | T_WORD + { + + PLpgSQL_condition *c = plpgsql_parse_err_condition(yytext); + if (c->sqlerrstate == 0) /* others */ + yyerror("You have to use exception's variable or system exception"); + + $$.nospec = false; + $$.sqlstate = c->sqlerrstate; + $$.refname = c->condname; + } + | /* EMPTY */ + { + $$.nospec = true; + } + + loop_body : proc_sect lno K_END K_LOOP opt_endlabel ';' { $$.list = $1; *************** *** 1583,1589 **** } ; ! proc_conditions : proc_conditions K_OR opt_lblname { PLpgSQL_condition *old; --- 1676,1682 ---- } ; ! proc_conditions : proc_conditions K_OR excpt_name { PLpgSQL_condition *old; *************** *** 1593,1604 **** $$ = $1; } ! | opt_lblname { $$ = plpgsql_parse_err_condition($1); } ; expr_until_semi : { $$ = plpgsql_read_expression(';', ";"); } ; --- 1686,1711 ---- $$ = $1; } ! | excpt_name { $$ = plpgsql_parse_err_condition($1); } ; + excpt_name: T_WORD + { + char *name; + + plpgsql_convert_ident(yytext, &name, 1); + $$ = name; + } + | T_EXCEPTION + { + $$ = yylval.usrexcpt->refname; + } + ; + + expr_until_semi : { $$ = plpgsql_read_expression(';', ";"); } ; diff -c -r --new-file pgsql.01/src/pl/plpgsql/src/pl_comp.c pgsql.02/src/pl/plpgsql/src/pl_comp.c *** pgsql.01/src/pl/plpgsql/src/pl_comp.c 2005-06-24 13:11:25.000000000 +0200 --- pgsql.02/src/pl/plpgsql/src/pl_comp.c 2005-06-25 20:03:14.000000000 +0200 *************** *** 80,85 **** --- 80,87 ---- bool plpgsql_DumpExecTree = false; bool plpgsql_check_syntax = false; + int plpgsql_user_excpt; + PLpgSQL_function *plpgsql_curr_compile; /* A context appropriate for short-term allocs during compilation */ *************** *** 315,320 **** --- 317,324 ---- /* This is short-lived, so needn't allocate in function's cxt */ plpgsql_Datums = palloc(sizeof(PLpgSQL_datum *) * datums_alloc); datums_last = 0; + + plpgsql_user_excpt = 0; /* * Do extra syntax checks when validating the function *************** *** 904,910 **** case PLPGSQL_NSTYPE_ROW: plpgsql_yylval.row = (PLpgSQL_row *) (plpgsql_Datums[nse->itemno]); return T_ROW; ! default: return T_ERROR; } --- 908,918 ---- case PLPGSQL_NSTYPE_ROW: plpgsql_yylval.row = (PLpgSQL_row *) (plpgsql_Datums[nse->itemno]); return T_ROW; ! ! case PLPGSQL_NSTYPE_EXCEPTION: ! plpgsql_yylval.usrexcpt = (PLpgSQL_usrexcpt *) (plpgsql_Datums[nse->itemno]); ! return T_EXCEPTION; ! default: return T_ERROR; } *************** *** 1626,1631 **** --- 1634,1658 ---- result = (PLpgSQL_variable *) rec; break; } + case PLPGSQL_TTYPE_EXCEPTION: + { + /* Exception pseudo type */ + PLpgSQL_usrexcpt *excpt; + + excpt = palloc0(sizeof(PLpgSQL_usrexcpt)); + excpt->dtype = PLPGSQL_DTYPE_EXCEPTION; + excpt->refname = pstrdup(refname); + excpt->lineno = lineno; + + plpgsql_adddatum((PLpgSQL_datum *) excpt); + if (add2namespace) + plpgsql_ns_additem(PLPGSQL_NSTYPE_EXCEPTION, + excpt->eno, + refname); + result = (PLpgSQL_variable *) excpt; + break; + } + case PLPGSQL_TTYPE_PSEUDO: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), *************** *** 1893,1898 **** --- 1920,1928 ---- PLpgSQL_condition *new; PLpgSQL_condition *prev; + PLpgSQL_nsitem *nse; + char *cp[1]; + /* * XXX Eventually we will want to look for user-defined exception * names here. *************** *** 1924,1929 **** --- 1954,1986 ---- } } + if (!prev) + { + /* Do case conversion and word separation */ + plpgsql_convert_ident(condname, cp, 1); + + /* + * Do a lookup on the compiler's namestack + */ + nse = plpgsql_ns_lookup(cp[0], NULL); + + if (nse != NULL) + { + PLpgSQL_usrexcpt *excpt = (PLpgSQL_usrexcpt *) (plpgsql_Datums[nse->itemno]); + if (nse->itemtype == PLPGSQL_NSTYPE_EXCEPTION) + { + new = palloc(sizeof(PLpgSQL_condition)); + new->sqlerrstate = excpt->sqlstate; + new->condname = condname; + new->next = prev; + prev = new; + } + } + pfree(cp[0]); + } + + + if (!prev) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), *************** *** 2177,2179 **** --- 2234,2251 ---- if (hentry == NULL) elog(WARNING, "trying to delete function that does not exist"); } + + #define MAX_USER_EXCPT 999 + + int + plpgsql_newUsrExceptions(void) + { + char rs[4]; + + if (plpgsql_user_excpt == MAX_USER_EXCPT) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("Too much user's exception"))); + sprintf(rs,"%03d", ++plpgsql_user_excpt); + return MAKE_SQLSTATE('U','0', rs[0],rs[1],rs[2]); + } diff -c -r --new-file pgsql.01/src/pl/plpgsql/src/pl_exec.c pgsql.02/src/pl/plpgsql/src/pl_exec.c *** pgsql.01/src/pl/plpgsql/src/pl_exec.c 2005-06-24 13:11:25.000000000 +0200 --- pgsql.02/src/pl/plpgsql/src/pl_exec.c 2005-06-25 19:36:02.000000000 +0200 *************** *** 722,727 **** --- 722,730 ---- */ result = datum; break; + case PLPGSQL_DTYPE_EXCEPTION: + result = NULL; + break; default: elog(ERROR, "unrecognized dtype: %d", datum->dtype); *************** *** 825,830 **** --- 828,834 ---- case PLPGSQL_DTYPE_RECFIELD: case PLPGSQL_DTYPE_ARRAYELEM: + case PLPGSQL_DTYPE_EXCEPTION: break; default: *************** *** 2061,2069 **** */ 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)))); estate->err_text = NULL; /* un-suppress... */ --- 2065,2081 ---- */ estate->err_text = raise_skip_msg; /* suppress traceback of raise */ ! ! if (stmt->refname != NULL) ! ereport(stmt->elog_level, /* User's exception */ ! (errcode(stmt->sqlstate), ! errdetail("User's exception/notice - sqlstate: '%s', name: '%s'", unpack_sql_state(stmt->sqlstate), stmt->refname), ! errhint("from RAISE stmt on line %d", stmt->lineno), ! errmsg_internal("%s", plpgsql_dstring_get(&ds)))); ! else ! ereport(stmt->elog_level, ! (errcode(stmt->sqlstate), ! errmsg_internal("%s", plpgsql_dstring_get(&ds)))); estate->err_text = NULL; /* un-suppress... */ diff -c -r --new-file pgsql.01/src/pl/plpgsql/src/plpgsql.h pgsql.02/src/pl/plpgsql/src/plpgsql.h *** pgsql.01/src/pl/plpgsql/src/plpgsql.h 2005-06-24 13:11:25.000000000 +0200 --- pgsql.02/src/pl/plpgsql/src/plpgsql.h 2005-06-25 19:27:28.000000000 +0200 *************** *** 58,64 **** PLPGSQL_NSTYPE_LABEL, PLPGSQL_NSTYPE_VAR, PLPGSQL_NSTYPE_ROW, ! PLPGSQL_NSTYPE_REC }; /* ---------- --- 58,65 ---- PLPGSQL_NSTYPE_LABEL, PLPGSQL_NSTYPE_VAR, PLPGSQL_NSTYPE_ROW, ! PLPGSQL_NSTYPE_REC, ! PLPGSQL_NSTYPE_EXCEPTION }; /* ---------- *************** *** 73,79 **** PLPGSQL_DTYPE_RECFIELD, PLPGSQL_DTYPE_ARRAYELEM, PLPGSQL_DTYPE_EXPR, ! PLPGSQL_DTYPE_TRIGARG }; /* ---------- --- 74,81 ---- PLPGSQL_DTYPE_RECFIELD, PLPGSQL_DTYPE_ARRAYELEM, PLPGSQL_DTYPE_EXPR, ! PLPGSQL_DTYPE_TRIGARG, ! PLPGSQL_DTYPE_EXCEPTION }; /* ---------- *************** *** 85,91 **** PLPGSQL_TTYPE_SCALAR, /* scalar types and domains */ PLPGSQL_TTYPE_ROW, /* composite types */ PLPGSQL_TTYPE_REC, /* RECORD pseudotype */ ! PLPGSQL_TTYPE_PSEUDO /* other pseudotypes */ }; /* ---------- --- 87,94 ---- PLPGSQL_TTYPE_SCALAR, /* scalar types and domains */ PLPGSQL_TTYPE_ROW, /* composite types */ PLPGSQL_TTYPE_REC, /* RECORD pseudotype */ ! PLPGSQL_TTYPE_PSEUDO, /* other pseudotypes */ ! PLPGSQL_TTYPE_EXCEPTION }; /* ---------- *************** *** 190,195 **** --- 193,207 ---- int lineno; } PLpgSQL_variable; + typedef struct + { + int dtype; /* Exception variable */ + int eno; + char *refname; + int lineno; + int sqlstate; + } PLpgSQL_usrexcpt; + typedef struct PLpgSQL_expr { /* SQL Query to plan and execute */ int dtype; *************** *** 516,521 **** --- 528,535 ---- int cmd_type; int lineno; int elog_level; + int sqlstate; + char *refname; char *message; List *params; /* list of expressions */ } PLpgSQL_stmt_raise; *************** *** 688,693 **** --- 702,709 ---- extern int plpgsql_add_initdatums(int **varnos); extern void plpgsql_HashTableInit(void); extern void plpgsql_compile_error_callback(void *arg); + extern int plpgsql_newUsrExceptions(void); + /* ---------- * Functions in pl_handler.c diff -c -r --new-file pgsql.01/src/test/regress/expected/plpgsql.out pgsql.02/src/test/regress/expected/plpgsql.out *** pgsql.01/src/test/regress/expected/plpgsql.out 2005-06-25 15:23:17.000000000 +0200 --- pgsql.02/src/test/regress/expected/plpgsql.out 2005-06-25 20:34:05.000000000 +0200 *************** *** 2709,2711 **** --- 2709,2770 ---- DROP FUNCTION drop function vfoo2(); ERROR: function vfoo2() does not exist + + -- user's exception + create function innerfx() returns integer as $$ + declare my_excpt exception = 'U0001'; + begin -- using msgtext as one param of exception + raise exception my_excpt '%', CURRENT_TIMESTAMP; + return 1; + end $$ language plpgsql; + ERROR: Invalid class for SQLSTATE value 'U0001' for user's exception. + HINT: Class 'U0' is reserved for default values user's exceptions. + CONTEXT: compile of PL/pgSQL function "innerfx" near line 1 + create function innerfx() returns integer as $$ + declare my_excpt exception = 'U1001'; + begin -- using msgtext as one param of exception + raise exception my_excpt '%', 100.34::numeric; + return 1; + end $$ language plpgsql; + CREATE FUNCTION + create function outerfx() returns integer as $$ + declare + my_excpt exception = 'U1001'; + alias_div_by_zero exception = '22012'; + my_excpt_def_sqlstate exception; + begin + begin + raise exception my_excpt_def_sqlstate 'foo'; + exception when my_excpt_def_sqlstate then + raise notice '01 catch: %, %', sqlstate, sqlerrm; + end; + begin + raise notice '%', innerfx(); + exception when my_excpt then + raise notice '02 catch: %, %', sqlstate, sqlerrm::numeric; + end; + begin + raise exception alias_div_by_zero 'testing'; + exception when division_by_zero then + raise notice 'Divison by zero: %, %', sqlstate, sqlerrm; + end; + return 1; + end; $$ language plpgsql; + CREATE FUNCTION + select innerfx(); + ERROR: 100.34 + DETAIL: User's exception/notice - sqlstate: 'U1001', name: 'my_excpt' + HINT: from RAISE stmt on line 3 + select outerfx(); + NOTICE: 01 catch: U0001, foo + NOTICE: 02 catch: U1001, 100.34 + NOTICE: Divison by zero: 22012, testing + outerfx + --------- + 1 + (1 row) + + drop function outerfx(); + DROP FUNCTION + drop function innerfx(); + DROP FUNCTION diff -c -r --new-file pgsql.01/src/test/regress/sql/plpgsql.sql pgsql.02/src/test/regress/sql/plpgsql.sql *** pgsql.01/src/test/regress/sql/plpgsql.sql 2005-06-25 14:10:30.000000000 +0200 --- pgsql.02/src/test/regress/sql/plpgsql.sql 2005-06-25 20:32:59.000000000 +0200 *************** *** 2266,2268 **** --- 2266,2314 ---- drop function vfoo(); drop function vfoo2(); + + -- user's exception + create function innerfx() returns integer as $$ + declare my_excpt exception = 'U0001'; + begin -- using msgtext as one param of exception + raise exception my_excpt '%', CURRENT_TIMESTAMP; + return 1; + end $$ language plpgsql; + + + create function innerfx() returns integer as $$ + declare my_excpt exception = 'U1001'; + begin -- using msgtext as one param of exception + raise exception my_excpt '%', 100.34::numeric; + return 1; + end $$ language plpgsql; + + create function outerfx() returns integer as $$ + declare + my_excpt exception = 'U1001'; + alias_div_by_zero exception = '22012'; + my_excpt_def_sqlstate exception; + begin + begin + raise exception my_excpt_def_sqlstate 'foo'; + exception when my_excpt_def_sqlstate then + raise notice '01 catch: %, %', sqlstate, sqlerrm; + end; + begin + raise notice '%', innerfx(); + exception when my_excpt then + raise notice '02 catch: %, %', sqlstate, sqlerrm::numeric; + end; + begin + raise exception alias_div_by_zero 'testing'; + exception when division_by_zero then + raise notice 'Divison by zero: %, %', sqlstate, sqlerrm; + end; + return 1; + end; $$ language plpgsql; + + select innerfx(); + select outerfx(); + + drop function outerfx(); + drop function innerfx();