diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8c7a48308ec6e2ac979ebb34a14cd7f8e299a261..d7430f1ccf8053623d8d15eecfabd4e42b31e051 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.259 2005/06/24 20:53:29 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.260 2005/06/26 22:05:35 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -6901,6 +6901,39 @@ SELECT NULLIF(value, '(none)') ...
 
   </sect2>
 
+  <sect2>
+   <title><literal>GREATEST</literal> and <literal>LEAST</literal></title>
+
+  <indexterm>
+   <primary>GREATEST</primary>
+  </indexterm>
+  <indexterm>
+   <primary>LEAST</primary>
+  </indexterm>
+
+<synopsis>
+<function>GREATEST</function>(<replaceable>value</replaceable> <optional>, ...</optional>)
+</synopsis>
+<synopsis>
+<function>LEAST</function>(<replaceable>value</replaceable> <optional>, ...</optional>)
+</synopsis>
+
+   <para>
+    The <function>GREATEST</> and <function>LEAST</> functions select the
+    largest or smallest value from a list of any number of expressions.
+    The expressions must all be convertible to a common data type, which
+    will be the type of the result
+    (see <xref linkend="typeconv-union-case"> for details).  NULL values
+    in the list are ignored.  The result will be NULL only if all the
+    expressions evaluate to NULL.
+   </para>
+
+   <para>
+    Note that <function>GREATEST</> and <function>LEAST</> are not in
+    the SQL standard, but are a common extension.
+   </para>
+  </sect2>
+
  </sect1>
 
 
diff --git a/doc/src/sgml/typeconv.sgml b/doc/src/sgml/typeconv.sgml
index ae0e3aea4d79fb60143faf341b6b7de6474ed72a..86e0561acc0533c06e0356c6761a4a38a3d866cd 100644
--- a/doc/src/sgml/typeconv.sgml
+++ b/doc/src/sgml/typeconv.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/typeconv.sgml,v 1.43 2004/12/23 23:07:38 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/typeconv.sgml,v 1.44 2005/06/26 22:05:36 tgl Exp $
 -->
 
 <chapter Id="typeconv">
@@ -120,7 +120,7 @@ with, and perhaps converted to, the types of the target columns.
 </varlistentry>
 <varlistentry>
 <term>
-<literal>UNION</literal>, <literal>CASE</literal>, and <literal>ARRAY</literal> constructs
+<literal>UNION</literal>, <literal>CASE</literal>, and related constructs
 </term>
 <listitem>
 <para>
@@ -129,7 +129,8 @@ must appear in a single set of columns, the types of the results of each
 <command>SELECT</> clause must be matched up and converted to a uniform set.
 Similarly, the result expressions of a <literal>CASE</> construct must be
 converted to a common type so that the <literal>CASE</> expression as a whole
-has a known output type.  The same holds for <literal>ARRAY</> constructs.
+has a known output type.  The same holds for <literal>ARRAY</> constructs,
+and for the <function>GREATEST</> and <function>LEAST</> functions.
 </para>
 </listitem>
 </varlistentry>
@@ -782,7 +783,7 @@ padding spaces.
 </sect1>
 
 <sect1 id="typeconv-union-case">
-<title><literal>UNION</literal>, <literal>CASE</literal>, and <literal>ARRAY</literal> Constructs</title>
+<title><literal>UNION</literal>, <literal>CASE</literal>, and Related Constructs</title>
 
 <indexterm zone="typeconv-union-case">
  <primary>UNION</primary>
@@ -799,20 +800,31 @@ padding spaces.
  <secondary>determination of result type</secondary>
 </indexterm>
 
+<indexterm zone="typeconv-union-case">
+ <primary>GREATEST</primary>
+ <secondary>determination of result type</secondary>
+</indexterm>
+
+<indexterm zone="typeconv-union-case">
+ <primary>LEAST</primary>
+ <secondary>determination of result type</secondary>
+</indexterm>
+
 <para>
 SQL <literal>UNION</> constructs must match up possibly dissimilar
 types to become a single result set.  The resolution algorithm is
 applied separately to each output column of a union query.  The
 <literal>INTERSECT</> and <literal>EXCEPT</> constructs resolve
 dissimilar types in the same way as <literal>UNION</>.  The
-<literal>CASE</> and <literal>ARRAY</> constructs use the identical
+<literal>CASE</>, <literal>ARRAY</>, <function>GREATEST</> and
+<function>LEAST</> constructs use the identical
 algorithm to match up their component expressions and select a result
 data type.
 </para>
 
 <procedure>
-<title><literal>UNION</literal>, <literal>CASE</literal>, and
-<literal>ARRAY</literal> Type Resolution</title>
+<title>Type Resolution for <literal>UNION</literal>, <literal>CASE</literal>,
+and Related Constructs</title>
 
 <step performance="required">
 <para>
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 1cbe70571a9764c3a011a70037957e4997780884..87fcf53bf05d7aa74eaddfe6932013a68fba3197 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.179 2005/05/12 20:41:56 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.180 2005/06/26 22:05:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -105,6 +105,9 @@ static Datum ExecEvalRow(RowExprState *rstate,
 static Datum ExecEvalCoalesce(CoalesceExprState *coalesceExpr,
 				 ExprContext *econtext,
 				 bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalMinMax(MinMaxExprState *minmaxExpr,
+							ExprContext *econtext,
+							bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalNullIf(FuncExprState *nullIfExpr,
 			   ExprContext *econtext,
 			   bool *isNull, ExprDoneCond *isDone);
@@ -2247,6 +2250,63 @@ ExecEvalCoalesce(CoalesceExprState *coalesceExpr, ExprContext *econtext,
 	return (Datum) 0;
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalMinMax
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalMinMax(MinMaxExprState *minmaxExpr, ExprContext *econtext,
+			   bool *isNull, ExprDoneCond *isDone)
+{
+	Datum result = (Datum) 0;
+	MinMaxOp	op = ((MinMaxExpr *) minmaxExpr->xprstate.expr)->op;
+	FunctionCallInfoData locfcinfo;
+	ListCell *arg;
+
+	if (isDone)
+		*isDone = ExprSingleResult;
+	*isNull = true;				/* until we get a result */
+
+	InitFunctionCallInfoData(locfcinfo, &minmaxExpr->cfunc, 2, NULL, NULL);
+	locfcinfo.argnull[0] = false;
+	locfcinfo.argnull[1] = false;
+
+	foreach(arg, minmaxExpr->args)
+	{
+		ExprState  *e = (ExprState *) lfirst(arg);
+		Datum		value;
+		bool		valueIsNull;
+		int32		cmpresult;
+
+		value = ExecEvalExpr(e, econtext, &valueIsNull, NULL);
+		if (valueIsNull)
+			continue;			/* ignore NULL inputs */
+
+		if (*isNull)
+		{
+			/* first nonnull input, adopt value */
+			result = value;
+			*isNull = false;
+		}
+		else
+		{
+			/* apply comparison function */
+			locfcinfo.arg[0] = result;
+			locfcinfo.arg[1] = value;
+			locfcinfo.isnull = false;
+			cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo));
+			if (locfcinfo.isnull) /* probably should not happen */
+				continue;
+			if (cmpresult > 0 && op == IS_LEAST)
+				result = value;
+			else if (cmpresult < 0 && op == IS_GREATEST)
+				result = value;
+		}
+	}
+
+	return result;
+}
+
 /* ----------------------------------------------------------------
  *		ExecEvalNullIf
  *
@@ -3206,6 +3266,36 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				state = (ExprState *) cstate;
 			}
 			break;
+		case T_MinMaxExpr:
+			{
+				MinMaxExpr *minmaxexpr = (MinMaxExpr *) node;
+				MinMaxExprState *mstate = makeNode(MinMaxExprState);
+				List	   *outlist = NIL;
+				ListCell   *l;
+				TypeCacheEntry *typentry;
+
+				mstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalMinMax;
+				foreach(l, minmaxexpr->args)
+				{
+					Expr	   *e = (Expr *) lfirst(l);
+					ExprState  *estate;
+
+					estate = ExecInitExpr(e, parent);
+					outlist = lappend(outlist, estate);
+				}
+				mstate->args = outlist;
+				/* Look up the btree comparison function for the datatype */
+				typentry = lookup_type_cache(minmaxexpr->minmaxtype,
+											 TYPECACHE_CMP_PROC);
+				if (!OidIsValid(typentry->cmp_proc))
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_FUNCTION),
+							 errmsg("could not identify a comparison function for type %s",
+									format_type_be(minmaxexpr->minmaxtype))));
+				fmgr_info(typentry->cmp_proc, &(mstate->cfunc));
+				state = (ExprState *) mstate;
+			}
+			break;
 		case T_NullIfExpr:
 			{
 				NullIfExpr *nullifexpr = (NullIfExpr *) node;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2ce9edac7079498b8653879a84c44fe14704387f..b6885f0d14932b0e74e2aa3a824b44aaf078ed18 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.308 2005/06/22 21:14:29 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.309 2005/06/26 22:05:37 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1047,6 +1047,21 @@ _copyCoalesceExpr(CoalesceExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyMinMaxExpr
+ */
+static MinMaxExpr *
+_copyMinMaxExpr(MinMaxExpr *from)
+{
+	MinMaxExpr *newnode = makeNode(MinMaxExpr);
+
+	COPY_SCALAR_FIELD(minmaxtype);
+	COPY_SCALAR_FIELD(op);
+	COPY_NODE_FIELD(args);
+
+	return newnode;
+}
+
 /*
  * _copyNullIfExpr (same as OpExpr)
  */
@@ -2805,6 +2820,9 @@ copyObject(void *from)
 		case T_CoalesceExpr:
 			retval = _copyCoalesceExpr(from);
 			break;
+		case T_MinMaxExpr:
+			retval = _copyMinMaxExpr(from);
+			break;
 		case T_NullIfExpr:
 			retval = _copyNullIfExpr(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e66ac81b7550bd1a42811ab7b05f413c77e9c905..31a2c302244a20d8394b6e65a4865bdb31b71c9a 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.245 2005/06/22 21:14:29 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.246 2005/06/26 22:05:37 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -450,6 +450,16 @@ _equalCoalesceExpr(CoalesceExpr *a, CoalesceExpr *b)
 	return true;
 }
 
+static bool
+_equalMinMaxExpr(MinMaxExpr *a, MinMaxExpr *b)
+{
+	COMPARE_SCALAR_FIELD(minmaxtype);
+	COMPARE_SCALAR_FIELD(op);
+	COMPARE_NODE_FIELD(args);
+
+	return true;
+}
+
 static bool
 _equalNullIfExpr(NullIfExpr *a, NullIfExpr *b)
 {
@@ -1868,6 +1878,9 @@ equal(void *a, void *b)
 		case T_CoalesceExpr:
 			retval = _equalCoalesceExpr(a, b);
 			break;
+		case T_MinMaxExpr:
+			retval = _equalMinMaxExpr(a, b);
+			break;
 		case T_NullIfExpr:
 			retval = _equalNullIfExpr(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2be5e1d98f6a30411d4e699b6c47cbc981bf9a6f..91705123bd7959c8db8f0e8a4950a674c3716972 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.255 2005/06/09 04:18:58 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.256 2005/06/26 22:05:37 tgl Exp $
  *
  * NOTES
  *	  Every node type that can appear in stored rules' parsetrees *must*
@@ -864,6 +864,16 @@ _outCoalesceExpr(StringInfo str, CoalesceExpr *node)
 	WRITE_NODE_FIELD(args);
 }
 
+static void
+_outMinMaxExpr(StringInfo str, MinMaxExpr *node)
+{
+	WRITE_NODE_TYPE("MINMAX");
+
+	WRITE_OID_FIELD(minmaxtype);
+	WRITE_ENUM_FIELD(op, MinMaxOp);
+	WRITE_NODE_FIELD(args);
+}
+
 static void
 _outNullIfExpr(StringInfo str, NullIfExpr *node)
 {
@@ -1896,6 +1906,9 @@ _outNode(StringInfo str, void *obj)
 			case T_CoalesceExpr:
 				_outCoalesceExpr(str, obj);
 				break;
+			case T_MinMaxExpr:
+				_outMinMaxExpr(str, obj);
+				break;
 			case T_NullIfExpr:
 				_outNullIfExpr(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index e01646a71d13e61baf54a926450160c69011f4e8..9b27dc478ef8c42268104ccc177ad0300c23d7f1 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.178 2005/06/05 22:32:54 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.179 2005/06/26 22:05:37 tgl Exp $
  *
  * NOTES
  *	  Path and Plan nodes do not have any readfuncs support, because we
@@ -658,6 +658,21 @@ _readCoalesceExpr(void)
 	READ_DONE();
 }
 
+/*
+ * _readMinMaxExpr
+ */
+static MinMaxExpr *
+_readMinMaxExpr(void)
+{
+	READ_LOCALS(MinMaxExpr);
+
+	READ_OID_FIELD(minmaxtype);
+	READ_ENUM_FIELD(op, MinMaxOp);
+	READ_NODE_FIELD(args);
+
+	READ_DONE();
+}
+
 /*
  * _readNullIfExpr
  */
@@ -982,6 +997,8 @@ parseNodeString(void)
 		return_value = _readRowExpr();
 	else if (MATCH("COALESCE", 8))
 		return_value = _readCoalesceExpr();
+	else if (MATCH("MINMAX", 6))
+		return_value = _readMinMaxExpr();
 	else if (MATCH("NULLIFEXPR", 10))
 		return_value = _readNullIfExpr();
 	else if (MATCH("NULLTEST", 8))
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 5663cce6e8d3c6ef39465d2962a8cd1f37147c15..38291be27af0e904ef1aee61c7e6032536406608 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.198 2005/06/05 22:32:56 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.199 2005/06/26 22:05:38 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -542,6 +542,8 @@ expression_returns_set_walker(Node *node, void *context)
 		return false;
 	if (IsA(node, CoalesceExpr))
 		return false;
+	if (IsA(node, MinMaxExpr))
+		return false;
 	if (IsA(node, NullIfExpr))
 		return false;
 
@@ -847,6 +849,8 @@ contain_nonstrict_functions_walker(Node *node, void *context)
 		return true;
 	if (IsA(node, CoalesceExpr))
 		return true;
+	if (IsA(node, MinMaxExpr))
+		return true;
 	if (IsA(node, NullIfExpr))
 		return true;
 	if (IsA(node, NullTest))
@@ -1685,7 +1689,7 @@ eval_const_expressions_mutator(Node *node,
 				newargs = lappend(newargs, newcasewhen);
 				continue;
 			}
-  
+
 			/*
 			 * Found a TRUE condition, so none of the remaining alternatives
 			 * can be reached.  We treat the result as the default result.
@@ -2932,6 +2936,8 @@ expression_tree_walker(Node *node,
 			return walker(((RowExpr *) node)->args, context);
 		case T_CoalesceExpr:
 			return walker(((CoalesceExpr *) node)->args, context);
+		case T_MinMaxExpr:
+			return walker(((MinMaxExpr *) node)->args, context);
 		case T_NullIfExpr:
 			return walker(((NullIfExpr *) node)->args, context);
 		case T_NullTest:
@@ -3392,6 +3398,16 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_MinMaxExpr:
+			{
+				MinMaxExpr *minmaxexpr = (MinMaxExpr *) node;
+				MinMaxExpr *newnode;
+
+				FLATCOPY(newnode, minmaxexpr, MinMaxExpr);
+				MUTATE(newnode->args, minmaxexpr->args, List *);
+				return (Node *) newnode;
+			}
+			break;
 		case T_NullIfExpr:
 			{
 				NullIfExpr *expr = (NullIfExpr *) node;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 4ad61ab409a3c068c68c52c08dfaa50b8197d1f5..28e1ac0264d1642344004743960a6e8f68b09836 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.497 2005/06/24 14:28:06 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.498 2005/06/26 22:05:38 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -360,7 +360,7 @@ static void doNegateFloat(Value *v);
 	FALSE_P FETCH FIRST_P FLOAT_P FOR FORCE FOREIGN FORWARD
 	FREEZE FROM FULL FUNCTION
 
-	GLOBAL GRANT GROUP_P
+	GLOBAL GRANT GREATEST GROUP_P
 
 	HANDLER HAVING HEADER HOLD HOUR_P
 
@@ -373,8 +373,8 @@ static void doNegateFloat(Value *v);
 
 	KEY
 
-	LANCOMPILER LANGUAGE LARGE_P LAST_P LEADING LEFT LEVEL LIKE LIMIT
-	LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
+	LANCOMPILER LANGUAGE LARGE_P  LAST_P LEADING LEAST LEFT LEVEL
+	LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
 	LOCK_P
 
 	MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -1154,7 +1154,7 @@ AlterTableStmt:
 					AlterTableStmt *n = makeNode(AlterTableStmt);
 					n->relation = $3;
 					n->cmds = $4;
-					n->relkind = OBJECT_TABLE; 
+					n->relkind = OBJECT_TABLE;
 					$$ = (Node *)n;
 				}
 		|	ALTER INDEX relation_expr alter_rel_cmds
@@ -3821,7 +3821,7 @@ opt_column: COLUMN									{ $$ = COLUMN; }
 
 /*****************************************************************************
  *
- * ALTER THING name OWNER TO newname.  
+ * ALTER THING name OWNER TO newname.
  *
  *****************************************************************************/
 
@@ -6373,7 +6373,7 @@ a_expr:		c_expr									{ $$ = $1; }
 						    (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", $1, $6)),
 						(Node *) makeA_Expr(AEXPR_AND, NIL,
 						    (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", $1, $6),
-						    (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", $1, $4)));					
+						    (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", $1, $4)));
 				}
 			| a_expr NOT BETWEEN SYMMETRIC b_expr AND b_expr		%prec BETWEEN
 				{
@@ -6383,7 +6383,7 @@ a_expr:		c_expr									{ $$ = $1; }
 						    (Node *) makeSimpleA_Expr(AEXPR_OP, ">", $1, $7)),
 						(Node *) makeA_Expr(AEXPR_OR, NIL,
 						    (Node *) makeSimpleA_Expr(AEXPR_OP, "<", $1, $7),
-						    (Node *) makeSimpleA_Expr(AEXPR_OP, ">", $1, $5)));					
+						    (Node *) makeSimpleA_Expr(AEXPR_OP, ">", $1, $5)));
 				}
 			| a_expr IN_P in_expr
 				{
@@ -7065,6 +7065,20 @@ func_expr:	func_name '(' ')'
 					c->args = $3;
 					$$ = (Node *)c;
 				}
+			| GREATEST '(' expr_list ')'
+				{
+					MinMaxExpr *v = makeNode(MinMaxExpr);
+					v->args = $3;
+					v->op = IS_GREATEST;
+					$$ = (Node *)v;
+				}
+			| LEAST '(' expr_list ')'
+				{
+					MinMaxExpr *v = makeNode(MinMaxExpr);
+					v->args = $3;
+					v->op = IS_LEAST;
+					$$ = (Node *)v;
+				}
 		;
 
 /*
@@ -7944,10 +7958,12 @@ col_name_keyword:
 			| EXISTS
 			| EXTRACT
 			| FLOAT_P
+			| GREATEST
 			| INOUT
 			| INT_P
 			| INTEGER
 			| INTERVAL
+			| LEAST
 			| NATIONAL
 			| NCHAR
 			| NONE
diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c
index 009cc2e7ba6df1dd67e16bf165d9cfee85b843e2..41e45bc7475d71489acf085c871581ec743c05eb 100644
--- a/src/backend/parser/keywords.c
+++ b/src/backend/parser/keywords.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.158 2005/06/22 21:14:30 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.159 2005/06/26 22:05:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -146,6 +146,7 @@ static const ScanKeyword ScanKeywords[] = {
 	{"function", FUNCTION},
 	{"global", GLOBAL},
 	{"grant", GRANT},
+	{"greatest", GREATEST},
 	{"group", GROUP_P},
 	{"handler", HANDLER},
 	{"having", HAVING},
@@ -184,6 +185,7 @@ static const ScanKeyword ScanKeywords[] = {
 	{"large", LARGE_P},
 	{"last", LAST_P},
 	{"leading", LEADING},
+	{"least", LEAST},
 	{"left", LEFT},
 	{"level", LEVEL},
 	{"like", LIKE},
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6d5f21b52ef8313b0a857b5addd3a1844c253749..fdb4c4dcf2554b502b5567a91d016b2bb976be3f 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.183 2005/06/04 20:56:13 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.184 2005/06/26 22:05:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,7 @@ static Node *transformSubLink(ParseState *pstate, SubLink *sublink);
 static Node *transformArrayExpr(ParseState *pstate, ArrayExpr *a);
 static Node *transformRowExpr(ParseState *pstate, RowExpr *r);
 static Node *transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c);
+static Node *transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m);
 static Node *transformBooleanTest(ParseState *pstate, BooleanTest *b);
 static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
 static Node *transformWholeRowRef(ParseState *pstate, char *schemaname,
@@ -209,6 +210,10 @@ transformExpr(ParseState *pstate, Node *expr)
 			result = transformCoalesceExpr(pstate, (CoalesceExpr *) expr);
 			break;
 
+		case T_MinMaxExpr:
+			result = transformMinMaxExpr(pstate, (MinMaxExpr *) expr);
+			break;
+
 		case T_NullTest:
 			{
 				NullTest   *n = (NullTest *) expr;
@@ -1229,6 +1234,44 @@ transformCoalesceExpr(ParseState *pstate, CoalesceExpr *c)
 	return (Node *) newc;
 }
 
+static Node *
+transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m)
+{
+	MinMaxExpr *newm = makeNode(MinMaxExpr);
+	List	   *newargs = NIL;
+	List	   *newcoercedargs = NIL;
+	List	   *typeids = NIL;
+	ListCell   *args;
+
+	newm->op = m->op;
+	foreach(args, m->args)
+	{
+		Node	   *e = (Node *) lfirst(args);
+		Node	   *newe;
+
+		newe = transformExpr(pstate, e);
+		newargs = lappend(newargs, newe);
+		typeids = lappend_oid(typeids, exprType(newe));
+	}
+
+	newm->minmaxtype = select_common_type(typeids, "GREATEST/LEAST");
+
+	/* Convert arguments if necessary */
+	foreach(args, newargs)
+	{
+		Node	   *e = (Node *) lfirst(args);
+		Node	   *newe;
+
+		newe = coerce_to_common_type(pstate, e,
+									 newm->minmaxtype,
+									 "GREATEST/LEAST");
+		newcoercedargs = lappend(newcoercedargs, newe);
+	}
+
+	newm->args = newcoercedargs;
+	return (Node *) newm;
+}
+
 static Node *
 transformBooleanTest(ParseState *pstate, BooleanTest *b)
 {
@@ -1503,6 +1546,9 @@ exprType(Node *expr)
 		case T_CoalesceExpr:
 			type = ((CoalesceExpr *) expr)->coalescetype;
 			break;
+		case T_MinMaxExpr:
+			type = ((MinMaxExpr *) expr)->minmaxtype;
+			break;
 		case T_NullIfExpr:
 			type = exprType((Node *) linitial(((NullIfExpr *) expr)->args));
 			break;
@@ -1637,6 +1683,30 @@ exprTypmod(Node *expr)
 				return typmod;
 			}
 			break;
+		case T_MinMaxExpr:
+			{
+				/*
+				 * If all the alternatives agree on type/typmod, return
+				 * that typmod, else use -1
+				 */
+				MinMaxExpr *mexpr = (MinMaxExpr *) expr;
+				Oid			minmaxtype = mexpr->minmaxtype;
+				int32		typmod;
+				ListCell   *arg;
+
+				typmod = exprTypmod((Node *) linitial(mexpr->args));
+				foreach(arg, mexpr->args)
+				{
+					Node	   *e = (Node *) lfirst(arg);
+
+					if (exprType(e) != minmaxtype)
+						return -1;
+					if (exprTypmod(e) != typmod)
+						return -1;
+				}
+				return typmod;
+			}
+			break;
 		case T_NullIfExpr:
 			{
 				NullIfExpr *nexpr = (NullIfExpr *) expr;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index dd2c0b4e31c5c5217b97c1c8942653637c783d47..00185a05e12c25ef7cfc31932fe94564b1b07757 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.136 2005/06/05 00:38:10 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.137 2005/06/26 22:05:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1123,6 +1123,18 @@ FigureColnameInternal(Node *node, char **name)
 			/* make coalesce() act like a regular function */
 			*name = "coalesce";
 			return 2;
+		case T_MinMaxExpr:
+			/* make greatest/least act like a regular function */
+			switch (((MinMaxExpr*) node)->op)
+			{
+				case IS_GREATEST:
+					*name = "greatest";
+					return 2;
+				case IS_LEAST:
+					*name = "least";
+					return 2;
+			}
+			break;
 		default:
 			break;
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index bc3e774d640ce8767c6c212da6d2026543d32d15..0bd1d73eae10512edb9a13c78a1ac41debfe81ef 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.200 2005/06/05 00:38:10 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.201 2005/06/26 22:05:40 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -2781,6 +2781,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
 		case T_ArrayExpr:
 		case T_RowExpr:
 		case T_CoalesceExpr:
+		case T_MinMaxExpr:
 		case T_NullIfExpr:
 		case T_Aggref:
 		case T_FuncExpr:
@@ -2886,10 +2887,11 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
 				case T_BoolExpr:		/* lower precedence */
 				case T_ArrayRef:		/* other separators */
 				case T_ArrayExpr:		/* other separators */
-				case T_RowExpr:	/* other separators */
+				case T_RowExpr:			/* other separators */
 				case T_CoalesceExpr:	/* own parentheses */
+				case T_MinMaxExpr:		/* 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:
@@ -2933,10 +2935,11 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
 					}
 				case T_ArrayRef:		/* other separators */
 				case T_ArrayExpr:		/* other separators */
-				case T_RowExpr:	/* other separators */
+				case T_RowExpr:			/* other separators */
 				case T_CoalesceExpr:	/* own parentheses */
+				case T_MinMaxExpr:		/* 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:
@@ -3491,6 +3494,24 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_MinMaxExpr:
+			{
+				MinMaxExpr *minmaxexpr = (MinMaxExpr *) node;
+
+				switch (minmaxexpr->op)
+				{
+					case IS_GREATEST:
+						appendStringInfo(buf, "GREATEST(");
+						break;
+					case IS_LEAST:
+						appendStringInfo(buf, "LEAST(");
+						break;
+				}
+				get_rule_expr((Node *) minmaxexpr->args, context, true);
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		case T_NullIfExpr:
 			{
 				NullIfExpr *nullifexpr = (NullIfExpr *) node;
@@ -4109,7 +4130,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 		bool		need_paren_on_right;
 
 		need_paren_on_right = PRETTY_PAREN(context) &&
-			!IsA(j->rarg, RangeTblRef) && 
+			!IsA(j->rarg, RangeTblRef) &&
 			!(IsA(j->rarg, JoinExpr) && ((JoinExpr*) j->rarg)->alias != NULL);
 
 		if (!PRETTY_PAREN(context) || j->alias != NULL)
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index df41c8561084b511a237a5ab134c443c438b3eb7..46d27a56f537ce15e1f9626a987f013f76700df7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.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/nodes/execnodes.h,v 1.135 2005/06/20 18:37:02 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.136 2005/06/26 22:05:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -673,6 +673,17 @@ typedef struct CoalesceExprState
 	List	   *args;			/* the arguments */
 } CoalesceExprState;
 
+/* ----------------
+ *		MinMaxExprState node
+ * ----------------
+ */
+typedef struct MinMaxExprState
+{
+	ExprState	xprstate;
+	List	   *args;			/* the arguments */
+	FmgrInfo	cfunc;			/* lookup info for comparison func */
+} MinMaxExprState;
+
 /* ----------------
  *		CoerceToDomainState node
  * ----------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d9c98321ea3760e18846d41368bf946f08c705a6..fd923f72af4d75fa1a31aed2ac2321c8f45b0844 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.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/nodes/nodes.h,v 1.170 2005/06/09 04:19:00 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.171 2005/06/26 22:05:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -126,6 +126,7 @@ typedef enum NodeTag
 	T_ArrayExpr,
 	T_RowExpr,
 	T_CoalesceExpr,
+	T_MinMaxExpr,
 	T_NullIfExpr,
 	T_NullTest,
 	T_BooleanTest,
@@ -159,6 +160,7 @@ typedef enum NodeTag
 	T_ArrayExprState,
 	T_RowExprState,
 	T_CoalesceExprState,
+	T_MinMaxExprState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index a1d1ef3ebf06048e0adfdfb33796a3c88443ecbf..279b79738ef119904577b1b4fb525dad3d881ad5 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.107 2005/04/06 16:34:07 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.108 2005/06/26 22:05:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -657,6 +657,23 @@ typedef struct CoalesceExpr
 	List	   *args;			/* the arguments */
 } CoalesceExpr;
 
+/*
+ * MinMaxExpr - a GREATEST or LEAST function
+ */
+typedef enum MinMaxOp
+{
+	IS_GREATEST,
+	IS_LEAST
+} MinMaxOp;
+
+typedef struct MinMaxExpr
+{
+	Expr		xpr;
+	Oid			minmaxtype;		/* common type of arguments and result */
+	MinMaxOp	op;				/* function to execute */
+	List	   *args;			/* the arguments */
+} MinMaxExpr;
+
 /*
  * NullIfExpr - a NULLIF expression
  *
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 946a5b1c82af28f48135f8e8768535f56cd7e57a..356210ccb36bd4938779ae5ff938d1bae8458c04 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.148 2005/06/22 07:28:47 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.149 2005/06/26 22:05:42 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -4296,6 +4296,16 @@ exec_simple_check_node(Node *node)
 				return TRUE;
 			}
 
+		case T_MinMaxExpr:
+			{
+				MinMaxExpr *expr = (MinMaxExpr *) node;
+
+				if (!exec_simple_check_node((Node *) expr->args))
+					return FALSE;
+
+				return TRUE;
+			}
+
 		case T_NullIfExpr:
 			{
 				NullIfExpr *expr = (NullIfExpr *) node;