diff --git a/src/backend/executor/execFlatten.c b/src/backend/executor/execFlatten.c
index bb45e63a8a59d28a205cfab2cee08127c9667929..e94a43f3cf4f75067373f0f06a6b31bc4f36b05a 100644
--- a/src/backend/executor/execFlatten.c
+++ b/src/backend/executor/execFlatten.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/Attic/execFlatten.c,v 1.12 2000/01/26 05:56:21 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/Attic/execFlatten.c,v 1.13 2000/08/24 03:29:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -36,11 +36,12 @@ static bool FjoinBumpOuterNodes(TargetEntry *tlist, ExprContext *econtext,
 
 #endif
 
+
 Datum
 ExecEvalIter(Iter *iterNode,
 			 ExprContext *econtext,
-			 bool *resultIsNull,
-			 bool *iterIsDone)
+			 bool *isNull,
+			 ExprDoneCond *isDone)
 {
 	Node	   *expression;
 
@@ -52,14 +53,14 @@ ExecEvalIter(Iter *iterNode,
 	 * only worrying about postquel functions, c functions will come
 	 * later.
 	 */
-	return ExecEvalExpr(expression, econtext, resultIsNull, iterIsDone);
+	return ExecEvalExpr(expression, econtext, isNull, isDone);
 }
 
 void
 ExecEvalFjoin(TargetEntry *tlist,
 			  ExprContext *econtext,
 			  bool *isNullVect,
-			  bool *fj_isDone)
+			  ExprDoneCond *fj_isDone)
 {
 
 #ifdef SETS_FIXED
@@ -72,7 +73,7 @@ ExecEvalFjoin(TargetEntry *tlist,
 	BoolPtr		alwaysDone = fjNode->fj_alwaysDone;
 
 	if (fj_isDone)
-		*fj_isDone = false;
+		*fj_isDone = ExprMultipleResult;
 
 	/*
 	 * For the next tuple produced by the plan, we need to re-initialize
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 83117d836ebef83d7d5f9fb3233a40fb09c32708..3929c8782a9a6351ae5f520ff730f0db979832a3 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.78 2000/08/21 20:55:30 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.79 2000/08/24 03:29:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -35,34 +35,34 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
-#include "catalog/pg_language.h"
 #include "executor/execFlatten.h"
 #include "executor/execdebug.h"
 #include "executor/functions.h"
 #include "executor/nodeSubplan.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
-#include "utils/fmgroids.h"
-#include "utils/fcache2.h"
+#include "utils/fcache.h"
 
 
 /* static function decls */
-static Datum ExecEvalAggref(Aggref *aggref, ExprContext *econtext, bool *isNull);
+static Datum ExecEvalAggref(Aggref *aggref, ExprContext *econtext,
+							bool *isNull);
 static Datum ExecEvalArrayRef(ArrayRef *arrayRef, ExprContext *econtext,
-				 bool *isNull, bool *isDone);
+							  bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull);
 static Datum ExecEvalOper(Expr *opClause, ExprContext *econtext,
-			 bool *isNull);
+						  bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalFunc(Expr *funcClause, ExprContext *econtext,
-			 bool *isNull, bool *isDone);
-static void ExecEvalFuncArgs(FunctionCachePtr fcache, ExprContext *econtext,
-							 List *argList, FunctionCallInfo fcinfo,
-							 bool *argIsDone);
+						  bool *isNull, ExprDoneCond *isDone);
+static ExprDoneCond ExecEvalFuncArgs(FunctionCachePtr fcache,
+									 List *argList,
+									 ExprContext *econtext);
 static Datum ExecEvalNot(Expr *notclause, ExprContext *econtext, bool *isNull);
 static Datum ExecEvalAnd(Expr *andExpr, ExprContext *econtext, bool *isNull);
 static Datum ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull);
-static Datum ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull);
-static Datum ExecMakeFunctionResult(Node *node, List *arguments,
-					   ExprContext *econtext, bool *isNull, bool *isDone);
+static Datum ExecEvalCase(CaseExpr *caseExpr, ExprContext *econtext,
+						  bool *isNull, ExprDoneCond *isDone);
+
 
 /*----------
  *	  ExecEvalArrayRef
@@ -93,7 +93,7 @@ static Datum
 ExecEvalArrayRef(ArrayRef *arrayRef,
 				 ExprContext *econtext,
 				 bool *isNull,
-				 bool *isDone)
+				 ExprDoneCond *isDone)
 {
 	ArrayType  *array_source;
 	ArrayType  *resultArray;
@@ -104,9 +104,6 @@ ExecEvalArrayRef(ArrayRef *arrayRef,
 	IntArray	upper,
 				lower;
 	int		   *lIndex;
-	bool		dummy;
-
-	*isNull = false;
 
 	if (arrayRef->refexpr != NULL)
 	{
@@ -146,7 +143,7 @@ ExecEvalArrayRef(ArrayRef *arrayRef,
 		upper.indx[i++] = DatumGetInt32(ExecEvalExpr((Node *) lfirst(elt),
 													 econtext,
 													 isNull,
-													 &dummy));
+													 NULL));
 		/* If any index expr yields NULL, result is NULL or source array */
 		if (*isNull)
 		{
@@ -168,7 +165,7 @@ ExecEvalArrayRef(ArrayRef *arrayRef,
 			lower.indx[j++] = DatumGetInt32(ExecEvalExpr((Node *) lfirst(elt),
 														 econtext,
 														 isNull,
-														 &dummy));
+														 NULL));
 			/* If any index expr yields NULL, result is NULL or source array */
 			if (*isNull)
 			{
@@ -191,7 +188,7 @@ ExecEvalArrayRef(ArrayRef *arrayRef,
 		Datum		sourceData = ExecEvalExpr(arrayRef->refassgnexpr,
 											  econtext,
 											  isNull,
-											  &dummy);
+											  NULL);
 		/*
 		 * For now, can't cope with inserting NULL into an array,
 		 * so make it a no-op per discussion above...
@@ -588,162 +585,109 @@ GetAttributeByName(TupleTableSlot *slot, char *attname, bool *isNull)
 	return (char *) retval;
 }
 
-
-static void
+/*
+ * Evaluate arguments for a function.
+ */
+static ExprDoneCond
 ExecEvalFuncArgs(FunctionCachePtr fcache,
-				 ExprContext *econtext,
 				 List *argList,
-				 FunctionCallInfo fcinfo,
-				 bool *argIsDone)
+				 ExprContext *econtext)
 {
+	ExprDoneCond argIsDone;
 	int			i;
 	List	   *arg;
 
+	argIsDone = ExprSingleResult; /* default assumption */
+
 	i = 0;
 	foreach(arg, argList)
 	{
+		ExprDoneCond	thisArgIsDone;
 
-		/*
-		 * evaluate the expression, in general functions cannot take sets
-		 * as arguments but we make an exception in the case of nested dot
-		 * expressions.  We have to watch out for this case here.
-		 */
-		fcinfo->arg[i] = ExecEvalExpr((Node *) lfirst(arg),
-									  econtext,
-									  &fcinfo->argnull[i],
-									  argIsDone);
+		fcache->fcinfo.arg[i] = ExecEvalExpr((Node *) lfirst(arg),
+											 econtext,
+											 &fcache->fcinfo.argnull[i],
+											 &thisArgIsDone);
 
-		if (!(*argIsDone))
+		if (thisArgIsDone != ExprSingleResult)
 		{
-			if (i != 0)
-				elog(ERROR, "functions can only take sets in their first argument");
-			fcache->setArg = fcinfo->arg[0];
+			/*
+			 * We allow only one argument to have a set value; we'd need
+			 * much more complexity to keep track of multiple set arguments
+			 * (cf. ExecTargetList) and it doesn't seem worth it.
+			 */
+			if (argIsDone != ExprSingleResult)
+				elog(ERROR, "Functions and operators can take only one set argument");
 			fcache->hasSetArg = true;
+			argIsDone = thisArgIsDone;
 		}
 		i++;
 	}
+
+	return argIsDone;
 }
 
 /*
  *		ExecMakeFunctionResult
+ *
+ * Evaluate the arguments to a function and then the function itself.
+ *
+ * NOTE: econtext is used only for evaluating the argument expressions;
+ * it is not passed to the function itself.
  */
-static Datum
-ExecMakeFunctionResult(Node *node,
+Datum
+ExecMakeFunctionResult(FunctionCachePtr fcache,
 					   List *arguments,
 					   ExprContext *econtext,
 					   bool *isNull,
-					   bool *isDone)
+					   ExprDoneCond *isDone)
 {
-	FunctionCallInfoData	fcinfo;
-	FunctionCachePtr		fcache;
-	bool					funcisset;
-	Datum					result;
-	bool					argDone;
-
-	MemSet(&fcinfo, 0, sizeof(fcinfo));
-
-	/*
-	 * This is kind of ugly, Func nodes now have targetlists so that we
-	 * know when and what to project out from postquel function results.
-	 * ExecMakeFunctionResult becomes a little bit more of a dual personality
-	 * as a result.
-	 */
-	if (IsA(node, Func))
-	{
-		fcache = ((Func *) node)->func_fcache;
-		funcisset = (((Func *) node)->funcid == F_SETEVAL);
-	}
-	else
-	{
-		fcache = ((Oper *) node)->op_fcache;
-		funcisset = false;
-	}
-
-	fcinfo.flinfo = &fcache->func;
-	fcinfo.nargs = fcache->nargs;
+	Datum				result;
+	ExprDoneCond		argDone;
+	int					i;
 
 	/*
 	 * arguments is a list of expressions to evaluate before passing to
-	 * the function manager.  We collect the results of evaluating the
-	 * expressions into the FunctionCallInfo struct.  Note we assume that
-	 * fcache->nargs is the correct length of the arguments list!
+	 * the function manager.  We skip the evaluation if it was already
+	 * done in the previous call (ie, we are continuing the evaluation
+	 * of a set-valued function).  Otherwise, collect the current argument
+	 * values into fcache->fcinfo.
 	 */
-	if (fcache->nargs > 0)
+	if (fcache->fcinfo.nargs > 0 && !fcache->argsValid)
 	{
-		if (fcache->nargs > FUNC_MAX_ARGS)
-			elog(ERROR, "ExecMakeFunctionResult: too many arguments");
-
-		/*
-		 * If the setArg in the fcache is set we have an argument
-		 * returning a set of tuples (i.e. a nested dot expression).  We
-		 * don't want to evaluate the arguments again until the function
-		 * is done. hasSetArg will always be false until we eval the args
-		 * for the first time.
-		 */
-		if (fcache->hasSetArg && fcache->setArg != (Datum) 0)
-		{
-			fcinfo.arg[0] = fcache->setArg;
-			argDone = false;
-		}
-		else
-			ExecEvalFuncArgs(fcache, econtext, arguments, &fcinfo, &argDone);
-
-		if (fcache->hasSetArg && argDone)
+		argDone = ExecEvalFuncArgs(fcache, arguments, econtext);
+		if (argDone == ExprEndResult)
 		{
-			/* can only get here if input is an empty set. */
+			/* input is an empty set, so return an empty set. */
 			*isNull = true;
-			*isDone = true;
+			if (isDone)
+				*isDone = ExprEndResult;
+			else
+				elog(ERROR, "Set-valued function called in context that cannot accept a set");
 			return (Datum) 0;
 		}
 	}
 
-	/*
-	 * If this function is really a set, we have to diddle with things. If
-	 * the function has already been called at least once, then the setArg
-	 * field of the fcache holds the OID of this set in pg_proc.  (This is
-	 * not quite legit, since the setArg field is really for functions
-	 * which take sets of tuples as input - set functions take no inputs
-	 * at all.	But it's a nice place to stash this value, for now.)
-	 *
-	 * If this is the first call of the set's function, then the call to
-	 * ExecEvalFuncArgs above just returned the OID of the pg_proc tuple
-	 * which defines this set.	So replace the existing funcid in the
-	 * funcnode with the set's OID.  Also, we want a new fcache which
-	 * points to the right function, so get that, now that we have the
-	 * right OID.  Also zero out fcinfo.arg, since the real set doesn't take
-	 * any arguments.
-	 */
-	if (funcisset)
-	{
-		if (fcache->setArg)
-		{
-			((Func *) node)->funcid = DatumGetObjectId(fcache->setArg);
-		}
-		else
-		{
-			((Func *) node)->funcid = DatumGetObjectId(fcinfo.arg[0]);
-			setFcache(node, DatumGetObjectId(fcinfo.arg[0]), NIL, econtext);
-			fcache = ((Func *) node)->func_fcache;
-			fcache->setArg = fcinfo.arg[0];
-		}
-		fcinfo.arg[0] = (Datum) 0;
-	}
-
 	/*
 	 * now return the value gotten by calling the function manager,
 	 * passing the function the evaluated parameter values.
 	 */
-	if (fcache->language == SQLlanguageId)
+	if (fcache->func.fn_retset || fcache->hasSetArg)
 	{
-		/*--------------------
-		 * This loop handles the situation where we are iterating through
-		 * all results in a nested dot function (whose argument function
-		 * returns a set of tuples) and the current function finally
-		 * finishes.  We need to get the next argument in the set and start
-		 * the function all over again.  We might have to do it more than
-		 * once, if the function produces no results for a particular argument.
-		 * This is getting unclean.
-		 *--------------------
+		/*
+		 * We need to return a set result.  Complain if caller not ready
+		 * to accept one.
+		 */
+		if (isDone == NULL)
+			elog(ERROR, "Set-valued function called in context that cannot accept a set");
+
+		/*
+		 * This loop handles the situation where we have both a set argument
+		 * and a set-valued function.  Once we have exhausted the function's
+		 * value(s) for a particular argument value, we have to get the next
+		 * argument value and start the function over again.  We might have
+		 * to do it more than once, if the function produces an empty result
+		 * set for a particular input value.
 		 */
 		for (;;)
 		{
@@ -753,13 +697,11 @@ ExecMakeFunctionResult(Node *node,
 			 */
 			bool	callit = true;
 
-			if (fcinfo.flinfo->fn_strict)
+			if (fcache->func.fn_strict)
 			{
-				int		i;
-
-				for (i = 0; i < fcinfo.nargs; i++)
+				for (i = 0; i < fcache->fcinfo.nargs; i++)
 				{
-					if (fcinfo.argnull[i])
+					if (fcache->fcinfo.argnull[i])
 					{
 						callit = false;
 						break;
@@ -769,35 +711,55 @@ ExecMakeFunctionResult(Node *node,
 
 			if (callit)
 			{
-				result = postquel_function(&fcinfo, fcache, isDone);
-				*isNull = fcinfo.isnull;
+				fcache->fcinfo.isnull = false;
+				fcache->rsinfo.isDone = ExprSingleResult;
+				result = FunctionCallInvoke(&fcache->fcinfo);
+				*isNull = fcache->fcinfo.isnull;
+				*isDone = fcache->rsinfo.isDone;
 			}
 			else
 			{
 				result = (Datum) 0;
 				*isNull = true;
-				*isDone = true;
+				*isDone = ExprEndResult;
+			}
+
+			if (*isDone != ExprEndResult)
+			{
+				/*
+				 * Got a result from current argument.  If function itself
+				 * returns set, flag that we want to reuse current argument
+				 * values on next call.
+				 */
+				if (fcache->func.fn_retset)
+					fcache->argsValid = true;
+				/*
+				 * Make sure we say we are returning a set, even if the
+				 * function itself doesn't return sets.
+				 */
+				*isDone = ExprMultipleResult;
+				break;
 			}
 
-			if (!*isDone)
-				break;			/* got a result from current argument */
+			/* Else, done with this argument */
+			fcache->argsValid = false;
+
 			if (!fcache->hasSetArg)
 				break;			/* input not a set, so done */
 
-			/* OK, get the next argument... */
-			ExecEvalFuncArgs(fcache, econtext, arguments, &fcinfo, &argDone);
+			/* Re-eval args to get the next element of the input set */
+			argDone = ExecEvalFuncArgs(fcache, arguments, econtext);
 
-			if (argDone)
+			if (argDone != ExprMultipleResult)
 			{
 
 				/*
-				 * End of arguments, so reset the setArg flag and say
+				 * End of arguments, so reset the hasSetArg flag and say
 				 * "Done"
 				 */
-				fcache->setArg = (Datum) 0;
 				fcache->hasSetArg = false;
-				*isDone = true;
 				*isNull = true;
+				*isDone = ExprEndResult;
 				result = (Datum) 0;
 				break;
 			}
@@ -807,50 +769,29 @@ ExecMakeFunctionResult(Node *node,
 			 * new argument.
 			 */
 		}
-
-		if (funcisset)
-		{
-
-			/*
-			 * reset the funcid so that next call to this routine will
-			 * still recognize this func as a set. Note that for now we
-			 * assume that the set function in pg_proc must be a Postquel
-			 * function - the funcid is not reset below for C functions.
-			 */
-			((Func *) node)->funcid = F_SETEVAL;
-
-			/*
-			 * If we're done with the results of this function, get rid of
-			 * its func cache.
-			 */
-			if (*isDone)
-				((Func *) node)->func_fcache = NULL;
-		}
 	}
 	else
 	{
-		/* A non-SQL function cannot return a set, at present. */
-		*isDone = true;
-
 		/*
+		 * Non-set case: much easier.
+		 *
 		 * If function is strict, and there are any NULL arguments,
 		 * skip calling the function and return NULL.
 		 */
-		if (fcinfo.flinfo->fn_strict)
+		if (fcache->func.fn_strict)
 		{
-			int		i;
-
-			for (i = 0; i < fcinfo.nargs; i++)
+			for (i = 0; i < fcache->fcinfo.nargs; i++)
 			{
-				if (fcinfo.argnull[i])
+				if (fcache->fcinfo.argnull[i])
 				{
 					*isNull = true;
 					return (Datum) 0;
 				}
 			}
 		}
-		result = FunctionCallInvoke(&fcinfo);
-		*isNull = fcinfo.isnull;
+		fcache->fcinfo.isnull = false;
+		result = FunctionCallInvoke(&fcache->fcinfo);
+		*isNull = fcache->fcinfo.isnull;
 	}
 
 	return result;
@@ -871,12 +812,14 @@ ExecMakeFunctionResult(Node *node,
  * ----------------------------------------------------------------
  */
 static Datum
-ExecEvalOper(Expr *opClause, ExprContext *econtext, bool *isNull)
+ExecEvalOper(Expr *opClause,
+			 ExprContext *econtext,
+			 bool *isNull,
+			 ExprDoneCond *isDone)
 {
 	Oper	   *op;
 	List	   *argList;
 	FunctionCachePtr fcache;
-	bool		isDone;
 
 	/*
 	 * we extract the oid of the function associated with the op and then
@@ -894,16 +837,13 @@ ExecEvalOper(Expr *opClause, ExprContext *econtext, bool *isNull)
 	fcache = op->op_fcache;
 	if (fcache == NULL)
 	{
-		setFcache((Node *) op, op->opid, argList, econtext);
-		fcache = op->op_fcache;
+		fcache = init_fcache(op->opid, length(argList),
+							 econtext->ecxt_per_query_memory);
+		op->op_fcache = fcache;
 	}
 
-	/*
-	 * call ExecMakeFunctionResult() with a dummy isDone that we ignore.
-	 * We don't have operator whose arguments are sets.
-	 */
-	return ExecMakeFunctionResult((Node *) op, argList, econtext,
-								  isNull, &isDone);
+	return ExecMakeFunctionResult(fcache, argList, econtext,
+								  isNull, isDone);
 }
 
 /* ----------------------------------------------------------------
@@ -915,7 +855,7 @@ static Datum
 ExecEvalFunc(Expr *funcClause,
 			 ExprContext *econtext,
 			 bool *isNull,
-			 bool *isDone)
+			 ExprDoneCond *isDone)
 {
 	Func	   *func;
 	List	   *argList;
@@ -939,11 +879,12 @@ ExecEvalFunc(Expr *funcClause,
 	fcache = func->func_fcache;
 	if (fcache == NULL)
 	{
-		setFcache((Node *) func, func->funcid, argList, econtext);
-		fcache = func->func_fcache;
+		fcache = init_fcache(func->funcid, length(argList),
+							 econtext->ecxt_per_query_memory);
+		func->func_fcache = fcache;
 	}
 
-	return ExecMakeFunctionResult((Node *) func, argList, econtext,
+	return ExecMakeFunctionResult(fcache, argList, econtext,
 								  isNull, isDone);
 }
 
@@ -968,15 +909,10 @@ ExecEvalNot(Expr *notclause, ExprContext *econtext, bool *isNull)
 {
 	Node	   *clause;
 	Datum		expr_value;
-	bool		isDone;
 
 	clause = lfirst(notclause->args);
 
-	/*
-	 * We don't iterate over sets in the quals, so pass in an isDone flag,
-	 * but ignore it.
-	 */
-	expr_value = ExecEvalExpr(clause, econtext, isNull, &isDone);
+	expr_value = ExecEvalExpr(clause, econtext, isNull, NULL);
 
 	/*
 	 * if the expression evaluates to null, then we just cascade the null
@@ -1001,7 +937,6 @@ ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull)
 {
 	List	   *clauses;
 	List	   *clause;
-	bool		isDone;
 	bool		AnyNull;
 	Datum		clause_value;
 
@@ -1024,15 +959,8 @@ ExecEvalOr(Expr *orExpr, ExprContext *econtext, bool *isNull)
 	 */
 	foreach(clause, clauses)
 	{
-
-		/*
-		 * We don't iterate over sets in the quals, so pass in an isDone
-		 * flag, but ignore it.
-		 */
 		clause_value = ExecEvalExpr((Node *) lfirst(clause),
-									econtext,
-									isNull,
-									&isDone);
+									econtext, isNull, NULL);
 
 		/*
 		 * if we have a non-null true result, then return it.
@@ -1057,7 +985,6 @@ ExecEvalAnd(Expr *andExpr, ExprContext *econtext, bool *isNull)
 {
 	List	   *clauses;
 	List	   *clause;
-	bool		isDone;
 	bool		AnyNull;
 	Datum		clause_value;
 
@@ -1074,15 +1001,8 @@ ExecEvalAnd(Expr *andExpr, ExprContext *econtext, bool *isNull)
 	 */
 	foreach(clause, clauses)
 	{
-
-		/*
-		 * We don't iterate over sets in the quals, so pass in an isDone
-		 * flag, but ignore it.
-		 */
 		clause_value = ExecEvalExpr((Node *) lfirst(clause),
-									econtext,
-									isNull,
-									&isDone);
+									econtext, isNull, NULL);
 
 		/*
 		 * if we have a non-null false result, then return it.
@@ -1108,12 +1028,12 @@ ExecEvalAnd(Expr *andExpr, ExprContext *econtext, bool *isNull)
  * ----------------------------------------------------------------
  */
 static Datum
-ExecEvalCase(CaseExpr *caseExpr, ExprContext *econtext, bool *isNull)
+ExecEvalCase(CaseExpr *caseExpr, ExprContext *econtext,
+			 bool *isNull, ExprDoneCond *isDone)
 {
 	List	   *clauses;
 	List	   *clause;
 	Datum		clause_value;
-	bool		isDone;
 
 	clauses = caseExpr->args;
 
@@ -1126,14 +1046,10 @@ ExecEvalCase(CaseExpr *caseExpr, ExprContext *econtext, bool *isNull)
 	{
 		CaseWhen   *wclause = lfirst(clause);
 
-		/*
-		 * We don't iterate over sets in the quals, so pass in an isDone
-		 * flag, but ignore it.
-		 */
 		clause_value = ExecEvalExpr(wclause->expr,
 									econtext,
 									isNull,
-									&isDone);
+									NULL);
 
 		/*
 		 * if we have a true test, then we return the result, since the
@@ -1145,7 +1061,7 @@ ExecEvalCase(CaseExpr *caseExpr, ExprContext *econtext, bool *isNull)
 			return ExecEvalExpr(wclause->result,
 								econtext,
 								isNull,
-								&isDone);
+								isDone);
 		}
 	}
 
@@ -1154,7 +1070,7 @@ ExecEvalCase(CaseExpr *caseExpr, ExprContext *econtext, bool *isNull)
 		return ExecEvalExpr(caseExpr->defresult,
 							econtext,
 							isNull,
-							&isDone);
+							isDone);
 	}
 
 	*isNull = true;
@@ -1171,7 +1087,7 @@ static Datum
 ExecEvalFieldSelect(FieldSelect *fselect,
 					ExprContext *econtext,
 					bool *isNull,
-					bool *isDone)
+					ExprDoneCond *isDone)
 {
 	Datum			result;
 	TupleTableSlot *resSlot;
@@ -1179,7 +1095,6 @@ ExecEvalFieldSelect(FieldSelect *fselect,
 	result = ExecEvalExpr(fselect->arg, econtext, isNull, isDone);
 	if (*isNull)
 		return result;
-	/* XXX what about isDone? */
 	resSlot = (TupleTableSlot *) DatumGetPointer(result);
 	Assert(resSlot != NULL && IsA(resSlot, TupleTableSlot));
 	result = heap_getattr(resSlot->val,
@@ -1194,28 +1109,52 @@ ExecEvalFieldSelect(FieldSelect *fselect,
  *
  *		Recursively evaluate a targetlist or qualification expression.
  *
- *		The caller should already have switched into the temporary
- *		memory context econtext->ecxt_per_tuple_memory.  The convenience
- *		entry point ExecEvalExprSwitchContext() is provided for callers
- *		who don't prefer to do the switch in an outer loop.  We do not
- *		do the switch here because it'd be a waste of cycles during
- *		recursive entries to ExecEvalExpr().
+ * Inputs:
+ *		expression: the expression tree to evaluate
+ *		econtext: evaluation context information
+ *
+ * Outputs:
+ *		return value: Datum value of result
+ *		*isNull: set to TRUE if result is NULL (actual return value is
+ *				 meaningless if so); set to FALSE if non-null result
+ *		*isDone: set to indicator of set-result status
+ *
+ * A caller that can only accept a singleton (non-set) result should pass
+ * NULL for isDone; if the expression computes a set result then an elog()
+ * error will be reported.  If the caller does pass an isDone pointer then
+ * *isDone is set to one of these three states:
+ *		ExprSingleResult		singleton result (not a set)
+ *		ExprMultipleResult		return value is one element of a set
+ *		ExprEndResult			there are no more elements in the set
+ * When ExprMultipleResult is returned, the caller should invoke
+ * ExecEvalExpr() repeatedly until ExprEndResult is returned.  ExprEndResult
+ * is returned after the last real set element.  For convenience isNull will
+ * always be set TRUE when ExprEndResult is returned, but this should not be
+ * taken as indicating a NULL element of the set.  Note that these return
+ * conventions allow us to distinguish among a singleton NULL, a NULL element
+ * of a set, and an empty set.
  *
- *		This routine is an inner loop routine and must be as fast
- *		as possible.
+ * The caller should already have switched into the temporary memory
+ * context econtext->ecxt_per_tuple_memory.  The convenience entry point
+ * ExecEvalExprSwitchContext() is provided for callers who don't prefer to
+ * do the switch in an outer loop.  We do not do the switch here because
+ * it'd be a waste of cycles during recursive entries to ExecEvalExpr().
+ *
+ * This routine is an inner loop routine and must be as fast as possible.
  * ----------------------------------------------------------------
  */
 Datum
 ExecEvalExpr(Node *expression,
 			 ExprContext *econtext,
 			 bool *isNull,
-			 bool *isDone)
+			 ExprDoneCond *isDone)
 {
 	Datum		retDatum;
 
 	/* Set default values for result flags: non-null, not a set result */
 	*isNull = false;
-	*isDone = true;
+	if (isDone)
+		*isDone = ExprSingleResult;
 
 	/* Is this still necessary?  Doubtful... */
 	if (expression == NULL)
@@ -1266,7 +1205,8 @@ ExecEvalExpr(Node *expression,
 				switch (expr->opType)
 				{
 					case OP_EXPR:
-						retDatum = ExecEvalOper(expr, econtext, isNull);
+						retDatum = ExecEvalOper(expr, econtext,
+												isNull, isDone);
 						break;
 					case FUNC_EXPR:
 						retDatum = ExecEvalFunc(expr, econtext,
@@ -1307,7 +1247,10 @@ ExecEvalExpr(Node *expression,
 									isDone);
 			break;
 		case T_CaseExpr:
-			retDatum = ExecEvalCase((CaseExpr *) expression, econtext, isNull);
+			retDatum = ExecEvalCase((CaseExpr *) expression,
+									econtext,
+									isNull,
+									isDone);
 			break;
 
 		default:
@@ -1328,7 +1271,7 @@ Datum
 ExecEvalExprSwitchContext(Node *expression,
 						  ExprContext *econtext,
 						  bool *isNull,
-						  bool *isDone)
+						  ExprDoneCond *isDone)
 {
 	Datum		retDatum;
 	MemoryContext oldContext;
@@ -1413,13 +1356,8 @@ ExecQual(List *qual, ExprContext *econtext, bool resultForNull)
 		Node	   *clause = (Node *) lfirst(qlist);
 		Datum		expr_value;
 		bool		isNull;
-		bool		isDone;
 
-		/*
-		 * pass isDone, but ignore it.	We don't iterate over multiple
-		 * returns in the qualifications.
-		 */
-		expr_value = ExecEvalExpr(clause, econtext, &isNull, &isDone);
+		expr_value = ExecEvalExpr(clause, econtext, &isNull, NULL);
 
 		if (isNull)
 		{
@@ -1496,6 +1434,11 @@ ExecCleanTargetListLength(List *targetlist)
  *
  *		Evaluates a targetlist with respect to the current
  *		expression context and return a tuple.
+ *
+ * As with ExecEvalExpr, the caller should pass isDone = NULL if not
+ * prepared to deal with sets of result tuples.  Otherwise, a return
+ * of *isDone = ExprMultipleResult signifies a set element, and a return
+ * of *isDone = ExprEndResult signifies end of the set of tuple.
  * ----------------------------------------------------------------
  */
 static HeapTuple
@@ -1504,24 +1447,22 @@ ExecTargetList(List *targetlist,
 			   TupleDesc targettype,
 			   Datum *values,
 			   ExprContext *econtext,
-			   bool *isDone)
+			   ExprDoneCond *isDone)
 {
 	MemoryContext oldContext;
-	char		nulls_array[64];
-	bool		fjNullArray[64];
-	bool		itemIsDoneArray[64];
-	char	   *null_head;
+#define NPREALLOCDOMAINS 64
+	char		nullsArray[NPREALLOCDOMAINS];
+	bool		fjIsNullArray[NPREALLOCDOMAINS];
+	ExprDoneCond itemIsDoneArray[NPREALLOCDOMAINS];
+	char	   *nulls;
 	bool	   *fjIsNull;
-	bool	   *itemIsDone;
+	ExprDoneCond *itemIsDone;
 	List	   *tl;
 	TargetEntry *tle;
-	Node	   *expr;
-	Resdom	   *resdom;
 	AttrNumber	resind;
-	Datum		constvalue;
 	HeapTuple	newTuple;
 	bool		isNull;
-	bool		haveDoneIters;
+	bool		haveDoneSets;
 	static struct tupleDesc NullTupleDesc;		/* we assume this inits to
 												 * zeroes */
 
@@ -1553,70 +1494,67 @@ ExecTargetList(List *targetlist,
 	 * we have a really large targetlist.  otherwise we use the stack.
 	 *
 	 * We also allocate a bool array that is used to hold fjoin result state,
-	 * and another that holds the isDone status for each targetlist item.
+	 * and another array that holds the isDone status for each targetlist item.
+	 * The isDone status is needed so that we can iterate, generating multiple
+	 * tuples, when one or more tlist items return sets.  (We expect the caller
+	 * to call us again if we return *isDone = ExprMultipleResult.)
 	 */
-	if (nodomains > 64)
+	if (nodomains > NPREALLOCDOMAINS)
 	{
-		null_head = (char *) palloc(nodomains + 1);
-		fjIsNull = (bool *) palloc(nodomains + 1);
-		itemIsDone = (bool *) palloc(nodomains + 1);
+		nulls = (char *) palloc(nodomains * sizeof(char));
+		fjIsNull = (bool *) palloc(nodomains * sizeof(bool));
+		itemIsDone = (ExprDoneCond *) palloc(nodomains * sizeof(ExprDoneCond));
 	}
 	else
 	{
-		null_head = &nulls_array[0];
-		fjIsNull = &fjNullArray[0];
-		itemIsDone = &itemIsDoneArray[0];
+		nulls = nullsArray;
+		fjIsNull = fjIsNullArray;
+		itemIsDone = itemIsDoneArray;
 	}
 
 	/*
 	 * evaluate all the expressions in the target list
 	 */
 
-	*isDone = true;				/* until proven otherwise */
-	haveDoneIters = false;		/* any isDone Iter exprs in tlist? */
+	if (isDone)
+		*isDone = ExprSingleResult;	/* until proven otherwise */
+
+	haveDoneSets = false;		/* any exhausted set exprs in tlist? */
 
 	foreach(tl, targetlist)
 	{
-
-		/*
-		 * remember, a target list is a list of lists:
-		 *
-		 * ((<resdom | fjoin> expr) (<resdom | fjoin> expr) ...)
-		 *
-		 * tl is a pointer to successive cdr's of the targetlist tle is a
-		 * pointer to the target list entry in tl
-		 */
 		tle = lfirst(tl);
 
 		if (tle->resdom != NULL)
 		{
-			expr = tle->expr;
-			resdom = tle->resdom;
-			resind = resdom->resno - 1;
-
-			constvalue = ExecEvalExpr(expr,
-									  econtext,
-									  &isNull,
-									  &itemIsDone[resind]);
+			resind = tle->resdom->resno - 1;
 
-			values[resind] = constvalue;
+			values[resind] = ExecEvalExpr(tle->expr,
+										  econtext,
+										  &isNull,
+										  &itemIsDone[resind]);
+			nulls[resind] = isNull ? 'n' : ' ';
 
-			if (!isNull)
-				null_head[resind] = ' ';
-			else
-				null_head[resind] = 'n';
-
-			if (IsA(expr, Iter))
+			if (itemIsDone[resind] != ExprSingleResult)
 			{
-				if (itemIsDone[resind])
-					haveDoneIters = true;
+				/* We have a set-valued expression in the tlist */
+				if (isDone == NULL)
+					elog(ERROR, "Set-valued function called in context that cannot accept a set");
+				if (itemIsDone[resind] == ExprMultipleResult)
+				{
+					/* we have undone sets in the tlist, set flag */
+					*isDone = ExprMultipleResult;
+				}
 				else
-					*isDone = false;	/* we have undone Iters in the
-										 * list */
+				{
+					/* we have done sets in the tlist, set flag for that */
+					haveDoneSets = true;
+				}
 			}
 		}
 		else
 		{
+#ifdef SETS_FIXED
 			int			curNode;
 			Resdom	   *fjRes;
 			List	   *fjTlist = (List *) tle->expr;
@@ -1626,9 +1564,12 @@ ExecTargetList(List *targetlist,
 
 			ExecEvalFjoin(tle, econtext, fjIsNull, isDone);
 
-			/* this is probably wrong: */
-			if (*isDone)
+			/* XXX this is wrong, but since fjoin code is completely broken
+			 * anyway, I'm not going to worry about it now --- tgl 8/23/00
+			 */
+			if (isDone && *isDone == ExprEndResult)
 			{
+				MemoryContextSwitchTo(oldContext);
 				newTuple = NULL;
 				goto exit;
 			}
@@ -1638,13 +1579,8 @@ ExecTargetList(List *targetlist,
 			 */
 			fjRes = (Resdom *) fjNode->fj_innerNode;
 			resind = fjRes->resno - 1;
-			if (fjIsNull[0])
-				null_head[resind] = 'n';
-			else
-			{
-				null_head[resind] = ' ';
-				values[resind] = results[0];
-			}
+			values[resind] = results[0];
+			nulls[resind] = fjIsNull[0] ? 'n' : ' ';
 
 			/*
 			 * Get results from all of the outer nodes
@@ -1653,32 +1589,32 @@ ExecTargetList(List *targetlist,
 				 curNode < nNodes;
 				 curNode++, fjTlist = lnext(fjTlist))
 			{
-#ifdef NOT_USED					/* what is this?? */
 				Node	   *outernode = lfirst(fjTlist);
 
 				fjRes = (Resdom *) outernode->iterexpr;
-#endif
 				resind = fjRes->resno - 1;
-				if (fjIsNull[curNode])
-					null_head[resind] = 'n';
-				else
-				{
-					null_head[resind] = ' ';
-					values[resind] = results[curNode];
-				}
+				values[resind] = results[curNode];
+				nulls[resind] = fjIsNull[curNode] ? 'n' : ' ';
 			}
+#else
+			elog(ERROR, "ExecTargetList: fjoin nodes not currently supported");
+#endif
 		}
 	}
 
-	if (haveDoneIters)
+	if (haveDoneSets)
 	{
-		if (*isDone)
+		/*
+		 * note: can't get here unless we verified isDone != NULL
+		 */
+		if (*isDone == ExprSingleResult)
 		{
 
 			/*
-			 * all Iters are done, so return a null indicating tlist set
-			 * expansion is complete.
+			 * all sets are done, so report that tlist expansion is complete.
 			 */
+			*isDone = ExprEndResult;
+			MemoryContextSwitchTo(oldContext);
 			newTuple = NULL;
 			goto exit;
 		}
@@ -1686,22 +1622,8 @@ ExecTargetList(List *targetlist,
 		{
 
 			/*
-			 * We have some done and some undone Iters.  Restart the done
+			 * We have some done and some undone sets.  Restart the done
 			 * ones so that we can deliver a tuple (if possible).
-			 *
-			 * XXX this code is a crock, because it only works for Iters at
-			 * the top level of tlist expressions, and doesn't even work
-			 * right for them: you should get all possible combinations of
-			 * Iter results, but you won't unless the numbers of values
-			 * returned by each are relatively prime.  Should have a
-			 * mechanism more like aggregate functions, where we make a
-			 * list of all Iters contained in the tlist and cycle through
-			 * their values in a methodical fashion.  To do someday; can't
-			 * get excited about fixing a Berkeley feature that's not in
-			 * SQL92.  (The only reason we're doing this much is that we
-			 * have to be sure all the Iters are run to completion, or
-			 * their subplan executors will have unreleased resources,
-			 * e.g. pinned buffers...)
 			 */
 			foreach(tl, targetlist)
 			{
@@ -1709,36 +1631,57 @@ ExecTargetList(List *targetlist,
 
 				if (tle->resdom != NULL)
 				{
-					expr = tle->expr;
-					resdom = tle->resdom;
-					resind = resdom->resno - 1;
+					resind = tle->resdom->resno - 1;
 
-					if (IsA(expr, Iter) &&itemIsDone[resind])
+					if (itemIsDone[resind] == ExprEndResult)
 					{
-						constvalue = ExecEvalExpr(expr,
-												  econtext,
-												  &isNull,
-												  &itemIsDone[resind]);
-						if (itemIsDone[resind])
+						values[resind] = ExecEvalExpr(tle->expr,
+													  econtext,
+													  &isNull,
+													  &itemIsDone[resind]);
+						nulls[resind] = isNull ? 'n' : ' ';
+
+						if (itemIsDone[resind] == ExprEndResult)
 						{
 
 							/*
-							 * Oh dear, this Iter is returning an empty
+							 * Oh dear, this item is returning an empty
 							 * set. Guess we can't make a tuple after all.
 							 */
-							*isDone = true;
-							newTuple = NULL;
-							goto exit;
+							*isDone = ExprEndResult;
+							break;
 						}
+					}
+				}
+			}
+			/*
+			 * If we cannot make a tuple because some sets are empty,
+			 * we still have to cycle the nonempty sets to completion,
+			 * else resources will not be released from subplans etc.
+			 */
+			if (*isDone == ExprEndResult)
+			{
+				foreach(tl, targetlist)
+				{
+					tle = lfirst(tl);
 
-						values[resind] = constvalue;
+					if (tle->resdom != NULL)
+					{
+						resind = tle->resdom->resno - 1;
 
-						if (!isNull)
-							null_head[resind] = ' ';
-						else
-							null_head[resind] = 'n';
+						while (itemIsDone[resind] == ExprMultipleResult)
+						{
+							(void) ExecEvalExpr(tle->expr,
+												econtext,
+												&isNull,
+												&itemIsDone[resind]);
+						}
 					}
 				}
+
+				MemoryContextSwitchTo(oldContext);
+				newTuple = NULL;
+				goto exit;
 			}
 		}
 	}
@@ -1748,30 +1691,27 @@ ExecTargetList(List *targetlist,
 	 */
 	MemoryContextSwitchTo(oldContext);
 
-	newTuple = (HeapTuple) heap_formtuple(targettype, values, null_head);
+	newTuple = (HeapTuple) heap_formtuple(targettype, values, nulls);
 
 exit:
 
 	/*
 	 * free the status arrays if we palloc'd them
 	 */
-	if (nodomains > 64)
+	if (nodomains > NPREALLOCDOMAINS)
 	{
-		pfree(null_head);
+		pfree(nulls);
 		pfree(fjIsNull);
 		pfree(itemIsDone);
 	}
 
-	/* make sure we are in the right context if we did "goto exit" */
-	MemoryContextSwitchTo(oldContext);
-
 	return newTuple;
 }
 
 /* ----------------------------------------------------------------
  *		ExecProject
  *
- *		projects a tuple based in projection info and stores
+ *		projects a tuple based on projection info and stores
  *		it in the specified tuple table slot.
  *
  *		Note: someday soon the executor can be extended to eliminate
@@ -1782,7 +1722,7 @@ exit:
  * ----------------------------------------------------------------
  */
 TupleTableSlot *
-ExecProject(ProjectionInfo *projInfo, bool *isDone)
+ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone)
 {
 	TupleTableSlot *slot;
 	List	   *targetlist;
@@ -1810,7 +1750,7 @@ ExecProject(ProjectionInfo *projInfo, bool *isDone)
 	econtext = projInfo->pi_exprContext;
 
 	/*
-	 * form a new (result) tuple
+	 * form a new result tuple (if possible --- result can be NULL)
 	 */
 	newTuple = ExecTargetList(targetlist,
 							  len,
@@ -1822,9 +1762,8 @@ ExecProject(ProjectionInfo *projInfo, bool *isDone)
 	/*
 	 * store the tuple in the projection slot and return the slot.
 	 */
-	return (TupleTableSlot *)
-		ExecStoreTuple(newTuple,/* tuple to store */
-					   slot,	/* slot to store in */
-					   InvalidBuffer,	/* tuple has no buffer */
-					   true);
+	return ExecStoreTuple(newTuple,			/* tuple to store */
+						  slot,				/* slot to store in */
+						  InvalidBuffer,	/* tuple has no buffer */
+						  true);
 }
diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c
index a3f66d20cad24d39cfdf3921c742ce4d26a00279..d000a4cf50ad0f446a014604e4dd541750d04df0 100644
--- a/src/backend/executor/execScan.c
+++ b/src/backend/executor/execScan.c
@@ -12,7 +12,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/execScan.c,v 1.13 2000/07/17 03:04:53 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execScan.c,v 1.14 2000/08/24 03:29:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -50,11 +50,10 @@ ExecScan(Scan *node,
 {
 	CommonScanState *scanstate;
 	EState	   *estate;
+	ExprContext *econtext;
 	List	   *qual;
-	bool		isDone;
+	ExprDoneCond isDone;
 	TupleTableSlot *resultSlot;
-	ExprContext *econtext;
-	ProjectionInfo *projInfo;
 
 	/* ----------------
 	 *	Fetch data from node
@@ -65,13 +64,6 @@ ExecScan(Scan *node,
 	econtext = scanstate->cstate.cs_ExprContext;
 	qual = node->plan.qual;
 
-	/* ----------------
-	 *	Reset per-tuple memory context to free any expression evaluation
-	 *	storage allocated in the previous tuple cycle.
-	 * ----------------
-	 */
-	ResetExprContext(econtext);
-
 	/* ----------------
 	 *	Check to see if we're still projecting out tuples from a previous
 	 *	scan tuple (because there is a function-returning-set in the
@@ -80,14 +72,21 @@ ExecScan(Scan *node,
 	 */
 	if (scanstate->cstate.cs_TupFromTlist)
 	{
-		projInfo = scanstate->cstate.cs_ProjInfo;
-		resultSlot = ExecProject(projInfo, &isDone);
-		if (!isDone)
+		resultSlot = ExecProject(scanstate->cstate.cs_ProjInfo, &isDone);
+		if (isDone == ExprMultipleResult)
 			return resultSlot;
 		/* Done with that source tuple... */
 		scanstate->cstate.cs_TupFromTlist = false;
 	}
 
+	/* ----------------
+	 *	Reset per-tuple memory context to free any expression evaluation
+	 *	storage allocated in the previous tuple cycle.  Note this can't
+	 *	happen until we're done projecting out tuples from a scan tuple.
+	 * ----------------
+	 */
+	ResetExprContext(econtext);
+
 	/*
 	 * get a tuple from the access method loop until we obtain a tuple
 	 * which passes the qualification.
@@ -121,8 +120,6 @@ ExecScan(Scan *node,
 
 		/* ----------------
 		 *	check that the current tuple satisfies the qual-clause
-		 *	if our qualification succeeds then we may
-		 *	leave the loop.
 		 *
 		 * check for non-nil qual here to avoid a function call to
 		 * ExecQual() when the qual is nil ... saves only a few cycles,
@@ -130,7 +127,22 @@ ExecScan(Scan *node,
 		 * ----------------
 		 */
 		if (!qual || ExecQual(qual, econtext, false))
-			break;
+		{
+			/* ----------------
+			 *	Found a satisfactory scan tuple.
+			 *
+			 *	Form a projection tuple, store it in the result tuple
+			 *	slot and return it --- unless we find we can project no
+			 *	tuples from this scan tuple, in which case continue scan.
+			 * ----------------
+			 */
+			resultSlot = ExecProject(scanstate->cstate.cs_ProjInfo, &isDone);
+			if (isDone != ExprEndResult)
+			{
+				scanstate->cstate.cs_TupFromTlist = (isDone == ExprMultipleResult);
+				return resultSlot;
+			}
+		}
 
 		/* ----------------
 		 *	Tuple fails qual, so free per-tuple memory and try again.
@@ -138,18 +150,4 @@ ExecScan(Scan *node,
 		 */
 		ResetExprContext(econtext);
 	}
-
-	/* ----------------
-	 *	Found a satisfactory scan tuple.
-	 *
-	 *	Form a projection tuple, store it in the result tuple
-	 *	slot and return it.
-	 * ----------------
-	 */
-	projInfo = scanstate->cstate.cs_ProjInfo;
-
-	resultSlot = ExecProject(projInfo, &isDone);
-	scanstate->cstate.cs_TupFromTlist = !isDone;
-
-	return resultSlot;
 }
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 001feb267ffe02e0b65f14228ae3f8dc902f0430..58fb68a6113b241f9a5867661039817125c5a680 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -8,22 +8,29 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.37 2000/08/08 15:41:22 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.38 2000/08/24 03:29:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
 #include "executor/execdefs.h"
 #include "executor/executor.h"
 #include "executor/functions.h"
 #include "tcop/pquery.h"
 #include "tcop/tcopprot.h"
 #include "tcop/utility.h"
+#include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/syscache.h"
 
 
+/*
+ * We have an execution_state record for each query in the function.
+ */
 typedef enum
 {
 	F_EXEC_START, F_EXEC_RUN, F_EXEC_DONE
@@ -39,15 +46,40 @@ typedef struct local_es
 
 #define LAST_POSTQUEL_COMMAND(es) ((es)->next == (execution_state *) NULL)
 
+
+/*
+ * An SQLFunctionCache record is built during the first call,
+ * and linked to from the fn_extra field of the FmgrInfo struct.
+ */
+
+typedef struct
+{
+	int			typlen;			/* length of the return type */
+	bool		typbyval;		/* true if return type is pass by value */
+	bool		returnsTuple;	/* true if return type is a tuple */
+
+	TupleTableSlot *funcSlot;	/* if one result we need to copy it before
+								 * we end execution of the function and
+								 * free stuff */
+
+	/* head of linked list of execution_state records */
+	execution_state *func_state;
+} SQLFunctionCache;
+
+typedef SQLFunctionCache *SQLFunctionCachePtr;
+
+
 /* non-export function prototypes */
+static execution_state *init_execution_state(char *src,
+											 Oid *argOidVect, int nargs);
+static void init_sql_fcache(FmgrInfo *finfo);
 static TupleDesc postquel_start(execution_state *es);
-static execution_state *init_execution_state(FunctionCachePtr fcache);
 static TupleTableSlot *postquel_getnext(execution_state *es);
 static void postquel_end(execution_state *es);
 static void postquel_sub_params(execution_state *es, FunctionCallInfo fcinfo);
 static Datum postquel_execute(execution_state *es,
 							  FunctionCallInfo fcinfo,
-							  FunctionCachePtr fcache);
+							  SQLFunctionCachePtr fcache);
 
 
 static Datum
@@ -69,21 +101,19 @@ ProjectAttribute(HeapTuple tup,
 }
 
 static execution_state *
-init_execution_state(FunctionCachePtr fcache)
+init_execution_state(char *src, Oid *argOidVect, int nargs)
 {
 	execution_state *newes;
 	execution_state *nextes;
 	execution_state *preves;
 	List	   *queryTree_list,
 			   *qtl_item;
-	int			nargs = fcache->nargs;
 
 	newes = (execution_state *) palloc(sizeof(execution_state));
 	nextes = newes;
 	preves = (execution_state *) NULL;
 
-	queryTree_list = pg_parse_and_rewrite(fcache->src,
-										  fcache->argOidVect, nargs);
+	queryTree_list = pg_parse_and_rewrite(src, argOidVect, nargs);
 
 	foreach(qtl_item, queryTree_list)
 	{
@@ -138,6 +168,134 @@ init_execution_state(FunctionCachePtr fcache)
 	return newes;
 }
 
+
+static void
+init_sql_fcache(FmgrInfo *finfo)
+{
+	Oid			foid = finfo->fn_oid;
+	HeapTuple	procedureTuple;
+	HeapTuple	typeTuple;
+	Form_pg_proc procedureStruct;
+	Form_pg_type typeStruct;
+	SQLFunctionCachePtr fcache;
+	Oid		   *argOidVect;
+	char	   *src;
+	int			nargs;
+	Datum		tmp;
+	bool		isNull;
+
+	/* ----------------
+	 *	 get the procedure tuple corresponding to the given function Oid
+	 *
+	 *	 NB: use SearchSysCacheTupleCopy to ensure tuple lives long enough
+	 * ----------------
+	 */
+	procedureTuple = SearchSysCacheTupleCopy(PROCOID,
+											 ObjectIdGetDatum(foid),
+											 0, 0, 0);
+
+	if (!HeapTupleIsValid(procedureTuple))
+		elog(ERROR, "init_sql_fcache: Cache lookup failed for procedure %u",
+			 foid);
+
+	procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);
+
+	/* ----------------
+	 *	 get the return type from the procedure tuple
+	 * ----------------
+	 */
+	typeTuple = SearchSysCacheTuple(TYPEOID,
+						   ObjectIdGetDatum(procedureStruct->prorettype),
+									0, 0, 0);
+
+	if (!HeapTupleIsValid(typeTuple))
+		elog(ERROR, "init_sql_fcache: Cache lookup failed for type %u",
+			 procedureStruct->prorettype);
+
+	typeStruct = (Form_pg_type) GETSTRUCT(typeTuple);
+
+	fcache = (SQLFunctionCachePtr) palloc(sizeof(SQLFunctionCache));
+	MemSet(fcache, 0, sizeof(SQLFunctionCache));
+
+	/* ----------------
+	 *	 get the type length and by-value flag from the type tuple
+	 * ----------------
+	 */
+	fcache->typlen = typeStruct->typlen;
+	if (typeStruct->typrelid == InvalidOid)
+	{
+		/* The return type is not a relation, so just use byval */
+		fcache->typbyval = typeStruct->typbyval;
+		fcache->returnsTuple = false;
+	}
+	else
+	{
+
+		/*
+		 * This is a hack.	We assume here that any function returning a
+		 * tuple returns it by reference.  This needs to be fixed, since
+		 * actually the mechanism isn't quite like return-by-reference.
+		 */
+		fcache->typbyval = false;
+		fcache->returnsTuple = true;
+	}
+
+	/*
+	 * If we are returning exactly one result then we have to copy tuples
+	 * and by reference results because we have to end the execution
+	 * before we return the results.  When you do this everything
+	 * allocated by the executor (i.e. slots and tuples) is freed.
+	 */
+	if (!finfo->fn_retset && !fcache->typbyval)
+	{
+		TupleTableSlot *slot;
+
+		slot = makeNode(TupleTableSlot);
+		slot->val = (HeapTuple) NULL;
+		slot->ttc_shouldFree = true;
+		slot->ttc_descIsNew = true;
+		slot->ttc_tupleDescriptor = (TupleDesc) NULL;
+		slot->ttc_buffer = InvalidBuffer;
+		slot->ttc_whichplan = -1;
+
+		fcache->funcSlot = slot;
+	}
+	else
+		fcache->funcSlot = NULL;
+
+	nargs = procedureStruct->pronargs;
+
+	if (nargs > 0)
+	{
+		argOidVect = (Oid *) palloc(nargs * sizeof(Oid));
+		memcpy(argOidVect,
+			   procedureStruct->proargtypes,
+			   nargs * sizeof(Oid));
+	}
+	else
+	{
+		argOidVect = (Oid *) NULL;
+	}
+
+	tmp = SysCacheGetAttr(PROCOID,
+						  procedureTuple,
+						  Anum_pg_proc_prosrc,
+						  &isNull);
+	if (isNull)
+		elog(ERROR, "init_sql_fcache: null prosrc for procedure %u",
+			 foid);
+	src = DatumGetCString(DirectFunctionCall1(textout, tmp));
+
+	fcache->func_state = init_execution_state(src, argOidVect, nargs);
+
+	pfree(src);
+
+	heap_freetuple(procedureTuple);
+
+	finfo->fn_extra = (void *) fcache;
+}
+
+
 static TupleDesc
 postquel_start(execution_state *es)
 {
@@ -208,7 +366,7 @@ postquel_sub_params(execution_state *es, FunctionCallInfo fcinfo)
 }
 
 static TupleTableSlot *
-copy_function_result(FunctionCachePtr fcache,
+copy_function_result(SQLFunctionCachePtr fcache,
 					 TupleTableSlot *resultSlot)
 {
 	TupleTableSlot *funcSlot;
@@ -219,10 +377,10 @@ copy_function_result(FunctionCachePtr fcache,
 	Assert(!TupIsNull(resultSlot));
 	resultTuple = resultSlot->val;
 
-	funcSlot = (TupleTableSlot *) fcache->funcSlot;
+	funcSlot = fcache->funcSlot;
 
-	if (funcSlot == (TupleTableSlot *) NULL)
-		return resultSlot;
+	if (funcSlot == NULL)
+		return resultSlot;		/* no need to copy result */
 
 	/*
 	 * If first time through, we have to initialize the funcSlot's
@@ -243,7 +401,7 @@ copy_function_result(FunctionCachePtr fcache,
 static Datum
 postquel_execute(execution_state *es,
 				 FunctionCallInfo fcinfo,
-				 FunctionCachePtr fcache)
+				 SQLFunctionCachePtr fcache)
 {
 	TupleTableSlot *slot;
 	Datum		value;
@@ -319,7 +477,7 @@ postquel_execute(execution_state *es,
 		 * If this is a single valued function we have to end the function
 		 * execution now.
 		 */
-		if (!fcache->returnsSet)
+		if (!fcinfo->flinfo->fn_retset)
 		{
 			postquel_end(es);
 			es->status = F_EXEC_DONE;
@@ -338,11 +496,10 @@ postquel_execute(execution_state *es,
 }
 
 Datum
-postquel_function(FunctionCallInfo fcinfo,
-				  FunctionCachePtr fcache,
-				  bool *isDone)
+fmgr_sql(PG_FUNCTION_ARGS)
 {
 	MemoryContext oldcontext;
+	SQLFunctionCachePtr fcache;
 	execution_state *es;
 	Datum		result = 0;
 	CommandId	savedId;
@@ -352,7 +509,7 @@ postquel_function(FunctionCallInfo fcinfo,
 	 * parsetrees, plans, etc, will have sufficient lifetime.  The
 	 * sub-executor is responsible for deleting per-tuple information.
 	 */
-	oldcontext = MemoryContextSwitchTo(fcache->fcacheCxt);
+	oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
 
 	/*
 	 * Before we start do anything we must save CurrentScanCommandId to
@@ -362,13 +519,21 @@ postquel_function(FunctionCallInfo fcinfo,
 	savedId = GetScanCommandId();
 	SetScanCommandId(GetCurrentCommandId());
 
-	es = (execution_state *) fcache->func_state;
-	if (es == NULL)
+	/*
+	 * Initialize fcache and execution state if first time through.
+	 */
+	fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra;
+	if (fcache == NULL)
 	{
-		es = init_execution_state(fcache);
-		fcache->func_state = (char *) es;
+		init_sql_fcache(fcinfo->flinfo);
+		fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra;
 	}
+	es = fcache->func_state;
+	Assert(es);
 
+	/*
+	 * Find first unfinished query in function.
+	 */
 	while (es && es->status == F_EXEC_DONE)
 		es = es->next;
 
@@ -401,7 +566,7 @@ postquel_function(FunctionCallInfo fcinfo,
 		/*
 		 * Reset the execution states to start over again
 		 */
-		es = (execution_state *) fcache->func_state;
+		es = fcache->func_state;
 		while (es)
 		{
 			es->status = F_EXEC_START;
@@ -411,9 +576,21 @@ postquel_function(FunctionCallInfo fcinfo,
 		/*
 		 * Let caller know we're finished.
 		 */
-		*isDone = true;
+		if (fcinfo->flinfo->fn_retset)
+		{
+			ReturnSetInfo  *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+			if (rsi && IsA(rsi, ReturnSetInfo))
+				rsi->isDone = ExprEndResult;
+			else
+				elog(ERROR, "Set-valued function called in context that cannot accept a set");
+			fcinfo->isnull = true;
+			result = (Datum) 0;
+		}
+
 		MemoryContextSwitchTo(oldcontext);
-		return (fcache->returnsSet) ? (Datum) NULL : result;
+
+		return result;
 	}
 
 	/*
@@ -422,7 +599,18 @@ postquel_function(FunctionCallInfo fcinfo,
 	 */
 	Assert(LAST_POSTQUEL_COMMAND(es));
 
-	*isDone = false;
+	/*
+	 * Let caller know we're not finished.
+	 */
+	if (fcinfo->flinfo->fn_retset)
+	{
+		ReturnSetInfo  *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+		if (rsi && IsA(rsi, ReturnSetInfo))
+			rsi->isDone = ExprMultipleResult;
+		else
+			elog(ERROR, "Set-valued function called in context that cannot accept a set");
+	}
 
 	MemoryContextSwitchTo(oldcontext);
 
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 547a946b4ce81bf941dfb5ae41181d8dd591b0c6..fae1a5b0d87feeef87bef2e7937b1028e8ca85db 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -34,7 +34,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeAgg.c,v 1.70 2000/07/17 03:04:53 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeAgg.c,v 1.71 2000/08/24 03:29:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -451,7 +451,6 @@ ExecAgg(Agg *node)
 	TupleTableSlot *resultSlot;
 	HeapTuple	inputTuple;
 	int			aggno;
-	bool		isDone;
 	bool		isNull;
 
 	/* ---------------------
@@ -523,7 +522,7 @@ ExecAgg(Agg *node)
 				Datum		newVal;
 
 				newVal = ExecEvalExpr(aggref->target, econtext,
-									  &isNull, &isDone);
+									  &isNull, NULL);
 
 				if (aggref->aggdistinct)
 				{
@@ -677,8 +676,9 @@ ExecAgg(Agg *node)
 		/*
 		 * Form a projection tuple using the aggregate results and the
 		 * representative input tuple.	Store it in the result tuple slot.
+		 * Note we do not support aggregates returning sets ...
 		 */
-		resultSlot = ExecProject(projInfo, &isDone);
+		resultSlot = ExecProject(projInfo, NULL);
 
 		/*
 		 * If the completed tuple does not match the qualifications, it is
diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c
index 8a445b53d4197944d0c7bea3eaec850147c79136..8fc319a77f0e67c770b0c5e12e9de473ca230df3 100644
--- a/src/backend/executor/nodeGroup.c
+++ b/src/backend/executor/nodeGroup.c
@@ -15,7 +15,7 @@
  *	  locate group boundaries.
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeGroup.c,v 1.37 2000/07/12 02:37:03 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeGroup.c,v 1.38 2000/08/24 03:29:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -73,7 +73,6 @@ ExecGroupEveryTuple(Group *node)
 	TupleTableSlot *outerslot;
 	ProjectionInfo *projInfo;
 	TupleTableSlot *resultSlot;
-	bool		isDone;
 
 	/* ---------------------
 	 *	get state info from node
@@ -163,7 +162,7 @@ ExecGroupEveryTuple(Group *node)
 	projInfo = grpstate->csstate.cstate.cs_ProjInfo;
 
 	econtext->ecxt_scantuple = grpstate->csstate.css_ScanTupleSlot;
-	resultSlot = ExecProject(projInfo, &isDone);
+	resultSlot = ExecProject(projInfo, NULL);
 
 	return resultSlot;
 }
@@ -185,7 +184,6 @@ ExecGroupOneTuple(Group *node)
 	TupleTableSlot *outerslot;
 	ProjectionInfo *projInfo;
 	TupleTableSlot *resultSlot;
-	bool		isDone;
 
 	/* ---------------------
 	 *	get state info from node
@@ -258,7 +256,7 @@ ExecGroupOneTuple(Group *node)
 				   grpstate->csstate.css_ScanTupleSlot,
 				   InvalidBuffer, false);
 	econtext->ecxt_scantuple = grpstate->csstate.css_ScanTupleSlot;
-	resultSlot = ExecProject(projInfo, &isDone);
+	resultSlot = ExecProject(projInfo, NULL);
 
 	/* save outerTuple if we are not done yet */
 	if (!grpstate->grp_done)
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index 682afdba4afa7e9620034af6501897a868175b44..9cd85195cdcc4fe85ea3de4328a3c0b95b1ca168 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
- *	$Id: nodeHash.c,v 1.51 2000/08/22 04:06:19 tgl Exp $
+ *	$Id: nodeHash.c,v 1.52 2000/08/24 03:29:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -524,7 +524,6 @@ ExecHashGetBucket(HashJoinTable hashtable,
 	int			bucketno;
 	Datum		keyval;
 	bool		isNull;
-	bool		isDone;
 
 	/* ----------------
 	 *	Get the join attribute value of the tuple
@@ -535,8 +534,7 @@ ExecHashGetBucket(HashJoinTable hashtable,
 	 */
 	ResetExprContext(econtext);
 
-	keyval = ExecEvalExprSwitchContext(hashkey, econtext,
-									   &isNull, &isDone);
+	keyval = ExecEvalExprSwitchContext(hashkey, econtext, &isNull, NULL);
 
 	/* ------------------
 	 *	compute the hash function
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 54af882db12333b3d914b5091deab068467d99ec..4b3b4a825050e4acdaa5a17c1f3c93030a2ec81f 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v 1.32 2000/07/17 03:04:53 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v 1.33 2000/08/24 03:29:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -55,6 +55,7 @@ ExecHashJoin(HashJoin *node)
 	TupleTableSlot *inntuple;
 	Node	   *outerVar;
 	ExprContext *econtext;
+	ExprDoneCond isDone;
 	HashJoinTable hashtable;
 	HeapTuple	curtuple;
 	TupleTableSlot *outerTupleSlot;
@@ -83,13 +84,6 @@ ExecHashJoin(HashJoin *node)
 	hashtable = hjstate->hj_HashTable;
 	econtext = hjstate->jstate.cs_ExprContext;
 
-	/* ----------------
-	 *	Reset per-tuple memory context to free any expression evaluation
-	 *	storage allocated in the previous tuple cycle.
-	 * ----------------
-	 */
-	ResetExprContext(econtext);
-
 	/* ----------------
 	 *	Check to see if we're still projecting out tuples from a previous
 	 *	join tuple (because there is a function-returning-set in the
@@ -99,15 +93,22 @@ ExecHashJoin(HashJoin *node)
 	if (hjstate->jstate.cs_TupFromTlist)
 	{
 		TupleTableSlot *result;
-		bool		isDone;
 
 		result = ExecProject(hjstate->jstate.cs_ProjInfo, &isDone);
-		if (!isDone)
+		if (isDone == ExprMultipleResult)
 			return result;
 		/* Done with that source tuple... */
 		hjstate->jstate.cs_TupFromTlist = false;
 	}
 
+	/* ----------------
+	 *	Reset per-tuple memory context to free any expression evaluation
+	 *	storage allocated in the previous tuple cycle.  Note this can't
+	 *	happen until we're done projecting out tuples from a join tuple.
+	 * ----------------
+	 */
+	ResetExprContext(econtext);
+
 	/* ----------------
 	 *	if this is the first call, build the hash table for inner relation
 	 * ----------------
@@ -241,15 +242,15 @@ ExecHashJoin(HashJoin *node)
 			 */
 			if (ExecQual(qual, econtext, false))
 			{
-				ProjectionInfo *projInfo;
 				TupleTableSlot *result;
-				bool		isDone;
 
 				hjstate->jstate.cs_OuterTupleSlot = outerTupleSlot;
-				projInfo = hjstate->jstate.cs_ProjInfo;
-				result = ExecProject(projInfo, &isDone);
-				hjstate->jstate.cs_TupFromTlist = !isDone;
-				return result;
+				result = ExecProject(hjstate->jstate.cs_ProjInfo, &isDone);
+				if (isDone != ExprEndResult)
+				{
+					hjstate->jstate.cs_TupFromTlist = (isDone == ExprMultipleResult);
+					return result;
+				}
 			}
 		}
 
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 57770d2405806ce5c06fadfc2bce8a95ccbce3bb..a8b29514b882e9538792f0d56f4488f816215413 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeIndexscan.c,v 1.53 2000/08/13 02:50:03 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeIndexscan.c,v 1.54 2000/08/24 03:29:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -334,7 +334,6 @@ ExecIndexReScan(IndexScan *node, ExprContext *exprCtxt, Plan *parent)
 	Node	   *scanexpr;
 	Datum		scanvalue;
 	bool		isNull;
-	bool		isDone;
 
 	estate = node->scan.plan.state;
 	indexstate = node->indxstate;
@@ -411,14 +410,10 @@ ExecIndexReScan(IndexScan *node, ExprContext *exprCtxt, Plan *parent)
 						(Node *) get_rightop(clause) :
 						(Node *) get_leftop(clause);
 
-					/*
-					 * pass in isDone but ignore it.  We don't iterate in
-					 * quals
-					 */
 					scanvalue = ExecEvalExprSwitchContext(scanexpr,
 														  econtext,
 														  &isNull,
-														  &isDone);
+														  NULL);
 					scan_keys[j].sk_argument = scanvalue;
 					if (isNull)
 						scan_keys[j].sk_flags |= SK_ISNULL;
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index a3f92b06901392dec85ab99cb15335a3c09cb394..5a2f45028a0348d44ed9e75352eb472e9e96a44d 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeMergejoin.c,v 1.36 2000/07/12 02:37:03 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeMergejoin.c,v 1.37 2000/08/24 03:29:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -226,18 +226,16 @@ MergeCompare(List *eqQual, List *compareQual, ExprContext *econtext)
 	{
 		Datum		const_value;
 		bool		isNull;
-		bool		isDone;
 
 		/* ----------------
 		 *	 first test if our compare clause is satisfied.
 		 *	 if so then return true.
 		 *
 		 *	 A NULL result is considered false.
-		 *	 ignore isDone, don't iterate in quals.
 		 * ----------------
 		 */
 		const_value = ExecEvalExpr((Node *) lfirst(clause), econtext,
-								   &isNull, &isDone);
+								   &isNull, NULL);
 
 		if (DatumGetBool(const_value) && !isNull)
 		{
@@ -254,7 +252,7 @@ MergeCompare(List *eqQual, List *compareQual, ExprContext *econtext)
 		const_value = ExecEvalExpr((Node *) lfirst(eqclause),
 								   econtext,
 								   &isNull,
-								   &isDone);
+								   NULL);
 
 		if (! DatumGetBool(const_value) || isNull)
 			break;				/* return false */
@@ -447,13 +445,6 @@ ExecMergeJoin(MergeJoin *node)
 		innerSkipQual = mergestate->mj_OuterSkipQual;
 	}
 
-	/* ----------------
-	 *	Reset per-tuple memory context to free any expression evaluation
-	 *	storage allocated in the previous tuple cycle.
-	 * ----------------
-	 */
-	ResetExprContext(econtext);
-
 	/* ----------------
 	 *	Check to see if we're still projecting out tuples from a previous
 	 *	join tuple (because there is a function-returning-set in the
@@ -463,15 +454,23 @@ ExecMergeJoin(MergeJoin *node)
 	if (mergestate->jstate.cs_TupFromTlist)
 	{
 		TupleTableSlot *result;
-		bool		isDone;
+		ExprDoneCond	isDone;
 
 		result = ExecProject(mergestate->jstate.cs_ProjInfo, &isDone);
-		if (!isDone)
+		if (isDone == ExprMultipleResult)
 			return result;
 		/* Done with that source tuple... */
 		mergestate->jstate.cs_TupFromTlist = false;
 	}
 
+	/* ----------------
+	 *	Reset per-tuple memory context to free any expression evaluation
+	 *	storage allocated in the previous tuple cycle.  Note this can't
+	 *	happen until we're done projecting out tuples from a join tuple.
+	 * ----------------
+	 */
+	ResetExprContext(econtext);
+
 	/* ----------------
 	 *	ok, everything is setup.. let's go to work
 	 * ----------------
@@ -599,17 +598,19 @@ ExecMergeJoin(MergeJoin *node)
 					 *	projection tuple and return the slot containing it.
 					 * ----------------
 					 */
-					ProjectionInfo *projInfo;
 					TupleTableSlot *result;
-					bool		isDone;
+					ExprDoneCond isDone;
 
 					MJ_printf("ExecMergeJoin: **** returning tuple ****\n");
 
-					projInfo = mergestate->jstate.cs_ProjInfo;
+					result = ExecProject(mergestate->jstate.cs_ProjInfo,
+										 &isDone);
 
-					result = ExecProject(projInfo, &isDone);
-					mergestate->jstate.cs_TupFromTlist = !isDone;
-					return result;
+					if (isDone != ExprEndResult)
+					{
+						mergestate->jstate.cs_TupFromTlist = (isDone == ExprMultipleResult);
+						return result;
+					}
 				}
 				break;
 
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index f59c1b0f602d2d518abb15581308da85752a7946..3685232c7e42b15e5b9777e81a573920d461ec5f 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeNestloop.c,v 1.19 2000/08/13 02:50:03 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeNestloop.c,v 1.20 2000/08/24 03:29:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -87,13 +87,6 @@ ExecNestLoop(NestLoop *node)
 	outerTupleSlot = nlstate->jstate.cs_OuterTupleSlot;
 	econtext->ecxt_outertuple = outerTupleSlot;
 
-	/* ----------------
-	 *	Reset per-tuple memory context to free any expression evaluation
-	 *	storage allocated in the previous tuple cycle.
-	 * ----------------
-	 */
-	ResetExprContext(econtext);
-
 	/* ----------------
 	 *	Check to see if we're still projecting out tuples from a previous
 	 *	join tuple (because there is a function-returning-set in the
@@ -103,15 +96,23 @@ ExecNestLoop(NestLoop *node)
 	if (nlstate->jstate.cs_TupFromTlist)
 	{
 		TupleTableSlot *result;
-		bool		isDone;
+		ExprDoneCond	isDone;
 
 		result = ExecProject(nlstate->jstate.cs_ProjInfo, &isDone);
-		if (!isDone)
+		if (isDone == ExprMultipleResult)
 			return result;
 		/* Done with that source tuple... */
 		nlstate->jstate.cs_TupFromTlist = false;
 	}
 
+	/* ----------------
+	 *	Reset per-tuple memory context to free any expression evaluation
+	 *	storage allocated in the previous tuple cycle.  Note this can't
+	 *	happen until we're done projecting out tuples from a join tuple.
+	 * ----------------
+	 */
+	ResetExprContext(econtext);
+
 	/* ----------------
 	 *	Ok, everything is setup for the join so now loop until
 	 *	we return a qualifying join tuple..
@@ -219,16 +220,18 @@ ExecNestLoop(NestLoop *node)
 			 *	using ExecProject().
 			 * ----------------
 			 */
-			ProjectionInfo *projInfo;
 			TupleTableSlot *result;
-			bool		isDone;
+			ExprDoneCond isDone;
 
 			ENL1_printf("qualification succeeded, projecting tuple");
 
-			projInfo = nlstate->jstate.cs_ProjInfo;
-			result = ExecProject(projInfo, &isDone);
-			nlstate->jstate.cs_TupFromTlist = !isDone;
-			return result;
+			result = ExecProject(nlstate->jstate.cs_ProjInfo, &isDone);
+
+			if (isDone != ExprEndResult)
+			{
+				nlstate->jstate.cs_TupFromTlist = (isDone == ExprMultipleResult);
+				return result;
+			}
 		}
 
 		/* ----------------
diff --git a/src/backend/executor/nodeResult.c b/src/backend/executor/nodeResult.c
index 770cc47ccc4f405b80e6f1083e1b35ac6c1c36f7..e36037de7108f63004728135099024581b025fc2 100644
--- a/src/backend/executor/nodeResult.c
+++ b/src/backend/executor/nodeResult.c
@@ -34,7 +34,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeResult.c,v 1.15 2000/07/17 03:04:53 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeResult.c,v 1.16 2000/08/24 03:29:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -67,8 +67,7 @@ ExecResult(Result *node)
 	TupleTableSlot *resultSlot;
 	Plan	   *outerPlan;
 	ExprContext *econtext;
-	bool		isDone;
-	ProjectionInfo *projInfo;
+	ExprDoneCond isDone;
 
 	/* ----------------
 	 *	initialize the result node's state
@@ -77,13 +76,6 @@ ExecResult(Result *node)
 	resstate = node->resstate;
 	econtext = resstate->cstate.cs_ExprContext;
 
-	/* ----------------
-	 *	Reset per-tuple memory context to free any expression evaluation
-	 *	storage allocated in the previous tuple cycle.
-	 * ----------------
-	 */
-	ResetExprContext(econtext);
-
 	/* ----------------
 	 *	 check constant qualifications like (2 > 1), if not already done
 	 * ----------------
@@ -111,12 +103,20 @@ ExecResult(Result *node)
 	if (resstate->cstate.cs_TupFromTlist)
 	{
 		resultSlot = ExecProject(resstate->cstate.cs_ProjInfo, &isDone);
-		if (!isDone)
+		if (isDone == ExprMultipleResult)
 			return resultSlot;
 		/* Done with that source tuple... */
 		resstate->cstate.cs_TupFromTlist = false;
 	}
 
+	/* ----------------
+	 *	Reset per-tuple memory context to free any expression evaluation
+	 *	storage allocated in the previous tuple cycle.  Note this can't
+	 *	happen until we're done projecting out tuples from a scan tuple.
+	 * ----------------
+	 */
+	ResetExprContext(econtext);
+
 	/* ----------------
 	 *	if rs_done is true then it means that we were asked to return
 	 *	a constant tuple and we already did the last time ExecResult()
@@ -124,7 +124,7 @@ ExecResult(Result *node)
 	 *	Either way, now we are through.
 	 * ----------------
 	 */
-	if (!resstate->rs_done)
+	while (!resstate->rs_done)
 	{
 		outerPlan = outerPlan(node);
 
@@ -159,13 +159,18 @@ ExecResult(Result *node)
 		}
 
 		/* ----------------
-		 *	 form the result tuple using ExecProject(), and return it.
+		 *	 form the result tuple using ExecProject(), and return it
+		 *	 --- unless the projection produces an empty set, in which case
+		 *	 we must loop back to see if there are more outerPlan tuples.
 		 * ----------------
 		 */
-		projInfo = resstate->cstate.cs_ProjInfo;
-		resultSlot = ExecProject(projInfo, &isDone);
-		resstate->cstate.cs_TupFromTlist = !isDone;
-		return resultSlot;
+		resultSlot = ExecProject(resstate->cstate.cs_ProjInfo, &isDone);
+
+		if (isDone != ExprEndResult)
+		{
+			resstate->cstate.cs_TupFromTlist = (isDone == ExprMultipleResult);
+			return resultSlot;
+		}
 	}
 
 	return NULL;
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 3d331c714f74ef853d3150052499a663ba135bf1..aee6911e5e424d5794d624db3473f9373c393e25 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeSubplan.c,v 1.26 2000/07/12 02:37:04 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeSubplan.c,v 1.27 2000/08/24 03:29:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -40,7 +40,6 @@ ExecSubPlan(SubPlan *node, List *pvar, ExprContext *econtext, bool *isNull)
 	MemoryContext oldcontext;
 	TupleTableSlot *slot;
 	Datum		result;
-	bool		isDone;
 	bool		found = false;	/* TRUE if got at least one subplan tuple */
 	List	   *lst;
 
@@ -67,9 +66,7 @@ ExecSubPlan(SubPlan *node, List *pvar, ExprContext *econtext, bool *isNull)
 			prm->value = ExecEvalExprSwitchContext((Node *) lfirst(pvar),
 												   econtext,
 												   &(prm->isnull),
-												   &isDone);
-			if (!isDone)
-				elog(ERROR, "ExecSubPlan: set values not supported for params");
+												   NULL);
 			pvar = lnext(pvar);
 		}
 		plan->chgParam = nconc(plan->chgParam, listCopy(node->parParam));
@@ -189,9 +186,7 @@ ExecSubPlan(SubPlan *node, List *pvar, ExprContext *econtext, bool *isNull)
 			 * Now we can eval the combining operator for this column.
 			 */
 			expresult = ExecEvalExprSwitchContext((Node *) expr, econtext,
-												  &expnull, &isDone);
-			if (!isDone)
-				elog(ERROR, "ExecSubPlan: set values not supported for combining operators");
+												  &expnull, NULL);
 
 			/*
 			 * Combine the result into the row result as appropriate.
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index ac6511dcbfb5a0a4688bbc51a06738c7fa5834fe..055d07998b9108837c99ec3543d9b562fbb601a3 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeTidscan.c,v 1.11 2000/08/03 19:19:30 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeTidscan.c,v 1.12 2000/08/24 03:29:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -38,7 +38,6 @@ TidListCreate(List *evalList, ExprContext *econtext, ItemPointer *tidList)
 	List	   *lst;
 	ItemPointer itemptr;
 	bool		isNull;
-	bool		isDone;
 	int			numTids = 0;
 
 	foreach(lst, evalList)
@@ -47,8 +46,8 @@ TidListCreate(List *evalList, ExprContext *econtext, ItemPointer *tidList)
 			DatumGetPointer(ExecEvalExprSwitchContext(lfirst(lst),
 													  econtext,
 													  &isNull,
-													  &isDone));
-		if (itemptr && ItemPointerIsValid(itemptr))
+													  NULL));
+		if (!isNull && itemptr && ItemPointerIsValid(itemptr))
 		{
 			tidList[numTids] = itemptr;
 			numTids++;
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 27ae4056d99124a82d478d4e0b4fa08c48d686b5..05f32d25972d24dd10a4f72dba920807d0b9c704 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.93 2000/08/13 02:50:04 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.94 2000/08/24 03:29:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1380,9 +1380,8 @@ clause_pred_clause_test(Expr *predicate, Node *clause)
 							  copyObject(clause_const),
 							  copyObject(pred_const));
 
-#ifndef OMIT_PARTIAL_INDEX
 	test_result = ExecEvalExpr((Node *) test_expr, NULL, &isNull, NULL);
-#endif	 /* OMIT_PARTIAL_INDEX */
+
 	if (isNull)
 	{
 		elog(DEBUG, "clause_pred_clause_test: null test result");
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 22d93b26e7ba33c0d62f378565f34f1852b66582..cf0b6dd703c76b3a904c4322b47b49b9b11ee5fc 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.72 2000/08/21 17:22:34 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.73 2000/08/24 03:29:05 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -1447,7 +1447,6 @@ simplify_op_or_func(Expr *expr, List *args)
 	bool		has_nonconst_input = false;
 	bool		has_null_input = false;
 	bool		const_is_null;
-	bool		isDone;
 
 	/*
 	 * Check for constant inputs and especially constant-NULL inputs.
@@ -1566,8 +1565,7 @@ simplify_op_or_func(Expr *expr, List *args)
 	econtext = MakeExprContext(NULL, CurrentMemoryContext);
 
 	const_val = ExecEvalExprSwitchContext((Node *) newexpr, econtext,
-										  &const_is_null, &isDone);
-	Assert(isDone);				/* if this isn't set, we blew it... */
+										  &const_is_null, NULL);
 
 	/* Must copy result out of sub-context used by expression eval */
 	const_val = datumCopy(const_val, resultTypByVal, resultTypLen);
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 551e7d2b585510092c12ba8fd244f620c0d7162f..bad9401c609f9118525c006b425477c6a1bf50eb 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.88 2000/08/20 00:44:18 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.89 2000/08/24 03:29:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -560,13 +560,13 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
 	{							/* we know all of these fields already */
 
 		/*
-		 * We create a funcnode with a placeholder function SetEval.
-		 * SetEval() never actually gets executed.	When the function
-		 * evaluation routines see it, they use the funcid projected out
-		 * from the relation as the actual function to call. Example:
-		 * retrieve (emp.mgr.name) The plan for this will scan the emp
-		 * relation, projecting out the mgr attribute, which is a funcid.
-		 * This function is then called (instead of SetEval) and "name" is
+		 * We create a funcnode with a placeholder function seteval().
+		 * At runtime, seteval() will execute the function identified
+		 * by the funcid it receives as parameter.
+		 *
+		 * Example: retrieve (emp.mgr.name).  The plan for this will scan the
+		 * emp relation, projecting out the mgr attribute, which is a funcid.
+		 * This function is then called (via seteval()) and "name" is
 		 * projected from its result.
 		 */
 		funcid = F_SETEVAL;
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 2bda101538ce88af27123b4df1649b75e393d97e..5d363ea3e690d085a5545a816d629ee1ae873feb 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.44 2000/08/08 15:42:04 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.45 2000/08/24 03:29:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -34,7 +34,6 @@
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
-static void disallow_setop(char *op, Type optype, Node *operand);
 static bool fitsInFloat(Value *value);
 
 
@@ -71,7 +70,6 @@ make_operand(char *opname,
 
 	if (tree != NULL)
 	{
-		disallow_setop(opname, target_type, tree);
 		/* must coerce? */
 		if (target_typeId != orig_typeId)
 			result = coerce_type(NULL, tree, orig_typeId, target_typeId, -1);
@@ -96,21 +94,6 @@ make_operand(char *opname,
 }	/* make_operand() */
 
 
-static void
-disallow_setop(char *op, Type optype, Node *operand)
-{
-	if (operand == NULL)
-		return;
-
-	if (nodeTag(operand) == T_Iter)
-	{
-		elog(ERROR, "An operand to the '%s' operator returns a set of %s,"
-			 "\n\tbut '%s' takes single values, not sets.",
-			 op, typeTypeName(optype), op);
-	}
-}
-
-
 /* make_op()
  * Operator construction.
  *
diff --git a/src/backend/utils/adt/sets.c b/src/backend/utils/adt/sets.c
index a0c0aa8cb14e3738e13119f483ff61b2ab7d5f3a..9a5f05134cd45f275b130f8d11ac16cdd5fd9578 100644
--- a/src/backend/utils/adt/sets.c
+++ b/src/backend/utils/adt/sets.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/Attic/sets.c,v 1.32 2000/06/09 01:11:09 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/Attic/sets.c,v 1.33 2000/08/24 03:29:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -21,6 +21,8 @@
 #include "catalog/catname.h"
 #include "catalog/indexing.h"
 #include "catalog/pg_proc.h"
+#include "executor/executor.h"
+#include "utils/fcache.h"
 #include "utils/sets.h"
 #include "utils/syscache.h"
 
@@ -30,9 +32,9 @@ extern CommandDest whereToSendOutput;	/* defined in tcop/postgres.c */
 /*
  *	  SetDefine		   - converts query string defining set to an oid
  *
- *	  The query string is used to store the set as a function in
- *	  pg_proc.	The name of the function is then changed to use the
- *	  OID of its tuple in pg_proc.
+ *	  We create an SQL function having the given querystring as its body.
+ *	  The name of the function is then changed to use the OID of its tuple
+ *	  in pg_proc.
  */
 Oid
 SetDefine(char *querystr, char *typename)
@@ -57,11 +59,11 @@ SetDefine(char *querystr, char *typename)
 							 querystr,	/* sourceCode */
 							 fileName,	/* fileName */
 							 true,		/* trusted */
-							 false,		/* canCache XXX appropriate? */
-							 false,		/* isStrict XXX appropriate? */
+							 false,		/* canCache (assume unsafe) */
+							 false,		/* isStrict (irrelevant, no args) */
 							 100,		/* byte_pct */
-							 0, /* perbyte_cpu */
-							 0, /* percall_cpu */
+							 0,			/* perbyte_cpu */
+							 0,			/* percall_cpu */
 							 100,		/* outin_ratio */
 							 NIL,		/* argList */
 							 whereToSendOutput);
@@ -74,11 +76,12 @@ SetDefine(char *querystr, char *typename)
 	 * until you start the next command.)
 	 */
 	CommandCounterIncrement();
+
 	tup = SearchSysCacheTuple(PROCOID,
 							  ObjectIdGetDatum(setoid),
 							  0, 0, 0);
 	if (!HeapTupleIsValid(tup))
-		elog(ERROR, "setin: unable to define set %s", querystr);
+		elog(ERROR, "SetDefine: unable to define set %s", querystr);
 
 	/*
 	 * We can tell whether the set was already defined by checking the
@@ -86,7 +89,7 @@ SetDefine(char *querystr, char *typename)
 	 * oid>" it's already defined.
 	 */
 	proc = (Form_pg_proc) GETSTRUCT(tup);
-	if (!strcmp((char *) procname, (char *) &(proc->proname)))
+	if (strcmp(procname, NameStr(proc->proname)) == 0)
 	{
 		/* make the real proc name */
 		sprintf(realprocname, "set%u", setoid);
@@ -120,7 +123,7 @@ SetDefine(char *querystr, char *typename)
 			setoid = newtup->t_data->t_oid;
 		}
 		else
-			elog(ERROR, "setin: could not find new set oid tuple");
+			elog(ERROR, "SetDefine: could not find new set oid tuple");
 
 		if (RelationGetForm(procrel)->relhasindex)
 		{
@@ -132,20 +135,79 @@ SetDefine(char *querystr, char *typename)
 		}
 		heap_close(procrel, RowExclusiveLock);
 	}
+
 	return setoid;
 }
 
-/* This function is a placeholder.	The parser uses the OID of this
- * function to fill in the :funcid field  of a set.  This routine is
- * never executed.	At runtime, the OID of the actual set is substituted
- * into the :funcid.
+/*
+ * This function executes set evaluation.  The parser sets up a set reference
+ * as a call to this function with the OID of the set to evaluate as argument.
+ *
+ * We build a new fcache for execution of the set's function and run the
+ * function until it says "no mas".  The fn_extra field of the call's
+ * FmgrInfo record is a handy place to hold onto the fcache.  (Since this
+ * is a built-in function, there is no competing use of fn_extra.)
  */
 Datum
 seteval(PG_FUNCTION_ARGS)
 {
 	Oid			funcoid = PG_GETARG_OID(0);
+	FunctionCachePtr fcache;
+	Datum		result;
+	bool		isNull;
+	ExprDoneCond isDone;
+
+	/*
+	 * If this is the first call, we need to set up the fcache for the
+	 * target set's function.
+	 */
+	fcache = (FunctionCachePtr) fcinfo->flinfo->fn_extra;
+	if (fcache == NULL)
+	{
+		fcache = init_fcache(funcoid, 0, fcinfo->flinfo->fn_mcxt);
+		fcinfo->flinfo->fn_extra = (void *) fcache;
+	}
+
+	/*
+	 * Evaluate the function.  NOTE: we need no econtext because there
+	 * are no arguments to evaluate.
+	 */
+
+	/* ExecMakeFunctionResult assumes these are initialized at call: */
+	isNull = false;
+	isDone = ExprSingleResult;
 
-	elog(ERROR, "seteval called for OID %u", funcoid);
+	result = ExecMakeFunctionResult(fcache,
+									NIL,
+									NULL, /* no econtext, see above */
+									&isNull,
+									&isDone);
+
+	/*
+	 * If we're done with the results of this set function, get rid of
+	 * its func cache so that we will start from the top next time.
+	 * (Can you say "memory leak"?  This feature is a crock anyway...)
+	 */
+	if (isDone != ExprMultipleResult)
+	{
+		pfree(fcache);
+		fcinfo->flinfo->fn_extra = NULL;
+	}
+
+	/*
+	 * Return isNull/isDone status.
+	 */
+	fcinfo->isnull = isNull;
+
+	if (isDone != ExprSingleResult)
+	{
+		ReturnSetInfo  *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+		if (rsi && IsA(rsi, ReturnSetInfo))
+			rsi->isDone = isDone;
+		else
+			elog(ERROR, "Set-valued function called in context that cannot accept a set");
+	}
 
-	PG_RETURN_INT32(0);			/* keep compiler happy */
+	PG_RETURN_DATUM(result);
 }
diff --git a/src/backend/utils/cache/fcache.c b/src/backend/utils/cache/fcache.c
index 080f70b6ce8fd5d9e4444a42be7a5879558faff0..9186f34d104737af5144649f8eb71b7f42ef4ca2 100644
--- a/src/backend/utils/cache/fcache.c
+++ b/src/backend/utils/cache/fcache.c
@@ -1,267 +1,61 @@
 /*-------------------------------------------------------------------------
  *
  * fcache.c
- *	  Code for the 'function cache' used in Oper and Func nodes....
+ *	  Code for the 'function cache' used in Oper and Func nodes.
+ *
  *
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/cache/Attic/fcache.c,v 1.36 2000/08/11 18:35:50 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/cache/Attic/fcache.c,v 1.37 2000/08/24 03:29:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
-#include "access/heapam.h"
-#include "catalog/pg_language.h"
-#include "catalog/pg_proc.h"
-#include "catalog/pg_type.h"
-#include "parser/parsetree.h"
-#include "utils/builtins.h"
-#include "utils/fcache2.h"
-#include "utils/syscache.h"
-
-static Oid	GetDynamicFuncArgType(Var *arg, ExprContext *econtext);
-static FunctionCachePtr init_fcache(Oid foid,
-									List *argList,
-									ExprContext *econtext);
-
-#define FuncArgTypeIsDynamic(arg) \
-	(IsA(arg,Var) && ((Var*)arg)->varattno == InvalidAttrNumber)
-
-static Oid
-GetDynamicFuncArgType(Var *arg, ExprContext *econtext)
-{
-	char	   *relname;
-	int			rtid;
-	HeapTuple	tup;
-
-	Assert(IsA(arg, Var));
+#include "utils/fcache.h"
 
-	rtid = ((Var *) arg)->varno;
-	relname = (char *) getrelname(rtid, econtext->ecxt_range_table);
-
-	tup = SearchSysCacheTuple(TYPENAME,
-							  PointerGetDatum(relname),
-							  0, 0, 0);
-	if (!tup)
-		elog(ERROR, "Lookup failed on type tuple for class %s",
-			 relname);
-
-	return tup->t_data->t_oid;
-}
 
 /*-----------------------------------------------------------------
  *
- * Initialize a 'FunctionCache' struct given the PG_PROC oid.
+ * Build a 'FunctionCache' struct given the PG_PROC oid.
  *
  *-----------------------------------------------------------------
  */
-static FunctionCachePtr
-init_fcache(Oid foid,
-			List *argList,
-			ExprContext *econtext)
+FunctionCachePtr
+init_fcache(Oid foid, int nargs, MemoryContext fcacheCxt)
 {
-	HeapTuple	procedureTuple;
-	HeapTuple	typeTuple;
-	Form_pg_proc procedureStruct;
-	Form_pg_type typeStruct;
+	MemoryContext oldcontext;
 	FunctionCachePtr retval;
-	int			nargs;
-	Datum		tmp;
-	bool		isNull;
+
+	/* Switch to a context long-lived enough for the fcache entry */
+	oldcontext = MemoryContextSwitchTo(fcacheCxt);
 
 	retval = (FunctionCachePtr) palloc(sizeof(FunctionCache));
 	MemSet(retval, 0, sizeof(FunctionCache));
-	retval->fcacheCxt = CurrentMemoryContext;
-
-	/* ----------------
-	 *	 get the procedure tuple corresponding to the given functionOid
-	 *
-	 *	 NB: use SearchSysCacheTupleCopy to ensure tuple lives long enough
-	 * ----------------
-	 */
-	procedureTuple = SearchSysCacheTupleCopy(PROCOID,
-											 ObjectIdGetDatum(foid),
-											 0, 0, 0);
 
-	if (!HeapTupleIsValid(procedureTuple))
-		elog(ERROR, "init_fcache: Cache lookup failed for procedure %u",
-			 foid);
+	/* Set up the primary fmgr lookup information */
+	fmgr_info(foid, &(retval->func));
 
-	procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);
+	/* Initialize unvarying fields of per-call info block */
+	retval->fcinfo.flinfo = &(retval->func);
+	retval->fcinfo.nargs = nargs;
 
-	/* ----------------
-	 *	 get the return type from the procedure tuple
-	 * ----------------
-	 */
-	typeTuple = SearchSysCacheTuple(TYPEOID,
-						   ObjectIdGetDatum(procedureStruct->prorettype),
-									0, 0, 0);
+	if (nargs > FUNC_MAX_ARGS)
+		elog(ERROR, "init_fcache: too many arguments");
 
-	if (!HeapTupleIsValid(typeTuple))
-		elog(ERROR, "init_fcache: Cache lookup failed for type %u",
-			 procedureStruct->prorettype);
-
-	typeStruct = (Form_pg_type) GETSTRUCT(typeTuple);
-
-	/* ----------------
-	 *	 get the type length and by-value flag from the type tuple
-	 * ----------------
-	 */
-	retval->typlen = typeStruct->typlen;
-	if (typeStruct->typrelid == InvalidOid)
+	/* If function returns set, prepare a resultinfo node for communication */
+	if (retval->func.fn_retset)
 	{
-		/* The return type is not a relation, so just use byval */
-		retval->typbyval = typeStruct->typbyval;
-		retval->returnsTuple = false;
+		retval->fcinfo.resultinfo = (Node *) &(retval->rsinfo);
+		retval->rsinfo.type = T_ReturnSetInfo;
 	}
-	else
-	{
 
-		/*
-		 * This is a hack.	We assume here that any function returning a
-		 * tuple returns it by reference.  This needs to be fixed, since
-		 * actually the mechanism isn't quite like return-by-reference.
-		 */
-		retval->typbyval = false;
-		retval->returnsTuple = true;
-	}
-	retval->foid = foid;
-	retval->language = procedureStruct->prolang;
-	retval->returnsSet = procedureStruct->proretset;
+	retval->argsValid = false;
 	retval->hasSetArg = false;
-	retval->func_state = (char *) NULL;
-	retval->setArg = (Datum) 0;
-
-	/*
-	 * If we are returning exactly one result then we have to copy tuples
-	 * and by reference results because we have to end the execution
-	 * before we return the results.  When you do this everything
-	 * allocated by the executor (i.e. slots and tuples) is freed.
-	 */
-	if ((retval->language == SQLlanguageId) &&
-		!retval->returnsSet &&
-		!retval->typbyval)
-	{
-		TupleTableSlot *slot;
-
-		slot = makeNode(TupleTableSlot);
-		slot->ttc_shouldFree = true;
-		slot->ttc_descIsNew = true;
-		slot->ttc_tupleDescriptor = (TupleDesc) NULL;
-		slot->ttc_buffer = InvalidBuffer;
-		slot->ttc_whichplan = -1;
-
-		retval->funcSlot = (Pointer) slot;
-	}
-	else
-		retval->funcSlot = (Pointer) NULL;
-
-	nargs = procedureStruct->pronargs;
-	retval->nargs = nargs;
-
-	if (nargs > 0)
-	{
-		Oid		   *argTypes;
-
-		if (retval->language == SQLlanguageId)
-		{
-			int			i;
-			List	   *oneArg;
-
-			retval->argOidVect = (Oid *) palloc(retval->nargs * sizeof(Oid));
-			argTypes = procedureStruct->proargtypes;
-			memmove(retval->argOidVect,
-					argTypes,
-					(retval->nargs) * sizeof(Oid));
 
-			for (i = 0;
-				 argList;
-				 i++, argList = lnext(argList))
-			{
-				oneArg = lfirst(argList);
-				if (FuncArgTypeIsDynamic(oneArg))
-					retval->argOidVect[i] = GetDynamicFuncArgType((Var *) oneArg,
-															   econtext);
-			}
-		}
-		else
-			retval->argOidVect = (Oid *) NULL;
-	}
-	else
-	{
-		retval->argOidVect = (Oid *) NULL;
-	}
-
-	if (procedureStruct->prolang == SQLlanguageId)
-	{
-		tmp = SysCacheGetAttr(PROCOID,
-							  procedureTuple,
-							  Anum_pg_proc_prosrc,
-							  &isNull);
-		if (isNull)
-			elog(ERROR, "init_fcache: null prosrc for procedure %u",
-				 foid);
-		retval->src = DatumGetCString(DirectFunctionCall1(textout, tmp));
-		retval->bin = (char *) NULL;
-	}
-	else
-	{
-		retval->src = (char *) NULL;
-		if (procedureStruct->proistrusted)
-			retval->bin = (char *) NULL;
-		else
-		{
-			tmp = SysCacheGetAttr(PROCOID,
-								  procedureTuple,
-								  Anum_pg_proc_probin,
-								  &isNull);
-			if (isNull)
-				elog(ERROR, "init_fcache: null probin for procedure %u",
-					 foid);
-			retval->bin = DatumGetCString(DirectFunctionCall1(textout, tmp));
-		}
-	}
-
-	if (retval->language != SQLlanguageId)
-	{
-		fmgr_info(foid, &(retval->func));
-		retval->nargs = retval->func.fn_nargs;
-	}
-	else
-		retval->func.fn_addr = (PGFunction) NULL;
-
-	heap_freetuple(procedureTuple);
+	MemoryContextSwitchTo(oldcontext);
 
 	return retval;
 }
-
-void
-setFcache(Node *node, Oid foid, List *argList, ExprContext *econtext)
-{
-	MemoryContext oldcontext;
-	FunctionCachePtr fcache;
-
-	/* Switch to a context long-lived enough for the fcache entry */
-	oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-
-	fcache = init_fcache(foid, argList, econtext);
-
-	if (IsA(node, Oper))
-	{
-		Oper	   *onode = (Oper *) node;
-		onode->op_fcache = fcache;
-	}
-	else if (IsA(node, Func))
-	{
-		Func	   *fnode = (Func *) node;
-		fnode->func_fcache = fcache;
-	}
-	else
-		elog(ERROR, "init_fcache: node must be Oper or Func!");
-
-	MemoryContextSwitchTo(oldcontext);
-}
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 7b8aca3feb4a6887aa400ea9d98b0d35824b71f8..c7fbf251f9d96b6d52d591be6f5c4af852c20edf 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/fmgr/fmgr.c,v 1.45 2000/07/06 05:48:13 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/fmgr/fmgr.c,v 1.46 2000/08/24 03:29:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -17,6 +17,7 @@
 
 #include "catalog/pg_language.h"
 #include "catalog/pg_proc.h"
+#include "executor/functions.h"
 #include "utils/builtins.h"
 #include "utils/fmgrtab.h"
 #include "utils/syscache.h"
@@ -44,7 +45,6 @@ typedef char *((*func_ptr) ());
 
 static Datum fmgr_oldstyle(PG_FUNCTION_ARGS);
 static Datum fmgr_untrusted(PG_FUNCTION_ARGS);
-static Datum fmgr_sql(PG_FUNCTION_ARGS);
 
 
 /*
@@ -111,6 +111,7 @@ fmgr_info(Oid functionId, FmgrInfo *finfo)
 
 	finfo->fn_oid = functionId;
 	finfo->fn_extra = NULL;
+	finfo->fn_mcxt = CurrentMemoryContext;
 
 	if ((fbp = fmgr_isbuiltin(functionId)) != NULL)
 	{
@@ -119,6 +120,7 @@ fmgr_info(Oid functionId, FmgrInfo *finfo)
 		 */
 		finfo->fn_nargs = fbp->nargs;
 		finfo->fn_strict = fbp->strict;
+		finfo->fn_retset = false; /* assume no builtins return sets! */
 		if (fbp->oldstyle)
 		{
 			finfo->fn_addr = fmgr_oldstyle;
@@ -142,6 +144,7 @@ fmgr_info(Oid functionId, FmgrInfo *finfo)
 
 	finfo->fn_nargs = procedureStruct->pronargs;
 	finfo->fn_strict = procedureStruct->proisstrict;
+	finfo->fn_retset = procedureStruct->proretset;
 
 	if (!procedureStruct->proistrusted)
 	{
@@ -427,21 +430,6 @@ fmgr_untrusted(PG_FUNCTION_ARGS)
 	return 0;					/* keep compiler happy */
 }
 
-/*
- * Handler for SQL-language functions
- */
-static Datum
-fmgr_sql(PG_FUNCTION_ARGS)
-{
-	/*
-	 * XXX It'd be really nice to support SQL functions anywhere that
-	 * builtins are supported.	What would we have to do?  What pitfalls
-	 * are there?
-	 */
-	elog(ERROR, "SQL-language function not supported in this context");
-	return 0;					/* keep compiler happy */
-}
-
 
 /*-------------------------------------------------------------------------
  *		Support routines for callers of fmgr-compatible functions
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index b50f74fdf4fba9b8d46074f58ad26e02dae2f215..c7e092c7020fec5a75f4518ba29c4a136c698439 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pg_proc.h,v 1.163 2000/08/23 06:04:44 thomas Exp $
+ * $Id: pg_proc.h,v 1.164 2000/08/24 03:29:08 tgl Exp $
  *
  * NOTES
  *	  The script catalog/genbki.sh reads this file and generates .bki
@@ -1266,8 +1266,8 @@ DATA(insert OID = 1036 (  aclremove		   PGUID 12 f t f t 2 f 1034 "1034 1033" 10
 DESCR("remove ACL item");
 DATA(insert OID = 1037 (  aclcontains	   PGUID 12 f t f t 2 f 16 "1034 1033" 100 0 0 100  aclcontains - ));
 DESCR("does ACL contain item?");
-DATA(insert OID = 1038 (  seteval		   PGUID 12 f t f t 1 f 23 "26" 100 0 0 100  seteval - ));
-DESCR("");
+DATA(insert OID = 1038 (  seteval		   PGUID 12 f t f t 1 t 23 "26" 100 0 0 100  seteval - ));
+DESCR("internal function supporting PostQuel-style sets");
 DATA(insert OID = 1044 (  bpcharin		   PGUID 12 f t t t 3 f 1042 "0 26 23" 100 0 0 100 bpcharin - ));
 DESCR("(internal)");
 DATA(insert OID = 1045 (  bpcharout		   PGUID 12 f t t t 1 f 23 "0" 100 0 0 100  bpcharout - ));
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index f0a1e381e1ae383a8ea686eb92b0fe15981a0dd1..2a7a4bdcfa82464f297871162df9275581022b1a 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: defrem.h,v 1.19 2000/02/18 09:29:49 inoue Exp $
+ * $Id: defrem.h,v 1.20 2000/08/24 03:29:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -44,7 +44,6 @@ extern void CreateFunction(ProcedureStmt *stmt, CommandDest dest);
 extern void DefineOperator(char *name, List *parameters);
 extern void DefineAggregate(char *name, List *parameters);
 extern void DefineType(char *name, List *parameters);
-extern void CreateFunction(ProcedureStmt *stmt, CommandDest dest);
 
 /*
  * prototypes in remove.c
diff --git a/src/include/executor/execFlatten.h b/src/include/executor/execFlatten.h
index c7d85e2e6da4282b5dc564efcaefe7a443688dc9..3fa3673b201de37bd695a2d53c60d3ed9dcc54c8 100644
--- a/src/include/executor/execFlatten.h
+++ b/src/include/executor/execFlatten.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: execFlatten.h,v 1.11 2000/01/26 05:58:05 momjian Exp $
+ * $Id: execFlatten.h,v 1.12 2000/08/24 03:29:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -15,11 +15,14 @@
 #define EXECFLATTEN_H
 
 #include "nodes/execnodes.h"
-#include "nodes/relation.h"
+#include "nodes/parsenodes.h"
 
-extern Datum ExecEvalIter(Iter *iterNode, ExprContext *econtext, bool *resultIsNull, bool *iterIsDone);
 
-extern void ExecEvalFjoin(TargetEntry *tlist, ExprContext *econtext, bool *isNullVect, bool *fj_isDone);
+extern Datum ExecEvalIter(Iter *iterNode, ExprContext *econtext,
+						  bool *isNull, ExprDoneCond *isDone);
+
+extern void ExecEvalFjoin(TargetEntry *tlist, ExprContext *econtext,
+						  bool *isNullVect, ExprDoneCond *fj_isDone);
 
 
 #endif	 /* EXECFLATTEN_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index ea589e06dd95e9f8f9555aa56a59f8e21c4de3fc..e39a60a6a2478140b6d7db997e766264e2e49f3b 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: executor.h,v 1.48 2000/08/21 20:55:29 tgl Exp $
+ * $Id: executor.h,v 1.49 2000/08/24 03:29:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -78,14 +78,20 @@ extern char *GetAttributeByNum(TupleTableSlot *slot, AttrNumber attrno,
 				  bool *isNull);
 extern char *GetAttributeByName(TupleTableSlot *slot, char *attname,
 								bool *isNull);
+extern Datum ExecMakeFunctionResult(FunctionCachePtr fcache,
+									List *arguments,
+									ExprContext *econtext,
+									bool *isNull,
+									ExprDoneCond *isDone);
 extern Datum ExecEvalExpr(Node *expression, ExprContext *econtext,
-						  bool *isNull, bool *isDone);
+						  bool *isNull, ExprDoneCond *isDone);
 extern Datum ExecEvalExprSwitchContext(Node *expression, ExprContext *econtext,
-									   bool *isNull, bool *isDone);
+									   bool *isNull, ExprDoneCond *isDone);
 extern bool ExecQual(List *qual, ExprContext *econtext, bool resultForNull);
 extern int	ExecTargetListLength(List *targetlist);
 extern int	ExecCleanTargetListLength(List *targetlist);
-extern TupleTableSlot *ExecProject(ProjectionInfo *projInfo, bool *isDone);
+extern TupleTableSlot *ExecProject(ProjectionInfo *projInfo,
+								   ExprDoneCond *isDone);
 
 /*
  * prototypes from functions in execScan.c
diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h
index 0e55be1980ed24fc6d6a9a1bfbe605ffe45a68f3..649e38e142f2360251012b3566d53549e9eb89ad 100644
--- a/src/include/executor/functions.h
+++ b/src/include/executor/functions.h
@@ -1,24 +1,21 @@
 /*-------------------------------------------------------------------------
  *
  * functions.h
- *
+ *		Declarations for execution of SQL-language functions.
  *
  *
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: functions.h,v 1.13 2000/08/08 15:42:39 tgl Exp $
+ * $Id: functions.h,v 1.14 2000/08/24 03:29:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #ifndef FUNCTIONS_H
 #define FUNCTIONS_H
 
-#include "nodes/parsenodes.h"
-#include "utils/syscache.h"
+#include "fmgr.h"
 
-extern Datum postquel_function(FunctionCallInfo fcinfo,
-							   FunctionCachePtr fcache,
-							   bool *isDone);
+extern Datum fmgr_sql(PG_FUNCTION_ARGS);
 
 #endif	 /* FUNCTIONS_H */
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 274f8f959d86edde2e4ff9e3bf62ce9c4aadbccf..28634262bcc13a964342cb652a23bcfb1058dd93 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -11,7 +11,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: fmgr.h,v 1.9 2000/07/29 03:26:47 tgl Exp $
+ * $Id: fmgr.h,v 1.10 2000/08/24 03:29:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -41,7 +41,9 @@ typedef struct
     Oid         fn_oid;     /* OID of function (NOT of handler, if any) */
     short       fn_nargs;   /* 0..FUNC_MAX_ARGS, or -1 if variable arg count */
     bool        fn_strict;  /* function is "strict" (NULL in => NULL out) */
+	bool		fn_retset;	/* function returns a set (over multiple calls) */
     void       *fn_extra;   /* extra space for use by handler */
+	MemoryContext fn_mcxt;	/* memory context to store fn_extra in */
 } FmgrInfo;
 
 /*
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1ec14f4a9697d35d758ae4b25380b5af9cad950f..9626dbf8b1c0c658122af32860d5ed3118b0c5b9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: execnodes.h,v 1.47 2000/08/22 04:06:22 tgl Exp $
+ * $Id: execnodes.h,v 1.48 2000/08/24 03:29:13 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -120,6 +120,31 @@ typedef struct ExprContext
 	List	   *ecxt_range_table;
 } ExprContext;
 
+/*
+ * Set-result status returned by ExecEvalExpr()
+ */
+typedef enum
+{
+	ExprSingleResult,			/* expression does not return a set */
+	ExprMultipleResult,			/* this result is an element of a set */
+	ExprEndResult				/* there are no more elements in the set */
+} ExprDoneCond;
+
+/*
+ * When calling a function that might return a set (multiple rows),
+ * a node of this type is passed as fcinfo->resultinfo to allow
+ * return status to be passed back.  A function returning set should
+ * raise an error if no such resultinfo is provided.
+ *
+ * XXX this mechanism is a quick hack and probably needs to be redesigned.
+ */
+typedef struct ReturnSetInfo
+{
+	NodeTag		type;
+	ExprDoneCond isDone;
+} ReturnSetInfo;
+
+
 /* ----------------
  *		ProjectionInfo node information
  *
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f05e0a0e1c93b784b9ee1c23bcbfd9890011ff8b..d825c8fe395bcd8f64f4d191277884213608104e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: nodes.h,v 1.74 2000/08/11 23:46:54 tgl Exp $
+ * $Id: nodes.h,v 1.75 2000/08/24 03:29:13 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -227,7 +227,9 @@ typedef enum NodeTag
 	 * TAGS FOR FUNCTION-CALL CONTEXT AND RESULTINFO NODES (cf. fmgr.h)
 	 *---------------------
 	 */
-	T_TriggerData = 800			/* in commands/trigger.h */
+	T_TriggerData = 800,		/* in commands/trigger.h */
+	T_ReturnSetInfo				/* in nodes/execnodes.h */
+
 } NodeTag;
 
 /*
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1ad9a3d082ad1bd5ecfbf87771482a910fb7b692..0ef350687dc41a6466dc0192260b875c20424aba 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: primnodes.h,v 1.46 2000/08/08 15:42:59 tgl Exp $
+ * $Id: primnodes.h,v 1.47 2000/08/24 03:29:13 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,7 +16,10 @@
 
 #include "access/attnum.h"
 #include "nodes/pg_list.h"
-#include "utils/fcache.h"
+
+/* FunctionCache is declared in utils/fcache.h */
+typedef struct FunctionCache *FunctionCachePtr;
+
 
 /* ----------------------------------------------------------------
  *						node definitions
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 1e16af6874f5f951ad936fe818dc7a8b2f4cb1b5..f1ebe40b5907e7ad2b32dac1ace9ec50ff41be9c 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -7,13 +7,14 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: builtins.h,v 1.133 2000/08/23 06:04:49 thomas Exp $
+ * $Id: builtins.h,v 1.134 2000/08/24 03:29:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #ifndef BUILTINS_H
 #define BUILTINS_H
 
+#include "fmgr.h"
 #include "nodes/relation.h"		/* for amcostestimate parameters */
 #include "storage/itemptr.h"
 #include "utils/numeric.h"
diff --git a/src/include/utils/fcache.h b/src/include/utils/fcache.h
index efae7613959a75a75491483dd5fd3c91ef73d102..a30c72836085085e0fb90c2a1fa86331d93f9fcd 100644
--- a/src/include/utils/fcache.h
+++ b/src/include/utils/fcache.h
@@ -11,7 +11,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: fcache.h,v 1.13 2000/08/08 15:43:12 tgl Exp $
+ * $Id: fcache.h,v 1.14 2000/08/24 03:29:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -19,45 +19,50 @@
 #define FCACHE_H
 
 #include "fmgr.h"
+#include "nodes/execnodes.h"
 
+/*
+ * A FunctionCache record is built for all functions regardless of language.
+ *
+ * We store the fmgr lookup info to avoid recomputing it on each call.
+ * We also store a prebuilt FunctionCallInfo struct.  When evaluating a
+ * function-returning-set, fcinfo holds the argument values across calls
+ * so that we need not re-evaluate the arguments for each call.  Even for
+ * non-set functions, fcinfo saves a few cycles per call by allowing us to
+ * avoid redundant setup of its fields.
+ */
 
-typedef struct
+typedef struct FunctionCache
 {
-	FmgrInfo	func;			/* info for fmgr call mechanism */
-	Oid			foid;			/* oid of the function in pg_proc */
-	Oid			language;		/* oid of the language in pg_language */
-	int			typlen;			/* length of the return type */
-	bool		typbyval;		/* true if return type is pass by value */
-
-	bool		returnsTuple;	/* true if return type is a tuple */
-	bool		returnsSet;		/* true if func returns a set (multi rows) */
-
-	bool		hasSetArg;		/* true if func is part of a nested dot
-								 * expr whose argument is func returning a
-								 * set ugh! */
-
-	/* If additional info is added to an existing fcache, be sure to
-	 * allocate it in the fcacheCxt.
+	/*
+	 * Function manager's lookup info for the target function.
 	 */
-	MemoryContext fcacheCxt;	/* context the fcache lives in */
-
-	int			nargs;			/* actual number of arguments */
-	Oid		   *argOidVect;		/* oids of all the argument types */
-
-	char	   *src;			/* source code of the function */
-	char	   *bin;			/* binary object code ?? */
-	char	   *func_state;		/* function_state struct for execution */
-
-	Pointer		funcSlot;		/* if one result we need to copy it before
-								 * we end execution of the function and
-								 * free stuff */
-
-	Datum		setArg;			/* current argument for nested dot
-								 * execution Nested dot expressions mean
-								 * we have funcs whose argument is a set
-								 * of tuples */
+	FmgrInfo	func;
+	/*
+	 * Per-call info for calling the target function.  Unvarying fields
+	 * are set up by init_fcache().  Argument values are filled in as needed.
+	 */
+	FunctionCallInfoData fcinfo;
+	/*
+	 * "Resultinfo" node --- used only if target function returns a set.
+	 */
+	ReturnSetInfo rsinfo;
+	/*
+	 * argsValid is true when we are evaluating a set-valued function and
+	 * we are in the middle of a call series; we want to pass the same
+	 * argument values to the function again (and again, until it returns
+	 * ExprEndResult).
+	 */
+	bool		argsValid;		/* TRUE if fcinfo contains valid arguments */
+	/*
+	 * hasSetArg is true if we found a set-valued argument to the function.
+	 * This causes the function result to be a set as well.
+	 */
+	bool		hasSetArg;		/* some argument returns a set */
 } FunctionCache;
 
-typedef FunctionCache *FunctionCachePtr;
+
+extern FunctionCachePtr init_fcache(Oid foid, int nargs,
+									MemoryContext fcacheCxt);
 
 #endif	 /* FCACHE_H */
diff --git a/src/include/utils/fcache2.h b/src/include/utils/fcache2.h
deleted file mode 100644
index 984edde4929eef84cb6f9df8963e6bc491f34333..0000000000000000000000000000000000000000
--- a/src/include/utils/fcache2.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * fcache2.h
- *
- *
- *
- * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * $Id: fcache2.h,v 1.10 2000/01/26 05:58:38 momjian Exp $
- *
- *-------------------------------------------------------------------------
- */
-#ifndef FCACHE2_H
-#define FCACHE2_H
-
-#include "nodes/execnodes.h"
-
-extern void setFcache(Node *node, Oid foid, List *argList, ExprContext *econtext);
-
-#endif	 /* FCACHE2_H */
diff --git a/src/include/utils/sets.h b/src/include/utils/sets.h
index a7b5d6826e2bc53087ffdea921eb7dae28353bc8..7f284ac3467d3f13bbf2cf07291f86dd7e9dd1e3 100644
--- a/src/include/utils/sets.h
+++ b/src/include/utils/sets.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: sets.h,v 1.7 2000/06/09 01:11:15 tgl Exp $
+ * $Id: sets.h,v 1.8 2000/08/24 03:29:14 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -17,10 +17,11 @@
 #include "fmgr.h"
 
 
-/* Temporary name of set, before SetDefine changes it. */
-#define GENERICSETNAME "zyxset"
+/* Temporary name of a set function, before SetDefine changes it. */
+#define GENERICSETNAME "ZYX#Set#ZYX"
 
 extern Oid	SetDefine(char *querystr, char *typename);
+
 extern Datum seteval(PG_FUNCTION_ARGS);
 
 #endif	 /* SETS_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 0096b3825c63019e0aac39c97e3bb0b0d285a401..6c761c50f0c843e93cc8d4914c2051090b2a3408 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -3,7 +3,7 @@
  *			  procedural language
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.27 2000/08/13 02:50:35 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.28 2000/08/24 03:29:15 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -2200,7 +2200,6 @@ exec_eval_simple_expr(PLpgSQL_execstate * estate,
 	int			fno;
 	int			i;
 	bool		isnull;
-	bool		isdone;
 	ExprContext *econtext;
 	ParamListInfo paramLI;
 
@@ -2274,9 +2273,7 @@ exec_eval_simple_expr(PLpgSQL_execstate * estate,
 	 * Initialize things
 	 * ----------
 	 */
-	*isNull = FALSE;
 	*rettype = expr->plan_simple_type;
-	isdone = FALSE;
 
 	/* ----------
 	 * Clear the function cache
@@ -2292,14 +2289,17 @@ exec_eval_simple_expr(PLpgSQL_execstate * estate,
 	retval = ExecEvalExprSwitchContext(expr->plan_simple_expr,
 									   econtext,
 									   isNull,
-									   &isdone);
+									   NULL);
 	SPI_pop();
 
 	/*
 	 * Copy the result out of the expression-evaluation memory context,
 	 * so that we can free the expression context.
 	 */
-	retval = datumCopy(retval, get_typbyval(*rettype), get_typlen(*rettype));
+	if (! *isNull)
+		retval = datumCopy(retval,
+						   get_typbyval(*rettype),
+						   get_typlen(*rettype));
 
 	FreeExprContext(econtext);