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> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </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);