diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index b30cc7e83fe7a1eea8972072b4bd359fba8773e9..7f3f84448d789ce47cbc3027dc50fe399d50298b 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.41 2004/07/11 23:26:51 momjian Exp $ +$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.42 2004/07/31 07:39:17 tgl Exp $ --> <chapter id="plpgsql"> @@ -1796,6 +1796,101 @@ END LOOP; rather than the simple syntax error one might expect to get. </para> </note> + </sect2> + + <sect2 id="plpgsql-error-trapping"> + <title>Trapping Errors</title> + + <para> + By default, any error occurring in a <application>PL/pgSQL</> + function aborts execution of the function, and indeed of the + surrounding transaction as well. You can trap errors and recover + from them by using a <command>BEGIN</> block with an + <literal>EXCEPTION</> clause. The syntax is an extension of the + normal syntax for a <command>BEGIN</> block: + +<synopsis> +<optional> <<<replaceable>label</replaceable>>> </optional> +<optional> DECLARE + <replaceable>declarations</replaceable> </optional> +BEGIN + <replaceable>statements</replaceable> +EXCEPTION + WHEN <replaceable>condition</replaceable> THEN + <replaceable>handler_statements</replaceable> + <optional> WHEN <replaceable>condition</replaceable> THEN + <replaceable>handler_statements</replaceable> + ... + </optional> +END; +</synopsis> + </para> + + <para> + If no error occurs, this form of block simply executes all the + <replaceable>statements</replaceable>, and then control passes + to the next statement after <literal>END</>. But if an error + occurs within the <replaceable>statements</replaceable>, further + processing of the <replaceable>statements</replaceable> is + abandoned, and control passes to the <literal>EXCEPTION</> list. + The list is searched for the first <replaceable>condition</replaceable> + matching the error that occurred. If a match is found, the + corresponding <replaceable>handler_statements</replaceable> are + executed, and then control passes to the next statement after + <literal>END</>. If no match is found, the error propagates out + as though the <literal>EXCEPTION</> clause were not there at all: + the error can be caught by an enclosing block with + <literal>EXCEPTION</>, or if there is none it aborts processing + of the function. The special condition name <literal>OTHERS</> + matches every error type except <literal>QUERY_CANCELED</>. + (It is possible, but usually not a good idea, to trap + <literal>QUERY_CANCELED</> by name.) + </para> + + <para> + If a new error occurs within the selected + <replaceable>handler_statements</replaceable>, it cannot be caught + by this <literal>EXCEPTION</> clause, but is propagated out. + A surrounding <literal>EXCEPTION</> clause could catch it. + </para> + + <para> + When an error is caught by an <literal>EXCEPTION</> clause, + the local variables of the <application>PL/pgSQL</> function + remain as they were when the error occurred, but all changes + to persistent database state within the block are rolled back. + As an example, consider this fragment: + +<programlisting> + INSERT INTO mytab(firstname, lastname) VALUES('Tom', 'Jones'); + BEGIN + UPDATE mytab SET firstname = 'Joe' WHERE lastname = 'Jones'; + x := x + 1; + y := x / 0; + EXCEPTION + WHEN division_by_zero THEN + RAISE NOTICE 'caught division_by_zero'; + RETURN x; + END; +</programlisting> + + When control reaches the assignment to <literal>y</>, it will + fail with a <literal>division_by_zero</> error. This will be caught by + the <literal>EXCEPTION</> clause. The value returned in the + <command>RETURN</> statement will be the incremented value of + <literal>x</>, but the effects of the <command>UPDATE</> command will + have been rolled back. The <command>INSERT</> command is not rolled + back, however, so the end result is that the database contains + <literal>Tom Jones</> not <literal>Joe Jones</>. + </para> + + <tip> + <para> + A block containing an <literal>EXCEPTION</> clause is significantly + more expensive to enter and exit than a block without one. Therefore, + don't use <literal>EXCEPTION</> without need. + </para> + </tip> </sect2> </sect1> @@ -2120,11 +2215,11 @@ RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="pa </synopsis> Possible levels are <literal>DEBUG</literal>, - <literal>LOG</literal>, + <literal>LOG</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>, and <literal>EXCEPTION</literal>. - <literal>EXCEPTION</literal> raises an error and aborts the current - transaction; the other levels only generate messages of different + <literal>EXCEPTION</literal> raises an error (which normally aborts the + current transaction); the other levels only generate messages of different priority levels. Whether messages of a particular priority are reported to the client, written to the server log, or both is controlled by the @@ -2164,28 +2259,11 @@ RAISE EXCEPTION 'Nonexistent ID --> %', user_id; </para> <para> - <productname>PostgreSQL</productname> does not have a very smart - exception handling model. Whenever the parser, planner/optimizer - or executor decide that a statement cannot be processed any longer, - the whole transaction gets aborted and the system jumps back - into the main loop to get the next command from the client application. - </para> - - <para> - It is possible to hook into the error mechanism to notice that this - happens. But currently it is impossible to tell what really - caused the abort (data type format error, floating-point - error, parse error, etc.). And it is possible that the database server - is in an inconsistent state at this point so returning to the upper - executor or issuing more commands might corrupt the whole database. - </para> - - <para> - Thus, the only thing <application>PL/pgSQL</application> - currently does when it encounters an abort during execution of a - function or trigger procedure is to add some fields to the message - telling in which function and where (line number and type of statement) - the error happened. The error always stops execution of the function. + <command>RAISE EXCEPTION</command> presently always generates + the same SQLSTATE 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. </para> </sect1> diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index f938cdcc5ba55f86bd24ae0c5d7e8e84091fe1a5..b6758a14b227a1376ee805b4b2a46f44b67bdab5 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.173 2004/07/28 14:23:27 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.174 2004/07/31 07:39:18 tgl Exp $ * * NOTES * Transaction aborts can now occur two ways: @@ -1589,7 +1589,8 @@ CleanupTransaction(void) * State should still be TRANS_ABORT from AbortTransaction(). */ if (s->state != TRANS_ABORT) - elog(FATAL, "CleanupTransaction and not in abort state"); + elog(FATAL, "CleanupTransaction: unexpected state %s", + TransStateAsString(s->state)); /* * do abort cleanup processing @@ -1773,7 +1774,7 @@ CommitTransactionCommand(void) /* * We were just issued a SAVEPOINT inside a transaction block. - * Start a subtransaction. (BeginTransactionBlock already + * Start a subtransaction. (DefineSavepoint already * did PushTransaction, so as to have someplace to put the * SUBBEGIN state.) */ @@ -1853,6 +1854,7 @@ CleanupAbortedSubTransactions(bool returnName) AssertState(PointerIsValid(s->parent)); Assert(s->parent->blockState == TBLOCK_SUBINPROGRESS || s->parent->blockState == TBLOCK_INPROGRESS || + s->parent->blockState == TBLOCK_STARTED || s->parent->blockState == TBLOCK_SUBABORT_PENDING); /* @@ -1878,7 +1880,8 @@ CleanupAbortedSubTransactions(bool returnName) } AssertState(s->blockState == TBLOCK_SUBINPROGRESS || - s->blockState == TBLOCK_INPROGRESS); + s->blockState == TBLOCK_INPROGRESS || + s->blockState == TBLOCK_STARTED); return name; } @@ -2468,7 +2471,7 @@ DefineSavepoint(char *name) case TBLOCK_SUBABORT_PENDING: case TBLOCK_SUBENDABORT_RELEASE: case TBLOCK_SUBEND: - elog(FATAL, "BeginTransactionBlock: unexpected state %s", + elog(FATAL, "DefineSavepoint: unexpected state %s", BlockStateAsString(s->blockState)); break; } @@ -2657,20 +2660,126 @@ RollbackToSavepoint(List *options) } /* - * RollbackAndReleaseSavepoint + * BeginInternalSubTransaction + * This is the same as DefineSavepoint except it allows TBLOCK_STARTED + * state, and therefore it can safely be used in a function that might + * be called when not inside a BEGIN block. Also, we automatically + * cycle through CommitTransactionCommand/StartTransactionCommand + * instead of expecting the caller to do it. + * + * Optionally, name can be NULL to create an unnamed savepoint. + */ +void +BeginInternalSubTransaction(char *name) +{ + TransactionState s = CurrentTransactionState; + + switch (s->blockState) + { + case TBLOCK_STARTED: + case TBLOCK_INPROGRESS: + case TBLOCK_SUBINPROGRESS: + /* Normal subtransaction start */ + PushTransaction(); + s = CurrentTransactionState; /* changed by push */ + /* + * Note that we are allocating the savepoint name in the + * parent transaction's CurTransactionContext, since we + * don't yet have a transaction context for the new guy. + */ + if (name) + s->name = MemoryContextStrdup(CurTransactionContext, name); + s->blockState = TBLOCK_SUBBEGIN; + break; + + /* These cases are invalid. Reject them altogether. */ + case TBLOCK_DEFAULT: + case TBLOCK_BEGIN: + case TBLOCK_SUBBEGIN: + case TBLOCK_ABORT: + case TBLOCK_SUBABORT: + case TBLOCK_ENDABORT: + case TBLOCK_END: + case TBLOCK_SUBENDABORT_ALL: + case TBLOCK_SUBENDABORT: + case TBLOCK_SUBABORT_PENDING: + case TBLOCK_SUBENDABORT_RELEASE: + case TBLOCK_SUBEND: + elog(FATAL, "BeginInternalSubTransaction: unexpected state %s", + BlockStateAsString(s->blockState)); + break; + } + + CommitTransactionCommand(); + StartTransactionCommand(); +} + +/* + * ReleaseCurrentSubTransaction + * + * RELEASE (ie, commit) the innermost subtransaction, regardless of its + * savepoint name (if any). + * NB: do NOT use CommitTransactionCommand/StartTransactionCommand with this. + */ +void +ReleaseCurrentSubTransaction(void) +{ + TransactionState s = CurrentTransactionState; + + if (s->blockState != TBLOCK_SUBINPROGRESS) + elog(ERROR, "ReleaseCurrentSubTransaction: unexpected state %s", + BlockStateAsString(s->blockState)); + MemoryContextSwitchTo(CurTransactionContext); + CommitTransactionToLevel(GetCurrentTransactionNestLevel()); +} + +/* + * RollbackAndReleaseCurrentSubTransaction * - * Executes a ROLLBACK TO command, immediately followed by a RELEASE - * of the same savepoint. + * ROLLBACK and RELEASE (ie, abort) the innermost subtransaction, regardless + * of its savepoint name (if any). + * NB: do NOT use CommitTransactionCommand/StartTransactionCommand with this. */ void -RollbackAndReleaseSavepoint(List *options) +RollbackAndReleaseCurrentSubTransaction(void) { - TransactionState s; + TransactionState s = CurrentTransactionState; - RollbackToSavepoint(options); - s = CurrentTransactionState; - Assert(s->blockState == TBLOCK_SUBENDABORT); + switch (s->blockState) + { + /* Must be in a subtransaction */ + case TBLOCK_SUBABORT: + case TBLOCK_SUBINPROGRESS: + break; + + /* these cases are invalid. */ + case TBLOCK_DEFAULT: + case TBLOCK_STARTED: + case TBLOCK_ABORT: + case TBLOCK_INPROGRESS: + case TBLOCK_BEGIN: + case TBLOCK_END: + case TBLOCK_ENDABORT: + case TBLOCK_SUBEND: + case TBLOCK_SUBENDABORT_ALL: + case TBLOCK_SUBENDABORT: + case TBLOCK_SUBABORT_PENDING: + case TBLOCK_SUBENDABORT_RELEASE: + case TBLOCK_SUBBEGIN: + elog(FATAL, "RollbackAndReleaseCurrentSubTransaction: unexpected state %s", + BlockStateAsString(s->blockState)); + break; + } + + /* + * Abort the current subtransaction, if needed. + */ + if (s->blockState == TBLOCK_SUBINPROGRESS) + AbortSubTransaction(); s->blockState = TBLOCK_SUBENDABORT_RELEASE; + + /* And clean it up, too */ + CleanupAbortedSubTransactions(false); } /* @@ -2748,7 +2857,7 @@ AbortOutOfAnyTransaction(void) * Commit everything from the current transaction level * up to the specified level (inclusive). */ -void +static void CommitTransactionToLevel(int level) { TransactionState s = CurrentTransactionState; diff --git a/src/include/access/xact.h b/src/include/access/xact.h index 7bf92b0153de8b6fa092150e191099ebbcdb94ee..532dcf51b0e97e7a4e5a331e05df6ab4df747c94 100644 --- a/src/include/access/xact.h +++ b/src/include/access/xact.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/access/xact.h,v 1.67 2004/07/27 05:11:24 tgl Exp $ + * $PostgreSQL: pgsql/src/include/access/xact.h,v 1.68 2004/07/31 07:39:19 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -107,7 +107,9 @@ extern void UserAbortTransactionBlock(void); extern void ReleaseSavepoint(List *options); extern void DefineSavepoint(char *name); extern void RollbackToSavepoint(List *options); -extern void RollbackAndReleaseSavepoint(List *options); +extern void BeginInternalSubTransaction(char *name); +extern void ReleaseCurrentSubTransaction(void); +extern void RollbackAndReleaseCurrentSubTransaction(void); extern bool IsSubTransaction(void); extern bool IsTransactionBlock(void); extern bool IsTransactionOrTransactionBlock(void); diff --git a/src/include/utils/errcodes.h b/src/include/utils/errcodes.h index f3ac7f6c0b4b94a92cb252bc952ae2bec60bf595..a695b90a2ea7b2c9f12a7d1105f7e1021e557321 100644 --- a/src/include/utils/errcodes.h +++ b/src/include/utils/errcodes.h @@ -11,7 +11,7 @@ * * Copyright (c) 2003, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.13 2004/07/27 05:11:35 tgl Exp $ + * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.14 2004/07/31 07:39:20 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -326,6 +326,10 @@ #define ERRCODE_CONFIG_FILE_ERROR MAKE_SQLSTATE('F','0', '0','0','0') #define ERRCODE_LOCK_FILE_EXISTS MAKE_SQLSTATE('F','0', '0','0','1') +/* Class P0 - PL/pgSQL Error (PostgreSQL-specific error class) */ +#define ERRCODE_PLPGSQL_ERROR MAKE_SQLSTATE('P','0', '0','0','0') +#define ERRCODE_RAISE_EXCEPTION MAKE_SQLSTATE('P','0', '0','0','1') + /* Class XX - Internal Error (PostgreSQL-specific error class) */ /* (this is for "can't-happen" conditions and software bugs) */ #define ERRCODE_INTERNAL_ERROR MAKE_SQLSTATE('X','X', '0','0','0') diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y index 9b70d944ab98aea9bba481ca119c7b428ad5955f..628c81b570bc7d1cf3269277fb1581762154639c 100644 --- a/src/pl/plpgsql/src/gram.y +++ b/src/pl/plpgsql/src/gram.y @@ -4,7 +4,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.57 2004/07/04 02:49:04 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.58 2004/07/31 07:39:20 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -96,6 +96,8 @@ static void check_assignable(PLpgSQL_datum *datum); PLpgSQL_stmt *stmt; PLpgSQL_stmts *stmts; PLpgSQL_stmt_block *program; + PLpgSQL_exception *exception; + PLpgSQL_exceptions *exceptions; PLpgSQL_nsitem *nsitem; } @@ -131,6 +133,9 @@ static void check_assignable(PLpgSQL_datum *datum); %type <stmt> stmt_dynexecute stmt_getdiag %type <stmt> stmt_open stmt_fetch stmt_close +%type <exceptions> exception_sect proc_exceptions +%type <exception> proc_exception + %type <intlist> raise_params %type <ival> raise_level raise_param %type <str> raise_msg @@ -240,7 +245,7 @@ opt_semi : | ';' ; -pl_block : decl_sect K_BEGIN lno proc_sect K_END +pl_block : decl_sect K_BEGIN lno proc_sect exception_sect K_END { PLpgSQL_stmt_block *new; @@ -253,6 +258,7 @@ pl_block : decl_sect K_BEGIN lno proc_sect K_END new->n_initvars = $1.n_initvars; new->initvarnos = $1.initvarnos; new->body = $4; + new->exceptions = $5; plpgsql_ns_pop(); @@ -588,7 +594,7 @@ proc_stmts : proc_stmts proc_stmt $1->stmts_alloc *= 2; $1->stmts = realloc($1->stmts, sizeof(PLpgSQL_stmt *) * $1->stmts_alloc); } - $1->stmts[$1->stmts_used++] = (struct PLpgSQL_stmt *)$2; + $1->stmts[$1->stmts_used++] = $2; $$ = $1; } @@ -602,7 +608,7 @@ proc_stmts : proc_stmts proc_stmt new->stmts_alloc = 64; new->stmts_used = 1; new->stmts = malloc(sizeof(PLpgSQL_stmt *) * new->stmts_alloc); - new->stmts[0] = (struct PLpgSQL_stmt *)$1; + new->stmts[0] = $1; $$ = new; @@ -832,7 +838,7 @@ stmt_else : new->stmts_alloc = 64; new->stmts_used = 1; new->stmts = malloc(sizeof(PLpgSQL_stmt *) * new->stmts_alloc); - new->stmts[0] = (struct PLpgSQL_stmt *)new_if; + new->stmts[0] = (PLpgSQL_stmt *) new_if; $$ = new; @@ -1524,6 +1530,54 @@ execsql_start : T_WORD { $$ = strdup(yytext); } ; +exception_sect : + { $$ = NULL; } + | K_EXCEPTION proc_exceptions + { $$ = $2; } + ; + +proc_exceptions : proc_exceptions proc_exception + { + if ($1->exceptions_used == $1->exceptions_alloc) + { + $1->exceptions_alloc *= 2; + $1->exceptions = realloc($1->exceptions, sizeof(PLpgSQL_exception *) * $1->exceptions_alloc); + } + $1->exceptions[$1->exceptions_used++] = $2; + + $$ = $1; + } + | proc_exception + { + PLpgSQL_exceptions *new; + + new = malloc(sizeof(PLpgSQL_exceptions)); + memset(new, 0, sizeof(PLpgSQL_exceptions)); + + new->exceptions_alloc = 64; + new->exceptions_used = 1; + new->exceptions = malloc(sizeof(PLpgSQL_exception *) * new->exceptions_alloc); + new->exceptions[0] = $1; + + $$ = new; + } + ; + +proc_exception : K_WHEN lno opt_lblname K_THEN proc_sect + { + PLpgSQL_exception *new; + + new = malloc(sizeof(PLpgSQL_exception)); + memset(new, 0, sizeof(PLpgSQL_exception)); + + new->lineno = $2; + new->label = $3; + new->action = $5; + + $$ = new; + } + ; + expr_until_semi : { $$ = plpgsql_read_expression(';', ";"); } ; diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index d6f8c6835ccc993418ab90d36efba206341876e4..d8befe4d529cc5ea16eca827631eb2a08c3e7173 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.108 2004/07/31 00:45:46 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.109 2004/07/31 07:39:20 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -56,6 +56,16 @@ static const char *const raise_skip_msg = "RAISE"; +typedef struct { + const char *label; + int sqlerrstate; +} ExceptionLabelMap; + +static const ExceptionLabelMap exception_label_map[] = { +#include "plerrcodes.h" + { NULL, 0 } +}; + /* * All plpgsql function executions within a single transaction share * the same executor EState for evaluating "simple" expressions. Each @@ -784,6 +794,32 @@ copy_rec(PLpgSQL_rec * rec) } +static bool +exception_matches_label(ErrorData *edata, const char *label) +{ + int i; + + /* + * OTHERS matches everything *except* query-canceled; + * if you're foolish enough, you can match that explicitly. + */ + if (pg_strcasecmp(label, "OTHERS") == 0) + { + if (edata->sqlerrcode == ERRCODE_QUERY_CANCELED) + return false; + else + return true; + } + for (i = 0; exception_label_map[i].label != NULL; i++) + { + if (pg_strcasecmp(label, exception_label_map[i].label) == 0) + return (edata->sqlerrcode == exception_label_map[i].sqlerrstate); + } + /* Should we raise an error if label is unrecognized?? */ + return false; +} + + /* ---------- * exec_stmt_block Execute a block of statements * ---------- @@ -791,7 +827,7 @@ copy_rec(PLpgSQL_rec * rec) static int exec_stmt_block(PLpgSQL_execstate * estate, PLpgSQL_stmt_block * block) { - int rc; + volatile int rc = -1; int i; int n; @@ -859,13 +895,86 @@ exec_stmt_block(PLpgSQL_execstate * estate, PLpgSQL_stmt_block * block) elog(ERROR, "unrecognized dtype: %d", estate->datums[n]->dtype); } - } - /* - * Execute the statements in the block's body - */ - rc = exec_stmts(estate, block->body); + if (block->exceptions) + { + /* + * Execute the statements in the block's body inside a sub-transaction + */ + MemoryContext oldcontext = CurrentMemoryContext; + volatile bool caught = false; + + /* + * Start a subtransaction, and re-connect to SPI within it + */ + SPI_push(); + BeginInternalSubTransaction(NULL); + /* Want to run statements inside function's memory context */ + MemoryContextSwitchTo(oldcontext); + if (SPI_connect() != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + PG_TRY(); + { + rc = exec_stmts(estate, block->body); + } + PG_CATCH(); + { + ErrorData *edata; + PLpgSQL_exceptions *exceptions; + int j; + + /* Save error info */ + MemoryContextSwitchTo(oldcontext); + edata = CopyErrorData(); + FlushErrorState(); + + /* Abort the inner transaction (and inner SPI connection) */ + RollbackAndReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(oldcontext); + + SPI_pop(); + + /* Look for a matching exception handler */ + exceptions = block->exceptions; + for (j = 0; j < exceptions->exceptions_used; j++) + { + PLpgSQL_exception *exception = exceptions->exceptions[j]; + + if (exception_matches_label(edata, exception->label)) + { + rc = exec_stmts(estate, exception->action); + break; + } + } + + /* If no match found, re-throw the error */ + if (j >= exceptions->exceptions_used) + ReThrowError(edata); + else + FreeErrorData(edata); + caught = true; + } + PG_END_TRY(); + + /* Commit the inner transaction, return to outer xact context */ + if (!caught) + { + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + ReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(oldcontext); + SPI_pop(); + } + } + else + { + /* + * Just execute the statements in the block's body + */ + rc = exec_stmts(estate, block->body); + } /* * Handle the return code. @@ -909,7 +1018,7 @@ exec_stmts(PLpgSQL_execstate * estate, PLpgSQL_stmts * stmts) for (i = 0; i < stmts->stmts_used; i++) { - rc = exec_stmt(estate, (PLpgSQL_stmt *) (stmts->stmts[i])); + rc = exec_stmt(estate, stmts->stmts[i]); if (rc != PLPGSQL_RC_OK) return rc; } @@ -1852,7 +1961,8 @@ exec_stmt_raise(PLpgSQL_execstate * estate, PLpgSQL_stmt_raise * stmt) estate->err_text = raise_skip_msg; /* suppress traceback of raise */ ereport(stmt->elog_level, - (errmsg_internal("%s", plpgsql_dstring_get(&ds)))); + ((stmt->elog_level >= ERROR) ? errcode(ERRCODE_RAISE_EXCEPTION) : 0, + errmsg_internal("%s", plpgsql_dstring_get(&ds)))); estate->err_text = NULL; /* un-suppress... */ diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index f49a2ac500c72da0526c68badbf5cba5bec49839..b4649067522a2bb2a88ec26ede4ffddb8f743dc9 100644 --- a/src/pl/plpgsql/src/pl_funcs.c +++ b/src/pl/plpgsql/src/pl_funcs.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.32 2004/02/21 00:34:53 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.33 2004/07/31 07:39:20 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -583,6 +583,17 @@ dump_stmt(PLpgSQL_stmt * stmt) } } +static void +dump_stmts(PLpgSQL_stmts * stmts) +{ + int i; + + dump_indent += 2; + for (i = 0; i < stmts->stmts_used; i++) + dump_stmt(stmts->stmts[i]); + dump_indent -= 2; +} + static void dump_block(PLpgSQL_stmt_block * block) { @@ -597,10 +608,19 @@ dump_block(PLpgSQL_stmt_block * block) dump_ind(); printf("BLOCK <<%s>>\n", name); - dump_indent += 2; - for (i = 0; i < block->body->stmts_used; i++) - dump_stmt((PLpgSQL_stmt *) (block->body->stmts[i])); - dump_indent -= 2; + dump_stmts(block->body); + + if (block->exceptions) + { + for (i = 0; i < block->exceptions->exceptions_used; i++) + { + PLpgSQL_exception *exc = block->exceptions->exceptions[i]; + + dump_ind(); + printf(" EXCEPTION WHEN %s THEN\n", exc->label); + dump_stmts(exc->action); + } + } dump_ind(); printf(" END -- %s\n", name); @@ -618,25 +638,17 @@ dump_assign(PLpgSQL_stmt_assign * stmt) static void dump_if(PLpgSQL_stmt_if * stmt) { - int i; - dump_ind(); printf("IF "); dump_expr(stmt->cond); printf(" THEN\n"); - dump_indent += 2; - for (i = 0; i < stmt->true_body->stmts_used; i++) - dump_stmt((PLpgSQL_stmt *) (stmt->true_body->stmts[i])); - dump_indent -= 2; + dump_stmts(stmt->true_body); dump_ind(); printf(" ELSE\n"); - dump_indent += 2; - for (i = 0; i < stmt->false_body->stmts_used; i++) - dump_stmt((PLpgSQL_stmt *) (stmt->false_body->stmts[i])); - dump_indent -= 2; + dump_stmts(stmt->false_body); dump_ind(); printf(" ENDIF\n"); @@ -645,15 +657,10 @@ dump_if(PLpgSQL_stmt_if * stmt) static void dump_loop(PLpgSQL_stmt_loop * stmt) { - int i; - dump_ind(); printf("LOOP\n"); - dump_indent += 2; - for (i = 0; i < stmt->body->stmts_used; i++) - dump_stmt((PLpgSQL_stmt *) (stmt->body->stmts[i])); - dump_indent -= 2; + dump_stmts(stmt->body); dump_ind(); printf(" ENDLOOP\n"); @@ -662,17 +669,12 @@ dump_loop(PLpgSQL_stmt_loop * stmt) static void dump_while(PLpgSQL_stmt_while * stmt) { - int i; - dump_ind(); printf("WHILE "); dump_expr(stmt->cond); printf("\n"); - dump_indent += 2; - for (i = 0; i < stmt->body->stmts_used; i++) - dump_stmt((PLpgSQL_stmt *) (stmt->body->stmts[i])); - dump_indent -= 2; + dump_stmts(stmt->body); dump_ind(); printf(" ENDWHILE\n"); @@ -681,8 +683,6 @@ dump_while(PLpgSQL_stmt_while * stmt) static void dump_fori(PLpgSQL_stmt_fori * stmt) { - int i; - dump_ind(); printf("FORI %s %s\n", stmt->var->refname, (stmt->reverse) ? "REVERSE" : "NORMAL"); @@ -695,11 +695,10 @@ dump_fori(PLpgSQL_stmt_fori * stmt) printf(" upper = "); dump_expr(stmt->upper); printf("\n"); - - for (i = 0; i < stmt->body->stmts_used; i++) - dump_stmt((PLpgSQL_stmt *) (stmt->body->stmts[i])); dump_indent -= 2; + dump_stmts(stmt->body); + dump_ind(); printf(" ENDFORI\n"); } @@ -707,17 +706,12 @@ dump_fori(PLpgSQL_stmt_fori * stmt) static void dump_fors(PLpgSQL_stmt_fors * stmt) { - int i; - dump_ind(); printf("FORS %s ", (stmt->rec != NULL) ? stmt->rec->refname : stmt->row->refname); dump_expr(stmt->query); printf("\n"); - dump_indent += 2; - for (i = 0; i < stmt->body->stmts_used; i++) - dump_stmt((PLpgSQL_stmt *) (stmt->body->stmts[i])); - dump_indent -= 2; + dump_stmts(stmt->body); dump_ind(); printf(" ENDFORS\n"); @@ -891,17 +885,12 @@ dump_dynexecute(PLpgSQL_stmt_dynexecute * stmt) static void dump_dynfors(PLpgSQL_stmt_dynfors * stmt) { - int i; - dump_ind(); printf("FORS %s EXECUTE ", (stmt->rec != NULL) ? stmt->rec->refname : stmt->row->refname); dump_expr(stmt->query); printf("\n"); - dump_indent += 2; - for (i = 0; i < stmt->body->stmts_used; i++) - dump_stmt((PLpgSQL_stmt *) (stmt->body->stmts[i])); - dump_indent -= 2; + dump_stmts(stmt->body); dump_ind(); printf(" ENDFORS\n"); @@ -1051,4 +1040,5 @@ plpgsql_dumptree(PLpgSQL_function * func) printf("%3d:", func->action->lineno); dump_block(func->action); printf("\nEnd of execution tree of function %s\n\n", func->fn_name); + fflush(stdout); } diff --git a/src/pl/plpgsql/src/plerrcodes.h b/src/pl/plpgsql/src/plerrcodes.h new file mode 100644 index 0000000000000000000000000000000000000000..4e76bde6739945d5e3efaefe95698ad039e36351 --- /dev/null +++ b/src/pl/plpgsql/src/plerrcodes.h @@ -0,0 +1,203 @@ +/*------------------------------------------------------------------------- + * + * plerrcodes.h + * PL/pgSQL error codes (mapping of exception labels to SQLSTATEs) + * + * Eventually this header file should be auto-generated from errcodes.h + * with some sort of sed hackery, but no time for that now. It's likely + * that an exact mapping will not be what's wanted anyhow ... + * + * Copyright (c) 2003, PostgreSQL Global Development Group + * + * $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.1 2004/07/31 07:39:20 tgl Exp $ + * + *------------------------------------------------------------------------- + */ + +{ "SUCCESSFUL_COMPLETION", ERRCODE_SUCCESSFUL_COMPLETION }, +{ "WARNING", ERRCODE_WARNING }, +{ "WARNING_DYNAMIC_RESULT_SETS_RETURNED", ERRCODE_WARNING_DYNAMIC_RESULT_SETS_RETURNED }, +{ "WARNING_IMPLICIT_ZERO_BIT_PADDING", ERRCODE_WARNING_IMPLICIT_ZERO_BIT_PADDING }, +{ "WARNING_NULL_VALUE_ELIMINATED_IN_SET_FUNCTION", ERRCODE_WARNING_NULL_VALUE_ELIMINATED_IN_SET_FUNCTION }, +{ "WARNING_PRIVILEGE_NOT_GRANTED", ERRCODE_WARNING_PRIVILEGE_NOT_GRANTED }, +{ "WARNING_PRIVILEGE_NOT_REVOKED", ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED }, +{ "WARNING_STRING_DATA_RIGHT_TRUNCATION", ERRCODE_WARNING_STRING_DATA_RIGHT_TRUNCATION }, +{ "WARNING_DEPRECATED_FEATURE", ERRCODE_WARNING_DEPRECATED_FEATURE }, +{ "NO_DATA", ERRCODE_NO_DATA }, +{ "NO_ADDITIONAL_DYNAMIC_RESULT_SETS_RETURNED", ERRCODE_NO_ADDITIONAL_DYNAMIC_RESULT_SETS_RETURNED }, +{ "SQL_STATEMENT_NOT_YET_COMPLETE", ERRCODE_SQL_STATEMENT_NOT_YET_COMPLETE }, +{ "CONNECTION_EXCEPTION", ERRCODE_CONNECTION_EXCEPTION }, +{ "CONNECTION_DOES_NOT_EXIST", ERRCODE_CONNECTION_DOES_NOT_EXIST }, +{ "CONNECTION_FAILURE", ERRCODE_CONNECTION_FAILURE }, +{ "SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION", ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION }, +{ "SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION", ERRCODE_SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION }, +{ "TRANSACTION_RESOLUTION_UNKNOWN", ERRCODE_TRANSACTION_RESOLUTION_UNKNOWN }, +{ "PROTOCOL_VIOLATION", ERRCODE_PROTOCOL_VIOLATION }, +{ "TRIGGERED_ACTION_EXCEPTION", ERRCODE_TRIGGERED_ACTION_EXCEPTION }, +{ "FEATURE_NOT_SUPPORTED", ERRCODE_FEATURE_NOT_SUPPORTED }, +{ "INVALID_TRANSACTION_INITIATION", ERRCODE_INVALID_TRANSACTION_INITIATION }, +{ "LOCATOR_EXCEPTION", ERRCODE_LOCATOR_EXCEPTION }, +{ "L_E_INVALID_SPECIFICATION", ERRCODE_L_E_INVALID_SPECIFICATION }, +{ "INVALID_GRANTOR", ERRCODE_INVALID_GRANTOR }, +{ "INVALID_GRANT_OPERATION", ERRCODE_INVALID_GRANT_OPERATION }, +{ "INVALID_ROLE_SPECIFICATION", ERRCODE_INVALID_ROLE_SPECIFICATION }, +{ "CARDINALITY_VIOLATION", ERRCODE_CARDINALITY_VIOLATION }, +{ "DATA_EXCEPTION", ERRCODE_DATA_EXCEPTION }, +{ "ARRAY_ELEMENT_ERROR", ERRCODE_ARRAY_ELEMENT_ERROR }, +{ "ARRAY_SUBSCRIPT_ERROR", ERRCODE_ARRAY_SUBSCRIPT_ERROR }, +{ "CHARACTER_NOT_IN_REPERTOIRE", ERRCODE_CHARACTER_NOT_IN_REPERTOIRE }, +{ "DATETIME_FIELD_OVERFLOW", ERRCODE_DATETIME_FIELD_OVERFLOW }, +{ "DATETIME_VALUE_OUT_OF_RANGE", ERRCODE_DATETIME_VALUE_OUT_OF_RANGE }, +{ "DIVISION_BY_ZERO", ERRCODE_DIVISION_BY_ZERO }, +{ "ERROR_IN_ASSIGNMENT", ERRCODE_ERROR_IN_ASSIGNMENT }, +{ "ESCAPE_CHARACTER_CONFLICT", ERRCODE_ESCAPE_CHARACTER_CONFLICT }, +{ "INDICATOR_OVERFLOW", ERRCODE_INDICATOR_OVERFLOW }, +{ "INTERVAL_FIELD_OVERFLOW", ERRCODE_INTERVAL_FIELD_OVERFLOW }, +{ "INVALID_ARGUMENT_FOR_LOG", ERRCODE_INVALID_ARGUMENT_FOR_LOG }, +{ "INVALID_ARGUMENT_FOR_POWER_FUNCTION", ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION }, +{ "INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION", ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION }, +{ "INVALID_CHARACTER_VALUE_FOR_CAST", ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST }, +{ "INVALID_DATETIME_FORMAT", ERRCODE_INVALID_DATETIME_FORMAT }, +{ "INVALID_ESCAPE_CHARACTER", ERRCODE_INVALID_ESCAPE_CHARACTER }, +{ "INVALID_ESCAPE_OCTET", ERRCODE_INVALID_ESCAPE_OCTET }, +{ "INVALID_ESCAPE_SEQUENCE", ERRCODE_INVALID_ESCAPE_SEQUENCE }, +{ "INVALID_INDICATOR_PARAMETER_VALUE", ERRCODE_INVALID_INDICATOR_PARAMETER_VALUE }, +{ "INVALID_LIMIT_VALUE", ERRCODE_INVALID_LIMIT_VALUE }, +{ "INVALID_PARAMETER_VALUE", ERRCODE_INVALID_PARAMETER_VALUE }, +{ "INVALID_REGULAR_EXPRESSION", ERRCODE_INVALID_REGULAR_EXPRESSION }, +{ "INVALID_TIME_ZONE_DISPLACEMENT_VALUE", ERRCODE_INVALID_TIME_ZONE_DISPLACEMENT_VALUE }, +{ "INVALID_USE_OF_ESCAPE_CHARACTER", ERRCODE_INVALID_USE_OF_ESCAPE_CHARACTER }, +{ "MOST_SPECIFIC_TYPE_MISMATCH", ERRCODE_MOST_SPECIFIC_TYPE_MISMATCH }, +{ "NULL_VALUE_NOT_ALLOWED", ERRCODE_NULL_VALUE_NOT_ALLOWED }, +{ "NULL_VALUE_NO_INDICATOR_PARAMETER", ERRCODE_NULL_VALUE_NO_INDICATOR_PARAMETER }, +{ "NUMERIC_VALUE_OUT_OF_RANGE", ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE }, +{ "STRING_DATA_LENGTH_MISMATCH", ERRCODE_STRING_DATA_LENGTH_MISMATCH }, +{ "STRING_DATA_RIGHT_TRUNCATION", ERRCODE_STRING_DATA_RIGHT_TRUNCATION }, +{ "SUBSTRING_ERROR", ERRCODE_SUBSTRING_ERROR }, +{ "TRIM_ERROR", ERRCODE_TRIM_ERROR }, +{ "UNTERMINATED_C_STRING", ERRCODE_UNTERMINATED_C_STRING }, +{ "ZERO_LENGTH_CHARACTER_STRING", ERRCODE_ZERO_LENGTH_CHARACTER_STRING }, +{ "FLOATING_POINT_EXCEPTION", ERRCODE_FLOATING_POINT_EXCEPTION }, +{ "INVALID_TEXT_REPRESENTATION", ERRCODE_INVALID_TEXT_REPRESENTATION }, +{ "INVALID_BINARY_REPRESENTATION", ERRCODE_INVALID_BINARY_REPRESENTATION }, +{ "BAD_COPY_FILE_FORMAT", ERRCODE_BAD_COPY_FILE_FORMAT }, +{ "UNTRANSLATABLE_CHARACTER", ERRCODE_UNTRANSLATABLE_CHARACTER }, +{ "INTEGRITY_CONSTRAINT_VIOLATION", ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION }, +{ "RESTRICT_VIOLATION", ERRCODE_RESTRICT_VIOLATION }, +{ "NOT_NULL_VIOLATION", ERRCODE_NOT_NULL_VIOLATION }, +{ "FOREIGN_KEY_VIOLATION", ERRCODE_FOREIGN_KEY_VIOLATION }, +{ "UNIQUE_VIOLATION", ERRCODE_UNIQUE_VIOLATION }, +{ "CHECK_VIOLATION", ERRCODE_CHECK_VIOLATION }, +{ "INVALID_CURSOR_STATE", ERRCODE_INVALID_CURSOR_STATE }, +{ "INVALID_TRANSACTION_STATE", ERRCODE_INVALID_TRANSACTION_STATE }, +{ "ACTIVE_SQL_TRANSACTION", ERRCODE_ACTIVE_SQL_TRANSACTION }, +{ "BRANCH_TRANSACTION_ALREADY_ACTIVE", ERRCODE_BRANCH_TRANSACTION_ALREADY_ACTIVE }, +{ "HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL", ERRCODE_HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL }, +{ "INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION", ERRCODE_INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION }, +{ "INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION", ERRCODE_INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION }, +{ "NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION", ERRCODE_NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION }, +{ "READ_ONLY_SQL_TRANSACTION", ERRCODE_READ_ONLY_SQL_TRANSACTION }, +{ "SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED", ERRCODE_SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED }, +{ "NO_ACTIVE_SQL_TRANSACTION", ERRCODE_NO_ACTIVE_SQL_TRANSACTION }, +{ "IN_FAILED_SQL_TRANSACTION", ERRCODE_IN_FAILED_SQL_TRANSACTION }, +{ "INVALID_SQL_STATEMENT_NAME", ERRCODE_INVALID_SQL_STATEMENT_NAME }, +{ "TRIGGERED_DATA_CHANGE_VIOLATION", ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION }, +{ "INVALID_AUTHORIZATION_SPECIFICATION", ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION }, +{ "DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST", ERRCODE_DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST }, +{ "DEPENDENT_OBJECTS_STILL_EXIST", ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST }, +{ "INVALID_TRANSACTION_TERMINATION", ERRCODE_INVALID_TRANSACTION_TERMINATION }, +{ "SQL_ROUTINE_EXCEPTION", ERRCODE_SQL_ROUTINE_EXCEPTION }, +{ "S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT", ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT }, +{ "S_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED", ERRCODE_S_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED }, +{ "S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED", ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED }, +{ "S_R_E_READING_SQL_DATA_NOT_PERMITTED", ERRCODE_S_R_E_READING_SQL_DATA_NOT_PERMITTED }, +{ "INVALID_CURSOR_NAME", ERRCODE_INVALID_CURSOR_NAME }, +{ "EXTERNAL_ROUTINE_EXCEPTION", ERRCODE_EXTERNAL_ROUTINE_EXCEPTION }, +{ "E_R_E_CONTAINING_SQL_NOT_PERMITTED", ERRCODE_E_R_E_CONTAINING_SQL_NOT_PERMITTED }, +{ "E_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED", ERRCODE_E_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED }, +{ "E_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED", ERRCODE_E_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED }, +{ "E_R_E_READING_SQL_DATA_NOT_PERMITTED", ERRCODE_E_R_E_READING_SQL_DATA_NOT_PERMITTED }, +{ "EXTERNAL_ROUTINE_INVOCATION_EXCEPTION", ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION }, +{ "E_R_I_E_INVALID_SQLSTATE_RETURNED", ERRCODE_E_R_I_E_INVALID_SQLSTATE_RETURNED }, +{ "E_R_I_E_NULL_VALUE_NOT_ALLOWED", ERRCODE_E_R_I_E_NULL_VALUE_NOT_ALLOWED }, +{ "E_R_I_E_TRIGGER_PROTOCOL_VIOLATED", ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED }, +{ "E_R_I_E_SRF_PROTOCOL_VIOLATED", ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED }, +{ "SAVEPOINT_EXCEPTION", ERRCODE_SAVEPOINT_EXCEPTION }, +{ "S_E_INVALID_SPECIFICATION", ERRCODE_S_E_INVALID_SPECIFICATION }, +{ "INVALID_CATALOG_NAME", ERRCODE_INVALID_CATALOG_NAME }, +{ "INVALID_SCHEMA_NAME", ERRCODE_INVALID_SCHEMA_NAME }, +{ "TRANSACTION_ROLLBACK", ERRCODE_TRANSACTION_ROLLBACK }, +{ "T_R_INTEGRITY_CONSTRAINT_VIOLATION", ERRCODE_T_R_INTEGRITY_CONSTRAINT_VIOLATION }, +{ "T_R_SERIALIZATION_FAILURE", ERRCODE_T_R_SERIALIZATION_FAILURE }, +{ "T_R_STATEMENT_COMPLETION_UNKNOWN", ERRCODE_T_R_STATEMENT_COMPLETION_UNKNOWN }, +{ "T_R_DEADLOCK_DETECTED", ERRCODE_T_R_DEADLOCK_DETECTED }, +{ "SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION", ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION }, +{ "SYNTAX_ERROR", ERRCODE_SYNTAX_ERROR }, +{ "INSUFFICIENT_PRIVILEGE", ERRCODE_INSUFFICIENT_PRIVILEGE }, +{ "CANNOT_COERCE", ERRCODE_CANNOT_COERCE }, +{ "GROUPING_ERROR", ERRCODE_GROUPING_ERROR }, +{ "INVALID_FOREIGN_KEY", ERRCODE_INVALID_FOREIGN_KEY }, +{ "INVALID_NAME", ERRCODE_INVALID_NAME }, +{ "NAME_TOO_LONG", ERRCODE_NAME_TOO_LONG }, +{ "RESERVED_NAME", ERRCODE_RESERVED_NAME }, +{ "DATATYPE_MISMATCH", ERRCODE_DATATYPE_MISMATCH }, +{ "INDETERMINATE_DATATYPE", ERRCODE_INDETERMINATE_DATATYPE }, +{ "WRONG_OBJECT_TYPE", ERRCODE_WRONG_OBJECT_TYPE }, +{ "UNDEFINED_COLUMN", ERRCODE_UNDEFINED_COLUMN }, +{ "UNDEFINED_CURSOR", ERRCODE_UNDEFINED_CURSOR }, +{ "UNDEFINED_DATABASE", ERRCODE_UNDEFINED_DATABASE }, +{ "UNDEFINED_FUNCTION", ERRCODE_UNDEFINED_FUNCTION }, +{ "UNDEFINED_PSTATEMENT", ERRCODE_UNDEFINED_PSTATEMENT }, +{ "UNDEFINED_SCHEMA", ERRCODE_UNDEFINED_SCHEMA }, +{ "UNDEFINED_TABLE", ERRCODE_UNDEFINED_TABLE }, +{ "UNDEFINED_PARAMETER", ERRCODE_UNDEFINED_PARAMETER }, +{ "UNDEFINED_OBJECT", ERRCODE_UNDEFINED_OBJECT }, +{ "DUPLICATE_COLUMN", ERRCODE_DUPLICATE_COLUMN }, +{ "DUPLICATE_CURSOR", ERRCODE_DUPLICATE_CURSOR }, +{ "DUPLICATE_DATABASE", ERRCODE_DUPLICATE_DATABASE }, +{ "DUPLICATE_FUNCTION", ERRCODE_DUPLICATE_FUNCTION }, +{ "DUPLICATE_PSTATEMENT", ERRCODE_DUPLICATE_PSTATEMENT }, +{ "DUPLICATE_SCHEMA", ERRCODE_DUPLICATE_SCHEMA }, +{ "DUPLICATE_TABLE", ERRCODE_DUPLICATE_TABLE }, +{ "DUPLICATE_ALIAS", ERRCODE_DUPLICATE_ALIAS }, +{ "DUPLICATE_OBJECT", ERRCODE_DUPLICATE_OBJECT }, +{ "AMBIGUOUS_COLUMN", ERRCODE_AMBIGUOUS_COLUMN }, +{ "AMBIGUOUS_FUNCTION", ERRCODE_AMBIGUOUS_FUNCTION }, +{ "AMBIGUOUS_PARAMETER", ERRCODE_AMBIGUOUS_PARAMETER }, +{ "AMBIGUOUS_ALIAS", ERRCODE_AMBIGUOUS_ALIAS }, +{ "INVALID_COLUMN_REFERENCE", ERRCODE_INVALID_COLUMN_REFERENCE }, +{ "INVALID_COLUMN_DEFINITION", ERRCODE_INVALID_COLUMN_DEFINITION }, +{ "INVALID_CURSOR_DEFINITION", ERRCODE_INVALID_CURSOR_DEFINITION }, +{ "INVALID_DATABASE_DEFINITION", ERRCODE_INVALID_DATABASE_DEFINITION }, +{ "INVALID_FUNCTION_DEFINITION", ERRCODE_INVALID_FUNCTION_DEFINITION }, +{ "INVALID_PSTATEMENT_DEFINITION", ERRCODE_INVALID_PSTATEMENT_DEFINITION }, +{ "INVALID_SCHEMA_DEFINITION", ERRCODE_INVALID_SCHEMA_DEFINITION }, +{ "INVALID_TABLE_DEFINITION", ERRCODE_INVALID_TABLE_DEFINITION }, +{ "INVALID_OBJECT_DEFINITION", ERRCODE_INVALID_OBJECT_DEFINITION }, +{ "WITH_CHECK_OPTION_VIOLATION", ERRCODE_WITH_CHECK_OPTION_VIOLATION }, +{ "INSUFFICIENT_RESOURCES", ERRCODE_INSUFFICIENT_RESOURCES }, +{ "DISK_FULL", ERRCODE_DISK_FULL }, +{ "OUT_OF_MEMORY", ERRCODE_OUT_OF_MEMORY }, +{ "TOO_MANY_CONNECTIONS", ERRCODE_TOO_MANY_CONNECTIONS }, +{ "PROGRAM_LIMIT_EXCEEDED", ERRCODE_PROGRAM_LIMIT_EXCEEDED }, +{ "STATEMENT_TOO_COMPLEX", ERRCODE_STATEMENT_TOO_COMPLEX }, +{ "TOO_MANY_COLUMNS", ERRCODE_TOO_MANY_COLUMNS }, +{ "TOO_MANY_ARGUMENTS", ERRCODE_TOO_MANY_ARGUMENTS }, +{ "OBJECT_NOT_IN_PREREQUISITE_STATE", ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE }, +{ "OBJECT_IN_USE", ERRCODE_OBJECT_IN_USE }, +{ "CANT_CHANGE_RUNTIME_PARAM", ERRCODE_CANT_CHANGE_RUNTIME_PARAM }, +{ "OPERATOR_INTERVENTION", ERRCODE_OPERATOR_INTERVENTION }, +{ "QUERY_CANCELED", ERRCODE_QUERY_CANCELED }, +{ "ADMIN_SHUTDOWN", ERRCODE_ADMIN_SHUTDOWN }, +{ "CRASH_SHUTDOWN", ERRCODE_CRASH_SHUTDOWN }, +{ "CANNOT_CONNECT_NOW", ERRCODE_CANNOT_CONNECT_NOW }, +{ "IO_ERROR", ERRCODE_IO_ERROR }, +{ "UNDEFINED_FILE", ERRCODE_UNDEFINED_FILE }, +{ "DUPLICATE_FILE", ERRCODE_DUPLICATE_FILE }, +{ "CONFIG_FILE_ERROR", ERRCODE_CONFIG_FILE_ERROR }, +{ "LOCK_FILE_EXISTS", ERRCODE_LOCK_FILE_EXISTS }, +{ "PLPGSQL_ERROR", ERRCODE_PLPGSQL_ERROR }, +{ "RAISE_EXCEPTION", ERRCODE_RAISE_EXCEPTION }, +{ "INTERNAL_ERROR", ERRCODE_INTERNAL_ERROR }, +{ "DATA_CORRUPTED", ERRCODE_DATA_CORRUPTED }, +{ "INDEX_CORRUPTED", ERRCODE_INDEX_CORRUPTED }, diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 92ece7da29d43aa3a63ef6c7d1d5d6a719124ed9..28868cf731efbbc0a189f08aa7dd989ae8461972 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.47 2004/06/06 00:41:28 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.48 2004/07/31 07:39:20 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -307,19 +307,35 @@ typedef struct PLpgSQL_ns } PLpgSQL_ns; +typedef struct +{ /* Generic execution node */ + int cmd_type; + int lineno; +} PLpgSQL_stmt; + + typedef struct { /* List of execution nodes */ - int stmts_alloc; + int stmts_alloc; /* XXX this oughta just be a List ... */ int stmts_used; - struct PLpgSQL_stmt **stmts; + PLpgSQL_stmt **stmts; } PLpgSQL_stmts; typedef struct -{ /* Generic execution node */ - int cmd_type; +{ /* One EXCEPTION ... WHEN clause */ int lineno; -} PLpgSQL_stmt; + char *label; + PLpgSQL_stmts *action; +} PLpgSQL_exception; + + +typedef struct +{ /* List of WHEN clauses */ + int exceptions_alloc; /* XXX this oughta just be a List ... */ + int exceptions_used; + PLpgSQL_exception **exceptions; +} PLpgSQL_exceptions; typedef struct @@ -328,6 +344,7 @@ typedef struct int lineno; char *label; PLpgSQL_stmts *body; + PLpgSQL_exceptions *exceptions; int n_initvars; int *initvarnos; } PLpgSQL_stmt_block; diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index 694165e506a1530a63793e1be418a405447b542b..6907905cc51ec1c2532b55e7069690ff6908eaef 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -1793,3 +1793,60 @@ SELECT * FROM perform_test; (3 rows) drop table perform_test; +-- +-- Test error trapping +-- +create function trap_zero_divide(int) returns int as $$ +declare x int; +declare sx smallint; +begin + begin -- start a subtransaction + raise notice 'should see this'; + x := 100 / $1; + raise notice 'should see this only if % <> 0', $1; + sx := $1; + raise notice 'should see this only if % fits in smallint', $1; + if $1 < 0 then + raise exception '% is less than zero', $1; + end if; + exception + when division_by_zero then + raise notice 'caught division_by_zero'; + x := -1; + when NUMERIC_VALUE_OUT_OF_RANGE then + raise notice 'caught numeric_value_out_of_range'; + x := -2; + end; + return x; +end$$ language plpgsql; +select trap_zero_divide(50); +NOTICE: should see this +NOTICE: should see this only if 50 <> 0 +NOTICE: should see this only if 50 fits in smallint + trap_zero_divide +------------------ + 2 +(1 row) + +select trap_zero_divide(0); +NOTICE: should see this +NOTICE: caught division_by_zero + trap_zero_divide +------------------ + -1 +(1 row) + +select trap_zero_divide(100000); +NOTICE: should see this +NOTICE: should see this only if 100000 <> 0 +NOTICE: caught numeric_value_out_of_range + trap_zero_divide +------------------ + -2 +(1 row) + +select trap_zero_divide(-100); +NOTICE: should see this +NOTICE: should see this only if -100 <> 0 +NOTICE: should see this only if -100 fits in smallint +ERROR: -100 is less than zero diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index 9cb7f7c9407cf12542647e1968ad72397f2d6b6b..48a78196a892efeb7b53bf905b42d47af0857676 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -1608,3 +1608,36 @@ SELECT perform_test_func(); SELECT * FROM perform_test; drop table perform_test; + +-- +-- Test error trapping +-- + +create function trap_zero_divide(int) returns int as $$ +declare x int; +declare sx smallint; +begin + begin -- start a subtransaction + raise notice 'should see this'; + x := 100 / $1; + raise notice 'should see this only if % <> 0', $1; + sx := $1; + raise notice 'should see this only if % fits in smallint', $1; + if $1 < 0 then + raise exception '% is less than zero', $1; + end if; + exception + when division_by_zero then + raise notice 'caught division_by_zero'; + x := -1; + when NUMERIC_VALUE_OUT_OF_RANGE then + raise notice 'caught numeric_value_out_of_range'; + x := -2; + end; + return x; +end$$ language plpgsql; + +select trap_zero_divide(50); +select trap_zero_divide(0); +select trap_zero_divide(100000); +select trap_zero_divide(-100);