diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index de3b72738d752140aa54d9031490cb0aeb48d3e8..a88bca3002610283b43dd431dd425ff2b49df4b3 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/protocol.sgml,v 1.51 2004/03/21 22:29:10 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/protocol.sgml,v 1.52 2004/06/11 01:08:33 tgl Exp $ -->
 
 <chapter id="protocol">
  <title>Frontend/Backend Protocol</title>
@@ -684,6 +684,46 @@
     </para>
    </note>
 
+   <para>
+    Query planning for named prepared-statement objects occurs when the Parse
+    message is received. If a query will be repeatedly executed with
+    different parameters, it may be beneficial to send a single Parse message
+    containing a parameterized query, followed by multiple Bind
+    and Execute messages. This will avoid replanning the query on each
+    execution.
+   </para>
+
+   <para>
+    The unnamed prepared statement is likewise planned during Parse processing
+    if the Parse message defines no parameters.  But if there are parameters,
+    query planning is delayed until the first Bind message for the statement
+    is received. The planner will consider the actual values of the parameters
+    provided in the Bind message when planning the query.
+   </para>
+
+   <note>
+    <para>
+     Query plans generated from a parameterized query may be less
+     efficient than query plans generated from an equivalent query with actual
+     parameter values substituted. The query planner cannot make decisions
+     based on actual parameter values (for example, index selectivity) when
+     planning a parameterized query assigned to a named prepared-statement
+     object.  This possible penalty is avoided when using the unnamed
+     statement, since it is not planned until actual parameter values are
+     available.
+    </para>
+
+    <para>
+     If a second or subsequent Bind referencing the unnamed prepared-statement
+     object is received without an intervening Parse, the query is
+     not replanned. The parameter values used in the first Bind message may
+     produce a query plan that is only efficient for a subset of possible
+     parameter values. To force replanning of the query for a fresh set of
+     parameters, send another Parse message to replace the unnamed
+     prepared-statement object. 
+    </para>
+   </note>
+
    <para>
     If successfully created, a named portal object lasts till the end of the
     current transaction, unless explicitly destroyed.  An unnamed portal is
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 3658a00ea21f2bf4b4a7e6aa63c11d7f1bcf6cf2..972502895406144dfb88c1666ccbee49d34dccc4 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.121 2004/05/26 04:41:10 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.122 2004/06/11 01:08:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -176,7 +176,7 @@ ExplainOneQuery(Query *query, ExplainStmt *stmt, TupOutputState *tstate)
 	}
 
 	/* plan the query */
-	plan = planner(query, isCursor, cursorOptions);
+	plan = planner(query, isCursor, cursorOptions, NULL);
 
 	/* Create a QueryDesc requesting no output */
 	queryDesc = CreateQueryDesc(query, plan, None_Receiver, NULL,
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index d2fa894a76ec4139eb7d11c347bbe445b62b9458..9cb8febd3117bfe54ce4b39156ce1566e5c28ea9 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.27 2004/05/26 04:41:11 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.28 2004/06/11 01:08:37 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -84,7 +84,7 @@ PerformCursorOpen(DeclareCursorStmt *stmt)
 				 errmsg("DECLARE CURSOR ... FOR UPDATE is not supported"),
 				 errdetail("Cursors must be READ ONLY.")));
 
-	plan = planner(query, true, stmt->options);
+	plan = planner(query, true, stmt->options, NULL);
 
 	/*
 	 * Create a portal and copy the query and plan into its memory
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 083ad2af5e78938ecae0500449f4db120c0397b5..9c183cb7827b193881a6cbfa1e44f4d296278e5d 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -10,7 +10,7 @@
  * Copyright (c) 2002-2003, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.27 2004/05/26 04:41:11 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.28 2004/06/11 01:08:38 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -91,7 +91,7 @@ PrepareQuery(PrepareStmt *stmt)
 	query_list = QueryRewrite(stmt->query);
 
 	/* Generate plans for queries.	Snapshot is already set. */
-	plan_list = pg_plan_queries(query_list, false);
+	plan_list = pg_plan_queries(query_list, NULL, false);
 
 	/* Save the results. */
 	StorePreparedStatement(stmt->name,
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 2e75813a2ab97873c8788f963db097dcce6a4e86..9ddf6192775777ff63f11743823c72510bae6bb4 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.81 2004/05/26 04:41:15 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.82 2004/06/11 01:08:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -100,7 +100,7 @@ init_execution_state(List *queryTree_list)
 		Plan	   *planTree;
 		execution_state *newes;
 
-		planTree = pg_plan_query(queryTree);
+		planTree = pg_plan_query(queryTree, NULL);
 
 		newes = (execution_state *) palloc(sizeof(execution_state));
 		if (preves)
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index f5f2850b9822d88e4fda436a1f62b86ad88147e2..91b633d9bd8400d6f9fd4a481b5e9c62111bae10 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.117 2004/06/06 00:41:26 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.118 2004/06/11 01:08:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1125,7 +1125,7 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
 			QueryDesc  *qdesc;
 			DestReceiver *dest;
 
-			planTree = pg_plan_query(queryTree);
+			planTree = pg_plan_query(queryTree, NULL);
 			plan_list = lappend(plan_list, planTree);
 
 			dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None, NULL);
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index e736c6e309ecb5f0ac69de86f8fef635338a4a7a..996c98cc469e74e869a39196f0c2c983950ba78e 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.67 2004/05/30 23:40:28 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.68 2004/06/11 01:08:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -487,16 +487,27 @@ clause_selectivity(Query *root,
 			}
 		}
 	}
-	else if (IsA(clause, Param))
-	{
-		/* XXX any way to do better? */
-		s1 = 1.0;
-	}
 	else if (IsA(clause, Const))
 	{
 		/* bool constant is pretty easy... */
 		s1 = ((bool) ((Const *) clause)->constvalue) ? 1.0 : 0.0;
 	}
+	else if (IsA(clause, Param))
+	{
+		/* see if we can replace the Param */
+		Node	*subst = estimate_expression_value(clause);
+
+		if (IsA(subst, Const))
+		{
+			/* bool constant is pretty easy... */
+			s1 = ((bool) ((Const *) subst)->constvalue) ? 1.0 : 0.0;
+		}
+		else
+		{
+			/* XXX any way to do better? */
+			s1 = (Selectivity) 0.5;
+		}
+	}
 	else if (not_clause(clause))
 	{
 		/* inverse of the selectivity of the underlying clause */
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 064981b5af0496bfdc3be55b909625f0b0bc69a9..2fc82556c3283f3c9e860b6b78186553dbb7f5f9 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.171 2004/05/30 23:40:29 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.172 2004/06/11 01:08:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -43,6 +43,9 @@
 #include "utils/syscache.h"
 
 
+ParamListInfo PlannerBoundParamList = NULL;	/* current boundParams */
+
+
 /* Expression kind codes for preprocess_expression */
 #define EXPRKIND_QUAL	0
 #define EXPRKIND_TARGET 1
@@ -71,20 +74,24 @@ static List *postprocess_setop_tlist(List *new_tlist, List *orig_tlist);
  *
  *****************************************************************************/
 Plan *
-planner(Query *parse, bool isCursor, int cursorOptions)
+planner(Query *parse, bool isCursor, int cursorOptions,
+		ParamListInfo boundParams)
 {
 	double		tuple_fraction;
 	Plan	   *result_plan;
 	Index		save_PlannerQueryLevel;
 	List	   *save_PlannerParamList;
+	ParamListInfo save_PlannerBoundParamList;
 
 	/*
 	 * The planner can be called recursively (an example is when
 	 * eval_const_expressions tries to pre-evaluate an SQL function). So,
 	 * these global state variables must be saved and restored.
 	 *
-	 * These vars cannot be moved into the Query structure since their whole
-	 * purpose is communication across multiple sub-Queries.
+	 * Query level and the param list cannot be moved into the Query structure
+	 * since their whole purpose is communication across multiple sub-Queries.
+	 * Also, boundParams is explicitly info from outside the Query, and so
+	 * is likewise better handled as a global variable.
 	 *
 	 * Note we do NOT save and restore PlannerPlanId: it exists to assign
 	 * unique IDs to SubPlan nodes, and we want those IDs to be unique for
@@ -93,10 +100,12 @@ planner(Query *parse, bool isCursor, int cursorOptions)
 	 */
 	save_PlannerQueryLevel = PlannerQueryLevel;
 	save_PlannerParamList = PlannerParamList;
+	save_PlannerBoundParamList = PlannerBoundParamList;
 
 	/* Initialize state for handling outer-level references and params */
 	PlannerQueryLevel = 0;		/* will be 1 in top-level subquery_planner */
 	PlannerParamList = NIL;
+	PlannerBoundParamList = boundParams;
 
 	/* Determine what fraction of the plan is likely to be scanned */
 	if (isCursor)
@@ -139,6 +148,7 @@ planner(Query *parse, bool isCursor, int cursorOptions)
 	/* restore state for outer planner, if any */
 	PlannerQueryLevel = save_PlannerQueryLevel;
 	PlannerParamList = save_PlannerParamList;
+	PlannerBoundParamList = save_PlannerBoundParamList;
 
 	return result_plan;
 }
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index a3a2ddfbd830a3a858d93e263a484ced584e91d4..e7088d2b7610bea2b450f3e5079497b1b339fba1 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.175 2004/06/09 19:08:16 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.176 2004/06/11 01:08:54 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -28,6 +28,7 @@
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/planmain.h"
+#include "optimizer/planner.h"
 #include "optimizer/var.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
@@ -41,6 +42,12 @@
 #include "utils/syscache.h"
 
 
+typedef struct
+{
+	List	   *active_fns;
+	bool		estimate;
+} eval_const_expressions_context;
+
 typedef struct
 {
 	int			nargs;
@@ -57,17 +64,20 @@ static bool contain_mutable_functions_walker(Node *node, void *context);
 static bool contain_volatile_functions_walker(Node *node, void *context);
 static bool contain_nonstrict_functions_walker(Node *node, void *context);
 static bool set_coercionform_dontcare_walker(Node *node, void *context);
-static Node *eval_const_expressions_mutator(Node *node, List *active_fns);
+static Node *eval_const_expressions_mutator(Node *node,
+									eval_const_expressions_context *context);
 static List *simplify_or_arguments(List *args,
 								   bool *haveNull, bool *forceTrue);
 static List *simplify_and_arguments(List *args,
 									bool *haveNull, bool *forceFalse);
 static Expr *simplify_function(Oid funcid, Oid result_type, List *args,
-				  bool allow_inline, List *active_fns);
+							   bool allow_inline,
+							   eval_const_expressions_context *context);
 static Expr *evaluate_function(Oid funcid, Oid result_type, List *args,
 				  HeapTuple func_tuple);
 static Expr *inline_function(Oid funcid, Oid result_type, List *args,
-				HeapTuple func_tuple, List *active_fns);
+							 HeapTuple func_tuple,
+							 eval_const_expressions_context *context);
 static Node *substitute_actual_parameters(Node *expr, int nargs, List *args,
 							 int *usecounts);
 static Node *substitute_actual_parameters_mutator(Node *node,
@@ -1070,18 +1080,101 @@ set_coercionform_dontcare_walker(Node *node, void *context)
 Node *
 eval_const_expressions(Node *node)
 {
-	/*
-	 * The context for the mutator is a list of SQL functions being
-	 * recursively simplified, so we start with an empty list.
-	 */
-	return eval_const_expressions_mutator(node, NIL);
+	eval_const_expressions_context context;
+
+	context.active_fns = NIL;	/* nothing being recursively simplified */
+	context.estimate = false;	/* safe transformations only */
+	return eval_const_expressions_mutator(node, &context);
+}
+
+/*
+ * estimate_expression_value
+ *
+ * This function attempts to estimate the value of an expression for
+ * planning purposes.  It is in essence a more aggressive version of
+ * eval_const_expressions(): we will perform constant reductions that are
+ * not necessarily 100% safe, but are reasonable for estimation purposes.
+ *
+ * Currently the only such transform is to substitute values for Params,
+ * when a bound Param value has been made available by the caller of planner().
+ * In future we might consider other things, such as reducing now() to current
+ * time.  (XXX seems like there could be a lot of scope for ideas here...
+ * but we might need more volatility classifications ...)
+ */
+Node *
+estimate_expression_value(Node *node)
+{
+	eval_const_expressions_context context;
+
+	context.active_fns = NIL;	/* nothing being recursively simplified */
+	context.estimate = true;	/* unsafe transformations OK */
+	return eval_const_expressions_mutator(node, &context);
 }
 
 static Node *
-eval_const_expressions_mutator(Node *node, List *active_fns)
+eval_const_expressions_mutator(Node *node,
+							   eval_const_expressions_context *context)
 {
 	if (node == NULL)
 		return NULL;
+	if (IsA(node, Param))
+	{
+		Param	   *param = (Param *) node;
+		int			thisParamKind = param->paramkind;
+
+		/* OK to try to substitute value? */
+		if (context->estimate && thisParamKind != PARAM_EXEC &&
+			PlannerBoundParamList != NULL)
+		{
+			ParamListInfo paramList = PlannerBoundParamList;
+			bool		matchFound = false;
+
+			/* Search to see if we've been given a value for this Param */
+			while (paramList->kind != PARAM_INVALID && !matchFound)
+			{
+				if (thisParamKind == paramList->kind)
+				{
+					switch (thisParamKind)
+					{
+						case PARAM_NAMED:
+							if (strcmp(paramList->name, param->paramname) == 0)
+								matchFound = true;
+							break;
+						case PARAM_NUM:
+							if (paramList->id == param->paramid)
+								matchFound = true;
+							break;
+						default:
+							elog(ERROR, "unrecognized paramkind: %d",
+								 thisParamKind);
+					}
+				}
+				if (!matchFound)
+					paramList++;
+			}
+			if (matchFound)
+			{
+				/*
+				 * Found it, so return a Const representing the param value.
+				 * Note that we don't copy pass-by-ref datatypes, so the
+				 * Const will only be valid as long as the bound parameter
+				 * list exists. This is okay for intended uses of
+				 * estimate_expression_value().
+				 */
+				int16		typLen;
+				bool		typByVal;
+
+				get_typlenbyval(param->paramtype, &typLen, &typByVal);
+				return (Node *) makeConst(param->paramtype,
+										  (int) typLen,
+										  paramList->value,
+										  paramList->isnull,
+										  typByVal);
+			}
+		}
+		/* Not replaceable, so just copy the Param (no need to recurse) */
+		return (Node *) copyObject(param);
+	}
 	if (IsA(node, FuncExpr))
 	{
 		FuncExpr   *expr = (FuncExpr *) node;
@@ -1096,14 +1189,14 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 		 */
 		args = (List *) expression_tree_mutator((Node *) expr->args,
 										  eval_const_expressions_mutator,
-												(void *) active_fns);
+												(void *) context);
 
 		/*
 		 * Code for op/func reduction is pretty bulky, so split it out as
 		 * a separate function.
 		 */
 		simple = simplify_function(expr->funcid, expr->funcresulttype, args,
-								   true, active_fns);
+								   true, context);
 		if (simple)				/* successfully simplified it */
 			return (Node *) simple;
 
@@ -1134,7 +1227,7 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 		 */
 		args = (List *) expression_tree_mutator((Node *) expr->args,
 										  eval_const_expressions_mutator,
-												(void *) active_fns);
+												(void *) context);
 
 		/*
 		 * Need to get OID of underlying function.	Okay to scribble on
@@ -1147,7 +1240,7 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 		 * a separate function.
 		 */
 		simple = simplify_function(expr->opfuncid, expr->opresulttype, args,
-								   true, active_fns);
+								   true, context);
 		if (simple)				/* successfully simplified it */
 			return (Node *) simple;
 
@@ -1182,7 +1275,7 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 		 */
 		args = (List *) expression_tree_mutator((Node *) expr->args,
 										  eval_const_expressions_mutator,
-												(void *) active_fns);
+												(void *) context);
 
 		/*
 		 * We must do our own check for NULLs because DistinctExpr has
@@ -1226,7 +1319,7 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 			 * as a separate function.
 			 */
 			simple = simplify_function(expr->opfuncid, expr->opresulttype,
-									   args, false, active_fns);
+									   args, false, context);
 			if (simple)			/* successfully simplified it */
 			{
 				/*
@@ -1267,7 +1360,7 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 		 */
 		args = (List *) expression_tree_mutator((Node *) expr->args,
 										  eval_const_expressions_mutator,
-												(void *) active_fns);
+												(void *) context);
 
 		switch (expr->boolop)
 		{
@@ -1360,7 +1453,7 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 		Node	   *arg;
 
 		arg = eval_const_expressions_mutator((Node *) relabel->arg,
-											 active_fns);
+											 context);
 
 		/*
 		 * If we find stacked RelabelTypes (eg, from foo :: int :: oid) we
@@ -1424,7 +1517,7 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 
 		/* Simplify the test expression, if any */
 		newarg = eval_const_expressions_mutator((Node *) caseexpr->arg,
-												active_fns);
+												context);
 
 		/* Simplify the WHEN clauses */
 		newargs = NIL;
@@ -1434,7 +1527,7 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 			CaseWhen   *casewhen = (CaseWhen *)
 			expression_tree_mutator((Node *) lfirst(arg),
 									eval_const_expressions_mutator,
-									(void *) active_fns);
+									(void *) context);
 
 			Assert(IsA(casewhen, CaseWhen));
 			if (casewhen->expr == NULL ||
@@ -1464,7 +1557,7 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 
 		/* Simplify the default result */
 		defresult = eval_const_expressions_mutator((Node *) caseexpr->defresult,
-												   active_fns);
+												   context);
 
 		/*
 		 * If no non-FALSE alternatives, CASE reduces to the default
@@ -1494,7 +1587,7 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 			Node	   *e;
 
 			e = eval_const_expressions_mutator((Node *) lfirst(element),
-											   active_fns);
+											   context);
 			if (!IsA(e, Const))
 				all_const = false;
 			newelems = lappend(newelems, e);
@@ -1525,7 +1618,7 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 			Node	   *e;
 
 			e = eval_const_expressions_mutator((Node *) lfirst(arg),
-											   active_fns);
+											   context);
 
 			/*
 			 * We can remove null constants from the list. For a non-null
@@ -1561,7 +1654,7 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 		Node	   *arg;
 
 		arg = eval_const_expressions_mutator((Node *) fselect->arg,
-											 active_fns);
+											 context);
 		if (arg && IsA(arg, Var) &&
 			((Var *) arg)->varattno == InvalidAttrNumber)
 		{
@@ -1595,7 +1688,7 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 	 * simplify constant expressions in its subscripts.
 	 */
 	return expression_tree_mutator(node, eval_const_expressions_mutator,
-								   (void *) active_fns);
+								   (void *) context);
 }
 
 /*
@@ -1726,14 +1819,15 @@ simplify_and_arguments(List *args, bool *haveNull, bool *forceFalse)
  *
  * Inputs are the function OID, actual result type OID (which is needed for
  * polymorphic functions), and the pre-simplified argument list;
- * also a list of already-active inline function expansions.
+ * also the context data for eval_const_expressions.
  *
  * Returns a simplified expression if successful, or NULL if cannot
  * simplify the function call.
  */
 static Expr *
 simplify_function(Oid funcid, Oid result_type, List *args,
-				  bool allow_inline, List *active_fns)
+				  bool allow_inline,
+				  eval_const_expressions_context *context)
 {
 	HeapTuple	func_tuple;
 	Expr	   *newexpr;
@@ -1756,7 +1850,7 @@ simplify_function(Oid funcid, Oid result_type, List *args,
 
 	if (!newexpr && allow_inline)
 		newexpr = inline_function(funcid, result_type, args,
-								  func_tuple, active_fns);
+								  func_tuple, context);
 
 	ReleaseSysCache(func_tuple);
 
@@ -1860,7 +1954,8 @@ evaluate_function(Oid funcid, Oid result_type, List *args,
  */
 static Expr *
 inline_function(Oid funcid, Oid result_type, List *args,
-				HeapTuple func_tuple, List *active_fns)
+				HeapTuple func_tuple,
+				eval_const_expressions_context *context)
 {
 	Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
 	bool		polymorphic = false;
@@ -1890,7 +1985,7 @@ inline_function(Oid funcid, Oid result_type, List *args,
 		return NULL;
 
 	/* Check for recursive function, and give up trying to expand if so */
-	if (list_member_oid(active_fns, funcid))
+	if (list_member_oid(context->active_fns, funcid))
 		return NULL;
 
 	/* Check permission to call function (fail later, if not) */
@@ -2083,8 +2178,9 @@ inline_function(Oid funcid, Oid result_type, List *args,
 	 * Recursively try to simplify the modified expression.  Here we must
 	 * add the current function to the context list of active functions.
 	 */
-	newexpr = eval_const_expressions_mutator(newexpr,
-											 lcons_oid(funcid, active_fns));
+	context->active_fns = lcons_oid(funcid, context->active_fns);
+	newexpr = eval_const_expressions_mutator(newexpr, context);
+	context->active_fns = list_delete_first(context->active_fns);
 
 	error_context_stack = sqlerrcontext.previous;
 
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 07b60969a45f410216f9b143f440bfb4a39ce2d0..976b417e096fa454da871dc93b40359265550a9d 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.419 2004/06/06 00:41:27 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.420 2004/06/11 01:09:00 tgl Exp $
  *
  * NOTES
  *	  this is the "main" module of the postgres backend and
@@ -630,7 +630,7 @@ pg_rewrite_queries(List *querytree_list)
 
 /* Generate a plan for a single already-rewritten query. */
 Plan *
-pg_plan_query(Query *querytree)
+pg_plan_query(Query *querytree, ParamListInfo boundParams)
 {
 	Plan	   *plan;
 
@@ -642,7 +642,7 @@ pg_plan_query(Query *querytree)
 		ResetUsage();
 
 	/* call the optimizer */
-	plan = planner(querytree, false, 0);
+	plan = planner(querytree, false, 0, boundParams);
 
 	if (log_planner_stats)
 		ShowUsage("PLANNER STATISTICS");
@@ -687,7 +687,8 @@ pg_plan_query(Query *querytree)
  * statements in the rewriter's output.)
  */
 List *
-pg_plan_queries(List *querytrees, bool needSnapshot)
+pg_plan_queries(List *querytrees, ParamListInfo boundParams,
+				bool needSnapshot)
 {
 	List	   *plan_list = NIL;
 	ListCell   *query_list;
@@ -709,7 +710,7 @@ pg_plan_queries(List *querytrees, bool needSnapshot)
 				SetQuerySnapshot();
 				needSnapshot = false;
 			}
-			plan = pg_plan_query(query);
+			plan = pg_plan_query(query, boundParams);
 		}
 
 		plan_list = lappend(plan_list, plan);
@@ -867,7 +868,7 @@ exec_simple_query(const char *query_string)
 
 		querytree_list = pg_analyze_and_rewrite(parsetree, NULL, 0);
 
-		plantree_list = pg_plan_queries(querytree_list, true);
+		plantree_list = pg_plan_queries(querytree_list, NULL, true);
 
 		/* If we got a cancel signal in analysis or planning, quit */
 		CHECK_FOR_INTERRUPTS();
@@ -1205,7 +1206,14 @@ exec_parse_message(const char *query_string,	/* string to execute */
 
 		querytree_list = pg_rewrite_queries(querytree_list);
 
-		plantree_list = pg_plan_queries(querytree_list, true);
+		/*
+		 * If this is the unnamed statement and it has parameters, defer
+		 * query planning until Bind.  Otherwise do it now.
+		 */
+		if (!is_named && numParams > 0)
+			plantree_list = NIL;
+		else
+			plantree_list = pg_plan_queries(querytree_list, NULL, true);
 	}
 	else
 	{
@@ -1291,6 +1299,7 @@ exec_bind_message(StringInfo input_message)
 	PreparedStatement *pstmt;
 	Portal		portal;
 	ParamListInfo params;
+	bool		isaborted = IsAbortedTransactionBlockState();
 
 	pgstat_report_activity("<BIND>");
 
@@ -1356,13 +1365,6 @@ exec_bind_message(StringInfo input_message)
 	else
 		portal = CreatePortal(portal_name, false, false);
 
-	PortalDefineQuery(portal,
-					  pstmt->query_string,
-					  pstmt->commandTag,
-					  pstmt->query_list,
-					  pstmt->plan_list,
-					  pstmt->context);
-
 	/*
 	 * Fetch parameters, if any, and store in the portal's memory context.
 	 *
@@ -1372,7 +1374,6 @@ exec_bind_message(StringInfo input_message)
 	 */
 	if (numParams > 0)
 	{
-		bool		isaborted = IsAbortedTransactionBlockState();
 		ListCell   *l;
 		MemoryContext oldContext;
 
@@ -1516,8 +1517,32 @@ exec_bind_message(StringInfo input_message)
 	pq_getmsgend(input_message);
 
 	/*
-	 * Start portal execution.
+	 * If we didn't plan the query before, do it now.  This allows the
+	 * planner to make use of the concrete parameter values we now have.
+	 *
+	 * This happens only for unnamed statements, and so switching into
+	 * the statement context for planning is correct (see notes in
+	 * exec_parse_message).
 	 */
+	if (pstmt->plan_list == NIL && pstmt->query_list != NIL &&
+		!isaborted)
+	{
+		MemoryContext oldContext = MemoryContextSwitchTo(pstmt->context);
+
+		pstmt->plan_list = pg_plan_queries(pstmt->query_list, params, true);
+		MemoryContextSwitchTo(oldContext);
+	}
+
+	/*
+	 * Define portal and start execution.
+	 */
+	PortalDefineQuery(portal,
+					  pstmt->query_string,
+					  pstmt->commandTag,
+					  pstmt->query_list,
+					  pstmt->plan_list,
+					  pstmt->context);
+
 	PortalStart(portal, params);
 
 	/*
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 75eb0d2a5299579ec5117c1617d9755763f8bf7f..c7ee847a11a7ee3e995f7b45db9de66678d7ebd3 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -15,7 +15,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.160 2004/05/30 23:40:36 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.161 2004/06/11 01:09:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2823,7 +2823,8 @@ convert_timevalue_to_scalar(Datum value, Oid typid)
  *
  * Outputs: (these are valid only if TRUE is returned)
  *	*vardata: gets information about variable (see examine_variable)
- *	*other: gets other clause argument, stripped of binary relabeling
+ *	*other: gets other clause argument, stripped of binary relabeling,
+ *		and aggressively reduced to a constant
  *	*varonleft: set TRUE if variable is on the left, FALSE if on the right
  *
  * Returns TRUE if a variable is identified, otherwise FALSE.
@@ -2860,7 +2861,7 @@ get_restriction_variable(Query *root, List *args, int varRelid,
 	if (vardata->rel && rdata.rel == NULL)
 	{
 		*varonleft = true;
-		*other = rdata.var;
+		*other = estimate_expression_value(rdata.var);
 		/* Assume we need no ReleaseVariableStats(rdata) here */
 		return true;
 	}
@@ -2868,7 +2869,7 @@ get_restriction_variable(Query *root, List *args, int varRelid,
 	if (vardata->rel == NULL && rdata.rel)
 	{
 		*varonleft = false;
-		*other = vardata->var;
+		*other = estimate_expression_value(vardata->var);
 		/* Assume we need no ReleaseVariableStats(*vardata) here */
 		*vardata = rdata;
 		return true;
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 09f0822f468389cf9fd8ae8dccb809f315bfa03c..4d645f43d59efff8f94c66ce88166dd1fe36c621 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.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/optimizer/clauses.h,v 1.73 2004/03/14 23:41:27 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/clauses.h,v 1.74 2004/06/11 01:09:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -67,6 +67,8 @@ extern void set_coercionform_dontcare(Node *node);
 
 extern Node *eval_const_expressions(Node *node);
 
+extern Node *estimate_expression_value(Node *node);
+
 extern bool expression_tree_walker(Node *node, bool (*walker) (),
 											   void *context);
 extern Node *expression_tree_mutator(Node *node, Node *(*mutator) (),
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index 8688f3dd65937486aec176cd0c2d7f0a82485f41..15440c24c4c77d833bfcb47e8c831a80d77fe451 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -7,18 +7,22 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/planner.h,v 1.28 2003/11/29 22:41:07 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/planner.h,v 1.29 2004/06/11 01:09:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #ifndef PLANNER_H
 #define PLANNER_H
 
+#include "nodes/params.h"
 #include "nodes/parsenodes.h"
 #include "nodes/plannodes.h"
 
 
-extern Plan *planner(Query *parse, bool isCursor, int cursorOptions);
+extern ParamListInfo PlannerBoundParamList;	/* current boundParams */
+
+extern Plan *planner(Query *parse, bool isCursor, int cursorOptions,
+					 ParamListInfo boundParams);
 extern Plan *subquery_planner(Query *parse, double tuple_fraction);
 
 #endif   /* PLANNER_H */
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 1218e04fdfbc821077aca83d1ec1fd2260d0d91b..0f55e9787fdf347c27e3598861c196865e81f5d9 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.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/tcop/tcopprot.h,v 1.66 2004/05/29 22:48:23 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/tcop/tcopprot.h,v 1.67 2004/06/11 01:09:22 tgl Exp $
  *
  * OLD COMMENTS
  *	  This file was created so that other c files could get the two
@@ -22,6 +22,7 @@
 #include <setjmp.h>
 
 #include "executor/execdesc.h"
+#include "nodes/params.h"
 #include "tcop/dest.h"
 #include "utils/guc.h"
 
@@ -55,8 +56,9 @@ extern List *pg_parse_query(const char *query_string);
 extern List *pg_analyze_and_rewrite(Node *parsetree,
 					   Oid *paramTypes, int numParams);
 extern List *pg_rewrite_queries(List *querytree_list);
-extern Plan *pg_plan_query(Query *querytree);
-extern List *pg_plan_queries(List *querytrees, bool needSnapshot);
+extern Plan *pg_plan_query(Query *querytree, ParamListInfo boundParams);
+extern List *pg_plan_queries(List *querytrees, ParamListInfo boundParams,
+							 bool needSnapshot);
 
 extern bool assign_max_stack_depth(int newval, bool doit, GucSource source);