diff --git a/doc/src/sgml/errcodes.sgml b/doc/src/sgml/errcodes.sgml
index 5d1a770bf87f1c47e7e91d3640c641dcab947387..ca3bc9ca4f1e8e6f4aca28faa2114d9195015b92 100644
--- a/doc/src/sgml/errcodes.sgml
+++ b/doc/src/sgml/errcodes.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/errcodes.sgml,v 1.6 2004/05/16 23:18:52 neilc Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/errcodes.sgml,v 1.7 2004/07/31 23:04:54 tgl Exp $ -->
 
 <appendix id="errcodes-appendix">
  <title><productname>PostgreSQL</productname> Error Codes</title>
@@ -41,10 +41,18 @@
   within the class but do not have any more-specific code assigned.
  </para>
 
+ <para>
+  The <application>PL/pgSQL</> condition name for each error code is the
+  same as the phrase shown in the table, with underscores substituted
+  for spaces.  For example, code <literal>22012</>, DIVISION BY ZERO,
+  has condition name <literal>DIVISION_BY_ZERO</>.  Condition names can
+  be written in either upper or lower case.
+ </para>
+
 
 <!--
  The following table should correspond to the contents of
- src/include/utils/errcodes.h.
+ src/include/utils/errcodes.h and src/pl/plpgsql/src/plerrcodes.h.
 -->
 
 <table id="errcodes-table">
@@ -82,27 +90,37 @@
 
 <row>
 <entry><literal>0100C</literal></entry>
-<entry>WARNING DYNAMIC RESULT SETS RETURNED</entry>
+<entry>DYNAMIC RESULT SETS RETURNED</entry>
 </row>
 
 <row>
 <entry><literal>01008</literal></entry>
-<entry>WARNING IMPLICIT ZERO BIT PADDING</entry>
+<entry>IMPLICIT ZERO BIT PADDING</entry>
 </row>
 
 <row>
 <entry><literal>01003</literal></entry>
-<entry>WARNING NULL VALUE ELIMINATED IN SET FUNCTION</entry>
+<entry>NULL VALUE ELIMINATED IN SET FUNCTION</entry>
+</row>
+
+<row>
+<entry><literal>01007</literal></entry>
+<entry>PRIVILEGE NOT GRANTED</entry>
+</row>
+
+<row>
+<entry><literal>01006</literal></entry>
+<entry>PRIVILEGE NOT REVOKED</entry>
 </row>
 
 <row>
 <entry><literal>01004</literal></entry>
-<entry>WARNING STRING DATA RIGHT TRUNCATION</entry>
+<entry>STRING DATA RIGHT TRUNCATION</entry>
 </row>
 
 <row>
 <entry><literal>01P01</literal></entry>
-<entry>WARNING DEPRECATED FEATURE</entry>
+<entry>DEPRECATED FEATURE</entry>
 </row>
 
 <row>
@@ -218,7 +236,7 @@
 
 <row>
 <entry><literal>0F001</literal></entry>
-<entry>INVALID SPECIFICATION</entry>
+<entry>INVALID LOCATOR SPECIFICATION</entry>
 </row>
 
 
@@ -272,7 +290,7 @@
 
 <row>
 <entry><literal>2202E</literal></entry>
-<entry>ARRAY ELEMENT ERROR</entry>
+<entry>ARRAY SUBSCRIPT ERROR</entry>
 </row>
 
 <row>
@@ -728,6 +746,22 @@
 </row>
 
 
+<row>
+<entry>Class 3B</entry>
+<entry>Savepoint Exception</entry>
+</row>
+
+<row>
+<entry><literal>3B000</literal></entry>
+<entry>SAVEPOINT EXCEPTION</entry>
+</row>
+
+<row>
+<entry><literal>3B001</literal></entry>
+<entry>INVALID SAVEPOINT SPECIFICATION</entry>
+</row>
+
+
 <row>
 <entry>Class 3D</entry>
 <entry>Invalid Catalog Name</entry>
@@ -762,7 +796,7 @@
 
 <row>
 <entry><literal>40002</literal></entry>
-<entry>INTEGRITY CONSTRAINT VIOLATION</entry>
+<entry>TRANSACTION INTEGRITY CONSTRAINT VIOLATION</entry>
 </row>
 
 <row>
@@ -893,7 +927,7 @@
 
 <row>
 <entry><literal>42P05</literal></entry>
-<entry>DUPLICATE PSTATEMENT</entry>
+<entry>DUPLICATE PREPARED STATEMENT</entry>
 </row>
 
 <row>
@@ -963,7 +997,7 @@
 
 <row>
 <entry><literal>42P14</literal></entry>
-<entry>INVALID PSTATEMENT DEFINITION</entry>
+<entry>INVALID PREPARED STATEMENT DEFINITION</entry>
 </row>
 
 <row>
@@ -1134,6 +1168,22 @@
 </row>
 
 
+<row>
+<entry>Class P0</entry>
+<entry><application>PL/pgSQL</> Error</entry>
+</row>
+
+<row>
+<entry><literal>P0000</literal></entry>
+<entry>PLPGSQL ERROR</entry>
+</row>
+
+<row>
+<entry><literal>P0001</literal></entry>
+<entry>RAISE EXCEPTION</entry>
+</row>
+
+
 <row>
 <entry>Class XX</entry>
 <entry>Internal Error</entry>
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 7f3f84448d789ce47cbc3027dc50fe399d50298b..e44b886214b06acf7829fd71e5e4e54de62fb85d 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.42 2004/07/31 07:39:17 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.43 2004/07/31 23:04:54 tgl Exp $
 -->
 
 <chapter id="plpgsql"> 
@@ -1816,12 +1816,11 @@ END LOOP;
 BEGIN
     <replaceable>statements</replaceable>
 EXCEPTION
-    WHEN <replaceable>condition</replaceable> THEN
+    WHEN <replaceable>condition</replaceable> <optional> OR <replaceable>condition</replaceable> ... </optional> THEN
         <replaceable>handler_statements</replaceable>
-    <optional> WHEN <replaceable>condition</replaceable> THEN
-                   <replaceable>handler_statements</replaceable>
-    ...
-    </optional>
+    <optional> WHEN <replaceable>condition</replaceable> <optional> OR <replaceable>condition</replaceable> ... </optional> THEN
+          <replaceable>handler_statements</replaceable>
+    ... </optional>
 END;
 </synopsis>
     </para>
@@ -1841,10 +1840,18 @@ END;
      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</>
+     of the function.
+    </para>
+
+    <para>
+     The <replaceable>condition</replaceable> names can be any of those
+     shown in <xref linkend="errcodes-appendix">.  A category name matches
+     any error within its category.
+     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
+     (It is possible, but often unwise, to trap
      <literal>QUERY_CANCELED</> by name.)
+     Condition names are not case-sensitive.
     </para>
 
     <para>
@@ -1879,9 +1886,9 @@ END;
      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</>.
+     have been rolled back.  The <command>INSERT</> command preceding the
+     block is not rolled back, however, so the end result is that the database
+     contains <literal>Tom Jones</> not <literal>Joe Jones</>.
     </para>
 
     <tip>
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index e9f2b0e879f296d894674776f3c3e2ce21308ee4..4a2d26ddb9d97a291cb33b74a563bf420b5b97cf 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.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/utils/elog.h,v 1.71 2004/07/31 00:45:43 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/elog.h,v 1.72 2004/07/31 23:04:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -61,6 +61,10 @@
 	(PGSIXBIT(ch1) + (PGSIXBIT(ch2) << 6) + (PGSIXBIT(ch3) << 12) + \
 	 (PGSIXBIT(ch4) << 18) + (PGSIXBIT(ch5) << 24))
 
+/* These macros depend on the fact that '0' becomes a zero in SIXBIT */
+#define ERRCODE_TO_CATEGORY(ec)  ((ec) & ((1 << 12) - 1))
+#define ERRCODE_IS_CATEGORY(ec)  (((ec) & ~((1 << 12) - 1)) == 0)
+
 /* SQLSTATE codes for errors are defined in a separate file */
 #include "utils/errcodes.h"
 
diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y
index 628c81b570bc7d1cf3269277fb1581762154639c..96c5c61725dcdad56537f577d5c68f6b7dd3270f 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.58 2004/07/31 07:39:20 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.59 2004/07/31 23:04:56 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -96,6 +96,7 @@ static	void check_assignable(PLpgSQL_datum *datum);
 		PLpgSQL_stmt			*stmt;
 		PLpgSQL_stmts			*stmts;
 		PLpgSQL_stmt_block		*program;
+		PLpgSQL_condition		*condition;
 		PLpgSQL_exception		*exception;
 		PLpgSQL_exceptions		*exceptions;
 		PLpgSQL_nsitem			*nsitem;
@@ -135,6 +136,7 @@ static	void check_assignable(PLpgSQL_datum *datum);
 
 %type <exceptions>	exception_sect proc_exceptions
 %type <exception>	proc_exception
+%type <condition>	proc_conditions
 
 %type <intlist>	raise_params
 %type <ival>	raise_level raise_param
@@ -181,6 +183,7 @@ static	void check_assignable(PLpgSQL_datum *datum);
 %token	K_NOTICE
 %token	K_NULL
 %token	K_OPEN
+%token	K_OR
 %token	K_PERFORM
 %token	K_ROW_COUNT
 %token	K_RAISE
@@ -1563,21 +1566,52 @@ proc_exceptions	: proc_exceptions proc_exception
 						}
 				;
 
-proc_exception	: K_WHEN lno opt_lblname K_THEN proc_sect
+proc_exception	: K_WHEN lno proc_conditions 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->lineno     = $2;
+						new->conditions = $3;
+						new->action	    = $5;
 
 						$$ = new;
 					}
 				;
 
+proc_conditions	: proc_conditions K_OR opt_lblname
+						{
+								PLpgSQL_condition	*new;
+								PLpgSQL_condition	*old;
+
+								new = malloc(sizeof(PLpgSQL_condition));
+								memset(new, 0, sizeof(PLpgSQL_condition));
+
+								new->condname = $3;
+								new->next = NULL;
+
+								for (old = $1; old->next != NULL; old = old->next)
+									/* skip */ ;
+								old->next = new;
+
+								$$ = $1;
+						}
+				| opt_lblname
+						{
+								PLpgSQL_condition	*new;
+
+								new = malloc(sizeof(PLpgSQL_condition));
+								memset(new, 0, sizeof(PLpgSQL_condition));
+
+								new->condname = $1;
+								new->next = NULL;
+
+								$$ = 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 0950149a494763ef6d025b433a897b0f12872153..4c579223d91a0fff0bd8c0fafc30ab1442f44f83 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.110 2004/07/31 20:55:44 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.111 2004/07/31 23:04:56 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -795,27 +795,45 @@ copy_rec(PLpgSQL_rec * rec)
 
 
 static bool
-exception_matches_label(ErrorData *edata, const char *label)
+exception_matches_conditions(ErrorData *edata, PLpgSQL_condition *cond)
 {
-	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++)
+	for (; cond != NULL; cond = cond->next)
 	{
-		if (pg_strcasecmp(label, exception_label_map[i].label) == 0)
-			return (edata->sqlerrcode == exception_label_map[i].sqlerrstate);
+		const char *condname = cond->condname;
+		int			i;
+
+		/*
+		 * OTHERS matches everything *except* query-canceled;
+		 * if you're foolish enough, you can match that explicitly.
+		 */
+		if (pg_strcasecmp(condname, "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(condname, exception_label_map[i].label) == 0)
+			{
+				int labelerrcode = exception_label_map[i].sqlerrstate;
+
+				/* Exact match? */
+				if (edata->sqlerrcode == labelerrcode)
+					return true;
+				/* Category match? */
+				if (ERRCODE_IS_CATEGORY(labelerrcode) &&
+					ERRCODE_TO_CATEGORY(edata->sqlerrcode) == labelerrcode)
+					return true;
+				/*
+				 * You would think we should "break" here, but there are some
+				 * duplicate names in the table, so keep looking.
+				 */
+			}
+		}
+		/* Should we raise an error if condname is unrecognized?? */
 	}
-	/* Should we raise an error if label is unrecognized?? */
 	return false;
 }
 
@@ -944,7 +962,7 @@ exec_stmt_block(PLpgSQL_execstate * estate, PLpgSQL_stmt_block * block)
 			{
 				PLpgSQL_exception *exception = exceptions->exceptions[j];
 
-				if (exception_matches_label(edata, exception->label))
+				if (exception_matches_conditions(edata, exception->conditions))
 				{
 					rc = exec_stmts(estate, exception->action);
 					break;
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index b4649067522a2bb2a88ec26ede4ffddb8f743dc9..028bc3836cbb6b8d68f3193b975a79740eac3710 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.33 2004/07/31 07:39:20 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.34 2004/07/31 23:04:56 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -615,9 +615,17 @@ dump_block(PLpgSQL_stmt_block * block)
 		for (i = 0; i < block->exceptions->exceptions_used; i++)
 		{
 			PLpgSQL_exception *exc = block->exceptions->exceptions[i];
+			PLpgSQL_condition *cond;
 
 			dump_ind();
-			printf("    EXCEPTION WHEN %s THEN\n", exc->label);
+			printf("    EXCEPTION WHEN ");
+			for (cond = exc->conditions; cond; cond = cond->next)
+			{
+				if (cond != exc->conditions)
+					printf(" OR ");
+				printf("%s", cond->condname);
+			}
+			printf(" THEN\n");
 			dump_stmts(exc->action);
 		}
 	}
diff --git a/src/pl/plpgsql/src/plerrcodes.h b/src/pl/plpgsql/src/plerrcodes.h
index 4e76bde6739945d5e3efaefe95698ad039e36351..2448701d906d57e85dd26d1c4d4e215f229c7079 100644
--- a/src/pl/plpgsql/src/plerrcodes.h
+++ b/src/pl/plpgsql/src/plerrcodes.h
@@ -9,22 +9,12 @@
  *
  * 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 $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.2 2004/07/31 23:04:56 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 },
+/* Success and warnings can't be caught, so omit them from table */
 { "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 },
@@ -37,7 +27,7 @@
 { "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_LOCATOR_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 },
@@ -53,7 +43,7 @@
 { "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_LOGARITHM", 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 },
@@ -107,30 +97,30 @@
 { "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 },
+{ "FUNCTION_EXECUTED_NO_RETURN_STATEMENT", ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT },
+{ "MODIFYING_SQL_DATA_NOT_PERMITTED", ERRCODE_S_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED },
+{ "PROHIBITED_SQL_STATEMENT_ATTEMPTED", ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED },
+{ "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 },
+{ "CONTAINING_SQL_NOT_PERMITTED", ERRCODE_E_R_E_CONTAINING_SQL_NOT_PERMITTED },
+{ "MODIFYING_SQL_DATA_NOT_PERMITTED", ERRCODE_E_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED },
+{ "PROHIBITED_SQL_STATEMENT_ATTEMPTED", ERRCODE_E_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED },
+{ "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 },
+{ "INVALID_SQLSTATE_RETURNED", ERRCODE_E_R_I_E_INVALID_SQLSTATE_RETURNED },
+{ "NULL_VALUE_NOT_ALLOWED", ERRCODE_E_R_I_E_NULL_VALUE_NOT_ALLOWED },
+{ "TRIGGER_PROTOCOL_VIOLATED", ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED },
+{ "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_SAVEPOINT_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 },
+{ "TRANSACTION_INTEGRITY_CONSTRAINT_VIOLATION", ERRCODE_T_R_INTEGRITY_CONSTRAINT_VIOLATION },
+{ "SERIALIZATION_FAILURE", ERRCODE_T_R_SERIALIZATION_FAILURE },
+{ "STATEMENT_COMPLETION_UNKNOWN", ERRCODE_T_R_STATEMENT_COMPLETION_UNKNOWN },
+{ "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 },
@@ -156,7 +146,7 @@
 { "DUPLICATE_CURSOR", ERRCODE_DUPLICATE_CURSOR },
 { "DUPLICATE_DATABASE", ERRCODE_DUPLICATE_DATABASE },
 { "DUPLICATE_FUNCTION", ERRCODE_DUPLICATE_FUNCTION },
-{ "DUPLICATE_PSTATEMENT", ERRCODE_DUPLICATE_PSTATEMENT },
+{ "DUPLICATE_PREPARED_STATEMENT", ERRCODE_DUPLICATE_PSTATEMENT },
 { "DUPLICATE_SCHEMA", ERRCODE_DUPLICATE_SCHEMA },
 { "DUPLICATE_TABLE", ERRCODE_DUPLICATE_TABLE },
 { "DUPLICATE_ALIAS", ERRCODE_DUPLICATE_ALIAS },
@@ -170,7 +160,7 @@
 { "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_PREPARED_STATEMENT_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 },
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 28868cf731efbbc0a189f08aa7dd989ae8461972..d57d4a7025ecc85917543cdc7c33ddfdbbec71de 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.48 2004/07/31 07:39:20 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.49 2004/07/31 23:04:56 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -322,10 +322,16 @@ typedef struct
 }	PLpgSQL_stmts;
 
 
+typedef struct PLpgSQL_condition
+{								/* One EXCEPTION condition name */
+	char	   *condname;
+	struct PLpgSQL_condition *next;
+}	PLpgSQL_condition;
+
 typedef struct
 {								/* One EXCEPTION ... WHEN clause */
 	int			lineno;
-	char	   *label;
+	PLpgSQL_condition *conditions;
 	PLpgSQL_stmts *action;
 }	PLpgSQL_exception;
 
diff --git a/src/pl/plpgsql/src/scan.l b/src/pl/plpgsql/src/scan.l
index d369170cf3eafd9365e5bae212dcf1720785b68c..5e6ccd68d71f6b33e75b25cbb710f3ec1693c868 100644
--- a/src/pl/plpgsql/src/scan.l
+++ b/src/pl/plpgsql/src/scan.l
@@ -4,7 +4,7 @@
  *			  procedural language
  *
  * IDENTIFICATION
- *    $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.35 2004/06/03 22:56:43 tgl Exp $
+ *    $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.36 2004/07/31 23:04:56 tgl Exp $
  *
  *    This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -165,6 +165,7 @@ not				{ return K_NOT;				}
 notice			{ return K_NOTICE;			}
 null			{ return K_NULL;			}
 open			{ return K_OPEN;			}
+or				{ return K_OR;				}
 perform			{ return K_PERFORM;			}
 raise			{ return K_RAISE;			}
 rename			{ return K_RENAME;			}
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 6907905cc51ec1c2532b55e7069690ff6908eaef..73962dbb37a5c6a24cf79e797686538e8c9a5c5d 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1798,7 +1798,7 @@ drop table perform_test;
 --
 create function trap_zero_divide(int) returns int as $$
 declare x int;
-declare sx smallint;
+	sx smallint;
 begin
 	begin	-- start a subtransaction
 		raise notice 'should see this';
@@ -1850,3 +1850,88 @@ 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
+create function trap_matching_test(int) returns int as $$
+declare x int;
+	sx smallint;
+	y int;
+begin
+	begin	-- start a subtransaction
+		x := 100 / $1;
+		sx := $1;
+		select into y unique1 from tenk1 where unique2 =
+			(select unique2 from tenk1 b where ten = $1);
+	exception
+		when data_exception then  -- category match
+			raise notice 'caught data_exception';
+			x := -1;
+		when NUMERIC_VALUE_OUT_OF_RANGE OR CARDINALITY_VIOLATION then
+			raise notice 'caught numeric_value_out_of_range or cardinality_violation';
+			x := -2;
+	end;
+	return x;
+end$$ language plpgsql;
+select trap_matching_test(50);
+ trap_matching_test 
+--------------------
+                  2
+(1 row)
+
+select trap_matching_test(0);
+NOTICE:  caught data_exception
+ trap_matching_test 
+--------------------
+                 -1
+(1 row)
+
+select trap_matching_test(100000);
+NOTICE:  caught data_exception
+ trap_matching_test 
+--------------------
+                 -1
+(1 row)
+
+select trap_matching_test(1);
+NOTICE:  caught numeric_value_out_of_range or cardinality_violation
+ trap_matching_test 
+--------------------
+                 -2
+(1 row)
+
+create temp table foo (f1 int);
+create function blockme() returns int as $$
+declare x int;
+begin
+  x := 1;
+  insert into foo values(x);
+  begin
+    x := x + 1;
+    insert into foo values(x);
+    -- we assume this will take longer than 1 second:
+    select count(*) into x from tenk1 a, tenk1 b, tenk1 c;
+  exception
+    when others then
+      raise notice 'caught others?';
+      return -1;
+    when query_canceled then
+      raise notice 'nyeah nyeah, can''t stop me';
+      x := x * 10;
+  end;
+  insert into foo values(x);
+  return x;
+end$$ language plpgsql;
+set statement_timeout to 1000;
+select blockme();
+NOTICE:  nyeah nyeah, can't stop me
+ blockme 
+---------
+      20
+(1 row)
+
+reset statement_timeout;
+select * from foo;
+ f1 
+----
+  1
+ 20
+(2 rows)
+
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 48a78196a892efeb7b53bf905b42d47af0857676..948a02ac0e214c833c9727055274d752b8c3fd67 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -1615,7 +1615,7 @@ drop table perform_test;
 
 create function trap_zero_divide(int) returns int as $$
 declare x int;
-declare sx smallint;
+	sx smallint;
 begin
 	begin	-- start a subtransaction
 		raise notice 'should see this';
@@ -1641,3 +1641,61 @@ select trap_zero_divide(50);
 select trap_zero_divide(0);
 select trap_zero_divide(100000);
 select trap_zero_divide(-100);
+
+create function trap_matching_test(int) returns int as $$
+declare x int;
+	sx smallint;
+	y int;
+begin
+	begin	-- start a subtransaction
+		x := 100 / $1;
+		sx := $1;
+		select into y unique1 from tenk1 where unique2 =
+			(select unique2 from tenk1 b where ten = $1);
+	exception
+		when data_exception then  -- category match
+			raise notice 'caught data_exception';
+			x := -1;
+		when NUMERIC_VALUE_OUT_OF_RANGE OR CARDINALITY_VIOLATION then
+			raise notice 'caught numeric_value_out_of_range or cardinality_violation';
+			x := -2;
+	end;
+	return x;
+end$$ language plpgsql;
+
+select trap_matching_test(50);
+select trap_matching_test(0);
+select trap_matching_test(100000);
+select trap_matching_test(1);
+
+create temp table foo (f1 int);
+
+create function blockme() returns int as $$
+declare x int;
+begin
+  x := 1;
+  insert into foo values(x);
+  begin
+    x := x + 1;
+    insert into foo values(x);
+    -- we assume this will take longer than 1 second:
+    select count(*) into x from tenk1 a, tenk1 b, tenk1 c;
+  exception
+    when others then
+      raise notice 'caught others?';
+      return -1;
+    when query_canceled then
+      raise notice 'nyeah nyeah, can''t stop me';
+      x := x * 10;
+  end;
+  insert into foo values(x);
+  return x;
+end$$ language plpgsql;
+
+set statement_timeout to 1000;
+
+select blockme();
+
+reset statement_timeout;
+
+select * from foo;