diff --git a/doc/src/sgml/errcodes.sgml b/doc/src/sgml/errcodes.sgml
index a3f56a86b14bd26463f8995c25d8f0f008ae3e42..474c0ca8da711eb96f446898b1f41dcdb11c5f7b 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.23 2008/01/15 01:36:53 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/errcodes.sgml,v 1.24 2008/05/15 22:39:48 tgl Exp $ -->
 
 <appendix id="errcodes-appendix">
  <title><productname>PostgreSQL</productname> Error Codes</title>
@@ -62,14 +62,14 @@
 
  <tgroup cols="3">
   <colspec colnum="1" colname="errorcode">
-  <colspec colnum="3" colname="constant">
-  <spanspec namest="errorcode" nameend="constant" spanname="span13">
+  <colspec colnum="3" colname="condname">
+  <spanspec namest="errorcode" nameend="condname" spanname="span13">
 
   <thead>
    <row>
     <entry>Error Code</entry>
     <entry>Meaning</entry>
-    <entry>Constant</entry>
+    <entry>Condition Name</entry>
    </row>
   </thead>
 
@@ -292,6 +292,17 @@
 </row>
 
 
+<row>
+<entry spanname="span13"><emphasis role="bold">Class 20 &mdash; Case Not Found</></entry>
+</row>
+
+<row>
+<entry><literal>20000</literal></entry>
+<entry>CASE NOT FOUND</entry>
+<entry>case_not_found</entry>
+</row>
+
+
 <row>
 <entry spanname="span13"><emphasis role="bold">Class 21 &mdash; Cardinality Violation</></entry>
 </row>
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 09ad6944dba5a065745f46346149e3084a4e6121..d2853d2d901b2644b40963c22eb34dd6d2ce7d12 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.129 2008/05/13 22:10:29 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.130 2008/05/15 22:39:49 tgl Exp $ -->
 
 <chapter id="plpgsql">
   <title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
@@ -1581,9 +1581,9 @@ SELECT * FROM getallfoo();
     <title>Conditionals</title>
 
     <para>
-     <literal>IF</> statements let you execute commands based on
-     certain conditions.  <application>PL/pgSQL</> has five forms of
-     <literal>IF</>:
+     <command>IF</> and <command>CASE</> statements let you execute
+     alternative commands based on certain conditions.
+     <application>PL/pgSQL</> has five forms of <command>IF</>:
     <itemizedlist>
      <listitem>
       <para><literal>IF ... THEN</></>
@@ -1601,6 +1601,22 @@ SELECT * FROM getallfoo();
       <para><literal>IF ... THEN ... ELSEIF ... THEN ... ELSE</></>
      </listitem>
     </itemizedlist>
+
+    and four forms of <command>CASE</>:
+    <itemizedlist>
+     <listitem>
+      <para><literal>CASE ... WHEN ... THEN ... END CASE</></>
+     </listitem>
+     <listitem>
+      <para><literal>CASE ... WHEN ... THEN ... ELSE ... END CASE</></>
+     </listitem>
+     <listitem>
+      <para><literal>CASE WHEN ... THEN ... END CASE</></>
+     </listitem>
+     <listitem>
+      <para><literal>CASE WHEN ... THEN ... ELSE ... END CASE</></>
+     </listitem>
+    </itemizedlist>
     </para>
 
     <sect3>
@@ -1751,6 +1767,93 @@ END IF;
        <literal>ELSEIF</> is an alias for <literal>ELSIF</>.
       </para>
      </sect3>
+
+     <sect3>
+      <title>Simple <literal>CASE</></title>
+
+<synopsis>
+CASE <replaceable>search-expression</replaceable>
+    WHEN <replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> <optional> ... </optional></optional> THEN
+      <replaceable>statements</replaceable>
+  <optional> WHEN <replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> <optional> ... </optional></optional> THEN
+      <replaceable>statements</replaceable>
+    ... </optional>
+  <optional> ELSE
+      <replaceable>statements</replaceable> </optional>
+END CASE;
+</synopsis>
+
+      <para>
+       The simple form of <command>CASE</> provides conditional execution
+       based on equality of operands.  The <replaceable>search-expression</>
+       is evaluated (once) and successively compared to each
+       <replaceable>expression</> in the <literal>WHEN</> clauses.
+       If a match is found, then the corresponding
+       <replaceable>statements</replaceable> are executed, and then control
+       passes to the next statement after <literal>END CASE</>.  (Subsequent
+       <literal>WHEN</> expressions are not evaluated.)  If no match is
+       found, the <literal>ELSE</> <replaceable>statements</replaceable> are
+       executed; but if <literal>ELSE</> is not present, then a
+       <literal>CASE_NOT_FOUND</literal> exception is raised.
+      </para>
+
+      <para>
+       Here is a simple example:
+
+<programlisting>
+CASE x
+    WHEN 1, 2 THEN
+        msg := 'one or two';
+    ELSE
+        msg := 'other value than one or two';
+END CASE;
+</programlisting>
+      </para>
+     </sect3>
+
+     <sect3>
+      <title>Searched <literal>CASE</></title>
+
+<synopsis>
+CASE
+    WHEN <replaceable>boolean-expression</replaceable> THEN
+      <replaceable>statements</replaceable>
+  <optional> WHEN <replaceable>boolean-expression</replaceable> THEN
+      <replaceable>statements</replaceable>
+    ... </optional>
+  <optional> ELSE
+      <replaceable>statements</replaceable> </optional>
+END CASE;
+</synopsis>
+
+      <para>
+       The searched form of <command>CASE</> provides conditional execution
+       based on truth of boolean expressions.  Each <literal>WHEN</> clause's
+       <replaceable>boolean-expression</replaceable> is evaluated in turn,
+       until one is found that yields <literal>true</>.  Then the
+       corresponding <replaceable>statements</replaceable> are executed, and
+       then control passes to the next statement after <literal>END CASE</>.
+       (Subsequent <literal>WHEN</> expressions are not evaluated.)
+       If no true result is found, the <literal>ELSE</>
+       <replaceable>statements</replaceable> are executed;
+       but if <literal>ELSE</> is not present, then a
+       <literal>CASE_NOT_FOUND</literal> exception is raised.
+      </para>
+
+      <para>
+       Here is an example:
+
+<programlisting>
+CASE
+    WHEN x BETWEEN 0 AND 10 THEN
+        msg := 'value is between zero and ten';
+    WHEN x BETWEEN 11 AND 20 THEN
+        msg := 'value is between eleven and twenty';
+END CASE;
+</programlisting>
+      </para>
+
+     </sect3>
    </sect2>
 
    <sect2 id="plpgsql-control-structures-loops">
diff --git a/src/include/utils/errcodes.h b/src/include/utils/errcodes.h
index 1caaf69f2d76c30e7b89d090c4127dcdbb1bb93c..9cfdd16bde2454747eafb828e9a4fa071b154446 100644
--- a/src/include/utils/errcodes.h
+++ b/src/include/utils/errcodes.h
@@ -11,7 +11,7 @@
  *
  * Copyright (c) 2003-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.24 2008/01/01 19:45:59 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.25 2008/05/15 22:39:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -101,6 +101,9 @@
 /* Class 0P - Invalid Role Specification */
 #define ERRCODE_INVALID_ROLE_SPECIFICATION	MAKE_SQLSTATE('0','P', '0','0','0')
 
+/* Class 20 - Case Not Found */
+#define ERRCODE_CASE_NOT_FOUND				MAKE_SQLSTATE('2','0', '0','0','0')
+
 /* Class 21 - Cardinality Violation */
 /* (this means something returned the wrong number of rows) */
 #define ERRCODE_CARDINALITY_VIOLATION		MAKE_SQLSTATE('2','1', '0','0','0')
diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y
index b67a8bba776a28a493401ce233ee038e70678a4c..dfd37f67717a426d8374a32ce25a246e18b6e003 100644
--- a/src/pl/plpgsql/src/gram.y
+++ b/src/pl/plpgsql/src/gram.y
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.112 2008/05/13 22:10:29 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.113 2008/05/15 22:39:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -37,6 +37,8 @@ static	PLpgSQL_stmt_fetch *read_fetch_direction(void);
 static	PLpgSQL_stmt	*make_return_stmt(int lineno);
 static	PLpgSQL_stmt	*make_return_next_stmt(int lineno);
 static	PLpgSQL_stmt	*make_return_query_stmt(int lineno);
+static  PLpgSQL_stmt 	*make_case(int lineno, PLpgSQL_expr *t_expr,
+								   List *case_when_list, List *else_stmts);
 static	void			 check_assignable(PLpgSQL_datum *datum);
 static	void			 read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row,
 										  bool *strict);
@@ -102,6 +104,7 @@ static List				*read_raise_options(void);
 		PLpgSQL_nsitem			*nsitem;
 		PLpgSQL_diag_item		*diagitem;
 		PLpgSQL_stmt_fetch		*fetch;
+		PLpgSQL_case_when		*casewhen;
 }
 
 %type <declhdr> decl_sect
@@ -116,7 +119,7 @@ static List				*read_raise_options(void);
 %type <str>		decl_stmts decl_stmt
 
 %type <expr>	expr_until_semi expr_until_rightbracket
-%type <expr>	expr_until_then expr_until_loop
+%type <expr>	expr_until_then expr_until_loop opt_expr_until_when
 %type <expr>	opt_exitcond
 
 %type <ival>	assign_var
@@ -135,12 +138,16 @@ static List				*read_raise_options(void);
 %type <stmt>	stmt_return stmt_raise stmt_execsql stmt_execsql_insert
 %type <stmt>	stmt_dynexecute stmt_for stmt_perform stmt_getdiag
 %type <stmt>	stmt_open stmt_fetch stmt_move stmt_close stmt_null
+%type <stmt>	stmt_case
 
 %type <list>	proc_exceptions
 %type <exception_block> exception_sect
 %type <exception>	proc_exception
 %type <condition>	proc_conditions proc_condition
 
+%type <casewhen>	case_when
+%type <list>	case_when_list opt_case_else
+
 %type <list>	getdiag_list
 %type <diagitem> getdiag_list_item
 %type <ival>	getdiag_kind getdiag_target
@@ -157,6 +164,7 @@ static List				*read_raise_options(void);
 %token	K_ASSIGN
 %token	K_BEGIN
 %token	K_BY
+%token	K_CASE
 %token	K_CLOSE
 %token	K_CONSTANT
 %token	K_CONTINUE
@@ -581,9 +589,7 @@ decl_defkey		: K_ASSIGN
 				;
 
 proc_sect		:
-					{
-						$$ = NIL;
-					}
+					{ $$ = NIL; }
 				| proc_stmts
 					{ $$ = $1; }
 				;
@@ -598,7 +604,7 @@ proc_stmts		: proc_stmts proc_stmt
 				| proc_stmt
 						{
 							if ($1 == NULL)
-								$$ = NULL;
+								$$ = NIL;
 							else
 								$$ = list_make1($1);
 						}
@@ -610,6 +616,8 @@ proc_stmt		: pl_block ';'
 						{ $$ = $1; }
 				| stmt_if
 						{ $$ = $1; }
+				| stmt_case
+						{ $$ = $1; }
 				| stmt_loop
 						{ $$ = $1; }
 				| stmt_while
@@ -808,6 +816,67 @@ stmt_else		:
 					}
 				;
 
+stmt_case		: K_CASE lno opt_expr_until_when case_when_list opt_case_else K_END K_CASE ';'
+					{
+						$$ = make_case($2, $3, $4, $5);
+					}
+				;
+
+opt_expr_until_when	:
+					{
+						PLpgSQL_expr *expr = NULL;
+						int	tok = yylex();
+
+						if (tok != K_WHEN)
+						{
+							plpgsql_push_back_token(tok);
+							expr = plpgsql_read_expression(K_WHEN, "WHEN");
+						}
+						plpgsql_push_back_token(K_WHEN);
+						$$ = expr;
+					}
+			    ;
+
+case_when_list	: case_when_list case_when
+					{
+						$$ = lappend($1, $2);
+					}
+				| case_when
+					{
+						$$ = list_make1($1);
+					}
+				;
+
+case_when		: K_WHEN lno expr_until_then proc_sect
+					{
+						PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
+
+						new->lineno	= $2;
+						new->expr	= $3;
+						new->stmts	= $4;
+						$$ = new;
+					}
+				;
+
+opt_case_else	:
+					{
+						$$ = NIL;
+					}
+				| K_ELSE proc_sect
+					{
+						/*
+						 * proc_sect could return an empty list, but we
+						 * must distinguish that from not having ELSE at all.
+						 * Simplest fix is to return a list with one NULL
+						 * pointer, which make_case() must take care of.
+						 */
+						if ($2 != NIL)
+							$$ = $2;
+						else
+							$$ = list_make1(NULL);
+					}
+				;
+
 stmt_loop		: opt_block_label K_LOOP lno loop_body
 					{
 						PLpgSQL_stmt_loop *new;
@@ -2804,6 +2873,103 @@ read_raise_options(void)
 	return result;
 }
 
+/*
+ * Fix up CASE statement
+ */
+static PLpgSQL_stmt *
+make_case(int lineno, PLpgSQL_expr *t_expr,
+		  List *case_when_list, List *else_stmts)
+{
+	PLpgSQL_stmt_case 	*new;
+
+	new = palloc(sizeof(PLpgSQL_stmt_case));
+	new->cmd_type = PLPGSQL_STMT_CASE;
+	new->lineno = lineno;
+	new->t_expr = t_expr;
+	new->t_varno = 0;
+	new->case_when_list = case_when_list;
+	new->have_else = (else_stmts != NIL);
+	/* Get rid of list-with-NULL hack */
+	if (list_length(else_stmts) == 1 && linitial(else_stmts) == NULL)
+		new->else_stmts = NIL;
+	else
+		new->else_stmts = else_stmts;
+
+	/*
+	 * When test expression is present, we create a var for it and then
+	 * convert all the WHEN expressions to "VAR IN (original_expression)".
+	 * This is a bit klugy, but okay since we haven't yet done more than
+	 * read the expressions as text.  (Note that previous parsing won't
+	 * have complained if the WHEN ... THEN expression contained multiple
+	 * comma-separated values.)
+	 */
+	if (t_expr)
+	{
+		ListCell *l;
+		PLpgSQL_var *t_var;
+		int		t_varno;
+
+		/*
+		 * We don't yet know the result datatype of t_expr.  Build the
+		 * variable as if it were INT4; we'll fix this at runtime if needed.
+		 */
+		t_var = (PLpgSQL_var *)
+			plpgsql_build_variable("*case*", lineno,
+								   plpgsql_build_datatype(INT4OID, -1),
+								   false);
+		t_varno = t_var->varno;
+		new->t_varno = t_varno;
+
+		foreach(l, case_when_list)
+		{
+			PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+			PLpgSQL_expr *expr = cwt->expr;
+			int		nparams = expr->nparams;
+			PLpgSQL_expr *new_expr;
+			PLpgSQL_dstring ds;
+			char	buff[32];
+
+			/* Must add the CASE variable as an extra param to expression */
+			if (nparams >= MAX_EXPR_PARAMS)
+			{
+				plpgsql_error_lineno = cwt->lineno;
+				ereport(ERROR,
+					    (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+					     errmsg("too many variables specified in SQL statement")));
+			}
+
+			new_expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * (nparams + 1) - sizeof(int));
+			memcpy(new_expr, expr,
+				   sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
+			new_expr->nparams = nparams + 1;
+			new_expr->params[nparams] = t_varno;
+
+			/* And do the string hacking */
+			plpgsql_dstring_init(&ds);
+
+			plpgsql_dstring_append(&ds, "SELECT $");
+			snprintf(buff, sizeof(buff), "%d", nparams + 1);
+			plpgsql_dstring_append(&ds, buff);
+			plpgsql_dstring_append(&ds, " IN (");
+
+			/* copy expression query without SELECT keyword */
+			Assert(strncmp(expr->query, "SELECT ", 7) == 0);
+			plpgsql_dstring_append(&ds, expr->query + 7);
+			plpgsql_dstring_append_char(&ds, ')');
+
+			new_expr->query = pstrdup(plpgsql_dstring_get(&ds));
+
+			plpgsql_dstring_free(&ds);
+			pfree(expr->query);
+			pfree(expr);
+
+			cwt->expr = new_expr;
+		}
+	}
+
+	return (PLpgSQL_stmt *) new;
+}
+
 
 /* Needed to avoid conflict between different prefix settings: */
 #undef yylex
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 2ba45befb71e899473ec68a39918e8a119fcec90..aeb5d365b495a3eb976bae9439ccd078c203d078 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.214 2008/05/13 22:10:30 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.215 2008/05/15 22:39:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -94,6 +94,8 @@ static int exec_stmt_getdiag(PLpgSQL_execstate *estate,
 				  PLpgSQL_stmt_getdiag *stmt);
 static int exec_stmt_if(PLpgSQL_execstate *estate,
 			 PLpgSQL_stmt_if *stmt);
+static int exec_stmt_case(PLpgSQL_execstate *estate,
+						  PLpgSQL_stmt_case *stmt);
 static int exec_stmt_loop(PLpgSQL_execstate *estate,
 			   PLpgSQL_stmt_loop *stmt);
 static int exec_stmt_while(PLpgSQL_execstate *estate,
@@ -1229,7 +1231,7 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
 
 	CHECK_FOR_INTERRUPTS();
 
-	switch (stmt->cmd_type)
+	switch ((enum PLpgSQL_stmt_types) stmt->cmd_type)
 	{
 		case PLPGSQL_STMT_BLOCK:
 			rc = exec_stmt_block(estate, (PLpgSQL_stmt_block *) stmt);
@@ -1251,6 +1253,10 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
 			rc = exec_stmt_if(estate, (PLpgSQL_stmt_if *) stmt);
 			break;
 
+		case PLPGSQL_STMT_CASE:
+			rc = exec_stmt_case(estate, (PLpgSQL_stmt_case *) stmt);
+			break;
+
 		case PLPGSQL_STMT_LOOP:
 			rc = exec_stmt_loop(estate, (PLpgSQL_stmt_loop *) stmt);
 			break;
@@ -1442,6 +1448,91 @@ exec_stmt_if(PLpgSQL_execstate *estate, PLpgSQL_stmt_if *stmt)
 }
 
 
+/*-----------
+ * exec_stmt_case
+ *-----------
+ */
+static int
+exec_stmt_case(PLpgSQL_execstate *estate, PLpgSQL_stmt_case *stmt)
+{
+	PLpgSQL_var *t_var = NULL;
+	bool		isnull;
+	ListCell   *l;
+
+	if (stmt->t_expr != NULL)
+	{
+		/* simple case */
+		Datum	t_val;
+		Oid		t_oid;
+
+		t_val = exec_eval_expr(estate, stmt->t_expr, &isnull, &t_oid);
+
+		t_var = (PLpgSQL_var *) estate->datums[stmt->t_varno];
+
+		/*
+		 * When expected datatype is different from real, change it.
+		 * Note that what we're modifying here is an execution copy
+		 * of the datum, so this doesn't affect the originally stored
+		 * function parse tree.
+		 */
+		if (t_var->datatype->typoid != t_oid)
+			t_var->datatype = plpgsql_build_datatype(t_oid, -1);
+
+		/* now we can assign to the variable */
+		exec_assign_value(estate,
+						  (PLpgSQL_datum *) t_var,
+						  t_val,
+						  t_oid,
+						  &isnull);
+
+		exec_eval_cleanup(estate);
+	}
+
+	/* Now search for a successful WHEN clause */
+	foreach(l, stmt->case_when_list)
+	{
+		PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+		bool	value;
+
+		value = exec_eval_boolean(estate, cwt->expr, &isnull);
+		exec_eval_cleanup(estate);
+		if (!isnull && value)
+		{
+			/* Found it */
+
+			/* We can now discard any value we had for the temp variable */
+			if (t_var != NULL)
+			{
+				free_var(t_var);
+				t_var->value = (Datum) 0;
+				t_var->isnull = true;
+			}
+
+			/* Evaluate the statement(s), and we're done */
+			return exec_stmts(estate, cwt->stmts);
+		}
+	}
+
+	/* We can now discard any value we had for the temp variable */
+	if (t_var != NULL)
+	{
+		free_var(t_var);
+		t_var->value = (Datum) 0;
+		t_var->isnull = true;
+	}
+
+	/* SQL2003 mandates this error if there was no ELSE clause */
+	if (!stmt->have_else)
+		ereport(ERROR,
+				(errcode(ERRCODE_CASE_NOT_FOUND),
+				 errmsg("case not found"),
+				 errhint("CASE statement is missing ELSE part.")));
+
+	/* Evaluate the ELSE statements, and we're done */
+	return exec_stmts(estate, stmt->else_stmts);
+}
+
+
 /* ----------
  * exec_stmt_loop			Loop over statements until
  *					an exit occurs.
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index cb9e9c99ec3e33e1aea7f048997557fc80c7126c..aab349bdb2c0359b06cf9f4dd96c020045616e4f 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.71 2008/05/13 22:10:30 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.72 2008/05/15 22:39:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -466,7 +466,7 @@ plpgsql_convert_ident(const char *s, char **output, int numidents)
 const char *
 plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
 {
-	switch (stmt->cmd_type)
+	switch ((enum PLpgSQL_stmt_types) stmt->cmd_type)
 	{
 		case PLPGSQL_STMT_BLOCK:
 			return _("statement block");
@@ -474,6 +474,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
 			return _("assignment");
 		case PLPGSQL_STMT_IF:
 			return "IF";
+		case PLPGSQL_STMT_CASE:
+			return "CASE";
 		case PLPGSQL_STMT_LOOP:
 			return "LOOP";
 		case PLPGSQL_STMT_WHILE:
@@ -526,6 +528,7 @@ static void dump_stmt(PLpgSQL_stmt *stmt);
 static void dump_block(PLpgSQL_stmt_block *block);
 static void dump_assign(PLpgSQL_stmt_assign *stmt);
 static void dump_if(PLpgSQL_stmt_if *stmt);
+static void dump_case(PLpgSQL_stmt_case *stmt);
 static void dump_loop(PLpgSQL_stmt_loop *stmt);
 static void dump_while(PLpgSQL_stmt_while *stmt);
 static void dump_fori(PLpgSQL_stmt_fori *stmt);
@@ -561,7 +564,7 @@ static void
 dump_stmt(PLpgSQL_stmt *stmt)
 {
 	printf("%3d:", stmt->lineno);
-	switch (stmt->cmd_type)
+	switch ((enum PLpgSQL_stmt_types) stmt->cmd_type)
 	{
 		case PLPGSQL_STMT_BLOCK:
 			dump_block((PLpgSQL_stmt_block *) stmt);
@@ -572,6 +575,9 @@ dump_stmt(PLpgSQL_stmt *stmt)
 		case PLPGSQL_STMT_IF:
 			dump_if((PLpgSQL_stmt_if *) stmt);
 			break;
+		case PLPGSQL_STMT_CASE:
+			dump_case((PLpgSQL_stmt_case *) stmt);
+			break;
 		case PLPGSQL_STMT_LOOP:
 			dump_loop((PLpgSQL_stmt_loop *) stmt);
 			break;
@@ -714,6 +720,44 @@ dump_if(PLpgSQL_stmt_if *stmt)
 	printf("    ENDIF\n");
 }
 
+static void
+dump_case(PLpgSQL_stmt_case *stmt)
+{
+	ListCell	*l;
+
+	dump_ind();
+	printf("CASE %d ", stmt->t_varno);
+	if (stmt->t_expr)
+		dump_expr(stmt->t_expr);
+	printf("\n");
+	dump_indent += 6;
+	foreach(l, stmt->case_when_list)
+	{
+		PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+
+		dump_ind();
+		printf("WHEN ");
+		dump_expr(cwt->expr);
+		printf("\n");
+		dump_ind();
+		printf("THEN\n");
+		dump_indent += 2;
+		dump_stmts(cwt->stmts);
+		dump_indent -= 2;
+	}
+	if (stmt->have_else)
+	{
+		dump_ind();
+		printf("ELSE\n");
+		dump_indent += 2;
+		dump_stmts(stmt->else_stmts);
+		dump_indent -= 2;
+	}
+	dump_indent -= 6;
+	dump_ind();
+	printf("    ENDCASE\n");
+}
+
 static void
 dump_loop(PLpgSQL_stmt_loop *stmt)
 {
@@ -1025,7 +1069,7 @@ dump_raise(PLpgSQL_stmt_raise *stmt)
 		foreach(lc, stmt->options)
 		{
 			PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc);
-		
+
 			dump_ind();
 			switch (opt->opt_type)
 			{
@@ -1034,7 +1078,7 @@ dump_raise(PLpgSQL_stmt_raise *stmt)
 					break;
 				case PLPGSQL_RAISEOPTION_MESSAGE:
 					printf("    MESSAGE = ");
-					break;	
+					break;
 				case PLPGSQL_RAISEOPTION_DETAIL:
 					printf("    DETAIL = ");
 					break;
@@ -1044,7 +1088,7 @@ dump_raise(PLpgSQL_stmt_raise *stmt)
 			}
 			dump_expr(opt->expr);
 			printf("\n");
-		}		
+		}
 		dump_indent -= 2;
 	}
 	dump_indent -= 2;
diff --git a/src/pl/plpgsql/src/plerrcodes.h b/src/pl/plpgsql/src/plerrcodes.h
index 5fbdf255d820756c8d664cb15026c48639bdba0f..e6ab24dfd0e9990898377084983e98cce0c04264 100644
--- a/src/pl/plpgsql/src/plerrcodes.h
+++ b/src/pl/plpgsql/src/plerrcodes.h
@@ -9,7 +9,7 @@
  *
  * Copyright (c) 2003-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.13 2008/01/15 01:36:53 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plerrcodes.h,v 1.14 2008/05/15 22:39:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -79,6 +79,10 @@
 	"invalid_role_specification", ERRCODE_INVALID_ROLE_SPECIFICATION
 },
 
+{
+	"case_not_found", ERRCODE_CASE_NOT_FOUND
+},
+
 {
 	"cardinality_violation", ERRCODE_CARDINALITY_VIOLATION
 },
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 53d691a596ab091226e9261f916bb721929f2582..735a6810053bdf3249a97442fb4ef90638712088 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.99 2008/05/13 22:10:30 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.100 2008/05/15 22:39:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -71,11 +71,12 @@ enum
  * Execution tree node types
  * ----------
  */
-enum
+enum PLpgSQL_stmt_types
 {
 	PLPGSQL_STMT_BLOCK,
 	PLPGSQL_STMT_ASSIGN,
 	PLPGSQL_STMT_IF,
+	PLPGSQL_STMT_CASE,
 	PLPGSQL_STMT_LOOP,
 	PLPGSQL_STMT_WHILE,
 	PLPGSQL_STMT_FORI,
@@ -390,6 +391,25 @@ typedef struct
 } PLpgSQL_stmt_if;
 
 
+typedef struct					/* CASE statement */
+{
+	int			cmd_type;
+	int			lineno;
+	PLpgSQL_expr *t_expr;		/* test expression, or NULL if none */
+	int			t_varno;		/* var to store test expression value into */
+	List	   *case_when_list;	/* List of PLpgSQL_case_when structs */
+	bool		have_else;		/* flag needed because list could be empty */
+	List	   *else_stmts;		/* List of statements */
+} PLpgSQL_stmt_case;
+
+typedef struct					/* one arm of CASE statement */
+{
+	int			lineno;
+	PLpgSQL_expr *expr;			/* boolean expression for this case */
+	List	   *stmts;			/* List of statements */
+} PLpgSQL_case_when;
+
+
 typedef struct
 {								/* Unconditional LOOP statement		*/
 	int			cmd_type;
diff --git a/src/pl/plpgsql/src/scan.l b/src/pl/plpgsql/src/scan.l
index 73258ec364802ac675f8fde39b708a17665d0bb3..77e9335a0529f6dc8095a45bb330a02302dcbc23 100644
--- a/src/pl/plpgsql/src/scan.l
+++ b/src/pl/plpgsql/src/scan.l
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.63 2008/05/13 22:10:30 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.64 2008/05/15 22:39:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -116,6 +116,7 @@ dolqinside		[^$]+
 alias			{ return K_ALIAS;			}
 begin			{ return K_BEGIN;			}
 by				{ return K_BY;   			}
+case			{ return K_CASE;			}
 close			{ return K_CLOSE;			}
 constant		{ return K_CONSTANT;		}
 continue		{ return K_CONTINUE;		}
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 9f18f45126acca0b96b8fec3a536873836969cba..582c06785a8aee50c04bcbd95756676da4ff9335 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -2,24 +2,24 @@
 -- PLPGSQL
 --
 -- Scenario:
--- 
+--
 --     A building with a modern TP cable installation where any
 --     of the wall connectors can be used to plug in phones,
 --     ethernet interfaces or local office hubs. The backside
 --     of the wall connectors is wired to one of several patch-
 --     fields in the building.
--- 
+--
 --     In the patchfields, there are hubs and all the slots
 --     representing the wall connectors. In addition there are
 --     slots that can represent a phone line from the central
 --     phone system.
--- 
+--
 --     Triggers ensure consistency of the patching information.
--- 
+--
 --     Functions are used to build up powerful views that let
 --     you look behind the wall when looking at a patchfield
 --     or into a room.
--- 
+--
 create table Room (
     roomno	char(8),
     comment	text
@@ -84,10 +84,10 @@ create table PHone (
 );
 create unique index PHone_name on PHone using btree (slotname bpchar_ops);
 -- ************************************************************
--- * 
+-- *
 -- * Trigger procedures and functions for the patchfield
 -- * test of PL/pgSQL
--- * 
+-- *
 -- ************************************************************
 -- ************************************************************
 -- * AFTER UPDATE on Room
@@ -597,11 +597,11 @@ begin
     mytype := substr(myname, 1, 2);
     link := mytype || substr(blname, 1, 2);
     if link = ''PLPL'' then
-        raise exception 
+        raise exception
 		''backlink between two phone lines does not make sense'';
     end if;
     if link in (''PLWS'', ''WSPL'') then
-        raise exception 
+        raise exception
 		''direct link of phone line to wall slot not permitted'';
     end if;
     if mytype = ''PS'' then
@@ -745,19 +745,19 @@ begin
     mytype := substr(myname, 1, 2);
     link := mytype || substr(blname, 1, 2);
     if link = ''PHPH'' then
-        raise exception 
+        raise exception
 		''slotlink between two phones does not make sense'';
     end if;
     if link in (''PHHS'', ''HSPH'') then
-        raise exception 
+        raise exception
 		''link of phone to hub does not make sense'';
     end if;
     if link in (''PHIF'', ''IFPH'') then
-        raise exception 
+        raise exception
 		''link of phone to hub does not make sense'';
     end if;
     if link in (''PSWS'', ''WSPS'') then
-        raise exception 
+        raise exception
 		''slotlink from patchslot to wallslot not permitted'';
     end if;
     if mytype = ''PS'' then
@@ -2936,7 +2936,7 @@ CONTEXT:  PL/pgSQL function "footest" line 4 at EXECUTE statement
 drop function footest();
 -- test scrollable cursor support
 create function sc_test() returns setof integer as $$
-declare 
+declare
   c scroll cursor for select f1 from int4_tbl;
   x integer;
 begin
@@ -2960,7 +2960,7 @@ select * from sc_test();
 (5 rows)
 
 create or replace function sc_test() returns setof integer as $$
-declare 
+declare
   c no scroll cursor for select f1 from int4_tbl;
   x integer;
 begin
@@ -2978,7 +2978,7 @@ ERROR:  cursor can only scan forward
 HINT:  Declare it with SCROLL option to enable backward scan.
 CONTEXT:  PL/pgSQL function "sc_test" line 6 at FETCH
 create or replace function sc_test() returns setof integer as $$
-declare 
+declare
   c refcursor;
   x integer;
 begin
@@ -3002,7 +3002,7 @@ select * from sc_test();
 (5 rows)
 
 create or replace function sc_test() returns setof integer as $$
-declare 
+declare
   c refcursor;
   x integer;
 begin
@@ -3288,9 +3288,9 @@ drop function return_dquery();
 -- Tests for 8.4's new RAISE features
 create or replace function raise_test() returns void as $$
 begin
-  raise notice '% % %', 1, 2, 3 
+  raise notice '% % %', 1, 2, 3
      using errcode = '55001', detail = 'some detail info', hint = 'some hint';
-  raise '% % %', 1, 2, 3 
+  raise '% % %', 1, 2, 3
      using errcode = 'division_by_zero', detail = 'some detail info';
 end;
 $$ language plpgsql;
@@ -3414,3 +3414,133 @@ select raise_test();
 ERROR:  RAISE without parameters cannot be used outside an exception handler
 CONTEXT:  PL/pgSQL function "raise_test"
 drop function raise_test();
+-- test CASE statement
+create or replace function case_test(bigint) returns text as $$
+declare a int = 10;
+        b int = 1;
+begin
+  case $1
+    when 1 then
+      return 'one';
+    when 2 then
+      return 'two';
+    when 3,4,3+5 then
+      return 'three, four or eight';
+    when a then
+      return 'ten';
+    when a+b, a+b+1 then
+      return 'eleven, twelve';
+  end case;
+end;
+$$ language plpgsql immutable;
+select case_test(1);
+ case_test 
+-----------
+ one
+(1 row)
+
+select case_test(2);
+ case_test 
+-----------
+ two
+(1 row)
+
+select case_test(3);
+      case_test       
+----------------------
+ three, four or eight
+(1 row)
+
+select case_test(4);
+      case_test       
+----------------------
+ three, four or eight
+(1 row)
+
+select case_test(5); -- fails
+ERROR:  case not found
+HINT:  CASE statement is missing ELSE part.
+CONTEXT:  PL/pgSQL function "case_test" line 4 at CASE
+select case_test(8);
+      case_test       
+----------------------
+ three, four or eight
+(1 row)
+
+select case_test(10);
+ case_test 
+-----------
+ ten
+(1 row)
+
+select case_test(11);
+   case_test    
+----------------
+ eleven, twelve
+(1 row)
+
+select case_test(12);
+   case_test    
+----------------
+ eleven, twelve
+(1 row)
+
+select case_test(13); -- fails
+ERROR:  case not found
+HINT:  CASE statement is missing ELSE part.
+CONTEXT:  PL/pgSQL function "case_test" line 4 at CASE
+create or replace function catch() returns void as $$
+begin
+  raise notice '%', case_test(6);
+exception
+  when case_not_found then
+    raise notice 'caught case_not_found % %', SQLSTATE, SQLERRM;
+end
+$$ language plpgsql;
+select catch();
+NOTICE:  caught case_not_found 20000 case not found
+ catch 
+-------
+ 
+(1 row)
+
+-- test the searched variant too, as well as ELSE
+create or replace function case_test(bigint) returns text as $$
+declare a int = 10;
+begin
+  case
+    when $1 = 1 then
+      return 'one';
+    when $1 = a + 2 then
+      return 'twelve';
+    else
+      return 'other';
+  end case;
+end;
+$$ language plpgsql immutable;
+select case_test(1);
+ case_test 
+-----------
+ one
+(1 row)
+
+select case_test(2);
+ case_test 
+-----------
+ other
+(1 row)
+
+select case_test(12);
+ case_test 
+-----------
+ twelve
+(1 row)
+
+select case_test(13);
+ case_test 
+-----------
+ other
+(1 row)
+
+drop function catch();
+drop function case_test(bigint);
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index f64bfabc1e5385d79f1d8c4af55e793151e06c59..0267dda30ca21662ee7dbe0b6cf659aab93c01d0 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -2,24 +2,24 @@
 -- PLPGSQL
 --
 -- Scenario:
--- 
+--
 --     A building with a modern TP cable installation where any
 --     of the wall connectors can be used to plug in phones,
 --     ethernet interfaces or local office hubs. The backside
 --     of the wall connectors is wired to one of several patch-
 --     fields in the building.
--- 
+--
 --     In the patchfields, there are hubs and all the slots
 --     representing the wall connectors. In addition there are
 --     slots that can represent a phone line from the central
 --     phone system.
--- 
+--
 --     Triggers ensure consistency of the patching information.
--- 
+--
 --     Functions are used to build up powerful views that let
 --     you look behind the wall when looking at a patchfield
 --     or into a room.
--- 
+--
 
 
 create table Room (
@@ -116,10 +116,10 @@ create unique index PHone_name on PHone using btree (slotname bpchar_ops);
 
 
 -- ************************************************************
--- * 
+-- *
 -- * Trigger procedures and functions for the patchfield
 -- * test of PL/pgSQL
--- * 
+-- *
 -- ************************************************************
 
 
@@ -708,11 +708,11 @@ begin
     mytype := substr(myname, 1, 2);
     link := mytype || substr(blname, 1, 2);
     if link = ''PLPL'' then
-        raise exception 
+        raise exception
 		''backlink between two phone lines does not make sense'';
     end if;
     if link in (''PLWS'', ''WSPL'') then
-        raise exception 
+        raise exception
 		''direct link of phone line to wall slot not permitted'';
     end if;
     if mytype = ''PS'' then
@@ -868,19 +868,19 @@ begin
     mytype := substr(myname, 1, 2);
     link := mytype || substr(blname, 1, 2);
     if link = ''PHPH'' then
-        raise exception 
+        raise exception
 		''slotlink between two phones does not make sense'';
     end if;
     if link in (''PHHS'', ''HSPH'') then
-        raise exception 
+        raise exception
 		''link of phone to hub does not make sense'';
     end if;
     if link in (''PHIF'', ''IFPH'') then
-        raise exception 
+        raise exception
 		''link of phone to hub does not make sense'';
     end if;
     if link in (''PSWS'', ''WSPS'') then
-        raise exception 
+        raise exception
 		''slotlink from patchslot to wallslot not permitted'';
     end if;
     if mytype = ''PS'' then
@@ -2444,7 +2444,7 @@ drop function footest();
 -- test scrollable cursor support
 
 create function sc_test() returns setof integer as $$
-declare 
+declare
   c scroll cursor for select f1 from int4_tbl;
   x integer;
 begin
@@ -2461,7 +2461,7 @@ $$ language plpgsql;
 select * from sc_test();
 
 create or replace function sc_test() returns setof integer as $$
-declare 
+declare
   c no scroll cursor for select f1 from int4_tbl;
   x integer;
 begin
@@ -2478,7 +2478,7 @@ $$ language plpgsql;
 select * from sc_test();  -- fails because of NO SCROLL specification
 
 create or replace function sc_test() returns setof integer as $$
-declare 
+declare
   c refcursor;
   x integer;
 begin
@@ -2495,7 +2495,7 @@ $$ language plpgsql;
 select * from sc_test();
 
 create or replace function sc_test() returns setof integer as $$
-declare 
+declare
   c refcursor;
   x integer;
 begin
@@ -2688,9 +2688,9 @@ drop function return_dquery();
 
 create or replace function raise_test() returns void as $$
 begin
-  raise notice '% % %', 1, 2, 3 
+  raise notice '% % %', 1, 2, 3
      using errcode = '55001', detail = 'some detail info', hint = 'some hint';
-  raise '% % %', 1, 2, 3 
+  raise '% % %', 1, 2, 3
      using errcode = 'division_by_zero', detail = 'some detail info';
 end;
 $$ language plpgsql;
@@ -2812,3 +2812,69 @@ $$ language plpgsql;
 select raise_test();
 
 drop function raise_test();
+
+-- test CASE statement
+
+create or replace function case_test(bigint) returns text as $$
+declare a int = 10;
+        b int = 1;
+begin
+  case $1
+    when 1 then
+      return 'one';
+    when 2 then
+      return 'two';
+    when 3,4,3+5 then
+      return 'three, four or eight';
+    when a then
+      return 'ten';
+    when a+b, a+b+1 then
+      return 'eleven, twelve';
+  end case;
+end;
+$$ language plpgsql immutable;
+
+select case_test(1);
+select case_test(2);
+select case_test(3);
+select case_test(4);
+select case_test(5); -- fails
+select case_test(8);
+select case_test(10);
+select case_test(11);
+select case_test(12);
+select case_test(13); -- fails
+
+create or replace function catch() returns void as $$
+begin
+  raise notice '%', case_test(6);
+exception
+  when case_not_found then
+    raise notice 'caught case_not_found % %', SQLSTATE, SQLERRM;
+end
+$$ language plpgsql;
+
+select catch();
+
+-- test the searched variant too, as well as ELSE
+create or replace function case_test(bigint) returns text as $$
+declare a int = 10;
+begin
+  case
+    when $1 = 1 then
+      return 'one';
+    when $1 = a + 2 then
+      return 'twelve';
+    else
+      return 'other';
+  end case;
+end;
+$$ language plpgsql immutable;
+
+select case_test(1);
+select case_test(2);
+select case_test(12);
+select case_test(13);
+
+drop function catch();
+drop function case_test(bigint);