diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 94a5c110d410e56a5c7cf8aeb7a85609c350db97..fc16cccdda9fec82b2e871f084d99808e6fcbc65 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.155 2004/03/17 01:02:23 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.156 2004/03/17 20:48:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -81,6 +81,9 @@ static Datum ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext,
 						 bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
 			 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalCaseTestExpr(ExprState *exprstate,
+								  ExprContext *econtext,
+								  bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalArray(ArrayExprState *astate,
 						   ExprContext *econtext,
 						   bool *isNull, ExprDoneCond *isDone);
@@ -1809,10 +1812,29 @@ ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
 {
 	List	   *clauses = caseExpr->args;
 	List	   *clause;
+	Datum		save_datum;
+	bool		save_isNull;
 
 	if (isDone)
 		*isDone = ExprSingleResult;
 
+	/*
+	 * If there's a test expression, we have to evaluate it and save
+	 * the value where the CaseTestExpr placeholders can find it.
+	 * We must save and restore prior setting of econtext's caseValue fields,
+	 * in case this node is itself within a larger CASE.
+	 */
+	save_datum = econtext->caseValue_datum;
+	save_isNull = econtext->caseValue_isNull;
+
+	if (caseExpr->arg)
+	{
+		econtext->caseValue_datum = ExecEvalExpr(caseExpr->arg,
+												 econtext,
+												 &econtext->caseValue_isNull,
+												 NULL);
+	}
+
 	/*
 	 * we evaluate each of the WHEN clauses in turn, as soon as one is
 	 * true we return the corresponding result. If none are true then we
@@ -1835,6 +1857,8 @@ ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
 		 */
 		if (DatumGetBool(clause_value) && !*isNull)
 		{
+			econtext->caseValue_datum = save_datum;
+			econtext->caseValue_isNull = save_isNull;
 			return ExecEvalExpr(wclause->result,
 								econtext,
 								isNull,
@@ -1842,6 +1866,9 @@ ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
 		}
 	}
 
+	econtext->caseValue_datum = save_datum;
+	econtext->caseValue_isNull = save_isNull;
+
 	if (caseExpr->defresult)
 	{
 		return ExecEvalExpr(caseExpr->defresult,
@@ -1854,6 +1881,22 @@ ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
 	return (Datum) 0;
 }
 
+/*
+ * ExecEvalCaseTestExpr
+ *
+ * Return the value stored by CASE.
+ */
+static Datum
+ExecEvalCaseTestExpr(ExprState *exprstate,
+					 ExprContext *econtext,
+					 bool *isNull, ExprDoneCond *isDone)
+{
+	if (isDone)
+		*isDone = ExprSingleResult;
+	*isNull = econtext->caseValue_isNull;
+	return econtext->caseValue_datum;
+}
+
 /* ----------------------------------------------------------------
  *		ExecEvalArray - ARRAY[] expressions
  *
@@ -2478,6 +2521,10 @@ ExecInitExpr(Expr *node, PlanState *parent)
 			state = (ExprState *) makeNode(ExprState);
 			state->evalfunc = ExecEvalCoerceToDomainValue;
 			break;
+		case T_CaseTestExpr:
+			state = (ExprState *) makeNode(ExprState);
+			state->evalfunc = ExecEvalCaseTestExpr;
+			break;
 		case T_Aggref:
 			{
 				Aggref	   *aggref = (Aggref *) node;
@@ -2666,6 +2713,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				List	   *inlist;
 
 				cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCase;
+				cstate->arg = ExecInitExpr(caseexpr->arg, parent);
 				FastListInit(&outlist);
 				foreach(inlist, caseexpr->args)
 				{
@@ -2680,8 +2728,6 @@ ExecInitExpr(Expr *node, PlanState *parent)
 					FastAppend(&outlist, wstate);
 				}
 				cstate->args = FastListValue(&outlist);
-				/* caseexpr->arg should be null by now */
-				Assert(caseexpr->arg == NULL);
 				cstate->defresult = ExecInitExpr(caseexpr->defresult, parent);
 				state = (ExprState *) cstate;
 			}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index b89f4015970f6a43cd6bd81ff3816a41b563f54b..9702b1cf0aa7dc2cca0d4bdc984a974207ef74fc 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.109 2004/01/22 02:23:21 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.110 2004/03/17 20:48:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -306,6 +306,9 @@ CreateExprContext(EState *estate)
 	econtext->ecxt_aggvalues = NULL;
 	econtext->ecxt_aggnulls = NULL;
 
+	econtext->caseValue_datum = (Datum) 0;
+	econtext->caseValue_isNull = true;
+
 	econtext->domainValue_datum = (Datum) 0;
 	econtext->domainValue_isNull = true;
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c1c4ddfed8b0fa919d3aebd312c76e31c1c1ce6b..c7d6193280ed58ca17d02dcc853b5bbf0da0affb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.278 2004/03/11 01:47:35 ishii Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.279 2004/03/17 20:48:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -968,6 +968,20 @@ _copyCaseWhen(CaseWhen *from)
 	return newnode;
 }
 
+/*
+ * _copyCaseTestExpr
+ */
+static CaseTestExpr *
+_copyCaseTestExpr(CaseTestExpr *from)
+{
+	CaseTestExpr *newnode = makeNode(CaseTestExpr);
+
+	COPY_SCALAR_FIELD(typeId);
+	COPY_SCALAR_FIELD(typeMod);
+
+	return newnode;
+}
+
 /*
  * _copyArrayExpr
  */
@@ -2643,6 +2657,9 @@ copyObject(void *from)
 		case T_CaseWhen:
 			retval = _copyCaseWhen(from);
 			break;
+		case T_CaseTestExpr:
+			retval = _copyCaseTestExpr(from);
+			break;
 		case T_ArrayExpr:
 			retval = _copyArrayExpr(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index fadd02c935708768a0f6142b6a0cee24d98051b0..900d98dc8c0f8f88d4ff52b7e6029784a54a00d0 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.217 2004/03/14 23:41:26 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.218 2004/03/17 20:48:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -403,6 +403,15 @@ _equalCaseWhen(CaseWhen *a, CaseWhen *b)
 	return true;
 }
 
+static bool
+_equalCaseTestExpr(CaseTestExpr *a, CaseTestExpr *b)
+{
+	COMPARE_SCALAR_FIELD(typeId);
+	COMPARE_SCALAR_FIELD(typeMod);
+
+	return true;
+}
+
 static bool
 _equalArrayExpr(ArrayExpr *a, ArrayExpr *b)
 {
@@ -1724,6 +1733,9 @@ equal(void *a, void *b)
 		case T_CaseWhen:
 			retval = _equalCaseWhen(a, b);
 			break;
+		case T_CaseTestExpr:
+			retval = _equalCaseTestExpr(a, b);
+			break;
 		case T_ArrayExpr:
 			retval = _equalArrayExpr(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 93afd868f8751a72ea5b89bf07f9515f9bffe58f..4db6517cd766c7b1b5f83c5e4ef5f0f05a1087a4 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.232 2004/01/31 05:09:40 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.233 2004/03/17 20:48:42 tgl Exp $
  *
  * NOTES
  *	  Every node type that can appear in stored rules' parsetrees *must*
@@ -805,6 +805,15 @@ _outCaseWhen(StringInfo str, CaseWhen *node)
 	WRITE_NODE_FIELD(result);
 }
 
+static void
+_outCaseTestExpr(StringInfo str, CaseTestExpr *node)
+{
+	WRITE_NODE_TYPE("CASETESTEXPR");
+
+	WRITE_OID_FIELD(typeId);
+	WRITE_INT_FIELD(typeMod);
+}
+
 static void
 _outArrayExpr(StringInfo str, ArrayExpr *node)
 {
@@ -1701,6 +1710,9 @@ _outNode(StringInfo str, void *obj)
 			case T_CaseWhen:
 				_outCaseWhen(str, obj);
 				break;
+			case T_CaseTestExpr:
+				_outCaseTestExpr(str, obj);
+				break;
 			case T_ArrayExpr:
 				_outArrayExpr(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 93c71fd224775e28ee8b51616bfba178d65b410b..116345686bfa5b6fea581f94b83ea85587a9f82f 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.165 2004/01/14 23:01:55 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.166 2004/03/17 20:48:42 tgl Exp $
  *
  * NOTES
  *	  Path and Plan nodes do not have any readfuncs support, because we
@@ -648,6 +648,20 @@ _readCaseWhen(void)
 	READ_DONE();
 }
 
+/*
+ * _readCaseTestExpr
+ */
+static CaseTestExpr *
+_readCaseTestExpr(void)
+{
+	READ_LOCALS(CaseTestExpr);
+
+	READ_OID_FIELD(typeId);
+	READ_INT_FIELD(typeMod);
+
+	READ_DONE();
+}
+
 /*
  * _readArrayExpr
  */
@@ -1010,6 +1024,8 @@ parseNodeString(void)
 		return_value = _readCaseExpr();
 	else if (MATCH("WHEN", 4))
 		return_value = _readCaseWhen();
+	else if (MATCH("CASETESTEXPR", 12))
+		return_value = _readCaseTestExpr();
 	else if (MATCH("ARRAY", 5))
 		return_value = _readArrayExpr();
 	else if (MATCH("COALESCE", 8))
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 1f3a8afc7f44fa46059e503490b43c6aabe18d57..1487aec453d76deca5227bcc8210904396b4a93b 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.164 2004/03/14 23:41:27 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.165 2004/03/17 20:48:42 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -1397,15 +1397,29 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 		 * simplify the entire CASE to that alternative's expression.
 		 * If there are no non-FALSE alternatives, we simplify the entire
 		 * CASE to the default result (ELSE result).
+		 *
+		 * If we have a simple-form CASE with constant test expression and
+		 * one or more constant comparison expressions, we could run the
+		 * implied comparisons and potentially reduce those arms to constants.
+		 * This is not yet implemented, however.  At present, the
+		 * CaseTestExpr placeholder will always act as a non-constant node
+		 * and prevent the comparison boolean expressions from being reduced
+		 * to Const nodes.
 		 *----------
 		 */
 		CaseExpr   *caseexpr = (CaseExpr *) node;
 		CaseExpr   *newcase;
+		Node	   *newarg;
 		FastList	newargs;
 		Node	   *defresult;
 		Const	   *const_input;
 		List	   *arg;
 
+		/* Simplify the test expression, if any */
+		newarg = eval_const_expressions_mutator((Node *) caseexpr->arg,
+												active_fns);
+
+		/* Simplify the WHEN clauses */
 		FastListInit(&newargs);
 		foreach(arg, caseexpr->args)
 		{
@@ -1454,7 +1468,7 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 		/* Otherwise we need a new CASE node */
 		newcase = makeNode(CaseExpr);
 		newcase->casetype = caseexpr->casetype;
-		newcase->arg = NULL;
+		newcase->arg = (Expr *) newarg;
 		newcase->args = FastListValue(&newargs);
 		newcase->defresult = (Expr *) defresult;
 		return (Node *) newcase;
@@ -2319,6 +2333,7 @@ expression_tree_walker(Node *node,
 		case T_Const:
 		case T_Param:
 		case T_CoerceToDomainValue:
+		case T_CaseTestExpr:
 		case T_SetToDefault:
 		case T_RangeTblRef:
 			/* primitive node types with no subnodes */
@@ -2425,6 +2440,8 @@ expression_tree_walker(Node *node,
 			{
 				CaseExpr   *caseexpr = (CaseExpr *) node;
 
+				if (walker(caseexpr->arg, context))
+					return true;
 				/* we assume walker doesn't care about CaseWhens, either */
 				foreach(temp, caseexpr->args)
 				{
@@ -2436,9 +2453,6 @@ expression_tree_walker(Node *node,
 					if (walker(when->result, context))
 						return true;
 				}
-				/* caseexpr->arg should be null, but we'll check it anyway */
-				if (walker(caseexpr->arg, context))
-					return true;
 				if (walker(caseexpr->defresult, context))
 					return true;
 			}
@@ -2692,6 +2706,7 @@ expression_tree_mutator(Node *node,
 		case T_Const:
 		case T_Param:
 		case T_CoerceToDomainValue:
+		case T_CaseTestExpr:
 		case T_SetToDefault:
 		case T_RangeTblRef:
 			/* primitive node types with no subnodes */
@@ -2829,9 +2844,8 @@ expression_tree_mutator(Node *node,
 				CaseExpr   *newnode;
 
 				FLATCOPY(newnode, caseexpr, CaseExpr);
-				MUTATE(newnode->args, caseexpr->args, List *);
-				/* caseexpr->arg should be null, but we'll check it anyway */
 				MUTATE(newnode->arg, caseexpr->arg, Expr *);
+				MUTATE(newnode->args, caseexpr->args, List *);
 				MUTATE(newnode->defresult, caseexpr->defresult, Expr *);
 				return (Node *) newnode;
 			}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9fd2e5b5795e8d8726a01864658fd11e64def04a..e8abfe039f091ce53a3cd9e1759d86efd2d0d8a6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.448 2004/03/11 01:47:37 ishii Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.449 2004/03/17 20:48:42 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -6965,6 +6965,7 @@ in_expr:	select_with_parens
 case_expr:	CASE case_arg when_clause_list case_default END_P
 				{
 					CaseExpr *c = makeNode(CaseExpr);
+					c->casetype = InvalidOid; /* not analyzed yet */
 					c->arg = (Expr *) $2;
 					c->args = $3;
 					c->defresult = (Expr *) $4;
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index c23e39a25d0a1d0487ea9f7a55d2d91c8839c3f4..3881cc39538581bc530a011cb0d22124fe60ded4 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.165 2004/02/13 01:08:20 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.166 2004/03/17 20:48:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -637,14 +637,39 @@ transformExpr(ParseState *pstate, Node *expr)
 		case T_CaseExpr:
 			{
 				CaseExpr   *c = (CaseExpr *) expr;
-				CaseExpr   *newc = makeNode(CaseExpr);
-				List	   *newargs = NIL;
-				List	   *typeids = NIL;
+				CaseExpr   *newc;
+				Node	   *arg;
+				CaseTestExpr *placeholder;
+				List	   *newargs;
+				List	   *typeids;
 				List	   *args;
 				Node	   *defresult;
 				Oid			ptype;
 
+				/* If we already transformed this node, do nothing */
+				if (OidIsValid(c->casetype))
+				{
+					result = expr;
+					break;
+				}
+				newc = makeNode(CaseExpr);
+
+				/* transform the test expression, if any */
+				arg = transformExpr(pstate, (Node *) c->arg);
+				newc->arg = (Expr *) arg;
+				/* generate placeholder for test expression */
+				if (arg)
+				{
+					placeholder = makeNode(CaseTestExpr);
+					placeholder->typeId = exprType(arg);
+					placeholder->typeMod = exprTypmod(arg);
+				}
+				else
+					placeholder = NULL;
+
 				/* transform the list of arguments */
+				newargs = NIL;
+				typeids = NIL;
 				foreach(args, c->args)
 				{
 					CaseWhen   *w = (CaseWhen *) lfirst(args);
@@ -654,11 +679,11 @@ transformExpr(ParseState *pstate, Node *expr)
 					Assert(IsA(w, CaseWhen));
 
 					warg = (Node *) w->expr;
-					if (c->arg != NULL)
+					if (placeholder)
 					{
 						/* shorthand form was specified, so expand... */
 						warg = (Node *) makeSimpleA_Expr(AEXPR_OP, "=",
-														 (Node *) c->arg,
+														 (Node *) placeholder,
 														 warg);
 					}
 					neww->expr = (Expr *) transformExpr(pstate, warg);
@@ -667,18 +692,7 @@ transformExpr(ParseState *pstate, Node *expr)
 													 (Node *) neww->expr,
 															"CASE/WHEN");
 
-					/*
-					 * result is NULL for NULLIF() construct - thomas
-					 * 1998-11-11
-					 */
 					warg = (Node *) w->result;
-					if (warg == NULL)
-					{
-						A_Const    *n = makeNode(A_Const);
-
-						n->val.type = T_Null;
-						warg = (Node *) n;
-					}
 					neww->result = (Expr *) transformExpr(pstate, warg);
 
 					newargs = lappend(newargs, neww);
@@ -687,13 +701,6 @@ transformExpr(ParseState *pstate, Node *expr)
 
 				newc->args = newargs;
 
-				/*
-				 * It's not shorthand anymore, so drop the implicit
-				 * argument. This is necessary to keep any re-application
-				 * of transformExpr from doing the wrong thing.
-				 */
-				newc->arg = NULL;
-
 				/* transform the default clause */
 				defresult = (Node *) c->defresult;
 				if (defresult == NULL)
@@ -714,6 +721,7 @@ transformExpr(ParseState *pstate, Node *expr)
 				typeids = lconso(exprType((Node *) newc->defresult), typeids);
 
 				ptype = select_common_type(typeids, "CASE");
+				Assert(OidIsValid(ptype));
 				newc->casetype = ptype;
 
 				/* Convert default result clause, if necessary */
@@ -915,6 +923,7 @@ transformExpr(ParseState *pstate, Node *expr)
 		case T_BoolExpr:
 		case T_FieldSelect:
 		case T_RelabelType:
+		case T_CaseTestExpr:
 		case T_CoerceToDomain:
 		case T_CoerceToDomainValue:
 		case T_SetToDefault:
@@ -1288,6 +1297,9 @@ exprType(Node *expr)
 		case T_CaseWhen:
 			type = exprType((Node *) ((CaseWhen *) expr)->result);
 			break;
+		case T_CaseTestExpr:
+			type = ((CaseTestExpr *) expr)->typeId;
+			break;
 		case T_ArrayExpr:
 			type = ((ArrayExpr *) expr)->array_typeid;
 			break;
@@ -1408,6 +1420,8 @@ exprTypmod(Node *expr)
 				return typmod;
 			}
 			break;
+		case T_CaseTestExpr:
+			return ((CaseTestExpr *) expr)->typeMod;
 		case T_CoalesceExpr:
 			{
 				/*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7e1513796db032ba4c407e4ffebd406691226462..3960152f3876cdd97ea31eb62132ea04f30a692b 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3,7 +3,7 @@
  *				back to source text
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.162 2004/01/31 05:09:40 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.163 2004/03/17 20:48:42 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -2564,7 +2564,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
 				case T_ArrayExpr:		/* other separators */
 				case T_CoalesceExpr:	/* own parentheses */
 				case T_NullIfExpr:		/* other separators */
-				case T_Aggref:	/* own parentheses */
+				case T_Aggref:			/* own parentheses */
 				case T_CaseExpr:		/* other separators */
 					return true;
 				default:
@@ -2610,7 +2610,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
 				case T_ArrayExpr:		/* other separators */
 				case T_CoalesceExpr:	/* own parentheses */
 				case T_NullIfExpr:		/* other separators */
-				case T_Aggref:	/* own parentheses */
+				case T_Aggref:			/* own parentheses */
 				case T_CaseExpr:		/* other separators */
 					return true;
 				default:
@@ -3026,6 +3026,11 @@ get_rule_expr(Node *node, deparse_context *context,
 
 				appendContextKeyword(context, "CASE",
 									 0, PRETTYINDENT_VAR, 0);
+				if (caseexpr->arg)
+				{
+					appendStringInfoChar(buf, ' ');
+					get_rule_expr((Node *) caseexpr->arg, context, true);
+				}
 				foreach(temp, caseexpr->args)
 				{
 					CaseWhen   *when = (CaseWhen *) lfirst(temp);
@@ -3034,7 +3039,17 @@ get_rule_expr(Node *node, deparse_context *context,
 						appendStringInfoChar(buf, ' ');
 					appendContextKeyword(context, "WHEN ",
 										 0, 0, 0);
-					get_rule_expr((Node *) when->expr, context, false);
+					if (caseexpr->arg)
+					{
+						/* Show only the RHS of "CaseTestExpr = RHS" */
+						Node   *rhs;
+
+						Assert(IsA(when->expr, OpExpr));
+						rhs = (Node *) lsecond(((OpExpr *) when->expr)->args);
+						get_rule_expr(rhs, context, false);
+					}
+					else
+						get_rule_expr((Node *) when->expr, context, false);
 					appendStringInfo(buf, " THEN ");
 					get_rule_expr((Node *) when->result, context, true);
 				}
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 8f30e0bf5949400155d1af5de251b4c4d6a45e92..2d91191c49b9f322000dc6584cdeae3b2f6c6c6d 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.220 2004/03/15 01:13:41 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.221 2004/03/17 20:48:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200403141
+#define CATALOG_VERSION_NO	200403171
 
 #endif
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index afdd0669d40de0e7918532478b16a928aba989c1..440fcc4d576d7035b982e3c9114edb84e3acf8b6 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.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/nodes/execnodes.h,v 1.113 2004/03/17 01:02:24 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.114 2004/03/17 20:48:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -113,6 +113,10 @@ typedef struct ExprContext
 	Datum	   *ecxt_aggvalues; /* precomputed values for Aggref nodes */
 	bool	   *ecxt_aggnulls;	/* null flags for Aggref nodes */
 
+	/* Value to substitute for CaseTestExpr nodes in expression */
+	Datum		caseValue_datum;
+	bool		caseValue_isNull;
+
 	/* Value to substitute for CoerceToDomainValue nodes in expression */
 	Datum		domainValue_datum;
 	bool		domainValue_isNull;
@@ -566,6 +570,7 @@ typedef struct SubPlanState
 typedef struct CaseExprState
 {
 	ExprState	xprstate;
+	ExprState  *arg;			/* implicit equality comparison argument */
 	List	   *args;			/* the arguments (list of WHEN clauses) */
 	ExprState  *defresult;		/* the default result (ELSE clause) */
 } CaseExprState;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index ced6f6cb432ee0dbe8f90118d944e7cc524dd66e..5c34b8d8559a3042790b3b3107bf9e141a99c4ea 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.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/nodes/nodes.h,v 1.150 2004/01/07 18:43:36 neilc Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.151 2004/03/17 20:48:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -113,6 +113,7 @@ typedef enum NodeTag
 	T_RelabelType,
 	T_CaseExpr,
 	T_CaseWhen,
+	T_CaseTestExpr,
 	T_ArrayExpr,
 	T_CoalesceExpr,
 	T_NullIfExpr,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 065ca656a7f5ae91237786397512db7147abdd64..d94196d740069b2507e083147ec9f6eee58808d6 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.95 2004/03/14 23:41:27 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.96 2004/03/17 20:48:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -563,8 +563,27 @@ typedef struct RelabelType
 	CoercionForm relabelformat; /* how to display this node */
 } RelabelType;
 
-/*
+/*----------
  * CaseExpr - a CASE expression
+ *
+ * We support two distinct forms of CASE expression:
+ *		CASE WHEN boolexpr THEN expr [ WHEN boolexpr THEN expr ... ]
+ *		CASE testexpr WHEN compexpr THEN expr [ WHEN compexpr THEN expr ... ]
+ * These are distinguishable by the "arg" field being NULL in the first case
+ * and the testexpr in the second case.
+ *
+ * In the raw grammar output for the second form, the condition expressions
+ * of the WHEN clauses are just the comparison values.  Parse analysis
+ * converts these to valid boolean expressions of the form
+ *		CaseTestExpr '=' compexpr
+ * where the CaseTestExpr node is a placeholder that emits the correct
+ * value at runtime.  This structure is used so that the testexpr need be
+ * evaluated only once.  Note that after parse analysis, the condition
+ * expressions always yield boolean.
+ *
+ * Note: we can test whether a CaseExpr has been through parse analysis
+ * yet by checking whether casetype is InvalidOid or not.
+ *----------
  */
 typedef struct CaseExpr
 {
@@ -576,7 +595,7 @@ typedef struct CaseExpr
 } CaseExpr;
 
 /*
- * CaseWhen - an argument to a CASE expression
+ * CaseWhen - one arm of a CASE expression
  */
 typedef struct CaseWhen
 {
@@ -585,6 +604,18 @@ typedef struct CaseWhen
 	Expr	   *result;			/* substitution result */
 } CaseWhen;
 
+/*
+ * Placeholder node for the test value to be processed by a CASE expression.
+ * This is effectively like a Param, but can be implemented more simply
+ * since we need only one replacement value at a time.
+ */
+typedef struct CaseTestExpr
+{
+	Expr		xpr;
+	Oid			typeId;			/* type for substituted value */
+	int32		typeMod;		/* typemod for substituted value */
+} CaseTestExpr;
+
 /*
  * ArrayExpr - an ARRAY[] expression
  *
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 0c409c0e64e74e3b0c818b6c76453fda9b4b4520..b984f0932bbf55a5a459eaaf1e42d30d937651b7 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.97 2004/02/25 18:10:51 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.98 2004/03/17 20:48:43 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -3729,6 +3729,9 @@ exec_simple_check_node(Node *node)
 				return TRUE;
 			}
 
+		case T_CaseTestExpr:
+			return TRUE;
+
 		case T_ArrayExpr:
 			{
 				ArrayExpr  *expr = (ArrayExpr *) node;
@@ -3770,6 +3773,9 @@ exec_simple_check_node(Node *node)
 		case T_CoerceToDomain:
 			return exec_simple_check_node((Node *) ((CoerceToDomain *) node)->arg);
 
+		case T_CoerceToDomainValue:
+			return TRUE;
+
 		case T_List:
 			{
 				List	   *expr = (List *) node;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 902ff4472f6d8e4d9041cac3f9c391a44978f1b4..cf0f63833149b9e1069f0ada1416ce5860652259 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1272,8 +1272,8 @@ drop table cchild;
 -- Check that ruleutils are working
 --
 SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schema' ORDER BY viewname;
-         viewname         |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      definition                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
---------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+         viewname         |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          definition                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
+--------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  iexit                    | SELECT ih.name, ih.thepath, interpt_pp(ih.thepath, r.thepath) AS exit FROM ihighway ih, ramp r WHERE (ih.thepath ## r.thepath);
  pg_indexes               | SELECT n.nspname AS schemaname, c.relname AS tablename, i.relname AS indexname, pg_get_indexdef(i.oid) AS indexdef FROM (((pg_index x JOIN pg_class c ON ((c.oid = x.indrelid))) JOIN pg_class i ON ((i.oid = x.indexrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE ((c.relkind = 'r'::"char") AND (i.relkind = 'i'::"char"));
  pg_locks                 | SELECT l.relation, l."database", l."transaction", l.pid, l."mode", l.granted FROM pg_lock_status() l(relation oid, "database" oid, "transaction" xid, pid integer, "mode" text, granted boolean);
@@ -1296,7 +1296,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem
  pg_statio_user_indexes   | SELECT pg_statio_all_indexes.relid, pg_statio_all_indexes.indexrelid, pg_statio_all_indexes.schemaname, pg_statio_all_indexes.relname, pg_statio_all_indexes.indexrelname, pg_statio_all_indexes.idx_blks_read, pg_statio_all_indexes.idx_blks_hit FROM pg_statio_all_indexes WHERE ((pg_statio_all_indexes.schemaname <> 'pg_catalog'::name) AND (pg_statio_all_indexes.schemaname <> 'pg_toast'::name));
  pg_statio_user_sequences | SELECT pg_statio_all_sequences.relid, pg_statio_all_sequences.schemaname, pg_statio_all_sequences.relname, pg_statio_all_sequences.blks_read, pg_statio_all_sequences.blks_hit FROM pg_statio_all_sequences WHERE ((pg_statio_all_sequences.schemaname <> 'pg_catalog'::name) AND (pg_statio_all_sequences.schemaname <> 'pg_toast'::name));
  pg_statio_user_tables    | SELECT pg_statio_all_tables.relid, pg_statio_all_tables.schemaname, pg_statio_all_tables.relname, pg_statio_all_tables.heap_blks_read, pg_statio_all_tables.heap_blks_hit, pg_statio_all_tables.idx_blks_read, pg_statio_all_tables.idx_blks_hit, pg_statio_all_tables.toast_blks_read, pg_statio_all_tables.toast_blks_hit, pg_statio_all_tables.tidx_blks_read, pg_statio_all_tables.tidx_blks_hit FROM pg_statio_all_tables WHERE ((pg_statio_all_tables.schemaname <> 'pg_catalog'::name) AND (pg_statio_all_tables.schemaname <> 'pg_toast'::name));
- pg_stats                 | SELECT nspname AS schemaname, relname AS tablename, attname, stanullfrac AS null_frac, stawidth AS avg_width, stadistinct AS n_distinct, CASE WHEN (1 = stakind1) THEN stavalues1 WHEN (1 = stakind2) THEN stavalues2 WHEN (1 = stakind3) THEN stavalues3 WHEN (1 = stakind4) THEN stavalues4 ELSE NULL::"unknown" END AS most_common_vals, CASE WHEN (1 = stakind1) THEN stanumbers1 WHEN (1 = stakind2) THEN stanumbers2 WHEN (1 = stakind3) THEN stanumbers3 WHEN (1 = stakind4) THEN stanumbers4 ELSE NULL::real[] END AS most_common_freqs, CASE WHEN (2 = stakind1) THEN stavalues1 WHEN (2 = stakind2) THEN stavalues2 WHEN (2 = stakind3) THEN stavalues3 WHEN (2 = stakind4) THEN stavalues4 ELSE NULL::"unknown" END AS histogram_bounds, CASE WHEN (3 = stakind1) THEN stanumbers1[1] WHEN (3 = stakind2) THEN stanumbers2[1] WHEN (3 = stakind3) THEN stanumbers3[1] WHEN (3 = stakind4) THEN stanumbers4[1] ELSE NULL::real END AS correlation FROM (((pg_statistic s JOIN pg_class c ON ((c.oid = s.starelid))) JOIN pg_attribute a ON (((c.oid = a.attrelid) AND (a.attnum = s.staattnum)))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE has_table_privilege(c.oid, 'select'::text);
+ pg_stats                 | SELECT nspname AS schemaname, relname AS tablename, attname, stanullfrac AS null_frac, stawidth AS avg_width, stadistinct AS n_distinct, CASE 1 WHEN stakind1 THEN stavalues1 WHEN stakind2 THEN stavalues2 WHEN stakind3 THEN stavalues3 WHEN stakind4 THEN stavalues4 ELSE NULL::"unknown" END AS most_common_vals, CASE 1 WHEN stakind1 THEN stanumbers1 WHEN stakind2 THEN stanumbers2 WHEN stakind3 THEN stanumbers3 WHEN stakind4 THEN stanumbers4 ELSE NULL::real[] END AS most_common_freqs, CASE 2 WHEN stakind1 THEN stavalues1 WHEN stakind2 THEN stavalues2 WHEN stakind3 THEN stavalues3 WHEN stakind4 THEN stavalues4 ELSE NULL::"unknown" END AS histogram_bounds, CASE 3 WHEN stakind1 THEN stanumbers1[1] WHEN stakind2 THEN stanumbers2[1] WHEN stakind3 THEN stanumbers3[1] WHEN stakind4 THEN stanumbers4[1] ELSE NULL::real END AS correlation FROM (((pg_statistic s JOIN pg_class c ON ((c.oid = s.starelid))) JOIN pg_attribute a ON (((c.oid = a.attrelid) AND (a.attnum = s.staattnum)))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE has_table_privilege(c.oid, 'select'::text);
  pg_tables                | SELECT n.nspname AS schemaname, c.relname AS tablename, pg_get_userbyid(c.relowner) AS tableowner, c.relhasindex AS hasindexes, c.relhasrules AS hasrules, (c.reltriggers > 0) AS hastriggers FROM (pg_class c LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind = 'r'::"char");
  pg_user                  | SELECT pg_shadow.usename, pg_shadow.usesysid, pg_shadow.usecreatedb, pg_shadow.usesuper, pg_shadow.usecatupd, '********'::text AS passwd, pg_shadow.valuntil, pg_shadow.useconfig FROM pg_shadow;
  pg_views                 | SELECT n.nspname AS schemaname, c.relname AS viewname, pg_get_userbyid(c.relowner) AS viewowner, pg_get_viewdef(c.oid) AS definition FROM (pg_class c LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind = 'v'::"char");