diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index d96c123e21c0ec87c0de013c7e3b254f61539a00..c043c78fea3efb3ddf8ded119967a3be8b28067f 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.70 2005/06/07 02:47:15 neilc Exp $
+$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.71 2005/06/10 16:23:09 neilc Exp $
 -->
 
 <chapter id="plpgsql"> 
@@ -2110,6 +2110,17 @@ END;
       don't use <literal>EXCEPTION</> without need.
      </para>
     </tip>
+
+    <para>
+     Within an exception handler, the <varname>SQLSTATE</varname>
+     variable contains the error code that corresponds to the
+     exception that was raised (refer to <xref
+     linkend="errcodes-table"> for a list of possible error
+     codes). The <varname>SQLERRM</varname> variable contains the
+     error message associated with the exception. These variables are
+     undefined outside exception handlers.
+    </para>
+
     <example id="plpgsql-upsert-example">
     <title>Exceptions with UPDATE/INSERT</title>
     <para>
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 122a0a92a55df74d5ea12d4c7a60ce24e4496991..3e883887ee811795002b9d50cd9d908e9cf45b30 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -42,7 +42,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.159 2005/06/09 22:29:52 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.160 2005/06/10 16:23:10 neilc Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1482,6 +1482,26 @@ log_line_prefix(StringInfo buf)
 	}
 }
 
+/*
+ * Unpack MAKE_SQLSTATE code. Note that this returns a pointer to a
+ * static buffer.
+ */
+char *
+unpack_sql_state(int sql_state)
+{
+	static char	buf[12];
+	int			i;
+
+	for (i = 0; i < 5; i++)
+	{
+		buf[i] = PGUNSIXBIT(sql_state);
+		sql_state >>= 6;
+	}
+
+	buf[i] = '\0';
+	return buf;
+}
+
 
 /*
  * Write error report to server's log
@@ -1497,21 +1517,7 @@ send_message_to_server_log(ErrorData *edata)
 	appendStringInfo(&buf, "%s:  ", error_severity(edata->elevel));
 
 	if (Log_error_verbosity >= PGERROR_VERBOSE)
-	{
-		/* unpack MAKE_SQLSTATE code */
-		char		tbuf[12];
-		int			ssval;
-		int			i;
-
-		ssval = edata->sqlerrcode;
-		for (i = 0; i < 5; i++)
-		{
-			tbuf[i] = PGUNSIXBIT(ssval);
-			ssval >>= 6;
-		}
-		tbuf[i] = '\0';
-		appendStringInfo(&buf, "%s: ", tbuf);
-	}
+		appendStringInfo(&buf, "%s: ", unpack_sql_state(edata->sqlerrcode));
 
 	if (edata->message)
 		append_with_tabs(&buf, edata->message);
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 0b7d3f17ebb40d73e9b344a5e562858ee637f066..f226f772911a04b024b82de54742ee381c19b2e7 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/elog.h,v 1.78 2004/12/31 22:03:46 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/utils/elog.h,v 1.79 2005/06/10 16:23:10 neilc Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -282,6 +282,7 @@ extern int	Log_destination;
 
 /* Other exported functions */
 extern void DebugFileOpen(void);
+extern char *unpack_sql_state(int sql_state);
 
 /*
  * Write errors to stderr (or by equal means when stderr is
diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y
index e1ba564ca900b42cd542e0743bc9b5067bcc4092..48164d983b5586059cae992763e27c3d2e28638a 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.74 2005/06/08 00:49:36 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.75 2005/06/10 16:23:11 neilc Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -92,6 +92,7 @@ static	void			 plpgsql_sql_error_callback(void *arg);
 		PLpgSQL_stmt_block		*program;
 		PLpgSQL_condition		*condition;
 		PLpgSQL_exception		*exception;
+		PLpgSQL_exception_block	*exception_block;
 		PLpgSQL_nsitem			*nsitem;
 		PLpgSQL_diag_item		*diagitem;
 }
@@ -129,7 +130,8 @@ static	void			 plpgsql_sql_error_callback(void *arg);
 %type <stmt>	stmt_dynexecute stmt_getdiag
 %type <stmt>	stmt_open stmt_fetch stmt_close stmt_null
 
-%type <list>	exception_sect proc_exceptions
+%type <list>	proc_exceptions
+%type <exception_block> exception_sect
 %type <exception>	proc_exception
 %type <condition>	proc_conditions
 
@@ -1495,9 +1497,38 @@ execsql_start	: T_WORD
 				;
 
 exception_sect	:
-					{ $$ = NIL; }
-				| K_EXCEPTION proc_exceptions
-					{ $$ = $2; }
+					{ $$ = NULL; }
+				| K_EXCEPTION lno
+					{
+						/*
+						 * We use a mid-rule action to add these
+						 * special variables to the namespace before
+						 * parsing the WHEN clauses themselves.
+						 */
+						PLpgSQL_exception_block *new = palloc(sizeof(PLpgSQL_exception_block));
+						PLpgSQL_variable *var;
+
+						var = plpgsql_build_variable("sqlstate", $2,
+													 plpgsql_build_datatype(TEXTOID, -1),
+													 true);
+						((PLpgSQL_var *) var)->isconst = true;
+						new->sqlstate_varno = var->dno;
+
+						var = plpgsql_build_variable("sqlerrm", $2,
+													 plpgsql_build_datatype(TEXTOID, -1),
+													 true);
+						((PLpgSQL_var *) var)->isconst = true;
+						new->sqlerrm_varno = var->dno;
+
+						$<exception_block>$ = new;
+					}
+					proc_exceptions
+					{
+						PLpgSQL_exception_block *new = $<exception_block>3;
+						new->exc_list = $4;
+
+						$$ = new;
+					}
 				;
 
 proc_exceptions	: proc_exceptions proc_exception
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index b746a8374bda03603ff92feebc6de69fb7fef602..38b3d077de21221011559bd737572695be84c060 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -3,7 +3,7 @@
  *			  procedural language
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.90 2005/05/29 04:23:06 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.91 2005/06/10 16:23:11 neilc Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -656,7 +656,7 @@ do_compile(FunctionCallInfo fcinfo,
 	if (num_out_args > 0 || function->fn_rettype == VOIDOID ||
 		function->fn_retset)
 	{
-		if (function->action->exceptions != NIL)
+		if (function->action->exceptions != NULL)
 		{
 			PLpgSQL_stmt_block *new;
 
@@ -882,7 +882,7 @@ plpgsql_parse_word(char *word)
 	}
 
 	/*
-	 * Do a lookup on the compilers namestack
+	 * Do a lookup on the compiler's namestack
 	 */
 	nse = plpgsql_ns_lookup(cp[0], NULL);
 	if (nse != NULL)
@@ -1935,7 +1935,7 @@ plpgsql_parse_err_condition(char *condname)
 
 /* ----------
  * plpgsql_adddatum			Add a variable, record or row
- *					to the compilers datum list.
+ *					to the compiler's datum list.
  * ----------
  */
 void
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 1fe1ed4cbedf38612d3b40b39a1a0b5339849b50..ae5f7473146c1bfefcb5663a6289bc268d4a845d 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.142 2005/06/07 02:47:17 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.143 2005/06/10 16:23:11 neilc Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -180,7 +180,7 @@ static Datum exec_simple_cast_value(Datum value, Oid valtype,
 static void exec_init_tuple_store(PLpgSQL_execstate *estate);
 static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2);
 static void exec_set_found(PLpgSQL_execstate *estate, bool state);
-
+static void free_var(PLpgSQL_var *var);
 
 /* ----------
  * plpgsql_exec_function	Called by the call handler for
@@ -760,12 +760,7 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
 				{
 					PLpgSQL_var *var = (PLpgSQL_var *) (estate->datums[n]);
 
-					if (var->freeval)
-					{
-						pfree((void *) (var->value));
-						var->freeval = false;
-					}
-
+					free_var(var);
 					if (!var->isconst || var->isnull)
 					{
 						if (var->default_val == NULL)
@@ -864,13 +859,37 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
 			SPI_restore_connection();
 
 			/* Look for a matching exception handler */
-			foreach (e, block->exceptions)
+			foreach (e, block->exceptions->exc_list)
 			{
 				PLpgSQL_exception *exception = (PLpgSQL_exception *) lfirst(e);
 
 				if (exception_matches_conditions(edata, exception->conditions))
 				{
+					/*
+					 * Initialize the magic SQLSTATE and SQLERRM
+					 * variables for the exception block. We needn't
+					 * do this until we have found a matching
+					 * exception.
+					 */
+					PLpgSQL_var *state_var;
+					PLpgSQL_var *errm_var;
+
+					state_var = (PLpgSQL_var *) (estate->datums[block->exceptions->sqlstate_varno]);
+					state_var->value = DirectFunctionCall1(textin,
+														   CStringGetDatum(unpack_sql_state(edata->sqlerrcode)));
+					state_var->freeval = true;
+					state_var->isnull = false;
+
+					errm_var = (PLpgSQL_var *) (estate->datums[block->exceptions->sqlerrm_varno]);
+					errm_var->value = DirectFunctionCall1(textin,
+														  CStringGetDatum(edata->message));
+					errm_var->freeval = true;
+					errm_var->isnull = false;
+
 					rc = exec_stmts(estate, exception->action);
+
+					free_var(state_var);
+					free_var(errm_var);
 					break;
 				}
 			}
@@ -2586,9 +2605,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
 		 * Store the eventually assigned cursor name in the cursor variable
 		 * ----------
 		 */
-		if (curvar->freeval)
-			pfree((void *) (curvar->value));
-
+		free_var(curvar);
 		curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name));
 		curvar->isnull = false;
 		curvar->freeval = true;
@@ -2684,9 +2701,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
 	 * Store the eventually assigned portal name in the cursor variable
 	 * ----------
 	 */
-	if (curvar->freeval)
-		pfree((void *) (curvar->value));
-
+	free_var(curvar);
 	curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name));
 	curvar->isnull = false;
 	curvar->freeval = true;
@@ -2857,11 +2872,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
 							 errmsg("NULL cannot be assigned to variable \"%s\" declared NOT NULL",
 									var->refname)));
 
-				if (var->freeval)
-				{
-					pfree(DatumGetPointer(var->value));
-					var->freeval = false;
-				}
+				free_var(var);
 
 				/*
 				 * If type is by-reference, make sure we have a freshly
@@ -4343,3 +4354,13 @@ plpgsql_xact_cb(XactEvent event, void *arg)
 		FreeExecutorState(simple_eval_estate);
 	simple_eval_estate = NULL;
 }
+
+static void
+free_var(PLpgSQL_var *var)
+{
+	if (var->freeval)
+	{
+		pfree(DatumGetPointer(var->value));
+		var->freeval = false;
+	}
+}
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index 475d2224bffcfc6d13360540b96fb7d3c692744f..9e2ef4902c2b246d99077390da7cbf6ba4a4897e 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.40 2005/04/05 06:22:16 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.41 2005/06/10 16:23:11 neilc Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -634,7 +634,7 @@ dump_block(PLpgSQL_stmt_block *block)
 	{
 		ListCell *e;
 
-		foreach (e, block->exceptions)
+		foreach (e, block->exceptions->exc_list)
 		{
 			PLpgSQL_exception *exc = (PLpgSQL_exception *) lfirst(e);
 			PLpgSQL_condition *cond;
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index a70553ac2c8a2e62f1de10e33405af990cbf457d..580439c88c5682720e2c364ee022c478f93d997d 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.61 2005/06/07 02:47:18 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.62 2005/06/10 16:23:11 neilc Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -322,6 +322,13 @@ typedef struct PLpgSQL_condition
 	struct PLpgSQL_condition *next;
 } PLpgSQL_condition;
 
+typedef struct
+{
+	int			sqlstate_varno;
+	int			sqlerrm_varno;
+	List	   *exc_list;		/* List of WHEN clauses */
+} PLpgSQL_exception_block;
+
 typedef struct
 {								/* One EXCEPTION ... WHEN clause */
 	int			lineno;
@@ -336,9 +343,9 @@ typedef struct
 	int			lineno;
 	char	   *label;
 	List	   *body;			/* List of statements */
-	List	   *exceptions;		/* List of WHEN clauses */
 	int			n_initvars;
 	int		   *initvarnos;
+	PLpgSQL_exception_block *exceptions;
 } PLpgSQL_stmt_block;
 
 
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 39e61e09cf26d94a5e92a85f0c48dcb4b2658c68..2650cbb908a55dbd22d13c03b65b21ec70a89742 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -2415,3 +2415,57 @@ NOTICE:  10 15 20
 
 drop table eifoo cascade;
 drop type eitype cascade;
+--
+-- SQLSTATE and SQLERRM test
+--
+-- should fail: SQLSTATE and SQLERRM are only in defined EXCEPTION
+-- blocks
+create function excpt_test() returns void as $$
+begin
+    raise notice '% %', sqlstate, sqlerrm;
+end; $$ language plpgsql;
+ERROR:  syntax error at or near "sqlstate" at character 79
+LINE 3:     raise notice '% %', sqlstate, sqlerrm;
+                                ^
+-- should fail
+create function excpt_test() returns void as $$
+begin
+    begin
+        begin
+    	    raise notice '% %', sqlstate, sqlerrm;
+        end;
+    end;
+end; $$ language plpgsql;
+ERROR:  syntax error at or near "sqlstate" at character 108
+LINE 5:          raise notice '% %', sqlstate, sqlerrm;
+                                     ^
+create function excpt_test() returns void as $$
+begin
+    begin
+    	raise exception 'user exception';
+    exception when others then
+	    raise notice 'caught exception % %', sqlstate, sqlerrm;
+	    begin
+	        raise notice '% %', sqlstate, sqlerrm;
+	        perform 10/0;
+        exception
+            when substring_error then
+                -- this exception handler shouldn't be invoked
+                raise notice 'unexpected exception: % %', sqlstate, sqlerrm;
+	        when division_by_zero then
+	            raise notice 'caught exception % %', sqlstate, sqlerrm;
+	    end;
+	    raise notice '% %', sqlstate, sqlerrm;
+    end;
+end; $$ language plpgsql;
+select excpt_test();
+NOTICE:  caught exception P0001 user exception
+NOTICE:  P0001 user exception
+NOTICE:  caught exception 22012 division by zero
+NOTICE:  P0001 user exception
+ excpt_test 
+------------
+ 
+(1 row)
+
+drop function excpt_test();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 314f69915fc61dfecc5963666cf7c7c7083773d5..9dc00f2f1e5b7b15103d0d4b4bb3a69d62b0b39b 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -2050,3 +2050,47 @@ select execute_into_test('eifoo');
 
 drop table eifoo cascade;
 drop type eitype cascade;
+
+--
+-- SQLSTATE and SQLERRM test
+--
+
+-- should fail: SQLSTATE and SQLERRM are only in defined EXCEPTION
+-- blocks
+create function excpt_test() returns void as $$
+begin
+    raise notice '% %', sqlstate, sqlerrm;
+end; $$ language plpgsql;
+
+-- should fail
+create function excpt_test() returns void as $$
+begin
+    begin
+        begin
+    	    raise notice '% %', sqlstate, sqlerrm;
+        end;
+    end;
+end; $$ language plpgsql;
+
+create function excpt_test() returns void as $$
+begin
+    begin
+    	raise exception 'user exception';
+    exception when others then
+	    raise notice 'caught exception % %', sqlstate, sqlerrm;
+	    begin
+	        raise notice '% %', sqlstate, sqlerrm;
+	        perform 10/0;
+        exception
+            when substring_error then
+                -- this exception handler shouldn't be invoked
+                raise notice 'unexpected exception: % %', sqlstate, sqlerrm;
+	        when division_by_zero then
+	            raise notice 'caught exception % %', sqlstate, sqlerrm;
+	    end;
+	    raise notice '% %', sqlstate, sqlerrm;
+    end;
+end; $$ language plpgsql;
+
+select excpt_test();
+drop function excpt_test();