diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index e8cb2d0b4da69cff407cda4a95501dab95acc1be..03f14800b0e375cb83e1260fb1c05c4aad8a7831 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -3421,7 +3421,7 @@ prepare_query_params(PlanState *node,
 	 * benefit, and it'd require postgres_fdw to know more than is desirable
 	 * about Param evaluation.)
 	 */
-	*param_exprs = (List *) ExecInitExpr((Expr *) fdw_exprs, node);
+	*param_exprs = ExecInitExprList(fdw_exprs, node);
 
 	/* Allocate buffer for text form of query parameters. */
 	*param_values = (const char **) palloc0(numParams * sizeof(char *));
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 6511c6064b8f6df3d5e2b21fc3c66a3a4f5bdd13..6cfce4f8ddb0f897e1b211f9f95e2525cc55a282 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -1084,7 +1084,7 @@ index_register(Oid heap,
 	/* predicate will likely be null, but may as well copy it */
 	newind->il_info->ii_Predicate = (List *)
 		copyObject(indexInfo->ii_Predicate);
-	newind->il_info->ii_PredicateState = NIL;
+	newind->il_info->ii_PredicateState = NULL;
 	/* no exclusion constraints at bootstrap time, so no need to copy */
 	Assert(indexInfo->ii_ExclusionOps == NULL);
 	Assert(indexInfo->ii_ExclusionProcs == NULL);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7924c30369ffadb1ff358862f612712e863ceeda..1eb163f5392e9805682f73285d9cd3ef22f5ba4a 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1658,7 +1658,7 @@ BuildIndexInfo(Relation index)
 
 	/* fetch index predicate if any */
 	ii->ii_Predicate = RelationGetIndexPredicate(index);
-	ii->ii_PredicateState = NIL;
+	ii->ii_PredicateState = NULL;
 
 	/* fetch exclusion constraint info if any */
 	if (indexStruct->indisexclusion)
@@ -1774,9 +1774,8 @@ FormIndexDatum(IndexInfo *indexInfo,
 		indexInfo->ii_ExpressionsState == NIL)
 	{
 		/* First time through, set up expression evaluation state */
-		indexInfo->ii_ExpressionsState = (List *)
-			ExecPrepareExpr((Expr *) indexInfo->ii_Expressions,
-							estate);
+		indexInfo->ii_ExpressionsState =
+			ExecPrepareExprList(indexInfo->ii_Expressions, estate);
 		/* Check caller has set up context correctly */
 		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
 	}
@@ -2208,7 +2207,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
 	double		reltuples;
-	List	   *predicate;
+	ExprState  *predicate;
 	TupleTableSlot *slot;
 	EState	   *estate;
 	ExprContext *econtext;
@@ -2247,9 +2246,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	econtext->ecxt_scantuple = slot;
 
 	/* Set up execution state for predicate, if any. */
-	predicate = (List *)
-		ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
-						estate);
+	predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
 
 	/*
 	 * Prepare for scan of the base relation.  In a normal index build, we use
@@ -2552,9 +2549,9 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 		 * In a partial index, discard tuples that don't satisfy the
 		 * predicate.
 		 */
-		if (predicate != NIL)
+		if (predicate != NULL)
 		{
-			if (!ExecQual(predicate, econtext, false))
+			if (!ExecQual(predicate, econtext))
 				continue;
 		}
 
@@ -2619,7 +2616,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 
 	/* These may have been pointing to the now-gone estate */
 	indexInfo->ii_ExpressionsState = NIL;
-	indexInfo->ii_PredicateState = NIL;
+	indexInfo->ii_PredicateState = NULL;
 
 	return reltuples;
 }
@@ -2646,7 +2643,7 @@ IndexCheckExclusion(Relation heapRelation,
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
-	List	   *predicate;
+	ExprState  *predicate;
 	TupleTableSlot *slot;
 	EState	   *estate;
 	ExprContext *econtext;
@@ -2672,9 +2669,7 @@ IndexCheckExclusion(Relation heapRelation,
 	econtext->ecxt_scantuple = slot;
 
 	/* Set up execution state for predicate, if any. */
-	predicate = (List *)
-		ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
-						estate);
+	predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
 
 	/*
 	 * Scan all live tuples in the base relation.
@@ -2699,9 +2694,9 @@ IndexCheckExclusion(Relation heapRelation,
 		/*
 		 * In a partial index, ignore tuples that don't satisfy the predicate.
 		 */
-		if (predicate != NIL)
+		if (predicate != NULL)
 		{
-			if (!ExecQual(predicate, econtext, false))
+			if (!ExecQual(predicate, econtext))
 				continue;
 		}
 
@@ -2732,7 +2727,7 @@ IndexCheckExclusion(Relation heapRelation,
 
 	/* These may have been pointing to the now-gone estate */
 	indexInfo->ii_ExpressionsState = NIL;
-	indexInfo->ii_PredicateState = NIL;
+	indexInfo->ii_PredicateState = NULL;
 }
 
 
@@ -2962,7 +2957,7 @@ validate_index_heapscan(Relation heapRelation,
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
-	List	   *predicate;
+	ExprState  *predicate;
 	TupleTableSlot *slot;
 	EState	   *estate;
 	ExprContext *econtext;
@@ -2992,9 +2987,7 @@ validate_index_heapscan(Relation heapRelation,
 	econtext->ecxt_scantuple = slot;
 
 	/* Set up execution state for predicate, if any. */
-	predicate = (List *)
-		ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
-						estate);
+	predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
 
 	/*
 	 * Prepare for scan of the base relation.  We need just those tuples
@@ -3121,9 +3114,9 @@ validate_index_heapscan(Relation heapRelation,
 			 * In a partial index, discard tuples that don't satisfy the
 			 * predicate.
 			 */
-			if (predicate != NIL)
+			if (predicate != NULL)
 			{
-				if (!ExecQual(predicate, econtext, false))
+				if (!ExecQual(predicate, econtext))
 					continue;
 			}
 
@@ -3177,7 +3170,7 @@ validate_index_heapscan(Relation heapRelation,
 
 	/* These may have been pointing to the now-gone estate */
 	indexInfo->ii_ExpressionsState = NIL;
-	indexInfo->ii_PredicateState = NIL;
+	indexInfo->ii_PredicateState = NULL;
 }
 
 
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index e01ef864f00f5f02258752d060ac8b077bc5c623..2b5b8e89bbc75e8dabe62ed56b539a53de757bc8 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1618,8 +1618,7 @@ FormPartitionKeyDatum(PartitionDispatch pd,
 			   GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
 
 		/* First time through, set up expression evaluation state */
-		pd->keystate = (List *) ExecPrepareExpr((Expr *) pd->key->partexprs,
-												estate);
+		pd->keystate = ExecPrepareExprList(pd->key->partexprs, estate);
 	}
 
 	partexpr_item = list_head(pd->keystate);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 0e4231668d4e69a9d5b119b3e95b7c162b4aa449..29756eb14eb43f2a4f15ad8a7eda00bbdbd3158d 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -307,7 +307,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 	indexInfo->ii_Expressions = NIL;
 	indexInfo->ii_ExpressionsState = NIL;
 	indexInfo->ii_Predicate = NIL;
-	indexInfo->ii_PredicateState = NIL;
+	indexInfo->ii_PredicateState = NULL;
 	indexInfo->ii_ExclusionOps = NULL;
 	indexInfo->ii_ExclusionProcs = NULL;
 	indexInfo->ii_ExclusionStrats = NULL;
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index c5b5c54babf0994567ad6bf729f49bf6ba888d52..404acb2debb83e1411a9c085bb28ce0be0c2746b 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -713,7 +713,7 @@ compute_index_stats(Relation onerel, double totalrows,
 		TupleTableSlot *slot;
 		EState	   *estate;
 		ExprContext *econtext;
-		List	   *predicate;
+		ExprState  *predicate;
 		Datum	   *exprvals;
 		bool	   *exprnulls;
 		int			numindexrows,
@@ -739,9 +739,7 @@ compute_index_stats(Relation onerel, double totalrows,
 		econtext->ecxt_scantuple = slot;
 
 		/* Set up execution state for predicate. */
-		predicate = castNode(List,
-							 ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
-											 estate));
+		predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
 
 		/* Compute and save index expression values */
 		exprvals = (Datum *) palloc(numrows * attr_cnt * sizeof(Datum));
@@ -764,9 +762,9 @@ compute_index_stats(Relation onerel, double totalrows,
 			ExecStoreTuple(heapTuple, slot, InvalidBuffer, false);
 
 			/* If index is partial, check predicate */
-			if (predicate != NIL)
+			if (predicate != NULL)
 			{
-				if (!ExecQual(predicate, econtext, false))
+				if (!ExecQual(predicate, econtext))
 					continue;
 			}
 			numindexrows++;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index b4c7466666b1d529fb338ab5d19f5d0904617d00..1036b96aaea00f686c18ebb4b6a102ad4b487069 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -2890,7 +2890,7 @@ ExplainSubPlans(List *plans, List *ancestors,
 	foreach(lst, plans)
 	{
 		SubPlanState *sps = (SubPlanState *) lfirst(lst);
-		SubPlan    *sp = (SubPlan *) sps->xprstate.expr;
+		SubPlan    *sp = sps->subplan;
 
 		/*
 		 * There can be multiple SubPlan nodes referencing the same physical
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9618032356af50a000d546702ee4903a9d4a0d18..486179938c3e8537dc2cf631f4693a488168a53d 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -179,7 +179,7 @@ CheckIndexCompatible(Oid oldId,
 	indexInfo = makeNode(IndexInfo);
 	indexInfo->ii_Expressions = NIL;
 	indexInfo->ii_ExpressionsState = NIL;
-	indexInfo->ii_PredicateState = NIL;
+	indexInfo->ii_PredicateState = NULL;
 	indexInfo->ii_ExclusionOps = NULL;
 	indexInfo->ii_ExclusionProcs = NULL;
 	indexInfo->ii_ExclusionStrats = NULL;
@@ -551,7 +551,7 @@ DefineIndex(Oid relationId,
 	indexInfo->ii_Expressions = NIL;	/* for now */
 	indexInfo->ii_ExpressionsState = NIL;
 	indexInfo->ii_Predicate = make_ands_implicit((Expr *) stmt->whereClause);
-	indexInfo->ii_PredicateState = NIL;
+	indexInfo->ii_PredicateState = NULL;
 	indexInfo->ii_ExclusionOps = NULL;
 	indexInfo->ii_ExclusionProcs = NULL;
 	indexInfo->ii_ExclusionStrats = NULL;
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 992ba1c9a2e2f95ae56e76c6844414dc2f449979..a92461097748f5ce5957eee5e1945b579344a8aa 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -391,7 +391,7 @@ EvaluateParams(PreparedStatement *pstmt, List *params,
 	}
 
 	/* Prepare the expressions for execution */
-	exprstates = (List *) ExecPrepareExpr((Expr *) params, estate);
+	exprstates = ExecPrepareExprList(params, estate);
 
 	paramLI = (ParamListInfo)
 		palloc(offsetof(ParamListInfoData, params) +
@@ -407,7 +407,7 @@ EvaluateParams(PreparedStatement *pstmt, List *params,
 	i = 0;
 	foreach(l, exprstates)
 	{
-		ExprState  *n = lfirst(l);
+		ExprState  *n = (ExprState *) lfirst(l);
 		ParamExternData *prm = &paramLI->params[i];
 
 		prm->ptype = param_types[i];
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3b28e8c34f633965920118a73fb5b2f5a026c45b..96cf42a7f8266d58217004a8bef7ccda241c6544 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -185,7 +185,7 @@ typedef struct NewConstraint
 	Oid			refindid;		/* OID of PK's index, if FOREIGN */
 	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
 	Node	   *qual;			/* Check expr or CONSTR_FOREIGN Constraint */
-	List	   *qualstate;		/* Execution state for CHECK */
+	ExprState  *qualstate;		/* Execution state for CHECK expr */
 } NewConstraint;
 
 /*
@@ -4262,7 +4262,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	CommandId	mycid;
 	BulkInsertState bistate;
 	int			hi_options;
-	List	   *partqualstate = NIL;
+	ExprState  *partqualstate = NULL;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -4315,8 +4315,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		{
 			case CONSTR_CHECK:
 				needscan = true;
-				con->qualstate = (List *)
-					ExecPrepareExpr((Expr *) con->qual, estate);
+				con->qualstate = ExecPrepareExpr((Expr *) con->qual, estate);
 				break;
 			case CONSTR_FOREIGN:
 				/* Nothing to do here */
@@ -4331,9 +4330,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	if (tab->partition_constraint)
 	{
 		needscan = true;
-		partqualstate = (List *)
-			ExecPrepareExpr((Expr *) tab->partition_constraint,
-							estate);
+		partqualstate = ExecPrepareCheck(tab->partition_constraint, estate);
 	}
 
 	foreach(l, tab->newvals)
@@ -4508,7 +4505,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				switch (con->contype)
 				{
 					case CONSTR_CHECK:
-						if (!ExecQual(con->qualstate, econtext, true))
+						if (!ExecCheck(con->qualstate, econtext))
 							ereport(ERROR,
 									(errcode(ERRCODE_CHECK_VIOLATION),
 									 errmsg("check constraint \"%s\" is violated by some row",
@@ -4524,7 +4521,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				}
 			}
 
-			if (partqualstate && !ExecQual(partqualstate, econtext, true))
+			if (partqualstate && !ExecCheck(partqualstate, econtext))
 				ereport(ERROR,
 						(errcode(ERRCODE_CHECK_VIOLATION),
 					errmsg("partition constraint is violated by some row")));
@@ -6607,8 +6604,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
 			newcon->name = ccon->name;
 			newcon->contype = ccon->contype;
-			/* ExecQual wants implicit-AND format */
-			newcon->qual = (Node *) make_ands_implicit((Expr *) ccon->expr);
+			newcon->qual = ccon->expr;
 
 			tab->constraints = lappend(tab->constraints, newcon);
 		}
@@ -7786,7 +7782,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	Datum		val;
 	char	   *conbin;
 	Expr	   *origexpr;
-	List	   *exprstate;
+	ExprState  *exprstate;
 	TupleDesc	tupdesc;
 	HeapScanDesc scan;
 	HeapTuple	tuple;
@@ -7817,8 +7813,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 			 HeapTupleGetOid(constrtup));
 	conbin = TextDatumGetCString(val);
 	origexpr = (Expr *) stringToNode(conbin);
-	exprstate = (List *)
-		ExecPrepareExpr((Expr *) make_ands_implicit(origexpr), estate);
+	exprstate = ExecPrepareExpr(origexpr, estate);
 
 	econtext = GetPerTupleExprContext(estate);
 	tupdesc = RelationGetDescr(rel);
@@ -7838,7 +7833,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	{
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
-		if (!ExecQual(exprstate, econtext, true))
+		if (!ExecCheck(exprstate, econtext))
 			ereport(ERROR,
 					(errcode(ERRCODE_CHECK_VIOLATION),
 					 errmsg("check constraint \"%s\" is violated by some row",
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index a1bb3e958c768e8e0d30a3f82b395cf0bf1671df..f3b1a5268267b10b0ce3307017dda205c6fe059b 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3057,7 +3057,7 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 	if (trigger->tgqual)
 	{
 		TupleDesc	tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
-		List	  **predicate;
+		ExprState **predicate;
 		ExprContext *econtext;
 		TupleTableSlot *oldslot = NULL;
 		TupleTableSlot *newslot = NULL;
@@ -3078,7 +3078,7 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 		 * nodetrees for it.  Keep them in the per-query memory context so
 		 * they'll survive throughout the query.
 		 */
-		if (*predicate == NIL)
+		if (*predicate == NULL)
 		{
 			Node	   *tgqual;
 
@@ -3087,9 +3087,9 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 			/* Change references to OLD and NEW to INNER_VAR and OUTER_VAR */
 			ChangeVarNodes(tgqual, PRS2_OLD_VARNO, INNER_VAR, 0);
 			ChangeVarNodes(tgqual, PRS2_NEW_VARNO, OUTER_VAR, 0);
-			/* ExecQual wants implicit-AND form */
+			/* ExecPrepareQual wants implicit-AND form */
 			tgqual = (Node *) make_ands_implicit((Expr *) tgqual);
-			*predicate = (List *) ExecPrepareExpr((Expr *) tgqual, estate);
+			*predicate = ExecPrepareQual((List *) tgqual, estate);
 			MemoryContextSwitchTo(oldContext);
 		}
 
@@ -3137,7 +3137,7 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 		 */
 		econtext->ecxt_innertuple = oldslot;
 		econtext->ecxt_outertuple = newslot;
-		if (!ExecQual(*predicate, econtext, false))
+		if (!ExecQual(*predicate, econtext))
 			return false;
 	}
 
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index d281906cd5cf496449f61cd358a1566c8ad9b5ef..d1c1324399a05e9020df6c6b1fd927a402d06726 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -12,9 +12,10 @@ subdir = src/backend/executor
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = execAmi.o execCurrent.o execGrouping.o execIndexing.o execJunk.o \
-       execMain.o execParallel.o execProcnode.o execQual.o \
-       execReplication.o execScan.o execTuples.o \
+OBJS = execAmi.o execCurrent.o execExpr.o execExprInterp.o \
+       execGrouping.o execIndexing.o execJunk.o \
+       execMain.o execParallel.o execProcnode.o \
+       execReplication.o execScan.o execSRF.o execTuples.o \
        execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
        nodeBitmapAnd.o nodeBitmapOr.o \
        nodeBitmapHeapscan.o nodeBitmapIndexscan.o \
diff --git a/src/backend/executor/README b/src/backend/executor/README
index f1d1e4c76ce9d78cfc70bc6da7348e2a03dae52d..a0045067fb84ac27593ce400ff25f5a8450e290e 100644
--- a/src/backend/executor/README
+++ b/src/backend/executor/README
@@ -44,21 +44,171 @@ Plan Trees and State Trees
 --------------------------
 
 The plan tree delivered by the planner contains a tree of Plan nodes (struct
-types derived from struct Plan).  Each Plan node may have expression trees
-associated with it, to represent its target list, qualification conditions,
-etc.  During executor startup we build a parallel tree of identical structure
-containing executor state nodes --- every plan and expression node type has
-a corresponding executor state node type.  Each node in the state tree has a
-pointer to its corresponding node in the plan tree, plus executor state data
-as needed to implement that node type.  This arrangement allows the plan
-tree to be completely read-only as far as the executor is concerned: all data
-that is modified during execution is in the state tree.  Read-only plan trees
-make life much simpler for plan caching and reuse.
+types derived from struct Plan).  During executor startup we build a parallel
+tree of identical structure containing executor state nodes --- every plan
+node type has a corresponding executor state node type.  Each node in the
+state tree has a pointer to its corresponding node in the plan tree, plus
+executor state data as needed to implement that node type.  This arrangement
+allows the plan tree to be completely read-only so far as the executor is
+concerned: all data that is modified during execution is in the state tree.
+Read-only plan trees make life much simpler for plan caching and reuse.
+
+Each Plan node may have expression trees associated with it, to represent
+its target list, qualification conditions, etc.  These trees are also
+read-only to the executor, but the executor state for expression evaluation
+does not mirror the Plan expression's tree shape, as explained below.
+Rather, there's just one ExprState node per expression tree, although this
+may have sub-nodes for some complex expression node types.
 
 Altogether there are four classes of nodes used in these trees: Plan nodes,
-their corresponding PlanState nodes, Expr nodes, and their corresponding
-ExprState nodes.  (Actually, there are also List nodes, which are used as
-"glue" in all four kinds of tree.)
+their corresponding PlanState nodes, Expr nodes, and ExprState nodes.
+(Actually, there are also List nodes, which are used as "glue" in all
+three tree-based representations.)
+
+
+Expression Trees and ExprState nodes
+------------------------------------
+
+Expression trees, in contrast to Plan trees, are not mirrored into a
+corresponding tree of state nodes.  Instead each separately executable
+expression tree (e.g. a Plan's qual or targetlist) is represented by one
+ExprState node.  The ExprState node contains the information needed to
+evaluate the expression in a compact, linear form.  That compact form is
+stored as a flat array in ExprState->steps[] (an array of ExprEvalStep,
+not ExprEvalStep *).
+
+The reasons for choosing such a representation include:
+- commonly the amount of work needed to evaluate one Expr-type node is
+  small enough that the overhead of having to perform a tree-walk
+  during evaluation is significant.
+- the flat representation can be evaluated non-recursively within a single
+  function, reducing stack depth and function call overhead.
+- such a representation is usable both for fast interpreted execution,
+  and for compiling into native code.
+
+The Plan-tree representation of an expression is compiled into an
+ExprState node by ExecInitExpr().  As much complexity as possible should
+be handled by ExecInitExpr() (and helpers), instead of execution time
+where both interpreted and compiled versions would need to deal with the
+complexity.  Besides duplicating effort between execution approaches,
+runtime initialization checks also have a small but noticeable cost every
+time the expression is evaluated.  Therefore, we allow ExecInitExpr() to
+precompute information that we do not expect to vary across execution of a
+single query, for example the set of CHECK constraint expressions to be
+applied to a domain type.  This could not be done at plan time without
+greatly increasing the number of events that require plan invalidation.
+(Previously, some information of this kind was rechecked on each
+expression evaluation, but that seems like unnecessary overhead.)
+
+
+Expression Initialization
+-------------------------
+
+During ExecInitExpr() and similar routines, Expr trees are converted
+into the flat representation.  Each Expr node might be represented by
+zero, one, or more ExprEvalSteps.
+
+Each ExprEvalStep's work is determined by its opcode (of enum ExprEvalOp)
+and it stores the result of its work into the Datum variable and boolean
+null flag variable pointed to by ExprEvalStep->resvalue/resnull.
+Complex expressions are performed by chaining together several steps.
+For example, "a + b" (one OpExpr, with two Var expressions) would be
+represented as two steps to fetch the Var values, and one step for the
+evaluation of the function underlying the + operator.  The steps for the
+Vars would have their resvalue/resnull pointing directly to the appropriate
+arg[] and argnull[] array elements in the FunctionCallInfoData struct that
+is used by the function evaluation step, thus avoiding extra work to copy
+the result values around.
+
+The last entry in a completed ExprState->steps array is always an
+EEOP_DONE step; this removes the need to test for end-of-array while
+iterating.  Also, if the expression contains any variable references (to
+user columns of the ExprContext's INNER, OUTER, or SCAN tuples), the steps
+array begins with EEOP_*_FETCHSOME steps that ensure that the relevant
+tuples have been deconstructed to make the required columns directly
+available (cf. slot_getsomeattrs()).  This allows individual Var-fetching
+steps to be little more than an array lookup.
+
+Most of ExecInitExpr()'s work is done by the recursive function
+ExecInitExprRec() and its subroutines.  ExecInitExprRec() maps one Expr
+node into the steps required for execution, recursing as needed for
+sub-expressions.
+
+Each ExecInitExprRec() call has to specify where that subexpression's
+results are to be stored (via the resv/resnull parameters).  This allows
+the above scenario of evaluating a (sub-)expression directly into
+fcinfo->arg/argnull, but also requires some care: target Datum/isnull
+variables may not be shared with another ExecInitExprRec() unless the
+results are only needed by steps executing before further usages of those
+target Datum/isnull variables.  Due to the non-recursiveness of the
+ExprEvalStep representation that's usually easy to guarantee.
+
+ExecInitExprRec() pushes new operations into the ExprState->steps array
+using ExprEvalPushStep().  To keep the steps as a consecutively laid out
+array, ExprEvalPushStep() has to repalloc the entire array when there's
+not enough space.  Because of that it is *not* allowed to point directly
+into any of the steps during expression initialization.  Therefore, the
+resv/resnull for a subexpression usually point to some storage that is
+palloc'd separately from the steps array.  For instance, the
+FunctionCallInfoData for a function call step is separately allocated
+rather than being part of the ExprEvalStep array.  The overall result
+of a complete expression is typically returned into the resvalue/resnull
+fields of the ExprState node itself.
+
+Some steps, e.g. boolean expressions, allow skipping evaluation of
+certain subexpressions.  In the flat representation this amounts to
+jumping to some later step rather than just continuing consecutively
+with the next step.  The target for such a jump is represented by
+the integer index in the ExprState->steps array of the step to execute
+next.  (Compare the EEO_NEXT and EEO_JUMP macros in execExprInterp.c.)
+
+Typically, ExecInitExprRec() has to push a jumping step into the steps
+array, then recursively generate steps for the subexpression that might
+get skipped over, then go back and fix up the jump target index using
+the now-known length of the subexpression's steps.  This is handled by
+adjust_jumps lists in execExpr.c.
+
+The last step in constructing an ExprState is to apply ExecReadyExpr(),
+which readies it for execution using whichever execution method has been
+selected.
+
+
+Expression Evaluation
+---------------------
+
+To allow for different methods of expression evaluation, and for
+better branch/jump target prediction, expressions are evaluated by
+calling ExprState->evalfunc (via ExprEvalExpr() and friends).
+
+ExprReadyExpr() can choose the method of interpretation by setting
+evalfunc to an appropriate function.  The default execution function,
+ExecInterpExpr, is implemented in execExprInterp.c; see its header
+comment for details.  Special-case evalfuncs are used for certain
+especially-simple expressions.
+
+Note that a lot of the more complex expression evaluation steps, which are
+less performance-critical than the simpler ones, are implemented as
+separate functions outside the fast-path of expression execution, allowing
+their implementation to be shared between interpreted and compiled
+expression evaluation.  This means that these helper functions are not
+allowed to perform expression step dispatch themselves, as the method of
+dispatch will vary based on the caller.  The helpers therefore cannot call
+for the execution of subexpressions; all subexpression results they need
+must be computed by earlier steps.  And dispatch to the following
+expression step must be performed after returning from the helper.
+
+
+Targetlist Evaluation
+---------------------
+
+ExecBuildProjectionInfo builds an ExprState that has the effect of
+evaluating a targetlist into ExprState->resultslot.  A generic targetlist
+expression is executed by evaluating it as discussed above (storing the
+result into the ExprState's resvalue/resnull fields) and then using an
+EEOP_ASSIGN_TMP step to move the result into the appropriate tts_values[]
+and tts_isnull[] array elements of the result slot.  There are special
+fast-path step types (EEOP_ASSIGN_*_VAR) to handle targetlist entries that
+are simple Vars using only one step instead of two.
 
 
 Memory Management
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
new file mode 100644
index 0000000000000000000000000000000000000000..766dbbb074083678c5c18f1311a3ee2638e34f97
--- /dev/null
+++ b/src/backend/executor/execExpr.c
@@ -0,0 +1,2665 @@
+/*-------------------------------------------------------------------------
+ *
+ * execExpr.c
+ *	  Expression evaluation infrastructure.
+ *
+ *	During executor startup, we compile each expression tree (which has
+ *	previously been processed by the parser and planner) into an ExprState,
+ *	using ExecInitExpr() et al.  This converts the tree into a flat array
+ *	of ExprEvalSteps, which may be thought of as instructions in a program.
+ *	At runtime, we'll execute steps, starting with the first, until we reach
+ *	an EEOP_DONE opcode.
+ *
+ *	This file contains the "compilation" logic.  It is independent of the
+ *	specific execution technology we use (switch statement, computed goto,
+ *	JIT compilation, etc).
+ *
+ *	See src/backend/executor/README for some background, specifically the
+ *	"Expression Trees and ExprState nodes", "Expression Initialization",
+ *	and "Expession Evaluation" sections.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/executor/execExpr.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/nbtree.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_type.h"
+#include "executor/execExpr.h"
+#include "executor/nodeSubplan.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planner.h"
+#include "pgstat.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/typcache.h"
+
+
+typedef struct LastAttnumInfo
+{
+	AttrNumber	last_inner;
+	AttrNumber	last_outer;
+	AttrNumber	last_scan;
+} LastAttnumInfo;
+
+static void ExecReadyExpr(ExprState *state);
+static void ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
+				Datum *resv, bool *resnull);
+static void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s);
+static void ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args,
+			 Oid funcid, Oid inputcollid, PlanState *parent,
+			 ExprState *state);
+static void ExecInitExprSlots(ExprState *state, Node *node);
+static bool get_last_attnums_walker(Node *node, LastAttnumInfo *info);
+static void ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable,
+					PlanState *parent);
+static void ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref,
+				 PlanState *parent, ExprState *state,
+				 Datum *resv, bool *resnull);
+static bool isAssignmentIndirectionExpr(Expr *expr);
+static void ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
+					   PlanState *parent, ExprState *state,
+					   Datum *resv, bool *resnull);
+
+
+/*
+ * ExecInitExpr: prepare an expression tree for execution
+ *
+ * This function builds and returns an ExprState implementing the given
+ * Expr node tree.  The return ExprState can then be handed to ExecEvalExpr
+ * for execution.  Because the Expr tree itself is read-only as far as
+ * ExecInitExpr and ExecEvalExpr are concerned, several different executions
+ * of the same plan tree can occur concurrently.  (But note that an ExprState
+ * does mutate at runtime, so it can't be re-used concurrently.)
+ *
+ * This must be called in a memory context that will last as long as repeated
+ * executions of the expression are needed.  Typically the context will be
+ * the same as the per-query context of the associated ExprContext.
+ *
+ * Any Aggref, WindowFunc, or SubPlan nodes found in the tree are added to
+ * the lists of such nodes held by the parent PlanState (or more accurately,
+ * the AggrefExprState etc. nodes created for them are added).
+ *
+ * Note: there is no ExecEndExpr function; we assume that any resource
+ * cleanup needed will be handled by just releasing the memory context
+ * in which the state tree is built.  Functions that require additional
+ * cleanup work can register a shutdown callback in the ExprContext.
+ *
+ *	'node' is the root of the expression tree to compile.
+ *	'parent' is the PlanState node that owns the expression.
+ *
+ * 'parent' may be NULL if we are preparing an expression that is not
+ * associated with a plan tree.  (If so, it can't have aggs or subplans.)
+ * Such cases should usually come through ExecPrepareExpr, not directly here.
+ *
+ * Also, if 'node' is NULL, we just return NULL.  This is convenient for some
+ * callers that may or may not have an expression that needs to be compiled.
+ * Note that a NULL ExprState pointer *cannot* be handed to ExecEvalExpr,
+ * although ExecQual and ExecCheck will accept one (and treat it as "true").
+ */
+ExprState *
+ExecInitExpr(Expr *node, PlanState *parent)
+{
+	ExprState  *state;
+	ExprEvalStep scratch;
+
+	/* Special case: NULL expression produces a NULL ExprState pointer */
+	if (node == NULL)
+		return NULL;
+
+	/* Initialize ExprState with empty step list */
+	state = makeNode(ExprState);
+	state->expr = node;
+
+	/* Insert EEOP_*_FETCHSOME steps as needed */
+	ExecInitExprSlots(state, (Node *) node);
+
+	/* Compile the expression proper */
+	ExecInitExprRec(node, parent, state, &state->resvalue, &state->resnull);
+
+	/* Finally, append a DONE step */
+	scratch.opcode = EEOP_DONE;
+	ExprEvalPushStep(state, &scratch);
+
+	ExecReadyExpr(state);
+
+	return state;
+}
+
+/*
+ * ExecInitQual: prepare a qual for execution by ExecQual
+ *
+ * Prepares for the evaluation of a conjunctive boolean expression (qual list
+ * with implicit AND semantics) that returns true if none of the
+ * subexpressions are false.
+ *
+ * We must return true if the list is empty.  Since that's a very common case,
+ * we optimize it a bit further by translating to a NULL ExprState pointer
+ * rather than setting up an ExprState that computes constant TRUE.  (Some
+ * especially hot-spot callers of ExecQual detect this and avoid calling
+ * ExecQual at all.)
+ *
+ * If any of the subexpressions yield NULL, then the result of the conjunction
+ * is false.  This makes ExecQual primarily useful for evaluating WHERE
+ * clauses, since SQL specifies that tuples with null WHERE results do not
+ * get selected.
+ */
+ExprState *
+ExecInitQual(List *qual, PlanState *parent)
+{
+	ExprState  *state;
+	ExprEvalStep scratch;
+	List	   *adjust_jumps = NIL;
+	ListCell   *lc;
+
+	/* short-circuit (here and in ExecQual) for empty restriction list */
+	if (qual == NIL)
+		return NULL;
+
+	Assert(IsA(qual, List));
+
+	state = makeNode(ExprState);
+	state->expr = (Expr *) qual;
+	/* mark expression as to be used with ExecQual() */
+	state->flags = EEO_FLAG_IS_QUAL;
+
+	/* Insert EEOP_*_FETCHSOME steps as needed */
+	ExecInitExprSlots(state, (Node *) qual);
+
+	/*
+	 * ExecQual() needs to return false for an expression returning NULL. That
+	 * allows us to short-circuit the evaluation the first time a NULL is
+	 * encountered.  As qual evaluation is a hot-path this warrants using a
+	 * special opcode for qual evaluation that's simpler than BOOL_AND (which
+	 * has more complex NULL handling).
+	 */
+	scratch.opcode = EEOP_QUAL;
+
+	/*
+	 * We can use ExprState's resvalue/resnull as target for each qual expr.
+	 */
+	scratch.resvalue = &state->resvalue;
+	scratch.resnull = &state->resnull;
+
+	foreach(lc, qual)
+	{
+		Expr	   *node = (Expr *) lfirst(lc);
+
+		/* first evaluate expression */
+		ExecInitExprRec(node, parent, state, &state->resvalue, &state->resnull);
+
+		/* then emit EEOP_QUAL to detect if it's false (or null) */
+		scratch.d.qualexpr.jumpdone = -1;
+		ExprEvalPushStep(state, &scratch);
+		adjust_jumps = lappend_int(adjust_jumps,
+								   state->steps_len - 1);
+	}
+
+	/* adjust jump targets */
+	foreach(lc, adjust_jumps)
+	{
+		ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+
+		Assert(as->opcode == EEOP_QUAL);
+		Assert(as->d.qualexpr.jumpdone == -1);
+		as->d.qualexpr.jumpdone = state->steps_len;
+	}
+
+	/*
+	 * At the end, we don't need to do anything more.  The last qual expr must
+	 * have yielded TRUE, and since its result is stored in the desired output
+	 * location, we're done.
+	 */
+	scratch.opcode = EEOP_DONE;
+	ExprEvalPushStep(state, &scratch);
+
+	ExecReadyExpr(state);
+
+	return state;
+}
+
+/*
+ * ExecInitCheck: prepare a check constraint for execution by ExecCheck
+ *
+ * This is much like ExecInitQual/ExecQual, except that a null result from
+ * the conjunction is treated as TRUE.  This behavior is appropriate for
+ * evaluating CHECK constraints, since SQL specifies that NULL constraint
+ * conditions are not failures.
+ *
+ * Note that like ExecInitQual, this expects input in implicit-AND format.
+ * Users of ExecCheck that have expressions in normal explicit-AND format
+ * can just apply ExecInitExpr to produce suitable input for ExecCheck.
+ */
+ExprState *
+ExecInitCheck(List *qual, PlanState *parent)
+{
+	/* short-circuit (here and in ExecCheck) for empty restriction list */
+	if (qual == NIL)
+		return NULL;
+
+	Assert(IsA(qual, List));
+
+	/*
+	 * Just convert the implicit-AND list to an explicit AND (if there's more
+	 * than one entry), and compile normally.  Unlike ExecQual, we can't
+	 * short-circuit on NULL results, so the regular AND behavior is needed.
+	 */
+	return ExecInitExpr(make_ands_explicit(qual), parent);
+}
+
+/*
+ * Call ExecInitExpr() on a list of expressions, return a list of ExprStates.
+ */
+List *
+ExecInitExprList(List *nodes, PlanState *parent)
+{
+	List	   *result = NIL;
+	ListCell   *lc;
+
+	foreach(lc, nodes)
+	{
+		Expr	   *e = lfirst(lc);
+
+		result = lappend(result, ExecInitExpr(e, parent));
+	}
+
+	return result;
+}
+
+/*
+ *		ExecBuildProjectionInfo
+ *
+ * Build a ProjectionInfo node for evaluating the given tlist in the given
+ * econtext, and storing the result into the tuple slot.  (Caller must have
+ * ensured that tuple slot has a descriptor matching the tlist!)
+ *
+ * inputDesc can be NULL, but if it is not, we check to see whether simple
+ * Vars in the tlist match the descriptor.  It is important to provide
+ * inputDesc for relation-scan plan nodes, as a cross check that the relation
+ * hasn't been changed since the plan was made.  At higher levels of a plan,
+ * there is no need to recheck.
+ *
+ * This is implemented by internally building an ExprState that performs the
+ * whole projection in one go.
+ *
+ * Caution: before PG v10, the targetList was a list of ExprStates; now it
+ * should be the planner-created targetlist, since we do the compilation here.
+ */
+ProjectionInfo *
+ExecBuildProjectionInfo(List *targetList,
+						ExprContext *econtext,
+						TupleTableSlot *slot,
+						PlanState *parent,
+						TupleDesc inputDesc)
+{
+	ProjectionInfo *projInfo = makeNode(ProjectionInfo);
+	ExprState  *state;
+	ExprEvalStep scratch;
+	ListCell   *lc;
+
+	projInfo->pi_exprContext = econtext;
+	/* We embed ExprState into ProjectionInfo instead of doing extra palloc */
+	projInfo->pi_state.tag.type = T_ExprState;
+	state = &projInfo->pi_state;
+	state->expr = (Expr *) targetList;
+	state->resultslot = slot;
+
+	/* Insert EEOP_*_FETCHSOME steps as needed */
+	ExecInitExprSlots(state, (Node *) targetList);
+
+	/* Now compile each tlist column */
+	foreach(lc, targetList)
+	{
+		TargetEntry *tle = castNode(TargetEntry, lfirst(lc));
+		Var		   *variable = NULL;
+		AttrNumber	attnum = 0;
+		bool		isSafeVar = false;
+
+		/*
+		 * If tlist expression is a safe non-system Var, use the fast-path
+		 * ASSIGN_*_VAR opcodes.  "Safe" means that we don't need to apply
+		 * CheckVarSlotCompatibility() during plan startup.  If a source slot
+		 * was provided, we make the equivalent tests here; if a slot was not
+		 * provided, we assume that no check is needed because we're dealing
+		 * with a non-relation-scan-level expression.
+		 */
+		if (tle->expr != NULL &&
+			IsA(tle->expr, Var) &&
+			((Var *) tle->expr)->varattno > 0)
+		{
+			/* Non-system Var, but how safe is it? */
+			variable = (Var *) tle->expr;
+			attnum = variable->varattno;
+
+			if (inputDesc == NULL)
+				isSafeVar = true;		/* can't check, just assume OK */
+			else if (attnum <= inputDesc->natts)
+			{
+				Form_pg_attribute attr = inputDesc->attrs[attnum - 1];
+
+				/*
+				 * If user attribute is dropped or has a type mismatch, don't
+				 * use ASSIGN_*_VAR.  Instead let the normal expression
+				 * machinery handle it (which'll possibly error out).
+				 */
+				if (!attr->attisdropped && variable->vartype == attr->atttypid)
+				{
+					isSafeVar = true;
+				}
+			}
+		}
+
+		if (isSafeVar)
+		{
+			/* Fast-path: just generate an EEOP_ASSIGN_*_VAR step */
+			switch (variable->varno)
+			{
+				case INNER_VAR:
+					/* get the tuple from the inner node */
+					scratch.opcode = EEOP_ASSIGN_INNER_VAR;
+					break;
+
+				case OUTER_VAR:
+					/* get the tuple from the outer node */
+					scratch.opcode = EEOP_ASSIGN_OUTER_VAR;
+					break;
+
+					/* INDEX_VAR is handled by default case */
+
+				default:
+					/* get the tuple from the relation being scanned */
+					scratch.opcode = EEOP_ASSIGN_SCAN_VAR;
+					break;
+			}
+
+			scratch.d.assign_var.attnum = attnum - 1;
+			scratch.d.assign_var.resultnum = tle->resno - 1;
+			ExprEvalPushStep(state, &scratch);
+		}
+		else
+		{
+			/*
+			 * Otherwise, compile the column expression normally.
+			 *
+			 * We can't tell the expression to evaluate directly into the
+			 * result slot, as the result slot (and the exprstate for that
+			 * matter) can change between executions.  We instead evaluate
+			 * into the ExprState's resvalue/resnull and then move.
+			 */
+			ExecInitExprRec(tle->expr, parent, state,
+							&state->resvalue, &state->resnull);
+
+			/*
+			 * Column might be referenced multiple times in upper nodes, so
+			 * force value to R/O - but only if it could be an expanded datum.
+			 */
+			if (get_typlen(exprType((Node *) tle->expr)) == -1)
+				scratch.opcode = EEOP_ASSIGN_TMP_MAKE_RO;
+			else
+				scratch.opcode = EEOP_ASSIGN_TMP;
+			scratch.d.assign_tmp.resultnum = tle->resno - 1;
+			ExprEvalPushStep(state, &scratch);
+		}
+	}
+
+	scratch.opcode = EEOP_DONE;
+	ExprEvalPushStep(state, &scratch);
+
+	ExecReadyExpr(state);
+
+	return projInfo;
+}
+
+/*
+ * ExecPrepareExpr --- initialize for expression execution outside a normal
+ * Plan tree context.
+ *
+ * This differs from ExecInitExpr in that we don't assume the caller is
+ * already running in the EState's per-query context.  Also, we run the
+ * passed expression tree through expression_planner() to prepare it for
+ * execution.  (In ordinary Plan trees the regular planning process will have
+ * made the appropriate transformations on expressions, but for standalone
+ * expressions this won't have happened.)
+ */
+ExprState *
+ExecPrepareExpr(Expr *node, EState *estate)
+{
+	ExprState  *result;
+	MemoryContext oldcontext;
+
+	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+	node = expression_planner(node);
+
+	result = ExecInitExpr(node, NULL);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return result;
+}
+
+/*
+ * ExecPrepareQual --- initialize for qual execution outside a normal
+ * Plan tree context.
+ *
+ * This differs from ExecInitQual in that we don't assume the caller is
+ * already running in the EState's per-query context.  Also, we run the
+ * passed expression tree through expression_planner() to prepare it for
+ * execution.  (In ordinary Plan trees the regular planning process will have
+ * made the appropriate transformations on expressions, but for standalone
+ * expressions this won't have happened.)
+ */
+ExprState *
+ExecPrepareQual(List *qual, EState *estate)
+{
+	ExprState  *result;
+	MemoryContext oldcontext;
+
+	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+	qual = (List *) expression_planner((Expr *) qual);
+
+	result = ExecInitQual(qual, NULL);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return result;
+}
+
+/*
+ * ExecPrepareCheck -- initialize check constraint for execution outside a
+ * normal Plan tree context.
+ *
+ * See ExecPrepareExpr() and ExecInitCheck() for details.
+ */
+ExprState *
+ExecPrepareCheck(List *qual, EState *estate)
+{
+	ExprState  *result;
+	MemoryContext oldcontext;
+
+	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+	qual = (List *) expression_planner((Expr *) qual);
+
+	result = ExecInitCheck(qual, NULL);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return result;
+}
+
+/*
+ * Call ExecPrepareExpr() on each member of a list of Exprs, and return
+ * a list of ExprStates.
+ *
+ * See ExecPrepareExpr() for details.
+ */
+List *
+ExecPrepareExprList(List *nodes, EState *estate)
+{
+	List	   *result = NIL;
+	ListCell   *lc;
+
+	foreach(lc, nodes)
+	{
+		Expr	   *e = (Expr *) lfirst(lc);
+
+		result = lappend(result, ExecPrepareExpr(e, estate));
+	}
+
+	return result;
+}
+
+/*
+ * ExecCheck - evaluate a check constraint
+ *
+ * For check constraints, a null result is taken as TRUE, ie the constraint
+ * passes.
+ *
+ * The check constraint may have been prepared with ExecInitCheck
+ * (possibly via ExecPrepareCheck) if the caller had it in implicit-AND
+ * format, but a regular boolean expression prepared with ExecInitExpr or
+ * ExecPrepareExpr works too.
+ */
+bool
+ExecCheck(ExprState *state, ExprContext *econtext)
+{
+	Datum		ret;
+	bool		isnull;
+
+	/* short-circuit (here and in ExecInitCheck) for empty restriction list */
+	if (state == NULL)
+		return true;
+
+	/* verify that expression was not compiled using ExecInitQual */
+	Assert(!(state->flags & EEO_FLAG_IS_QUAL));
+
+	ret = ExecEvalExprSwitchContext(state, econtext, &isnull);
+
+	if (isnull)
+		return true;
+
+	return DatumGetBool(ret);
+}
+
+/*
+ * Prepare a compiled expression for execution.  This has to be called for
+ * every ExprState before it can be executed.
+ *
+ * NB: While this currently only calls ExecReadyInterpretedExpr(),
+ * this will likely get extended to further expression evaluation methods.
+ * Therefore this should be used instead of directly calling
+ * ExecReadyInterpretedExpr().
+ */
+static void
+ExecReadyExpr(ExprState *state)
+{
+	ExecReadyInterpretedExpr(state);
+}
+
+/*
+ * Append the steps necessary for the evaluation of node to ExprState->steps,
+ * possibly recursing into sub-expressions of node.
+ *
+ * node - expression to evaluate
+ * parent - parent executor node (or NULL if a standalone expression)
+ * state - ExprState to whose ->steps to append the necessary operations
+ * resv / resnull - where to store the result of the node into
+ */
+static void
+ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
+				Datum *resv, bool *resnull)
+{
+	ExprEvalStep scratch;
+
+	/* Guard against stack overflow due to overly complex expressions */
+	check_stack_depth();
+
+	/* Step's output location is always what the caller gave us */
+	Assert(resv != NULL && resnull != NULL);
+	scratch.resvalue = resv;
+	scratch.resnull = resnull;
+
+	/* cases should be ordered as they are in enum NodeTag */
+	switch (nodeTag(node))
+	{
+		case T_Var:
+			{
+				Var		   *variable = (Var *) node;
+
+				if (variable->varattno == InvalidAttrNumber)
+				{
+					/* whole-row Var */
+					ExecInitWholeRowVar(&scratch, variable, parent);
+				}
+				else if (variable->varattno <= 0)
+				{
+					/* system column */
+					scratch.d.var.attnum = variable->varattno;
+					scratch.d.var.vartype = variable->vartype;
+					switch (variable->varno)
+					{
+						case INNER_VAR:
+							scratch.opcode = EEOP_INNER_SYSVAR;
+							break;
+						case OUTER_VAR:
+							scratch.opcode = EEOP_OUTER_SYSVAR;
+							break;
+
+							/* INDEX_VAR is handled by default case */
+
+						default:
+							scratch.opcode = EEOP_SCAN_SYSVAR;
+							break;
+					}
+				}
+				else
+				{
+					/* regular user column */
+					scratch.d.var.attnum = variable->varattno - 1;
+					scratch.d.var.vartype = variable->vartype;
+					/* select EEOP_*_FIRST opcode to force one-time checks */
+					switch (variable->varno)
+					{
+						case INNER_VAR:
+							scratch.opcode = EEOP_INNER_VAR_FIRST;
+							break;
+						case OUTER_VAR:
+							scratch.opcode = EEOP_OUTER_VAR_FIRST;
+							break;
+
+							/* INDEX_VAR is handled by default case */
+
+						default:
+							scratch.opcode = EEOP_SCAN_VAR_FIRST;
+							break;
+					}
+				}
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_Const:
+			{
+				Const	   *con = (Const *) node;
+
+				scratch.opcode = EEOP_CONST;
+				scratch.d.constval.value = con->constvalue;
+				scratch.d.constval.isnull = con->constisnull;
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_Param:
+			{
+				Param	   *param = (Param *) node;
+
+				switch (param->paramkind)
+				{
+					case PARAM_EXEC:
+						scratch.opcode = EEOP_PARAM_EXEC;
+						scratch.d.param.paramid = param->paramid;
+						scratch.d.param.paramtype = param->paramtype;
+						break;
+					case PARAM_EXTERN:
+						scratch.opcode = EEOP_PARAM_EXTERN;
+						scratch.d.param.paramid = param->paramid;
+						scratch.d.param.paramtype = param->paramtype;
+						break;
+					default:
+						elog(ERROR, "unrecognized paramkind: %d",
+							 (int) param->paramkind);
+						break;
+				}
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_Aggref:
+			{
+				Aggref	   *aggref = (Aggref *) node;
+				AggrefExprState *astate = makeNode(AggrefExprState);
+
+				scratch.opcode = EEOP_AGGREF;
+				scratch.d.aggref.astate = astate;
+				astate->aggref = aggref;
+
+				if (parent && IsA(parent, AggState))
+				{
+					AggState   *aggstate = (AggState *) parent;
+
+					aggstate->aggs = lcons(astate, aggstate->aggs);
+					aggstate->numaggs++;
+				}
+				else
+				{
+					/* planner messed up */
+					elog(ERROR, "Aggref found in non-Agg plan node");
+				}
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_GroupingFunc:
+			{
+				GroupingFunc *grp_node = (GroupingFunc *) node;
+				Agg		   *agg;
+
+				if (!parent || !IsA(parent, AggState) ||
+					!IsA(parent->plan, Agg))
+					elog(ERROR, "GroupingFunc found in non-Agg plan node");
+
+				scratch.opcode = EEOP_GROUPING_FUNC;
+				scratch.d.grouping_func.parent = (AggState *) parent;
+
+				agg = (Agg *) (parent->plan);
+
+				if (agg->groupingSets)
+					scratch.d.grouping_func.clauses = grp_node->cols;
+				else
+					scratch.d.grouping_func.clauses = NIL;
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_WindowFunc:
+			{
+				WindowFunc *wfunc = (WindowFunc *) node;
+				WindowFuncExprState *wfstate = makeNode(WindowFuncExprState);
+
+				wfstate->wfunc = wfunc;
+
+				if (parent && IsA(parent, WindowAggState))
+				{
+					WindowAggState *winstate = (WindowAggState *) parent;
+					int			nfuncs;
+
+					winstate->funcs = lcons(wfstate, winstate->funcs);
+					nfuncs = ++winstate->numfuncs;
+					if (wfunc->winagg)
+						winstate->numaggs++;
+
+					/* for now initialize agg using old style expressions */
+					wfstate->args = ExecInitExprList(wfunc->args, parent);
+					wfstate->aggfilter = ExecInitExpr(wfunc->aggfilter,
+													  parent);
+
+					/*
+					 * Complain if the windowfunc's arguments contain any
+					 * windowfuncs; nested window functions are semantically
+					 * nonsensical.  (This should have been caught earlier,
+					 * but we defend against it here anyway.)
+					 */
+					if (nfuncs != winstate->numfuncs)
+						ereport(ERROR,
+								(errcode(ERRCODE_WINDOWING_ERROR),
+						  errmsg("window function calls cannot be nested")));
+				}
+				else
+				{
+					/* planner messed up */
+					elog(ERROR, "WindowFunc found in non-WindowAgg plan node");
+				}
+
+				scratch.opcode = EEOP_WINDOW_FUNC;
+				scratch.d.window_func.wfstate = wfstate;
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_ArrayRef:
+			{
+				ArrayRef   *aref = (ArrayRef *) node;
+
+				ExecInitArrayRef(&scratch, aref, parent, state, resv, resnull);
+				break;
+			}
+
+		case T_FuncExpr:
+			{
+				FuncExpr   *func = (FuncExpr *) node;
+
+				ExecInitFunc(&scratch, node,
+							 func->args, func->funcid, func->inputcollid,
+							 parent, state);
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_OpExpr:
+			{
+				OpExpr	   *op = (OpExpr *) node;
+
+				ExecInitFunc(&scratch, node,
+							 op->args, op->opfuncid, op->inputcollid,
+							 parent, state);
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_DistinctExpr:
+			{
+				DistinctExpr *op = (DistinctExpr *) node;
+
+				ExecInitFunc(&scratch, node,
+							 op->args, op->opfuncid, op->inputcollid,
+							 parent, state);
+
+				/*
+				 * Change opcode of call instruction to EEOP_DISTINCT.
+				 *
+				 * XXX: historically we've not called the function usage
+				 * pgstat infrastructure - that seems inconsistent given that
+				 * we do so for normal function *and* operator evaluation.  If
+				 * we decided to do that here, we'd probably want separate
+				 * opcodes for FUSAGE or not.
+				 */
+				scratch.opcode = EEOP_DISTINCT;
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_NullIfExpr:
+			{
+				NullIfExpr *op = (NullIfExpr *) node;
+
+				ExecInitFunc(&scratch, node,
+							 op->args, op->opfuncid, op->inputcollid,
+							 parent, state);
+
+				/*
+				 * Change opcode of call instruction to EEOP_NULLIF.
+				 *
+				 * XXX: historically we've not called the function usage
+				 * pgstat infrastructure - that seems inconsistent given that
+				 * we do so for normal function *and* operator evaluation.  If
+				 * we decided to do that here, we'd probably want separate
+				 * opcodes for FUSAGE or not.
+				 */
+				scratch.opcode = EEOP_NULLIF;
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_ScalarArrayOpExpr:
+			{
+				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
+				Expr	   *scalararg;
+				Expr	   *arrayarg;
+				FmgrInfo   *finfo;
+				FunctionCallInfo fcinfo;
+				AclResult	aclresult;
+
+				Assert(list_length(opexpr->args) == 2);
+				scalararg = (Expr *) linitial(opexpr->args);
+				arrayarg = (Expr *) lsecond(opexpr->args);
+
+				/* Check permission to call function */
+				aclresult = pg_proc_aclcheck(opexpr->opfuncid,
+											 GetUserId(),
+											 ACL_EXECUTE);
+				if (aclresult != ACLCHECK_OK)
+					aclcheck_error(aclresult, ACL_KIND_PROC,
+								   get_func_name(opexpr->opfuncid));
+				InvokeFunctionExecuteHook(opexpr->opfuncid);
+
+				/* Set up the primary fmgr lookup information */
+				finfo = palloc0(sizeof(FmgrInfo));
+				fcinfo = palloc0(sizeof(FunctionCallInfoData));
+				fmgr_info(opexpr->opfuncid, finfo);
+				fmgr_info_set_expr((Node *) node, finfo);
+				InitFunctionCallInfoData(*fcinfo, finfo, 2,
+										 opexpr->inputcollid, NULL, NULL);
+
+				/* Evaluate scalar directly into left function argument */
+				ExecInitExprRec(scalararg, parent, state,
+								&fcinfo->arg[0], &fcinfo->argnull[0]);
+
+				/*
+				 * Evaluate array argument into our return value.  There's no
+				 * danger in that, because the return value is guaranteed to
+				 * be overwritten by EEOP_SCALARARRAYOP, and will not be
+				 * passed to any other expression.
+				 */
+				ExecInitExprRec(arrayarg, parent, state, resv, resnull);
+
+				/* And perform the operation */
+				scratch.opcode = EEOP_SCALARARRAYOP;
+				scratch.d.scalararrayop.element_type = InvalidOid;
+				scratch.d.scalararrayop.useOr = opexpr->useOr;
+				scratch.d.scalararrayop.finfo = finfo;
+				scratch.d.scalararrayop.fcinfo_data = fcinfo;
+				scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_BoolExpr:
+			{
+				BoolExpr   *boolexpr = (BoolExpr *) node;
+				int			nargs = list_length(boolexpr->args);
+				List	   *adjust_jumps = NIL;
+				int			off;
+				ListCell   *lc;
+
+				/* allocate scratch memory used by all steps of AND/OR */
+				if (boolexpr->boolop != NOT_EXPR)
+					scratch.d.boolexpr.anynull = (bool *) palloc(sizeof(bool));
+
+				/*
+				 * For each argument evaluate the argument itself, then
+				 * perform the bool operation's appropriate handling.
+				 *
+				 * We can evaluate each argument into our result area, since
+				 * the short-circuiting logic means we only need to remember
+				 * previous NULL values.
+				 *
+				 * AND/OR is split into separate STEP_FIRST (one) / STEP (zero
+				 * or more) / STEP_LAST (one) steps, as each of those has to
+				 * perform different work.  The FIRST/LAST split is valid
+				 * because AND/OR have at least two arguments.
+				 */
+				off = 0;
+				foreach(lc, boolexpr->args)
+				{
+					Expr	   *arg = (Expr *) lfirst(lc);
+
+					/* Evaluate argument into our output variable */
+					ExecInitExprRec(arg, parent, state, resv, resnull);
+
+					/* Perform the appropriate step type */
+					switch (boolexpr->boolop)
+					{
+						case AND_EXPR:
+							Assert(nargs >= 2);
+
+							if (off == 0)
+								scratch.opcode = EEOP_BOOL_AND_STEP_FIRST;
+							else if (off + 1 == nargs)
+								scratch.opcode = EEOP_BOOL_AND_STEP_LAST;
+							else
+								scratch.opcode = EEOP_BOOL_AND_STEP;
+							break;
+						case OR_EXPR:
+							Assert(nargs >= 2);
+
+							if (off == 0)
+								scratch.opcode = EEOP_BOOL_OR_STEP_FIRST;
+							else if (off + 1 == nargs)
+								scratch.opcode = EEOP_BOOL_OR_STEP_LAST;
+							else
+								scratch.opcode = EEOP_BOOL_OR_STEP;
+							break;
+						case NOT_EXPR:
+							Assert(nargs == 1);
+
+							scratch.opcode = EEOP_BOOL_NOT_STEP;
+							break;
+						default:
+							elog(ERROR, "unrecognized boolop: %d",
+								 (int) boolexpr->boolop);
+							break;
+					}
+
+					scratch.d.boolexpr.jumpdone = -1;
+					ExprEvalPushStep(state, &scratch);
+					adjust_jumps = lappend_int(adjust_jumps,
+											   state->steps_len - 1);
+					off++;
+				}
+
+				/* adjust jump targets */
+				foreach(lc, adjust_jumps)
+				{
+					ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+
+					Assert(as->d.boolexpr.jumpdone == -1);
+					as->d.boolexpr.jumpdone = state->steps_len;
+				}
+
+				break;
+			}
+
+		case T_SubPlan:
+			{
+				SubPlan    *subplan = (SubPlan *) node;
+				SubPlanState *sstate;
+
+				if (!parent)
+					elog(ERROR, "SubPlan found with no parent plan");
+
+				sstate = ExecInitSubPlan(subplan, parent);
+
+				/* add SubPlanState nodes to parent->subPlan */
+				parent->subPlan = lappend(parent->subPlan, sstate);
+
+				scratch.opcode = EEOP_SUBPLAN;
+				scratch.d.subplan.sstate = sstate;
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_AlternativeSubPlan:
+			{
+				AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
+				AlternativeSubPlanState *asstate;
+
+				if (!parent)
+					elog(ERROR, "AlternativeSubPlan found with no parent plan");
+
+				asstate = ExecInitAlternativeSubPlan(asplan, parent);
+
+				scratch.opcode = EEOP_ALTERNATIVE_SUBPLAN;
+				scratch.d.alternative_subplan.asstate = asstate;
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_FieldSelect:
+			{
+				FieldSelect *fselect = (FieldSelect *) node;
+
+				/* evaluate row/record argument into result area */
+				ExecInitExprRec(fselect->arg, parent, state, resv, resnull);
+
+				/* and extract field */
+				scratch.opcode = EEOP_FIELDSELECT;
+				scratch.d.fieldselect.fieldnum = fselect->fieldnum;
+				scratch.d.fieldselect.resulttype = fselect->resulttype;
+				scratch.d.fieldselect.argdesc = NULL;
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_FieldStore:
+			{
+				FieldStore *fstore = (FieldStore *) node;
+				TupleDesc	tupDesc;
+				TupleDesc  *descp;
+				Datum	   *values;
+				bool	   *nulls;
+				int			ncolumns;
+				ListCell   *l1,
+						   *l2;
+
+				/* find out the number of columns in the composite type */
+				tupDesc = lookup_rowtype_tupdesc(fstore->resulttype, -1);
+				ncolumns = tupDesc->natts;
+				DecrTupleDescRefCount(tupDesc);
+
+				/* create workspace for column values */
+				values = (Datum *) palloc(sizeof(Datum) * ncolumns);
+				nulls = (bool *) palloc(sizeof(bool) * ncolumns);
+
+				/* create workspace for runtime tupdesc cache */
+				descp = (TupleDesc *) palloc(sizeof(TupleDesc));
+				*descp = NULL;
+
+				/* emit code to evaluate the composite input value */
+				ExecInitExprRec(fstore->arg, parent, state, resv, resnull);
+
+				/* next, deform the input tuple into our workspace */
+				scratch.opcode = EEOP_FIELDSTORE_DEFORM;
+				scratch.d.fieldstore.fstore = fstore;
+				scratch.d.fieldstore.argdesc = descp;
+				scratch.d.fieldstore.values = values;
+				scratch.d.fieldstore.nulls = nulls;
+				scratch.d.fieldstore.ncolumns = ncolumns;
+				ExprEvalPushStep(state, &scratch);
+
+				/* evaluate new field values, store in workspace columns */
+				forboth(l1, fstore->newvals, l2, fstore->fieldnums)
+				{
+					Expr	   *e = (Expr *) lfirst(l1);
+					AttrNumber	fieldnum = lfirst_int(l2);
+					Datum	   *save_innermost_caseval;
+					bool	   *save_innermost_casenull;
+
+					if (fieldnum <= 0 || fieldnum > ncolumns)
+						elog(ERROR, "field number %d is out of range in FieldStore",
+							 fieldnum);
+
+					/*
+					 * Use the CaseTestExpr mechanism to pass down the old
+					 * value of the field being replaced; this is needed in
+					 * case the newval is itself a FieldStore or ArrayRef that
+					 * has to obtain and modify the old value.  It's safe to
+					 * reuse the CASE mechanism because there cannot be a CASE
+					 * between here and where the value would be needed, and a
+					 * field assignment can't be within a CASE either.  (So
+					 * saving and restoring innermost_caseval is just
+					 * paranoia, but let's do it anyway.)
+					 */
+					save_innermost_caseval = state->innermost_caseval;
+					save_innermost_casenull = state->innermost_casenull;
+					state->innermost_caseval = &values[fieldnum - 1];
+					state->innermost_casenull = &nulls[fieldnum - 1];
+
+					ExecInitExprRec(e, parent, state,
+									&values[fieldnum - 1],
+									&nulls[fieldnum - 1]);
+
+					state->innermost_caseval = save_innermost_caseval;
+					state->innermost_casenull = save_innermost_casenull;
+				}
+
+				/* finally, form result tuple */
+				scratch.opcode = EEOP_FIELDSTORE_FORM;
+				scratch.d.fieldstore.fstore = fstore;
+				scratch.d.fieldstore.argdesc = descp;
+				scratch.d.fieldstore.values = values;
+				scratch.d.fieldstore.nulls = nulls;
+				scratch.d.fieldstore.ncolumns = ncolumns;
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_RelabelType:
+			{
+				/* relabel doesn't need to do anything at runtime */
+				RelabelType *relabel = (RelabelType *) node;
+
+				ExecInitExprRec(relabel->arg, parent, state, resv, resnull);
+				break;
+			}
+
+		case T_CoerceViaIO:
+			{
+				CoerceViaIO *iocoerce = (CoerceViaIO *) node;
+				Oid			iofunc;
+				bool		typisvarlena;
+				Oid			typioparam;
+				FunctionCallInfo fcinfo_in;
+
+				/* evaluate argument into step's result area */
+				ExecInitExprRec(iocoerce->arg, parent, state, resv, resnull);
+
+				/*
+				 * Prepare both output and input function calls, to be
+				 * evaluated inside a single evaluation step for speed - this
+				 * can be a very common operation.
+				 *
+				 * We don't check permissions here as a type's input/output
+				 * function are assumed to be executable by everyone.
+				 */
+				scratch.opcode = EEOP_IOCOERCE;
+
+				/* lookup the source type's output function */
+				scratch.d.iocoerce.finfo_out = palloc0(sizeof(FmgrInfo));
+				scratch.d.iocoerce.fcinfo_data_out = palloc0(sizeof(FunctionCallInfoData));
+
+				getTypeOutputInfo(exprType((Node *) iocoerce->arg),
+								  &iofunc, &typisvarlena);
+				fmgr_info(iofunc, scratch.d.iocoerce.finfo_out);
+				fmgr_info_set_expr((Node *) node, scratch.d.iocoerce.finfo_out);
+				InitFunctionCallInfoData(*scratch.d.iocoerce.fcinfo_data_out,
+										 scratch.d.iocoerce.finfo_out,
+										 1, InvalidOid, NULL, NULL);
+
+				/* lookup the result type's input function */
+				scratch.d.iocoerce.finfo_in = palloc0(sizeof(FmgrInfo));
+				scratch.d.iocoerce.fcinfo_data_in = palloc0(sizeof(FunctionCallInfoData));
+
+				getTypeInputInfo(iocoerce->resulttype,
+								 &iofunc, &typioparam);
+				fmgr_info(iofunc, scratch.d.iocoerce.finfo_in);
+				fmgr_info_set_expr((Node *) node, scratch.d.iocoerce.finfo_in);
+				InitFunctionCallInfoData(*scratch.d.iocoerce.fcinfo_data_in,
+										 scratch.d.iocoerce.finfo_in,
+										 3, InvalidOid, NULL, NULL);
+
+				/*
+				 * We can preload the second and third arguments for the input
+				 * function, since they're constants.
+				 */
+				fcinfo_in = scratch.d.iocoerce.fcinfo_data_in;
+				fcinfo_in->arg[1] = ObjectIdGetDatum(typioparam);
+				fcinfo_in->argnull[1] = false;
+				fcinfo_in->arg[2] = Int32GetDatum(-1);
+				fcinfo_in->argnull[2] = false;
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_ArrayCoerceExpr:
+			{
+				ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
+				Oid			resultelemtype;
+
+				/* evaluate argument into step's result area */
+				ExecInitExprRec(acoerce->arg, parent, state, resv, resnull);
+
+				resultelemtype = get_element_type(acoerce->resulttype);
+				if (!OidIsValid(resultelemtype))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("target type is not an array")));
+				/* Arrays over domains aren't supported yet */
+				Assert(getBaseType(resultelemtype) == resultelemtype);
+
+				scratch.opcode = EEOP_ARRAYCOERCE;
+				scratch.d.arraycoerce.coerceexpr = acoerce;
+				scratch.d.arraycoerce.resultelemtype = resultelemtype;
+
+				if (OidIsValid(acoerce->elemfuncid))
+				{
+					AclResult	aclresult;
+
+					/* Check permission to call function */
+					aclresult = pg_proc_aclcheck(acoerce->elemfuncid,
+												 GetUserId(),
+												 ACL_EXECUTE);
+					if (aclresult != ACLCHECK_OK)
+						aclcheck_error(aclresult, ACL_KIND_PROC,
+									   get_func_name(acoerce->elemfuncid));
+					InvokeFunctionExecuteHook(acoerce->elemfuncid);
+
+					/* Set up the primary fmgr lookup information */
+					scratch.d.arraycoerce.elemfunc =
+						(FmgrInfo *) palloc0(sizeof(FmgrInfo));
+					fmgr_info(acoerce->elemfuncid,
+							  scratch.d.arraycoerce.elemfunc);
+					fmgr_info_set_expr((Node *) acoerce,
+									   scratch.d.arraycoerce.elemfunc);
+
+					/* Set up workspace for array_map */
+					scratch.d.arraycoerce.amstate =
+						(ArrayMapState *) palloc0(sizeof(ArrayMapState));
+				}
+				else
+				{
+					/* Don't need workspace if there's no conversion func */
+					scratch.d.arraycoerce.elemfunc = NULL;
+					scratch.d.arraycoerce.amstate = NULL;
+				}
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_ConvertRowtypeExpr:
+			{
+				ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node;
+
+				/* evaluate argument into step's result area */
+				ExecInitExprRec(convert->arg, parent, state, resv, resnull);
+
+				/* and push conversion step */
+				scratch.opcode = EEOP_CONVERT_ROWTYPE;
+				scratch.d.convert_rowtype.convert = convert;
+				scratch.d.convert_rowtype.indesc = NULL;
+				scratch.d.convert_rowtype.outdesc = NULL;
+				scratch.d.convert_rowtype.map = NULL;
+				scratch.d.convert_rowtype.initialized = false;
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+			/* note that CaseWhen expressions are handled within this block */
+		case T_CaseExpr:
+			{
+				CaseExpr   *caseExpr = (CaseExpr *) node;
+				List	   *adjust_jumps = NIL;
+				Datum	   *caseval = NULL;
+				bool	   *casenull = NULL;
+				ListCell   *lc;
+
+				/*
+				 * If there's a test expression, we have to evaluate it and
+				 * save the value where the CaseTestExpr placeholders can find
+				 * it.
+				 */
+				if (caseExpr->arg != NULL)
+				{
+					/* Evaluate testexpr into caseval/casenull workspace */
+					caseval = palloc(sizeof(Datum));
+					casenull = palloc(sizeof(bool));
+
+					ExecInitExprRec(caseExpr->arg, parent, state,
+									caseval, casenull);
+
+					/*
+					 * Since value might be read multiple times, force to R/O
+					 * - but only if it could be an expanded datum.
+					 */
+					if (get_typlen(exprType((Node *) caseExpr->arg)) == -1)
+					{
+						/* change caseval in-place */
+						scratch.opcode = EEOP_MAKE_READONLY;
+						scratch.resvalue = caseval;
+						scratch.resnull = casenull;
+						scratch.d.make_readonly.value = caseval;
+						scratch.d.make_readonly.isnull = casenull;
+						ExprEvalPushStep(state, &scratch);
+						/* restore normal settings of scratch fields */
+						scratch.resvalue = resv;
+						scratch.resnull = resnull;
+					}
+				}
+
+				/*
+				 * Prepare to evaluate each of the WHEN clauses in turn; as
+				 * soon as one is true we return the value of the
+				 * corresponding THEN clause.  If none are true then we return
+				 * the value of the ELSE clause, or NULL if there is none.
+				 */
+				foreach(lc, caseExpr->args)
+				{
+					CaseWhen   *when = (CaseWhen *) lfirst(lc);
+					Datum	   *save_innermost_caseval;
+					bool	   *save_innermost_casenull;
+					int			whenstep;
+
+					/*
+					 * Make testexpr result available to CaseTestExpr nodes
+					 * within the condition.  We must save and restore prior
+					 * setting of innermost_caseval fields, in case this node
+					 * is itself within a larger CASE.
+					 *
+					 * If there's no test expression, we don't actually need
+					 * to save and restore these fields; but it's less code to
+					 * just do so unconditionally.
+					 */
+					save_innermost_caseval = state->innermost_caseval;
+					save_innermost_casenull = state->innermost_casenull;
+					state->innermost_caseval = caseval;
+					state->innermost_casenull = casenull;
+
+					/* evaluate condition into CASE's result variables */
+					ExecInitExprRec(when->expr, parent, state, resv, resnull);
+
+					state->innermost_caseval = save_innermost_caseval;
+					state->innermost_casenull = save_innermost_casenull;
+
+					/* If WHEN result isn't true, jump to next CASE arm */
+					scratch.opcode = EEOP_JUMP_IF_NOT_TRUE;
+					scratch.d.jump.jumpdone = -1;		/* computed later */
+					ExprEvalPushStep(state, &scratch);
+					whenstep = state->steps_len - 1;
+
+					/*
+					 * If WHEN result is true, evaluate THEN result, storing
+					 * it into the CASE's result variables.
+					 */
+					ExecInitExprRec(when->result, parent, state, resv, resnull);
+
+					/* Emit JUMP step to jump to end of CASE's code */
+					scratch.opcode = EEOP_JUMP;
+					scratch.d.jump.jumpdone = -1;		/* computed later */
+					ExprEvalPushStep(state, &scratch);
+
+					/*
+					 * Don't know address for that jump yet, compute once the
+					 * whole CASE expression is built.
+					 */
+					adjust_jumps = lappend_int(adjust_jumps,
+											   state->steps_len - 1);
+
+					/*
+					 * But we can set WHEN test's jump target now, to make it
+					 * jump to the next WHEN subexpression or the ELSE.
+					 */
+					state->steps[whenstep].d.jump.jumpdone = state->steps_len;
+				}
+
+				if (caseExpr->defresult)
+				{
+					/* evaluate ELSE expr into CASE's result variables */
+					ExecInitExprRec(caseExpr->defresult, parent, state,
+									resv, resnull);
+				}
+				else
+				{
+					/* default ELSE is to return NULL */
+					scratch.opcode = EEOP_CONST;
+					scratch.d.constval.value = (Datum) 0;
+					scratch.d.constval.isnull = true;
+					ExprEvalPushStep(state, &scratch);
+				}
+
+				/* adjust jump targets */
+				foreach(lc, adjust_jumps)
+				{
+					ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+
+					Assert(as->opcode == EEOP_JUMP);
+					Assert(as->d.jump.jumpdone == -1);
+					as->d.jump.jumpdone = state->steps_len;
+				}
+
+				break;
+			}
+
+		case T_CaseTestExpr:
+			{
+				/*
+				 * Read from location identified by innermost_caseval.  Note
+				 * that innermost_caseval could be NULL, if this node isn't
+				 * actually within a CASE structure; some parts of the system
+				 * abuse CaseTestExpr to cause a read of a value externally
+				 * supplied in econtext->caseValue_datum.  We'll take care of
+				 * that scenario at runtime.
+				 */
+				scratch.opcode = EEOP_CASE_TESTVAL;
+				scratch.d.casetest.value = state->innermost_caseval;
+				scratch.d.casetest.isnull = state->innermost_casenull;
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_ArrayExpr:
+			{
+				ArrayExpr  *arrayexpr = (ArrayExpr *) node;
+				int			nelems = list_length(arrayexpr->elements);
+				ListCell   *lc;
+				int			elemoff;
+
+				/*
+				 * Evaluate by computing each element, and then forming the
+				 * array.  Elements are computed into scratch arrays
+				 * associated with the ARRAYEXPR step.
+				 */
+				scratch.opcode = EEOP_ARRAYEXPR;
+				scratch.d.arrayexpr.elemvalues =
+					(Datum *) palloc(sizeof(Datum) * nelems);
+				scratch.d.arrayexpr.elemnulls =
+					(bool *) palloc(sizeof(bool) * nelems);
+				scratch.d.arrayexpr.nelems = nelems;
+
+				/* fill remaining fields of step */
+				scratch.d.arrayexpr.multidims = arrayexpr->multidims;
+				scratch.d.arrayexpr.elemtype = arrayexpr->element_typeid;
+
+				/* do one-time catalog lookup for type info */
+				get_typlenbyvalalign(arrayexpr->element_typeid,
+									 &scratch.d.arrayexpr.elemlength,
+									 &scratch.d.arrayexpr.elembyval,
+									 &scratch.d.arrayexpr.elemalign);
+
+				/* prepare to evaluate all arguments */
+				elemoff = 0;
+				foreach(lc, arrayexpr->elements)
+				{
+					Expr	   *e = (Expr *) lfirst(lc);
+
+					ExecInitExprRec(e, parent, state,
+									&scratch.d.arrayexpr.elemvalues[elemoff],
+									&scratch.d.arrayexpr.elemnulls[elemoff]);
+					elemoff++;
+				}
+
+				/* and then collect all into an array */
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_RowExpr:
+			{
+				RowExpr    *rowexpr = (RowExpr *) node;
+				int			nelems = list_length(rowexpr->args);
+				TupleDesc	tupdesc;
+				Form_pg_attribute *attrs;
+				int			i;
+				ListCell   *l;
+
+				/* Build tupdesc to describe result tuples */
+				if (rowexpr->row_typeid == RECORDOID)
+				{
+					/* generic record, use types of given expressions */
+					tupdesc = ExecTypeFromExprList(rowexpr->args);
+				}
+				else
+				{
+					/* it's been cast to a named type, use that */
+					tupdesc = lookup_rowtype_tupdesc_copy(rowexpr->row_typeid, -1);
+				}
+				/* In either case, adopt RowExpr's column aliases */
+				ExecTypeSetColNames(tupdesc, rowexpr->colnames);
+				/* Bless the tupdesc in case it's now of type RECORD */
+				BlessTupleDesc(tupdesc);
+
+				/*
+				 * In the named-type case, the tupdesc could have more columns
+				 * than are in the args list, since the type might have had
+				 * columns added since the ROW() was parsed.  We want those
+				 * extra columns to go to nulls, so we make sure that the
+				 * workspace arrays are large enough and then initialize any
+				 * extra columns to read as NULLs.
+				 */
+				Assert(nelems <= tupdesc->natts);
+				nelems = Max(nelems, tupdesc->natts);
+
+				/*
+				 * Evaluate by first building datums for each field, and then
+				 * a final step forming the composite datum.
+				 */
+				scratch.opcode = EEOP_ROW;
+				scratch.d.row.tupdesc = tupdesc;
+
+				/* space for the individual field datums */
+				scratch.d.row.elemvalues =
+					(Datum *) palloc(sizeof(Datum) * nelems);
+				scratch.d.row.elemnulls =
+					(bool *) palloc(sizeof(bool) * nelems);
+				/* as explained above, make sure any extra columns are null */
+				memset(scratch.d.row.elemnulls, true, sizeof(bool) * nelems);
+
+				/* Set up evaluation, skipping any deleted columns */
+				attrs = tupdesc->attrs;
+				i = 0;
+				foreach(l, rowexpr->args)
+				{
+					Expr	   *e = (Expr *) lfirst(l);
+
+					if (!attrs[i]->attisdropped)
+					{
+						/*
+						 * Guard against ALTER COLUMN TYPE on rowtype since
+						 * the RowExpr was created.  XXX should we check
+						 * typmod too?	Not sure we can be sure it'll be the
+						 * same.
+						 */
+						if (exprType((Node *) e) != attrs[i]->atttypid)
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("ROW() column has type %s instead of type %s",
+										format_type_be(exprType((Node *) e)),
+									   format_type_be(attrs[i]->atttypid))));
+					}
+					else
+					{
+						/*
+						 * Ignore original expression and insert a NULL. We
+						 * don't really care what type of NULL it is, so
+						 * always make an int4 NULL.
+						 */
+						e = (Expr *) makeNullConst(INT4OID, -1, InvalidOid);
+					}
+
+					/* Evaluate column expr into appropriate workspace slot */
+					ExecInitExprRec(e, parent, state,
+									&scratch.d.row.elemvalues[i],
+									&scratch.d.row.elemnulls[i]);
+					i++;
+				}
+
+				/* And finally build the row value */
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_RowCompareExpr:
+			{
+				RowCompareExpr *rcexpr = (RowCompareExpr *) node;
+				int			nopers = list_length(rcexpr->opnos);
+				List	   *adjust_jumps = NIL;
+				ListCell   *l_left_expr,
+						   *l_right_expr,
+						   *l_opno,
+						   *l_opfamily,
+						   *l_inputcollid;
+				ListCell   *lc;
+				int			off;
+
+				/*
+				 * Iterate over each field, prepare comparisons.  To handle
+				 * NULL results, prepare jumps to after the expression.  If a
+				 * comparison yields a != 0 result, jump to the final step.
+				 */
+				Assert(list_length(rcexpr->largs) == nopers);
+				Assert(list_length(rcexpr->rargs) == nopers);
+				Assert(list_length(rcexpr->opfamilies) == nopers);
+				Assert(list_length(rcexpr->inputcollids) == nopers);
+
+				off = 0;
+				for (off = 0,
+					 l_left_expr = list_head(rcexpr->largs),
+					 l_right_expr = list_head(rcexpr->rargs),
+					 l_opno = list_head(rcexpr->opnos),
+					 l_opfamily = list_head(rcexpr->opfamilies),
+					 l_inputcollid = list_head(rcexpr->inputcollids);
+					 off < nopers;
+					 off++,
+					 l_left_expr = lnext(l_left_expr),
+					 l_right_expr = lnext(l_right_expr),
+					 l_opno = lnext(l_opno),
+					 l_opfamily = lnext(l_opfamily),
+					 l_inputcollid = lnext(l_inputcollid))
+				{
+					Expr	   *left_expr = (Expr *) lfirst(l_left_expr);
+					Expr	   *right_expr = (Expr *) lfirst(l_right_expr);
+					Oid			opno = lfirst_oid(l_opno);
+					Oid			opfamily = lfirst_oid(l_opfamily);
+					Oid			inputcollid = lfirst_oid(l_inputcollid);
+					int			strategy;
+					Oid			lefttype;
+					Oid			righttype;
+					Oid			proc;
+					FmgrInfo   *finfo;
+					FunctionCallInfo fcinfo;
+
+					get_op_opfamily_properties(opno, opfamily, false,
+											   &strategy,
+											   &lefttype,
+											   &righttype);
+					proc = get_opfamily_proc(opfamily,
+											 lefttype,
+											 righttype,
+											 BTORDER_PROC);
+
+					/* Set up the primary fmgr lookup information */
+					finfo = palloc0(sizeof(FmgrInfo));
+					fcinfo = palloc0(sizeof(FunctionCallInfoData));
+					fmgr_info(proc, finfo);
+					fmgr_info_set_expr((Node *) node, finfo);
+					InitFunctionCallInfoData(*fcinfo, finfo, 2,
+											 inputcollid, NULL, NULL);
+
+					/*
+					 * If we enforced permissions checks on index support
+					 * functions, we'd need to make a check here.  But the
+					 * index support machinery doesn't do that, and thus
+					 * neither does this code.
+					 */
+
+					/* evaluate left and right args directly into fcinfo */
+					ExecInitExprRec(left_expr, parent, state,
+									&fcinfo->arg[0], &fcinfo->argnull[0]);
+					ExecInitExprRec(right_expr, parent, state,
+									&fcinfo->arg[1], &fcinfo->argnull[1]);
+
+					scratch.opcode = EEOP_ROWCOMPARE_STEP;
+					scratch.d.rowcompare_step.finfo = finfo;
+					scratch.d.rowcompare_step.fcinfo_data = fcinfo;
+					scratch.d.rowcompare_step.fn_addr = finfo->fn_addr;
+					/* jump targets filled below */
+					scratch.d.rowcompare_step.jumpnull = -1;
+					scratch.d.rowcompare_step.jumpdone = -1;
+
+					ExprEvalPushStep(state, &scratch);
+					adjust_jumps = lappend_int(adjust_jumps,
+											   state->steps_len - 1);
+				}
+
+				/*
+				 * We could have a zero-column rowtype, in which case the rows
+				 * necessarily compare equal.
+				 */
+				if (nopers == 0)
+				{
+					scratch.opcode = EEOP_CONST;
+					scratch.d.constval.value = Int32GetDatum(0);
+					scratch.d.constval.isnull = false;
+					ExprEvalPushStep(state, &scratch);
+				}
+
+				/* Finally, examine the last comparison result */
+				scratch.opcode = EEOP_ROWCOMPARE_FINAL;
+				scratch.d.rowcompare_final.rctype = rcexpr->rctype;
+				ExprEvalPushStep(state, &scratch);
+
+				/* adjust jump targetss */
+				foreach(lc, adjust_jumps)
+				{
+					ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+
+					Assert(as->opcode == EEOP_ROWCOMPARE_STEP);
+					Assert(as->d.rowcompare_step.jumpdone == -1);
+					Assert(as->d.rowcompare_step.jumpnull == -1);
+
+					/* jump to comparison evaluation */
+					as->d.rowcompare_step.jumpdone = state->steps_len - 1;
+					/* jump to the following expression */
+					as->d.rowcompare_step.jumpnull = state->steps_len;
+				}
+
+				break;
+			}
+
+		case T_CoalesceExpr:
+			{
+				CoalesceExpr *coalesce = (CoalesceExpr *) node;
+				List	   *adjust_jumps = NIL;
+				ListCell   *lc;
+
+				/* We assume there's at least one arg */
+				Assert(coalesce->args != NIL);
+
+				/*
+				 * Prepare evaluation of all coalesced arguments, after each
+				 * one push a step that short-circuits if not null.
+				 */
+				foreach(lc, coalesce->args)
+				{
+					Expr	   *e = (Expr *) lfirst(lc);
+
+					/* evaluate argument, directly into result datum */
+					ExecInitExprRec(e, parent, state, resv, resnull);
+
+					/* if it's not null, skip to end of COALESCE expr */
+					scratch.opcode = EEOP_JUMP_IF_NOT_NULL;
+					scratch.d.jump.jumpdone = -1;		/* adjust later */
+					ExprEvalPushStep(state, &scratch);
+
+					adjust_jumps = lappend_int(adjust_jumps,
+											   state->steps_len - 1);
+				}
+
+				/*
+				 * No need to add a constant NULL return - we only can get to
+				 * the end of the expression if a NULL already is being
+				 * returned.
+				 */
+
+				/* adjust jump targets */
+				foreach(lc, adjust_jumps)
+				{
+					ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+
+					Assert(as->opcode == EEOP_JUMP_IF_NOT_NULL);
+					Assert(as->d.jump.jumpdone == -1);
+					as->d.jump.jumpdone = state->steps_len;
+				}
+
+				break;
+			}
+
+		case T_MinMaxExpr:
+			{
+				MinMaxExpr *minmaxexpr = (MinMaxExpr *) node;
+				int			nelems = list_length(minmaxexpr->args);
+				TypeCacheEntry *typentry;
+				FmgrInfo   *finfo;
+				FunctionCallInfo fcinfo;
+				ListCell   *lc;
+				int			off;
+
+				/* Look up the btree comparison function for the datatype */
+				typentry = lookup_type_cache(minmaxexpr->minmaxtype,
+											 TYPECACHE_CMP_PROC);
+				if (!OidIsValid(typentry->cmp_proc))
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_FUNCTION),
+							 errmsg("could not identify a comparison function for type %s",
+									format_type_be(minmaxexpr->minmaxtype))));
+
+				/*
+				 * If we enforced permissions checks on index support
+				 * functions, we'd need to make a check here.  But the index
+				 * support machinery doesn't do that, and thus neither does
+				 * this code.
+				 */
+
+				/* Perform function lookup */
+				finfo = palloc0(sizeof(FmgrInfo));
+				fcinfo = palloc0(sizeof(FunctionCallInfoData));
+				fmgr_info(typentry->cmp_proc, finfo);
+				fmgr_info_set_expr((Node *) node, finfo);
+				InitFunctionCallInfoData(*fcinfo, finfo, 2,
+										 minmaxexpr->inputcollid, NULL, NULL);
+
+				scratch.opcode = EEOP_MINMAX;
+				/* allocate space to store arguments */
+				scratch.d.minmax.values =
+					(Datum *) palloc(sizeof(Datum) * nelems);
+				scratch.d.minmax.nulls =
+					(bool *) palloc(sizeof(bool) * nelems);
+				scratch.d.minmax.nelems = nelems;
+
+				scratch.d.minmax.op = minmaxexpr->op;
+				scratch.d.minmax.finfo = finfo;
+				scratch.d.minmax.fcinfo_data = fcinfo;
+
+				/* evaluate expressions into minmax->values/nulls */
+				off = 0;
+				foreach(lc, minmaxexpr->args)
+				{
+					Expr	   *e = (Expr *) lfirst(lc);
+
+					ExecInitExprRec(e, parent, state,
+									&scratch.d.minmax.values[off],
+									&scratch.d.minmax.nulls[off]);
+					off++;
+				}
+
+				/* and push the final comparison */
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_SQLValueFunction:
+			{
+				SQLValueFunction *svf = (SQLValueFunction *) node;
+
+				scratch.opcode = EEOP_SQLVALUEFUNCTION;
+				scratch.d.sqlvaluefunction.svf = svf;
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_XmlExpr:
+			{
+				XmlExpr    *xexpr = (XmlExpr *) node;
+				int			nnamed = list_length(xexpr->named_args);
+				int			nargs = list_length(xexpr->args);
+				int			off;
+				ListCell   *arg;
+
+				scratch.opcode = EEOP_XMLEXPR;
+				scratch.d.xmlexpr.xexpr = xexpr;
+
+				/* allocate space for storing all the arguments */
+				if (nnamed)
+				{
+					scratch.d.xmlexpr.named_argvalue =
+						(Datum *) palloc(sizeof(Datum) * nnamed);
+					scratch.d.xmlexpr.named_argnull =
+						(bool *) palloc(sizeof(bool) * nnamed);
+				}
+				else
+				{
+					scratch.d.xmlexpr.named_argvalue = NULL;
+					scratch.d.xmlexpr.named_argnull = NULL;
+				}
+
+				if (nargs)
+				{
+					scratch.d.xmlexpr.argvalue =
+						(Datum *) palloc(sizeof(Datum) * nargs);
+					scratch.d.xmlexpr.argnull =
+						(bool *) palloc(sizeof(bool) * nargs);
+				}
+				else
+				{
+					scratch.d.xmlexpr.argvalue = NULL;
+					scratch.d.xmlexpr.argnull = NULL;
+				}
+
+				/* prepare argument execution */
+				off = 0;
+				foreach(arg, xexpr->named_args)
+				{
+					Expr	   *e = (Expr *) lfirst(arg);
+
+					ExecInitExprRec(e, parent, state,
+									&scratch.d.xmlexpr.named_argvalue[off],
+									&scratch.d.xmlexpr.named_argnull[off]);
+					off++;
+				}
+
+				off = 0;
+				foreach(arg, xexpr->args)
+				{
+					Expr	   *e = (Expr *) lfirst(arg);
+
+					ExecInitExprRec(e, parent, state,
+									&scratch.d.xmlexpr.argvalue[off],
+									&scratch.d.xmlexpr.argnull[off]);
+					off++;
+				}
+
+				/* and evaluate the actual XML expression */
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_NullTest:
+			{
+				NullTest   *ntest = (NullTest *) node;
+
+				if (ntest->nulltesttype == IS_NULL)
+				{
+					if (ntest->argisrow)
+						scratch.opcode = EEOP_NULLTEST_ROWISNULL;
+					else
+						scratch.opcode = EEOP_NULLTEST_ISNULL;
+				}
+				else if (ntest->nulltesttype == IS_NOT_NULL)
+				{
+					if (ntest->argisrow)
+						scratch.opcode = EEOP_NULLTEST_ROWISNOTNULL;
+					else
+						scratch.opcode = EEOP_NULLTEST_ISNOTNULL;
+				}
+				else
+				{
+					elog(ERROR, "unrecognized nulltesttype: %d",
+						 (int) ntest->nulltesttype);
+				}
+				/* initialize cache in case it's a row test */
+				scratch.d.nulltest_row.argdesc = NULL;
+
+				/* first evaluate argument into result variable */
+				ExecInitExprRec(ntest->arg, parent, state,
+								resv, resnull);
+
+				/* then push the test of that argument */
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_BooleanTest:
+			{
+				BooleanTest *btest = (BooleanTest *) node;
+
+				/*
+				 * Evaluate argument, directly into result datum.  That's ok,
+				 * because resv/resnull is definitely not used anywhere else,
+				 * and will get overwritten by the below EEOP_BOOLTEST_IS_*
+				 * step.
+				 */
+				ExecInitExprRec(btest->arg, parent, state, resv, resnull);
+
+				switch (btest->booltesttype)
+				{
+					case IS_TRUE:
+						scratch.opcode = EEOP_BOOLTEST_IS_TRUE;
+						break;
+					case IS_NOT_TRUE:
+						scratch.opcode = EEOP_BOOLTEST_IS_NOT_TRUE;
+						break;
+					case IS_FALSE:
+						scratch.opcode = EEOP_BOOLTEST_IS_FALSE;
+						break;
+					case IS_NOT_FALSE:
+						scratch.opcode = EEOP_BOOLTEST_IS_NOT_FALSE;
+						break;
+					case IS_UNKNOWN:
+						/* Same as scalar IS NULL test */
+						scratch.opcode = EEOP_NULLTEST_ISNULL;
+						break;
+					case IS_NOT_UNKNOWN:
+						/* Same as scalar IS NOT NULL test */
+						scratch.opcode = EEOP_NULLTEST_ISNOTNULL;
+						break;
+					default:
+						elog(ERROR, "unrecognized booltesttype: %d",
+							 (int) btest->booltesttype);
+				}
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_CoerceToDomain:
+			{
+				CoerceToDomain *ctest = (CoerceToDomain *) node;
+
+				ExecInitCoerceToDomain(&scratch, ctest, parent, state,
+									   resv, resnull);
+				break;
+			}
+
+		case T_CoerceToDomainValue:
+			{
+				/*
+				 * Read from location identified by innermost_domainval.  Note
+				 * that innermost_domainval could be NULL, if we're compiling
+				 * a standalone domain check rather than one embedded in a
+				 * larger expression.  In that case we must read from
+				 * econtext->domainValue_datum.  We'll take care of that
+				 * scenario at runtime.
+				 */
+				scratch.opcode = EEOP_DOMAIN_TESTVAL;
+				/* we share instruction union variant with case testval */
+				scratch.d.casetest.value = state->innermost_domainval;
+				scratch.d.casetest.isnull = state->innermost_domainnull;
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_CurrentOfExpr:
+			{
+				scratch.opcode = EEOP_CURRENTOFEXPR;
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		default:
+			elog(ERROR, "unrecognized node type: %d",
+				 (int) nodeTag(node));
+			break;
+	}
+}
+
+/*
+ * Add another expression evaluation step to ExprState->steps.
+ *
+ * Note that this potentially re-allocates es->steps, therefore no pointer
+ * into that array may be used while the expression is still being built.
+ */
+static void
+ExprEvalPushStep(ExprState *es, const ExprEvalStep *s)
+{
+	if (es->steps_alloc == 0)
+	{
+		es->steps_alloc = 16;
+		es->steps = palloc(sizeof(ExprEvalStep) * es->steps_alloc);
+	}
+	else if (es->steps_alloc == es->steps_len)
+	{
+		es->steps_alloc *= 2;
+		es->steps = repalloc(es->steps,
+							 sizeof(ExprEvalStep) * es->steps_alloc);
+	}
+
+	memcpy(&es->steps[es->steps_len++], s, sizeof(ExprEvalStep));
+}
+
+/*
+ * Perform setup necessary for the evaluation of a function-like expression,
+ * appending argument evaluation steps to the steps list in *state, and
+ * setting up *scratch so it is ready to be pushed.
+ *
+ * *scratch is not pushed here, so that callers may override the opcode,
+ * which is useful for function-like cases like DISTINCT.
+ */
+static void
+ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
+			 Oid inputcollid, PlanState *parent, ExprState *state)
+{
+	int			nargs = list_length(args);
+	AclResult	aclresult;
+	FmgrInfo   *flinfo;
+	FunctionCallInfo fcinfo;
+	int			argno;
+	ListCell   *lc;
+
+	/* Check permission to call function */
+	aclresult = pg_proc_aclcheck(funcid, GetUserId(), ACL_EXECUTE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(funcid));
+	InvokeFunctionExecuteHook(funcid);
+
+	/*
+	 * Safety check on nargs.  Under normal circumstances this should never
+	 * fail, as parser should check sooner.  But possibly it might fail if
+	 * server has been compiled with FUNC_MAX_ARGS smaller than some functions
+	 * declared in pg_proc?
+	 */
+	if (nargs > FUNC_MAX_ARGS)
+		ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+			 errmsg_plural("cannot pass more than %d argument to a function",
+						   "cannot pass more than %d arguments to a function",
+						   FUNC_MAX_ARGS,
+						   FUNC_MAX_ARGS)));
+
+	/* Allocate function lookup data and parameter workspace for this call */
+	scratch->d.func.finfo = palloc0(sizeof(FmgrInfo));
+	scratch->d.func.fcinfo_data = palloc0(sizeof(FunctionCallInfoData));
+	flinfo = scratch->d.func.finfo;
+	fcinfo = scratch->d.func.fcinfo_data;
+
+	/* Set up the primary fmgr lookup information */
+	fmgr_info(funcid, flinfo);
+	fmgr_info_set_expr((Node *) node, flinfo);
+
+	/* Initialize function call parameter structure too */
+	InitFunctionCallInfoData(*fcinfo, flinfo,
+							 nargs, inputcollid, NULL, NULL);
+
+	/* Keep extra copies of this info to save an indirection at runtime */
+	scratch->d.func.fn_addr = flinfo->fn_addr;
+	scratch->d.func.nargs = nargs;
+
+	/* We only support non-set functions here */
+	if (flinfo->fn_retset)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+
+	/* Build code to evaluate arguments directly into the fcinfo struct */
+	argno = 0;
+	foreach(lc, args)
+	{
+		Expr	   *arg = (Expr *) lfirst(lc);
+
+		if (IsA(arg, Const))
+		{
+			/*
+			 * Don't evaluate const arguments every round; especially
+			 * interesting for constants in comparisons.
+			 */
+			Const	   *con = (Const *) arg;
+
+			fcinfo->arg[argno] = con->constvalue;
+			fcinfo->argnull[argno] = con->constisnull;
+		}
+		else
+		{
+			ExecInitExprRec(arg, parent, state,
+							&fcinfo->arg[argno], &fcinfo->argnull[argno]);
+		}
+		argno++;
+	}
+
+	/* Insert appropriate opcode depending on strictness and stats level */
+	if (pgstat_track_functions <= flinfo->fn_stats)
+	{
+		if (flinfo->fn_strict && nargs > 0)
+			scratch->opcode = EEOP_FUNCEXPR_STRICT;
+		else
+			scratch->opcode = EEOP_FUNCEXPR;
+	}
+	else
+	{
+		if (flinfo->fn_strict && nargs > 0)
+			scratch->opcode = EEOP_FUNCEXPR_STRICT_FUSAGE;
+		else
+			scratch->opcode = EEOP_FUNCEXPR_FUSAGE;
+	}
+}
+
+/*
+ * Add expression steps deforming the ExprState's inner/outer/scan slots
+ * as much as required by the expression.
+ */
+static void
+ExecInitExprSlots(ExprState *state, Node *node)
+{
+	LastAttnumInfo info = {0, 0, 0};
+	ExprEvalStep scratch;
+
+	/*
+	 * Figure out which attributes we're going to need.
+	 */
+	get_last_attnums_walker(node, &info);
+
+	/* Emit steps as needed */
+	if (info.last_inner > 0)
+	{
+		scratch.opcode = EEOP_INNER_FETCHSOME;
+		scratch.d.fetch.last_var = info.last_inner;
+		ExprEvalPushStep(state, &scratch);
+	}
+	if (info.last_outer > 0)
+	{
+		scratch.opcode = EEOP_OUTER_FETCHSOME;
+		scratch.d.fetch.last_var = info.last_outer;
+		ExprEvalPushStep(state, &scratch);
+	}
+	if (info.last_scan > 0)
+	{
+		scratch.opcode = EEOP_SCAN_FETCHSOME;
+		scratch.d.fetch.last_var = info.last_scan;
+		ExprEvalPushStep(state, &scratch);
+	}
+}
+
+/*
+ * get_last_attnums_walker: expression walker for ExecInitExprSlots
+ */
+static bool
+get_last_attnums_walker(Node *node, LastAttnumInfo *info)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *variable = (Var *) node;
+		AttrNumber	attnum = variable->varattno;
+
+		switch (variable->varno)
+		{
+			case INNER_VAR:
+				info->last_inner = Max(info->last_inner, attnum);
+				break;
+
+			case OUTER_VAR:
+				info->last_outer = Max(info->last_outer, attnum);
+				break;
+
+				/* INDEX_VAR is handled by default case */
+
+			default:
+				info->last_scan = Max(info->last_scan, attnum);
+				break;
+		}
+		return false;
+	}
+
+	/*
+	 * Don't examine the arguments or filters of Aggrefs or WindowFuncs,
+	 * because those do not represent expressions to be evaluated within the
+	 * calling expression's econtext.  GroupingFunc arguments are never
+	 * evaluated at all.
+	 */
+	if (IsA(node, Aggref))
+		return false;
+	if (IsA(node, WindowFunc))
+		return false;
+	if (IsA(node, GroupingFunc))
+		return false;
+	return expression_tree_walker(node, get_last_attnums_walker,
+								  (void *) info);
+}
+
+/*
+ * Prepare step for the evaluation of a whole-row variable.
+ * The caller still has to push the step.
+ */
+static void
+ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, PlanState *parent)
+{
+	/* fill in all but the target */
+	scratch->opcode = EEOP_WHOLEROW;
+	scratch->d.wholerow.var = variable;
+	scratch->d.wholerow.first = true;
+	scratch->d.wholerow.slow = false;
+	scratch->d.wholerow.tupdesc = NULL; /* filled at runtime */
+	scratch->d.wholerow.junkFilter = NULL;
+
+	/*
+	 * If the input tuple came from a subquery, it might contain "resjunk"
+	 * columns (such as GROUP BY or ORDER BY columns), which we don't want to
+	 * keep in the whole-row result.  We can get rid of such columns by
+	 * passing the tuple through a JunkFilter --- but to make one, we have to
+	 * lay our hands on the subquery's targetlist.  Fortunately, there are not
+	 * very many cases where this can happen, and we can identify all of them
+	 * by examining our parent PlanState.  We assume this is not an issue in
+	 * standalone expressions that don't have parent plans.  (Whole-row Vars
+	 * can occur in such expressions, but they will always be referencing
+	 * table rows.)
+	 */
+	if (parent)
+	{
+		PlanState  *subplan = NULL;
+
+		switch (nodeTag(parent))
+		{
+			case T_SubqueryScanState:
+				subplan = ((SubqueryScanState *) parent)->subplan;
+				break;
+			case T_CteScanState:
+				subplan = ((CteScanState *) parent)->cteplanstate;
+				break;
+			default:
+				break;
+		}
+
+		if (subplan)
+		{
+			bool		junk_filter_needed = false;
+			ListCell   *tlist;
+
+			/* Detect whether subplan tlist actually has any junk columns */
+			foreach(tlist, subplan->plan->targetlist)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(tlist);
+
+				if (tle->resjunk)
+				{
+					junk_filter_needed = true;
+					break;
+				}
+			}
+
+			/* If so, build the junkfilter now */
+			if (junk_filter_needed)
+			{
+				scratch->d.wholerow.junkFilter =
+					ExecInitJunkFilter(subplan->plan->targetlist,
+									   ExecGetResultType(subplan)->tdhasoid,
+									   ExecInitExtraTupleSlot(parent->state));
+			}
+		}
+	}
+}
+
+/*
+ * Prepare evaluation of an ArrayRef expression.
+ */
+static void
+ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, PlanState *parent,
+				 ExprState *state, Datum *resv, bool *resnull)
+{
+	bool		isAssignment = (aref->refassgnexpr != NULL);
+	ArrayRefState *arefstate = palloc0(sizeof(ArrayRefState));
+	List	   *adjust_jumps = NIL;
+	ListCell   *lc;
+	int			i;
+
+	/* Fill constant fields of ArrayRefState */
+	arefstate->isassignment = isAssignment;
+	arefstate->refelemtype = aref->refelemtype;
+	arefstate->refattrlength = get_typlen(aref->refarraytype);
+	get_typlenbyvalalign(aref->refelemtype,
+						 &arefstate->refelemlength,
+						 &arefstate->refelembyval,
+						 &arefstate->refelemalign);
+
+	/*
+	 * Evaluate array input.  It's safe to do so into resv/resnull, because we
+	 * won't use that as target for any of the other subexpressions, and it'll
+	 * be overwritten by the final EEOP_ARRAYREF_FETCH/ASSIGN step, which is
+	 * pushed last.
+	 */
+	ExecInitExprRec(aref->refexpr, parent, state, resv, resnull);
+
+	/*
+	 * If refexpr yields NULL, and it's a fetch, then result is NULL.  We can
+	 * implement this with just JUMP_IF_NULL, since we evaluated the array
+	 * into the desired target location.
+	 */
+	if (!isAssignment)
+	{
+		scratch->opcode = EEOP_JUMP_IF_NULL;
+		scratch->d.jump.jumpdone = -1;	/* adjust later */
+		ExprEvalPushStep(state, scratch);
+		adjust_jumps = lappend_int(adjust_jumps,
+								   state->steps_len - 1);
+	}
+
+	/* Verify subscript list lengths are within limit */
+	if (list_length(aref->refupperindexpr) > MAXDIM)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
+						list_length(aref->refupperindexpr), MAXDIM)));
+
+	if (list_length(aref->reflowerindexpr) > MAXDIM)
+		ereport(ERROR,
+				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+				 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
+						list_length(aref->reflowerindexpr), MAXDIM)));
+
+	/* Evaluate upper subscripts */
+	i = 0;
+	foreach(lc, aref->refupperindexpr)
+	{
+		Expr	   *e = (Expr *) lfirst(lc);
+
+		/* When slicing, individual subscript bounds can be omitted */
+		if (!e)
+		{
+			arefstate->upperprovided[i] = false;
+			i++;
+			continue;
+		}
+
+		arefstate->upperprovided[i] = true;
+
+		/* Each subscript is evaluated into subscriptvalue/subscriptnull */
+		ExecInitExprRec(e, parent, state,
+					  &arefstate->subscriptvalue, &arefstate->subscriptnull);
+
+		/* ... and then ARRAYREF_SUBSCRIPT saves it into step's workspace */
+		scratch->opcode = EEOP_ARRAYREF_SUBSCRIPT;
+		scratch->d.arrayref_subscript.state = arefstate;
+		scratch->d.arrayref_subscript.off = i;
+		scratch->d.arrayref_subscript.isupper = true;
+		scratch->d.arrayref_subscript.jumpdone = -1;	/* adjust later */
+		ExprEvalPushStep(state, scratch);
+		adjust_jumps = lappend_int(adjust_jumps,
+								   state->steps_len - 1);
+		i++;
+	}
+	arefstate->numupper = i;
+
+	/* Evaluate lower subscripts similarly */
+	i = 0;
+	foreach(lc, aref->reflowerindexpr)
+	{
+		Expr	   *e = (Expr *) lfirst(lc);
+
+		/* When slicing, individual subscript bounds can be omitted */
+		if (!e)
+		{
+			arefstate->lowerprovided[i] = false;
+			i++;
+			continue;
+		}
+
+		arefstate->lowerprovided[i] = true;
+
+		/* Each subscript is evaluated into subscriptvalue/subscriptnull */
+		ExecInitExprRec(e, parent, state,
+					  &arefstate->subscriptvalue, &arefstate->subscriptnull);
+
+		/* ... and then ARRAYREF_SUBSCRIPT saves it into step's workspace */
+		scratch->opcode = EEOP_ARRAYREF_SUBSCRIPT;
+		scratch->d.arrayref_subscript.state = arefstate;
+		scratch->d.arrayref_subscript.off = i;
+		scratch->d.arrayref_subscript.isupper = false;
+		scratch->d.arrayref_subscript.jumpdone = -1;	/* adjust later */
+		ExprEvalPushStep(state, scratch);
+		adjust_jumps = lappend_int(adjust_jumps,
+								   state->steps_len - 1);
+		i++;
+	}
+	arefstate->numlower = i;
+
+	/* Should be impossible if parser is sane, but check anyway: */
+	if (arefstate->numlower != 0 &&
+		arefstate->numupper != arefstate->numlower)
+		elog(ERROR, "upper and lower index lists are not same length");
+
+	if (isAssignment)
+	{
+		Datum	   *save_innermost_caseval;
+		bool	   *save_innermost_casenull;
+
+		/*
+		 * We might have a nested-assignment situation, in which the
+		 * refassgnexpr is itself a FieldStore or ArrayRef that needs to
+		 * obtain and modify the previous value of the array element or slice
+		 * being replaced.  If so, we have to extract that value from the
+		 * array and pass it down via the CaseTextExpr mechanism.  It's safe
+		 * to reuse the CASE mechanism because there cannot be a CASE between
+		 * here and where the value would be needed, and an array assignment
+		 * can't be within a CASE either.  (So saving and restoring
+		 * innermost_caseval is just paranoia, but let's do it anyway.)
+		 *
+		 * Since fetching the old element might be a nontrivial expense, do it
+		 * only if the argument appears to actually need it.
+		 */
+		if (isAssignmentIndirectionExpr(aref->refassgnexpr))
+		{
+			scratch->opcode = EEOP_ARRAYREF_OLD;
+			scratch->d.arrayref.state = arefstate;
+			ExprEvalPushStep(state, scratch);
+		}
+
+		/* ARRAYREF_OLD puts extracted value into prevvalue/prevnull */
+		save_innermost_caseval = state->innermost_caseval;
+		save_innermost_casenull = state->innermost_casenull;
+		state->innermost_caseval = &arefstate->prevvalue;
+		state->innermost_casenull = &arefstate->prevnull;
+
+		/* evaluate replacement value into replacevalue/replacenull */
+		ExecInitExprRec(aref->refassgnexpr, parent, state,
+						&arefstate->replacevalue, &arefstate->replacenull);
+
+		state->innermost_caseval = save_innermost_caseval;
+		state->innermost_casenull = save_innermost_casenull;
+
+		/* and perform the assignment */
+		scratch->opcode = EEOP_ARRAYREF_ASSIGN;
+		scratch->d.arrayref.state = arefstate;
+		ExprEvalPushStep(state, scratch);
+	}
+	else
+	{
+		/* array fetch is much simpler */
+		scratch->opcode = EEOP_ARRAYREF_FETCH;
+		scratch->d.arrayref.state = arefstate;
+		ExprEvalPushStep(state, scratch);
+	}
+
+	/* adjust jump targets */
+	foreach(lc, adjust_jumps)
+	{
+		ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+
+		if (as->opcode == EEOP_ARRAYREF_SUBSCRIPT)
+		{
+			Assert(as->d.arrayref_subscript.jumpdone == -1);
+			as->d.arrayref_subscript.jumpdone = state->steps_len;
+		}
+		else
+		{
+			Assert(as->opcode == EEOP_JUMP_IF_NULL);
+			Assert(as->d.jump.jumpdone == -1);
+			as->d.jump.jumpdone = state->steps_len;
+		}
+	}
+}
+
+/*
+ * Helper for preparing ArrayRef expressions for evaluation: is expr a nested
+ * FieldStore or ArrayRef that might need the old element value passed down?
+ *
+ * (We could use this in FieldStore too, but in that case passing the old
+ * value is so cheap there's no need.)
+ */
+static bool
+isAssignmentIndirectionExpr(Expr *expr)
+{
+	if (expr == NULL)
+		return false;			/* just paranoia */
+	if (IsA(expr, FieldStore))
+	{
+		FieldStore *fstore = (FieldStore *) expr;
+
+		if (fstore->arg && IsA(fstore->arg, CaseTestExpr))
+			return true;
+	}
+	else if (IsA(expr, ArrayRef))
+	{
+		ArrayRef   *arrayRef = (ArrayRef *) expr;
+
+		if (arrayRef->refexpr && IsA(arrayRef->refexpr, CaseTestExpr))
+			return true;
+	}
+	return false;
+}
+
+/*
+ * Prepare evaluation of a CoerceToDomain expression.
+ */
+static void
+ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
+					   PlanState *parent, ExprState *state,
+					   Datum *resv, bool *resnull)
+{
+	ExprEvalStep scratch2;
+	DomainConstraintRef *constraint_ref;
+	Datum	   *domainval = NULL;
+	bool	   *domainnull = NULL;
+	Datum	   *save_innermost_domainval;
+	bool	   *save_innermost_domainnull;
+	ListCell   *l;
+
+	scratch->d.domaincheck.resulttype = ctest->resulttype;
+	/* we'll allocate workspace only if needed */
+	scratch->d.domaincheck.checkvalue = NULL;
+	scratch->d.domaincheck.checknull = NULL;
+
+	/*
+	 * Evaluate argument - it's fine to directly store it into resv/resnull,
+	 * if there's constraint failures there'll be errors, otherwise it's what
+	 * needs to be returned.
+	 */
+	ExecInitExprRec(ctest->arg, parent, state, resv, resnull);
+
+	/*
+	 * Note: if the argument is of varlena type, it could be a R/W expanded
+	 * object.  We want to return the R/W pointer as the final result, but we
+	 * have to pass a R/O pointer as the value to be tested by any functions
+	 * in check expressions.  We don't bother to emit a MAKE_READONLY step
+	 * unless there's actually at least one check expression, though.  Until
+	 * we've tested that, domainval/domainnull are NULL.
+	 */
+
+	/*
+	 * Collect the constraints associated with the domain.
+	 *
+	 * Note: before PG v10 we'd recheck the set of constraints during each
+	 * evaluation of the expression.  Now we bake them into the ExprState
+	 * during executor initialization.  That means we don't need typcache.c to
+	 * provide compiled exprs.
+	 */
+	constraint_ref = (DomainConstraintRef *)
+		palloc(sizeof(DomainConstraintRef));
+	InitDomainConstraintRef(ctest->resulttype,
+							constraint_ref,
+							CurrentMemoryContext,
+							false);
+
+	/*
+	 * Compile code to check each domain constraint.  NOTNULL constraints can
+	 * just be applied on the resv/resnull value, but for CHECK constraints we
+	 * need more pushups.
+	 */
+	foreach(l, constraint_ref->constraints)
+	{
+		DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
+
+		scratch->d.domaincheck.constraintname = con->name;
+
+		switch (con->constrainttype)
+		{
+			case DOM_CONSTRAINT_NOTNULL:
+				scratch->opcode = EEOP_DOMAIN_NOTNULL;
+				ExprEvalPushStep(state, scratch);
+				break;
+			case DOM_CONSTRAINT_CHECK:
+				/* Allocate workspace for CHECK output if we didn't yet */
+				if (scratch->d.domaincheck.checkvalue == NULL)
+				{
+					scratch->d.domaincheck.checkvalue =
+						(Datum *) palloc(sizeof(Datum));
+					scratch->d.domaincheck.checknull =
+						(bool *) palloc(sizeof(bool));
+				}
+
+				/*
+				 * If first time through, determine where CoerceToDomainValue
+				 * nodes should read from.
+				 */
+				if (domainval == NULL)
+				{
+					/*
+					 * Since value might be read multiple times, force to R/O
+					 * - but only if it could be an expanded datum.
+					 */
+					if (get_typlen(ctest->resulttype) == -1)
+					{
+						/* Yes, so make output workspace for MAKE_READONLY */
+						domainval = (Datum *) palloc(sizeof(Datum));
+						domainnull = (bool *) palloc(sizeof(bool));
+
+						/* Emit MAKE_READONLY */
+						scratch2.opcode = EEOP_MAKE_READONLY;
+						scratch2.resvalue = domainval;
+						scratch2.resnull = domainnull;
+						scratch2.d.make_readonly.value = resv;
+						scratch2.d.make_readonly.isnull = resnull;
+						ExprEvalPushStep(state, &scratch2);
+					}
+					else
+					{
+						/* No, so it's fine to read from resv/resnull */
+						domainval = resv;
+						domainnull = resnull;
+					}
+				}
+
+				/*
+				 * Set up value to be returned by CoerceToDomainValue nodes.
+				 * We must save and restore innermost_domainval/null fields,
+				 * in case this node is itself within a check expression for
+				 * another domain.
+				 */
+				save_innermost_domainval = state->innermost_domainval;
+				save_innermost_domainnull = state->innermost_domainnull;
+				state->innermost_domainval = domainval;
+				state->innermost_domainnull = domainnull;
+
+				/* evaluate check expression value */
+				ExecInitExprRec(con->check_expr, parent, state,
+								scratch->d.domaincheck.checkvalue,
+								scratch->d.domaincheck.checknull);
+
+				state->innermost_domainval = save_innermost_domainval;
+				state->innermost_domainnull = save_innermost_domainnull;
+
+				/* now test result */
+				scratch->opcode = EEOP_DOMAIN_CHECK;
+				ExprEvalPushStep(state, scratch);
+
+				break;
+			default:
+				elog(ERROR, "unrecognized constraint type: %d",
+					 (int) con->constrainttype);
+				break;
+		}
+	}
+}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
new file mode 100644
index 0000000000000000000000000000000000000000..de7fe895f8153d3270041647fe84d6708f234022
--- /dev/null
+++ b/src/backend/executor/execExprInterp.c
@@ -0,0 +1,3525 @@
+/*-------------------------------------------------------------------------
+ *
+ * execExprInterp.c
+ *	  Interpreted evaluation of an expression step list.
+ *
+ * This file provides either a "direct threaded" (for gcc, clang and
+ * compatible) or a "switch threaded" (for all compilers) implementation of
+ * expression evaluation.  The former is amongst the fastest known methods
+ * of interpreting programs without resorting to assembly level work, or
+ * just-in-time compilation, but it requires support for computed gotos.
+ * The latter is amongst the fastest approaches doable in standard C.
+ *
+ * In either case we use ExprEvalStep->opcode to dispatch to the code block
+ * within ExecInterpExpr() that implements the specific opcode type.
+ *
+ * Switch-threading uses a plain switch() statement to perform the
+ * dispatch.  This has the advantages of being plain C and allowing the
+ * compiler to warn if implementation of a specific opcode has been forgotten.
+ * The disadvantage is that dispatches will, as commonly implemented by
+ * compilers, happen from a single location, requiring more jumps and causing
+ * bad branch prediction.
+ *
+ * In direct threading, we use gcc's label-as-values extension - also adopted
+ * by some other compilers - to replace ExprEvalStep->opcode with the address
+ * of the block implementing the instruction. Dispatch to the next instruction
+ * is done by a "computed goto".  This allows for better branch prediction
+ * (as the jumps are happening from different locations) and fewer jumps
+ * (as no preparatory jump to a common dispatch location is needed).
+ *
+ * When using direct threading, ExecReadyInterpretedExpr will replace
+ * each step's opcode field with the address of the relevant code block and
+ * ExprState->flags will contain EEO_FLAG_DIRECT_THREADED to remember that
+ * that's been done.
+ *
+ * For very simple instructions the overhead of the full interpreter
+ * "startup", as minimal as it is, is noticeable.  Therefore
+ * ExecReadyInterpretedExpr will choose to implement simple scalar Var
+ * and Const expressions using special fast-path routines (ExecJust*).
+ * Benchmarking shows anything more complex than those may as well use the
+ * "full interpreter".
+ *
+ * Complex or uncommon instructions are not implemented in-line in
+ * ExecInterpExpr(), rather we call out to a helper function appearing later
+ * in this file.  For one reason, there'd not be a noticeable performance
+ * benefit, but more importantly those complex routines are intended to be
+ * shared between different expression evaluation approaches.  For instance
+ * a JIT compiler would generate calls to them.  (This is why they are
+ * exported rather than being "static" in this file.)
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/executor/execExprInterp.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "catalog/pg_type.h"
+#include "executor/execExpr.h"
+#include "executor/nodeSubplan.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/parsetree.h"
+#include "pgstat.h"
+#include "utils/builtins.h"
+#include "utils/date.h"
+#include "utils/lsyscache.h"
+#include "utils/timestamp.h"
+#include "utils/typcache.h"
+#include "utils/xml.h"
+
+
+/*
+ * Use computed-goto-based opcode dispatch when computed gotos are available.
+ * But use a separate symbol so that it's easy to adjust locally in this file
+ * for development and testing.
+ */
+#ifdef HAVE_COMPUTED_GOTO
+#define EEO_USE_COMPUTED_GOTO
+#endif   /* HAVE_COMPUTED_GOTO */
+
+/*
+ * Macros for opcode dispatch.
+ *
+ * EEO_SWITCH - just hides the switch if not in use.
+ * EEO_CASE - labels the implementation of named expression step type.
+ * EEO_DISPATCH - jump to the implementation of the step type for 'op'.
+ * EEO_OPCODE - compute opcode required by used expression evaluation method.
+ * EEO_NEXT - increment 'op' and jump to correct next step type.
+ * EEO_JUMP - jump to the specified step number within the current expression.
+ */
+#if defined(EEO_USE_COMPUTED_GOTO)
+
+/* to make dispatch_table accessible outside ExecInterpExpr() */
+static const void **dispatch_table = NULL;
+
+#define EEO_SWITCH()
+#define EEO_CASE(name)		CASE_##name:
+#define EEO_DISPATCH()		goto *((void *) op->opcode)
+#define EEO_OPCODE(opcode)	((intptr_t) dispatch_table[opcode])
+
+#else							/* !EEO_USE_COMPUTED_GOTO */
+
+#define EEO_SWITCH()		starteval: switch ((ExprEvalOp) op->opcode)
+#define EEO_CASE(name)		case name:
+#define EEO_DISPATCH()		goto starteval
+#define EEO_OPCODE(opcode)	(opcode)
+
+#endif   /* EEO_USE_COMPUTED_GOTO */
+
+#define EEO_NEXT() \
+	do { \
+		op++; \
+		EEO_DISPATCH(); \
+	} while (0)
+
+#define EEO_JUMP(stepno) \
+	do { \
+		op = &state->steps[stepno]; \
+		EEO_DISPATCH(); \
+	} while (0)
+
+
+static Datum ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull);
+static void ExecInitInterpreter(void);
+
+/* support functions */
+static void CheckVarSlotCompatibility(TupleTableSlot *slot, int attnum, Oid vartype);
+static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod,
+				   TupleDesc *cache_field, ExprContext *econtext);
+static void ShutdownTupleDescRef(Datum arg);
+static void ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op,
+				   ExprContext *econtext, bool checkisnull);
+
+/* fast-path evaluation functions */
+static Datum ExecJustInnerVarFirst(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustInnerVar(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustOuterVarFirst(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustOuterVar(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustScanVarFirst(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustScanVar(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustConst(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustAssignInnerVar(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustAssignOuterVar(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustAssignScanVar(ExprState *state, ExprContext *econtext, bool *isnull);
+
+
+/*
+ * Prepare ExprState for interpreted execution.
+ */
+void
+ExecReadyInterpretedExpr(ExprState *state)
+{
+	/* Ensure one-time interpreter setup has been done */
+	ExecInitInterpreter();
+
+	/* Simple validity checks on expression */
+	Assert(state->steps_len >= 1);
+	Assert(state->steps[state->steps_len - 1].opcode == EEOP_DONE);
+
+	/*
+	 * Don't perform redundant initialization. This is unreachable in current
+	 * cases, but might be hit if there's additional expression evaluation
+	 * methods that rely on interpreted execution to work.
+	 */
+	if (state->flags & EEO_FLAG_INTERPRETER_INITIALIZED)
+		return;
+
+	/* DIRECT_THREADED should not already be set */
+	Assert((state->flags & EEO_FLAG_DIRECT_THREADED) == 0);
+
+	/*
+	 * There shouldn't be any errors before the expression is fully
+	 * initialized, and even if so, it'd lead to the expression being
+	 * abandoned.  So we can set the flag now and save some code.
+	 */
+	state->flags |= EEO_FLAG_INTERPRETER_INITIALIZED;
+
+	/*
+	 * Select fast-path evalfuncs for very simple expressions.  "Starting up"
+	 * the full interpreter is a measurable overhead for these.  Plain Vars
+	 * and Const seem to be the only ones where the intrinsic cost is small
+	 * enough that the overhead of ExecInterpExpr matters.  For more complex
+	 * expressions it's cheaper to use ExecInterpExpr always.
+	 */
+	if (state->steps_len == 3)
+	{
+		ExprEvalOp	step0 = state->steps[0].opcode;
+		ExprEvalOp	step1 = state->steps[1].opcode;
+
+		if (step0 == EEOP_INNER_FETCHSOME &&
+			step1 == EEOP_INNER_VAR_FIRST)
+		{
+			state->evalfunc = ExecJustInnerVarFirst;
+			return;
+		}
+		else if (step0 == EEOP_OUTER_FETCHSOME &&
+				 step1 == EEOP_OUTER_VAR_FIRST)
+		{
+			state->evalfunc = ExecJustOuterVarFirst;
+			return;
+		}
+		else if (step0 == EEOP_SCAN_FETCHSOME &&
+				 step1 == EEOP_SCAN_VAR_FIRST)
+		{
+			state->evalfunc = ExecJustScanVarFirst;
+			return;
+		}
+		else if (step0 == EEOP_INNER_FETCHSOME &&
+				 step1 == EEOP_ASSIGN_INNER_VAR)
+		{
+			state->evalfunc = ExecJustAssignInnerVar;
+			return;
+		}
+		else if (step0 == EEOP_OUTER_FETCHSOME &&
+				 step1 == EEOP_ASSIGN_OUTER_VAR)
+		{
+			state->evalfunc = ExecJustAssignOuterVar;
+			return;
+		}
+		else if (step0 == EEOP_SCAN_FETCHSOME &&
+				 step1 == EEOP_ASSIGN_SCAN_VAR)
+		{
+			state->evalfunc = ExecJustAssignScanVar;
+			return;
+		}
+	}
+	else if (state->steps_len == 2 &&
+			 state->steps[0].opcode == EEOP_CONST)
+	{
+		state->evalfunc = ExecJustConst;
+		return;
+	}
+
+#if defined(EEO_USE_COMPUTED_GOTO)
+
+	/*
+	 * In the direct-threaded implementation, replace each opcode with the
+	 * address to jump to.  (Use ExecEvalStepOp() to get back the opcode.)
+	 */
+	{
+		int			off;
+
+		for (off = 0; off < state->steps_len; off++)
+		{
+			ExprEvalStep *op = &state->steps[off];
+
+			op->opcode = EEO_OPCODE(op->opcode);
+		}
+
+		state->flags |= EEO_FLAG_DIRECT_THREADED;
+	}
+#endif   /* EEO_USE_COMPUTED_GOTO */
+
+	state->evalfunc = ExecInterpExpr;
+}
+
+
+/*
+ * Evaluate expression identified by "state" in the execution context
+ * given by "econtext".  *isnull is set to the is-null flag for the result,
+ * and the Datum value is the function result.
+ *
+ * As a special case, return the dispatch table's address if state is NULL.
+ * This is used by ExecInitInterpreter to set up the dispatch_table global.
+ * (Only applies when EEO_USE_COMPUTED_GOTO is defined.)
+ */
+static Datum
+ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+	ExprEvalStep *op;
+	TupleTableSlot *resultslot;
+	TupleTableSlot *innerslot;
+	TupleTableSlot *outerslot;
+	TupleTableSlot *scanslot;
+
+	/*
+	 * This array has to be in the same order as enum ExprEvalOp.
+	 */
+#if defined(EEO_USE_COMPUTED_GOTO)
+	static const void *const dispatch_table[] = {
+		&&CASE_EEOP_DONE,
+		&&CASE_EEOP_INNER_FETCHSOME,
+		&&CASE_EEOP_OUTER_FETCHSOME,
+		&&CASE_EEOP_SCAN_FETCHSOME,
+		&&CASE_EEOP_INNER_VAR_FIRST,
+		&&CASE_EEOP_INNER_VAR,
+		&&CASE_EEOP_OUTER_VAR_FIRST,
+		&&CASE_EEOP_OUTER_VAR,
+		&&CASE_EEOP_SCAN_VAR_FIRST,
+		&&CASE_EEOP_SCAN_VAR,
+		&&CASE_EEOP_INNER_SYSVAR,
+		&&CASE_EEOP_OUTER_SYSVAR,
+		&&CASE_EEOP_SCAN_SYSVAR,
+		&&CASE_EEOP_WHOLEROW,
+		&&CASE_EEOP_ASSIGN_INNER_VAR,
+		&&CASE_EEOP_ASSIGN_OUTER_VAR,
+		&&CASE_EEOP_ASSIGN_SCAN_VAR,
+		&&CASE_EEOP_ASSIGN_TMP,
+		&&CASE_EEOP_ASSIGN_TMP_MAKE_RO,
+		&&CASE_EEOP_CONST,
+		&&CASE_EEOP_FUNCEXPR,
+		&&CASE_EEOP_FUNCEXPR_STRICT,
+		&&CASE_EEOP_FUNCEXPR_FUSAGE,
+		&&CASE_EEOP_FUNCEXPR_STRICT_FUSAGE,
+		&&CASE_EEOP_BOOL_AND_STEP_FIRST,
+		&&CASE_EEOP_BOOL_AND_STEP,
+		&&CASE_EEOP_BOOL_AND_STEP_LAST,
+		&&CASE_EEOP_BOOL_OR_STEP_FIRST,
+		&&CASE_EEOP_BOOL_OR_STEP,
+		&&CASE_EEOP_BOOL_OR_STEP_LAST,
+		&&CASE_EEOP_BOOL_NOT_STEP,
+		&&CASE_EEOP_QUAL,
+		&&CASE_EEOP_JUMP,
+		&&CASE_EEOP_JUMP_IF_NULL,
+		&&CASE_EEOP_JUMP_IF_NOT_NULL,
+		&&CASE_EEOP_JUMP_IF_NOT_TRUE,
+		&&CASE_EEOP_NULLTEST_ISNULL,
+		&&CASE_EEOP_NULLTEST_ISNOTNULL,
+		&&CASE_EEOP_NULLTEST_ROWISNULL,
+		&&CASE_EEOP_NULLTEST_ROWISNOTNULL,
+		&&CASE_EEOP_BOOLTEST_IS_TRUE,
+		&&CASE_EEOP_BOOLTEST_IS_NOT_TRUE,
+		&&CASE_EEOP_BOOLTEST_IS_FALSE,
+		&&CASE_EEOP_BOOLTEST_IS_NOT_FALSE,
+		&&CASE_EEOP_PARAM_EXEC,
+		&&CASE_EEOP_PARAM_EXTERN,
+		&&CASE_EEOP_CASE_TESTVAL,
+		&&CASE_EEOP_MAKE_READONLY,
+		&&CASE_EEOP_IOCOERCE,
+		&&CASE_EEOP_DISTINCT,
+		&&CASE_EEOP_NULLIF,
+		&&CASE_EEOP_SQLVALUEFUNCTION,
+		&&CASE_EEOP_CURRENTOFEXPR,
+		&&CASE_EEOP_ARRAYEXPR,
+		&&CASE_EEOP_ARRAYCOERCE,
+		&&CASE_EEOP_ROW,
+		&&CASE_EEOP_ROWCOMPARE_STEP,
+		&&CASE_EEOP_ROWCOMPARE_FINAL,
+		&&CASE_EEOP_MINMAX,
+		&&CASE_EEOP_FIELDSELECT,
+		&&CASE_EEOP_FIELDSTORE_DEFORM,
+		&&CASE_EEOP_FIELDSTORE_FORM,
+		&&CASE_EEOP_ARRAYREF_SUBSCRIPT,
+		&&CASE_EEOP_ARRAYREF_OLD,
+		&&CASE_EEOP_ARRAYREF_ASSIGN,
+		&&CASE_EEOP_ARRAYREF_FETCH,
+		&&CASE_EEOP_DOMAIN_TESTVAL,
+		&&CASE_EEOP_DOMAIN_NOTNULL,
+		&&CASE_EEOP_DOMAIN_CHECK,
+		&&CASE_EEOP_CONVERT_ROWTYPE,
+		&&CASE_EEOP_SCALARARRAYOP,
+		&&CASE_EEOP_XMLEXPR,
+		&&CASE_EEOP_AGGREF,
+		&&CASE_EEOP_GROUPING_FUNC,
+		&&CASE_EEOP_WINDOW_FUNC,
+		&&CASE_EEOP_SUBPLAN,
+		&&CASE_EEOP_ALTERNATIVE_SUBPLAN,
+		&&CASE_EEOP_LAST
+	};
+
+	StaticAssertStmt(EEOP_LAST + 1 == lengthof(dispatch_table),
+					 "dispatch_table out of whack with ExprEvalOp");
+
+	if (unlikely(state == NULL))
+		return PointerGetDatum(dispatch_table);
+#else
+	Assert(state != NULL);
+#endif   /* EEO_USE_COMPUTED_GOTO */
+
+	/* setup state */
+	op = state->steps;
+	resultslot = state->resultslot;
+	innerslot = econtext->ecxt_innertuple;
+	outerslot = econtext->ecxt_outertuple;
+	scanslot = econtext->ecxt_scantuple;
+
+#if defined(EEO_USE_COMPUTED_GOTO)
+	EEO_DISPATCH();
+#endif
+
+	EEO_SWITCH()
+	{
+		EEO_CASE(EEOP_DONE)
+		{
+			goto out;
+		}
+
+		EEO_CASE(EEOP_INNER_FETCHSOME)
+		{
+			/* XXX: worthwhile to check tts_nvalid inline first? */
+			slot_getsomeattrs(innerslot, op->d.fetch.last_var);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_OUTER_FETCHSOME)
+		{
+			slot_getsomeattrs(outerslot, op->d.fetch.last_var);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_SCAN_FETCHSOME)
+		{
+			slot_getsomeattrs(scanslot, op->d.fetch.last_var);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_INNER_VAR_FIRST)
+		{
+			int			attnum = op->d.var.attnum;
+
+			/*
+			 * First time through, check whether attribute matches Var.  Might
+			 * not be ok anymore, due to schema changes.
+			 */
+			CheckVarSlotCompatibility(innerslot, attnum + 1, op->d.var.vartype);
+
+			/* Skip that check on subsequent evaluations */
+			op->opcode = EEO_OPCODE(EEOP_INNER_VAR);
+
+			/* FALL THROUGH to EEOP_INNER_VAR */
+		}
+
+		EEO_CASE(EEOP_INNER_VAR)
+		{
+			int			attnum = op->d.var.attnum;
+
+			/*
+			 * Since we already extracted all referenced columns from the
+			 * tuple with a FETCHSOME step, we can just grab the value
+			 * directly out of the slot's decomposed-data arrays.  But let's
+			 * have an Assert to check that that did happen.
+			 */
+			Assert(attnum >= 0 && attnum < innerslot->tts_nvalid);
+			*op->resvalue = innerslot->tts_values[attnum];
+			*op->resnull = innerslot->tts_isnull[attnum];
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_OUTER_VAR_FIRST)
+		{
+			int			attnum = op->d.var.attnum;
+
+			/* See EEOP_INNER_VAR_FIRST comments */
+
+			CheckVarSlotCompatibility(outerslot, attnum + 1, op->d.var.vartype);
+			op->opcode = EEO_OPCODE(EEOP_OUTER_VAR);
+
+			/* FALL THROUGH to EEOP_OUTER_VAR */
+		}
+
+		EEO_CASE(EEOP_OUTER_VAR)
+		{
+			int			attnum = op->d.var.attnum;
+
+			/* See EEOP_INNER_VAR comments */
+
+			Assert(attnum >= 0 && attnum < outerslot->tts_nvalid);
+			*op->resvalue = outerslot->tts_values[attnum];
+			*op->resnull = outerslot->tts_isnull[attnum];
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_SCAN_VAR_FIRST)
+		{
+			int			attnum = op->d.var.attnum;
+
+			/* See EEOP_INNER_VAR_FIRST comments */
+
+			CheckVarSlotCompatibility(scanslot, attnum + 1, op->d.var.vartype);
+			op->opcode = EEO_OPCODE(EEOP_SCAN_VAR);
+
+			/* FALL THROUGH to EEOP_SCAN_VAR */
+		}
+
+		EEO_CASE(EEOP_SCAN_VAR)
+		{
+			int			attnum = op->d.var.attnum;
+
+			/* See EEOP_INNER_VAR comments */
+
+			Assert(attnum >= 0 && attnum < scanslot->tts_nvalid);
+			*op->resvalue = scanslot->tts_values[attnum];
+			*op->resnull = scanslot->tts_isnull[attnum];
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_INNER_SYSVAR)
+		{
+			int			attnum = op->d.var.attnum;
+
+			/* these asserts must match defenses in slot_getattr */
+			Assert(innerslot->tts_tuple != NULL);
+			Assert(innerslot->tts_tuple != &(innerslot->tts_minhdr));
+			/* heap_getsysattr has sufficient defenses against bad attnums */
+
+			*op->resvalue = heap_getsysattr(innerslot->tts_tuple, attnum,
+											innerslot->tts_tupleDescriptor,
+											op->resnull);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_OUTER_SYSVAR)
+		{
+			int			attnum = op->d.var.attnum;
+
+			/* these asserts must match defenses in slot_getattr */
+			Assert(outerslot->tts_tuple != NULL);
+			Assert(outerslot->tts_tuple != &(outerslot->tts_minhdr));
+
+			/* heap_getsysattr has sufficient defenses against bad attnums */
+			*op->resvalue = heap_getsysattr(outerslot->tts_tuple, attnum,
+											outerslot->tts_tupleDescriptor,
+											op->resnull);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_SCAN_SYSVAR)
+		{
+			int			attnum = op->d.var.attnum;
+
+			/* these asserts must match defenses in slot_getattr */
+			Assert(scanslot->tts_tuple != NULL);
+			Assert(scanslot->tts_tuple != &(scanslot->tts_minhdr));
+			/* heap_getsysattr has sufficient defenses against bad attnums */
+
+			*op->resvalue = heap_getsysattr(scanslot->tts_tuple, attnum,
+											scanslot->tts_tupleDescriptor,
+											op->resnull);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_WHOLEROW)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalWholeRowVar(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_ASSIGN_INNER_VAR)
+		{
+			int			resultnum = op->d.assign_var.resultnum;
+			int			attnum = op->d.assign_var.attnum;
+
+			/*
+			 * We do not need CheckVarSlotCompatibility here; that was taken
+			 * care of at compilation time.  But see EEOP_INNER_VAR comments.
+			 */
+			Assert(attnum >= 0 && attnum < innerslot->tts_nvalid);
+			resultslot->tts_values[resultnum] = innerslot->tts_values[attnum];
+			resultslot->tts_isnull[resultnum] = innerslot->tts_isnull[attnum];
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_ASSIGN_OUTER_VAR)
+		{
+			int			resultnum = op->d.assign_var.resultnum;
+			int			attnum = op->d.assign_var.attnum;
+
+			/*
+			 * We do not need CheckVarSlotCompatibility here; that was taken
+			 * care of at compilation time.  But see EEOP_INNER_VAR comments.
+			 */
+			Assert(attnum >= 0 && attnum < outerslot->tts_nvalid);
+			resultslot->tts_values[resultnum] = outerslot->tts_values[attnum];
+			resultslot->tts_isnull[resultnum] = outerslot->tts_isnull[attnum];
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_ASSIGN_SCAN_VAR)
+		{
+			int			resultnum = op->d.assign_var.resultnum;
+			int			attnum = op->d.assign_var.attnum;
+
+			/*
+			 * We do not need CheckVarSlotCompatibility here; that was taken
+			 * care of at compilation time.  But see EEOP_INNER_VAR comments.
+			 */
+			Assert(attnum >= 0 && attnum < scanslot->tts_nvalid);
+			resultslot->tts_values[resultnum] = scanslot->tts_values[attnum];
+			resultslot->tts_isnull[resultnum] = scanslot->tts_isnull[attnum];
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_ASSIGN_TMP)
+		{
+			int			resultnum = op->d.assign_tmp.resultnum;
+
+			resultslot->tts_values[resultnum] = state->resvalue;
+			resultslot->tts_isnull[resultnum] = state->resnull;
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_ASSIGN_TMP_MAKE_RO)
+		{
+			int			resultnum = op->d.assign_tmp.resultnum;
+
+			resultslot->tts_isnull[resultnum] = state->resnull;
+			if (!resultslot->tts_isnull[resultnum])
+				resultslot->tts_values[resultnum] =
+					MakeExpandedObjectReadOnlyInternal(state->resvalue);
+			else
+				resultslot->tts_values[resultnum] = state->resvalue;
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CONST)
+		{
+			*op->resnull = op->d.constval.isnull;
+			*op->resvalue = op->d.constval.value;
+
+			EEO_NEXT();
+		}
+
+		/*
+		 * Function-call implementations. Arguments have previously been
+		 * evaluated directly into fcinfo->args.
+		 *
+		 * As both STRICT checks and function-usage are noticeable performance
+		 * wise, and function calls are a very hot-path (they also back
+		 * operators!), it's worth having so many separate opcodes.
+		 */
+		EEO_CASE(EEOP_FUNCEXPR)
+		{
+			FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+
+			fcinfo->isnull = false;
+			*op->resvalue = (op->d.func.fn_addr) (fcinfo);
+			*op->resnull = fcinfo->isnull;
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_FUNCEXPR_STRICT)
+		{
+			FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+			bool	   *argnull = fcinfo->argnull;
+			int			argno;
+
+			/* strict function, so check for NULL args */
+			for (argno = 0; argno < op->d.func.nargs; argno++)
+			{
+				if (argnull[argno])
+				{
+					*op->resnull = true;
+					goto strictfail;
+				}
+			}
+			fcinfo->isnull = false;
+			*op->resvalue = (op->d.func.fn_addr) (fcinfo);
+			*op->resnull = fcinfo->isnull;
+
+	strictfail:
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_FUNCEXPR_FUSAGE)
+		{
+			FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+			PgStat_FunctionCallUsage fcusage;
+
+			pgstat_init_function_usage(fcinfo, &fcusage);
+
+			fcinfo->isnull = false;
+			*op->resvalue = (op->d.func.fn_addr) (fcinfo);
+			*op->resnull = fcinfo->isnull;
+
+			pgstat_end_function_usage(&fcusage, true);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_FUNCEXPR_STRICT_FUSAGE)
+		{
+			FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+			PgStat_FunctionCallUsage fcusage;
+			bool	   *argnull = fcinfo->argnull;
+			int			argno;
+
+			/* strict function, so check for NULL args */
+			for (argno = 0; argno < op->d.func.nargs; argno++)
+			{
+				if (argnull[argno])
+				{
+					*op->resnull = true;
+					goto strictfail_fusage;
+				}
+			}
+
+			pgstat_init_function_usage(fcinfo, &fcusage);
+
+			fcinfo->isnull = false;
+			*op->resvalue = (op->d.func.fn_addr) (fcinfo);
+			*op->resnull = fcinfo->isnull;
+
+			pgstat_end_function_usage(&fcusage, true);
+
+	strictfail_fusage:
+			EEO_NEXT();
+		}
+
+		/*
+		 * If any of its clauses is FALSE, an AND's result is FALSE regardless
+		 * of the states of the rest of the clauses, so we can stop evaluating
+		 * and return FALSE immediately.  If none are FALSE and one or more is
+		 * NULL, we return NULL; otherwise we return TRUE.  This makes sense
+		 * when you interpret NULL as "don't know": perhaps one of the "don't
+		 * knows" would have been FALSE if we'd known its value.  Only when
+		 * all the inputs are known to be TRUE can we state confidently that
+		 * the AND's result is TRUE.
+		 */
+		EEO_CASE(EEOP_BOOL_AND_STEP_FIRST)
+		{
+			*op->d.boolexpr.anynull = false;
+
+			/*
+			 * EEOP_BOOL_AND_STEP_FIRST resets anynull, otherwise it's the
+			 * same as EEOP_BOOL_AND_STEP - so fall through to that.
+			 */
+
+			/* FALL THROUGH */
+		}
+
+		EEO_CASE(EEOP_BOOL_AND_STEP)
+		{
+			if (*op->resnull)
+			{
+				*op->d.boolexpr.anynull = true;
+			}
+			else if (!DatumGetBool(*op->resvalue))
+			{
+				/* result is already set to FALSE, need not change it */
+				/* bail out early */
+				EEO_JUMP(op->d.boolexpr.jumpdone);
+			}
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_BOOL_AND_STEP_LAST)
+		{
+			if (*op->resnull)
+			{
+				/* result is already set to NULL, need not change it */
+			}
+			else if (!DatumGetBool(*op->resvalue))
+			{
+				/* result is already set to FALSE, need not change it */
+
+				/*
+				 * No point jumping early to jumpdone - would be same target
+				 * (as this is the last argument to the AND expression),
+				 * except more expensive.
+				 */
+			}
+			else if (*op->d.boolexpr.anynull)
+			{
+				*op->resvalue = (Datum) 0;
+				*op->resnull = true;
+			}
+			else
+			{
+				/* result is already set to TRUE, need not change it */
+			}
+
+			EEO_NEXT();
+		}
+
+		/*
+		 * If any of its clauses is TRUE, an OR's result is TRUE regardless of
+		 * the states of the rest of the clauses, so we can stop evaluating
+		 * and return TRUE immediately.  If none are TRUE and one or more is
+		 * NULL, we return NULL; otherwise we return FALSE.  This makes sense
+		 * when you interpret NULL as "don't know": perhaps one of the "don't
+		 * knows" would have been TRUE if we'd known its value.  Only when all
+		 * the inputs are known to be FALSE can we state confidently that the
+		 * OR's result is FALSE.
+		 */
+		EEO_CASE(EEOP_BOOL_OR_STEP_FIRST)
+		{
+			*op->d.boolexpr.anynull = false;
+
+			/*
+			 * EEOP_BOOL_OR_STEP_FIRST resets anynull, otherwise it's the same
+			 * as EEOP_BOOL_OR_STEP - so fall through to that.
+			 */
+
+			/* FALL THROUGH */
+		}
+
+		EEO_CASE(EEOP_BOOL_OR_STEP)
+		{
+			if (*op->resnull)
+			{
+				*op->d.boolexpr.anynull = true;
+			}
+			else if (DatumGetBool(*op->resvalue))
+			{
+				/* result is already set to TRUE, need not change it */
+				/* bail out early */
+				EEO_JUMP(op->d.boolexpr.jumpdone);
+			}
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_BOOL_OR_STEP_LAST)
+		{
+			if (*op->resnull)
+			{
+				/* result is already set to NULL, need not change it */
+			}
+			else if (DatumGetBool(*op->resvalue))
+			{
+				/* result is already set to TRUE, need not change it */
+
+				/*
+				 * No point jumping to jumpdone - would be same target (as
+				 * this is the last argument to the AND expression), except
+				 * more expensive.
+				 */
+			}
+			else if (*op->d.boolexpr.anynull)
+			{
+				*op->resvalue = (Datum) 0;
+				*op->resnull = true;
+			}
+			else
+			{
+				/* result is already set to FALSE, need not change it */
+			}
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_BOOL_NOT_STEP)
+		{
+			/*
+			 * Evaluation of 'not' is simple... if expr is false, then return
+			 * 'true' and vice versa.  It's safe to do this even on a
+			 * nominally null value, so we ignore resnull; that means that
+			 * NULL in produces NULL out, which is what we want.
+			 */
+			*op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue));
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_QUAL)
+		{
+			/* simplified version of BOOL_AND_STEP for use by ExecQual() */
+
+			/* If argument (also result) is false or null ... */
+			if (*op->resnull ||
+				!DatumGetBool(*op->resvalue))
+			{
+				/* ... bail out early, returning FALSE */
+				*op->resnull = false;
+				*op->resvalue = BoolGetDatum(false);
+				EEO_JUMP(op->d.qualexpr.jumpdone);
+			}
+
+			/*
+			 * Otherwise, leave the TRUE value in place, in case this is the
+			 * last qual.  Then, TRUE is the correct answer.
+			 */
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_JUMP)
+		{
+			/* Unconditionally jump to target step */
+			EEO_JUMP(op->d.jump.jumpdone);
+		}
+
+		EEO_CASE(EEOP_JUMP_IF_NULL)
+		{
+			/* Transfer control if current result is null */
+			if (*op->resnull)
+				EEO_JUMP(op->d.jump.jumpdone);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_JUMP_IF_NOT_NULL)
+		{
+			/* Transfer control if current result is non-null */
+			if (!*op->resnull)
+				EEO_JUMP(op->d.jump.jumpdone);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_JUMP_IF_NOT_TRUE)
+		{
+			/* Transfer control if current result is null or false */
+			if (*op->resnull || !DatumGetBool(*op->resvalue))
+				EEO_JUMP(op->d.jump.jumpdone);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_NULLTEST_ISNULL)
+		{
+			*op->resvalue = BoolGetDatum(*op->resnull);
+			*op->resnull = false;
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_NULLTEST_ISNOTNULL)
+		{
+			*op->resvalue = BoolGetDatum(!*op->resnull);
+			*op->resnull = false;
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_NULLTEST_ROWISNULL)
+		{
+			/* out of line implementation: too large */
+			ExecEvalRowNull(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_NULLTEST_ROWISNOTNULL)
+		{
+			/* out of line implementation: too large */
+			ExecEvalRowNotNull(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
+		/* BooleanTest implementations for all booltesttypes */
+
+		EEO_CASE(EEOP_BOOLTEST_IS_TRUE)
+		{
+			if (*op->resnull)
+				*op->resvalue = BoolGetDatum(false);
+			else
+				*op->resvalue = *op->resvalue;
+			*op->resnull = false;
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_BOOLTEST_IS_NOT_TRUE)
+		{
+			if (*op->resnull)
+				*op->resvalue = BoolGetDatum(true);
+			else
+				*op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue));
+			*op->resnull = false;
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_BOOLTEST_IS_FALSE)
+		{
+			if (*op->resnull)
+				*op->resvalue = BoolGetDatum(false);
+			else
+				*op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue));
+			*op->resnull = false;
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_BOOLTEST_IS_NOT_FALSE)
+		{
+			if (*op->resnull)
+				*op->resvalue = BoolGetDatum(true);
+			else
+				*op->resvalue = *op->resvalue;
+			*op->resnull = false;
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_PARAM_EXEC)
+		{
+			/* out of line implementation: too large */
+			ExecEvalParamExec(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_PARAM_EXTERN)
+		{
+			/* out of line implementation: too large */
+			ExecEvalParamExtern(state, op, econtext);
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CASE_TESTVAL)
+		{
+			/*
+			 * Normally upper parts of the expression tree have setup the
+			 * values to be returned here, but some parts of the system
+			 * currently misuse {caseValue,domainValue}_{datum,isNull} to set
+			 * run-time data.  So if no values have been set-up, use
+			 * ExprContext's.  This isn't pretty, but also not *that* ugly,
+			 * and this is unlikely to be performance sensitive enough to
+			 * worry about an extra branch.
+			 */
+			if (op->d.casetest.value)
+			{
+				*op->resvalue = *op->d.casetest.value;
+				*op->resnull = *op->d.casetest.isnull;
+			}
+			else
+			{
+				*op->resvalue = econtext->caseValue_datum;
+				*op->resnull = econtext->caseValue_isNull;
+			}
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_DOMAIN_TESTVAL)
+		{
+			/*
+			 * See EEOP_CASE_TESTVAL comment.
+			 */
+			if (op->d.casetest.value)
+			{
+				*op->resvalue = *op->d.casetest.value;
+				*op->resnull = *op->d.casetest.isnull;
+			}
+			else
+			{
+				*op->resvalue = econtext->domainValue_datum;
+				*op->resnull = econtext->domainValue_isNull;
+			}
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_MAKE_READONLY)
+		{
+			/*
+			 * Force a varlena value that might be read multiple times to R/O
+			 */
+			if (!*op->d.make_readonly.isnull)
+				*op->resvalue =
+					MakeExpandedObjectReadOnlyInternal(*op->d.make_readonly.value);
+			*op->resnull = *op->d.make_readonly.isnull;
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_IOCOERCE)
+		{
+			/*
+			 * Evaluate a CoerceViaIO node.  This can be quite a hot path, so
+			 * inline as much work as possible.  The source value is in our
+			 * result variable.
+			 */
+			char	   *str;
+
+			/* call output function (similar to OutputFunctionCall) */
+			if (*op->resnull)
+			{
+				/* output functions are not called on nulls */
+				str = NULL;
+			}
+			else
+			{
+				FunctionCallInfo fcinfo_out;
+
+				fcinfo_out = op->d.iocoerce.fcinfo_data_out;
+				fcinfo_out->arg[0] = *op->resvalue;
+				fcinfo_out->argnull[0] = false;
+
+				fcinfo_out->isnull = false;
+				str = DatumGetCString(FunctionCallInvoke(fcinfo_out));
+
+				/* OutputFunctionCall assumes result isn't null */
+				Assert(!fcinfo_out->isnull);
+			}
+
+			/* call input function (similar to InputFunctionCall) */
+			if (!op->d.iocoerce.finfo_in->fn_strict || str != NULL)
+			{
+				FunctionCallInfo fcinfo_in;
+
+				fcinfo_in = op->d.iocoerce.fcinfo_data_in;
+				fcinfo_in->arg[0] = PointerGetDatum(str);
+				fcinfo_in->argnull[0] = *op->resnull;
+				/* second and third arguments are already set up */
+
+				fcinfo_in->isnull = false;
+				*op->resvalue = FunctionCallInvoke(fcinfo_in);
+
+				/* Should get null result if and only if str is NULL */
+				if (str == NULL)
+				{
+					Assert(*op->resnull);
+					Assert(fcinfo_in->isnull);
+				}
+				else
+				{
+					Assert(!*op->resnull);
+					Assert(!fcinfo_in->isnull);
+				}
+			}
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_DISTINCT)
+		{
+			/*
+			 * IS DISTINCT FROM must evaluate arguments (already done into
+			 * fcinfo->arg/argnull) to determine whether they are NULL; if
+			 * either is NULL then the result is determined.  If neither is
+			 * NULL, then proceed to evaluate the comparison function, which
+			 * is just the type's standard equality operator.  We need not
+			 * care whether that function is strict.  Because the handling of
+			 * nulls is different, we can't just reuse EEOP_FUNCEXPR.
+			 */
+			FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+
+			/* check function arguments for NULLness */
+			if (fcinfo->argnull[0] && fcinfo->argnull[1])
+			{
+				/* Both NULL? Then is not distinct... */
+				*op->resvalue = BoolGetDatum(false);
+				*op->resnull = false;
+			}
+			else if (fcinfo->argnull[0] || fcinfo->argnull[1])
+			{
+				/* Only one is NULL? Then is distinct... */
+				*op->resvalue = BoolGetDatum(true);
+				*op->resnull = false;
+			}
+			else
+			{
+				/* Neither null, so apply the equality function */
+				Datum		eqresult;
+
+				fcinfo->isnull = false;
+				eqresult = (op->d.func.fn_addr) (fcinfo);
+				/* Must invert result of "="; safe to do even if null */
+				*op->resvalue = BoolGetDatum(!DatumGetBool(eqresult));
+				*op->resnull = fcinfo->isnull;
+			}
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_NULLIF)
+		{
+			/*
+			 * The arguments are already evaluated into fcinfo->arg/argnull.
+			 */
+			FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+
+			/* if either argument is NULL they can't be equal */
+			if (!fcinfo->argnull[0] && !fcinfo->argnull[1])
+			{
+				Datum		result;
+
+				fcinfo->isnull = false;
+				result = (op->d.func.fn_addr) (fcinfo);
+
+				/* if the arguments are equal return null */
+				if (!fcinfo->isnull && DatumGetBool(result))
+				{
+					*op->resvalue = (Datum) 0;
+					*op->resnull = true;
+
+					EEO_NEXT();
+				}
+			}
+
+			/* Arguments aren't equal, so return the first one */
+			*op->resvalue = fcinfo->arg[0];
+			*op->resnull = fcinfo->argnull[0];
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_SQLVALUEFUNCTION)
+		{
+			/*
+			 * Doesn't seem worthwhile to have an inline implementation
+			 * efficiency-wise.
+			 */
+			ExecEvalSQLValueFunction(state, op);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CURRENTOFEXPR)
+		{
+			/* error invocation uses space, and shouldn't ever occur */
+			ExecEvalCurrentOfExpr(state, op);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_ARRAYEXPR)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalArrayExpr(state, op);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_ARRAYCOERCE)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalArrayCoerce(state, op);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_ROW)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalRow(state, op);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_ROWCOMPARE_STEP)
+		{
+			FunctionCallInfo fcinfo = op->d.rowcompare_step.fcinfo_data;
+
+			/* force NULL result if strict fn and NULL input */
+			if (op->d.rowcompare_step.finfo->fn_strict &&
+				(fcinfo->argnull[0] || fcinfo->argnull[1]))
+			{
+				*op->resnull = true;
+				EEO_JUMP(op->d.rowcompare_step.jumpnull);
+			}
+
+			/* Apply comparison function */
+			fcinfo->isnull = false;
+			*op->resvalue = (op->d.rowcompare_step.fn_addr) (fcinfo);
+
+			/* force NULL result if NULL function result */
+			if (fcinfo->isnull)
+			{
+				*op->resnull = true;
+				EEO_JUMP(op->d.rowcompare_step.jumpnull);
+			}
+			*op->resnull = false;
+
+			/* If unequal, no need to compare remaining columns */
+			if (DatumGetInt32(*op->resvalue) != 0)
+			{
+				EEO_JUMP(op->d.rowcompare_step.jumpdone);
+			}
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_ROWCOMPARE_FINAL)
+		{
+			int32		cmpresult = DatumGetInt32(*op->resvalue);
+			RowCompareType rctype = op->d.rowcompare_final.rctype;
+
+			*op->resnull = false;
+			switch (rctype)
+			{
+					/* EQ and NE cases aren't allowed here */
+				case ROWCOMPARE_LT:
+					*op->resvalue = BoolGetDatum(cmpresult < 0);
+					break;
+				case ROWCOMPARE_LE:
+					*op->resvalue = BoolGetDatum(cmpresult <= 0);
+					break;
+				case ROWCOMPARE_GE:
+					*op->resvalue = BoolGetDatum(cmpresult >= 0);
+					break;
+				case ROWCOMPARE_GT:
+					*op->resvalue = BoolGetDatum(cmpresult > 0);
+					break;
+				default:
+					Assert(false);
+					break;
+			}
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_MINMAX)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalMinMax(state, op);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_FIELDSELECT)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalFieldSelect(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_FIELDSTORE_DEFORM)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalFieldStoreDeForm(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_FIELDSTORE_FORM)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalFieldStoreForm(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_ARRAYREF_SUBSCRIPT)
+		{
+			/* Process an array subscript */
+
+			/* too complex for an inline implementation */
+			if (ExecEvalArrayRefSubscript(state, op))
+			{
+				EEO_NEXT();
+			}
+			else
+			{
+				/* Subscript is null, short-circuit ArrayRef to NULL */
+				EEO_JUMP(op->d.arrayref_subscript.jumpdone);
+			}
+		}
+
+		EEO_CASE(EEOP_ARRAYREF_OLD)
+		{
+			/*
+			 * Fetch the old value in an arrayref assignment, in case it's
+			 * referenced (via a CaseTestExpr) inside the assignment
+			 * expression.
+			 */
+
+			/* too complex for an inline implementation */
+			ExecEvalArrayRefOld(state, op);
+
+			EEO_NEXT();
+		}
+
+		/*
+		 * Perform ArrayRef assignment
+		 */
+		EEO_CASE(EEOP_ARRAYREF_ASSIGN)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalArrayRefAssign(state, op);
+
+			EEO_NEXT();
+		}
+
+		/*
+		 * Fetch subset of an array.
+		 */
+		EEO_CASE(EEOP_ARRAYREF_FETCH)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalArrayRefFetch(state, op);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_CONVERT_ROWTYPE)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalConvertRowtype(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_SCALARARRAYOP)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalScalarArrayOp(state, op);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_DOMAIN_NOTNULL)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalConstraintNotNull(state, op);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_DOMAIN_CHECK)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalConstraintCheck(state, op);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_XMLEXPR)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalXmlExpr(state, op);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_AGGREF)
+		{
+			/*
+			 * Returns a Datum whose value is the precomputed aggregate value
+			 * found in the given expression context.
+			 */
+			AggrefExprState *aggref = op->d.aggref.astate;
+
+			Assert(econtext->ecxt_aggvalues != NULL);
+
+			*op->resvalue = econtext->ecxt_aggvalues[aggref->aggno];
+			*op->resnull = econtext->ecxt_aggnulls[aggref->aggno];
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_GROUPING_FUNC)
+		{
+			/* too complex/uncommon for an inline implementation */
+			ExecEvalGroupingFunc(state, op);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_WINDOW_FUNC)
+		{
+			/*
+			 * Like Aggref, just return a precomputed value from the econtext.
+			 */
+			WindowFuncExprState *wfunc = op->d.window_func.wfstate;
+
+			Assert(econtext->ecxt_aggvalues != NULL);
+
+			*op->resvalue = econtext->ecxt_aggvalues[wfunc->wfuncno];
+			*op->resnull = econtext->ecxt_aggnulls[wfunc->wfuncno];
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_SUBPLAN)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalSubPlan(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_ALTERNATIVE_SUBPLAN)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalAlternativeSubPlan(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
+		EEO_CASE(EEOP_LAST)
+		{
+			/* unreachable */
+			Assert(false);
+			goto out;
+		}
+	}
+
+out:
+	*isnull = state->resnull;
+	return state->resvalue;
+}
+
+/*
+ * Check whether a user attribute in a slot can be referenced by a Var
+ * expression.  This should succeed unless there have been schema changes
+ * since the expression tree has been created.
+ */
+static void
+CheckVarSlotCompatibility(TupleTableSlot *slot, int attnum, Oid vartype)
+{
+	/*
+	 * What we have to check for here is the possibility of an attribute
+	 * having been changed in type since the plan tree was created.  Ideally
+	 * the plan will get invalidated and not re-used, but just in case, we
+	 * keep these defenses.  Fortunately it's sufficient to check once on the
+	 * first time through.
+	 *
+	 * System attributes don't require checking since their types never
+	 * change.
+	 *
+	 * Note: we allow a reference to a dropped attribute.  slot_getattr will
+	 * force a NULL result in such cases.
+	 *
+	 * Note: ideally we'd check typmod as well as typid, but that seems
+	 * impractical at the moment: in many cases the tupdesc will have been
+	 * generated by ExecTypeFromTL(), and that can't guarantee to generate an
+	 * accurate typmod in all cases, because some expression node types don't
+	 * carry typmod.
+	 */
+	if (attnum > 0)
+	{
+		TupleDesc	slot_tupdesc = slot->tts_tupleDescriptor;
+		Form_pg_attribute attr;
+
+		if (attnum > slot_tupdesc->natts)		/* should never happen */
+			elog(ERROR, "attribute number %d exceeds number of columns %d",
+				 attnum, slot_tupdesc->natts);
+
+		attr = slot_tupdesc->attrs[attnum - 1];
+
+		/* can't check type if dropped, since atttypid is probably 0 */
+		if (!attr->attisdropped)
+		{
+			if (vartype != attr->atttypid)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("attribute %d has wrong type", attnum),
+						 errdetail("Table has type %s, but query expects %s.",
+								   format_type_be(attr->atttypid),
+								   format_type_be(vartype))));
+		}
+	}
+}
+
+/*
+ * get_cached_rowtype: utility function to lookup a rowtype tupdesc
+ *
+ * type_id, typmod: identity of the rowtype
+ * cache_field: where to cache the TupleDesc pointer in expression state node
+ *		(field must be initialized to NULL)
+ * econtext: expression context we are executing in
+ *
+ * NOTE: because the shutdown callback will be called during plan rescan,
+ * must be prepared to re-do this during any node execution; cannot call
+ * just once during expression initialization.
+ */
+static TupleDesc
+get_cached_rowtype(Oid type_id, int32 typmod,
+				   TupleDesc *cache_field, ExprContext *econtext)
+{
+	TupleDesc	tupDesc = *cache_field;
+
+	/* Do lookup if no cached value or if requested type changed */
+	if (tupDesc == NULL ||
+		type_id != tupDesc->tdtypeid ||
+		typmod != tupDesc->tdtypmod)
+	{
+		tupDesc = lookup_rowtype_tupdesc(type_id, typmod);
+
+		if (*cache_field)
+		{
+			/* Release old tupdesc; but callback is already registered */
+			ReleaseTupleDesc(*cache_field);
+		}
+		else
+		{
+			/* Need to register shutdown callback to release tupdesc */
+			RegisterExprContextCallback(econtext,
+										ShutdownTupleDescRef,
+										PointerGetDatum(cache_field));
+		}
+		*cache_field = tupDesc;
+	}
+	return tupDesc;
+}
+
+/*
+ * Callback function to release a tupdesc refcount at econtext shutdown
+ */
+static void
+ShutdownTupleDescRef(Datum arg)
+{
+	TupleDesc  *cache_field = (TupleDesc *) DatumGetPointer(arg);
+
+	if (*cache_field)
+		ReleaseTupleDesc(*cache_field);
+	*cache_field = NULL;
+}
+
+/*
+ * Fast-path functions, for very simple expressions
+ */
+
+/* Simple reference to inner Var, first time through */
+static Datum
+ExecJustInnerVarFirst(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+	ExprEvalStep *op = &state->steps[1];
+	int			attnum = op->d.var.attnum + 1;
+	TupleTableSlot *slot = econtext->ecxt_innertuple;
+
+	/* See ExecInterpExpr()'s comments for EEOP_INNER_VAR_FIRST */
+
+	CheckVarSlotCompatibility(slot, attnum, op->d.var.vartype);
+	op->opcode = EEOP_INNER_VAR;	/* just for cleanliness */
+	state->evalfunc = ExecJustInnerVar;
+
+	/*
+	 * Since we use slot_getattr(), we don't need to implement the FETCHSOME
+	 * step explicitly, and we also needn't Assert that the attnum is in range
+	 * --- slot_getattr() will take care of any problems.
+	 */
+	return slot_getattr(slot, attnum, isnull);
+}
+
+/* Simple reference to inner Var */
+static Datum
+ExecJustInnerVar(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+	ExprEvalStep *op = &state->steps[1];
+	int			attnum = op->d.var.attnum + 1;
+	TupleTableSlot *slot = econtext->ecxt_innertuple;
+
+	/* See comments in ExecJustInnerVarFirst */
+	return slot_getattr(slot, attnum, isnull);
+}
+
+/* Simple reference to outer Var, first time through */
+static Datum
+ExecJustOuterVarFirst(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+	ExprEvalStep *op = &state->steps[1];
+	int			attnum = op->d.var.attnum + 1;
+	TupleTableSlot *slot = econtext->ecxt_outertuple;
+
+	CheckVarSlotCompatibility(slot, attnum, op->d.var.vartype);
+	op->opcode = EEOP_OUTER_VAR;	/* just for cleanliness */
+	state->evalfunc = ExecJustOuterVar;
+
+	/* See comments in ExecJustInnerVarFirst */
+	return slot_getattr(slot, attnum, isnull);
+}
+
+/* Simple reference to outer Var */
+static Datum
+ExecJustOuterVar(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+	ExprEvalStep *op = &state->steps[1];
+	int			attnum = op->d.var.attnum + 1;
+	TupleTableSlot *slot = econtext->ecxt_outertuple;
+
+	/* See comments in ExecJustInnerVarFirst */
+	return slot_getattr(slot, attnum, isnull);
+}
+
+/* Simple reference to scan Var, first time through */
+static Datum
+ExecJustScanVarFirst(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+	ExprEvalStep *op = &state->steps[1];
+	int			attnum = op->d.var.attnum + 1;
+	TupleTableSlot *slot = econtext->ecxt_scantuple;
+
+	CheckVarSlotCompatibility(slot, attnum, op->d.var.vartype);
+	op->opcode = EEOP_SCAN_VAR; /* just for cleanliness */
+	state->evalfunc = ExecJustScanVar;
+
+	/* See comments in ExecJustInnerVarFirst */
+	return slot_getattr(slot, attnum, isnull);
+}
+
+/* Simple reference to scan Var */
+static Datum
+ExecJustScanVar(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+	ExprEvalStep *op = &state->steps[1];
+	int			attnum = op->d.var.attnum + 1;
+	TupleTableSlot *slot = econtext->ecxt_scantuple;
+
+	/* See comments in ExecJustInnerVarFirst */
+	return slot_getattr(slot, attnum, isnull);
+}
+
+/* Simple Const expression */
+static Datum
+ExecJustConst(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+	ExprEvalStep *op = &state->steps[0];
+
+	*isnull = op->d.constval.isnull;
+	return op->d.constval.value;
+}
+
+/* Evaluate inner Var and assign to appropriate column of result tuple */
+static Datum
+ExecJustAssignInnerVar(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+	ExprEvalStep *op = &state->steps[1];
+	int			attnum = op->d.assign_var.attnum + 1;
+	int			resultnum = op->d.assign_var.resultnum;
+	TupleTableSlot *inslot = econtext->ecxt_innertuple;
+	TupleTableSlot *outslot = state->resultslot;
+
+	/*
+	 * We do not need CheckVarSlotCompatibility here; that was taken care of
+	 * at compilation time.
+	 *
+	 * Since we use slot_getattr(), we don't need to implement the FETCHSOME
+	 * step explicitly, and we also needn't Assert that the attnum is in range
+	 * --- slot_getattr() will take care of any problems.
+	 */
+	outslot->tts_values[resultnum] =
+		slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]);
+	return 0;
+}
+
+/* Evaluate outer Var and assign to appropriate column of result tuple */
+static Datum
+ExecJustAssignOuterVar(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+	ExprEvalStep *op = &state->steps[1];
+	int			attnum = op->d.assign_var.attnum + 1;
+	int			resultnum = op->d.assign_var.resultnum;
+	TupleTableSlot *inslot = econtext->ecxt_outertuple;
+	TupleTableSlot *outslot = state->resultslot;
+
+	/* See comments in ExecJustAssignInnerVar */
+	outslot->tts_values[resultnum] =
+		slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]);
+	return 0;
+}
+
+/* Evaluate scan Var and assign to appropriate column of result tuple */
+static Datum
+ExecJustAssignScanVar(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+	ExprEvalStep *op = &state->steps[1];
+	int			attnum = op->d.assign_var.attnum + 1;
+	int			resultnum = op->d.assign_var.resultnum;
+	TupleTableSlot *inslot = econtext->ecxt_scantuple;
+	TupleTableSlot *outslot = state->resultslot;
+
+	/* See comments in ExecJustAssignInnerVar */
+	outslot->tts_values[resultnum] =
+		slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]);
+	return 0;
+}
+
+
+/*
+ * Do one-time initialization of interpretation machinery.
+ */
+static void
+ExecInitInterpreter(void)
+{
+#if defined(EEO_USE_COMPUTED_GOTO)
+	/* Set up externally-visible pointer to dispatch table */
+	if (dispatch_table == NULL)
+		dispatch_table = (const void **)
+			DatumGetPointer(ExecInterpExpr(NULL, NULL, NULL));
+#endif
+}
+
+/*
+ * Function to return the opcode of an expression step.
+ *
+ * When direct-threading is in use, ExprState->opcode isn't easily
+ * decipherable. This function returns the appropriate enum member.
+ *
+ * This currently is only supposed to be used in paths that aren't critical
+ * performance-wise.  If that changes, we could add an inverse dispatch_table
+ * that's sorted on the address, so a binary search can be performed.
+ */
+ExprEvalOp
+ExecEvalStepOp(ExprState *state, ExprEvalStep *op)
+{
+#if defined(EEO_USE_COMPUTED_GOTO)
+	if (state->flags & EEO_FLAG_DIRECT_THREADED)
+	{
+		int			i;
+
+		for (i = 0; i < EEOP_LAST; i++)
+		{
+			if ((void *) op->opcode == dispatch_table[i])
+			{
+				return (ExprEvalOp) i;
+			}
+		}
+		elog(ERROR, "unknown opcode");
+	}
+#endif
+	return (ExprEvalOp) op->opcode;
+}
+
+
+/*
+ * Out-of-line helper functions for complex instructions.
+ */
+
+/*
+ * Evaluate a PARAM_EXEC parameter.
+ *
+ * PARAM_EXEC params (internal executor parameters) are stored in the
+ * ecxt_param_exec_vals array, and can be accessed by array index.
+ */
+void
+ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	ParamExecData *prm;
+
+	prm = &(econtext->ecxt_param_exec_vals[op->d.param.paramid]);
+	if (unlikely(prm->execPlan != NULL))
+	{
+		/* Parameter not evaluated yet, so go do it */
+		ExecSetParamPlan(prm->execPlan, econtext);
+		/* ExecSetParamPlan should have processed this param... */
+		Assert(prm->execPlan == NULL);
+	}
+	*op->resvalue = prm->value;
+	*op->resnull = prm->isnull;
+}
+
+/*
+ * Evaluate a PARAM_EXTERN parameter.
+ *
+ * PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
+ */
+void
+ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	ParamListInfo paramInfo = econtext->ecxt_param_list_info;
+	int			paramId = op->d.param.paramid;
+
+	if (likely(paramInfo &&
+			   paramId > 0 && paramId <= paramInfo->numParams))
+	{
+		ParamExternData *prm = &paramInfo->params[paramId - 1];
+
+		/* give hook a chance in case parameter is dynamic */
+		if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL)
+			(*paramInfo->paramFetch) (paramInfo, paramId);
+
+		if (likely(OidIsValid(prm->ptype)))
+		{
+			/* safety check in case hook did something unexpected */
+			if (unlikely(prm->ptype != op->d.param.paramtype))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
+								paramId,
+								format_type_be(prm->ptype),
+								format_type_be(op->d.param.paramtype))));
+			*op->resvalue = prm->value;
+			*op->resnull = prm->isnull;
+			return;
+		}
+	}
+
+	ereport(ERROR,
+			(errcode(ERRCODE_UNDEFINED_OBJECT),
+			 errmsg("no value found for parameter %d", paramId)));
+}
+
+/*
+ * Evaluate a SQLValueFunction expression.
+ */
+void
+ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
+{
+	SQLValueFunction *svf = op->d.sqlvaluefunction.svf;
+	FunctionCallInfoData fcinfo;
+
+	*op->resnull = false;
+
+	/*
+	 * Note: current_schema() can return NULL.  current_user() etc currently
+	 * cannot, but might as well code those cases the same way for safety.
+	 */
+	switch (svf->op)
+	{
+		case SVFOP_CURRENT_DATE:
+			*op->resvalue = DateADTGetDatum(GetSQLCurrentDate());
+			break;
+		case SVFOP_CURRENT_TIME:
+		case SVFOP_CURRENT_TIME_N:
+			*op->resvalue = TimeTzADTPGetDatum(GetSQLCurrentTime(svf->typmod));
+			break;
+		case SVFOP_CURRENT_TIMESTAMP:
+		case SVFOP_CURRENT_TIMESTAMP_N:
+			*op->resvalue = TimestampTzGetDatum(GetSQLCurrentTimestamp(svf->typmod));
+			break;
+		case SVFOP_LOCALTIME:
+		case SVFOP_LOCALTIME_N:
+			*op->resvalue = TimeADTGetDatum(GetSQLLocalTime(svf->typmod));
+			break;
+		case SVFOP_LOCALTIMESTAMP:
+		case SVFOP_LOCALTIMESTAMP_N:
+			*op->resvalue = TimestampGetDatum(GetSQLLocalTimestamp(svf->typmod));
+			break;
+		case SVFOP_CURRENT_ROLE:
+		case SVFOP_CURRENT_USER:
+		case SVFOP_USER:
+			InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+			*op->resvalue = current_user(&fcinfo);
+			*op->resnull = fcinfo.isnull;
+			break;
+		case SVFOP_SESSION_USER:
+			InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+			*op->resvalue = session_user(&fcinfo);
+			*op->resnull = fcinfo.isnull;
+			break;
+		case SVFOP_CURRENT_CATALOG:
+			InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+			*op->resvalue = current_database(&fcinfo);
+			*op->resnull = fcinfo.isnull;
+			break;
+		case SVFOP_CURRENT_SCHEMA:
+			InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+			*op->resvalue = current_schema(&fcinfo);
+			*op->resnull = fcinfo.isnull;
+			break;
+	}
+}
+
+/*
+ * Raise error if a CURRENT OF expression is evaluated.
+ *
+ * The planner should convert CURRENT OF into a TidScan qualification, or some
+ * other special handling in a ForeignScan node.  So we have to be able to do
+ * ExecInitExpr on a CurrentOfExpr, but we shouldn't ever actually execute it.
+ * If we get here, we suppose we must be dealing with CURRENT OF on a foreign
+ * table whose FDW doesn't handle it, and complain accordingly.
+ */
+void
+ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op)
+{
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		   errmsg("WHERE CURRENT OF is not supported for this table type")));
+}
+
+/*
+ * Evaluate NullTest / IS NULL for rows.
+ */
+void
+ExecEvalRowNull(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	ExecEvalRowNullInt(state, op, econtext, true);
+}
+
+/*
+ * Evaluate NullTest / IS NOT NULL for rows.
+ */
+void
+ExecEvalRowNotNull(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	ExecEvalRowNullInt(state, op, econtext, false);
+}
+
+/* Common code for IS [NOT] NULL on a row value */
+static void
+ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op,
+				   ExprContext *econtext, bool checkisnull)
+{
+	Datum		value = *op->resvalue;
+	bool		isnull = *op->resnull;
+	HeapTupleHeader tuple;
+	Oid			tupType;
+	int32		tupTypmod;
+	TupleDesc	tupDesc;
+	HeapTupleData tmptup;
+	int			att;
+
+	*op->resnull = false;
+
+	/* NULL row variables are treated just as NULL scalar columns */
+	if (isnull)
+	{
+		*op->resvalue = BoolGetDatum(checkisnull);
+		return;
+	}
+
+	/*
+	 * The SQL standard defines IS [NOT] NULL for a non-null rowtype argument
+	 * as:
+	 *
+	 * "R IS NULL" is true if every field is the null value.
+	 *
+	 * "R IS NOT NULL" is true if no field is the null value.
+	 *
+	 * This definition is (apparently intentionally) not recursive; so our
+	 * tests on the fields are primitive attisnull tests, not recursive checks
+	 * to see if they are all-nulls or no-nulls rowtypes.
+	 *
+	 * The standard does not consider the possibility of zero-field rows, but
+	 * here we consider them to vacuously satisfy both predicates.
+	 */
+
+	tuple = DatumGetHeapTupleHeader(value);
+
+	tupType = HeapTupleHeaderGetTypeId(tuple);
+	tupTypmod = HeapTupleHeaderGetTypMod(tuple);
+
+	/* Lookup tupdesc if first time through or if type changes */
+	tupDesc = get_cached_rowtype(tupType, tupTypmod,
+								 &op->d.nulltest_row.argdesc,
+								 econtext);
+
+	/*
+	 * heap_attisnull needs a HeapTuple not a bare HeapTupleHeader.
+	 */
+	tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
+	tmptup.t_data = tuple;
+
+	for (att = 1; att <= tupDesc->natts; att++)
+	{
+		/* ignore dropped columns */
+		if (tupDesc->attrs[att - 1]->attisdropped)
+			continue;
+		if (heap_attisnull(&tmptup, att))
+		{
+			/* null field disproves IS NOT NULL */
+			if (!checkisnull)
+			{
+				*op->resvalue = BoolGetDatum(false);
+				return;
+			}
+		}
+		else
+		{
+			/* non-null field disproves IS NULL */
+			if (checkisnull)
+			{
+				*op->resvalue = BoolGetDatum(false);
+				return;
+			}
+		}
+	}
+
+	*op->resvalue = BoolGetDatum(true);
+}
+
+/*
+ * Evaluate an ARRAY[] expression.
+ *
+ * The individual array elements (or subarrays) have already been evaluated
+ * into op->d.arrayexpr.elemvalues[]/elemnulls[].
+ */
+void
+ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op)
+{
+	ArrayType  *result;
+	Oid			element_type = op->d.arrayexpr.elemtype;
+	int			nelems = op->d.arrayexpr.nelems;
+	int			ndims = 0;
+	int			dims[MAXDIM];
+	int			lbs[MAXDIM];
+
+	/* Set non-null as default */
+	*op->resnull = false;
+
+	if (!op->d.arrayexpr.multidims)
+	{
+		/* Elements are presumably of scalar type */
+		Datum	   *dvalues = op->d.arrayexpr.elemvalues;
+		bool	   *dnulls = op->d.arrayexpr.elemnulls;
+
+		/* Shouldn't happen here, but if length is 0, return empty array */
+		if (nelems == 0)
+		{
+			*op->resvalue =
+				PointerGetDatum(construct_empty_array(element_type));
+			return;
+		}
+
+		/* setup for 1-D array of the given length */
+		ndims = 1;
+		dims[0] = nelems;
+		lbs[0] = 1;
+
+		result = construct_md_array(dvalues, dnulls, ndims, dims, lbs,
+									element_type,
+									op->d.arrayexpr.elemlength,
+									op->d.arrayexpr.elembyval,
+									op->d.arrayexpr.elemalign);
+	}
+	else
+	{
+		/* Must be nested array expressions */
+		int			nbytes = 0;
+		int			nitems = 0;
+		int			outer_nelems = 0;
+		int			elem_ndims = 0;
+		int		   *elem_dims = NULL;
+		int		   *elem_lbs = NULL;
+		bool		firstone = true;
+		bool		havenulls = false;
+		bool		haveempty = false;
+		char	  **subdata;
+		bits8	  **subbitmaps;
+		int		   *subbytes;
+		int		   *subnitems;
+		int32		dataoffset;
+		char	   *dat;
+		int			iitem;
+		int			elemoff;
+		int			i;
+
+		subdata = (char **) palloc(nelems * sizeof(char *));
+		subbitmaps = (bits8 **) palloc(nelems * sizeof(bits8 *));
+		subbytes = (int *) palloc(nelems * sizeof(int));
+		subnitems = (int *) palloc(nelems * sizeof(int));
+
+		/* loop through and get data area from each element */
+		for (elemoff = 0; elemoff < nelems; elemoff++)
+		{
+			Datum		arraydatum;
+			bool		eisnull;
+			ArrayType  *array;
+			int			this_ndims;
+
+			arraydatum = op->d.arrayexpr.elemvalues[elemoff];
+			eisnull = op->d.arrayexpr.elemnulls[elemoff];
+
+			/* temporarily ignore null subarrays */
+			if (eisnull)
+			{
+				haveempty = true;
+				continue;
+			}
+
+			array = DatumGetArrayTypeP(arraydatum);
+
+			/* run-time double-check on element type */
+			if (element_type != ARR_ELEMTYPE(array))
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("cannot merge incompatible arrays"),
+						 errdetail("Array with element type %s cannot be "
+						 "included in ARRAY construct with element type %s.",
+								   format_type_be(ARR_ELEMTYPE(array)),
+								   format_type_be(element_type))));
+
+			this_ndims = ARR_NDIM(array);
+			/* temporarily ignore zero-dimensional subarrays */
+			if (this_ndims <= 0)
+			{
+				haveempty = true;
+				continue;
+			}
+
+			if (firstone)
+			{
+				/* Get sub-array details from first member */
+				elem_ndims = this_ndims;
+				ndims = elem_ndims + 1;
+				if (ndims <= 0 || ndims > MAXDIM)
+					ereport(ERROR,
+							(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+						  errmsg("number of array dimensions (%d) exceeds " \
+								 "the maximum allowed (%d)", ndims, MAXDIM)));
+
+				elem_dims = (int *) palloc(elem_ndims * sizeof(int));
+				memcpy(elem_dims, ARR_DIMS(array), elem_ndims * sizeof(int));
+				elem_lbs = (int *) palloc(elem_ndims * sizeof(int));
+				memcpy(elem_lbs, ARR_LBOUND(array), elem_ndims * sizeof(int));
+
+				firstone = false;
+			}
+			else
+			{
+				/* Check other sub-arrays are compatible */
+				if (elem_ndims != this_ndims ||
+					memcmp(elem_dims, ARR_DIMS(array),
+						   elem_ndims * sizeof(int)) != 0 ||
+					memcmp(elem_lbs, ARR_LBOUND(array),
+						   elem_ndims * sizeof(int)) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+							 errmsg("multidimensional arrays must have array "
+									"expressions with matching dimensions")));
+			}
+
+			subdata[outer_nelems] = ARR_DATA_PTR(array);
+			subbitmaps[outer_nelems] = ARR_NULLBITMAP(array);
+			subbytes[outer_nelems] = ARR_SIZE(array) - ARR_DATA_OFFSET(array);
+			nbytes += subbytes[outer_nelems];
+			subnitems[outer_nelems] = ArrayGetNItems(this_ndims,
+													 ARR_DIMS(array));
+			nitems += subnitems[outer_nelems];
+			havenulls |= ARR_HASNULL(array);
+			outer_nelems++;
+		}
+
+		/*
+		 * If all items were null or empty arrays, return an empty array;
+		 * otherwise, if some were and some weren't, raise error.  (Note: we
+		 * must special-case this somehow to avoid trying to generate a 1-D
+		 * array formed from empty arrays.  It's not ideal...)
+		 */
+		if (haveempty)
+		{
+			if (ndims == 0)		/* didn't find any nonempty array */
+			{
+				*op->resvalue = PointerGetDatum(construct_empty_array(element_type));
+				return;
+			}
+			ereport(ERROR,
+					(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+					 errmsg("multidimensional arrays must have array "
+							"expressions with matching dimensions")));
+		}
+
+		/* setup for multi-D array */
+		dims[0] = outer_nelems;
+		lbs[0] = 1;
+		for (i = 1; i < ndims; i++)
+		{
+			dims[i] = elem_dims[i - 1];
+			lbs[i] = elem_lbs[i - 1];
+		}
+
+		if (havenulls)
+		{
+			dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems);
+			nbytes += dataoffset;
+		}
+		else
+		{
+			dataoffset = 0;		/* marker for no null bitmap */
+			nbytes += ARR_OVERHEAD_NONULLS(ndims);
+		}
+
+		result = (ArrayType *) palloc(nbytes);
+		SET_VARSIZE(result, nbytes);
+		result->ndim = ndims;
+		result->dataoffset = dataoffset;
+		result->elemtype = element_type;
+		memcpy(ARR_DIMS(result), dims, ndims * sizeof(int));
+		memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int));
+
+		dat = ARR_DATA_PTR(result);
+		iitem = 0;
+		for (i = 0; i < outer_nelems; i++)
+		{
+			memcpy(dat, subdata[i], subbytes[i]);
+			dat += subbytes[i];
+			if (havenulls)
+				array_bitmap_copy(ARR_NULLBITMAP(result), iitem,
+								  subbitmaps[i], 0,
+								  subnitems[i]);
+			iitem += subnitems[i];
+		}
+	}
+
+	*op->resvalue = PointerGetDatum(result);
+}
+
+/*
+ * Evaluate an ArrayCoerceExpr expression.
+ *
+ * Source array is in step's result variable.
+ */
+void
+ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op)
+{
+	ArrayCoerceExpr *acoerce = op->d.arraycoerce.coerceexpr;
+	Datum		arraydatum;
+	FunctionCallInfoData locfcinfo;
+
+	/* NULL array -> NULL result */
+	if (*op->resnull)
+		return;
+
+	arraydatum = *op->resvalue;
+
+	/*
+	 * If it's binary-compatible, modify the element type in the array header,
+	 * but otherwise leave the array as we received it.
+	 */
+	if (!OidIsValid(acoerce->elemfuncid))
+	{
+		/* Detoast input array if necessary, and copy in any case */
+		ArrayType  *array = DatumGetArrayTypePCopy(arraydatum);
+
+		ARR_ELEMTYPE(array) = op->d.arraycoerce.resultelemtype;
+		*op->resvalue = PointerGetDatum(array);
+		return;
+	}
+
+	/*
+	 * Use array_map to apply the function to each array element.
+	 *
+	 * We pass on the desttypmod and isExplicit flags whether or not the
+	 * function wants them.
+	 *
+	 * Note: coercion functions are assumed to not use collation.
+	 */
+	InitFunctionCallInfoData(locfcinfo, op->d.arraycoerce.elemfunc, 3,
+							 InvalidOid, NULL, NULL);
+	locfcinfo.arg[0] = arraydatum;
+	locfcinfo.arg[1] = Int32GetDatum(acoerce->resulttypmod);
+	locfcinfo.arg[2] = BoolGetDatum(acoerce->isExplicit);
+	locfcinfo.argnull[0] = false;
+	locfcinfo.argnull[1] = false;
+	locfcinfo.argnull[2] = false;
+
+	*op->resvalue = array_map(&locfcinfo, op->d.arraycoerce.resultelemtype,
+							  op->d.arraycoerce.amstate);
+}
+
+/*
+ * Evaluate a ROW() expression.
+ *
+ * The individual columns have already been evaluated into
+ * op->d.row.elemvalues[]/elemnulls[].
+ */
+void
+ExecEvalRow(ExprState *state, ExprEvalStep *op)
+{
+	HeapTuple	tuple;
+
+	/* build tuple from evaluated field values */
+	tuple = heap_form_tuple(op->d.row.tupdesc,
+							op->d.row.elemvalues,
+							op->d.row.elemnulls);
+
+	*op->resvalue = HeapTupleGetDatum(tuple);
+	*op->resnull = false;
+}
+
+/*
+ * Evaluate GREATEST() or LEAST() expression (note this is *not* MIN()/MAX()).
+ *
+ * All of the to-be-compared expressions have already been evaluated into
+ * op->d.minmax.values[]/nulls[].
+ */
+void
+ExecEvalMinMax(ExprState *state, ExprEvalStep *op)
+{
+	Datum	   *values = op->d.minmax.values;
+	bool	   *nulls = op->d.minmax.nulls;
+	FunctionCallInfo fcinfo = op->d.minmax.fcinfo_data;
+	MinMaxOp	operator = op->d.minmax.op;
+	int			off;
+
+	/* set at initialization */
+	Assert(fcinfo->argnull[0] == false);
+	Assert(fcinfo->argnull[1] == false);
+
+	/* default to null result */
+	*op->resnull = true;
+
+	for (off = 0; off < op->d.minmax.nelems; off++)
+	{
+		/* ignore NULL inputs */
+		if (nulls[off])
+			continue;
+
+		if (*op->resnull)
+		{
+			/* first nonnull input, adopt value */
+			*op->resvalue = values[off];
+			*op->resnull = false;
+		}
+		else
+		{
+			int			cmpresult;
+
+			/* apply comparison function */
+			fcinfo->arg[0] = *op->resvalue;
+			fcinfo->arg[1] = values[off];
+
+			fcinfo->isnull = false;
+			cmpresult = DatumGetInt32(FunctionCallInvoke(fcinfo));
+			if (fcinfo->isnull) /* probably should not happen */
+				continue;
+
+			if (cmpresult > 0 && operator == IS_LEAST)
+				*op->resvalue = values[off];
+			else if (cmpresult < 0 && operator == IS_GREATEST)
+				*op->resvalue = values[off];
+		}
+	}
+}
+
+/*
+ * Evaluate a FieldSelect node.
+ *
+ * Source record is in step's result variable.
+ */
+void
+ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	AttrNumber	fieldnum = op->d.fieldselect.fieldnum;
+	Datum		tupDatum;
+	HeapTupleHeader tuple;
+	Oid			tupType;
+	int32		tupTypmod;
+	TupleDesc	tupDesc;
+	Form_pg_attribute attr;
+	HeapTupleData tmptup;
+
+	/* NULL record -> NULL result */
+	if (*op->resnull)
+		return;
+
+	/* Get the composite datum and extract its type fields */
+	tupDatum = *op->resvalue;
+	tuple = DatumGetHeapTupleHeader(tupDatum);
+
+	tupType = HeapTupleHeaderGetTypeId(tuple);
+	tupTypmod = HeapTupleHeaderGetTypMod(tuple);
+
+	/* Lookup tupdesc if first time through or if type changes */
+	tupDesc = get_cached_rowtype(tupType, tupTypmod,
+								 &op->d.fieldselect.argdesc,
+								 econtext);
+
+	/*
+	 * Find field's attr record.  Note we don't support system columns here: a
+	 * datum tuple doesn't have valid values for most of the interesting
+	 * system columns anyway.
+	 */
+	if (fieldnum <= 0)			/* should never happen */
+		elog(ERROR, "unsupported reference to system column %d in FieldSelect",
+			 fieldnum);
+	if (fieldnum > tupDesc->natts)		/* should never happen */
+		elog(ERROR, "attribute number %d exceeds number of columns %d",
+			 fieldnum, tupDesc->natts);
+	attr = tupDesc->attrs[fieldnum - 1];
+
+	/* Check for dropped column, and force a NULL result if so */
+	if (attr->attisdropped)
+	{
+		*op->resnull = true;
+		return;
+	}
+
+	/* Check for type mismatch --- possible after ALTER COLUMN TYPE? */
+	/* As in CheckVarSlotCompatibility, we should but can't check typmod */
+	if (op->d.fieldselect.resulttype != attr->atttypid)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("attribute %d has wrong type", fieldnum),
+				 errdetail("Table has type %s, but query expects %s.",
+						   format_type_be(attr->atttypid),
+						   format_type_be(op->d.fieldselect.resulttype))));
+
+	/* heap_getattr needs a HeapTuple not a bare HeapTupleHeader */
+	tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
+	tmptup.t_data = tuple;
+
+	/* extract the field */
+	*op->resvalue = heap_getattr(&tmptup,
+								 fieldnum,
+								 tupDesc,
+								 op->resnull);
+}
+
+/*
+ * Deform source tuple, filling in the step's values/nulls arrays, before
+ * evaluating individual new values as part of a FieldStore expression.
+ * Subsequent steps will overwrite individual elements of the values/nulls
+ * arrays with the new field values, and then FIELDSTORE_FORM will build the
+ * new tuple value.
+ *
+ * Source record is in step's result variable.
+ */
+void
+ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	TupleDesc	tupDesc;
+
+	/* Lookup tupdesc if first time through or after rescan */
+	tupDesc = get_cached_rowtype(op->d.fieldstore.fstore->resulttype, -1,
+								 op->d.fieldstore.argdesc, econtext);
+
+	/* Check that current tupdesc doesn't have more fields than we allocated */
+	if (unlikely(tupDesc->natts > op->d.fieldstore.ncolumns))
+		elog(ERROR, "too many columns in composite type %u",
+			 op->d.fieldstore.fstore->resulttype);
+
+	if (*op->resnull)
+	{
+		/* Convert null input tuple into an all-nulls row */
+		memset(op->d.fieldstore.nulls, true,
+			   op->d.fieldstore.ncolumns * sizeof(bool));
+	}
+	else
+	{
+		/*
+		 * heap_deform_tuple needs a HeapTuple not a bare HeapTupleHeader. We
+		 * set all the fields in the struct just in case.
+		 */
+		Datum		tupDatum = *op->resvalue;
+		HeapTupleHeader tuphdr;
+		HeapTupleData tmptup;
+
+		tuphdr = DatumGetHeapTupleHeader(tupDatum);
+		tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr);
+		ItemPointerSetInvalid(&(tmptup.t_self));
+		tmptup.t_tableOid = InvalidOid;
+		tmptup.t_data = tuphdr;
+
+		heap_deform_tuple(&tmptup, tupDesc,
+						  op->d.fieldstore.values,
+						  op->d.fieldstore.nulls);
+	}
+}
+
+/*
+ * Compute the new composite datum after each individual field value of a
+ * FieldStore expression has been evaluated.
+ */
+void
+ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	HeapTuple	tuple;
+
+	/* argdesc should already be valid from the DeForm step */
+	tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
+							op->d.fieldstore.values,
+							op->d.fieldstore.nulls);
+
+	*op->resvalue = HeapTupleGetDatum(tuple);
+	*op->resnull = false;
+}
+
+/*
+ * Process a subscript in an ArrayRef expression.
+ *
+ * If subscript is NULL, throw error in assignment case, or in fetch case
+ * set result to NULL and return false (instructing caller to skip the rest
+ * of the ArrayRef sequence).
+ *
+ * Subscript expression result is in subscriptvalue/subscriptnull.
+ * On success, integer subscript value has been saved in upperindex[] or
+ * lowerindex[] for use later.
+ */
+bool
+ExecEvalArrayRefSubscript(ExprState *state, ExprEvalStep *op)
+{
+	ArrayRefState *arefstate = op->d.arrayref_subscript.state;
+	int		   *indexes;
+	int			off;
+
+	/* If any index expr yields NULL, result is NULL or error */
+	if (arefstate->subscriptnull)
+	{
+		if (arefstate->isassignment)
+			ereport(ERROR,
+					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				  errmsg("array subscript in assignment must not be null")));
+		*op->resnull = true;
+		return false;
+	}
+
+	/* Convert datum to int, save in appropriate place */
+	if (op->d.arrayref_subscript.isupper)
+		indexes = arefstate->upperindex;
+	else
+		indexes = arefstate->lowerindex;
+	off = op->d.arrayref_subscript.off;
+
+	indexes[off] = DatumGetInt32(arefstate->subscriptvalue);
+
+	return true;
+}
+
+/*
+ * Evaluate ArrayRef fetch.
+ *
+ * Source array is in step's result variable.
+ */
+void
+ExecEvalArrayRefFetch(ExprState *state, ExprEvalStep *op)
+{
+	ArrayRefState *arefstate = op->d.arrayref.state;
+
+	/* Should not get here if source array (or any subscript) is null */
+	Assert(!(*op->resnull));
+
+	if (arefstate->numlower == 0)
+	{
+		/* Scalar case */
+		*op->resvalue = array_get_element(*op->resvalue,
+										  arefstate->numupper,
+										  arefstate->upperindex,
+										  arefstate->refattrlength,
+										  arefstate->refelemlength,
+										  arefstate->refelembyval,
+										  arefstate->refelemalign,
+										  op->resnull);
+	}
+	else
+	{
+		/* Slice case */
+		*op->resvalue = array_get_slice(*op->resvalue,
+										arefstate->numupper,
+										arefstate->upperindex,
+										arefstate->lowerindex,
+										arefstate->upperprovided,
+										arefstate->lowerprovided,
+										arefstate->refattrlength,
+										arefstate->refelemlength,
+										arefstate->refelembyval,
+										arefstate->refelemalign);
+	}
+}
+
+/*
+ * Compute old array element/slice value for an ArrayRef assignment
+ * expression.  Will only be generated if the new-value subexpression
+ * contains ArrayRef or FieldStore.  The value is stored into the
+ * ArrayRefState's prevvalue/prevnull fields.
+ */
+void
+ExecEvalArrayRefOld(ExprState *state, ExprEvalStep *op)
+{
+	ArrayRefState *arefstate = op->d.arrayref.state;
+
+	if (*op->resnull)
+	{
+		/* whole array is null, so any element or slice is too */
+		arefstate->prevvalue = (Datum) 0;
+		arefstate->prevnull = true;
+	}
+	else if (arefstate->numlower == 0)
+	{
+		/* Scalar case */
+		arefstate->prevvalue = array_get_element(*op->resvalue,
+												 arefstate->numupper,
+												 arefstate->upperindex,
+												 arefstate->refattrlength,
+												 arefstate->refelemlength,
+												 arefstate->refelembyval,
+												 arefstate->refelemalign,
+												 &arefstate->prevnull);
+	}
+	else
+	{
+		/* Slice case */
+		/* this is currently unreachable */
+		arefstate->prevvalue = array_get_slice(*op->resvalue,
+											   arefstate->numupper,
+											   arefstate->upperindex,
+											   arefstate->lowerindex,
+											   arefstate->upperprovided,
+											   arefstate->lowerprovided,
+											   arefstate->refattrlength,
+											   arefstate->refelemlength,
+											   arefstate->refelembyval,
+											   arefstate->refelemalign);
+		arefstate->prevnull = false;
+	}
+}
+
+/*
+ * Evaluate ArrayRef assignment.
+ *
+ * Input array (possibly null) is in result area, replacement value is in
+ * ArrayRefState's replacevalue/replacenull.
+ */
+void
+ExecEvalArrayRefAssign(ExprState *state, ExprEvalStep *op)
+{
+	ArrayRefState *arefstate = op->d.arrayref.state;
+
+	/*
+	 * For an assignment to a fixed-length array type, both the original array
+	 * and the value to be assigned into it must be non-NULL, else we punt and
+	 * return the original array.
+	 */
+	if (arefstate->refattrlength > 0)	/* fixed-length array? */
+	{
+		if (*op->resnull || arefstate->replacenull)
+			return;
+	}
+
+	/*
+	 * For assignment to varlena arrays, we handle a NULL original array by
+	 * substituting an empty (zero-dimensional) array; insertion of the new
+	 * element will result in a singleton array value.  It does not matter
+	 * whether the new element is NULL.
+	 */
+	if (*op->resnull)
+	{
+		*op->resvalue = PointerGetDatum(construct_empty_array(arefstate->refelemtype));
+		*op->resnull = false;
+	}
+
+	if (arefstate->numlower == 0)
+	{
+		/* Scalar case */
+		*op->resvalue = array_set_element(*op->resvalue,
+										  arefstate->numupper,
+										  arefstate->upperindex,
+										  arefstate->replacevalue,
+										  arefstate->replacenull,
+										  arefstate->refattrlength,
+										  arefstate->refelemlength,
+										  arefstate->refelembyval,
+										  arefstate->refelemalign);
+	}
+	else
+	{
+		/* Slice case */
+		*op->resvalue = array_set_slice(*op->resvalue,
+										arefstate->numupper,
+										arefstate->upperindex,
+										arefstate->lowerindex,
+										arefstate->upperprovided,
+										arefstate->lowerprovided,
+										arefstate->replacevalue,
+										arefstate->replacenull,
+										arefstate->refattrlength,
+										arefstate->refelemlength,
+										arefstate->refelembyval,
+										arefstate->refelemalign);
+	}
+}
+
+/*
+ * Evaluate a rowtype coercion operation.
+ * This may require rearranging field positions.
+ *
+ * Source record is in step's result variable.
+ */
+void
+ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	ConvertRowtypeExpr *convert = op->d.convert_rowtype.convert;
+	HeapTuple	result;
+	Datum		tupDatum;
+	HeapTupleHeader tuple;
+	HeapTupleData tmptup;
+	TupleDesc	indesc,
+				outdesc;
+
+	/* NULL in -> NULL out */
+	if (*op->resnull)
+		return;
+
+	tupDatum = *op->resvalue;
+	tuple = DatumGetHeapTupleHeader(tupDatum);
+
+	/* Lookup tupdescs if first time through or after rescan */
+	if (op->d.convert_rowtype.indesc == NULL)
+	{
+		get_cached_rowtype(exprType((Node *) convert->arg), -1,
+						   &op->d.convert_rowtype.indesc,
+						   econtext);
+		op->d.convert_rowtype.initialized = false;
+	}
+	if (op->d.convert_rowtype.outdesc == NULL)
+	{
+		get_cached_rowtype(convert->resulttype, -1,
+						   &op->d.convert_rowtype.outdesc,
+						   econtext);
+		op->d.convert_rowtype.initialized = false;
+	}
+
+	indesc = op->d.convert_rowtype.indesc;
+	outdesc = op->d.convert_rowtype.outdesc;
+
+	/*
+	 * We used to be able to assert that incoming tuples are marked with
+	 * exactly the rowtype of indesc.  However, now that ExecEvalWholeRowVar
+	 * might change the tuples' marking to plain RECORD due to inserting
+	 * aliases, we can only make this weak test:
+	 */
+	Assert(HeapTupleHeaderGetTypeId(tuple) == indesc->tdtypeid ||
+		   HeapTupleHeaderGetTypeId(tuple) == RECORDOID);
+
+	/* if first time through, initialize conversion map */
+	if (!op->d.convert_rowtype.initialized)
+	{
+		MemoryContext old_cxt;
+
+		/* allocate map in long-lived memory context */
+		old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+		/* prepare map from old to new attribute numbers */
+		op->d.convert_rowtype.map =
+			convert_tuples_by_name(indesc, outdesc,
+								 gettext_noop("could not convert row type"));
+		op->d.convert_rowtype.initialized = true;
+
+		MemoryContextSwitchTo(old_cxt);
+	}
+
+	/*
+	 * No-op if no conversion needed (not clear this can happen here).
+	 */
+	if (op->d.convert_rowtype.map == NULL)
+		return;
+
+	/*
+	 * do_convert_tuple needs a HeapTuple not a bare HeapTupleHeader.
+	 */
+	tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
+	tmptup.t_data = tuple;
+
+	result = do_convert_tuple(&tmptup, op->d.convert_rowtype.map);
+
+	*op->resvalue = HeapTupleGetDatum(result);
+}
+
+/*
+ * Evaluate "scalar op ANY/ALL (array)".
+ *
+ * Source array is in our result area, scalar arg is already evaluated into
+ * fcinfo->arg[0]/argnull[0].
+ *
+ * The operator always yields boolean, and we combine the results across all
+ * array elements using OR and AND (for ANY and ALL respectively).  Of course
+ * we short-circuit as soon as the result is known.
+ */
+void
+ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
+{
+	FunctionCallInfo fcinfo = op->d.scalararrayop.fcinfo_data;
+	bool		useOr = op->d.scalararrayop.useOr;
+	bool		strictfunc = op->d.scalararrayop.finfo->fn_strict;
+	ArrayType  *arr;
+	int			nitems;
+	Datum		result;
+	bool		resultnull;
+	int			i;
+	int16		typlen;
+	bool		typbyval;
+	char		typalign;
+	char	   *s;
+	bits8	   *bitmap;
+	int			bitmask;
+
+	/*
+	 * If the array is NULL then we return NULL --- it's not very meaningful
+	 * to do anything else, even if the operator isn't strict.
+	 */
+	if (*op->resnull)
+		return;
+
+	/* Else okay to fetch and detoast the array */
+	arr = DatumGetArrayTypeP(*op->resvalue);
+
+	/*
+	 * If the array is empty, we return either FALSE or TRUE per the useOr
+	 * flag.  This is correct even if the scalar is NULL; since we would
+	 * evaluate the operator zero times, it matters not whether it would want
+	 * to return NULL.
+	 */
+	nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+	if (nitems <= 0)
+	{
+		*op->resvalue = BoolGetDatum(!useOr);
+		*op->resnull = false;
+		return;
+	}
+
+	/*
+	 * If the scalar is NULL, and the function is strict, return NULL; no
+	 * point in iterating the loop.
+	 */
+	if (fcinfo->argnull[0] && strictfunc)
+	{
+		*op->resnull = true;
+		return;
+	}
+
+	/*
+	 * We arrange to look up info about the element type only once per series
+	 * of calls, assuming the element type doesn't change underneath us.
+	 */
+	if (op->d.scalararrayop.element_type != ARR_ELEMTYPE(arr))
+	{
+		get_typlenbyvalalign(ARR_ELEMTYPE(arr),
+							 &op->d.scalararrayop.typlen,
+							 &op->d.scalararrayop.typbyval,
+							 &op->d.scalararrayop.typalign);
+		op->d.scalararrayop.element_type = ARR_ELEMTYPE(arr);
+	}
+
+	typlen = op->d.scalararrayop.typlen;
+	typbyval = op->d.scalararrayop.typbyval;
+	typalign = op->d.scalararrayop.typalign;
+
+	/* Initialize result appropriately depending on useOr */
+	result = BoolGetDatum(!useOr);
+	resultnull = false;
+
+	/* Loop over the array elements */
+	s = (char *) ARR_DATA_PTR(arr);
+	bitmap = ARR_NULLBITMAP(arr);
+	bitmask = 1;
+
+	for (i = 0; i < nitems; i++)
+	{
+		Datum		elt;
+		Datum		thisresult;
+
+		/* Get array element, checking for NULL */
+		if (bitmap && (*bitmap & bitmask) == 0)
+		{
+			fcinfo->arg[1] = (Datum) 0;
+			fcinfo->argnull[1] = true;
+		}
+		else
+		{
+			elt = fetch_att(s, typbyval, typlen);
+			s = att_addlength_pointer(s, typlen, s);
+			s = (char *) att_align_nominal(s, typalign);
+			fcinfo->arg[1] = elt;
+			fcinfo->argnull[1] = false;
+		}
+
+		/* Call comparison function */
+		if (fcinfo->argnull[1] && strictfunc)
+		{
+			fcinfo->isnull = true;
+			thisresult = (Datum) 0;
+		}
+		else
+		{
+			fcinfo->isnull = false;
+			thisresult = (op->d.scalararrayop.fn_addr) (fcinfo);
+		}
+
+		/* Combine results per OR or AND semantics */
+		if (fcinfo->isnull)
+			resultnull = true;
+		else if (useOr)
+		{
+			if (DatumGetBool(thisresult))
+			{
+				result = BoolGetDatum(true);
+				resultnull = false;
+				break;			/* needn't look at any more elements */
+			}
+		}
+		else
+		{
+			if (!DatumGetBool(thisresult))
+			{
+				result = BoolGetDatum(false);
+				resultnull = false;
+				break;			/* needn't look at any more elements */
+			}
+		}
+
+		/* advance bitmap pointer if any */
+		if (bitmap)
+		{
+			bitmask <<= 1;
+			if (bitmask == 0x100)
+			{
+				bitmap++;
+				bitmask = 1;
+			}
+		}
+	}
+
+	*op->resvalue = result;
+	*op->resnull = resultnull;
+}
+
+/*
+ * Evaluate a NOT NULL domain constraint.
+ */
+void
+ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op)
+{
+	if (*op->resnull)
+		ereport(ERROR,
+				(errcode(ERRCODE_NOT_NULL_VIOLATION),
+				 errmsg("domain %s does not allow null values",
+						format_type_be(op->d.domaincheck.resulttype)),
+				 errdatatype(op->d.domaincheck.resulttype)));
+}
+
+/*
+ * Evaluate a CHECK domain constraint.
+ */
+void
+ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op)
+{
+	if (!*op->d.domaincheck.checknull &&
+		!DatumGetBool(*op->d.domaincheck.checkvalue))
+		ereport(ERROR,
+				(errcode(ERRCODE_CHECK_VIOLATION),
+			   errmsg("value for domain %s violates check constraint \"%s\"",
+					  format_type_be(op->d.domaincheck.resulttype),
+					  op->d.domaincheck.constraintname),
+				 errdomainconstraint(op->d.domaincheck.resulttype,
+									 op->d.domaincheck.constraintname)));
+}
+
+/*
+ * Evaluate the various forms of XmlExpr.
+ *
+ * Arguments have been evaluated into named_argvalue/named_argnull
+ * and/or argvalue/argnull arrays.
+ */
+void
+ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
+{
+	XmlExpr    *xexpr = op->d.xmlexpr.xexpr;
+	Datum		value;
+	int			i;
+
+	*op->resnull = true;		/* until we get a result */
+	*op->resvalue = (Datum) 0;
+
+	switch (xexpr->op)
+	{
+		case IS_XMLCONCAT:
+			{
+				Datum	   *argvalue = op->d.xmlexpr.argvalue;
+				bool	   *argnull = op->d.xmlexpr.argnull;
+				List	   *values = NIL;
+
+				for (i = 0; i < list_length(xexpr->args); i++)
+				{
+					if (!argnull[i])
+						values = lappend(values, DatumGetPointer(argvalue[i]));
+				}
+
+				if (values != NIL)
+				{
+					*op->resvalue = PointerGetDatum(xmlconcat(values));
+					*op->resnull = false;
+				}
+			}
+			break;
+
+		case IS_XMLFOREST:
+			{
+				Datum	   *argvalue = op->d.xmlexpr.named_argvalue;
+				bool	   *argnull = op->d.xmlexpr.named_argnull;
+				StringInfoData buf;
+				ListCell   *lc;
+				ListCell   *lc2;
+
+				initStringInfo(&buf);
+
+				i = 0;
+				forboth(lc, xexpr->named_args, lc2, xexpr->arg_names)
+				{
+					Expr	   *e = (Expr *) lfirst(lc);
+					char	   *argname = strVal(lfirst(lc2));
+
+					if (!argnull[i])
+					{
+						value = argvalue[i];
+						appendStringInfo(&buf, "<%s>%s</%s>",
+										 argname,
+										 map_sql_value_to_xml_value(value,
+												 exprType((Node *) e), true),
+										 argname);
+						*op->resnull = false;
+					}
+					i++;
+				}
+
+				if (!*op->resnull)
+				{
+					text	   *result;
+
+					result = cstring_to_text_with_len(buf.data, buf.len);
+					*op->resvalue = PointerGetDatum(result);
+				}
+
+				pfree(buf.data);
+			}
+			break;
+
+		case IS_XMLELEMENT:
+			*op->resvalue = PointerGetDatum(xmlelement(xexpr,
+												op->d.xmlexpr.named_argvalue,
+												 op->d.xmlexpr.named_argnull,
+													   op->d.xmlexpr.argvalue,
+													 op->d.xmlexpr.argnull));
+			*op->resnull = false;
+			break;
+
+		case IS_XMLPARSE:
+			{
+				Datum	   *argvalue = op->d.xmlexpr.argvalue;
+				bool	   *argnull = op->d.xmlexpr.argnull;
+				text	   *data;
+				bool		preserve_whitespace;
+
+				/* arguments are known to be text, bool */
+				Assert(list_length(xexpr->args) == 2);
+
+				if (argnull[0])
+					return;
+				value = argvalue[0];
+				data = DatumGetTextPP(value);
+
+				if (argnull[1]) /* probably can't happen */
+					return;
+				value = argvalue[1];
+				preserve_whitespace = DatumGetBool(value);
+
+				*op->resvalue = PointerGetDatum(xmlparse(data,
+														 xexpr->xmloption,
+													   preserve_whitespace));
+				*op->resnull = false;
+			}
+			break;
+
+		case IS_XMLPI:
+			{
+				text	   *arg;
+				bool		isnull;
+
+				/* optional argument is known to be text */
+				Assert(list_length(xexpr->args) <= 1);
+
+				if (xexpr->args)
+				{
+					isnull = op->d.xmlexpr.argnull[0];
+					if (isnull)
+						arg = NULL;
+					else
+						arg = DatumGetTextPP(op->d.xmlexpr.argvalue[0]);
+				}
+				else
+				{
+					arg = NULL;
+					isnull = false;
+				}
+
+				*op->resvalue = PointerGetDatum(xmlpi(xexpr->name,
+													  arg,
+													  isnull,
+													  op->resnull));
+			}
+			break;
+
+		case IS_XMLROOT:
+			{
+				Datum	   *argvalue = op->d.xmlexpr.argvalue;
+				bool	   *argnull = op->d.xmlexpr.argnull;
+				xmltype    *data;
+				text	   *version;
+				int			standalone;
+
+				/* arguments are known to be xml, text, int */
+				Assert(list_length(xexpr->args) == 3);
+
+				if (argnull[0])
+					return;
+				data = DatumGetXmlP(argvalue[0]);
+
+				if (argnull[1])
+					version = NULL;
+				else
+					version = DatumGetTextPP(argvalue[1]);
+
+				Assert(!argnull[2]);	/* always present */
+				standalone = DatumGetInt32(argvalue[2]);
+
+				*op->resvalue = PointerGetDatum(xmlroot(data,
+														version,
+														standalone));
+				*op->resnull = false;
+			}
+			break;
+
+		case IS_XMLSERIALIZE:
+			{
+				Datum	   *argvalue = op->d.xmlexpr.argvalue;
+				bool	   *argnull = op->d.xmlexpr.argnull;
+
+				/* argument type is known to be xml */
+				Assert(list_length(xexpr->args) == 1);
+
+				if (argnull[0])
+					return;
+				value = argvalue[0];
+
+				*op->resvalue = PointerGetDatum(
+								xmltotext_with_xmloption(DatumGetXmlP(value),
+														 xexpr->xmloption));
+				*op->resnull = false;
+			}
+			break;
+
+		case IS_DOCUMENT:
+			{
+				Datum	   *argvalue = op->d.xmlexpr.argvalue;
+				bool	   *argnull = op->d.xmlexpr.argnull;
+
+				/* optional argument is known to be xml */
+				Assert(list_length(xexpr->args) == 1);
+
+				if (argnull[0])
+					return;
+				value = argvalue[0];
+
+				*op->resvalue =
+					BoolGetDatum(xml_is_document(DatumGetXmlP(value)));
+				*op->resnull = false;
+			}
+			break;
+
+		default:
+			elog(ERROR, "unrecognized XML operation");
+			break;
+	}
+}
+
+/*
+ * ExecEvalGroupingFunc
+ *
+ * Computes a bitmask with a bit for each (unevaluated) argument expression
+ * (rightmost arg is least significant bit).
+ *
+ * A bit is set if the corresponding expression is NOT part of the set of
+ * grouping expressions in the current grouping set.
+ */
+void
+ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op)
+{
+	int			result = 0;
+	Bitmapset  *grouped_cols = op->d.grouping_func.parent->grouped_cols;
+	ListCell   *lc;
+
+	foreach(lc, op->d.grouping_func.clauses)
+	{
+		int			attnum = lfirst_int(lc);
+
+		result <<= 1;
+
+		if (!bms_is_member(attnum, grouped_cols))
+			result |= 1;
+	}
+
+	*op->resvalue = Int32GetDatum(result);
+	*op->resnull = false;
+}
+
+/*
+ * Hand off evaluation of a subplan to nodeSubplan.c
+ */
+void
+ExecEvalSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	SubPlanState *sstate = op->d.subplan.sstate;
+
+	/* could potentially be nested, so make sure there's enough stack */
+	check_stack_depth();
+
+	*op->resvalue = ExecSubPlan(sstate, econtext, op->resnull);
+}
+
+/*
+ * Hand off evaluation of an alternative subplan to nodeSubplan.c
+ */
+void
+ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	AlternativeSubPlanState *asstate = op->d.alternative_subplan.asstate;
+
+	/* could potentially be nested, so make sure there's enough stack */
+	check_stack_depth();
+
+	*op->resvalue = ExecAlternativeSubPlan(asstate, econtext, op->resnull);
+}
+
+/*
+ * Evaluate a wholerow Var expression.
+ *
+ * Returns a Datum whose value is the value of a whole-row range variable
+ * with respect to given expression context.
+ */
+void
+ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	Var		   *variable = op->d.wholerow.var;
+	TupleTableSlot *slot;
+	TupleDesc	output_tupdesc;
+	MemoryContext oldcontext;
+	HeapTupleHeader dtuple;
+	HeapTuple	tuple;
+
+	/* This was checked by ExecInitExpr */
+	Assert(variable->varattno == InvalidAttrNumber);
+
+	/* Get the input slot we want */
+	switch (variable->varno)
+	{
+		case INNER_VAR:
+			/* get the tuple from the inner node */
+			slot = econtext->ecxt_innertuple;
+			break;
+
+		case OUTER_VAR:
+			/* get the tuple from the outer node */
+			slot = econtext->ecxt_outertuple;
+			break;
+
+			/* INDEX_VAR is handled by default case */
+
+		default:
+			/* get the tuple from the relation being scanned */
+			slot = econtext->ecxt_scantuple;
+			break;
+	}
+
+	/* Apply the junkfilter if any */
+	if (op->d.wholerow.junkFilter != NULL)
+		slot = ExecFilterJunk(op->d.wholerow.junkFilter, slot);
+
+	/*
+	 * If first time through, obtain tuple descriptor and check compatibility.
+	 *
+	 * XXX: It'd be great if this could be moved to the expression
+	 * initialization phase, but due to using slots that's currently not
+	 * feasible.
+	 */
+	if (op->d.wholerow.first)
+	{
+		/* optimistically assume we don't need slow path */
+		op->d.wholerow.slow = false;
+
+		/*
+		 * If the Var identifies a named composite type, we must check that
+		 * the actual tuple type is compatible with it.
+		 */
+		if (variable->vartype != RECORDOID)
+		{
+			TupleDesc	var_tupdesc;
+			TupleDesc	slot_tupdesc;
+			int			i;
+
+			/*
+			 * We really only care about numbers of attributes and data types.
+			 * Also, we can ignore type mismatch on columns that are dropped
+			 * in the destination type, so long as (1) the physical storage
+			 * matches or (2) the actual column value is NULL.  Case (1) is
+			 * helpful in some cases involving out-of-date cached plans, while
+			 * case (2) is expected behavior in situations such as an INSERT
+			 * into a table with dropped columns (the planner typically
+			 * generates an INT4 NULL regardless of the dropped column type).
+			 * If we find a dropped column and cannot verify that case (1)
+			 * holds, we have to use the slow path to check (2) for each row.
+			 */
+			var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
+
+			slot_tupdesc = slot->tts_tupleDescriptor;
+
+			if (var_tupdesc->natts != slot_tupdesc->natts)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("table row type and query-specified row type do not match"),
+						 errdetail_plural("Table row contains %d attribute, but query expects %d.",
+				   "Table row contains %d attributes, but query expects %d.",
+										  slot_tupdesc->natts,
+										  slot_tupdesc->natts,
+										  var_tupdesc->natts)));
+
+			for (i = 0; i < var_tupdesc->natts; i++)
+			{
+				Form_pg_attribute vattr = var_tupdesc->attrs[i];
+				Form_pg_attribute sattr = slot_tupdesc->attrs[i];
+
+				if (vattr->atttypid == sattr->atttypid)
+					continue;	/* no worries */
+				if (!vattr->attisdropped)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("table row type and query-specified row type do not match"),
+							 errdetail("Table has type %s at ordinal position %d, but query expects %s.",
+									   format_type_be(sattr->atttypid),
+									   i + 1,
+									   format_type_be(vattr->atttypid))));
+
+				if (vattr->attlen != sattr->attlen ||
+					vattr->attalign != sattr->attalign)
+					op->d.wholerow.slow = true; /* need to check for nulls */
+			}
+
+			/*
+			 * Use the variable's declared rowtype as the descriptor for the
+			 * output values, modulo possibly assigning new column names
+			 * below. In particular, we *must* absorb any attisdropped
+			 * markings.
+			 */
+			oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+			output_tupdesc = CreateTupleDescCopy(var_tupdesc);
+			MemoryContextSwitchTo(oldcontext);
+
+			ReleaseTupleDesc(var_tupdesc);
+		}
+		else
+		{
+			/*
+			 * In the RECORD case, we use the input slot's rowtype as the
+			 * descriptor for the output values, modulo possibly assigning new
+			 * column names below.
+			 */
+			oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+			output_tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor);
+			MemoryContextSwitchTo(oldcontext);
+		}
+
+		/*
+		 * Construct a tuple descriptor for the composite values we'll
+		 * produce, and make sure its record type is "blessed".  The main
+		 * reason to do this is to be sure that operations such as
+		 * row_to_json() will see the desired column names when they look up
+		 * the descriptor from the type information embedded in the composite
+		 * values.
+		 *
+		 * We already got the correct physical datatype info above, but now we
+		 * should try to find the source RTE and adopt its column aliases, in
+		 * case they are different from the original rowtype's names.  For
+		 * example, in "SELECT foo(t) FROM tab t(x,y)", the first two columns
+		 * in the composite output should be named "x" and "y" regardless of
+		 * tab's column names.
+		 *
+		 * If we can't locate the RTE, assume the column names we've got are
+		 * OK.  (As of this writing, the only cases where we can't locate the
+		 * RTE are in execution of trigger WHEN clauses, and then the Var will
+		 * have the trigger's relation's rowtype, so its names are fine.)
+		 * Also, if the creator of the RTE didn't bother to fill in an eref
+		 * field, assume our column names are OK.  (This happens in COPY, and
+		 * perhaps other places.)
+		 */
+		if (econtext->ecxt_estate &&
+		variable->varno <= list_length(econtext->ecxt_estate->es_range_table))
+		{
+			RangeTblEntry *rte = rt_fetch(variable->varno,
+									  econtext->ecxt_estate->es_range_table);
+
+			if (rte->eref)
+				ExecTypeSetColNames(output_tupdesc, rte->eref->colnames);
+		}
+
+		/* Bless the tupdesc if needed, and save it in the execution state */
+		op->d.wholerow.tupdesc = BlessTupleDesc(output_tupdesc);
+
+		op->d.wholerow.first = false;
+	}
+
+	/*
+	 * Make sure all columns of the slot are accessible in the slot's
+	 * Datum/isnull arrays.
+	 */
+	slot_getallattrs(slot);
+
+	if (op->d.wholerow.slow)
+	{
+		/* Check to see if any dropped attributes are non-null */
+		TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
+		TupleDesc	var_tupdesc = op->d.wholerow.tupdesc;
+		int			i;
+
+		Assert(var_tupdesc->natts == tupleDesc->natts);
+
+		for (i = 0; i < var_tupdesc->natts; i++)
+		{
+			Form_pg_attribute vattr = var_tupdesc->attrs[i];
+			Form_pg_attribute sattr = tupleDesc->attrs[i];
+
+			if (!vattr->attisdropped)
+				continue;		/* already checked non-dropped cols */
+			if (slot->tts_isnull[i])
+				continue;		/* null is always okay */
+			if (vattr->attlen != sattr->attlen ||
+				vattr->attalign != sattr->attalign)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("table row type and query-specified row type do not match"),
+						 errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
+								   i + 1)));
+		}
+	}
+
+	/*
+	 * Copy the slot tuple and make sure any toasted fields get detoasted.
+	 *
+	 * (The intermediate copy is a tad annoying here, but we currently have no
+	 * primitive that will do the right thing.  Note it is critical that we
+	 * not change the slot's state, so we can't use ExecFetchSlotTupleDatum.)
+	 */
+	tuple = ExecCopySlotTuple(slot);
+	dtuple = (HeapTupleHeader)
+		DatumGetPointer(heap_copy_tuple_as_datum(tuple,
+												 slot->tts_tupleDescriptor));
+	heap_freetuple(tuple);
+
+	/*
+	 * Label the datum with the composite type info we identified before.
+	 */
+	HeapTupleHeaderSetTypeId(dtuple, op->d.wholerow.tupdesc->tdtypeid);
+	HeapTupleHeaderSetTypMod(dtuple, op->d.wholerow.tupdesc->tdtypmod);
+
+	*op->resnull = false;
+	*op->resvalue = PointerGetDatum(dtuple);
+}
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 5242dee00644725ba4eb45727f1d6ad11ec84269..108060ac0f17c810a7c33113ccd7a35626fdde4f 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -327,23 +327,21 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 		/* Check for partial index */
 		if (indexInfo->ii_Predicate != NIL)
 		{
-			List	   *predicate;
+			ExprState  *predicate;
 
 			/*
 			 * If predicate state not set up yet, create it (in the estate's
 			 * per-query context)
 			 */
 			predicate = indexInfo->ii_PredicateState;
-			if (predicate == NIL)
+			if (predicate == NULL)
 			{
-				predicate = (List *)
-					ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
-									estate);
+				predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
 				indexInfo->ii_PredicateState = predicate;
 			}
 
 			/* Skip this index-update if the predicate isn't satisfied */
-			if (!ExecQual(predicate, econtext, false))
+			if (!ExecQual(predicate, econtext))
 				continue;
 		}
 
@@ -551,23 +549,21 @@ ExecCheckIndexConstraints(TupleTableSlot *slot,
 		/* Check for partial index */
 		if (indexInfo->ii_Predicate != NIL)
 		{
-			List	   *predicate;
+			ExprState  *predicate;
 
 			/*
 			 * If predicate state not set up yet, create it (in the estate's
 			 * per-query context)
 			 */
 			predicate = indexInfo->ii_PredicateState;
-			if (predicate == NIL)
+			if (predicate == NULL)
 			{
-				predicate = (List *)
-					ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
-									estate);
+				predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
 				indexInfo->ii_PredicateState = predicate;
 			}
 
 			/* Skip this index-update if the predicate isn't satisfied */
-			if (!ExecQual(predicate, econtext, false))
+			if (!ExecQual(predicate, econtext))
 				continue;
 		}
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index c28cf9c8eab7f2b678918f62a29429c40648e4d3..f2995f2e7ba2242eb1c7e8246a09745fe53757c1 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -600,8 +600,8 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
 
 	/*
 	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
-	 * checked by init_fcache when the function is prepared for execution.
-	 * Join, subquery, and special RTEs need no checks.
+	 * checked when the function is prepared for execution.  Join, subquery,
+	 * and special RTEs need no checks.
 	 */
 	if (rte->rtekind != RTE_RELATION)
 		return true;
@@ -1275,8 +1275,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 
 		resultRelInfo->ri_TrigFunctions = (FmgrInfo *)
 			palloc0(n * sizeof(FmgrInfo));
-		resultRelInfo->ri_TrigWhenExprs = (List **)
-			palloc0(n * sizeof(List *));
+		resultRelInfo->ri_TrigWhenExprs = (ExprState **)
+			palloc0(n * sizeof(ExprState *));
 		if (instrument_options)
 			resultRelInfo->ri_TrigInstrument = InstrAlloc(n, instrument_options);
 	}
@@ -1723,7 +1723,6 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	ConstrCheck *check = rel->rd_att->constr->check;
 	ExprContext *econtext;
 	MemoryContext oldContext;
-	List	   *qual;
 	int			i;
 
 	/*
@@ -1735,13 +1734,14 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	{
 		oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
 		resultRelInfo->ri_ConstraintExprs =
-			(List **) palloc(ncheck * sizeof(List *));
+			(ExprState **) palloc(ncheck * sizeof(ExprState *));
 		for (i = 0; i < ncheck; i++)
 		{
-			/* ExecQual wants implicit-AND form */
-			qual = make_ands_implicit(stringToNode(check[i].ccbin));
-			resultRelInfo->ri_ConstraintExprs[i] = (List *)
-				ExecPrepareExpr((Expr *) qual, estate);
+			Expr	   *checkconstr;
+
+			checkconstr = stringToNode(check[i].ccbin);
+			resultRelInfo->ri_ConstraintExprs[i] =
+				ExecPrepareExpr(checkconstr, estate);
 		}
 		MemoryContextSwitchTo(oldContext);
 	}
@@ -1758,14 +1758,14 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	/* And evaluate the constraints */
 	for (i = 0; i < ncheck; i++)
 	{
-		qual = resultRelInfo->ri_ConstraintExprs[i];
+		ExprState  *checkconstr = resultRelInfo->ri_ConstraintExprs[i];
 
 		/*
 		 * NOTE: SQL specifies that a NULL result from a constraint expression
-		 * is not to be treated as a failure.  Therefore, tell ExecQual to
-		 * return TRUE for NULL.
+		 * is not to be treated as a failure.  Therefore, use ExecCheck not
+		 * ExecQual.
 		 */
-		if (!ExecQual(qual, econtext, true))
+		if (!ExecCheck(checkconstr, econtext))
 			return check[i].ccname;
 	}
 
@@ -1793,8 +1793,7 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 	{
 		List	   *qual = resultRelInfo->ri_PartitionCheck;
 
-		resultRelInfo->ri_PartitionCheckExpr = (List *)
-			ExecPrepareExpr((Expr *) qual, estate);
+		resultRelInfo->ri_PartitionCheckExpr = ExecPrepareCheck(qual, estate);
 	}
 
 	/*
@@ -1810,7 +1809,7 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 	 * As in case of the catalogued constraints, we treat a NULL result as
 	 * success here, not a failure.
 	 */
-	return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+	return ExecCheck(resultRelInfo->ri_PartitionCheckExpr, econtext);
 }
 
 /*
@@ -1990,11 +1989,9 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 		 * is visible (in the case of a view) or that it passes the
 		 * 'with-check' policy (in the case of row security). If the qual
 		 * evaluates to NULL or FALSE, then the new tuple won't be included in
-		 * the view or doesn't pass the 'with-check' policy for the table.  We
-		 * need ExecQual to return FALSE for NULL to handle the view case (the
-		 * opposite of what we do above for CHECK constraints).
+		 * the view or doesn't pass the 'with-check' policy for the table.
 		 */
-		if (!ExecQual((List *) wcoExpr, econtext, false))
+		if (!ExecQual(wcoExpr, econtext))
 		{
 			char	   *val_desc;
 			Bitmapset  *modifiedCols;
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 90bef6f01f04af338fdc8fe9518fd079c23385dd..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -1,5313 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * execQual.c
- *	  Routines to evaluate qualification and targetlist expressions
- *
- * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- *
- * IDENTIFICATION
- *	  src/backend/executor/execQual.c
- *
- *-------------------------------------------------------------------------
- */
-/*
- *	 INTERFACE ROUTINES
- *		ExecEvalExpr	- (now a macro) evaluate an expression, return a datum
- *		ExecEvalExprSwitchContext - same, but switch into eval memory context
- *		ExecQual		- return true/false if qualification is satisfied
- *		ExecProject		- form a new tuple by projecting the given tuple
- *
- *	 NOTES
- *		The more heavily used ExecEvalExpr routines, such as ExecEvalScalarVar,
- *		are hotspots. Making these faster will speed up the entire system.
- *
- *		ExecProject() is used to make tuple projections.  Rather then
- *		trying to speed it up, the execution plan should be pre-processed
- *		to facilitate attribute sharing between nodes wherever possible,
- *		instead of doing needless copying.  -cim 5/31/91
- *
- *		During expression evaluation, we check_stack_depth only in
- *		ExecMakeFunctionResultSet/ExecMakeFunctionResultNoSets rather than at
- *		every single node.  This is a compromise that trades off precision of
- *		the stack limit setting to gain speed.
- */
-
-#include "postgres.h"
-
-#include "access/htup_details.h"
-#include "access/nbtree.h"
-#include "access/tupconvert.h"
-#include "catalog/objectaccess.h"
-#include "catalog/pg_type.h"
-#include "executor/execdebug.h"
-#include "executor/nodeSubplan.h"
-#include "funcapi.h"
-#include "miscadmin.h"
-#include "nodes/makefuncs.h"
-#include "nodes/nodeFuncs.h"
-#include "optimizer/planner.h"
-#include "parser/parse_coerce.h"
-#include "parser/parsetree.h"
-#include "pgstat.h"
-#include "utils/acl.h"
-#include "utils/builtins.h"
-#include "utils/date.h"
-#include "utils/lsyscache.h"
-#include "utils/memutils.h"
-#include "utils/timestamp.h"
-#include "utils/typcache.h"
-#include "utils/xml.h"
-
-
-/* static function decls */
-static Datum ExecEvalArrayRef(ArrayRefExprState *astate,
-				 ExprContext *econtext,
-				 bool *isNull);
-static bool isAssignmentIndirectionExpr(ExprState *exprstate);
-static Datum ExecEvalAggref(AggrefExprState *aggref,
-			   ExprContext *econtext,
-			   bool *isNull);
-static Datum ExecEvalWindowFunc(WindowFuncExprState *wfunc,
-				   ExprContext *econtext,
-				   bool *isNull);
-static Datum ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
-				  bool *isNull);
-static Datum ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext,
-					  bool *isNull);
-static Datum ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate,
-					ExprContext *econtext,
-					bool *isNull);
-static Datum ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate,
-					 ExprContext *econtext,
-					 bool *isNull);
-static Datum ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate,
-					 ExprContext *econtext,
-					 bool *isNull);
-static Datum ExecEvalConst(ExprState *exprstate, ExprContext *econtext,
-			  bool *isNull);
-static Datum ExecEvalParamExec(ExprState *exprstate, ExprContext *econtext,
-				  bool *isNull);
-static Datum ExecEvalParamExtern(ExprState *exprstate, ExprContext *econtext,
-					bool *isNull);
-static void init_fcache(Oid foid, Oid input_collation, FuncExprState *fcache,
-			MemoryContext fcacheCxt, bool allowSRF, bool needDescForSRF);
-static void ShutdownFuncExpr(Datum arg);
-static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod,
-				   TupleDesc *cache_field, ExprContext *econtext);
-static void ShutdownTupleDescRef(Datum arg);
-static void ExecEvalFuncArgs(FunctionCallInfo fcinfo,
-				 List *argList, ExprContext *econtext);
-static void ExecPrepareTuplestoreResult(FuncExprState *fcache,
-							ExprContext *econtext,
-							Tuplestorestate *resultStore,
-							TupleDesc resultDesc);
-static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc);
-static Datum ExecMakeFunctionResultNoSets(FuncExprState *fcache,
-							 ExprContext *econtext,
-							 bool *isNull);
-static Datum ExecEvalFunc(FuncExprState *fcache, ExprContext *econtext,
-			 bool *isNull);
-static Datum ExecEvalOper(FuncExprState *fcache, ExprContext *econtext,
-			 bool *isNull);
-static Datum ExecEvalDistinct(FuncExprState *fcache, ExprContext *econtext,
-				 bool *isNull);
-static Datum ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate,
-					  ExprContext *econtext,
-					  bool *isNull);
-static Datum ExecEvalNot(BoolExprState *notclause, ExprContext *econtext,
-			bool *isNull);
-static Datum ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext,
-		   bool *isNull);
-static Datum ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext,
-			bool *isNull);
-static Datum ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
-					   ExprContext *econtext,
-					   bool *isNull);
-static Datum ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
-			 bool *isNull);
-static Datum ExecEvalCaseTestExpr(ExprState *exprstate,
-					 ExprContext *econtext,
-					 bool *isNull);
-static Datum ExecEvalArray(ArrayExprState *astate,
-			  ExprContext *econtext,
-			  bool *isNull);
-static Datum ExecEvalRow(RowExprState *rstate,
-			ExprContext *econtext,
-			bool *isNull);
-static Datum ExecEvalRowCompare(RowCompareExprState *rstate,
-				   ExprContext *econtext,
-				   bool *isNull);
-static Datum ExecEvalCoalesce(CoalesceExprState *coalesceExpr,
-				 ExprContext *econtext,
-				 bool *isNull);
-static Datum ExecEvalMinMax(MinMaxExprState *minmaxExpr,
-			   ExprContext *econtext,
-			   bool *isNull);
-static Datum ExecEvalSQLValueFunction(ExprState *svfExpr,
-						 ExprContext *econtext,
-						 bool *isNull);
-static Datum ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext,
-			bool *isNull);
-static Datum ExecEvalNullIf(FuncExprState *nullIfExpr,
-			   ExprContext *econtext,
-			   bool *isNull);
-static Datum ExecEvalNullTest(NullTestState *nstate,
-				 ExprContext *econtext,
-				 bool *isNull);
-static Datum ExecEvalBooleanTest(GenericExprState *bstate,
-					ExprContext *econtext,
-					bool *isNull);
-static Datum ExecEvalCoerceToDomain(CoerceToDomainState *cstate,
-					   ExprContext *econtext,
-					   bool *isNull);
-static Datum ExecEvalCoerceToDomainValue(ExprState *exprstate,
-							ExprContext *econtext,
-							bool *isNull);
-static Datum ExecEvalFieldSelect(FieldSelectState *fstate,
-					ExprContext *econtext,
-					bool *isNull);
-static Datum ExecEvalFieldStore(FieldStoreState *fstate,
-				   ExprContext *econtext,
-				   bool *isNull);
-static Datum ExecEvalRelabelType(GenericExprState *exprstate,
-					ExprContext *econtext,
-					bool *isNull);
-static Datum ExecEvalCoerceViaIO(CoerceViaIOState *iostate,
-					ExprContext *econtext,
-					bool *isNull);
-static Datum ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
-						ExprContext *econtext,
-						bool *isNull);
-static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
-					  bool *isNull);
-static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
-						 ExprContext *econtext,
-						 bool *isNull);
-
-
-/* ----------------------------------------------------------------
- *		ExecEvalExpr routines
- *
- *		Recursively evaluate a targetlist or qualification expression.
- *
- * Each of the following routines having the signature
- *		Datum ExecEvalFoo(ExprState *expression,
- *						  ExprContext *econtext,
- *						  bool *isNull);
- * is responsible for evaluating one type or subtype of ExprState node.
- * They are normally called via the ExecEvalExpr macro, which makes use of
- * the function pointer set up when the ExprState node was built by
- * ExecInitExpr.  (In some cases, we change this pointer later to avoid
- * re-executing one-time overhead.)
- *
- * Note: for notational simplicity we declare these functions as taking the
- * specific type of ExprState that they work on.  This requires casting when
- * assigning the function pointer in ExecInitExpr.  Be careful that the
- * function signature is declared correctly, because the cast suppresses
- * automatic checking!
- *
- *
- * All these functions share this calling convention:
- *
- * Inputs:
- *		expression: the expression state 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
- *
- * 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 in these routines
- * because it'd be a waste of cycles during nested expression evaluation.
- * ----------------------------------------------------------------
- */
-
-
-/*----------
- *	  ExecEvalArrayRef
- *
- *	   This function takes an ArrayRef and returns the extracted Datum
- *	   if it's a simple reference, or the modified array value if it's
- *	   an array assignment (i.e., array element or slice insertion).
- *
- * NOTE: if we get a NULL result from a subscript expression, we return NULL
- * when it's an array reference, or raise an error when it's an assignment.
- *----------
- */
-static Datum
-ExecEvalArrayRef(ArrayRefExprState *astate,
-				 ExprContext *econtext,
-				 bool *isNull)
-{
-	ArrayRef   *arrayRef = (ArrayRef *) astate->xprstate.expr;
-	Datum		array_source;
-	bool		isAssignment = (arrayRef->refassgnexpr != NULL);
-	bool		eisnull;
-	ListCell   *l;
-	int			i = 0,
-				j = 0;
-	IntArray	upper,
-				lower;
-	bool		upperProvided[MAXDIM],
-				lowerProvided[MAXDIM];
-	int		   *lIndex;
-
-	array_source = ExecEvalExpr(astate->refexpr,
-								econtext,
-								isNull);
-
-	/*
-	 * If refexpr yields NULL, and it's a fetch, then result is NULL. In the
-	 * assignment case, we'll cons up something below.
-	 */
-	if (*isNull)
-	{
-		if (!isAssignment)
-			return (Datum) NULL;
-	}
-
-	foreach(l, astate->refupperindexpr)
-	{
-		ExprState  *eltstate = (ExprState *) lfirst(l);
-
-		if (i >= MAXDIM)
-			ereport(ERROR,
-					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-					 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
-							i + 1, MAXDIM)));
-
-		if (eltstate == NULL)
-		{
-			/* Slice bound is omitted, so use array's upper bound */
-			Assert(astate->reflowerindexpr != NIL);
-			upperProvided[i++] = false;
-			continue;
-		}
-		upperProvided[i] = true;
-
-		upper.indx[i++] = DatumGetInt32(ExecEvalExpr(eltstate,
-													 econtext,
-													 &eisnull));
-		/* If any index expr yields NULL, result is NULL or error */
-		if (eisnull)
-		{
-			if (isAssignment)
-				ereport(ERROR,
-						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-				  errmsg("array subscript in assignment must not be null")));
-			*isNull = true;
-			return (Datum) NULL;
-		}
-	}
-
-	if (astate->reflowerindexpr != NIL)
-	{
-		foreach(l, astate->reflowerindexpr)
-		{
-			ExprState  *eltstate = (ExprState *) lfirst(l);
-
-			if (j >= MAXDIM)
-				ereport(ERROR,
-						(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-						 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
-								j + 1, MAXDIM)));
-
-			if (eltstate == NULL)
-			{
-				/* Slice bound is omitted, so use array's lower bound */
-				lowerProvided[j++] = false;
-				continue;
-			}
-			lowerProvided[j] = true;
-
-			lower.indx[j++] = DatumGetInt32(ExecEvalExpr(eltstate,
-														 econtext,
-														 &eisnull));
-			/* If any index expr yields NULL, result is NULL or error */
-			if (eisnull)
-			{
-				if (isAssignment)
-					ereport(ERROR,
-							(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-							 errmsg("array subscript in assignment must not be null")));
-				*isNull = true;
-				return (Datum) NULL;
-			}
-		}
-		/* this can't happen unless parser messed up */
-		if (i != j)
-			elog(ERROR, "upper and lower index lists are not same length");
-		lIndex = lower.indx;
-	}
-	else
-		lIndex = NULL;
-
-	if (isAssignment)
-	{
-		Datum		sourceData;
-		Datum		save_datum;
-		bool		save_isNull;
-
-		/*
-		 * We might have a nested-assignment situation, in which the
-		 * refassgnexpr is itself a FieldStore or ArrayRef that needs to
-		 * obtain and modify the previous value of the array element or slice
-		 * being replaced.  If so, we have to extract that value from the
-		 * array and pass it down via the econtext's caseValue.  It's safe to
-		 * reuse the CASE mechanism because there cannot be a CASE between
-		 * here and where the value would be needed, and an array assignment
-		 * can't be within a CASE either.  (So saving and restoring the
-		 * caseValue is just paranoia, but let's do it anyway.)
-		 *
-		 * Since fetching the old element might be a nontrivial expense, do it
-		 * only if the argument appears to actually need it.
-		 */
-		save_datum = econtext->caseValue_datum;
-		save_isNull = econtext->caseValue_isNull;
-
-		if (isAssignmentIndirectionExpr(astate->refassgnexpr))
-		{
-			if (*isNull)
-			{
-				/* whole array is null, so any element or slice is too */
-				econtext->caseValue_datum = (Datum) 0;
-				econtext->caseValue_isNull = true;
-			}
-			else if (lIndex == NULL)
-			{
-				econtext->caseValue_datum =
-					array_get_element(array_source, i,
-									  upper.indx,
-									  astate->refattrlength,
-									  astate->refelemlength,
-									  astate->refelembyval,
-									  astate->refelemalign,
-									  &econtext->caseValue_isNull);
-			}
-			else
-			{
-				/* this is currently unreachable */
-				econtext->caseValue_datum =
-					array_get_slice(array_source, i,
-									upper.indx, lower.indx,
-									upperProvided, lowerProvided,
-									astate->refattrlength,
-									astate->refelemlength,
-									astate->refelembyval,
-									astate->refelemalign);
-				econtext->caseValue_isNull = false;
-			}
-		}
-		else
-		{
-			/* argument shouldn't need caseValue, but for safety set it null */
-			econtext->caseValue_datum = (Datum) 0;
-			econtext->caseValue_isNull = true;
-		}
-
-		/*
-		 * Evaluate the value to be assigned into the array.
-		 */
-		sourceData = ExecEvalExpr(astate->refassgnexpr,
-								  econtext,
-								  &eisnull);
-
-		econtext->caseValue_datum = save_datum;
-		econtext->caseValue_isNull = save_isNull;
-
-		/*
-		 * For an assignment to a fixed-length array type, both the original
-		 * array and the value to be assigned into it must be non-NULL, else
-		 * we punt and return the original array.
-		 */
-		if (astate->refattrlength > 0)	/* fixed-length array? */
-			if (eisnull || *isNull)
-				return array_source;
-
-		/*
-		 * For assignment to varlena arrays, we handle a NULL original array
-		 * by substituting an empty (zero-dimensional) array; insertion of the
-		 * new element will result in a singleton array value.  It does not
-		 * matter whether the new element is NULL.
-		 */
-		if (*isNull)
-		{
-			array_source = PointerGetDatum(construct_empty_array(arrayRef->refelemtype));
-			*isNull = false;
-		}
-
-		if (lIndex == NULL)
-			return array_set_element(array_source, i,
-									 upper.indx,
-									 sourceData,
-									 eisnull,
-									 astate->refattrlength,
-									 astate->refelemlength,
-									 astate->refelembyval,
-									 astate->refelemalign);
-		else
-			return array_set_slice(array_source, i,
-								   upper.indx, lower.indx,
-								   upperProvided, lowerProvided,
-								   sourceData,
-								   eisnull,
-								   astate->refattrlength,
-								   astate->refelemlength,
-								   astate->refelembyval,
-								   astate->refelemalign);
-	}
-
-	if (lIndex == NULL)
-		return array_get_element(array_source, i,
-								 upper.indx,
-								 astate->refattrlength,
-								 astate->refelemlength,
-								 astate->refelembyval,
-								 astate->refelemalign,
-								 isNull);
-	else
-		return array_get_slice(array_source, i,
-							   upper.indx, lower.indx,
-							   upperProvided, lowerProvided,
-							   astate->refattrlength,
-							   astate->refelemlength,
-							   astate->refelembyval,
-							   astate->refelemalign);
-}
-
-/*
- * Helper for ExecEvalArrayRef: is expr a nested FieldStore or ArrayRef
- * that might need the old element value passed down?
- *
- * (We could use this in ExecEvalFieldStore too, but in that case passing
- * the old value is so cheap there's no need.)
- */
-static bool
-isAssignmentIndirectionExpr(ExprState *exprstate)
-{
-	if (exprstate == NULL)
-		return false;			/* just paranoia */
-	if (IsA(exprstate, FieldStoreState))
-	{
-		FieldStore *fstore = (FieldStore *) exprstate->expr;
-
-		if (fstore->arg && IsA(fstore->arg, CaseTestExpr))
-			return true;
-	}
-	else if (IsA(exprstate, ArrayRefExprState))
-	{
-		ArrayRef   *arrayRef = (ArrayRef *) exprstate->expr;
-
-		if (arrayRef->refexpr && IsA(arrayRef->refexpr, CaseTestExpr))
-			return true;
-	}
-	return false;
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalAggref
- *
- *		Returns a Datum whose value is the value of the precomputed
- *		aggregate found in the given expression context.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext,
-			   bool *isNull)
-{
-	if (econtext->ecxt_aggvalues == NULL)		/* safety check */
-		elog(ERROR, "no aggregates in this expression context");
-
-	*isNull = econtext->ecxt_aggnulls[aggref->aggno];
-	return econtext->ecxt_aggvalues[aggref->aggno];
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalWindowFunc
- *
- *		Returns a Datum whose value is the value of the precomputed
- *		window function found in the given expression context.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalWindowFunc(WindowFuncExprState *wfunc, ExprContext *econtext,
-				   bool *isNull)
-{
-	if (econtext->ecxt_aggvalues == NULL)		/* safety check */
-		elog(ERROR, "no window functions in this expression context");
-
-	*isNull = econtext->ecxt_aggnulls[wfunc->wfuncno];
-	return econtext->ecxt_aggvalues[wfunc->wfuncno];
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalScalarVar
- *
- *		Returns a Datum whose value is the value of a scalar (not whole-row)
- *		range variable with respect to given expression context.
- *
- * Note: ExecEvalScalarVar is executed only the first time through in a given
- * plan; it changes the ExprState's function pointer to pass control directly
- * to ExecEvalScalarVarFast after making one-time checks.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
-				  bool *isNull)
-{
-	Var		   *variable = (Var *) exprstate->expr;
-	TupleTableSlot *slot;
-	AttrNumber	attnum;
-
-	/* Get the input slot and attribute number we want */
-	switch (variable->varno)
-	{
-		case INNER_VAR: /* get the tuple from the inner node */
-			slot = econtext->ecxt_innertuple;
-			break;
-
-		case OUTER_VAR: /* get the tuple from the outer node */
-			slot = econtext->ecxt_outertuple;
-			break;
-
-			/* INDEX_VAR is handled by default case */
-
-		default:				/* get the tuple from the relation being
-								 * scanned */
-			slot = econtext->ecxt_scantuple;
-			break;
-	}
-
-	attnum = variable->varattno;
-
-	/* This was checked by ExecInitExpr */
-	Assert(attnum != InvalidAttrNumber);
-
-	/*
-	 * If it's a user attribute, check validity (bogus system attnums will be
-	 * caught inside slot_getattr).  What we have to check for here is the
-	 * possibility of an attribute having been changed in type since the plan
-	 * tree was created.  Ideally the plan will get invalidated and not
-	 * re-used, but just in case, we keep these defenses.  Fortunately it's
-	 * sufficient to check once on the first time through.
-	 *
-	 * Note: we allow a reference to a dropped attribute.  slot_getattr will
-	 * force a NULL result in such cases.
-	 *
-	 * Note: ideally we'd check typmod as well as typid, but that seems
-	 * impractical at the moment: in many cases the tupdesc will have been
-	 * generated by ExecTypeFromTL(), and that can't guarantee to generate an
-	 * accurate typmod in all cases, because some expression node types don't
-	 * carry typmod.
-	 */
-	if (attnum > 0)
-	{
-		TupleDesc	slot_tupdesc = slot->tts_tupleDescriptor;
-		Form_pg_attribute attr;
-
-		if (attnum > slot_tupdesc->natts)		/* should never happen */
-			elog(ERROR, "attribute number %d exceeds number of columns %d",
-				 attnum, slot_tupdesc->natts);
-
-		attr = slot_tupdesc->attrs[attnum - 1];
-
-		/* can't check type if dropped, since atttypid is probably 0 */
-		if (!attr->attisdropped)
-		{
-			if (variable->vartype != attr->atttypid)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("attribute %d has wrong type", attnum),
-						 errdetail("Table has type %s, but query expects %s.",
-								   format_type_be(attr->atttypid),
-								   format_type_be(variable->vartype))));
-		}
-	}
-
-	/* Skip the checking on future executions of node */
-	exprstate->evalfunc = ExecEvalScalarVarFast;
-
-	/* Fetch the value from the slot */
-	return slot_getattr(slot, attnum, isNull);
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalScalarVarFast
- *
- *		Returns a Datum for a scalar variable.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext,
-					  bool *isNull)
-{
-	Var		   *variable = (Var *) exprstate->expr;
-	TupleTableSlot *slot;
-	AttrNumber	attnum;
-
-	/* Get the input slot and attribute number we want */
-	switch (variable->varno)
-	{
-		case INNER_VAR: /* get the tuple from the inner node */
-			slot = econtext->ecxt_innertuple;
-			break;
-
-		case OUTER_VAR: /* get the tuple from the outer node */
-			slot = econtext->ecxt_outertuple;
-			break;
-
-			/* INDEX_VAR is handled by default case */
-
-		default:				/* get the tuple from the relation being
-								 * scanned */
-			slot = econtext->ecxt_scantuple;
-			break;
-	}
-
-	attnum = variable->varattno;
-
-	/* Fetch the value from the slot */
-	return slot_getattr(slot, attnum, isNull);
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalWholeRowVar
- *
- *		Returns a Datum whose value is the value of a whole-row range
- *		variable with respect to given expression context.
- *
- * Note: ExecEvalWholeRowVar is executed only the first time through in a
- * given plan; it changes the ExprState's function pointer to pass control
- * directly to ExecEvalWholeRowFast or ExecEvalWholeRowSlow after making
- * one-time checks.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
-					bool *isNull)
-{
-	Var		   *variable = (Var *) wrvstate->xprstate.expr;
-	TupleTableSlot *slot;
-	TupleDesc	output_tupdesc;
-	MemoryContext oldcontext;
-	bool		needslow = false;
-
-	/* This was checked by ExecInitExpr */
-	Assert(variable->varattno == InvalidAttrNumber);
-
-	/* Get the input slot we want */
-	switch (variable->varno)
-	{
-		case INNER_VAR: /* get the tuple from the inner node */
-			slot = econtext->ecxt_innertuple;
-			break;
-
-		case OUTER_VAR: /* get the tuple from the outer node */
-			slot = econtext->ecxt_outertuple;
-			break;
-
-			/* INDEX_VAR is handled by default case */
-
-		default:				/* get the tuple from the relation being
-								 * scanned */
-			slot = econtext->ecxt_scantuple;
-			break;
-	}
-
-	/*
-	 * If the input tuple came from a subquery, it might contain "resjunk"
-	 * columns (such as GROUP BY or ORDER BY columns), which we don't want to
-	 * keep in the whole-row result.  We can get rid of such columns by
-	 * passing the tuple through a JunkFilter --- but to make one, we have to
-	 * lay our hands on the subquery's targetlist.  Fortunately, there are not
-	 * very many cases where this can happen, and we can identify all of them
-	 * by examining our parent PlanState.  We assume this is not an issue in
-	 * standalone expressions that don't have parent plans.  (Whole-row Vars
-	 * can occur in such expressions, but they will always be referencing
-	 * table rows.)
-	 */
-	if (wrvstate->parent)
-	{
-		PlanState  *subplan = NULL;
-
-		switch (nodeTag(wrvstate->parent))
-		{
-			case T_SubqueryScanState:
-				subplan = ((SubqueryScanState *) wrvstate->parent)->subplan;
-				break;
-			case T_CteScanState:
-				subplan = ((CteScanState *) wrvstate->parent)->cteplanstate;
-				break;
-			default:
-				break;
-		}
-
-		if (subplan)
-		{
-			bool		junk_filter_needed = false;
-			ListCell   *tlist;
-
-			/* Detect whether subplan tlist actually has any junk columns */
-			foreach(tlist, subplan->plan->targetlist)
-			{
-				TargetEntry *tle = (TargetEntry *) lfirst(tlist);
-
-				if (tle->resjunk)
-				{
-					junk_filter_needed = true;
-					break;
-				}
-			}
-
-			/* If so, build the junkfilter in the query memory context */
-			if (junk_filter_needed)
-			{
-				oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-				wrvstate->wrv_junkFilter =
-					ExecInitJunkFilter(subplan->plan->targetlist,
-									   ExecGetResultType(subplan)->tdhasoid,
-							ExecInitExtraTupleSlot(wrvstate->parent->state));
-				MemoryContextSwitchTo(oldcontext);
-			}
-		}
-	}
-
-	/* Apply the junkfilter if any */
-	if (wrvstate->wrv_junkFilter != NULL)
-		slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
-
-	/*
-	 * If the Var identifies a named composite type, we must check that the
-	 * actual tuple type is compatible with it.
-	 */
-	if (variable->vartype != RECORDOID)
-	{
-		TupleDesc	var_tupdesc;
-		TupleDesc	slot_tupdesc;
-		int			i;
-
-		/*
-		 * We really only care about numbers of attributes and data types.
-		 * Also, we can ignore type mismatch on columns that are dropped in
-		 * the destination type, so long as (1) the physical storage matches
-		 * or (2) the actual column value is NULL.  Case (1) is helpful in
-		 * some cases involving out-of-date cached plans, while case (2) is
-		 * expected behavior in situations such as an INSERT into a table with
-		 * dropped columns (the planner typically generates an INT4 NULL
-		 * regardless of the dropped column type).  If we find a dropped
-		 * column and cannot verify that case (1) holds, we have to use
-		 * ExecEvalWholeRowSlow to check (2) for each row.
-		 */
-		var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
-
-		slot_tupdesc = slot->tts_tupleDescriptor;
-
-		if (var_tupdesc->natts != slot_tupdesc->natts)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("table row type and query-specified row type do not match"),
-					 errdetail_plural("Table row contains %d attribute, but query expects %d.",
-				   "Table row contains %d attributes, but query expects %d.",
-									  slot_tupdesc->natts,
-									  slot_tupdesc->natts,
-									  var_tupdesc->natts)));
-
-		for (i = 0; i < var_tupdesc->natts; i++)
-		{
-			Form_pg_attribute vattr = var_tupdesc->attrs[i];
-			Form_pg_attribute sattr = slot_tupdesc->attrs[i];
-
-			if (vattr->atttypid == sattr->atttypid)
-				continue;		/* no worries */
-			if (!vattr->attisdropped)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("table row type and query-specified row type do not match"),
-						 errdetail("Table has type %s at ordinal position %d, but query expects %s.",
-								   format_type_be(sattr->atttypid),
-								   i + 1,
-								   format_type_be(vattr->atttypid))));
-
-			if (vattr->attlen != sattr->attlen ||
-				vattr->attalign != sattr->attalign)
-				needslow = true;	/* need runtime check for null */
-		}
-
-		/*
-		 * Use the variable's declared rowtype as the descriptor for the
-		 * output values, modulo possibly assigning new column names below. In
-		 * particular, we *must* absorb any attisdropped markings.
-		 */
-		oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-		output_tupdesc = CreateTupleDescCopy(var_tupdesc);
-		MemoryContextSwitchTo(oldcontext);
-
-		ReleaseTupleDesc(var_tupdesc);
-	}
-	else
-	{
-		/*
-		 * In the RECORD case, we use the input slot's rowtype as the
-		 * descriptor for the output values, modulo possibly assigning new
-		 * column names below.
-		 */
-		oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-		output_tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor);
-		MemoryContextSwitchTo(oldcontext);
-	}
-
-	/*
-	 * Construct a tuple descriptor for the composite values we'll produce,
-	 * and make sure its record type is "blessed".  The main reason to do this
-	 * is to be sure that operations such as row_to_json() will see the
-	 * desired column names when they look up the descriptor from the type
-	 * information embedded in the composite values.
-	 *
-	 * We already got the correct physical datatype info above, but now we
-	 * should try to find the source RTE and adopt its column aliases, in case
-	 * they are different from the original rowtype's names.  For example, in
-	 * "SELECT foo(t) FROM tab t(x,y)", the first two columns in the composite
-	 * output should be named "x" and "y" regardless of tab's column names.
-	 *
-	 * If we can't locate the RTE, assume the column names we've got are OK.
-	 * (As of this writing, the only cases where we can't locate the RTE are
-	 * in execution of trigger WHEN clauses, and then the Var will have the
-	 * trigger's relation's rowtype, so its names are fine.)  Also, if the
-	 * creator of the RTE didn't bother to fill in an eref field, assume our
-	 * column names are OK.  (This happens in COPY, and perhaps other places.)
-	 */
-	if (econtext->ecxt_estate &&
-		variable->varno <= list_length(econtext->ecxt_estate->es_range_table))
-	{
-		RangeTblEntry *rte = rt_fetch(variable->varno,
-									  econtext->ecxt_estate->es_range_table);
-
-		if (rte->eref)
-			ExecTypeSetColNames(output_tupdesc, rte->eref->colnames);
-	}
-
-	/* Bless the tupdesc if needed, and save it in the execution state */
-	wrvstate->wrv_tupdesc = BlessTupleDesc(output_tupdesc);
-
-	/* Skip all the above on future executions of node */
-	if (needslow)
-		wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowSlow;
-	else
-		wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowFast;
-
-	/* Fetch the value */
-	return (*wrvstate->xprstate.evalfunc) ((ExprState *) wrvstate, econtext,
-										   isNull);
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalWholeRowFast
- *
- *		Returns a Datum for a whole-row variable.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
-					 bool *isNull)
-{
-	Var		   *variable = (Var *) wrvstate->xprstate.expr;
-	TupleTableSlot *slot;
-	HeapTupleHeader dtuple;
-
-	*isNull = false;
-
-	/* Get the input slot we want */
-	switch (variable->varno)
-	{
-		case INNER_VAR: /* get the tuple from the inner node */
-			slot = econtext->ecxt_innertuple;
-			break;
-
-		case OUTER_VAR: /* get the tuple from the outer node */
-			slot = econtext->ecxt_outertuple;
-			break;
-
-			/* INDEX_VAR is handled by default case */
-
-		default:				/* get the tuple from the relation being
-								 * scanned */
-			slot = econtext->ecxt_scantuple;
-			break;
-	}
-
-	/* Apply the junkfilter if any */
-	if (wrvstate->wrv_junkFilter != NULL)
-		slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
-
-	/*
-	 * Copy the slot tuple and make sure any toasted fields get detoasted.
-	 */
-	dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot));
-
-	/*
-	 * Label the datum with the composite type info we identified before.
-	 */
-	HeapTupleHeaderSetTypeId(dtuple, wrvstate->wrv_tupdesc->tdtypeid);
-	HeapTupleHeaderSetTypMod(dtuple, wrvstate->wrv_tupdesc->tdtypmod);
-
-	return PointerGetDatum(dtuple);
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalWholeRowSlow
- *
- *		Returns a Datum for a whole-row variable, in the "slow" case where
- *		we can't just copy the subplan's output.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,
-					 bool *isNull)
-{
-	Var		   *variable = (Var *) wrvstate->xprstate.expr;
-	TupleTableSlot *slot;
-	HeapTuple	tuple;
-	TupleDesc	tupleDesc;
-	TupleDesc	var_tupdesc;
-	HeapTupleHeader dtuple;
-	int			i;
-
-	*isNull = false;
-
-	/* Get the input slot we want */
-	switch (variable->varno)
-	{
-		case INNER_VAR: /* get the tuple from the inner node */
-			slot = econtext->ecxt_innertuple;
-			break;
-
-		case OUTER_VAR: /* get the tuple from the outer node */
-			slot = econtext->ecxt_outertuple;
-			break;
-
-			/* INDEX_VAR is handled by default case */
-
-		default:				/* get the tuple from the relation being
-								 * scanned */
-			slot = econtext->ecxt_scantuple;
-			break;
-	}
-
-	/* Apply the junkfilter if any */
-	if (wrvstate->wrv_junkFilter != NULL)
-		slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
-
-	tuple = ExecFetchSlotTuple(slot);
-	tupleDesc = slot->tts_tupleDescriptor;
-
-	/* wrv_tupdesc is a good enough representation of the Var's rowtype */
-	Assert(variable->vartype != RECORDOID);
-	var_tupdesc = wrvstate->wrv_tupdesc;
-
-	/* Check to see if any dropped attributes are non-null */
-	for (i = 0; i < var_tupdesc->natts; i++)
-	{
-		Form_pg_attribute vattr = var_tupdesc->attrs[i];
-		Form_pg_attribute sattr = tupleDesc->attrs[i];
-
-		if (!vattr->attisdropped)
-			continue;			/* already checked non-dropped cols */
-		if (heap_attisnull(tuple, i + 1))
-			continue;			/* null is always okay */
-		if (vattr->attlen != sattr->attlen ||
-			vattr->attalign != sattr->attalign)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("table row type and query-specified row type do not match"),
-					 errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
-							   i + 1)));
-	}
-
-	/*
-	 * Copy the slot tuple and make sure any toasted fields get detoasted.
-	 */
-	dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot));
-
-	/*
-	 * Label the datum with the composite type info we identified before.
-	 */
-	HeapTupleHeaderSetTypeId(dtuple, wrvstate->wrv_tupdesc->tdtypeid);
-	HeapTupleHeaderSetTypMod(dtuple, wrvstate->wrv_tupdesc->tdtypmod);
-
-	return PointerGetDatum(dtuple);
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalConst
- *
- *		Returns the value of a constant.
- *
- *		Note that for pass-by-ref datatypes, we return a pointer to the
- *		actual constant node.  This is one of the reasons why functions
- *		must treat their input arguments as read-only.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalConst(ExprState *exprstate, ExprContext *econtext,
-			  bool *isNull)
-{
-	Const	   *con = (Const *) exprstate->expr;
-
-	*isNull = con->constisnull;
-	return con->constvalue;
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalParamExec
- *
- *		Returns the value of a PARAM_EXEC parameter.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalParamExec(ExprState *exprstate, ExprContext *econtext,
-				  bool *isNull)
-{
-	Param	   *expression = (Param *) exprstate->expr;
-	int			thisParamId = expression->paramid;
-	ParamExecData *prm;
-
-	/*
-	 * PARAM_EXEC params (internal executor parameters) are stored in the
-	 * ecxt_param_exec_vals array, and can be accessed by array index.
-	 */
-	prm = &(econtext->ecxt_param_exec_vals[thisParamId]);
-	if (prm->execPlan != NULL)
-	{
-		/* Parameter not evaluated yet, so go do it */
-		ExecSetParamPlan(prm->execPlan, econtext);
-		/* ExecSetParamPlan should have processed this param... */
-		Assert(prm->execPlan == NULL);
-	}
-	*isNull = prm->isnull;
-	return prm->value;
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalParamExtern
- *
- *		Returns the value of a PARAM_EXTERN parameter.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalParamExtern(ExprState *exprstate, ExprContext *econtext,
-					bool *isNull)
-{
-	Param	   *expression = (Param *) exprstate->expr;
-	int			thisParamId = expression->paramid;
-	ParamListInfo paramInfo = econtext->ecxt_param_list_info;
-
-	/*
-	 * PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
-	 */
-	if (paramInfo &&
-		thisParamId > 0 && thisParamId <= paramInfo->numParams)
-	{
-		ParamExternData *prm = &paramInfo->params[thisParamId - 1];
-
-		/* give hook a chance in case parameter is dynamic */
-		if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL)
-			(*paramInfo->paramFetch) (paramInfo, thisParamId);
-
-		if (OidIsValid(prm->ptype))
-		{
-			/* safety check in case hook did something unexpected */
-			if (prm->ptype != expression->paramtype)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
-								thisParamId,
-								format_type_be(prm->ptype),
-								format_type_be(expression->paramtype))));
-
-			*isNull = prm->isnull;
-			return prm->value;
-		}
-	}
-
-	ereport(ERROR,
-			(errcode(ERRCODE_UNDEFINED_OBJECT),
-			 errmsg("no value found for parameter %d", thisParamId)));
-	return (Datum) 0;			/* keep compiler quiet */
-}
-
-
-/* ----------------------------------------------------------------
- *		ExecEvalOper / ExecEvalFunc support routines
- * ----------------------------------------------------------------
- */
-
-/*
- *		GetAttributeByName
- *		GetAttributeByNum
- *
- *		These functions return the value of the requested attribute
- *		out of the given tuple Datum.
- *		C functions which take a tuple as an argument are expected
- *		to use these.  Ex: overpaid(EMP) might call GetAttributeByNum().
- *		Note: these are actually rather slow because they do a typcache
- *		lookup on each call.
- */
-Datum
-GetAttributeByNum(HeapTupleHeader tuple,
-				  AttrNumber attrno,
-				  bool *isNull)
-{
-	Datum		result;
-	Oid			tupType;
-	int32		tupTypmod;
-	TupleDesc	tupDesc;
-	HeapTupleData tmptup;
-
-	if (!AttributeNumberIsValid(attrno))
-		elog(ERROR, "invalid attribute number %d", attrno);
-
-	if (isNull == NULL)
-		elog(ERROR, "a NULL isNull pointer was passed");
-
-	if (tuple == NULL)
-	{
-		/* Kinda bogus but compatible with old behavior... */
-		*isNull = true;
-		return (Datum) 0;
-	}
-
-	tupType = HeapTupleHeaderGetTypeId(tuple);
-	tupTypmod = HeapTupleHeaderGetTypMod(tuple);
-	tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
-
-	/*
-	 * heap_getattr needs a HeapTuple not a bare HeapTupleHeader.  We set all
-	 * the fields in the struct just in case user tries to inspect system
-	 * columns.
-	 */
-	tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
-	ItemPointerSetInvalid(&(tmptup.t_self));
-	tmptup.t_tableOid = InvalidOid;
-	tmptup.t_data = tuple;
-
-	result = heap_getattr(&tmptup,
-						  attrno,
-						  tupDesc,
-						  isNull);
-
-	ReleaseTupleDesc(tupDesc);
-
-	return result;
-}
-
-Datum
-GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull)
-{
-	AttrNumber	attrno;
-	Datum		result;
-	Oid			tupType;
-	int32		tupTypmod;
-	TupleDesc	tupDesc;
-	HeapTupleData tmptup;
-	int			i;
-
-	if (attname == NULL)
-		elog(ERROR, "invalid attribute name");
-
-	if (isNull == NULL)
-		elog(ERROR, "a NULL isNull pointer was passed");
-
-	if (tuple == NULL)
-	{
-		/* Kinda bogus but compatible with old behavior... */
-		*isNull = true;
-		return (Datum) 0;
-	}
-
-	tupType = HeapTupleHeaderGetTypeId(tuple);
-	tupTypmod = HeapTupleHeaderGetTypMod(tuple);
-	tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
-
-	attrno = InvalidAttrNumber;
-	for (i = 0; i < tupDesc->natts; i++)
-	{
-		if (namestrcmp(&(tupDesc->attrs[i]->attname), attname) == 0)
-		{
-			attrno = tupDesc->attrs[i]->attnum;
-			break;
-		}
-	}
-
-	if (attrno == InvalidAttrNumber)
-		elog(ERROR, "attribute \"%s\" does not exist", attname);
-
-	/*
-	 * heap_getattr needs a HeapTuple not a bare HeapTupleHeader.  We set all
-	 * the fields in the struct just in case user tries to inspect system
-	 * columns.
-	 */
-	tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
-	ItemPointerSetInvalid(&(tmptup.t_self));
-	tmptup.t_tableOid = InvalidOid;
-	tmptup.t_data = tuple;
-
-	result = heap_getattr(&tmptup,
-						  attrno,
-						  tupDesc,
-						  isNull);
-
-	ReleaseTupleDesc(tupDesc);
-
-	return result;
-}
-
-/*
- * init_fcache - initialize a FuncExprState node during first use
- */
-static void
-init_fcache(Oid foid, Oid input_collation, FuncExprState *fcache,
-			MemoryContext fcacheCxt, bool allowSRF, bool needDescForSRF)
-{
-	AclResult	aclresult;
-
-	/* Check permission to call function */
-	aclresult = pg_proc_aclcheck(foid, GetUserId(), ACL_EXECUTE);
-	if (aclresult != ACLCHECK_OK)
-		aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(foid));
-	InvokeFunctionExecuteHook(foid);
-
-	/*
-	 * Safety check on nargs.  Under normal circumstances this should never
-	 * fail, as parser should check sooner.  But possibly it might fail if
-	 * server has been compiled with FUNC_MAX_ARGS smaller than some functions
-	 * declared in pg_proc?
-	 */
-	if (list_length(fcache->args) > FUNC_MAX_ARGS)
-		ereport(ERROR,
-				(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
-			 errmsg_plural("cannot pass more than %d argument to a function",
-						   "cannot pass more than %d arguments to a function",
-						   FUNC_MAX_ARGS,
-						   FUNC_MAX_ARGS)));
-
-	/* Set up the primary fmgr lookup information */
-	fmgr_info_cxt(foid, &(fcache->func), fcacheCxt);
-	fmgr_info_set_expr((Node *) fcache->xprstate.expr, &(fcache->func));
-
-	/* Initialize the function call parameter struct as well */
-	InitFunctionCallInfoData(fcache->fcinfo_data, &(fcache->func),
-							 list_length(fcache->args),
-							 input_collation, NULL, NULL);
-
-	/* If function returns set, check if that's allowed by caller */
-	if (fcache->func.fn_retset && !allowSRF)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-
-	/* Otherwise, ExecInitExpr should have marked the fcache correctly */
-	Assert(fcache->func.fn_retset == fcache->funcReturnsSet);
-
-	/* If function returns set, prepare expected tuple descriptor */
-	if (fcache->func.fn_retset && needDescForSRF)
-	{
-		TypeFuncClass functypclass;
-		Oid			funcrettype;
-		TupleDesc	tupdesc;
-		MemoryContext oldcontext;
-
-		functypclass = get_expr_result_type(fcache->func.fn_expr,
-											&funcrettype,
-											&tupdesc);
-
-		/* Must save tupdesc in fcache's context */
-		oldcontext = MemoryContextSwitchTo(fcacheCxt);
-
-		if (functypclass == TYPEFUNC_COMPOSITE)
-		{
-			/* Composite data type, e.g. a table's row type */
-			Assert(tupdesc);
-			/* Must copy it out of typcache for safety */
-			fcache->funcResultDesc = CreateTupleDescCopy(tupdesc);
-			fcache->funcReturnsTuple = true;
-		}
-		else if (functypclass == TYPEFUNC_SCALAR)
-		{
-			/* Base data type, i.e. scalar */
-			tupdesc = CreateTemplateTupleDesc(1, false);
-			TupleDescInitEntry(tupdesc,
-							   (AttrNumber) 1,
-							   NULL,
-							   funcrettype,
-							   -1,
-							   0);
-			fcache->funcResultDesc = tupdesc;
-			fcache->funcReturnsTuple = false;
-		}
-		else if (functypclass == TYPEFUNC_RECORD)
-		{
-			/* This will work if function doesn't need an expectedDesc */
-			fcache->funcResultDesc = NULL;
-			fcache->funcReturnsTuple = true;
-		}
-		else
-		{
-			/* Else, we will fail if function needs an expectedDesc */
-			fcache->funcResultDesc = NULL;
-		}
-
-		MemoryContextSwitchTo(oldcontext);
-	}
-	else
-		fcache->funcResultDesc = NULL;
-
-	/* Initialize additional state */
-	fcache->funcResultStore = NULL;
-	fcache->funcResultSlot = NULL;
-	fcache->shutdown_reg = false;
-}
-
-/*
- * callback function in case a FuncExpr returning a set needs to be shut down
- * before it has been run to completion
- */
-static void
-ShutdownFuncExpr(Datum arg)
-{
-	FuncExprState *fcache = (FuncExprState *) DatumGetPointer(arg);
-
-	/* If we have a slot, make sure it's let go of any tuplestore pointer */
-	if (fcache->funcResultSlot)
-		ExecClearTuple(fcache->funcResultSlot);
-
-	/* Release any open tuplestore */
-	if (fcache->funcResultStore)
-		tuplestore_end(fcache->funcResultStore);
-	fcache->funcResultStore = NULL;
-
-	/* Clear any active set-argument state */
-	fcache->setArgsValid = false;
-
-	/* execUtils will deregister the callback... */
-	fcache->shutdown_reg = false;
-}
-
-/*
- * get_cached_rowtype: utility function to lookup a rowtype tupdesc
- *
- * type_id, typmod: identity of the rowtype
- * cache_field: where to cache the TupleDesc pointer in expression state node
- *		(field must be initialized to NULL)
- * econtext: expression context we are executing in
- *
- * NOTE: because the shutdown callback will be called during plan rescan,
- * must be prepared to re-do this during any node execution; cannot call
- * just once during expression initialization
- */
-static TupleDesc
-get_cached_rowtype(Oid type_id, int32 typmod,
-				   TupleDesc *cache_field, ExprContext *econtext)
-{
-	TupleDesc	tupDesc = *cache_field;
-
-	/* Do lookup if no cached value or if requested type changed */
-	if (tupDesc == NULL ||
-		type_id != tupDesc->tdtypeid ||
-		typmod != tupDesc->tdtypmod)
-	{
-		tupDesc = lookup_rowtype_tupdesc(type_id, typmod);
-
-		if (*cache_field)
-		{
-			/* Release old tupdesc; but callback is already registered */
-			ReleaseTupleDesc(*cache_field);
-		}
-		else
-		{
-			/* Need to register shutdown callback to release tupdesc */
-			RegisterExprContextCallback(econtext,
-										ShutdownTupleDescRef,
-										PointerGetDatum(cache_field));
-		}
-		*cache_field = tupDesc;
-	}
-	return tupDesc;
-}
-
-/*
- * Callback function to release a tupdesc refcount at expression tree shutdown
- */
-static void
-ShutdownTupleDescRef(Datum arg)
-{
-	TupleDesc  *cache_field = (TupleDesc *) DatumGetPointer(arg);
-
-	if (*cache_field)
-		ReleaseTupleDesc(*cache_field);
-	*cache_field = NULL;
-}
-
-/*
- * Evaluate arguments for a function.
- */
-static void
-ExecEvalFuncArgs(FunctionCallInfo fcinfo,
-				 List *argList,
-				 ExprContext *econtext)
-{
-	int			i;
-	ListCell   *arg;
-
-	i = 0;
-	foreach(arg, argList)
-	{
-		ExprState  *argstate = (ExprState *) lfirst(arg);
-
-		fcinfo->arg[i] = ExecEvalExpr(argstate,
-									  econtext,
-									  &fcinfo->argnull[i]);
-		i++;
-	}
-
-	Assert(i == fcinfo->nargs);
-}
-
-/*
- *		ExecPrepareTuplestoreResult
- *
- * Subroutine for ExecMakeFunctionResultSet: prepare to extract rows from a
- * tuplestore function result.  We must set up a funcResultSlot (unless
- * already done in a previous call cycle) and verify that the function
- * returned the expected tuple descriptor.
- */
-static void
-ExecPrepareTuplestoreResult(FuncExprState *fcache,
-							ExprContext *econtext,
-							Tuplestorestate *resultStore,
-							TupleDesc resultDesc)
-{
-	fcache->funcResultStore = resultStore;
-
-	if (fcache->funcResultSlot == NULL)
-	{
-		/* Create a slot so we can read data out of the tuplestore */
-		TupleDesc	slotDesc;
-		MemoryContext oldcontext;
-
-		oldcontext = MemoryContextSwitchTo(fcache->func.fn_mcxt);
-
-		/*
-		 * If we were not able to determine the result rowtype from context,
-		 * and the function didn't return a tupdesc, we have to fail.
-		 */
-		if (fcache->funcResultDesc)
-			slotDesc = fcache->funcResultDesc;
-		else if (resultDesc)
-		{
-			/* don't assume resultDesc is long-lived */
-			slotDesc = CreateTupleDescCopy(resultDesc);
-		}
-		else
-		{
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("function returning setof record called in "
-							"context that cannot accept type record")));
-			slotDesc = NULL;	/* keep compiler quiet */
-		}
-
-		fcache->funcResultSlot = MakeSingleTupleTableSlot(slotDesc);
-		MemoryContextSwitchTo(oldcontext);
-	}
-
-	/*
-	 * If function provided a tupdesc, cross-check it.  We only really need to
-	 * do this for functions returning RECORD, but might as well do it always.
-	 */
-	if (resultDesc)
-	{
-		if (fcache->funcResultDesc)
-			tupledesc_match(fcache->funcResultDesc, resultDesc);
-
-		/*
-		 * If it is a dynamically-allocated TupleDesc, free it: it is
-		 * typically allocated in a per-query context, so we must avoid
-		 * leaking it across multiple usages.
-		 */
-		if (resultDesc->tdrefcount == -1)
-			FreeTupleDesc(resultDesc);
-	}
-
-	/* Register cleanup callback if we didn't already */
-	if (!fcache->shutdown_reg)
-	{
-		RegisterExprContextCallback(econtext,
-									ShutdownFuncExpr,
-									PointerGetDatum(fcache));
-		fcache->shutdown_reg = true;
-	}
-}
-
-/*
- * Check that function result tuple type (src_tupdesc) matches or can
- * be considered to match what the query expects (dst_tupdesc). If
- * they don't match, ereport.
- *
- * We really only care about number of attributes and data type.
- * Also, we can ignore type mismatch on columns that are dropped in the
- * destination type, so long as the physical storage matches.  This is
- * helpful in some cases involving out-of-date cached plans.
- */
-static void
-tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc)
-{
-	int			i;
-
-	if (dst_tupdesc->natts != src_tupdesc->natts)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATATYPE_MISMATCH),
-				 errmsg("function return row and query-specified return row do not match"),
-				 errdetail_plural("Returned row contains %d attribute, but query expects %d.",
-				"Returned row contains %d attributes, but query expects %d.",
-								  src_tupdesc->natts,
-								  src_tupdesc->natts, dst_tupdesc->natts)));
-
-	for (i = 0; i < dst_tupdesc->natts; i++)
-	{
-		Form_pg_attribute dattr = dst_tupdesc->attrs[i];
-		Form_pg_attribute sattr = src_tupdesc->attrs[i];
-
-		if (IsBinaryCoercible(sattr->atttypid, dattr->atttypid))
-			continue;			/* no worries */
-		if (!dattr->attisdropped)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("function return row and query-specified return row do not match"),
-					 errdetail("Returned type %s at ordinal position %d, but query expects %s.",
-							   format_type_be(sattr->atttypid),
-							   i + 1,
-							   format_type_be(dattr->atttypid))));
-
-		if (dattr->attlen != sattr->attlen ||
-			dattr->attalign != sattr->attalign)
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("function return row and query-specified return row do not match"),
-					 errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
-							   i + 1)));
-	}
-}
-
-/*
- *		ExecMakeFunctionResultSet
- *
- * Evaluate the arguments to a set-returning function and then call the
- * function itself.  The argument expressions may not contain set-returning
- * functions (the planner is supposed to have separated evaluation for those).
- */
-Datum
-ExecMakeFunctionResultSet(FuncExprState *fcache,
-						  ExprContext *econtext,
-						  bool *isNull,
-						  ExprDoneCond *isDone)
-{
-	List	   *arguments;
-	Datum		result;
-	FunctionCallInfo fcinfo;
-	PgStat_FunctionCallUsage fcusage;
-	ReturnSetInfo rsinfo;
-	bool		callit;
-	int			i;
-
-restart:
-
-	/* Guard against stack overflow due to overly complex expressions */
-	check_stack_depth();
-
-	/*
-	 * Initialize function cache if first time through.  The expression node
-	 * could be either a FuncExpr or an OpExpr.
-	 */
-	if (fcache->func.fn_oid == InvalidOid)
-	{
-		if (IsA(fcache->xprstate.expr, FuncExpr))
-		{
-			FuncExpr   *func = (FuncExpr *) fcache->xprstate.expr;
-
-			init_fcache(func->funcid, func->inputcollid, fcache,
-						econtext->ecxt_per_query_memory, true, true);
-		}
-		else if (IsA(fcache->xprstate.expr, OpExpr))
-		{
-			OpExpr	   *op = (OpExpr *) fcache->xprstate.expr;
-
-			init_fcache(op->opfuncid, op->inputcollid, fcache,
-						econtext->ecxt_per_query_memory, true, true);
-		}
-		else
-			elog(ERROR, "unrecognized node type: %d",
-				 (int) nodeTag(fcache->xprstate.expr));
-
-		/* shouldn't get here otherwise */
-		Assert(fcache->func.fn_retset);
-	}
-
-	/*
-	 * If a previous call of the function returned a set result in the form of
-	 * a tuplestore, continue reading rows from the tuplestore until it's
-	 * empty.
-	 */
-	if (fcache->funcResultStore)
-	{
-		if (tuplestore_gettupleslot(fcache->funcResultStore, true, false,
-									fcache->funcResultSlot))
-		{
-			*isDone = ExprMultipleResult;
-			if (fcache->funcReturnsTuple)
-			{
-				/* We must return the whole tuple as a Datum. */
-				*isNull = false;
-				return ExecFetchSlotTupleDatum(fcache->funcResultSlot);
-			}
-			else
-			{
-				/* Extract the first column and return it as a scalar. */
-				return slot_getattr(fcache->funcResultSlot, 1, isNull);
-			}
-		}
-		/* Exhausted the tuplestore, so clean up */
-		tuplestore_end(fcache->funcResultStore);
-		fcache->funcResultStore = NULL;
-		*isDone = ExprEndResult;
-		*isNull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * arguments is a list of expressions to evaluate before passing to 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 fcinfo.
-	 */
-	fcinfo = &fcache->fcinfo_data;
-	arguments = fcache->args;
-	if (!fcache->setArgsValid)
-		ExecEvalFuncArgs(fcinfo, arguments, econtext);
-	else
-	{
-		/* Reset flag (we may set it again below) */
-		fcache->setArgsValid = false;
-	}
-
-	/*
-	 * Now call the function, passing the evaluated parameter values.
-	 */
-
-	/* Prepare a resultinfo node for communication. */
-	fcinfo->resultinfo = (Node *) &rsinfo;
-	rsinfo.type = T_ReturnSetInfo;
-	rsinfo.econtext = econtext;
-	rsinfo.expectedDesc = fcache->funcResultDesc;
-	rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
-	/* note we do not set SFRM_Materialize_Random or _Preferred */
-	rsinfo.returnMode = SFRM_ValuePerCall;
-	/* isDone is filled below */
-	rsinfo.setResult = NULL;
-	rsinfo.setDesc = NULL;
-
-	/*
-	 * If function is strict, and there are any NULL arguments, skip calling
-	 * the function.
-	 */
-	callit = true;
-	if (fcache->func.fn_strict)
-	{
-		for (i = 0; i < fcinfo->nargs; i++)
-		{
-			if (fcinfo->argnull[i])
-			{
-				callit = false;
-				break;
-			}
-		}
-	}
-
-	if (callit)
-	{
-		pgstat_init_function_usage(fcinfo, &fcusage);
-
-		fcinfo->isnull = false;
-		rsinfo.isDone = ExprSingleResult;
-		result = FunctionCallInvoke(fcinfo);
-		*isNull = fcinfo->isnull;
-		*isDone = rsinfo.isDone;
-
-		pgstat_end_function_usage(&fcusage,
-								  rsinfo.isDone != ExprMultipleResult);
-	}
-	else
-	{
-		/* for a strict SRF, result for NULL is an empty set */
-		result = (Datum) 0;
-		*isNull = true;
-		*isDone = ExprEndResult;
-	}
-
-	/* Which protocol does function want to use? */
-	if (rsinfo.returnMode == SFRM_ValuePerCall)
-	{
-		if (*isDone != ExprEndResult)
-		{
-			/*
-			 * Save the current argument values to re-use on the next call.
-			 */
-			if (*isDone == ExprMultipleResult)
-			{
-				fcache->setArgsValid = true;
-				/* Register cleanup callback if we didn't already */
-				if (!fcache->shutdown_reg)
-				{
-					RegisterExprContextCallback(econtext,
-												ShutdownFuncExpr,
-												PointerGetDatum(fcache));
-					fcache->shutdown_reg = true;
-				}
-			}
-		}
-	}
-	else if (rsinfo.returnMode == SFRM_Materialize)
-	{
-		/* check we're on the same page as the function author */
-		if (rsinfo.isDone != ExprSingleResult)
-			ereport(ERROR,
-					(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
-					 errmsg("table-function protocol for materialize mode was not followed")));
-		if (rsinfo.setResult != NULL)
-		{
-			/* prepare to return values from the tuplestore */
-			ExecPrepareTuplestoreResult(fcache, econtext,
-										rsinfo.setResult,
-										rsinfo.setDesc);
-			/* loop back to top to start returning from tuplestore */
-			goto restart;
-		}
-		/* if setResult was left null, treat it as empty set */
-		*isDone = ExprEndResult;
-		*isNull = true;
-		result = (Datum) 0;
-	}
-	else
-		ereport(ERROR,
-				(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
-				 errmsg("unrecognized table-function returnMode: %d",
-						(int) rsinfo.returnMode)));
-
-	return result;
-}
-
-/*
- *		ExecMakeFunctionResultNoSets
- *
- * Evaluate a function or operator node with a non-set-returning function.
- * Assumes init_fcache() already done.  Hand-tuned for speed.
- */
-static Datum
-ExecMakeFunctionResultNoSets(FuncExprState *fcache,
-							 ExprContext *econtext,
-							 bool *isNull)
-{
-	ListCell   *arg;
-	Datum		result;
-	FunctionCallInfo fcinfo;
-	PgStat_FunctionCallUsage fcusage;
-	int			i;
-
-	/* Guard against stack overflow due to overly complex expressions */
-	check_stack_depth();
-
-	/* inlined, simplified version of ExecEvalFuncArgs */
-	fcinfo = &fcache->fcinfo_data;
-	i = 0;
-	foreach(arg, fcache->args)
-	{
-		ExprState  *argstate = (ExprState *) lfirst(arg);
-
-		fcinfo->arg[i] = ExecEvalExpr(argstate,
-									  econtext,
-									  &fcinfo->argnull[i]);
-		i++;
-	}
-
-	/*
-	 * If function is strict, and there are any NULL arguments, skip calling
-	 * the function and return NULL.
-	 */
-	if (fcache->func.fn_strict)
-	{
-		while (--i >= 0)
-		{
-			if (fcinfo->argnull[i])
-			{
-				*isNull = true;
-				return (Datum) 0;
-			}
-		}
-	}
-
-	pgstat_init_function_usage(fcinfo, &fcusage);
-
-	fcinfo->isnull = false;
-	result = FunctionCallInvoke(fcinfo);
-	*isNull = fcinfo->isnull;
-
-	pgstat_end_function_usage(&fcusage, true);
-
-	return result;
-}
-
-
-/*
- *		ExecMakeTableFunctionResult
- *
- * Evaluate a table function, producing a materialized result in a Tuplestore
- * object.
- */
-Tuplestorestate *
-ExecMakeTableFunctionResult(ExprState *funcexpr,
-							ExprContext *econtext,
-							MemoryContext argContext,
-							TupleDesc expectedDesc,
-							bool randomAccess)
-{
-	Tuplestorestate *tupstore = NULL;
-	TupleDesc	tupdesc = NULL;
-	Oid			funcrettype;
-	bool		returnsTuple;
-	bool		returnsSet = false;
-	FunctionCallInfoData fcinfo;
-	PgStat_FunctionCallUsage fcusage;
-	ReturnSetInfo rsinfo;
-	HeapTupleData tmptup;
-	MemoryContext callerContext;
-	MemoryContext oldcontext;
-	bool		direct_function_call;
-	bool		first_time = true;
-
-	callerContext = CurrentMemoryContext;
-
-	funcrettype = exprType((Node *) funcexpr->expr);
-
-	returnsTuple = type_is_rowtype(funcrettype);
-
-	/*
-	 * Prepare a resultinfo node for communication.  We always do this even if
-	 * not expecting a set result, so that we can pass expectedDesc.  In the
-	 * generic-expression case, the expression doesn't actually get to see the
-	 * resultinfo, but set it up anyway because we use some of the fields as
-	 * our own state variables.
-	 */
-	rsinfo.type = T_ReturnSetInfo;
-	rsinfo.econtext = econtext;
-	rsinfo.expectedDesc = expectedDesc;
-	rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize | SFRM_Materialize_Preferred);
-	if (randomAccess)
-		rsinfo.allowedModes |= (int) SFRM_Materialize_Random;
-	rsinfo.returnMode = SFRM_ValuePerCall;
-	/* isDone is filled below */
-	rsinfo.setResult = NULL;
-	rsinfo.setDesc = NULL;
-
-	/*
-	 * Normally the passed expression tree will be a FuncExprState, since the
-	 * grammar only allows a function call at the top level of a table
-	 * function reference.  However, if the function doesn't return set then
-	 * the planner might have replaced the function call via constant-folding
-	 * or inlining.  So if we see any other kind of expression node, execute
-	 * it via the general ExecEvalExpr() code; the only difference is that we
-	 * don't get a chance to pass a special ReturnSetInfo to any functions
-	 * buried in the expression.
-	 */
-	if (funcexpr && IsA(funcexpr, FuncExprState) &&
-		IsA(funcexpr->expr, FuncExpr))
-	{
-		FuncExprState *fcache = (FuncExprState *) funcexpr;
-
-		/*
-		 * This path is similar to ExecMakeFunctionResultSet.
-		 */
-		direct_function_call = true;
-
-		/*
-		 * Initialize function cache if first time through
-		 */
-		if (fcache->func.fn_oid == InvalidOid)
-		{
-			FuncExpr   *func = (FuncExpr *) fcache->xprstate.expr;
-
-			init_fcache(func->funcid, func->inputcollid, fcache,
-						econtext->ecxt_per_query_memory, true, false);
-		}
-		returnsSet = fcache->func.fn_retset;
-		InitFunctionCallInfoData(fcinfo, &(fcache->func),
-								 list_length(fcache->args),
-								 fcache->fcinfo_data.fncollation,
-								 NULL, (Node *) &rsinfo);
-
-		/*
-		 * Evaluate the function's argument list.
-		 *
-		 * We can't do this in the per-tuple context: the argument values
-		 * would disappear when we reset that context in the inner loop.  And
-		 * the caller's CurrentMemoryContext is typically a query-lifespan
-		 * context, so we don't want to leak memory there.  We require the
-		 * caller to pass a separate memory context that can be used for this,
-		 * and can be reset each time through to avoid bloat.
-		 */
-		MemoryContextReset(argContext);
-		oldcontext = MemoryContextSwitchTo(argContext);
-		ExecEvalFuncArgs(&fcinfo, fcache->args, econtext);
-		MemoryContextSwitchTo(oldcontext);
-
-		/*
-		 * If function is strict, and there are any NULL arguments, skip
-		 * calling the function and act like it returned NULL (or an empty
-		 * set, in the returns-set case).
-		 */
-		if (fcache->func.fn_strict)
-		{
-			int			i;
-
-			for (i = 0; i < fcinfo.nargs; i++)
-			{
-				if (fcinfo.argnull[i])
-					goto no_function_result;
-			}
-		}
-	}
-	else
-	{
-		/* Treat funcexpr as a generic expression */
-		direct_function_call = false;
-		InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
-	}
-
-	/*
-	 * Switch to short-lived context for calling the function or expression.
-	 */
-	MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
-
-	/*
-	 * Loop to handle the ValuePerCall protocol (which is also the same
-	 * behavior needed in the generic ExecEvalExpr path).
-	 */
-	for (;;)
-	{
-		Datum		result;
-
-		CHECK_FOR_INTERRUPTS();
-
-		/*
-		 * reset per-tuple memory context before each call of the function or
-		 * expression. This cleans up any local memory the function may leak
-		 * when called.
-		 */
-		ResetExprContext(econtext);
-
-		/* Call the function or expression one time */
-		if (direct_function_call)
-		{
-			pgstat_init_function_usage(&fcinfo, &fcusage);
-
-			fcinfo.isnull = false;
-			rsinfo.isDone = ExprSingleResult;
-			result = FunctionCallInvoke(&fcinfo);
-
-			pgstat_end_function_usage(&fcusage,
-									  rsinfo.isDone != ExprMultipleResult);
-		}
-		else
-		{
-			result = ExecEvalExpr(funcexpr, econtext, &fcinfo.isnull);
-			rsinfo.isDone = ExprSingleResult;
-		}
-
-		/* Which protocol does function want to use? */
-		if (rsinfo.returnMode == SFRM_ValuePerCall)
-		{
-			/*
-			 * Check for end of result set.
-			 */
-			if (rsinfo.isDone == ExprEndResult)
-				break;
-
-			/*
-			 * If first time through, build tuplestore for result.  For a
-			 * scalar function result type, also make a suitable tupdesc.
-			 */
-			if (first_time)
-			{
-				oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-				tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-				rsinfo.setResult = tupstore;
-				if (!returnsTuple)
-				{
-					tupdesc = CreateTemplateTupleDesc(1, false);
-					TupleDescInitEntry(tupdesc,
-									   (AttrNumber) 1,
-									   "column",
-									   funcrettype,
-									   -1,
-									   0);
-					rsinfo.setDesc = tupdesc;
-				}
-				MemoryContextSwitchTo(oldcontext);
-			}
-
-			/*
-			 * Store current resultset item.
-			 */
-			if (returnsTuple)
-			{
-				if (!fcinfo.isnull)
-				{
-					HeapTupleHeader td = DatumGetHeapTupleHeader(result);
-
-					if (tupdesc == NULL)
-					{
-						/*
-						 * This is the first non-NULL result from the
-						 * function.  Use the type info embedded in the
-						 * rowtype Datum to look up the needed tupdesc.  Make
-						 * a copy for the query.
-						 */
-						oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-						tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td),
-											   HeapTupleHeaderGetTypMod(td));
-						rsinfo.setDesc = tupdesc;
-						MemoryContextSwitchTo(oldcontext);
-					}
-					else
-					{
-						/*
-						 * Verify all later returned rows have same subtype;
-						 * necessary in case the type is RECORD.
-						 */
-						if (HeapTupleHeaderGetTypeId(td) != tupdesc->tdtypeid ||
-							HeapTupleHeaderGetTypMod(td) != tupdesc->tdtypmod)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("rows returned by function are not all of the same row type")));
-					}
-
-					/*
-					 * tuplestore_puttuple needs a HeapTuple not a bare
-					 * HeapTupleHeader, but it doesn't need all the fields.
-					 */
-					tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
-					tmptup.t_data = td;
-
-					tuplestore_puttuple(tupstore, &tmptup);
-				}
-				else
-				{
-					/*
-					 * NULL result from a tuple-returning function; expand it
-					 * to a row of all nulls.  We rely on the expectedDesc to
-					 * form such rows.  (Note: this would be problematic if
-					 * tuplestore_putvalues saved the tdtypeid/tdtypmod from
-					 * the provided descriptor, since that might not match
-					 * what we get from the function itself.  But it doesn't.)
-					 */
-					int			natts = expectedDesc->natts;
-					bool	   *nullflags;
-
-					nullflags = (bool *) palloc(natts * sizeof(bool));
-					memset(nullflags, true, natts * sizeof(bool));
-					tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags);
-				}
-			}
-			else
-			{
-				/* Scalar-type case: just store the function result */
-				tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo.isnull);
-			}
-
-			/*
-			 * Are we done?
-			 */
-			if (rsinfo.isDone != ExprMultipleResult)
-				break;
-		}
-		else if (rsinfo.returnMode == SFRM_Materialize)
-		{
-			/* check we're on the same page as the function author */
-			if (!first_time || rsinfo.isDone != ExprSingleResult)
-				ereport(ERROR,
-						(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
-						 errmsg("table-function protocol for materialize mode was not followed")));
-			/* Done evaluating the set result */
-			break;
-		}
-		else
-			ereport(ERROR,
-					(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
-					 errmsg("unrecognized table-function returnMode: %d",
-							(int) rsinfo.returnMode)));
-
-		first_time = false;
-	}
-
-no_function_result:
-
-	/*
-	 * If we got nothing from the function (ie, an empty-set or NULL result),
-	 * we have to create the tuplestore to return, and if it's a
-	 * non-set-returning function then insert a single all-nulls row.  As
-	 * above, we depend on the expectedDesc to manufacture the dummy row.
-	 */
-	if (rsinfo.setResult == NULL)
-	{
-		MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-		tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
-		rsinfo.setResult = tupstore;
-		if (!returnsSet)
-		{
-			int			natts = expectedDesc->natts;
-			bool	   *nullflags;
-
-			MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
-			nullflags = (bool *) palloc(natts * sizeof(bool));
-			memset(nullflags, true, natts * sizeof(bool));
-			tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags);
-		}
-	}
-
-	/*
-	 * If function provided a tupdesc, cross-check it.  We only really need to
-	 * do this for functions returning RECORD, but might as well do it always.
-	 */
-	if (rsinfo.setDesc)
-	{
-		tupledesc_match(expectedDesc, rsinfo.setDesc);
-
-		/*
-		 * If it is a dynamically-allocated TupleDesc, free it: it is
-		 * typically allocated in a per-query context, so we must avoid
-		 * leaking it across multiple usages.
-		 */
-		if (rsinfo.setDesc->tdrefcount == -1)
-			FreeTupleDesc(rsinfo.setDesc);
-	}
-
-	MemoryContextSwitchTo(callerContext);
-
-	/* All done, pass back the tuplestore */
-	return rsinfo.setResult;
-}
-
-
-/* ----------------------------------------------------------------
- *		ExecEvalFunc
- *		ExecEvalOper
- *
- *		Evaluate the functional result of a list of arguments by calling the
- *		function manager.
- * ----------------------------------------------------------------
- */
-
-/* ----------------------------------------------------------------
- *		ExecEvalFunc
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalFunc(FuncExprState *fcache,
-			 ExprContext *econtext,
-			 bool *isNull)
-{
-	/* This is called only the first time through */
-	FuncExpr   *func = (FuncExpr *) fcache->xprstate.expr;
-
-	/* Initialize function lookup info */
-	init_fcache(func->funcid, func->inputcollid, fcache,
-				econtext->ecxt_per_query_memory, false, false);
-
-	/* Change the evalfunc pointer to save a few cycles in additional calls */
-	fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResultNoSets;
-	return ExecMakeFunctionResultNoSets(fcache, econtext, isNull);
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalOper
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalOper(FuncExprState *fcache,
-			 ExprContext *econtext,
-			 bool *isNull)
-{
-	/* This is called only the first time through */
-	OpExpr	   *op = (OpExpr *) fcache->xprstate.expr;
-
-	/* Initialize function lookup info */
-	init_fcache(op->opfuncid, op->inputcollid, fcache,
-				econtext->ecxt_per_query_memory, false, false);
-
-	/* Change the evalfunc pointer to save a few cycles in additional calls */
-	fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResultNoSets;
-	return ExecMakeFunctionResultNoSets(fcache, econtext, isNull);
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalDistinct
- *
- * IS DISTINCT FROM must evaluate arguments to determine whether
- * they are NULL; if either is NULL then the result is already
- * known. If neither is NULL, then proceed to evaluate the
- * function. Note that this is *always* derived from the equals
- * operator, but since we need special processing of the arguments
- * we can not simply reuse ExecEvalOper() or ExecEvalFunc().
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalDistinct(FuncExprState *fcache,
-				 ExprContext *econtext,
-				 bool *isNull)
-{
-	Datum		result;
-	FunctionCallInfo fcinfo;
-
-	/* Set non-null as default */
-	*isNull = false;
-
-	/*
-	 * Initialize function cache if first time through
-	 */
-	if (fcache->func.fn_oid == InvalidOid)
-	{
-		DistinctExpr *op = (DistinctExpr *) fcache->xprstate.expr;
-
-		init_fcache(op->opfuncid, op->inputcollid, fcache,
-					econtext->ecxt_per_query_memory, false, false);
-	}
-
-	/*
-	 * Evaluate arguments
-	 */
-	fcinfo = &fcache->fcinfo_data;
-	ExecEvalFuncArgs(fcinfo, fcache->args, econtext);
-	Assert(fcinfo->nargs == 2);
-
-	if (fcinfo->argnull[0] && fcinfo->argnull[1])
-	{
-		/* Both NULL? Then is not distinct... */
-		result = BoolGetDatum(FALSE);
-	}
-	else if (fcinfo->argnull[0] || fcinfo->argnull[1])
-	{
-		/* Only one is NULL? Then is distinct... */
-		result = BoolGetDatum(TRUE);
-	}
-	else
-	{
-		fcinfo->isnull = false;
-		result = FunctionCallInvoke(fcinfo);
-		*isNull = fcinfo->isnull;
-		/* Must invert result of "=" */
-		result = BoolGetDatum(!DatumGetBool(result));
-	}
-
-	return result;
-}
-
-/*
- * ExecEvalScalarArrayOp
- *
- * Evaluate "scalar op ANY/ALL (array)".  The operator always yields boolean,
- * and we combine the results across all array elements using OR and AND
- * (for ANY and ALL respectively).  Of course we short-circuit as soon as
- * the result is known.
- */
-static Datum
-ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate,
-					  ExprContext *econtext,
-					  bool *isNull)
-{
-	ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) sstate->fxprstate.xprstate.expr;
-	bool		useOr = opexpr->useOr;
-	ArrayType  *arr;
-	int			nitems;
-	Datum		result;
-	bool		resultnull;
-	FunctionCallInfo fcinfo;
-	int			i;
-	int16		typlen;
-	bool		typbyval;
-	char		typalign;
-	char	   *s;
-	bits8	   *bitmap;
-	int			bitmask;
-
-	/* Set non-null as default */
-	*isNull = false;
-
-	/*
-	 * Initialize function cache if first time through
-	 */
-	if (sstate->fxprstate.func.fn_oid == InvalidOid)
-	{
-		init_fcache(opexpr->opfuncid, opexpr->inputcollid, &sstate->fxprstate,
-					econtext->ecxt_per_query_memory, false, false);
-	}
-
-	/*
-	 * Evaluate arguments
-	 */
-	fcinfo = &sstate->fxprstate.fcinfo_data;
-	ExecEvalFuncArgs(fcinfo, sstate->fxprstate.args, econtext);
-	Assert(fcinfo->nargs == 2);
-
-	/*
-	 * If the array is NULL then we return NULL --- it's not very meaningful
-	 * to do anything else, even if the operator isn't strict.
-	 */
-	if (fcinfo->argnull[1])
-	{
-		*isNull = true;
-		return (Datum) 0;
-	}
-	/* Else okay to fetch and detoast the array */
-	arr = DatumGetArrayTypeP(fcinfo->arg[1]);
-
-	/*
-	 * If the array is empty, we return either FALSE or TRUE per the useOr
-	 * flag.  This is correct even if the scalar is NULL; since we would
-	 * evaluate the operator zero times, it matters not whether it would want
-	 * to return NULL.
-	 */
-	nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
-	if (nitems <= 0)
-		return BoolGetDatum(!useOr);
-
-	/*
-	 * If the scalar is NULL, and the function is strict, return NULL; no
-	 * point in iterating the loop.
-	 */
-	if (fcinfo->argnull[0] && sstate->fxprstate.func.fn_strict)
-	{
-		*isNull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * We arrange to look up info about the element type only once per series
-	 * of calls, assuming the element type doesn't change underneath us.
-	 */
-	if (sstate->element_type != ARR_ELEMTYPE(arr))
-	{
-		get_typlenbyvalalign(ARR_ELEMTYPE(arr),
-							 &sstate->typlen,
-							 &sstate->typbyval,
-							 &sstate->typalign);
-		sstate->element_type = ARR_ELEMTYPE(arr);
-	}
-	typlen = sstate->typlen;
-	typbyval = sstate->typbyval;
-	typalign = sstate->typalign;
-
-	result = BoolGetDatum(!useOr);
-	resultnull = false;
-
-	/* Loop over the array elements */
-	s = (char *) ARR_DATA_PTR(arr);
-	bitmap = ARR_NULLBITMAP(arr);
-	bitmask = 1;
-
-	for (i = 0; i < nitems; i++)
-	{
-		Datum		elt;
-		Datum		thisresult;
-
-		/* Get array element, checking for NULL */
-		if (bitmap && (*bitmap & bitmask) == 0)
-		{
-			fcinfo->arg[1] = (Datum) 0;
-			fcinfo->argnull[1] = true;
-		}
-		else
-		{
-			elt = fetch_att(s, typbyval, typlen);
-			s = att_addlength_pointer(s, typlen, s);
-			s = (char *) att_align_nominal(s, typalign);
-			fcinfo->arg[1] = elt;
-			fcinfo->argnull[1] = false;
-		}
-
-		/* Call comparison function */
-		if (fcinfo->argnull[1] && sstate->fxprstate.func.fn_strict)
-		{
-			fcinfo->isnull = true;
-			thisresult = (Datum) 0;
-		}
-		else
-		{
-			fcinfo->isnull = false;
-			thisresult = FunctionCallInvoke(fcinfo);
-		}
-
-		/* Combine results per OR or AND semantics */
-		if (fcinfo->isnull)
-			resultnull = true;
-		else if (useOr)
-		{
-			if (DatumGetBool(thisresult))
-			{
-				result = BoolGetDatum(true);
-				resultnull = false;
-				break;			/* needn't look at any more elements */
-			}
-		}
-		else
-		{
-			if (!DatumGetBool(thisresult))
-			{
-				result = BoolGetDatum(false);
-				resultnull = false;
-				break;			/* needn't look at any more elements */
-			}
-		}
-
-		/* advance bitmap pointer if any */
-		if (bitmap)
-		{
-			bitmask <<= 1;
-			if (bitmask == 0x100)
-			{
-				bitmap++;
-				bitmask = 1;
-			}
-		}
-	}
-
-	*isNull = resultnull;
-	return result;
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalNot
- *		ExecEvalOr
- *		ExecEvalAnd
- *
- *		Evaluate boolean expressions, with appropriate short-circuiting.
- *
- *		The query planner reformulates clause expressions in the
- *		qualification to conjunctive normal form.  If we ever get
- *		an AND to evaluate, we can be sure that it's not a top-level
- *		clause in the qualification, but appears lower (as a function
- *		argument, for example), or in the target list.  Not that you
- *		need to know this, mind you...
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalNot(BoolExprState *notclause, ExprContext *econtext,
-			bool *isNull)
-{
-	ExprState  *clause = linitial(notclause->args);
-	Datum		expr_value;
-
-	expr_value = ExecEvalExpr(clause, econtext, isNull);
-
-	/*
-	 * if the expression evaluates to null, then we just cascade the null back
-	 * to whoever called us.
-	 */
-	if (*isNull)
-		return expr_value;
-
-	/*
-	 * evaluation of 'not' is simple.. expr is false, then return 'true' and
-	 * vice versa.
-	 */
-	return BoolGetDatum(!DatumGetBool(expr_value));
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalOr
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext,
-		   bool *isNull)
-{
-	List	   *clauses = orExpr->args;
-	ListCell   *clause;
-	bool		AnyNull;
-
-	AnyNull = false;
-
-	/*
-	 * If any of the clauses is TRUE, the OR result is TRUE regardless of the
-	 * states of the rest of the clauses, so we can stop evaluating and return
-	 * TRUE immediately.  If none are TRUE and one or more is NULL, we return
-	 * NULL; otherwise we return FALSE.  This makes sense when you interpret
-	 * NULL as "don't know": if we have a TRUE then the OR is TRUE even if we
-	 * aren't sure about some of the other inputs. If all the known inputs are
-	 * FALSE, but we have one or more "don't knows", then we have to report
-	 * that we "don't know" what the OR's result should be --- perhaps one of
-	 * the "don't knows" would have been TRUE if we'd known its value.  Only
-	 * when all the inputs are known to be FALSE can we state confidently that
-	 * the OR's result is FALSE.
-	 */
-	foreach(clause, clauses)
-	{
-		ExprState  *clausestate = (ExprState *) lfirst(clause);
-		Datum		clause_value;
-
-		clause_value = ExecEvalExpr(clausestate, econtext, isNull);
-
-		/*
-		 * if we have a non-null true result, then return it.
-		 */
-		if (*isNull)
-			AnyNull = true;		/* remember we got a null */
-		else if (DatumGetBool(clause_value))
-			return clause_value;
-	}
-
-	/* AnyNull is true if at least one clause evaluated to NULL */
-	*isNull = AnyNull;
-	return BoolGetDatum(false);
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalAnd
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext,
-			bool *isNull)
-{
-	List	   *clauses = andExpr->args;
-	ListCell   *clause;
-	bool		AnyNull;
-
-	AnyNull = false;
-
-	/*
-	 * If any of the clauses is FALSE, the AND result is FALSE regardless of
-	 * the states of the rest of the clauses, so we can stop evaluating and
-	 * return FALSE immediately.  If none are FALSE and one or more is NULL,
-	 * we return NULL; otherwise we return TRUE.  This makes sense when you
-	 * interpret NULL as "don't know", using the same sort of reasoning as for
-	 * OR, above.
-	 */
-
-	foreach(clause, clauses)
-	{
-		ExprState  *clausestate = (ExprState *) lfirst(clause);
-		Datum		clause_value;
-
-		clause_value = ExecEvalExpr(clausestate, econtext, isNull);
-
-		/*
-		 * if we have a non-null false result, then return it.
-		 */
-		if (*isNull)
-			AnyNull = true;		/* remember we got a null */
-		else if (!DatumGetBool(clause_value))
-			return clause_value;
-	}
-
-	/* AnyNull is true if at least one clause evaluated to NULL */
-	*isNull = AnyNull;
-	return BoolGetDatum(!AnyNull);
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalConvertRowtype
- *
- *		Evaluate a rowtype coercion operation.  This may require
- *		rearranging field positions.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
-					   ExprContext *econtext,
-					   bool *isNull)
-{
-	ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) cstate->xprstate.expr;
-	HeapTuple	result;
-	Datum		tupDatum;
-	HeapTupleHeader tuple;
-	HeapTupleData tmptup;
-
-	tupDatum = ExecEvalExpr(cstate->arg, econtext, isNull);
-
-	/* this test covers the isDone exception too: */
-	if (*isNull)
-		return tupDatum;
-
-	tuple = DatumGetHeapTupleHeader(tupDatum);
-
-	/* Lookup tupdescs if first time through or after rescan */
-	if (cstate->indesc == NULL)
-	{
-		get_cached_rowtype(exprType((Node *) convert->arg), -1,
-						   &cstate->indesc, econtext);
-		cstate->initialized = false;
-	}
-	if (cstate->outdesc == NULL)
-	{
-		get_cached_rowtype(convert->resulttype, -1,
-						   &cstate->outdesc, econtext);
-		cstate->initialized = false;
-	}
-
-	/*
-	 * We used to be able to assert that incoming tuples are marked with
-	 * exactly the rowtype of cstate->indesc.  However, now that
-	 * ExecEvalWholeRowVar might change the tuples' marking to plain RECORD
-	 * due to inserting aliases, we can only make this weak test:
-	 */
-	Assert(HeapTupleHeaderGetTypeId(tuple) == cstate->indesc->tdtypeid ||
-		   HeapTupleHeaderGetTypeId(tuple) == RECORDOID);
-
-	/* if first time through, initialize conversion map */
-	if (!cstate->initialized)
-	{
-		MemoryContext old_cxt;
-
-		/* allocate map in long-lived memory context */
-		old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-
-		/* prepare map from old to new attribute numbers */
-		cstate->map = convert_tuples_by_name(cstate->indesc,
-											 cstate->outdesc,
-								 gettext_noop("could not convert row type"));
-		cstate->initialized = true;
-
-		MemoryContextSwitchTo(old_cxt);
-	}
-
-	/*
-	 * No-op if no conversion needed (not clear this can happen here).
-	 */
-	if (cstate->map == NULL)
-		return tupDatum;
-
-	/*
-	 * do_convert_tuple needs a HeapTuple not a bare HeapTupleHeader.
-	 */
-	tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
-	tmptup.t_data = tuple;
-
-	result = do_convert_tuple(&tmptup, cstate->map);
-
-	return HeapTupleGetDatum(result);
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalCase
- *
- *		Evaluate a CASE clause. Will have boolean expressions
- *		inside the WHEN clauses, and will have expressions
- *		for results.
- *		- thomas 1998-11-09
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
-			 bool *isNull)
-{
-	List	   *clauses = caseExpr->args;
-	ListCell   *clause;
-	Datum		save_datum;
-	bool		save_isNull;
-
-	/*
-	 * If there's a test expression, we have to evaluate it and save the value
-	 * where the CaseTestExpr placeholders can find it.  We must save and
-	 * restore prior setting of econtext's caseValue fields, in case this node
-	 * is itself within a larger CASE.  Furthermore, don't assign to the
-	 * econtext fields until after returning from evaluation of the test
-	 * expression.  We used to pass &econtext->caseValue_isNull to the
-	 * recursive call, but that leads to aliasing that variable within said
-	 * call, which can (and did) produce bugs when the test expression itself
-	 * contains a CASE.
-	 *
-	 * If there's no test expression, we don't actually need to save and
-	 * restore these fields; but it's less code to just do so unconditionally.
-	 */
-	save_datum = econtext->caseValue_datum;
-	save_isNull = econtext->caseValue_isNull;
-
-	if (caseExpr->arg)
-	{
-		Datum		arg_value;
-		bool		arg_isNull;
-
-		arg_value = ExecEvalExpr(caseExpr->arg,
-								 econtext,
-								 &arg_isNull);
-		/* Since caseValue_datum may be read multiple times, force to R/O */
-		econtext->caseValue_datum =
-			MakeExpandedObjectReadOnly(arg_value,
-									   arg_isNull,
-									   caseExpr->argtyplen);
-		econtext->caseValue_isNull = arg_isNull;
-	}
-
-	/*
-	 * we evaluate each of the WHEN clauses in turn, as soon as one is true we
-	 * return the corresponding result. If none are true then we return the
-	 * value of the default clause, or NULL if there is none.
-	 */
-	foreach(clause, clauses)
-	{
-		CaseWhenState *wclause = lfirst(clause);
-		Datum		clause_value;
-		bool		clause_isNull;
-
-		clause_value = ExecEvalExpr(wclause->expr,
-									econtext,
-									&clause_isNull);
-
-		/*
-		 * if we have a true test, then we return the result, since the case
-		 * statement is satisfied.  A NULL result from the test is not
-		 * considered true.
-		 */
-		if (DatumGetBool(clause_value) && !clause_isNull)
-		{
-			econtext->caseValue_datum = save_datum;
-			econtext->caseValue_isNull = save_isNull;
-			return ExecEvalExpr(wclause->result,
-								econtext,
-								isNull);
-		}
-	}
-
-	econtext->caseValue_datum = save_datum;
-	econtext->caseValue_isNull = save_isNull;
-
-	if (caseExpr->defresult)
-	{
-		return ExecEvalExpr(caseExpr->defresult,
-							econtext,
-							isNull);
-	}
-
-	*isNull = true;
-	return (Datum) 0;
-}
-
-/*
- * ExecEvalCaseTestExpr
- *
- * Return the value stored by CASE.
- */
-static Datum
-ExecEvalCaseTestExpr(ExprState *exprstate,
-					 ExprContext *econtext,
-					 bool *isNull)
-{
-	*isNull = econtext->caseValue_isNull;
-	return econtext->caseValue_datum;
-}
-
-/*
- * ExecEvalGroupingFuncExpr
- *
- * Return a bitmask with a bit for each (unevaluated) argument expression
- * (rightmost arg is least significant bit).
- *
- * A bit is set if the corresponding expression is NOT part of the set of
- * grouping expressions in the current grouping set.
- */
-static Datum
-ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
-						 ExprContext *econtext,
-						 bool *isNull)
-{
-	int			result = 0;
-	int			attnum = 0;
-	Bitmapset  *grouped_cols = gstate->aggstate->grouped_cols;
-	ListCell   *lc;
-
-	*isNull = false;
-
-	foreach(lc, (gstate->clauses))
-	{
-		attnum = lfirst_int(lc);
-
-		result = result << 1;
-
-		if (!bms_is_member(attnum, grouped_cols))
-			result = result | 1;
-	}
-
-	return (Datum) result;
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalArray - ARRAY[] expressions
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalArray(ArrayExprState *astate, ExprContext *econtext,
-			  bool *isNull)
-{
-	ArrayExpr  *arrayExpr = (ArrayExpr *) astate->xprstate.expr;
-	ArrayType  *result;
-	ListCell   *element;
-	Oid			element_type = arrayExpr->element_typeid;
-	int			ndims = 0;
-	int			dims[MAXDIM];
-	int			lbs[MAXDIM];
-
-	/* Set non-null as default */
-	*isNull = false;
-
-	if (!arrayExpr->multidims)
-	{
-		/* Elements are presumably of scalar type */
-		int			nelems;
-		Datum	   *dvalues;
-		bool	   *dnulls;
-		int			i = 0;
-
-		ndims = 1;
-		nelems = list_length(astate->elements);
-
-		/* Shouldn't happen here, but if length is 0, return empty array */
-		if (nelems == 0)
-			return PointerGetDatum(construct_empty_array(element_type));
-
-		dvalues = (Datum *) palloc(nelems * sizeof(Datum));
-		dnulls = (bool *) palloc(nelems * sizeof(bool));
-
-		/* loop through and build array of datums */
-		foreach(element, astate->elements)
-		{
-			ExprState  *e = (ExprState *) lfirst(element);
-
-			dvalues[i] = ExecEvalExpr(e, econtext, &dnulls[i]);
-			i++;
-		}
-
-		/* setup for 1-D array of the given length */
-		dims[0] = nelems;
-		lbs[0] = 1;
-
-		result = construct_md_array(dvalues, dnulls, ndims, dims, lbs,
-									element_type,
-									astate->elemlength,
-									astate->elembyval,
-									astate->elemalign);
-	}
-	else
-	{
-		/* Must be nested array expressions */
-		int			nbytes = 0;
-		int			nitems = 0;
-		int			outer_nelems = 0;
-		int			elem_ndims = 0;
-		int		   *elem_dims = NULL;
-		int		   *elem_lbs = NULL;
-		bool		firstone = true;
-		bool		havenulls = false;
-		bool		haveempty = false;
-		char	  **subdata;
-		bits8	  **subbitmaps;
-		int		   *subbytes;
-		int		   *subnitems;
-		int			i;
-		int32		dataoffset;
-		char	   *dat;
-		int			iitem;
-
-		i = list_length(astate->elements);
-		subdata = (char **) palloc(i * sizeof(char *));
-		subbitmaps = (bits8 **) palloc(i * sizeof(bits8 *));
-		subbytes = (int *) palloc(i * sizeof(int));
-		subnitems = (int *) palloc(i * sizeof(int));
-
-		/* loop through and get data area from each element */
-		foreach(element, astate->elements)
-		{
-			ExprState  *e = (ExprState *) lfirst(element);
-			bool		eisnull;
-			Datum		arraydatum;
-			ArrayType  *array;
-			int			this_ndims;
-
-			arraydatum = ExecEvalExpr(e, econtext, &eisnull);
-			/* temporarily ignore null subarrays */
-			if (eisnull)
-			{
-				haveempty = true;
-				continue;
-			}
-
-			array = DatumGetArrayTypeP(arraydatum);
-
-			/* run-time double-check on element type */
-			if (element_type != ARR_ELEMTYPE(array))
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("cannot merge incompatible arrays"),
-						 errdetail("Array with element type %s cannot be "
-						 "included in ARRAY construct with element type %s.",
-								   format_type_be(ARR_ELEMTYPE(array)),
-								   format_type_be(element_type))));
-
-			this_ndims = ARR_NDIM(array);
-			/* temporarily ignore zero-dimensional subarrays */
-			if (this_ndims <= 0)
-			{
-				haveempty = true;
-				continue;
-			}
-
-			if (firstone)
-			{
-				/* Get sub-array details from first member */
-				elem_ndims = this_ndims;
-				ndims = elem_ndims + 1;
-				if (ndims <= 0 || ndims > MAXDIM)
-					ereport(ERROR,
-							(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-						  errmsg("number of array dimensions (%d) exceeds " \
-								 "the maximum allowed (%d)", ndims, MAXDIM)));
-
-				elem_dims = (int *) palloc(elem_ndims * sizeof(int));
-				memcpy(elem_dims, ARR_DIMS(array), elem_ndims * sizeof(int));
-				elem_lbs = (int *) palloc(elem_ndims * sizeof(int));
-				memcpy(elem_lbs, ARR_LBOUND(array), elem_ndims * sizeof(int));
-
-				firstone = false;
-			}
-			else
-			{
-				/* Check other sub-arrays are compatible */
-				if (elem_ndims != this_ndims ||
-					memcmp(elem_dims, ARR_DIMS(array),
-						   elem_ndims * sizeof(int)) != 0 ||
-					memcmp(elem_lbs, ARR_LBOUND(array),
-						   elem_ndims * sizeof(int)) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
-							 errmsg("multidimensional arrays must have array "
-									"expressions with matching dimensions")));
-			}
-
-			subdata[outer_nelems] = ARR_DATA_PTR(array);
-			subbitmaps[outer_nelems] = ARR_NULLBITMAP(array);
-			subbytes[outer_nelems] = ARR_SIZE(array) - ARR_DATA_OFFSET(array);
-			nbytes += subbytes[outer_nelems];
-			subnitems[outer_nelems] = ArrayGetNItems(this_ndims,
-													 ARR_DIMS(array));
-			nitems += subnitems[outer_nelems];
-			havenulls |= ARR_HASNULL(array);
-			outer_nelems++;
-		}
-
-		/*
-		 * If all items were null or empty arrays, return an empty array;
-		 * otherwise, if some were and some weren't, raise error.  (Note: we
-		 * must special-case this somehow to avoid trying to generate a 1-D
-		 * array formed from empty arrays.  It's not ideal...)
-		 */
-		if (haveempty)
-		{
-			if (ndims == 0)		/* didn't find any nonempty array */
-				return PointerGetDatum(construct_empty_array(element_type));
-			ereport(ERROR,
-					(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
-					 errmsg("multidimensional arrays must have array "
-							"expressions with matching dimensions")));
-		}
-
-		/* setup for multi-D array */
-		dims[0] = outer_nelems;
-		lbs[0] = 1;
-		for (i = 1; i < ndims; i++)
-		{
-			dims[i] = elem_dims[i - 1];
-			lbs[i] = elem_lbs[i - 1];
-		}
-
-		if (havenulls)
-		{
-			dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems);
-			nbytes += dataoffset;
-		}
-		else
-		{
-			dataoffset = 0;		/* marker for no null bitmap */
-			nbytes += ARR_OVERHEAD_NONULLS(ndims);
-		}
-
-		result = (ArrayType *) palloc(nbytes);
-		SET_VARSIZE(result, nbytes);
-		result->ndim = ndims;
-		result->dataoffset = dataoffset;
-		result->elemtype = element_type;
-		memcpy(ARR_DIMS(result), dims, ndims * sizeof(int));
-		memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int));
-
-		dat = ARR_DATA_PTR(result);
-		iitem = 0;
-		for (i = 0; i < outer_nelems; i++)
-		{
-			memcpy(dat, subdata[i], subbytes[i]);
-			dat += subbytes[i];
-			if (havenulls)
-				array_bitmap_copy(ARR_NULLBITMAP(result), iitem,
-								  subbitmaps[i], 0,
-								  subnitems[i]);
-			iitem += subnitems[i];
-		}
-	}
-
-	return PointerGetDatum(result);
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalRow - ROW() expressions
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalRow(RowExprState *rstate,
-			ExprContext *econtext,
-			bool *isNull)
-{
-	HeapTuple	tuple;
-	Datum	   *values;
-	bool	   *isnull;
-	int			natts;
-	ListCell   *arg;
-	int			i;
-
-	/* Set non-null as default */
-	*isNull = false;
-
-	/* Allocate workspace */
-	natts = rstate->tupdesc->natts;
-	values = (Datum *) palloc0(natts * sizeof(Datum));
-	isnull = (bool *) palloc(natts * sizeof(bool));
-
-	/* preset to nulls in case rowtype has some later-added columns */
-	memset(isnull, true, natts * sizeof(bool));
-
-	/* Evaluate field values */
-	i = 0;
-	foreach(arg, rstate->args)
-	{
-		ExprState  *e = (ExprState *) lfirst(arg);
-
-		values[i] = ExecEvalExpr(e, econtext, &isnull[i]);
-		i++;
-	}
-
-	tuple = heap_form_tuple(rstate->tupdesc, values, isnull);
-
-	pfree(values);
-	pfree(isnull);
-
-	return HeapTupleGetDatum(tuple);
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalRowCompare - ROW() comparison-op ROW()
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalRowCompare(RowCompareExprState *rstate,
-				   ExprContext *econtext,
-				   bool *isNull)
-{
-	bool		result;
-	RowCompareType rctype = ((RowCompareExpr *) rstate->xprstate.expr)->rctype;
-	int32		cmpresult = 0;
-	ListCell   *l;
-	ListCell   *r;
-	int			i;
-
-	*isNull = true;				/* until we get a result */
-
-	i = 0;
-	forboth(l, rstate->largs, r, rstate->rargs)
-	{
-		ExprState  *le = (ExprState *) lfirst(l);
-		ExprState  *re = (ExprState *) lfirst(r);
-		FunctionCallInfoData locfcinfo;
-
-		InitFunctionCallInfoData(locfcinfo, &(rstate->funcs[i]), 2,
-								 rstate->collations[i],
-								 NULL, NULL);
-		locfcinfo.arg[0] = ExecEvalExpr(le, econtext,
-										&locfcinfo.argnull[0]);
-		locfcinfo.arg[1] = ExecEvalExpr(re, econtext,
-										&locfcinfo.argnull[1]);
-		if (rstate->funcs[i].fn_strict &&
-			(locfcinfo.argnull[0] || locfcinfo.argnull[1]))
-			return (Datum) 0;	/* force NULL result */
-		locfcinfo.isnull = false;
-		cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo));
-		if (locfcinfo.isnull)
-			return (Datum) 0;	/* force NULL result */
-		if (cmpresult != 0)
-			break;				/* no need to compare remaining columns */
-		i++;
-	}
-
-	switch (rctype)
-	{
-			/* EQ and NE cases aren't allowed here */
-		case ROWCOMPARE_LT:
-			result = (cmpresult < 0);
-			break;
-		case ROWCOMPARE_LE:
-			result = (cmpresult <= 0);
-			break;
-		case ROWCOMPARE_GE:
-			result = (cmpresult >= 0);
-			break;
-		case ROWCOMPARE_GT:
-			result = (cmpresult > 0);
-			break;
-		default:
-			elog(ERROR, "unrecognized RowCompareType: %d", (int) rctype);
-			result = 0;			/* keep compiler quiet */
-			break;
-	}
-
-	*isNull = false;
-	return BoolGetDatum(result);
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalCoalesce
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalCoalesce(CoalesceExprState *coalesceExpr, ExprContext *econtext,
-				 bool *isNull)
-{
-	ListCell   *arg;
-
-	/* Simply loop through until something NOT NULL is found */
-	foreach(arg, coalesceExpr->args)
-	{
-		ExprState  *e = (ExprState *) lfirst(arg);
-		Datum		value;
-
-		value = ExecEvalExpr(e, econtext, isNull);
-		if (!*isNull)
-			return value;
-	}
-
-	/* Else return NULL */
-	*isNull = true;
-	return (Datum) 0;
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalMinMax
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalMinMax(MinMaxExprState *minmaxExpr, ExprContext *econtext,
-			   bool *isNull)
-{
-	Datum		result = (Datum) 0;
-	MinMaxExpr *minmax = (MinMaxExpr *) minmaxExpr->xprstate.expr;
-	Oid			collation = minmax->inputcollid;
-	MinMaxOp	op = minmax->op;
-	FunctionCallInfoData locfcinfo;
-	ListCell   *arg;
-
-	*isNull = true;				/* until we get a result */
-
-	InitFunctionCallInfoData(locfcinfo, &minmaxExpr->cfunc, 2,
-							 collation, NULL, NULL);
-	locfcinfo.argnull[0] = false;
-	locfcinfo.argnull[1] = false;
-
-	foreach(arg, minmaxExpr->args)
-	{
-		ExprState  *e = (ExprState *) lfirst(arg);
-		Datum		value;
-		bool		valueIsNull;
-		int32		cmpresult;
-
-		value = ExecEvalExpr(e, econtext, &valueIsNull);
-		if (valueIsNull)
-			continue;			/* ignore NULL inputs */
-
-		if (*isNull)
-		{
-			/* first nonnull input, adopt value */
-			result = value;
-			*isNull = false;
-		}
-		else
-		{
-			/* apply comparison function */
-			locfcinfo.arg[0] = result;
-			locfcinfo.arg[1] = value;
-			locfcinfo.isnull = false;
-			cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo));
-			if (locfcinfo.isnull)		/* probably should not happen */
-				continue;
-			if (cmpresult > 0 && op == IS_LEAST)
-				result = value;
-			else if (cmpresult < 0 && op == IS_GREATEST)
-				result = value;
-		}
-	}
-
-	return result;
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalSQLValueFunction
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalSQLValueFunction(ExprState *svfExpr,
-						 ExprContext *econtext,
-						 bool *isNull)
-{
-	Datum		result = (Datum) 0;
-	SQLValueFunction *svf = (SQLValueFunction *) svfExpr->expr;
-	FunctionCallInfoData fcinfo;
-
-	*isNull = false;
-
-	/*
-	 * Note: current_schema() can return NULL.  current_user() etc currently
-	 * cannot, but might as well code those cases the same way for safety.
-	 */
-	switch (svf->op)
-	{
-		case SVFOP_CURRENT_DATE:
-			result = DateADTGetDatum(GetSQLCurrentDate());
-			break;
-		case SVFOP_CURRENT_TIME:
-		case SVFOP_CURRENT_TIME_N:
-			result = TimeTzADTPGetDatum(GetSQLCurrentTime(svf->typmod));
-			break;
-		case SVFOP_CURRENT_TIMESTAMP:
-		case SVFOP_CURRENT_TIMESTAMP_N:
-			result = TimestampTzGetDatum(GetSQLCurrentTimestamp(svf->typmod));
-			break;
-		case SVFOP_LOCALTIME:
-		case SVFOP_LOCALTIME_N:
-			result = TimeADTGetDatum(GetSQLLocalTime(svf->typmod));
-			break;
-		case SVFOP_LOCALTIMESTAMP:
-		case SVFOP_LOCALTIMESTAMP_N:
-			result = TimestampGetDatum(GetSQLLocalTimestamp(svf->typmod));
-			break;
-		case SVFOP_CURRENT_ROLE:
-		case SVFOP_CURRENT_USER:
-		case SVFOP_USER:
-			InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
-			result = current_user(&fcinfo);
-			*isNull = fcinfo.isnull;
-			break;
-		case SVFOP_SESSION_USER:
-			InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
-			result = session_user(&fcinfo);
-			*isNull = fcinfo.isnull;
-			break;
-		case SVFOP_CURRENT_CATALOG:
-			InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
-			result = current_database(&fcinfo);
-			*isNull = fcinfo.isnull;
-			break;
-		case SVFOP_CURRENT_SCHEMA:
-			InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
-			result = current_schema(&fcinfo);
-			*isNull = fcinfo.isnull;
-			break;
-	}
-
-	return result;
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalXml
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext,
-			bool *isNull)
-{
-	XmlExpr    *xexpr = (XmlExpr *) xmlExpr->xprstate.expr;
-	Datum		value;
-	bool		isnull;
-	ListCell   *arg;
-	ListCell   *narg;
-
-	*isNull = true;				/* until we get a result */
-
-	switch (xexpr->op)
-	{
-		case IS_XMLCONCAT:
-			{
-				List	   *values = NIL;
-
-				foreach(arg, xmlExpr->args)
-				{
-					ExprState  *e = (ExprState *) lfirst(arg);
-
-					value = ExecEvalExpr(e, econtext, &isnull);
-					if (!isnull)
-						values = lappend(values, DatumGetPointer(value));
-				}
-
-				if (list_length(values) > 0)
-				{
-					*isNull = false;
-					return PointerGetDatum(xmlconcat(values));
-				}
-				else
-					return (Datum) 0;
-			}
-			break;
-
-		case IS_XMLFOREST:
-			{
-				StringInfoData buf;
-
-				initStringInfo(&buf);
-				forboth(arg, xmlExpr->named_args, narg, xexpr->arg_names)
-				{
-					ExprState  *e = (ExprState *) lfirst(arg);
-					char	   *argname = strVal(lfirst(narg));
-
-					value = ExecEvalExpr(e, econtext, &isnull);
-					if (!isnull)
-					{
-						appendStringInfo(&buf, "<%s>%s</%s>",
-										 argname,
-										 map_sql_value_to_xml_value(value, exprType((Node *) e->expr), true),
-										 argname);
-						*isNull = false;
-					}
-				}
-
-				if (*isNull)
-				{
-					pfree(buf.data);
-					return (Datum) 0;
-				}
-				else
-				{
-					text	   *result;
-
-					result = cstring_to_text_with_len(buf.data, buf.len);
-					pfree(buf.data);
-
-					return PointerGetDatum(result);
-				}
-			}
-			break;
-
-		case IS_XMLELEMENT:
-			*isNull = false;
-			return PointerGetDatum(xmlelement(xmlExpr, econtext));
-			break;
-
-		case IS_XMLPARSE:
-			{
-				ExprState  *e;
-				text	   *data;
-				bool		preserve_whitespace;
-
-				/* arguments are known to be text, bool */
-				Assert(list_length(xmlExpr->args) == 2);
-
-				e = (ExprState *) linitial(xmlExpr->args);
-				value = ExecEvalExpr(e, econtext, &isnull);
-				if (isnull)
-					return (Datum) 0;
-				data = DatumGetTextPP(value);
-
-				e = (ExprState *) lsecond(xmlExpr->args);
-				value = ExecEvalExpr(e, econtext, &isnull);
-				if (isnull)		/* probably can't happen */
-					return (Datum) 0;
-				preserve_whitespace = DatumGetBool(value);
-
-				*isNull = false;
-
-				return PointerGetDatum(xmlparse(data,
-												xexpr->xmloption,
-												preserve_whitespace));
-			}
-			break;
-
-		case IS_XMLPI:
-			{
-				ExprState  *e;
-				text	   *arg;
-
-				/* optional argument is known to be text */
-				Assert(list_length(xmlExpr->args) <= 1);
-
-				if (xmlExpr->args)
-				{
-					e = (ExprState *) linitial(xmlExpr->args);
-					value = ExecEvalExpr(e, econtext, &isnull);
-					if (isnull)
-						arg = NULL;
-					else
-						arg = DatumGetTextPP(value);
-				}
-				else
-				{
-					arg = NULL;
-					isnull = false;
-				}
-
-				return PointerGetDatum(xmlpi(xexpr->name, arg, isnull, isNull));
-			}
-			break;
-
-		case IS_XMLROOT:
-			{
-				ExprState  *e;
-				xmltype    *data;
-				text	   *version;
-				int			standalone;
-
-				/* arguments are known to be xml, text, int */
-				Assert(list_length(xmlExpr->args) == 3);
-
-				e = (ExprState *) linitial(xmlExpr->args);
-				value = ExecEvalExpr(e, econtext, &isnull);
-				if (isnull)
-					return (Datum) 0;
-				data = DatumGetXmlP(value);
-
-				e = (ExprState *) lsecond(xmlExpr->args);
-				value = ExecEvalExpr(e, econtext, &isnull);
-				if (isnull)
-					version = NULL;
-				else
-					version = DatumGetTextPP(value);
-
-				e = (ExprState *) lthird(xmlExpr->args);
-				value = ExecEvalExpr(e, econtext, &isnull);
-				standalone = DatumGetInt32(value);
-
-				*isNull = false;
-
-				return PointerGetDatum(xmlroot(data,
-											   version,
-											   standalone));
-			}
-			break;
-
-		case IS_XMLSERIALIZE:
-			{
-				ExprState  *e;
-
-				/* argument type is known to be xml */
-				Assert(list_length(xmlExpr->args) == 1);
-
-				e = (ExprState *) linitial(xmlExpr->args);
-				value = ExecEvalExpr(e, econtext, &isnull);
-				if (isnull)
-					return (Datum) 0;
-
-				*isNull = false;
-
-				return PointerGetDatum(xmltotext_with_xmloption(DatumGetXmlP(value), xexpr->xmloption));
-			}
-			break;
-
-		case IS_DOCUMENT:
-			{
-				ExprState  *e;
-
-				/* optional argument is known to be xml */
-				Assert(list_length(xmlExpr->args) == 1);
-
-				e = (ExprState *) linitial(xmlExpr->args);
-				value = ExecEvalExpr(e, econtext, &isnull);
-				if (isnull)
-					return (Datum) 0;
-				else
-				{
-					*isNull = false;
-					return BoolGetDatum(xml_is_document(DatumGetXmlP(value)));
-				}
-			}
-			break;
-	}
-
-	elog(ERROR, "unrecognized XML operation");
-	return (Datum) 0;
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalNullIf
- *
- * Note that this is *always* derived from the equals operator,
- * but since we need special processing of the arguments
- * we can not simply reuse ExecEvalOper() or ExecEvalFunc().
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalNullIf(FuncExprState *nullIfExpr,
-			   ExprContext *econtext,
-			   bool *isNull)
-{
-	Datum		result;
-	FunctionCallInfo fcinfo;
-
-	/*
-	 * Initialize function cache if first time through
-	 */
-	if (nullIfExpr->func.fn_oid == InvalidOid)
-	{
-		NullIfExpr *op = (NullIfExpr *) nullIfExpr->xprstate.expr;
-
-		init_fcache(op->opfuncid, op->inputcollid, nullIfExpr,
-					econtext->ecxt_per_query_memory, false, false);
-	}
-
-	/*
-	 * Evaluate arguments
-	 */
-	fcinfo = &nullIfExpr->fcinfo_data;
-	ExecEvalFuncArgs(fcinfo, nullIfExpr->args, econtext);
-	Assert(fcinfo->nargs == 2);
-
-	/* if either argument is NULL they can't be equal */
-	if (!fcinfo->argnull[0] && !fcinfo->argnull[1])
-	{
-		fcinfo->isnull = false;
-		result = FunctionCallInvoke(fcinfo);
-		/* if the arguments are equal return null */
-		if (!fcinfo->isnull && DatumGetBool(result))
-		{
-			*isNull = true;
-			return (Datum) 0;
-		}
-	}
-
-	/* else return first argument */
-	*isNull = fcinfo->argnull[0];
-	return fcinfo->arg[0];
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalNullTest
- *
- *		Evaluate a NullTest node.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalNullTest(NullTestState *nstate,
-				 ExprContext *econtext,
-				 bool *isNull)
-{
-	NullTest   *ntest = (NullTest *) nstate->xprstate.expr;
-	Datum		result;
-
-	result = ExecEvalExpr(nstate->arg, econtext, isNull);
-
-	if (ntest->argisrow && !(*isNull))
-	{
-		/*
-		 * The SQL standard defines IS [NOT] NULL for a non-null rowtype
-		 * argument as:
-		 *
-		 * "R IS NULL" is true if every field is the null value.
-		 *
-		 * "R IS NOT NULL" is true if no field is the null value.
-		 *
-		 * This definition is (apparently intentionally) not recursive; so our
-		 * tests on the fields are primitive attisnull tests, not recursive
-		 * checks to see if they are all-nulls or no-nulls rowtypes.
-		 *
-		 * The standard does not consider the possibility of zero-field rows,
-		 * but here we consider them to vacuously satisfy both predicates.
-		 */
-		HeapTupleHeader tuple;
-		Oid			tupType;
-		int32		tupTypmod;
-		TupleDesc	tupDesc;
-		HeapTupleData tmptup;
-		int			att;
-
-		tuple = DatumGetHeapTupleHeader(result);
-
-		tupType = HeapTupleHeaderGetTypeId(tuple);
-		tupTypmod = HeapTupleHeaderGetTypMod(tuple);
-
-		/* Lookup tupdesc if first time through or if type changes */
-		tupDesc = get_cached_rowtype(tupType, tupTypmod,
-									 &nstate->argdesc, econtext);
-
-		/*
-		 * heap_attisnull needs a HeapTuple not a bare HeapTupleHeader.
-		 */
-		tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
-		tmptup.t_data = tuple;
-
-		for (att = 1; att <= tupDesc->natts; att++)
-		{
-			/* ignore dropped columns */
-			if (tupDesc->attrs[att - 1]->attisdropped)
-				continue;
-			if (heap_attisnull(&tmptup, att))
-			{
-				/* null field disproves IS NOT NULL */
-				if (ntest->nulltesttype == IS_NOT_NULL)
-					return BoolGetDatum(false);
-			}
-			else
-			{
-				/* non-null field disproves IS NULL */
-				if (ntest->nulltesttype == IS_NULL)
-					return BoolGetDatum(false);
-			}
-		}
-
-		return BoolGetDatum(true);
-	}
-	else
-	{
-		/* Simple scalar-argument case, or a null rowtype datum */
-		switch (ntest->nulltesttype)
-		{
-			case IS_NULL:
-				if (*isNull)
-				{
-					*isNull = false;
-					return BoolGetDatum(true);
-				}
-				else
-					return BoolGetDatum(false);
-			case IS_NOT_NULL:
-				if (*isNull)
-				{
-					*isNull = false;
-					return BoolGetDatum(false);
-				}
-				else
-					return BoolGetDatum(true);
-			default:
-				elog(ERROR, "unrecognized nulltesttype: %d",
-					 (int) ntest->nulltesttype);
-				return (Datum) 0;		/* keep compiler quiet */
-		}
-	}
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalBooleanTest
- *
- *		Evaluate a BooleanTest node.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalBooleanTest(GenericExprState *bstate,
-					ExprContext *econtext,
-					bool *isNull)
-{
-	BooleanTest *btest = (BooleanTest *) bstate->xprstate.expr;
-	Datum		result;
-
-	result = ExecEvalExpr(bstate->arg, econtext, isNull);
-
-	switch (btest->booltesttype)
-	{
-		case IS_TRUE:
-			if (*isNull)
-			{
-				*isNull = false;
-				return BoolGetDatum(false);
-			}
-			else if (DatumGetBool(result))
-				return BoolGetDatum(true);
-			else
-				return BoolGetDatum(false);
-		case IS_NOT_TRUE:
-			if (*isNull)
-			{
-				*isNull = false;
-				return BoolGetDatum(true);
-			}
-			else if (DatumGetBool(result))
-				return BoolGetDatum(false);
-			else
-				return BoolGetDatum(true);
-		case IS_FALSE:
-			if (*isNull)
-			{
-				*isNull = false;
-				return BoolGetDatum(false);
-			}
-			else if (DatumGetBool(result))
-				return BoolGetDatum(false);
-			else
-				return BoolGetDatum(true);
-		case IS_NOT_FALSE:
-			if (*isNull)
-			{
-				*isNull = false;
-				return BoolGetDatum(true);
-			}
-			else if (DatumGetBool(result))
-				return BoolGetDatum(true);
-			else
-				return BoolGetDatum(false);
-		case IS_UNKNOWN:
-			if (*isNull)
-			{
-				*isNull = false;
-				return BoolGetDatum(true);
-			}
-			else
-				return BoolGetDatum(false);
-		case IS_NOT_UNKNOWN:
-			if (*isNull)
-			{
-				*isNull = false;
-				return BoolGetDatum(false);
-			}
-			else
-				return BoolGetDatum(true);
-		default:
-			elog(ERROR, "unrecognized booltesttype: %d",
-				 (int) btest->booltesttype);
-			return (Datum) 0;	/* keep compiler quiet */
-	}
-}
-
-/*
- * ExecEvalCoerceToDomain
- *
- * Test the provided data against the domain constraint(s).  If the data
- * passes the constraint specifications, pass it through (return the
- * datum) otherwise throw an error.
- */
-static Datum
-ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext,
-					   bool *isNull)
-{
-	CoerceToDomain *ctest = (CoerceToDomain *) cstate->xprstate.expr;
-	Datum		result;
-	ListCell   *l;
-
-	result = ExecEvalExpr(cstate->arg, econtext, isNull);
-
-	/* Make sure we have up-to-date constraints */
-	UpdateDomainConstraintRef(cstate->constraint_ref);
-
-	foreach(l, cstate->constraint_ref->constraints)
-	{
-		DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
-
-		switch (con->constrainttype)
-		{
-			case DOM_CONSTRAINT_NOTNULL:
-				if (*isNull)
-					ereport(ERROR,
-							(errcode(ERRCODE_NOT_NULL_VIOLATION),
-							 errmsg("domain %s does not allow null values",
-									format_type_be(ctest->resulttype)),
-							 errdatatype(ctest->resulttype)));
-				break;
-			case DOM_CONSTRAINT_CHECK:
-				{
-					Datum		conResult;
-					bool		conIsNull;
-					Datum		save_datum;
-					bool		save_isNull;
-
-					/*
-					 * Set up value to be returned by CoerceToDomainValue
-					 * nodes. We must save and restore prior setting of
-					 * econtext's domainValue fields, in case this node is
-					 * itself within a check expression for another domain.
-					 *
-					 * Also, if we are working with a read-write expanded
-					 * datum, be sure that what we pass to CHECK expressions
-					 * is a read-only pointer; else called functions might
-					 * modify or even delete the expanded object.
-					 */
-					save_datum = econtext->domainValue_datum;
-					save_isNull = econtext->domainValue_isNull;
-
-					econtext->domainValue_datum =
-						MakeExpandedObjectReadOnly(result, *isNull,
-									 cstate->constraint_ref->tcache->typlen);
-					econtext->domainValue_isNull = *isNull;
-
-					conResult = ExecEvalExpr(con->check_expr, econtext,
-											 &conIsNull);
-
-					if (!conIsNull &&
-						!DatumGetBool(conResult))
-						ereport(ERROR,
-								(errcode(ERRCODE_CHECK_VIOLATION),
-								 errmsg("value for domain %s violates check constraint \"%s\"",
-										format_type_be(ctest->resulttype),
-										con->name),
-								 errdomainconstraint(ctest->resulttype,
-													 con->name)));
-					econtext->domainValue_datum = save_datum;
-					econtext->domainValue_isNull = save_isNull;
-
-					break;
-				}
-			default:
-				elog(ERROR, "unrecognized constraint type: %d",
-					 (int) con->constrainttype);
-				break;
-		}
-	}
-
-	/* If all has gone well (constraints did not fail) return the datum */
-	return result;
-}
-
-/*
- * ExecEvalCoerceToDomainValue
- *
- * Return the value stored by CoerceToDomain.
- */
-static Datum
-ExecEvalCoerceToDomainValue(ExprState *exprstate,
-							ExprContext *econtext,
-							bool *isNull)
-{
-	*isNull = econtext->domainValue_isNull;
-	return econtext->domainValue_datum;
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalFieldSelect
- *
- *		Evaluate a FieldSelect node.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalFieldSelect(FieldSelectState *fstate,
-					ExprContext *econtext,
-					bool *isNull)
-{
-	FieldSelect *fselect = (FieldSelect *) fstate->xprstate.expr;
-	AttrNumber	fieldnum = fselect->fieldnum;
-	Datum		result;
-	Datum		tupDatum;
-	HeapTupleHeader tuple;
-	Oid			tupType;
-	int32		tupTypmod;
-	TupleDesc	tupDesc;
-	Form_pg_attribute attr;
-	HeapTupleData tmptup;
-
-	tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull);
-
-	if (*isNull)
-		return tupDatum;
-
-	tuple = DatumGetHeapTupleHeader(tupDatum);
-
-	tupType = HeapTupleHeaderGetTypeId(tuple);
-	tupTypmod = HeapTupleHeaderGetTypMod(tuple);
-
-	/* Lookup tupdesc if first time through or if type changes */
-	tupDesc = get_cached_rowtype(tupType, tupTypmod,
-								 &fstate->argdesc, econtext);
-
-	/*
-	 * Find field's attr record.  Note we don't support system columns here: a
-	 * datum tuple doesn't have valid values for most of the interesting
-	 * system columns anyway.
-	 */
-	if (fieldnum <= 0)			/* should never happen */
-		elog(ERROR, "unsupported reference to system column %d in FieldSelect",
-			 fieldnum);
-	if (fieldnum > tupDesc->natts)		/* should never happen */
-		elog(ERROR, "attribute number %d exceeds number of columns %d",
-			 fieldnum, tupDesc->natts);
-	attr = tupDesc->attrs[fieldnum - 1];
-
-	/* Check for dropped column, and force a NULL result if so */
-	if (attr->attisdropped)
-	{
-		*isNull = true;
-		return (Datum) 0;
-	}
-
-	/* Check for type mismatch --- possible after ALTER COLUMN TYPE? */
-	/* As in ExecEvalScalarVar, we should but can't check typmod */
-	if (fselect->resulttype != attr->atttypid)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATATYPE_MISMATCH),
-				 errmsg("attribute %d has wrong type", fieldnum),
-				 errdetail("Table has type %s, but query expects %s.",
-						   format_type_be(attr->atttypid),
-						   format_type_be(fselect->resulttype))));
-
-	/* heap_getattr needs a HeapTuple not a bare HeapTupleHeader */
-	tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
-	tmptup.t_data = tuple;
-
-	result = heap_getattr(&tmptup,
-						  fieldnum,
-						  tupDesc,
-						  isNull);
-	return result;
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalFieldStore
- *
- *		Evaluate a FieldStore node.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalFieldStore(FieldStoreState *fstate,
-				   ExprContext *econtext,
-				   bool *isNull)
-{
-	FieldStore *fstore = (FieldStore *) fstate->xprstate.expr;
-	HeapTuple	tuple;
-	Datum		tupDatum;
-	TupleDesc	tupDesc;
-	Datum	   *values;
-	bool	   *isnull;
-	Datum		save_datum;
-	bool		save_isNull;
-	ListCell   *l1,
-			   *l2;
-
-	tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull);
-
-	/* Lookup tupdesc if first time through or after rescan */
-	tupDesc = get_cached_rowtype(fstore->resulttype, -1,
-								 &fstate->argdesc, econtext);
-
-	/* Allocate workspace */
-	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
-	isnull = (bool *) palloc(tupDesc->natts * sizeof(bool));
-
-	if (!*isNull)
-	{
-		/*
-		 * heap_deform_tuple needs a HeapTuple not a bare HeapTupleHeader. We
-		 * set all the fields in the struct just in case.
-		 */
-		HeapTupleHeader tuphdr;
-		HeapTupleData tmptup;
-
-		tuphdr = DatumGetHeapTupleHeader(tupDatum);
-		tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr);
-		ItemPointerSetInvalid(&(tmptup.t_self));
-		tmptup.t_tableOid = InvalidOid;
-		tmptup.t_data = tuphdr;
-
-		heap_deform_tuple(&tmptup, tupDesc, values, isnull);
-	}
-	else
-	{
-		/* Convert null input tuple into an all-nulls row */
-		memset(isnull, true, tupDesc->natts * sizeof(bool));
-	}
-
-	/* Result is never null */
-	*isNull = false;
-
-	save_datum = econtext->caseValue_datum;
-	save_isNull = econtext->caseValue_isNull;
-
-	forboth(l1, fstate->newvals, l2, fstore->fieldnums)
-	{
-		ExprState  *newval = (ExprState *) lfirst(l1);
-		AttrNumber	fieldnum = lfirst_int(l2);
-
-		Assert(fieldnum > 0 && fieldnum <= tupDesc->natts);
-
-		/*
-		 * Use the CaseTestExpr mechanism to pass down the old value of the
-		 * field being replaced; this is needed in case the newval is itself a
-		 * FieldStore or ArrayRef that has to obtain and modify the old value.
-		 * It's safe to reuse the CASE mechanism because there cannot be a
-		 * CASE between here and where the value would be needed, and a field
-		 * assignment can't be within a CASE either.  (So saving and restoring
-		 * the caseValue is just paranoia, but let's do it anyway.)
-		 */
-		econtext->caseValue_datum = values[fieldnum - 1];
-		econtext->caseValue_isNull = isnull[fieldnum - 1];
-
-		values[fieldnum - 1] = ExecEvalExpr(newval,
-											econtext,
-											&isnull[fieldnum - 1]);
-	}
-
-	econtext->caseValue_datum = save_datum;
-	econtext->caseValue_isNull = save_isNull;
-
-	tuple = heap_form_tuple(tupDesc, values, isnull);
-
-	pfree(values);
-	pfree(isnull);
-
-	return HeapTupleGetDatum(tuple);
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalRelabelType
- *
- *		Evaluate a RelabelType node.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalRelabelType(GenericExprState *exprstate,
-					ExprContext *econtext,
-					bool *isNull)
-{
-	return ExecEvalExpr(exprstate->arg, econtext, isNull);
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalCoerceViaIO
- *
- *		Evaluate a CoerceViaIO node.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalCoerceViaIO(CoerceViaIOState *iostate,
-					ExprContext *econtext,
-					bool *isNull)
-{
-	Datum		result;
-	Datum		inputval;
-	char	   *string;
-
-	inputval = ExecEvalExpr(iostate->arg, econtext, isNull);
-
-	if (*isNull)
-		string = NULL;			/* output functions are not called on nulls */
-	else
-		string = OutputFunctionCall(&iostate->outfunc, inputval);
-
-	result = InputFunctionCall(&iostate->infunc,
-							   string,
-							   iostate->intypioparam,
-							   -1);
-
-	/* The input function cannot change the null/not-null status */
-	return result;
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalArrayCoerceExpr
- *
- *		Evaluate an ArrayCoerceExpr node.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
-						ExprContext *econtext,
-						bool *isNull)
-{
-	ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) astate->xprstate.expr;
-	Datum		result;
-	FunctionCallInfoData locfcinfo;
-
-	result = ExecEvalExpr(astate->arg, econtext, isNull);
-
-	if (*isNull)
-		return result;			/* nothing to do */
-
-	/*
-	 * If it's binary-compatible, modify the element type in the array header,
-	 * but otherwise leave the array as we received it.
-	 */
-	if (!OidIsValid(acoerce->elemfuncid))
-	{
-		/* Detoast input array if necessary, and copy in any case */
-		ArrayType  *array = DatumGetArrayTypePCopy(result);
-
-		ARR_ELEMTYPE(array) = astate->resultelemtype;
-		PG_RETURN_ARRAYTYPE_P(array);
-	}
-
-	/* Initialize function cache if first time through */
-	if (astate->elemfunc.fn_oid == InvalidOid)
-	{
-		AclResult	aclresult;
-
-		/* Check permission to call function */
-		aclresult = pg_proc_aclcheck(acoerce->elemfuncid, GetUserId(),
-									 ACL_EXECUTE);
-		if (aclresult != ACLCHECK_OK)
-			aclcheck_error(aclresult, ACL_KIND_PROC,
-						   get_func_name(acoerce->elemfuncid));
-		InvokeFunctionExecuteHook(acoerce->elemfuncid);
-
-		/* Set up the primary fmgr lookup information */
-		fmgr_info_cxt(acoerce->elemfuncid, &(astate->elemfunc),
-					  econtext->ecxt_per_query_memory);
-		fmgr_info_set_expr((Node *) acoerce, &(astate->elemfunc));
-	}
-
-	/*
-	 * Use array_map to apply the function to each array element.
-	 *
-	 * We pass on the desttypmod and isExplicit flags whether or not the
-	 * function wants them.
-	 *
-	 * Note: coercion functions are assumed to not use collation.
-	 */
-	InitFunctionCallInfoData(locfcinfo, &(astate->elemfunc), 3,
-							 InvalidOid, NULL, NULL);
-	locfcinfo.arg[0] = result;
-	locfcinfo.arg[1] = Int32GetDatum(acoerce->resulttypmod);
-	locfcinfo.arg[2] = BoolGetDatum(acoerce->isExplicit);
-	locfcinfo.argnull[0] = false;
-	locfcinfo.argnull[1] = false;
-	locfcinfo.argnull[2] = false;
-
-	return array_map(&locfcinfo, astate->resultelemtype, astate->amstate);
-}
-
-/* ----------------------------------------------------------------
- *		ExecEvalCurrentOfExpr
- *
- * The planner should convert CURRENT OF into a TidScan qualification, or some
- * other special handling in a ForeignScan node.  So we have to be able to do
- * ExecInitExpr on a CurrentOfExpr, but we shouldn't ever actually execute it.
- * If we get here, we suppose we must be dealing with CURRENT OF on a foreign
- * table whose FDW doesn't handle it, and complain accordingly.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
-					  bool *isNull)
-{
-	ereport(ERROR,
-			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-		   errmsg("WHERE CURRENT OF is not supported for this table type")));
-	return 0;					/* keep compiler quiet */
-}
-
-
-/*
- * ExecEvalExprSwitchContext
- *
- * Same as ExecEvalExpr, but get into the right allocation context explicitly.
- */
-Datum
-ExecEvalExprSwitchContext(ExprState *expression,
-						  ExprContext *econtext,
-						  bool *isNull)
-{
-	Datum		retDatum;
-	MemoryContext oldContext;
-
-	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
-	retDatum = ExecEvalExpr(expression, econtext, isNull);
-	MemoryContextSwitchTo(oldContext);
-	return retDatum;
-}
-
-
-/*
- * ExecInitExpr: prepare an expression tree for execution
- *
- * This function builds and returns an ExprState tree paralleling the given
- * Expr node tree.  The ExprState tree can then be handed to ExecEvalExpr
- * for execution.  Because the Expr tree itself is read-only as far as
- * ExecInitExpr and ExecEvalExpr are concerned, several different executions
- * of the same plan tree can occur concurrently.
- *
- * This must be called in a memory context that will last as long as repeated
- * executions of the expression are needed.  Typically the context will be
- * the same as the per-query context of the associated ExprContext.
- *
- * Any Aggref, WindowFunc, or SubPlan nodes found in the tree are added to the
- * lists of such nodes held by the parent PlanState. Otherwise, we do very
- * little initialization here other than building the state-node tree.  Any
- * nontrivial work associated with initializing runtime info for a node should
- * happen during the first actual evaluation of that node.  (This policy lets
- * us avoid work if the node is never actually evaluated.)
- *
- * Note: there is no ExecEndExpr function; we assume that any resource
- * cleanup needed will be handled by just releasing the memory context
- * in which the state tree is built.  Functions that require additional
- * cleanup work can register a shutdown callback in the ExprContext.
- *
- *	'node' is the root of the expression tree to examine
- *	'parent' is the PlanState node that owns the expression.
- *
- * 'parent' may be NULL if we are preparing an expression that is not
- * associated with a plan tree.  (If so, it can't have aggs or subplans.)
- * This case should usually come through ExecPrepareExpr, not directly here.
- */
-ExprState *
-ExecInitExpr(Expr *node, PlanState *parent)
-{
-	ExprState  *state;
-
-	if (node == NULL)
-		return NULL;
-
-	/* Guard against stack overflow due to overly complex expressions */
-	check_stack_depth();
-
-	switch (nodeTag(node))
-	{
-		case T_Var:
-			/* varattno == InvalidAttrNumber means it's a whole-row Var */
-			if (((Var *) node)->varattno == InvalidAttrNumber)
-			{
-				WholeRowVarExprState *wstate = makeNode(WholeRowVarExprState);
-
-				wstate->parent = parent;
-				wstate->wrv_tupdesc = NULL;
-				wstate->wrv_junkFilter = NULL;
-				state = (ExprState *) wstate;
-				state->evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowVar;
-			}
-			else
-			{
-				state = makeNode(ExprState);
-				state->evalfunc = ExecEvalScalarVar;
-			}
-			break;
-		case T_Const:
-			state = makeNode(ExprState);
-			state->evalfunc = ExecEvalConst;
-			break;
-		case T_Param:
-			state = makeNode(ExprState);
-			switch (((Param *) node)->paramkind)
-			{
-				case PARAM_EXEC:
-					state->evalfunc = ExecEvalParamExec;
-					break;
-				case PARAM_EXTERN:
-					state->evalfunc = ExecEvalParamExtern;
-					break;
-				default:
-					elog(ERROR, "unrecognized paramkind: %d",
-						 (int) ((Param *) node)->paramkind);
-					break;
-			}
-			break;
-		case T_CoerceToDomainValue:
-			state = makeNode(ExprState);
-			state->evalfunc = ExecEvalCoerceToDomainValue;
-			break;
-		case T_CaseTestExpr:
-			state = makeNode(ExprState);
-			state->evalfunc = ExecEvalCaseTestExpr;
-			break;
-		case T_Aggref:
-			{
-				AggrefExprState *astate = makeNode(AggrefExprState);
-
-				astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalAggref;
-				if (parent && IsA(parent, AggState))
-				{
-					AggState   *aggstate = (AggState *) parent;
-
-					aggstate->aggs = lcons(astate, aggstate->aggs);
-					aggstate->numaggs++;
-				}
-				else
-				{
-					/* planner messed up */
-					elog(ERROR, "Aggref found in non-Agg plan node");
-				}
-				state = (ExprState *) astate;
-			}
-			break;
-		case T_GroupingFunc:
-			{
-				GroupingFunc *grp_node = (GroupingFunc *) node;
-				GroupingFuncExprState *grp_state = makeNode(GroupingFuncExprState);
-				Agg		   *agg = NULL;
-
-				if (!parent || !IsA(parent, AggState) ||!IsA(parent->plan, Agg))
-					elog(ERROR, "parent of GROUPING is not Agg node");
-
-				grp_state->aggstate = (AggState *) parent;
-
-				agg = (Agg *) (parent->plan);
-
-				if (agg->groupingSets)
-					grp_state->clauses = grp_node->cols;
-				else
-					grp_state->clauses = NIL;
-
-				state = (ExprState *) grp_state;
-				state->evalfunc = (ExprStateEvalFunc) ExecEvalGroupingFuncExpr;
-			}
-			break;
-		case T_WindowFunc:
-			{
-				WindowFunc *wfunc = (WindowFunc *) node;
-				WindowFuncExprState *wfstate = makeNode(WindowFuncExprState);
-
-				wfstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWindowFunc;
-				if (parent && IsA(parent, WindowAggState))
-				{
-					WindowAggState *winstate = (WindowAggState *) parent;
-					int			nfuncs;
-
-					winstate->funcs = lcons(wfstate, winstate->funcs);
-					nfuncs = ++winstate->numfuncs;
-					if (wfunc->winagg)
-						winstate->numaggs++;
-
-					wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
-														  parent);
-					wfstate->aggfilter = ExecInitExpr(wfunc->aggfilter,
-													  parent);
-
-					/*
-					 * Complain if the windowfunc's arguments contain any
-					 * windowfuncs; nested window functions are semantically
-					 * nonsensical.  (This should have been caught earlier,
-					 * but we defend against it here anyway.)
-					 */
-					if (nfuncs != winstate->numfuncs)
-						ereport(ERROR,
-								(errcode(ERRCODE_WINDOWING_ERROR),
-						  errmsg("window function calls cannot be nested")));
-				}
-				else
-				{
-					/* planner messed up */
-					elog(ERROR, "WindowFunc found in non-WindowAgg plan node");
-				}
-				state = (ExprState *) wfstate;
-			}
-			break;
-		case T_ArrayRef:
-			{
-				ArrayRef   *aref = (ArrayRef *) node;
-				ArrayRefExprState *astate = makeNode(ArrayRefExprState);
-
-				astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayRef;
-				astate->refupperindexpr = (List *)
-					ExecInitExpr((Expr *) aref->refupperindexpr, parent);
-				astate->reflowerindexpr = (List *)
-					ExecInitExpr((Expr *) aref->reflowerindexpr, parent);
-				astate->refexpr = ExecInitExpr(aref->refexpr, parent);
-				astate->refassgnexpr = ExecInitExpr(aref->refassgnexpr,
-													parent);
-				/* do one-time catalog lookups for type info */
-				astate->refattrlength = get_typlen(aref->refarraytype);
-				get_typlenbyvalalign(aref->refelemtype,
-									 &astate->refelemlength,
-									 &astate->refelembyval,
-									 &astate->refelemalign);
-				state = (ExprState *) astate;
-			}
-			break;
-		case T_FuncExpr:
-			{
-				FuncExpr   *funcexpr = (FuncExpr *) node;
-				FuncExprState *fstate = makeNode(FuncExprState);
-
-				fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFunc;
-				fstate->args = (List *)
-					ExecInitExpr((Expr *) funcexpr->args, parent);
-				fstate->func.fn_oid = InvalidOid;		/* not initialized */
-				fstate->funcReturnsSet = funcexpr->funcretset;
-				state = (ExprState *) fstate;
-			}
-			break;
-		case T_OpExpr:
-			{
-				OpExpr	   *opexpr = (OpExpr *) node;
-				FuncExprState *fstate = makeNode(FuncExprState);
-
-				fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalOper;
-				fstate->args = (List *)
-					ExecInitExpr((Expr *) opexpr->args, parent);
-				fstate->func.fn_oid = InvalidOid;		/* not initialized */
-				fstate->funcReturnsSet = opexpr->opretset;
-				state = (ExprState *) fstate;
-			}
-			break;
-		case T_DistinctExpr:
-			{
-				DistinctExpr *distinctexpr = (DistinctExpr *) node;
-				FuncExprState *fstate = makeNode(FuncExprState);
-
-				fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalDistinct;
-				fstate->args = (List *)
-					ExecInitExpr((Expr *) distinctexpr->args, parent);
-				fstate->func.fn_oid = InvalidOid;		/* not initialized */
-				fstate->funcReturnsSet = false; /* not supported */
-				state = (ExprState *) fstate;
-			}
-			break;
-		case T_NullIfExpr:
-			{
-				NullIfExpr *nullifexpr = (NullIfExpr *) node;
-				FuncExprState *fstate = makeNode(FuncExprState);
-
-				fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullIf;
-				fstate->args = (List *)
-					ExecInitExpr((Expr *) nullifexpr->args, parent);
-				fstate->func.fn_oid = InvalidOid;		/* not initialized */
-				fstate->funcReturnsSet = false; /* not supported */
-				state = (ExprState *) fstate;
-			}
-			break;
-		case T_ScalarArrayOpExpr:
-			{
-				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
-				ScalarArrayOpExprState *sstate = makeNode(ScalarArrayOpExprState);
-
-				sstate->fxprstate.xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalScalarArrayOp;
-				sstate->fxprstate.args = (List *)
-					ExecInitExpr((Expr *) opexpr->args, parent);
-				sstate->fxprstate.func.fn_oid = InvalidOid;		/* not initialized */
-				sstate->fxprstate.funcReturnsSet = false;		/* not supported */
-				sstate->element_type = InvalidOid;		/* ditto */
-				state = (ExprState *) sstate;
-			}
-			break;
-		case T_BoolExpr:
-			{
-				BoolExpr   *boolexpr = (BoolExpr *) node;
-				BoolExprState *bstate = makeNode(BoolExprState);
-
-				switch (boolexpr->boolop)
-				{
-					case AND_EXPR:
-						bstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalAnd;
-						break;
-					case OR_EXPR:
-						bstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalOr;
-						break;
-					case NOT_EXPR:
-						bstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNot;
-						break;
-					default:
-						elog(ERROR, "unrecognized boolop: %d",
-							 (int) boolexpr->boolop);
-						break;
-				}
-				bstate->args = (List *)
-					ExecInitExpr((Expr *) boolexpr->args, parent);
-				state = (ExprState *) bstate;
-			}
-			break;
-		case T_SubPlan:
-			{
-				SubPlan    *subplan = (SubPlan *) node;
-				SubPlanState *sstate;
-
-				if (!parent)
-					elog(ERROR, "SubPlan found with no parent plan");
-
-				sstate = ExecInitSubPlan(subplan, parent);
-
-				/* Add SubPlanState nodes to parent->subPlan */
-				parent->subPlan = lappend(parent->subPlan, sstate);
-
-				state = (ExprState *) sstate;
-			}
-			break;
-		case T_AlternativeSubPlan:
-			{
-				AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
-				AlternativeSubPlanState *asstate;
-
-				if (!parent)
-					elog(ERROR, "AlternativeSubPlan found with no parent plan");
-
-				asstate = ExecInitAlternativeSubPlan(asplan, parent);
-
-				state = (ExprState *) asstate;
-			}
-			break;
-		case T_FieldSelect:
-			{
-				FieldSelect *fselect = (FieldSelect *) node;
-				FieldSelectState *fstate = makeNode(FieldSelectState);
-
-				fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldSelect;
-				fstate->arg = ExecInitExpr(fselect->arg, parent);
-				fstate->argdesc = NULL;
-				state = (ExprState *) fstate;
-			}
-			break;
-		case T_FieldStore:
-			{
-				FieldStore *fstore = (FieldStore *) node;
-				FieldStoreState *fstate = makeNode(FieldStoreState);
-
-				fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldStore;
-				fstate->arg = ExecInitExpr(fstore->arg, parent);
-				fstate->newvals = (List *) ExecInitExpr((Expr *) fstore->newvals, parent);
-				fstate->argdesc = NULL;
-				state = (ExprState *) fstate;
-			}
-			break;
-		case T_RelabelType:
-			{
-				RelabelType *relabel = (RelabelType *) node;
-				GenericExprState *gstate = makeNode(GenericExprState);
-
-				gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRelabelType;
-				gstate->arg = ExecInitExpr(relabel->arg, parent);
-				state = (ExprState *) gstate;
-			}
-			break;
-		case T_CoerceViaIO:
-			{
-				CoerceViaIO *iocoerce = (CoerceViaIO *) node;
-				CoerceViaIOState *iostate = makeNode(CoerceViaIOState);
-				Oid			iofunc;
-				bool		typisvarlena;
-
-				iostate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceViaIO;
-				iostate->arg = ExecInitExpr(iocoerce->arg, parent);
-				/* lookup the result type's input function */
-				getTypeInputInfo(iocoerce->resulttype, &iofunc,
-								 &iostate->intypioparam);
-				fmgr_info(iofunc, &iostate->infunc);
-				/* lookup the input type's output function */
-				getTypeOutputInfo(exprType((Node *) iocoerce->arg),
-								  &iofunc, &typisvarlena);
-				fmgr_info(iofunc, &iostate->outfunc);
-				state = (ExprState *) iostate;
-			}
-			break;
-		case T_ArrayCoerceExpr:
-			{
-				ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
-				ArrayCoerceExprState *astate = makeNode(ArrayCoerceExprState);
-
-				astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayCoerceExpr;
-				astate->arg = ExecInitExpr(acoerce->arg, parent);
-				astate->resultelemtype = get_element_type(acoerce->resulttype);
-				if (astate->resultelemtype == InvalidOid)
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-							 errmsg("target type is not an array")));
-				/* Arrays over domains aren't supported yet */
-				Assert(getBaseType(astate->resultelemtype) ==
-					   astate->resultelemtype);
-				astate->elemfunc.fn_oid = InvalidOid;	/* not initialized */
-				astate->amstate = (ArrayMapState *) palloc0(sizeof(ArrayMapState));
-				state = (ExprState *) astate;
-			}
-			break;
-		case T_ConvertRowtypeExpr:
-			{
-				ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node;
-				ConvertRowtypeExprState *cstate = makeNode(ConvertRowtypeExprState);
-
-				cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalConvertRowtype;
-				cstate->arg = ExecInitExpr(convert->arg, parent);
-				state = (ExprState *) cstate;
-			}
-			break;
-		case T_CaseExpr:
-			{
-				CaseExpr   *caseexpr = (CaseExpr *) node;
-				CaseExprState *cstate = makeNode(CaseExprState);
-				List	   *outlist = NIL;
-				ListCell   *l;
-
-				cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCase;
-				cstate->arg = ExecInitExpr(caseexpr->arg, parent);
-				foreach(l, caseexpr->args)
-				{
-					CaseWhen   *when = castNode(CaseWhen, lfirst(l));
-					CaseWhenState *wstate = makeNode(CaseWhenState);
-
-					wstate->xprstate.evalfunc = NULL;	/* not used */
-					wstate->xprstate.expr = (Expr *) when;
-					wstate->expr = ExecInitExpr(when->expr, parent);
-					wstate->result = ExecInitExpr(when->result, parent);
-					outlist = lappend(outlist, wstate);
-				}
-				cstate->args = outlist;
-				cstate->defresult = ExecInitExpr(caseexpr->defresult, parent);
-				if (caseexpr->arg)
-					cstate->argtyplen = get_typlen(exprType((Node *) caseexpr->arg));
-				state = (ExprState *) cstate;
-			}
-			break;
-		case T_ArrayExpr:
-			{
-				ArrayExpr  *arrayexpr = (ArrayExpr *) node;
-				ArrayExprState *astate = makeNode(ArrayExprState);
-				List	   *outlist = NIL;
-				ListCell   *l;
-
-				astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArray;
-				foreach(l, arrayexpr->elements)
-				{
-					Expr	   *e = (Expr *) lfirst(l);
-					ExprState  *estate;
-
-					estate = ExecInitExpr(e, parent);
-					outlist = lappend(outlist, estate);
-				}
-				astate->elements = outlist;
-				/* do one-time catalog lookup for type info */
-				get_typlenbyvalalign(arrayexpr->element_typeid,
-									 &astate->elemlength,
-									 &astate->elembyval,
-									 &astate->elemalign);
-				state = (ExprState *) astate;
-			}
-			break;
-		case T_RowExpr:
-			{
-				RowExpr    *rowexpr = (RowExpr *) node;
-				RowExprState *rstate = makeNode(RowExprState);
-				Form_pg_attribute *attrs;
-				List	   *outlist = NIL;
-				ListCell   *l;
-				int			i;
-
-				rstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRow;
-				/* Build tupdesc to describe result tuples */
-				if (rowexpr->row_typeid == RECORDOID)
-				{
-					/* generic record, use types of given expressions */
-					rstate->tupdesc = ExecTypeFromExprList(rowexpr->args);
-				}
-				else
-				{
-					/* it's been cast to a named type, use that */
-					rstate->tupdesc = lookup_rowtype_tupdesc_copy(rowexpr->row_typeid, -1);
-				}
-				/* In either case, adopt RowExpr's column aliases */
-				ExecTypeSetColNames(rstate->tupdesc, rowexpr->colnames);
-				/* Bless the tupdesc in case it's now of type RECORD */
-				BlessTupleDesc(rstate->tupdesc);
-				/* Set up evaluation, skipping any deleted columns */
-				Assert(list_length(rowexpr->args) <= rstate->tupdesc->natts);
-				attrs = rstate->tupdesc->attrs;
-				i = 0;
-				foreach(l, rowexpr->args)
-				{
-					Expr	   *e = (Expr *) lfirst(l);
-					ExprState  *estate;
-
-					if (!attrs[i]->attisdropped)
-					{
-						/*
-						 * Guard against ALTER COLUMN TYPE on rowtype since
-						 * the RowExpr was created.  XXX should we check
-						 * typmod too?	Not sure we can be sure it'll be the
-						 * same.
-						 */
-						if (exprType((Node *) e) != attrs[i]->atttypid)
-							ereport(ERROR,
-									(errcode(ERRCODE_DATATYPE_MISMATCH),
-									 errmsg("ROW() column has type %s instead of type %s",
-										format_type_be(exprType((Node *) e)),
-									   format_type_be(attrs[i]->atttypid))));
-					}
-					else
-					{
-						/*
-						 * Ignore original expression and insert a NULL. We
-						 * don't really care what type of NULL it is, so
-						 * always make an int4 NULL.
-						 */
-						e = (Expr *) makeNullConst(INT4OID, -1, InvalidOid);
-					}
-					estate = ExecInitExpr(e, parent);
-					outlist = lappend(outlist, estate);
-					i++;
-				}
-				rstate->args = outlist;
-				state = (ExprState *) rstate;
-			}
-			break;
-		case T_RowCompareExpr:
-			{
-				RowCompareExpr *rcexpr = (RowCompareExpr *) node;
-				RowCompareExprState *rstate = makeNode(RowCompareExprState);
-				int			nopers = list_length(rcexpr->opnos);
-				List	   *outlist;
-				ListCell   *l;
-				ListCell   *l2;
-				ListCell   *l3;
-				int			i;
-
-				rstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRowCompare;
-				Assert(list_length(rcexpr->largs) == nopers);
-				outlist = NIL;
-				foreach(l, rcexpr->largs)
-				{
-					Expr	   *e = (Expr *) lfirst(l);
-					ExprState  *estate;
-
-					estate = ExecInitExpr(e, parent);
-					outlist = lappend(outlist, estate);
-				}
-				rstate->largs = outlist;
-				Assert(list_length(rcexpr->rargs) == nopers);
-				outlist = NIL;
-				foreach(l, rcexpr->rargs)
-				{
-					Expr	   *e = (Expr *) lfirst(l);
-					ExprState  *estate;
-
-					estate = ExecInitExpr(e, parent);
-					outlist = lappend(outlist, estate);
-				}
-				rstate->rargs = outlist;
-				Assert(list_length(rcexpr->opfamilies) == nopers);
-				rstate->funcs = (FmgrInfo *) palloc(nopers * sizeof(FmgrInfo));
-				rstate->collations = (Oid *) palloc(nopers * sizeof(Oid));
-				i = 0;
-				forthree(l, rcexpr->opnos, l2, rcexpr->opfamilies, l3, rcexpr->inputcollids)
-				{
-					Oid			opno = lfirst_oid(l);
-					Oid			opfamily = lfirst_oid(l2);
-					Oid			inputcollid = lfirst_oid(l3);
-					int			strategy;
-					Oid			lefttype;
-					Oid			righttype;
-					Oid			proc;
-
-					get_op_opfamily_properties(opno, opfamily, false,
-											   &strategy,
-											   &lefttype,
-											   &righttype);
-					proc = get_opfamily_proc(opfamily,
-											 lefttype,
-											 righttype,
-											 BTORDER_PROC);
-
-					/*
-					 * If we enforced permissions checks on index support
-					 * functions, we'd need to make a check here.  But the
-					 * index support machinery doesn't do that, and neither
-					 * does this code.
-					 */
-					fmgr_info(proc, &(rstate->funcs[i]));
-					rstate->collations[i] = inputcollid;
-					i++;
-				}
-				state = (ExprState *) rstate;
-			}
-			break;
-		case T_CoalesceExpr:
-			{
-				CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
-				CoalesceExprState *cstate = makeNode(CoalesceExprState);
-				List	   *outlist = NIL;
-				ListCell   *l;
-
-				cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoalesce;
-				foreach(l, coalesceexpr->args)
-				{
-					Expr	   *e = (Expr *) lfirst(l);
-					ExprState  *estate;
-
-					estate = ExecInitExpr(e, parent);
-					outlist = lappend(outlist, estate);
-				}
-				cstate->args = outlist;
-				state = (ExprState *) cstate;
-			}
-			break;
-		case T_MinMaxExpr:
-			{
-				MinMaxExpr *minmaxexpr = (MinMaxExpr *) node;
-				MinMaxExprState *mstate = makeNode(MinMaxExprState);
-				List	   *outlist = NIL;
-				ListCell   *l;
-				TypeCacheEntry *typentry;
-
-				mstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalMinMax;
-				foreach(l, minmaxexpr->args)
-				{
-					Expr	   *e = (Expr *) lfirst(l);
-					ExprState  *estate;
-
-					estate = ExecInitExpr(e, parent);
-					outlist = lappend(outlist, estate);
-				}
-				mstate->args = outlist;
-				/* Look up the btree comparison function for the datatype */
-				typentry = lookup_type_cache(minmaxexpr->minmaxtype,
-											 TYPECACHE_CMP_PROC);
-				if (!OidIsValid(typentry->cmp_proc))
-					ereport(ERROR,
-							(errcode(ERRCODE_UNDEFINED_FUNCTION),
-							 errmsg("could not identify a comparison function for type %s",
-									format_type_be(minmaxexpr->minmaxtype))));
-
-				/*
-				 * If we enforced permissions checks on index support
-				 * functions, we'd need to make a check here.  But the index
-				 * support machinery doesn't do that, and neither does this
-				 * code.
-				 */
-				fmgr_info(typentry->cmp_proc, &(mstate->cfunc));
-				state = (ExprState *) mstate;
-			}
-			break;
-		case T_SQLValueFunction:
-			state = makeNode(ExprState);
-			state->evalfunc = ExecEvalSQLValueFunction;
-			break;
-		case T_XmlExpr:
-			{
-				XmlExpr    *xexpr = (XmlExpr *) node;
-				XmlExprState *xstate = makeNode(XmlExprState);
-				List	   *outlist;
-				ListCell   *arg;
-
-				xstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalXml;
-				outlist = NIL;
-				foreach(arg, xexpr->named_args)
-				{
-					Expr	   *e = (Expr *) lfirst(arg);
-					ExprState  *estate;
-
-					estate = ExecInitExpr(e, parent);
-					outlist = lappend(outlist, estate);
-				}
-				xstate->named_args = outlist;
-
-				outlist = NIL;
-				foreach(arg, xexpr->args)
-				{
-					Expr	   *e = (Expr *) lfirst(arg);
-					ExprState  *estate;
-
-					estate = ExecInitExpr(e, parent);
-					outlist = lappend(outlist, estate);
-				}
-				xstate->args = outlist;
-
-				state = (ExprState *) xstate;
-			}
-			break;
-		case T_NullTest:
-			{
-				NullTest   *ntest = (NullTest *) node;
-				NullTestState *nstate = makeNode(NullTestState);
-
-				nstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullTest;
-				nstate->arg = ExecInitExpr(ntest->arg, parent);
-				nstate->argdesc = NULL;
-				state = (ExprState *) nstate;
-			}
-			break;
-		case T_BooleanTest:
-			{
-				BooleanTest *btest = (BooleanTest *) node;
-				GenericExprState *gstate = makeNode(GenericExprState);
-
-				gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalBooleanTest;
-				gstate->arg = ExecInitExpr(btest->arg, parent);
-				state = (ExprState *) gstate;
-			}
-			break;
-		case T_CoerceToDomain:
-			{
-				CoerceToDomain *ctest = (CoerceToDomain *) node;
-				CoerceToDomainState *cstate = makeNode(CoerceToDomainState);
-
-				cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceToDomain;
-				cstate->arg = ExecInitExpr(ctest->arg, parent);
-				/* We spend an extra palloc to reduce header inclusions */
-				cstate->constraint_ref = (DomainConstraintRef *)
-					palloc(sizeof(DomainConstraintRef));
-				InitDomainConstraintRef(ctest->resulttype,
-										cstate->constraint_ref,
-										CurrentMemoryContext);
-				state = (ExprState *) cstate;
-			}
-			break;
-		case T_CurrentOfExpr:
-			state = makeNode(ExprState);
-			state->evalfunc = ExecEvalCurrentOfExpr;
-			break;
-		case T_TargetEntry:
-			{
-				TargetEntry *tle = (TargetEntry *) node;
-				GenericExprState *gstate = makeNode(GenericExprState);
-
-				gstate->xprstate.evalfunc = NULL;		/* not used */
-				gstate->arg = ExecInitExpr(tle->expr, parent);
-				state = (ExprState *) gstate;
-			}
-			break;
-		case T_List:
-			{
-				List	   *outlist = NIL;
-				ListCell   *l;
-
-				foreach(l, (List *) node)
-				{
-					outlist = lappend(outlist,
-									  ExecInitExpr((Expr *) lfirst(l),
-												   parent));
-				}
-				/* Don't fall through to the "common" code below */
-				return (ExprState *) outlist;
-			}
-		default:
-			elog(ERROR, "unrecognized node type: %d",
-				 (int) nodeTag(node));
-			state = NULL;		/* keep compiler quiet */
-			break;
-	}
-
-	/* Common code for all state-node types */
-	state->expr = node;
-
-	return state;
-}
-
-/*
- * ExecPrepareExpr --- initialize for expression execution outside a normal
- * Plan tree context.
- *
- * This differs from ExecInitExpr in that we don't assume the caller is
- * already running in the EState's per-query context.  Also, we run the
- * passed expression tree through expression_planner() to prepare it for
- * execution.  (In ordinary Plan trees the regular planning process will have
- * made the appropriate transformations on expressions, but for standalone
- * expressions this won't have happened.)
- */
-ExprState *
-ExecPrepareExpr(Expr *node, EState *estate)
-{
-	ExprState  *result;
-	MemoryContext oldcontext;
-
-	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
-
-	node = expression_planner(node);
-
-	result = ExecInitExpr(node, NULL);
-
-	MemoryContextSwitchTo(oldcontext);
-
-	return result;
-}
-
-
-/* ----------------------------------------------------------------
- *					 ExecQual / ExecTargetList / ExecProject
- * ----------------------------------------------------------------
- */
-
-/* ----------------------------------------------------------------
- *		ExecQual
- *
- *		Evaluates a conjunctive boolean expression (qual list) and
- *		returns true iff none of the subexpressions are false.
- *		(We also return true if the list is empty.)
- *
- *	If some of the subexpressions yield NULL but none yield FALSE,
- *	then the result of the conjunction is NULL (ie, unknown)
- *	according to three-valued boolean logic.  In this case,
- *	we return the value specified by the "resultForNull" parameter.
- *
- *	Callers evaluating WHERE clauses should pass resultForNull=FALSE,
- *	since SQL specifies that tuples with null WHERE results do not
- *	get selected.  On the other hand, callers evaluating constraint
- *	conditions should pass resultForNull=TRUE, since SQL also specifies
- *	that NULL constraint conditions are not failures.
- *
- *	NOTE: it would not be correct to use this routine to evaluate an
- *	AND subclause of a boolean expression; for that purpose, a NULL
- *	result must be returned as NULL so that it can be properly treated
- *	in the next higher operator (cf. ExecEvalAnd and ExecEvalOr).
- *	This routine is only used in contexts where a complete expression
- *	is being evaluated and we know that NULL can be treated the same
- *	as one boolean result or the other.
- *
- * ----------------------------------------------------------------
- */
-bool
-ExecQual(List *qual, ExprContext *econtext, bool resultForNull)
-{
-	bool		result;
-	MemoryContext oldContext;
-	ListCell   *l;
-
-	/*
-	 * debugging stuff
-	 */
-	EV_printf("ExecQual: qual is ");
-	EV_nodeDisplay(qual);
-	EV_printf("\n");
-
-	/*
-	 * Run in short-lived per-tuple context while computing expressions.
-	 */
-	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
-
-	/*
-	 * Evaluate the qual conditions one at a time.  If we find a FALSE result,
-	 * we can stop evaluating and return FALSE --- the AND result must be
-	 * FALSE.  Also, if we find a NULL result when resultForNull is FALSE, we
-	 * can stop and return FALSE --- the AND result must be FALSE or NULL in
-	 * that case, and the caller doesn't care which.
-	 *
-	 * If we get to the end of the list, we can return TRUE.  This will happen
-	 * when the AND result is indeed TRUE, or when the AND result is NULL (one
-	 * or more NULL subresult, with all the rest TRUE) and the caller has
-	 * specified resultForNull = TRUE.
-	 */
-	result = true;
-
-	foreach(l, qual)
-	{
-		ExprState  *clause = (ExprState *) lfirst(l);
-		Datum		expr_value;
-		bool		isNull;
-
-		expr_value = ExecEvalExpr(clause, econtext, &isNull);
-
-		if (isNull)
-		{
-			if (resultForNull == false)
-			{
-				result = false; /* treat NULL as FALSE */
-				break;
-			}
-		}
-		else
-		{
-			if (!DatumGetBool(expr_value))
-			{
-				result = false; /* definitely FALSE */
-				break;
-			}
-		}
-	}
-
-	MemoryContextSwitchTo(oldContext);
-
-	return result;
-}
-
-/*
- * Number of items in a tlist (including any resjunk items!)
- */
-int
-ExecTargetListLength(List *targetlist)
-{
-	/* This used to be more complex, but fjoins are dead */
-	return list_length(targetlist);
-}
-
-/*
- * Number of items in a tlist, not including any resjunk items
- */
-int
-ExecCleanTargetListLength(List *targetlist)
-{
-	int			len = 0;
-	ListCell   *tl;
-
-	foreach(tl, targetlist)
-	{
-		TargetEntry *curTle = castNode(TargetEntry, lfirst(tl));
-
-		if (!curTle->resjunk)
-			len++;
-	}
-	return len;
-}
-
-/*
- * ExecTargetList
- *		Evaluates a targetlist with respect to the given
- *		expression context.
- *
- * tupdesc must describe the rowtype of the expected result.
- *
- * Results are stored into the passed values and isnull arrays.
- *
- * Since fields of the result tuple might be multiply referenced in higher
- * plan nodes, we have to force any read/write expanded values to read-only
- * status.  It's a bit annoying to have to do that for every projected
- * expression; in the future, consider teaching the planner to detect
- * actually-multiply-referenced Vars and insert an expression node that
- * would do that only where really required.
- */
-static void
-ExecTargetList(List *targetlist,
-			   TupleDesc tupdesc,
-			   ExprContext *econtext,
-			   Datum *values,
-			   bool *isnull)
-{
-	Form_pg_attribute *att = tupdesc->attrs;
-	MemoryContext oldContext;
-	ListCell   *tl;
-
-	/*
-	 * Run in short-lived per-tuple context while computing expressions.
-	 */
-	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
-
-	/*
-	 * evaluate all the expressions in the target list
-	 */
-	foreach(tl, targetlist)
-	{
-		GenericExprState *gstate = (GenericExprState *) lfirst(tl);
-		TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr;
-		AttrNumber	resind = tle->resno - 1;
-
-		values[resind] = ExecEvalExpr(gstate->arg,
-									  econtext,
-									  &isnull[resind]);
-
-		values[resind] = MakeExpandedObjectReadOnly(values[resind],
-													isnull[resind],
-													att[resind]->attlen);
-	}
-
-	MemoryContextSwitchTo(oldContext);
-}
-
-/*
- * ExecProject
- *
- *		projects a tuple based on projection info and stores
- *		it in the previously specified tuple table slot.
- *
- *		Note: the result is always a virtual tuple; therefore it
- *		may reference the contents of the exprContext's scan tuples
- *		and/or temporary results constructed in the exprContext.
- *		If the caller wishes the result to be valid longer than that
- *		data will be valid, he must call ExecMaterializeSlot on the
- *		result slot.
- */
-TupleTableSlot *
-ExecProject(ProjectionInfo *projInfo)
-{
-	TupleTableSlot *slot;
-	ExprContext *econtext;
-	int			numSimpleVars;
-
-	/*
-	 * sanity checks
-	 */
-	Assert(projInfo != NULL);
-
-	/*
-	 * get the projection info we want
-	 */
-	slot = projInfo->pi_slot;
-	econtext = projInfo->pi_exprContext;
-
-	/*
-	 * Clear any former contents of the result slot.  This makes it safe for
-	 * us to use the slot's Datum/isnull arrays as workspace.
-	 */
-	ExecClearTuple(slot);
-
-	/*
-	 * Force extraction of all input values that we'll need.  The
-	 * Var-extraction loops below depend on this, and we are also prefetching
-	 * all attributes that will be referenced in the generic expressions.
-	 */
-	if (projInfo->pi_lastInnerVar > 0)
-		slot_getsomeattrs(econtext->ecxt_innertuple,
-						  projInfo->pi_lastInnerVar);
-	if (projInfo->pi_lastOuterVar > 0)
-		slot_getsomeattrs(econtext->ecxt_outertuple,
-						  projInfo->pi_lastOuterVar);
-	if (projInfo->pi_lastScanVar > 0)
-		slot_getsomeattrs(econtext->ecxt_scantuple,
-						  projInfo->pi_lastScanVar);
-
-	/*
-	 * Assign simple Vars to result by direct extraction of fields from source
-	 * slots ... a mite ugly, but fast ...
-	 */
-	numSimpleVars = projInfo->pi_numSimpleVars;
-	if (numSimpleVars > 0)
-	{
-		Datum	   *values = slot->tts_values;
-		bool	   *isnull = slot->tts_isnull;
-		int		   *varSlotOffsets = projInfo->pi_varSlotOffsets;
-		int		   *varNumbers = projInfo->pi_varNumbers;
-		int			i;
-
-		if (projInfo->pi_directMap)
-		{
-			/* especially simple case where vars go to output in order */
-			for (i = 0; i < numSimpleVars; i++)
-			{
-				char	   *slotptr = ((char *) econtext) + varSlotOffsets[i];
-				TupleTableSlot *varSlot = *((TupleTableSlot **) slotptr);
-				int			varNumber = varNumbers[i] - 1;
-
-				values[i] = varSlot->tts_values[varNumber];
-				isnull[i] = varSlot->tts_isnull[varNumber];
-			}
-		}
-		else
-		{
-			/* we have to pay attention to varOutputCols[] */
-			int		   *varOutputCols = projInfo->pi_varOutputCols;
-
-			for (i = 0; i < numSimpleVars; i++)
-			{
-				char	   *slotptr = ((char *) econtext) + varSlotOffsets[i];
-				TupleTableSlot *varSlot = *((TupleTableSlot **) slotptr);
-				int			varNumber = varNumbers[i] - 1;
-				int			varOutputCol = varOutputCols[i] - 1;
-
-				values[varOutputCol] = varSlot->tts_values[varNumber];
-				isnull[varOutputCol] = varSlot->tts_isnull[varNumber];
-			}
-		}
-	}
-
-	/*
-	 * If there are any generic expressions, evaluate them.
-	 */
-	if (projInfo->pi_targetlist)
-	{
-		ExecTargetList(projInfo->pi_targetlist,
-					   slot->tts_tupleDescriptor,
-					   econtext,
-					   slot->tts_values,
-					   slot->tts_isnull);
-	}
-
-	/*
-	 * Mark the result slot as containing a valid virtual tuple.
-	 */
-	return ExecStoreVirtualTuple(slot);
-}
diff --git a/src/backend/executor/execSRF.c b/src/backend/executor/execSRF.c
new file mode 100644
index 0000000000000000000000000000000000000000..4badd5c576fc12d07b7a2af3800c2cd7da3cf1ab
--- /dev/null
+++ b/src/backend/executor/execSRF.c
@@ -0,0 +1,924 @@
+/*-------------------------------------------------------------------------
+ *
+ * execSRF.c
+ *	  Routines implementing the API for set-returning functions
+ *
+ * This file serves nodeFunctionscan.c and nodeProjectSet.c, providing
+ * common code for calling set-returning functions according to the
+ * ReturnSetInfo API.
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/executor/execSRF.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "catalog/objectaccess.h"
+#include "executor/execdebug.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/parse_coerce.h"
+#include "pgstat.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/typcache.h"
+
+
+/* static function decls */
+static void init_sexpr(Oid foid, Oid input_collation, SetExprState *sexpr,
+		   MemoryContext sexprCxt, bool allowSRF, bool needDescForSRF);
+static void ShutdownSetExpr(Datum arg);
+static void ExecEvalFuncArgs(FunctionCallInfo fcinfo,
+				 List *argList, ExprContext *econtext);
+static void ExecPrepareTuplestoreResult(SetExprState *sexpr,
+							ExprContext *econtext,
+							Tuplestorestate *resultStore,
+							TupleDesc resultDesc);
+static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc);
+
+
+/*
+ * Prepare function call in FROM (ROWS FROM) for execution.
+ *
+ * This is used by nodeFunctionscan.c.
+ */
+SetExprState *
+ExecInitTableFunctionResult(Expr *expr,
+							ExprContext *econtext, PlanState *parent)
+{
+	SetExprState *state = makeNode(SetExprState);
+
+	state->funcReturnsSet = false;
+	state->expr = expr;
+	state->func.fn_oid = InvalidOid;
+
+	/*
+	 * Normally the passed expression tree will be a FuncExpr, since the
+	 * grammar only allows a function call at the top level of a table
+	 * function reference.  However, if the function doesn't return set then
+	 * the planner might have replaced the function call via constant-folding
+	 * or inlining.  So if we see any other kind of expression node, execute
+	 * it via the general ExecEvalExpr() code.  That code path will not
+	 * support set-returning functions buried in the expression, though.
+	 */
+	if (IsA(expr, FuncExpr))
+	{
+		FuncExpr   *func = (FuncExpr *) expr;
+
+		state->funcReturnsSet = func->funcretset;
+		state->args = ExecInitExprList(func->args, parent);
+
+		init_sexpr(func->funcid, func->inputcollid, state,
+				   econtext->ecxt_per_query_memory, func->funcretset, false);
+	}
+	else
+	{
+		state->elidedFuncState = ExecInitExpr(expr, parent);
+	}
+
+	return state;
+}
+
+/*
+ *		ExecMakeTableFunctionResult
+ *
+ * Evaluate a table function, producing a materialized result in a Tuplestore
+ * object.
+ *
+ * This is used by nodeFunctionscan.c.
+ */
+Tuplestorestate *
+ExecMakeTableFunctionResult(SetExprState *setexpr,
+							ExprContext *econtext,
+							MemoryContext argContext,
+							TupleDesc expectedDesc,
+							bool randomAccess)
+{
+	Tuplestorestate *tupstore = NULL;
+	TupleDesc	tupdesc = NULL;
+	Oid			funcrettype;
+	bool		returnsTuple;
+	bool		returnsSet = false;
+	FunctionCallInfoData fcinfo;
+	PgStat_FunctionCallUsage fcusage;
+	ReturnSetInfo rsinfo;
+	HeapTupleData tmptup;
+	MemoryContext callerContext;
+	MemoryContext oldcontext;
+	bool		first_time = true;
+
+	callerContext = CurrentMemoryContext;
+
+	funcrettype = exprType((Node *) setexpr->expr);
+
+	returnsTuple = type_is_rowtype(funcrettype);
+
+	/*
+	 * Prepare a resultinfo node for communication.  We always do this even if
+	 * not expecting a set result, so that we can pass expectedDesc.  In the
+	 * generic-expression case, the expression doesn't actually get to see the
+	 * resultinfo, but set it up anyway because we use some of the fields as
+	 * our own state variables.
+	 */
+	rsinfo.type = T_ReturnSetInfo;
+	rsinfo.econtext = econtext;
+	rsinfo.expectedDesc = expectedDesc;
+	rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize | SFRM_Materialize_Preferred);
+	if (randomAccess)
+		rsinfo.allowedModes |= (int) SFRM_Materialize_Random;
+	rsinfo.returnMode = SFRM_ValuePerCall;
+	/* isDone is filled below */
+	rsinfo.setResult = NULL;
+	rsinfo.setDesc = NULL;
+
+	/*
+	 * Normally the passed expression tree will be a SetExprState, since the
+	 * grammar only allows a function call at the top level of a table
+	 * function reference.  However, if the function doesn't return set then
+	 * the planner might have replaced the function call via constant-folding
+	 * or inlining.  So if we see any other kind of expression node, execute
+	 * it via the general ExecEvalExpr() code; the only difference is that we
+	 * don't get a chance to pass a special ReturnSetInfo to any functions
+	 * buried in the expression.
+	 */
+	if (!setexpr->elidedFuncState)
+	{
+		/*
+		 * This path is similar to ExecMakeFunctionResultSet.
+		 */
+		returnsSet = setexpr->funcReturnsSet;
+		InitFunctionCallInfoData(fcinfo, &(setexpr->func),
+								 list_length(setexpr->args),
+								 setexpr->fcinfo_data.fncollation,
+								 NULL, (Node *) &rsinfo);
+
+		/*
+		 * Evaluate the function's argument list.
+		 *
+		 * We can't do this in the per-tuple context: the argument values
+		 * would disappear when we reset that context in the inner loop.  And
+		 * the caller's CurrentMemoryContext is typically a query-lifespan
+		 * context, so we don't want to leak memory there.  We require the
+		 * caller to pass a separate memory context that can be used for this,
+		 * and can be reset each time through to avoid bloat.
+		 */
+		MemoryContextReset(argContext);
+		oldcontext = MemoryContextSwitchTo(argContext);
+		ExecEvalFuncArgs(&fcinfo, setexpr->args, econtext);
+		MemoryContextSwitchTo(oldcontext);
+
+		/*
+		 * If function is strict, and there are any NULL arguments, skip
+		 * calling the function and act like it returned NULL (or an empty
+		 * set, in the returns-set case).
+		 */
+		if (setexpr->func.fn_strict)
+		{
+			int			i;
+
+			for (i = 0; i < fcinfo.nargs; i++)
+			{
+				if (fcinfo.argnull[i])
+					goto no_function_result;
+			}
+		}
+	}
+	else
+	{
+		/* Treat setexpr as a generic expression */
+		InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+	}
+
+	/*
+	 * Switch to short-lived context for calling the function or expression.
+	 */
+	MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+	/*
+	 * Loop to handle the ValuePerCall protocol (which is also the same
+	 * behavior needed in the generic ExecEvalExpr path).
+	 */
+	for (;;)
+	{
+		Datum		result;
+
+		CHECK_FOR_INTERRUPTS();
+
+		/*
+		 * reset per-tuple memory context before each call of the function or
+		 * expression. This cleans up any local memory the function may leak
+		 * when called.
+		 */
+		ResetExprContext(econtext);
+
+		/* Call the function or expression one time */
+		if (!setexpr->elidedFuncState)
+		{
+			pgstat_init_function_usage(&fcinfo, &fcusage);
+
+			fcinfo.isnull = false;
+			rsinfo.isDone = ExprSingleResult;
+			result = FunctionCallInvoke(&fcinfo);
+
+			pgstat_end_function_usage(&fcusage,
+									  rsinfo.isDone != ExprMultipleResult);
+		}
+		else
+		{
+			result =
+				ExecEvalExpr(setexpr->elidedFuncState, econtext, &fcinfo.isnull);
+			rsinfo.isDone = ExprSingleResult;
+		}
+
+		/* Which protocol does function want to use? */
+		if (rsinfo.returnMode == SFRM_ValuePerCall)
+		{
+			/*
+			 * Check for end of result set.
+			 */
+			if (rsinfo.isDone == ExprEndResult)
+				break;
+
+			/*
+			 * If first time through, build tuplestore for result.  For a
+			 * scalar function result type, also make a suitable tupdesc.
+			 */
+			if (first_time)
+			{
+				oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+				tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
+				rsinfo.setResult = tupstore;
+				if (!returnsTuple)
+				{
+					tupdesc = CreateTemplateTupleDesc(1, false);
+					TupleDescInitEntry(tupdesc,
+									   (AttrNumber) 1,
+									   "column",
+									   funcrettype,
+									   -1,
+									   0);
+					rsinfo.setDesc = tupdesc;
+				}
+				MemoryContextSwitchTo(oldcontext);
+			}
+
+			/*
+			 * Store current resultset item.
+			 */
+			if (returnsTuple)
+			{
+				if (!fcinfo.isnull)
+				{
+					HeapTupleHeader td = DatumGetHeapTupleHeader(result);
+
+					if (tupdesc == NULL)
+					{
+						/*
+						 * This is the first non-NULL result from the
+						 * function.  Use the type info embedded in the
+						 * rowtype Datum to look up the needed tupdesc.  Make
+						 * a copy for the query.
+						 */
+						oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+						tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td),
+											   HeapTupleHeaderGetTypMod(td));
+						rsinfo.setDesc = tupdesc;
+						MemoryContextSwitchTo(oldcontext);
+					}
+					else
+					{
+						/*
+						 * Verify all later returned rows have same subtype;
+						 * necessary in case the type is RECORD.
+						 */
+						if (HeapTupleHeaderGetTypeId(td) != tupdesc->tdtypeid ||
+							HeapTupleHeaderGetTypMod(td) != tupdesc->tdtypmod)
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("rows returned by function are not all of the same row type")));
+					}
+
+					/*
+					 * tuplestore_puttuple needs a HeapTuple not a bare
+					 * HeapTupleHeader, but it doesn't need all the fields.
+					 */
+					tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
+					tmptup.t_data = td;
+
+					tuplestore_puttuple(tupstore, &tmptup);
+				}
+				else
+				{
+					/*
+					 * NULL result from a tuple-returning function; expand it
+					 * to a row of all nulls.  We rely on the expectedDesc to
+					 * form such rows.  (Note: this would be problematic if
+					 * tuplestore_putvalues saved the tdtypeid/tdtypmod from
+					 * the provided descriptor, since that might not match
+					 * what we get from the function itself.  But it doesn't.)
+					 */
+					int			natts = expectedDesc->natts;
+					bool	   *nullflags;
+
+					nullflags = (bool *) palloc(natts * sizeof(bool));
+					memset(nullflags, true, natts * sizeof(bool));
+					tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags);
+				}
+			}
+			else
+			{
+				/* Scalar-type case: just store the function result */
+				tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo.isnull);
+			}
+
+			/*
+			 * Are we done?
+			 */
+			if (rsinfo.isDone != ExprMultipleResult)
+				break;
+		}
+		else if (rsinfo.returnMode == SFRM_Materialize)
+		{
+			/* check we're on the same page as the function author */
+			if (!first_time || rsinfo.isDone != ExprSingleResult)
+				ereport(ERROR,
+						(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
+						 errmsg("table-function protocol for materialize mode was not followed")));
+			/* Done evaluating the set result */
+			break;
+		}
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
+					 errmsg("unrecognized table-function returnMode: %d",
+							(int) rsinfo.returnMode)));
+
+		first_time = false;
+	}
+
+no_function_result:
+
+	/*
+	 * If we got nothing from the function (ie, an empty-set or NULL result),
+	 * we have to create the tuplestore to return, and if it's a
+	 * non-set-returning function then insert a single all-nulls row.  As
+	 * above, we depend on the expectedDesc to manufacture the dummy row.
+	 */
+	if (rsinfo.setResult == NULL)
+	{
+		MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+		tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
+		rsinfo.setResult = tupstore;
+		if (!returnsSet)
+		{
+			int			natts = expectedDesc->natts;
+			bool	   *nullflags;
+
+			MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+			nullflags = (bool *) palloc(natts * sizeof(bool));
+			memset(nullflags, true, natts * sizeof(bool));
+			tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags);
+		}
+	}
+
+	/*
+	 * If function provided a tupdesc, cross-check it.  We only really need to
+	 * do this for functions returning RECORD, but might as well do it always.
+	 */
+	if (rsinfo.setDesc)
+	{
+		tupledesc_match(expectedDesc, rsinfo.setDesc);
+
+		/*
+		 * If it is a dynamically-allocated TupleDesc, free it: it is
+		 * typically allocated in a per-query context, so we must avoid
+		 * leaking it across multiple usages.
+		 */
+		if (rsinfo.setDesc->tdrefcount == -1)
+			FreeTupleDesc(rsinfo.setDesc);
+	}
+
+	MemoryContextSwitchTo(callerContext);
+
+	/* All done, pass back the tuplestore */
+	return rsinfo.setResult;
+}
+
+
+/*
+ * Prepare targetlist SRF function call for execution.
+ *
+ * This is used by nodeProjectSet.c.
+ */
+SetExprState *
+ExecInitFunctionResultSet(Expr *expr,
+						  ExprContext *econtext, PlanState *parent)
+{
+	SetExprState *state = makeNode(SetExprState);
+
+	state->funcReturnsSet = true;
+	state->expr = expr;
+	state->func.fn_oid = InvalidOid;
+
+	/*
+	 * Initialize metadata.  The expression node could be either a FuncExpr or
+	 * an OpExpr.
+	 */
+	if (IsA(expr, FuncExpr))
+	{
+		FuncExpr   *func = (FuncExpr *) expr;
+
+		state->args = ExecInitExprList(func->args, parent);
+		init_sexpr(func->funcid, func->inputcollid, state,
+				   econtext->ecxt_per_query_memory, true, true);
+	}
+	else if (IsA(expr, OpExpr))
+	{
+		OpExpr	   *op = (OpExpr *) expr;
+
+		state->args = ExecInitExprList(op->args, parent);
+		init_sexpr(op->opfuncid, op->inputcollid, state,
+				   econtext->ecxt_per_query_memory, true, true);
+	}
+	else
+		elog(ERROR, "unrecognized node type: %d",
+			 (int) nodeTag(expr));
+
+	/* shouldn't get here unless the selected function returns set */
+	Assert(state->func.fn_retset);
+
+	return state;
+}
+
+/*
+ *		ExecMakeFunctionResultSet
+ *
+ * Evaluate the arguments to a set-returning function and then call the
+ * function itself.  The argument expressions may not contain set-returning
+ * functions (the planner is supposed to have separated evaluation for those).
+ *
+ * This is used by nodeProjectSet.c.
+ */
+Datum
+ExecMakeFunctionResultSet(SetExprState *fcache,
+						  ExprContext *econtext,
+						  bool *isNull,
+						  ExprDoneCond *isDone)
+{
+	List	   *arguments;
+	Datum		result;
+	FunctionCallInfo fcinfo;
+	PgStat_FunctionCallUsage fcusage;
+	ReturnSetInfo rsinfo;
+	bool		callit;
+	int			i;
+
+restart:
+
+	/* Guard against stack overflow due to overly complex expressions */
+	check_stack_depth();
+
+	/*
+	 * If a previous call of the function returned a set result in the form of
+	 * a tuplestore, continue reading rows from the tuplestore until it's
+	 * empty.
+	 */
+	if (fcache->funcResultStore)
+	{
+		if (tuplestore_gettupleslot(fcache->funcResultStore, true, false,
+									fcache->funcResultSlot))
+		{
+			*isDone = ExprMultipleResult;
+			if (fcache->funcReturnsTuple)
+			{
+				/* We must return the whole tuple as a Datum. */
+				*isNull = false;
+				return ExecFetchSlotTupleDatum(fcache->funcResultSlot);
+			}
+			else
+			{
+				/* Extract the first column and return it as a scalar. */
+				return slot_getattr(fcache->funcResultSlot, 1, isNull);
+			}
+		}
+		/* Exhausted the tuplestore, so clean up */
+		tuplestore_end(fcache->funcResultStore);
+		fcache->funcResultStore = NULL;
+		*isDone = ExprEndResult;
+		*isNull = true;
+		return (Datum) 0;
+	}
+
+	/*
+	 * arguments is a list of expressions to evaluate before passing to 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 fcinfo.
+	 */
+	fcinfo = &fcache->fcinfo_data;
+	arguments = fcache->args;
+	if (!fcache->setArgsValid)
+		ExecEvalFuncArgs(fcinfo, arguments, econtext);
+	else
+	{
+		/* Reset flag (we may set it again below) */
+		fcache->setArgsValid = false;
+	}
+
+	/*
+	 * Now call the function, passing the evaluated parameter values.
+	 */
+
+	/* Prepare a resultinfo node for communication. */
+	fcinfo->resultinfo = (Node *) &rsinfo;
+	rsinfo.type = T_ReturnSetInfo;
+	rsinfo.econtext = econtext;
+	rsinfo.expectedDesc = fcache->funcResultDesc;
+	rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
+	/* note we do not set SFRM_Materialize_Random or _Preferred */
+	rsinfo.returnMode = SFRM_ValuePerCall;
+	/* isDone is filled below */
+	rsinfo.setResult = NULL;
+	rsinfo.setDesc = NULL;
+
+	/*
+	 * If function is strict, and there are any NULL arguments, skip calling
+	 * the function.
+	 */
+	callit = true;
+	if (fcache->func.fn_strict)
+	{
+		for (i = 0; i < fcinfo->nargs; i++)
+		{
+			if (fcinfo->argnull[i])
+			{
+				callit = false;
+				break;
+			}
+		}
+	}
+
+	if (callit)
+	{
+		pgstat_init_function_usage(fcinfo, &fcusage);
+
+		fcinfo->isnull = false;
+		rsinfo.isDone = ExprSingleResult;
+		result = FunctionCallInvoke(fcinfo);
+		*isNull = fcinfo->isnull;
+		*isDone = rsinfo.isDone;
+
+		pgstat_end_function_usage(&fcusage,
+								  rsinfo.isDone != ExprMultipleResult);
+	}
+	else
+	{
+		/* for a strict SRF, result for NULL is an empty set */
+		result = (Datum) 0;
+		*isNull = true;
+		*isDone = ExprEndResult;
+	}
+
+	/* Which protocol does function want to use? */
+	if (rsinfo.returnMode == SFRM_ValuePerCall)
+	{
+		if (*isDone != ExprEndResult)
+		{
+			/*
+			 * Save the current argument values to re-use on the next call.
+			 */
+			if (*isDone == ExprMultipleResult)
+			{
+				fcache->setArgsValid = true;
+				/* Register cleanup callback if we didn't already */
+				if (!fcache->shutdown_reg)
+				{
+					RegisterExprContextCallback(econtext,
+												ShutdownSetExpr,
+												PointerGetDatum(fcache));
+					fcache->shutdown_reg = true;
+				}
+			}
+		}
+	}
+	else if (rsinfo.returnMode == SFRM_Materialize)
+	{
+		/* check we're on the same page as the function author */
+		if (rsinfo.isDone != ExprSingleResult)
+			ereport(ERROR,
+					(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
+					 errmsg("table-function protocol for materialize mode was not followed")));
+		if (rsinfo.setResult != NULL)
+		{
+			/* prepare to return values from the tuplestore */
+			ExecPrepareTuplestoreResult(fcache, econtext,
+										rsinfo.setResult,
+										rsinfo.setDesc);
+			/* loop back to top to start returning from tuplestore */
+			goto restart;
+		}
+		/* if setResult was left null, treat it as empty set */
+		*isDone = ExprEndResult;
+		*isNull = true;
+		result = (Datum) 0;
+	}
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
+				 errmsg("unrecognized table-function returnMode: %d",
+						(int) rsinfo.returnMode)));
+
+	return result;
+}
+
+
+/*
+ * init_sexpr - initialize a SetExprState node during first use
+ */
+static void
+init_sexpr(Oid foid, Oid input_collation, SetExprState *sexpr,
+		   MemoryContext sexprCxt, bool allowSRF, bool needDescForSRF)
+{
+	AclResult	aclresult;
+
+	/* Check permission to call function */
+	aclresult = pg_proc_aclcheck(foid, GetUserId(), ACL_EXECUTE);
+	if (aclresult != ACLCHECK_OK)
+		aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(foid));
+	InvokeFunctionExecuteHook(foid);
+
+	/*
+	 * Safety check on nargs.  Under normal circumstances this should never
+	 * fail, as parser should check sooner.  But possibly it might fail if
+	 * server has been compiled with FUNC_MAX_ARGS smaller than some functions
+	 * declared in pg_proc?
+	 */
+	if (list_length(sexpr->args) > FUNC_MAX_ARGS)
+		ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+			 errmsg_plural("cannot pass more than %d argument to a function",
+						   "cannot pass more than %d arguments to a function",
+						   FUNC_MAX_ARGS,
+						   FUNC_MAX_ARGS)));
+
+	/* Set up the primary fmgr lookup information */
+	fmgr_info_cxt(foid, &(sexpr->func), sexprCxt);
+	fmgr_info_set_expr((Node *) sexpr->expr, &(sexpr->func));
+
+	/* Initialize the function call parameter struct as well */
+	InitFunctionCallInfoData(sexpr->fcinfo_data, &(sexpr->func),
+							 list_length(sexpr->args),
+							 input_collation, NULL, NULL);
+
+	/* If function returns set, check if that's allowed by caller */
+	if (sexpr->func.fn_retset && !allowSRF)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+
+	/* Otherwise, caller should have marked the sexpr correctly */
+	Assert(sexpr->func.fn_retset == sexpr->funcReturnsSet);
+
+	/* If function returns set, prepare expected tuple descriptor */
+	if (sexpr->func.fn_retset && needDescForSRF)
+	{
+		TypeFuncClass functypclass;
+		Oid			funcrettype;
+		TupleDesc	tupdesc;
+		MemoryContext oldcontext;
+
+		functypclass = get_expr_result_type(sexpr->func.fn_expr,
+											&funcrettype,
+											&tupdesc);
+
+		/* Must save tupdesc in sexpr's context */
+		oldcontext = MemoryContextSwitchTo(sexprCxt);
+
+		if (functypclass == TYPEFUNC_COMPOSITE)
+		{
+			/* Composite data type, e.g. a table's row type */
+			Assert(tupdesc);
+			/* Must copy it out of typcache for safety */
+			sexpr->funcResultDesc = CreateTupleDescCopy(tupdesc);
+			sexpr->funcReturnsTuple = true;
+		}
+		else if (functypclass == TYPEFUNC_SCALAR)
+		{
+			/* Base data type, i.e. scalar */
+			tupdesc = CreateTemplateTupleDesc(1, false);
+			TupleDescInitEntry(tupdesc,
+							   (AttrNumber) 1,
+							   NULL,
+							   funcrettype,
+							   -1,
+							   0);
+			sexpr->funcResultDesc = tupdesc;
+			sexpr->funcReturnsTuple = false;
+		}
+		else if (functypclass == TYPEFUNC_RECORD)
+		{
+			/* This will work if function doesn't need an expectedDesc */
+			sexpr->funcResultDesc = NULL;
+			sexpr->funcReturnsTuple = true;
+		}
+		else
+		{
+			/* Else, we will fail if function needs an expectedDesc */
+			sexpr->funcResultDesc = NULL;
+		}
+
+		MemoryContextSwitchTo(oldcontext);
+	}
+	else
+		sexpr->funcResultDesc = NULL;
+
+	/* Initialize additional state */
+	sexpr->funcResultStore = NULL;
+	sexpr->funcResultSlot = NULL;
+	sexpr->shutdown_reg = false;
+}
+
+/*
+ * callback function in case a SetExprState needs to be shut down before it
+ * has been run to completion
+ */
+static void
+ShutdownSetExpr(Datum arg)
+{
+	SetExprState *sexpr = castNode(SetExprState, DatumGetPointer(arg));
+
+	/* If we have a slot, make sure it's let go of any tuplestore pointer */
+	if (sexpr->funcResultSlot)
+		ExecClearTuple(sexpr->funcResultSlot);
+
+	/* Release any open tuplestore */
+	if (sexpr->funcResultStore)
+		tuplestore_end(sexpr->funcResultStore);
+	sexpr->funcResultStore = NULL;
+
+	/* Clear any active set-argument state */
+	sexpr->setArgsValid = false;
+
+	/* execUtils will deregister the callback... */
+	sexpr->shutdown_reg = false;
+}
+
+/*
+ * Evaluate arguments for a function.
+ */
+static void
+ExecEvalFuncArgs(FunctionCallInfo fcinfo,
+				 List *argList,
+				 ExprContext *econtext)
+{
+	int			i;
+	ListCell   *arg;
+
+	i = 0;
+	foreach(arg, argList)
+	{
+		ExprState  *argstate = (ExprState *) lfirst(arg);
+
+		fcinfo->arg[i] = ExecEvalExpr(argstate,
+									  econtext,
+									  &fcinfo->argnull[i]);
+		i++;
+	}
+
+	Assert(i == fcinfo->nargs);
+}
+
+/*
+ *		ExecPrepareTuplestoreResult
+ *
+ * Subroutine for ExecMakeFunctionResultSet: prepare to extract rows from a
+ * tuplestore function result.  We must set up a funcResultSlot (unless
+ * already done in a previous call cycle) and verify that the function
+ * returned the expected tuple descriptor.
+ */
+static void
+ExecPrepareTuplestoreResult(SetExprState *sexpr,
+							ExprContext *econtext,
+							Tuplestorestate *resultStore,
+							TupleDesc resultDesc)
+{
+	sexpr->funcResultStore = resultStore;
+
+	if (sexpr->funcResultSlot == NULL)
+	{
+		/* Create a slot so we can read data out of the tuplestore */
+		TupleDesc	slotDesc;
+		MemoryContext oldcontext;
+
+		oldcontext = MemoryContextSwitchTo(sexpr->func.fn_mcxt);
+
+		/*
+		 * If we were not able to determine the result rowtype from context,
+		 * and the function didn't return a tupdesc, we have to fail.
+		 */
+		if (sexpr->funcResultDesc)
+			slotDesc = sexpr->funcResultDesc;
+		else if (resultDesc)
+		{
+			/* don't assume resultDesc is long-lived */
+			slotDesc = CreateTupleDescCopy(resultDesc);
+		}
+		else
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("function returning setof record called in "
+							"context that cannot accept type record")));
+			slotDesc = NULL;	/* keep compiler quiet */
+		}
+
+		sexpr->funcResultSlot = MakeSingleTupleTableSlot(slotDesc);
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	/*
+	 * If function provided a tupdesc, cross-check it.  We only really need to
+	 * do this for functions returning RECORD, but might as well do it always.
+	 */
+	if (resultDesc)
+	{
+		if (sexpr->funcResultDesc)
+			tupledesc_match(sexpr->funcResultDesc, resultDesc);
+
+		/*
+		 * If it is a dynamically-allocated TupleDesc, free it: it is
+		 * typically allocated in a per-query context, so we must avoid
+		 * leaking it across multiple usages.
+		 */
+		if (resultDesc->tdrefcount == -1)
+			FreeTupleDesc(resultDesc);
+	}
+
+	/* Register cleanup callback if we didn't already */
+	if (!sexpr->shutdown_reg)
+	{
+		RegisterExprContextCallback(econtext,
+									ShutdownSetExpr,
+									PointerGetDatum(sexpr));
+		sexpr->shutdown_reg = true;
+	}
+}
+
+/*
+ * Check that function result tuple type (src_tupdesc) matches or can
+ * be considered to match what the query expects (dst_tupdesc). If
+ * they don't match, ereport.
+ *
+ * We really only care about number of attributes and data type.
+ * Also, we can ignore type mismatch on columns that are dropped in the
+ * destination type, so long as the physical storage matches.  This is
+ * helpful in some cases involving out-of-date cached plans.
+ */
+static void
+tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc)
+{
+	int			i;
+
+	if (dst_tupdesc->natts != src_tupdesc->natts)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("function return row and query-specified return row do not match"),
+				 errdetail_plural("Returned row contains %d attribute, but query expects %d.",
+				"Returned row contains %d attributes, but query expects %d.",
+								  src_tupdesc->natts,
+								  src_tupdesc->natts, dst_tupdesc->natts)));
+
+	for (i = 0; i < dst_tupdesc->natts; i++)
+	{
+		Form_pg_attribute dattr = dst_tupdesc->attrs[i];
+		Form_pg_attribute sattr = src_tupdesc->attrs[i];
+
+		if (IsBinaryCoercible(sattr->atttypid, dattr->atttypid))
+			continue;			/* no worries */
+		if (!dattr->attisdropped)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("function return row and query-specified return row do not match"),
+					 errdetail("Returned type %s at ordinal position %d, but query expects %s.",
+							   format_type_be(sattr->atttypid),
+							   i + 1,
+							   format_type_be(dattr->atttypid))));
+
+		if (dattr->attlen != sattr->attlen ||
+			dattr->attalign != sattr->attalign)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("function return row and query-specified return row do not match"),
+					 errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
+							   i + 1)));
+	}
+}
diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c
index 65196795d7004831e6959e3cef610b8928796471..4f131b3ee0df60619e1e7b332470f875092b0731 100644
--- a/src/backend/executor/execScan.c
+++ b/src/backend/executor/execScan.c
@@ -123,7 +123,7 @@ ExecScan(ScanState *node,
 		 ExecScanRecheckMtd recheckMtd)
 {
 	ExprContext *econtext;
-	List	   *qual;
+	ExprState  *qual;
 	ProjectionInfo *projInfo;
 
 	/*
@@ -170,7 +170,7 @@ ExecScan(ScanState *node,
 		if (TupIsNull(slot))
 		{
 			if (projInfo)
-				return ExecClearTuple(projInfo->pi_slot);
+				return ExecClearTuple(projInfo->pi_state.resultslot);
 			else
 				return slot;
 		}
@@ -183,11 +183,11 @@ ExecScan(ScanState *node,
 		/*
 		 * check that the current tuple satisfies the qual-clause
 		 *
-		 * check for non-nil qual here to avoid a function call to ExecQual()
-		 * when the qual is nil ... saves only a few cycles, but they add up
+		 * check for non-null qual here to avoid a function call to ExecQual()
+		 * when the qual is null ... saves only a few cycles, but they add up
 		 * ...
 		 */
-		if (!qual || ExecQual(qual, econtext, false))
+		if (qual == NULL || ExecQual(qual, econtext))
 		{
 			/*
 			 * Found a satisfactory scan tuple.
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index a72cffeb6e5c8de949e02ffc78ef757b9fd138fc..2613ffbb718a6fc9ddbbbfb77da791648b2aacc8 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -31,6 +31,9 @@
  *		RegisterExprContextCallback    Register function shutdown callback
  *		UnregisterExprContextCallback  Deregister function shutdown callback
  *
+ *		GetAttributeByName		Runtime extraction of columns from tuples.
+ *		GetAttributeByNum
+ *
  *	 NOTES
  *		This file has traditionally been the place to stick misc.
  *		executor support stuff that doesn't really go anyplace else.
@@ -44,11 +47,12 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
 #include "storage/lmgr.h"
+#include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/typcache.h"
 
 
-static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
 static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
 
 
@@ -464,186 +468,6 @@ ExecGetResultType(PlanState *planstate)
 	return slot->tts_tupleDescriptor;
 }
 
-/* ----------------
- *		ExecBuildProjectionInfo
- *
- * Build a ProjectionInfo node for evaluating the given tlist in the given
- * econtext, and storing the result into the tuple slot.  (Caller must have
- * ensured that tuple slot has a descriptor matching the tlist!)  Note that
- * the given tlist should be a list of ExprState nodes, not Expr nodes.
- *
- * inputDesc can be NULL, but if it is not, we check to see whether simple
- * Vars in the tlist match the descriptor.  It is important to provide
- * inputDesc for relation-scan plan nodes, as a cross check that the relation
- * hasn't been changed since the plan was made.  At higher levels of a plan,
- * there is no need to recheck.
- * ----------------
- */
-ProjectionInfo *
-ExecBuildProjectionInfo(List *targetList,
-						ExprContext *econtext,
-						TupleTableSlot *slot,
-						TupleDesc inputDesc)
-{
-	ProjectionInfo *projInfo = makeNode(ProjectionInfo);
-	int			len = ExecTargetListLength(targetList);
-	int		   *workspace;
-	int		   *varSlotOffsets;
-	int		   *varNumbers;
-	int		   *varOutputCols;
-	List	   *exprlist;
-	int			numSimpleVars;
-	bool		directMap;
-	ListCell   *tl;
-
-	projInfo->pi_exprContext = econtext;
-	projInfo->pi_slot = slot;
-	/* since these are all int arrays, we need do just one palloc */
-	workspace = (int *) palloc(len * 3 * sizeof(int));
-	projInfo->pi_varSlotOffsets = varSlotOffsets = workspace;
-	projInfo->pi_varNumbers = varNumbers = workspace + len;
-	projInfo->pi_varOutputCols = varOutputCols = workspace + len * 2;
-	projInfo->pi_lastInnerVar = 0;
-	projInfo->pi_lastOuterVar = 0;
-	projInfo->pi_lastScanVar = 0;
-
-	/*
-	 * We separate the target list elements into simple Var references and
-	 * expressions which require the full ExecTargetList machinery.  To be a
-	 * simple Var, a Var has to be a user attribute and not mismatch the
-	 * inputDesc.  (Note: if there is a type mismatch then ExecEvalScalarVar
-	 * will probably throw an error at runtime, but we leave that to it.)
-	 */
-	exprlist = NIL;
-	numSimpleVars = 0;
-	directMap = true;
-	foreach(tl, targetList)
-	{
-		GenericExprState *gstate = (GenericExprState *) lfirst(tl);
-		Var		   *variable = (Var *) gstate->arg->expr;
-		bool		isSimpleVar = false;
-
-		if (variable != NULL &&
-			IsA(variable, Var) &&
-			variable->varattno > 0)
-		{
-			if (!inputDesc)
-				isSimpleVar = true;		/* can't check type, assume OK */
-			else if (variable->varattno <= inputDesc->natts)
-			{
-				Form_pg_attribute attr;
-
-				attr = inputDesc->attrs[variable->varattno - 1];
-				if (!attr->attisdropped && variable->vartype == attr->atttypid)
-					isSimpleVar = true;
-			}
-		}
-
-		if (isSimpleVar)
-		{
-			TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr;
-			AttrNumber	attnum = variable->varattno;
-
-			varNumbers[numSimpleVars] = attnum;
-			varOutputCols[numSimpleVars] = tle->resno;
-			if (tle->resno != numSimpleVars + 1)
-				directMap = false;
-
-			switch (variable->varno)
-			{
-				case INNER_VAR:
-					varSlotOffsets[numSimpleVars] = offsetof(ExprContext,
-															 ecxt_innertuple);
-					if (projInfo->pi_lastInnerVar < attnum)
-						projInfo->pi_lastInnerVar = attnum;
-					break;
-
-				case OUTER_VAR:
-					varSlotOffsets[numSimpleVars] = offsetof(ExprContext,
-															 ecxt_outertuple);
-					if (projInfo->pi_lastOuterVar < attnum)
-						projInfo->pi_lastOuterVar = attnum;
-					break;
-
-					/* INDEX_VAR is handled by default case */
-
-				default:
-					varSlotOffsets[numSimpleVars] = offsetof(ExprContext,
-															 ecxt_scantuple);
-					if (projInfo->pi_lastScanVar < attnum)
-						projInfo->pi_lastScanVar = attnum;
-					break;
-			}
-			numSimpleVars++;
-		}
-		else
-		{
-			/* Not a simple variable, add it to generic targetlist */
-			exprlist = lappend(exprlist, gstate);
-			/* Examine expr to include contained Vars in lastXXXVar counts */
-			get_last_attnums((Node *) variable, projInfo);
-		}
-	}
-	projInfo->pi_targetlist = exprlist;
-	projInfo->pi_numSimpleVars = numSimpleVars;
-	projInfo->pi_directMap = directMap;
-
-	return projInfo;
-}
-
-/*
- * get_last_attnums: expression walker for ExecBuildProjectionInfo
- *
- *	Update the lastXXXVar counts to be at least as large as the largest
- *	attribute numbers found in the expression
- */
-static bool
-get_last_attnums(Node *node, ProjectionInfo *projInfo)
-{
-	if (node == NULL)
-		return false;
-	if (IsA(node, Var))
-	{
-		Var		   *variable = (Var *) node;
-		AttrNumber	attnum = variable->varattno;
-
-		switch (variable->varno)
-		{
-			case INNER_VAR:
-				if (projInfo->pi_lastInnerVar < attnum)
-					projInfo->pi_lastInnerVar = attnum;
-				break;
-
-			case OUTER_VAR:
-				if (projInfo->pi_lastOuterVar < attnum)
-					projInfo->pi_lastOuterVar = attnum;
-				break;
-
-				/* INDEX_VAR is handled by default case */
-
-			default:
-				if (projInfo->pi_lastScanVar < attnum)
-					projInfo->pi_lastScanVar = attnum;
-				break;
-		}
-		return false;
-	}
-
-	/*
-	 * Don't examine the arguments or filters of Aggrefs or WindowFuncs,
-	 * because those do not represent expressions to be evaluated within the
-	 * overall targetlist's econtext.  GroupingFunc arguments are never
-	 * evaluated at all.
-	 */
-	if (IsA(node, Aggref))
-		return false;
-	if (IsA(node, WindowFunc))
-		return false;
-	if (IsA(node, GroupingFunc))
-		return false;
-	return expression_tree_walker(node, get_last_attnums,
-								  (void *) projInfo);
-}
 
 /* ----------------
  *		ExecAssignProjectionInfo
@@ -659,9 +483,10 @@ ExecAssignProjectionInfo(PlanState *planstate,
 						 TupleDesc inputDesc)
 {
 	planstate->ps_ProjInfo =
-		ExecBuildProjectionInfo(planstate->targetlist,
+		ExecBuildProjectionInfo(planstate->plan->targetlist,
 								planstate->ps_ExprContext,
 								planstate->ps_ResultTupleSlot,
+								planstate,
 								inputDesc);
 }
 
@@ -1009,3 +834,152 @@ ExecLockNonLeafAppendTables(List *partitioned_rels, EState *estate)
 		}
 	}
 }
+
+/*
+ *		GetAttributeByName
+ *		GetAttributeByNum
+ *
+ *		These functions return the value of the requested attribute
+ *		out of the given tuple Datum.
+ *		C functions which take a tuple as an argument are expected
+ *		to use these.  Ex: overpaid(EMP) might call GetAttributeByNum().
+ *		Note: these are actually rather slow because they do a typcache
+ *		lookup on each call.
+ */
+Datum
+GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull)
+{
+	AttrNumber	attrno;
+	Datum		result;
+	Oid			tupType;
+	int32		tupTypmod;
+	TupleDesc	tupDesc;
+	HeapTupleData tmptup;
+	int			i;
+
+	if (attname == NULL)
+		elog(ERROR, "invalid attribute name");
+
+	if (isNull == NULL)
+		elog(ERROR, "a NULL isNull pointer was passed");
+
+	if (tuple == NULL)
+	{
+		/* Kinda bogus but compatible with old behavior... */
+		*isNull = true;
+		return (Datum) 0;
+	}
+
+	tupType = HeapTupleHeaderGetTypeId(tuple);
+	tupTypmod = HeapTupleHeaderGetTypMod(tuple);
+	tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+
+	attrno = InvalidAttrNumber;
+	for (i = 0; i < tupDesc->natts; i++)
+	{
+		if (namestrcmp(&(tupDesc->attrs[i]->attname), attname) == 0)
+		{
+			attrno = tupDesc->attrs[i]->attnum;
+			break;
+		}
+	}
+
+	if (attrno == InvalidAttrNumber)
+		elog(ERROR, "attribute \"%s\" does not exist", attname);
+
+	/*
+	 * heap_getattr needs a HeapTuple not a bare HeapTupleHeader.  We set all
+	 * the fields in the struct just in case user tries to inspect system
+	 * columns.
+	 */
+	tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
+	ItemPointerSetInvalid(&(tmptup.t_self));
+	tmptup.t_tableOid = InvalidOid;
+	tmptup.t_data = tuple;
+
+	result = heap_getattr(&tmptup,
+						  attrno,
+						  tupDesc,
+						  isNull);
+
+	ReleaseTupleDesc(tupDesc);
+
+	return result;
+}
+
+Datum
+GetAttributeByNum(HeapTupleHeader tuple,
+				  AttrNumber attrno,
+				  bool *isNull)
+{
+	Datum		result;
+	Oid			tupType;
+	int32		tupTypmod;
+	TupleDesc	tupDesc;
+	HeapTupleData tmptup;
+
+	if (!AttributeNumberIsValid(attrno))
+		elog(ERROR, "invalid attribute number %d", attrno);
+
+	if (isNull == NULL)
+		elog(ERROR, "a NULL isNull pointer was passed");
+
+	if (tuple == NULL)
+	{
+		/* Kinda bogus but compatible with old behavior... */
+		*isNull = true;
+		return (Datum) 0;
+	}
+
+	tupType = HeapTupleHeaderGetTypeId(tuple);
+	tupTypmod = HeapTupleHeaderGetTypMod(tuple);
+	tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+
+	/*
+	 * heap_getattr needs a HeapTuple not a bare HeapTupleHeader.  We set all
+	 * the fields in the struct just in case user tries to inspect system
+	 * columns.
+	 */
+	tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
+	ItemPointerSetInvalid(&(tmptup.t_self));
+	tmptup.t_tableOid = InvalidOid;
+	tmptup.t_data = tuple;
+
+	result = heap_getattr(&tmptup,
+						  attrno,
+						  tupDesc,
+						  isNull);
+
+	ReleaseTupleDesc(tupDesc);
+
+	return result;
+}
+
+/*
+ * Number of items in a tlist (including any resjunk items!)
+ */
+int
+ExecTargetListLength(List *targetlist)
+{
+	/* This used to be more complex, but fjoins are dead */
+	return list_length(targetlist);
+}
+
+/*
+ * Number of items in a tlist, not including any resjunk items
+ */
+int
+ExecCleanTargetListLength(List *targetlist)
+{
+	int			len = 0;
+	ListCell   *tl;
+
+	foreach(tl, targetlist)
+	{
+		TargetEntry *curTle = castNode(TargetEntry, lfirst(tl));
+
+		if (!curTle->resjunk)
+			len++;
+	}
+	return len;
+}
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 65e52cca630f40fec1d28875f1904f90d69e5a98..3e4b0191c7ecdef3005ee2bd590875dbe54db8a3 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1279,7 +1279,7 @@ fmgr_sql(PG_FUNCTION_ARGS)
 			rsi->returnMode = SFRM_Materialize;
 			rsi->setResult = fcache->tstore;
 			fcache->tstore = NULL;
-			/* must copy desc because execQual will free it */
+			/* must copy desc because execSRF.c will free it */
 			if (fcache->junkFilter)
 				rsi->setDesc = CreateTupleDescCopy(fcache->junkFilter->jf_cleanTupType);
 
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 3207ee460c270290dd305bdf46720f3d9b56b9be..471acc4b3ec91db7a4c5a6670dbed526cc244560 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -1639,7 +1639,7 @@ project_aggregates(AggState *aggstate)
 	/*
 	 * Check the qual (HAVING clause); if the group does not match, ignore it.
 	 */
-	if (ExecQual(aggstate->ss.ps.qual, econtext, false))
+	if (ExecQual(aggstate->ss.ps.qual, econtext))
 	{
 		/*
 		 * Form and return projection tuple using the aggregate results and
@@ -2501,18 +2501,17 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 *
-	 * Note: ExecInitExpr finds Aggrefs for us, and also checks that no aggs
-	 * contain other agg calls in their arguments.  This would make no sense
-	 * under SQL semantics anyway (and it's forbidden by the spec). Because
-	 * that is true, we don't need to worry about evaluating the aggs in any
-	 * particular order.
+	 * We rely on the parser to have checked that no aggs contain other agg
+	 * calls in their arguments.  This would make no sense under SQL semantics
+	 * (and it's forbidden by the spec).  Because it is true, we don't need to
+	 * worry about evaluating the aggs in any particular order.
+	 *
+	 * Note: execExpr.c finds Aggrefs for us, and adds their AggrefExprState
+	 * nodes to aggstate->aggs.  Aggrefs in the qual are found here; Aggrefs
+	 * in the targetlist are found during ExecAssignProjectionInfo, below.
 	 */
-	aggstate->ss.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->plan.targetlist,
-					 (PlanState *) aggstate);
-	aggstate->ss.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->plan.qual,
-					 (PlanState *) aggstate);
+	aggstate->ss.ps.qual =
+		ExecInitQual(node->plan.qual, (PlanState *) aggstate);
 
 	/*
 	 * Initialize child nodes.
@@ -2540,7 +2539,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	ExecAssignProjectionInfo(&aggstate->ss.ps, NULL);
 
 	/*
-	 * get the count of aggregates in targetlist and quals
+	 * We should now have found all Aggrefs in the targetlist and quals.
 	 */
 	numaggs = aggstate->numaggs;
 	Assert(numaggs == list_length(aggstate->aggs));
@@ -2724,7 +2723,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	foreach(l, aggstate->aggs)
 	{
 		AggrefExprState *aggrefstate = (AggrefExprState *) lfirst(l);
-		Aggref	   *aggref = (Aggref *) aggrefstate->xprstate.expr;
+		Aggref	   *aggref = aggrefstate->aggref;
 		AggStatePerAgg peragg;
 		AggStatePerTrans pertrans;
 		int			existing_aggno;
@@ -3024,11 +3023,10 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	/* and then create a projection for that targetlist */
 	aggstate->evaldesc = ExecTypeFromTL(combined_inputeval, false);
 	aggstate->evalslot = ExecInitExtraTupleSlot(estate);
-	combined_inputeval = (List *) ExecInitExpr((Expr *) combined_inputeval,
-											   (PlanState *) aggstate);
 	aggstate->evalproj = ExecBuildProjectionInfo(combined_inputeval,
 												 aggstate->tmpcontext,
 												 aggstate->evalslot,
+												 &aggstate->ss.ps,
 												 NULL);
 	ExecSetSlotDescriptor(aggstate->evalslot, aggstate->evaldesc);
 
@@ -3206,8 +3204,8 @@ build_pertrans_for_aggref(AggStatePerTrans pertrans,
 	naggs = aggstate->numaggs;
 	pertrans->aggfilter = ExecInitExpr(aggref->aggfilter,
 									   (PlanState *) aggstate);
-	pertrans->aggdirectargs = (List *) ExecInitExpr((Expr *) aggref->aggdirectargs,
-													(PlanState *) aggstate);
+	pertrans->aggdirectargs = ExecInitExprList(aggref->aggdirectargs,
+											   (PlanState *) aggstate);
 
 	/*
 	 * Complain if the aggregate's arguments contain any aggregates; nested
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 2e9ff7d1b90bfa6fe2909946b05190b37c1f7665..19eb1755bea869e1c5fe35ff3b74ea6dc00563c0 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -319,7 +319,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			econtext->ecxt_scantuple = slot;
 			ResetExprContext(econtext);
 
-			if (!ExecQual(node->bitmapqualorig, econtext, false))
+			if (!ExecQual(node->bitmapqualorig, econtext))
 			{
 				/* Fails recheck, so drop it and loop back for another */
 				InstrCountFiltered2(node, 1);
@@ -654,7 +654,7 @@ BitmapHeapRecheck(BitmapHeapScanState *node, TupleTableSlot *slot)
 
 	ResetExprContext(econtext);
 
-	return ExecQual(node->bitmapqualorig, econtext, false);
+	return ExecQual(node->bitmapqualorig, econtext);
 }
 
 /* ----------------------------------------------------------------
@@ -837,15 +837,10 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	scanstate->ss.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.targetlist,
-					 (PlanState *) scanstate);
-	scanstate->ss.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.qual,
-					 (PlanState *) scanstate);
-	scanstate->bitmapqualorig = (List *)
-		ExecInitExpr((Expr *) node->bitmapqualorig,
-					 (PlanState *) scanstate);
+	scanstate->ss.ps.qual =
+		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+	scanstate->bitmapqualorig =
+		ExecInitQual(node->bitmapqualorig, (PlanState *) scanstate);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeCtescan.c b/src/backend/executor/nodeCtescan.c
index 8f4e0f527e4f847c6d15c20b7898e5d0c4b1d286..bed7949c5a0c0c54a369118955f586b770806ca6 100644
--- a/src/backend/executor/nodeCtescan.c
+++ b/src/backend/executor/nodeCtescan.c
@@ -242,12 +242,8 @@ ExecInitCteScan(CteScan *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	scanstate->ss.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.targetlist,
-					 (PlanState *) scanstate);
-	scanstate->ss.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.qual,
-					 (PlanState *) scanstate);
+	scanstate->ss.ps.qual =
+		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
index d4647482905bb692f7d84541aab6c90e47328ffb..5d309828ef185616b8d8c3afdbac6c954f9ad61d 100644
--- a/src/backend/executor/nodeCustom.c
+++ b/src/backend/executor/nodeCustom.c
@@ -49,12 +49,8 @@ ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags)
 	ExecAssignExprContext(estate, &css->ss.ps);
 
 	/* initialize child expressions */
-	css->ss.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) cscan->scan.plan.targetlist,
-					 (PlanState *) css);
-	css->ss.ps.qual = (List *)
-		ExecInitExpr((Expr *) cscan->scan.plan.qual,
-					 (PlanState *) css);
+	css->ss.ps.qual =
+		ExecInitQual(cscan->scan.plan.qual, (PlanState *) css);
 
 	/* tuple table initialization */
 	ExecInitScanTupleSlot(estate, &css->ss);
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 3b6d1390eb41285444a6daa55080984eabace7b9..9ae1561404b4ed84db2b26b2bef65d59760a5101 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -101,7 +101,7 @@ ForeignRecheck(ForeignScanState *node, TupleTableSlot *slot)
 		!fdwroutine->RecheckForeignScan(node, slot))
 		return false;
 
-	return ExecQual(node->fdw_recheck_quals, econtext, false);
+	return ExecQual(node->fdw_recheck_quals, econtext);
 }
 
 /* ----------------------------------------------------------------
@@ -155,15 +155,10 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	scanstate->ss.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.targetlist,
-					 (PlanState *) scanstate);
-	scanstate->ss.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.qual,
-					 (PlanState *) scanstate);
-	scanstate->fdw_recheck_quals = (List *)
-		ExecInitExpr((Expr *) node->fdw_recheck_quals,
-					 (PlanState *) scanstate);
+	scanstate->ss.ps.qual =
+		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+	scanstate->fdw_recheck_quals =
+		ExecInitQual(node->fdw_recheck_quals, (PlanState *) scanstate);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index 972022784d531ded41ab58d4338d5e49b7e82a16..426527d2a2ac7a54aac1e8e9bbf3cd0c36cf52c0 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -35,7 +35,7 @@
  */
 typedef struct FunctionScanPerFuncState
 {
-	ExprState  *funcexpr;		/* state of the expression being evaluated */
+	SetExprState *setexpr;		/* state of the expression being evaluated */
 	TupleDesc	tupdesc;		/* desc of the function result type */
 	int			colcount;		/* expected number of result columns */
 	Tuplestorestate *tstore;	/* holds the function result set */
@@ -92,7 +92,7 @@ FunctionNext(FunctionScanState *node)
 		if (tstore == NULL)
 		{
 			node->funcstates[0].tstore = tstore =
-				ExecMakeTableFunctionResult(node->funcstates[0].funcexpr,
+				ExecMakeTableFunctionResult(node->funcstates[0].setexpr,
 											node->ss.ps.ps_ExprContext,
 											node->argcontext,
 											node->funcstates[0].tupdesc,
@@ -151,7 +151,7 @@ FunctionNext(FunctionScanState *node)
 		if (fs->tstore == NULL)
 		{
 			fs->tstore =
-				ExecMakeTableFunctionResult(fs->funcexpr,
+				ExecMakeTableFunctionResult(fs->setexpr,
 											node->ss.ps.ps_ExprContext,
 											node->argcontext,
 											fs->tupdesc,
@@ -340,12 +340,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	scanstate->ss.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.targetlist,
-					 (PlanState *) scanstate);
-	scanstate->ss.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.qual,
-					 (PlanState *) scanstate);
+	scanstate->ss.ps.qual =
+		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
 
 	scanstate->funcstates = palloc(nfuncs * sizeof(FunctionScanPerFuncState));
 
@@ -361,7 +357,10 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 		Oid			funcrettype;
 		TupleDesc	tupdesc;
 
-		fs->funcexpr = ExecInitExpr((Expr *) funcexpr, (PlanState *) scanstate);
+		fs->setexpr =
+			ExecInitTableFunctionResult((Expr *) funcexpr,
+										scanstate->ss.ps.ps_ExprContext,
+										&scanstate->ss.ps);
 
 		/*
 		 * Don't allocate the tuplestores; the actual calls to the functions
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 32c97d390e0a3c46d25c263a04e5f78cc3249ab2..1e5b1b7675c5a10d0f9fdb79f245be80be4ef1c2 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -81,12 +81,8 @@ ExecInitGather(Gather *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	gatherstate->ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->plan.targetlist,
-					 (PlanState *) gatherstate);
-	gatherstate->ps.qual = (List *)
-		ExecInitExpr((Expr *) node->plan.qual,
-					 (PlanState *) gatherstate);
+	gatherstate->ps.qual =
+		ExecInitQual(node->plan.qual, (PlanState *) gatherstate);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 72f30ab4e6bbf9f9b1ff604bb35b6f31083dc91b..3f0c3ee4d180187a16b84b509b85b6c2320f4133 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -86,12 +86,8 @@ ExecInitGatherMerge(GatherMerge *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	gm_state->ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->plan.targetlist,
-					 (PlanState *) gm_state);
-	gm_state->ps.qual = (List *)
-		ExecInitExpr((Expr *) node->plan.qual,
-					 (PlanState *) gm_state);
+	gm_state->ps.qual =
+		ExecInitQual(node->plan.qual, &gm_state->ps);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c
index 66c095bc72305ed0ff8df76bb63b5d223c5117ae..af9ba4905eb24cc4b2ee80b271348f6ef4f05f21 100644
--- a/src/backend/executor/nodeGroup.c
+++ b/src/backend/executor/nodeGroup.c
@@ -85,7 +85,7 @@ ExecGroup(GroupState *node)
 		 * Check the qual (HAVING clause); if the group does not match, ignore
 		 * it and fall into scan loop.
 		 */
-		if (ExecQual(node->ss.ps.qual, econtext, false))
+		if (ExecQual(node->ss.ps.qual, econtext))
 		{
 			/*
 			 * Form and return a projection tuple using the first input tuple.
@@ -139,7 +139,7 @@ ExecGroup(GroupState *node)
 		 * Check the qual (HAVING clause); if the group does not match, ignore
 		 * it and loop back to scan the rest of the group.
 		 */
-		if (ExecQual(node->ss.ps.qual, econtext, false))
+		if (ExecQual(node->ss.ps.qual, econtext))
 		{
 			/*
 			 * Form and return a projection tuple using the first input tuple.
@@ -188,12 +188,8 @@ ExecInitGroup(Group *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	grpstate->ss.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->plan.targetlist,
-					 (PlanState *) grpstate);
-	grpstate->ss.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->plan.qual,
-					 (PlanState *) grpstate);
+	grpstate->ss.ps.qual =
+		ExecInitQual(node->plan.qual, (PlanState *) grpstate);
 
 	/*
 	 * initialize child nodes
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index e695d8834b550e6408e13fca4a05eea8d48b10a6..cfc6b9609355ceb426b07c1df21eb1ddaccad399 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -190,12 +190,8 @@ ExecInitHash(Hash *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	hashstate->ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->plan.targetlist,
-					 (PlanState *) hashstate);
-	hashstate->ps.qual = (List *)
-		ExecInitExpr((Expr *) node->plan.qual,
-					 (PlanState *) hashstate);
+	hashstate->ps.qual =
+		ExecInitQual(node->plan.qual, (PlanState *) hashstate);
 
 	/*
 	 * initialize child nodes
@@ -1063,7 +1059,7 @@ bool
 ExecScanHashBucket(HashJoinState *hjstate,
 				   ExprContext *econtext)
 {
-	List	   *hjclauses = hjstate->hashclauses;
+	ExprState  *hjclauses = hjstate->hashclauses;
 	HashJoinTable hashtable = hjstate->hj_HashTable;
 	HashJoinTuple hashTuple = hjstate->hj_CurTuple;
 	uint32		hashvalue = hjstate->hj_CurHashValue;
@@ -1097,7 +1093,7 @@ ExecScanHashBucket(HashJoinState *hjstate,
 			/* reset temp memory each time to avoid leaks from qual expr */
 			ResetExprContext(econtext);
 
-			if (ExecQual(hjclauses, econtext, false))
+			if (ExecQual(hjclauses, econtext))
 			{
 				hjstate->hj_CurTuple = hashTuple;
 				return true;
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index c50d93f43dbe19164e7cae4810bde02e83493d98..f2c885afbe2dfbdfb7d39ef0ec9dde55fb967ca4 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -63,8 +63,8 @@ ExecHashJoin(HashJoinState *node)
 {
 	PlanState  *outerNode;
 	HashState  *hashNode;
-	List	   *joinqual;
-	List	   *otherqual;
+	ExprState  *joinqual;
+	ExprState  *otherqual;
 	ExprContext *econtext;
 	HashJoinTable hashtable;
 	TupleTableSlot *outerTupleSlot;
@@ -275,7 +275,7 @@ ExecHashJoin(HashJoinState *node)
 				 * Only the joinquals determine tuple match status, but all
 				 * quals must pass to actually return the tuple.
 				 */
-				if (joinqual == NIL || ExecQual(joinqual, econtext, false))
+				if (joinqual == NULL || ExecQual(joinqual, econtext))
 				{
 					node->hj_MatchedOuter = true;
 					HeapTupleHeaderSetMatch(HJTUPLE_MINTUPLE(node->hj_CurTuple));
@@ -294,8 +294,7 @@ ExecHashJoin(HashJoinState *node)
 					if (node->js.jointype == JOIN_SEMI)
 						node->hj_JoinState = HJ_NEED_NEW_OUTER;
 
-					if (otherqual == NIL ||
-						ExecQual(otherqual, econtext, false))
+					if (otherqual == NULL || ExecQual(otherqual, econtext))
 						return ExecProject(node->js.ps.ps_ProjInfo);
 					else
 						InstrCountFiltered2(node, 1);
@@ -322,8 +321,7 @@ ExecHashJoin(HashJoinState *node)
 					 */
 					econtext->ecxt_innertuple = node->hj_NullInnerTupleSlot;
 
-					if (otherqual == NIL ||
-						ExecQual(otherqual, econtext, false))
+					if (otherqual == NULL || ExecQual(otherqual, econtext))
 						return ExecProject(node->js.ps.ps_ProjInfo);
 					else
 						InstrCountFiltered2(node, 1);
@@ -350,8 +348,7 @@ ExecHashJoin(HashJoinState *node)
 				 */
 				econtext->ecxt_outertuple = node->hj_NullOuterTupleSlot;
 
-				if (otherqual == NIL ||
-					ExecQual(otherqual, econtext, false))
+				if (otherqual == NULL || ExecQual(otherqual, econtext))
 					return ExecProject(node->js.ps.ps_ProjInfo);
 				else
 					InstrCountFiltered2(node, 1);
@@ -411,19 +408,13 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	hjstate->js.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->join.plan.targetlist,
-					 (PlanState *) hjstate);
-	hjstate->js.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->join.plan.qual,
-					 (PlanState *) hjstate);
+	hjstate->js.ps.qual =
+		ExecInitQual(node->join.plan.qual, (PlanState *) hjstate);
 	hjstate->js.jointype = node->join.jointype;
-	hjstate->js.joinqual = (List *)
-		ExecInitExpr((Expr *) node->join.joinqual,
-					 (PlanState *) hjstate);
-	hjstate->hashclauses = (List *)
-		ExecInitExpr((Expr *) node->hashclauses,
-					 (PlanState *) hjstate);
+	hjstate->js.joinqual =
+		ExecInitQual(node->join.joinqual, (PlanState *) hjstate);
+	hjstate->hashclauses =
+		ExecInitQual(node->hashclauses, (PlanState *) hjstate);
 
 	/*
 	 * initialize child nodes
@@ -517,13 +508,14 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
 	lclauses = NIL;
 	rclauses = NIL;
 	hoperators = NIL;
-	foreach(l, hjstate->hashclauses)
+	foreach(l, node->hashclauses)
 	{
-		FuncExprState *fstate = castNode(FuncExprState, lfirst(l));
-		OpExpr	   *hclause = castNode(OpExpr, fstate->xprstate.expr);
+		OpExpr	   *hclause = castNode(OpExpr, lfirst(l));
 
-		lclauses = lappend(lclauses, linitial(fstate->args));
-		rclauses = lappend(rclauses, lsecond(fstate->args));
+		lclauses = lappend(lclauses, ExecInitExpr(linitial(hclause->args),
+												  (PlanState *) hjstate));
+		rclauses = lappend(rclauses, ExecInitExpr(lsecond(hclause->args),
+												  (PlanState *) hjstate));
 		hoperators = lappend_oid(hoperators, hclause->opno);
 	}
 	hjstate->hj_OuterHashKeys = lclauses;
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index db7f2e120ea85b99dd2d22e784d5d449b1d0101f..5550f6c0a4be70bbb3735c2c7a83ef4f5beed2a3 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -211,7 +211,7 @@ IndexOnlyNext(IndexOnlyScanState *node)
 		{
 			econtext->ecxt_scantuple = slot;
 			ResetExprContext(econtext);
-			if (!ExecQual(node->indexqual, econtext, false))
+			if (!ExecQual(node->indexqual, econtext))
 			{
 				/* Fails recheck, so drop it and loop back for another */
 				InstrCountFiltered2(node, 1);
@@ -488,15 +488,10 @@ ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags)
 	 * Note: we don't initialize all of the indexorderby expression, only the
 	 * sub-parts corresponding to runtime keys (see below).
 	 */
-	indexstate->ss.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.targetlist,
-					 (PlanState *) indexstate);
-	indexstate->ss.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.qual,
-					 (PlanState *) indexstate);
-	indexstate->indexqual = (List *)
-		ExecInitExpr((Expr *) node->indexqual,
-					 (PlanState *) indexstate);
+	indexstate->ss.ps.qual =
+		ExecInitQual(node->scan.plan.qual, (PlanState *) indexstate);
+	indexstate->indexqual =
+		ExecInitQual(node->indexqual, (PlanState *) indexstate);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index cb6aff9137a42908c46e98d0323b80e9d06bf2d1..5afd02e09ddfad4417af5324e5986e7807ee37a5 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -149,7 +149,7 @@ IndexNext(IndexScanState *node)
 		{
 			econtext->ecxt_scantuple = slot;
 			ResetExprContext(econtext);
-			if (!ExecQual(node->indexqualorig, econtext, false))
+			if (!ExecQual(node->indexqualorig, econtext))
 			{
 				/* Fails recheck, so drop it and loop back for another */
 				InstrCountFiltered2(node, 1);
@@ -295,7 +295,7 @@ next_indextuple:
 		{
 			econtext->ecxt_scantuple = slot;
 			ResetExprContext(econtext);
-			if (!ExecQual(node->indexqualorig, econtext, false))
+			if (!ExecQual(node->indexqualorig, econtext))
 			{
 				/* Fails recheck, so drop it and loop back for another */
 				InstrCountFiltered2(node, 1);
@@ -415,7 +415,7 @@ IndexRecheck(IndexScanState *node, TupleTableSlot *slot)
 
 	ResetExprContext(econtext);
 
-	return ExecQual(node->indexqualorig, econtext, false);
+	return ExecQual(node->indexqualorig, econtext);
 }
 
 
@@ -921,18 +921,12 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
 	 * would be nice to improve that.  (Problem is that any SubPlans present
 	 * in the expression must be found now...)
 	 */
-	indexstate->ss.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.targetlist,
-					 (PlanState *) indexstate);
-	indexstate->ss.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.qual,
-					 (PlanState *) indexstate);
-	indexstate->indexqualorig = (List *)
-		ExecInitExpr((Expr *) node->indexqualorig,
-					 (PlanState *) indexstate);
-	indexstate->indexorderbyorig = (List *)
-		ExecInitExpr((Expr *) node->indexorderbyorig,
-					 (PlanState *) indexstate);
+	indexstate->ss.ps.qual =
+		ExecInitQual(node->scan.plan.qual, (PlanState *) indexstate);
+	indexstate->indexqualorig =
+		ExecInitQual(node->indexqualorig, (PlanState *) indexstate);
+	indexstate->indexorderbyorig =
+		ExecInitExprList(node->indexorderbyorig, (PlanState *) indexstate);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index 105e2dcedbe9e793d85d3080017a39b666ab2ef9..62784af304b0a939f8a55a33af53053823fe789b 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -452,14 +452,14 @@ static TupleTableSlot *
 MJFillOuter(MergeJoinState *node)
 {
 	ExprContext *econtext = node->js.ps.ps_ExprContext;
-	List	   *otherqual = node->js.ps.qual;
+	ExprState  *otherqual = node->js.ps.qual;
 
 	ResetExprContext(econtext);
 
 	econtext->ecxt_outertuple = node->mj_OuterTupleSlot;
 	econtext->ecxt_innertuple = node->mj_NullInnerTupleSlot;
 
-	if (ExecQual(otherqual, econtext, false))
+	if (ExecQual(otherqual, econtext))
 	{
 		/*
 		 * qualification succeeded.  now form the desired projection tuple and
@@ -483,14 +483,14 @@ static TupleTableSlot *
 MJFillInner(MergeJoinState *node)
 {
 	ExprContext *econtext = node->js.ps.ps_ExprContext;
-	List	   *otherqual = node->js.ps.qual;
+	ExprState  *otherqual = node->js.ps.qual;
 
 	ResetExprContext(econtext);
 
 	econtext->ecxt_outertuple = node->mj_NullOuterTupleSlot;
 	econtext->ecxt_innertuple = node->mj_InnerTupleSlot;
 
-	if (ExecQual(otherqual, econtext, false))
+	if (ExecQual(otherqual, econtext))
 	{
 		/*
 		 * qualification succeeded.  now form the desired projection tuple and
@@ -598,8 +598,8 @@ ExecMergeTupleDump(MergeJoinState *mergestate)
 TupleTableSlot *
 ExecMergeJoin(MergeJoinState *node)
 {
-	List	   *joinqual;
-	List	   *otherqual;
+	ExprState  *joinqual;
+	ExprState  *otherqual;
 	bool		qualResult;
 	int			compareResult;
 	PlanState  *innerPlan;
@@ -785,8 +785,8 @@ ExecMergeJoin(MergeJoinState *node)
 				innerTupleSlot = node->mj_InnerTupleSlot;
 				econtext->ecxt_innertuple = innerTupleSlot;
 
-				qualResult = (joinqual == NIL ||
-							  ExecQual(joinqual, econtext, false));
+				qualResult = (joinqual == NULL ||
+							  ExecQual(joinqual, econtext));
 				MJ_DEBUG_QUAL(joinqual, qualResult);
 
 				if (qualResult)
@@ -808,8 +808,8 @@ ExecMergeJoin(MergeJoinState *node)
 					if (node->js.jointype == JOIN_SEMI)
 						node->mj_JoinState = EXEC_MJ_NEXTOUTER;
 
-					qualResult = (otherqual == NIL ||
-								  ExecQual(otherqual, econtext, false));
+					qualResult = (otherqual == NULL ||
+								  ExecQual(otherqual, econtext));
 					MJ_DEBUG_QUAL(otherqual, qualResult);
 
 					if (qualResult)
@@ -1455,16 +1455,11 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	mergestate->js.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->join.plan.targetlist,
-					 (PlanState *) mergestate);
-	mergestate->js.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->join.plan.qual,
-					 (PlanState *) mergestate);
+	mergestate->js.ps.qual =
+		ExecInitQual(node->join.plan.qual, (PlanState *) mergestate);
 	mergestate->js.jointype = node->join.jointype;
-	mergestate->js.joinqual = (List *)
-		ExecInitExpr((Expr *) node->join.joinqual,
-					 (PlanState *) mergestate);
+	mergestate->js.joinqual =
+		ExecInitQual(node->join.joinqual, (PlanState *) mergestate);
 	mergestate->mj_ConstFalseJoin = false;
 	/* mergeclauses are handled below */
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 29c6a6e1d8d6124902f259788b14edc085e324c5..0b524e0b7cfb1634707f5ace7477dade752db42d 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1152,7 +1152,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 {
 	ExprContext *econtext = mtstate->ps.ps_ExprContext;
 	Relation	relation = resultRelInfo->ri_RelationDesc;
-	List	   *onConflictSetWhere = resultRelInfo->ri_onConflictSetWhere;
+	ExprState  *onConflictSetWhere = resultRelInfo->ri_onConflictSetWhere;
 	HeapTupleData tuple;
 	HeapUpdateFailureData hufd;
 	LockTupleMode lockmode;
@@ -1271,7 +1271,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	econtext->ecxt_innertuple = excludedSlot;
 	econtext->ecxt_outertuple = NULL;
 
-	if (!ExecQual(onConflictSetWhere, econtext, false))
+	if (!ExecQual(onConflictSetWhere, econtext))
 	{
 		ReleaseBuffer(buffer);
 		InstrCountFiltered1(&mtstate->ps, 1);
@@ -1646,7 +1646,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	mtstate = makeNode(ModifyTableState);
 	mtstate->ps.plan = (Plan *) node;
 	mtstate->ps.state = estate;
-	mtstate->ps.targetlist = NIL;		/* not actually used */
 
 	mtstate->operation = operation;
 	mtstate->canSetTag = node->canSetTag;
@@ -1778,7 +1777,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		foreach(ll, wcoList)
 		{
 			WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
-			ExprState  *wcoExpr = ExecInitExpr((Expr *) wco->qual,
+			ExprState  *wcoExpr = ExecInitQual((List *) wco->qual,
 											   mtstate->mt_plans[i]);
 
 			wcoExprs = lappend(wcoExprs, wcoExpr);
@@ -1818,8 +1817,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			foreach(ll, mapped_wcoList)
 			{
 				WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
-				ExprState  *wcoExpr = ExecInitExpr((Expr *) wco->qual,
-											   mtstate->mt_plans[i]);
+				ExprState  *wcoExpr = ExecInitQual((List *) wco->qual,
+												   mtstate->mt_plans[i]);
 
 				wcoExprs = lappend(wcoExprs, wcoExpr);
 			}
@@ -1852,8 +1851,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		slot = mtstate->ps.ps_ResultTupleSlot;
 
 		/* Need an econtext too */
-		econtext = CreateExprContext(estate);
-		mtstate->ps.ps_ExprContext = econtext;
+		if (mtstate->ps.ps_ExprContext == NULL)
+			ExecAssignExprContext(estate, &mtstate->ps);
+		econtext = mtstate->ps.ps_ExprContext;
 
 		/*
 		 * Build a projection for each result rel.
@@ -1862,11 +1862,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		foreach(l, node->returningLists)
 		{
 			List	   *rlist = (List *) lfirst(l);
-			List	   *rliststate;
 
-			rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps);
 			resultRelInfo->ri_projectReturning =
-				ExecBuildProjectionInfo(rliststate, econtext, slot,
+				ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
 									 resultRelInfo->ri_RelationDesc->rd_att);
 			resultRelInfo++;
 		}
@@ -1883,16 +1881,14 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		for (i = 0; i < mtstate->mt_num_partitions; i++)
 		{
 			Relation	partrel = resultRelInfo->ri_RelationDesc;
-			List	   *rlist,
-					   *rliststate;
+			List	   *rlist;
 
 			/* varno = node->nominalRelation */
 			rlist = map_partition_varattnos(returningList,
 											node->nominalRelation,
 											partrel, rel);
-			rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps);
 			resultRelInfo->ri_projectReturning =
-				ExecBuildProjectionInfo(rliststate, econtext, slot,
+				ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
 									 resultRelInfo->ri_RelationDesc->rd_att);
 			resultRelInfo++;
 		}
@@ -1922,7 +1918,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	if (node->onConflictAction == ONCONFLICT_UPDATE)
 	{
 		ExprContext *econtext;
-		ExprState  *setexpr;
 		TupleDesc	tupDesc;
 
 		/* insert may only have one plan, inheritance is not expanded */
@@ -1948,11 +1943,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		mtstate->mt_conflproj = ExecInitExtraTupleSlot(mtstate->ps.state);
 		ExecSetSlotDescriptor(mtstate->mt_conflproj, tupDesc);
 
-		/* build UPDATE SET expression and projection state */
-		setexpr = ExecInitExpr((Expr *) node->onConflictSet, &mtstate->ps);
+		/* build UPDATE SET projection state */
 		resultRelInfo->ri_onConflictSetProj =
-			ExecBuildProjectionInfo((List *) setexpr, econtext,
-									mtstate->mt_conflproj,
+			ExecBuildProjectionInfo(node->onConflictSet, econtext,
+									mtstate->mt_conflproj, &mtstate->ps,
 									resultRelInfo->ri_RelationDesc->rd_att);
 
 		/* build DO UPDATE WHERE clause expression */
@@ -1960,10 +1954,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		{
 			ExprState  *qualexpr;
 
-			qualexpr = ExecInitExpr((Expr *) node->onConflictWhere,
+			qualexpr = ExecInitQual((List *) node->onConflictWhere,
 									&mtstate->ps);
 
-			resultRelInfo->ri_onConflictSetWhere = (List *) qualexpr;
+			resultRelInfo->ri_onConflictSetWhere = qualexpr;
 		}
 	}
 
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index cac7ba1b9bfd8c3914f861efc07ff874f0c0a8b1..53977e0b329f40d8ae8bbb1001d8868801950a3f 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -64,8 +64,8 @@ ExecNestLoop(NestLoopState *node)
 	PlanState  *outerPlan;
 	TupleTableSlot *outerTupleSlot;
 	TupleTableSlot *innerTupleSlot;
-	List	   *joinqual;
-	List	   *otherqual;
+	ExprState  *joinqual;
+	ExprState  *otherqual;
 	ExprContext *econtext;
 	ListCell   *lc;
 
@@ -176,7 +176,7 @@ ExecNestLoop(NestLoopState *node)
 
 				ENL1_printf("testing qualification for outer-join tuple");
 
-				if (otherqual == NIL || ExecQual(otherqual, econtext, false))
+				if (otherqual == NULL || ExecQual(otherqual, econtext))
 				{
 					/*
 					 * qualification was satisfied so we project and return
@@ -207,7 +207,7 @@ ExecNestLoop(NestLoopState *node)
 		 */
 		ENL1_printf("testing qualification");
 
-		if (ExecQual(joinqual, econtext, false))
+		if (ExecQual(joinqual, econtext))
 		{
 			node->nl_MatchedOuter = true;
 
@@ -225,7 +225,7 @@ ExecNestLoop(NestLoopState *node)
 			if (node->js.jointype == JOIN_SEMI)
 				node->nl_NeedNewOuter = true;
 
-			if (otherqual == NIL || ExecQual(otherqual, econtext, false))
+			if (otherqual == NULL || ExecQual(otherqual, econtext))
 			{
 				/*
 				 * qualification was satisfied so we project and return the
@@ -282,16 +282,11 @@ ExecInitNestLoop(NestLoop *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	nlstate->js.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->join.plan.targetlist,
-					 (PlanState *) nlstate);
-	nlstate->js.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->join.plan.qual,
-					 (PlanState *) nlstate);
+	nlstate->js.ps.qual =
+		ExecInitQual(node->join.plan.qual, (PlanState *) nlstate);
 	nlstate->js.jointype = node->join.jointype;
-	nlstate->js.joinqual = (List *)
-		ExecInitExpr((Expr *) node->join.joinqual,
-					 (PlanState *) nlstate);
+	nlstate->js.joinqual =
+		ExecInitQual(node->join.joinqual, (PlanState *) nlstate);
 
 	/*
 	 * initialize child nodes
diff --git a/src/backend/executor/nodeProjectSet.c b/src/backend/executor/nodeProjectSet.c
index eae0f1dad93026975a98aa71a1355a36b0058cec..01048cc8268e0b59239e2a60163f90d7f8ba8327 100644
--- a/src/backend/executor/nodeProjectSet.c
+++ b/src/backend/executor/nodeProjectSet.c
@@ -24,6 +24,7 @@
 
 #include "executor/executor.h"
 #include "executor/nodeProjectSet.h"
+#include "nodes/nodeFuncs.h"
 #include "utils/memutils.h"
 
 
@@ -119,10 +120,9 @@ ExecProjectSRF(ProjectSetState *node, bool continuing)
 {
 	TupleTableSlot *resultSlot = node->ps.ps_ResultTupleSlot;
 	ExprContext *econtext = node->ps.ps_ExprContext;
-	bool		hassrf PG_USED_FOR_ASSERTS_ONLY = false;
+	bool		hassrf PG_USED_FOR_ASSERTS_ONLY;
 	bool		hasresult;
 	int			argno;
-	ListCell   *lc;
 
 	ExecClearTuple(resultSlot);
 
@@ -132,11 +132,10 @@ ExecProjectSRF(ProjectSetState *node, bool continuing)
 	 */
 	node->pending_srf_tuples = false;
 
-	hasresult = false;
-	argno = 0;
-	foreach(lc, node->ps.targetlist)
+	hassrf = hasresult = false;
+	for (argno = 0; argno < node->nelems; argno++)
 	{
-		GenericExprState *gstate = (GenericExprState *) lfirst(lc);
+		Node	   *elem = node->elems[argno];
 		ExprDoneCond *isdone = &node->elemdone[argno];
 		Datum	   *result = &resultSlot->tts_values[argno];
 		bool	   *isnull = &resultSlot->tts_isnull[argno];
@@ -151,13 +150,12 @@ ExecProjectSRF(ProjectSetState *node, bool continuing)
 			*isnull = true;
 			hassrf = true;
 		}
-		else if (IsA(gstate->arg, FuncExprState) &&
-				 ((FuncExprState *) gstate->arg)->funcReturnsSet)
+		else if (IsA(elem, SetExprState))
 		{
 			/*
 			 * Evaluate SRF - possibly continuing previously started output.
 			 */
-			*result = ExecMakeFunctionResultSet((FuncExprState *) gstate->arg,
+			*result = ExecMakeFunctionResultSet((SetExprState *) elem,
 												econtext, isnull, isdone);
 
 			if (*isdone != ExprEndResult)
@@ -169,11 +167,9 @@ ExecProjectSRF(ProjectSetState *node, bool continuing)
 		else
 		{
 			/* Non-SRF tlist expression, just evaluate normally. */
-			*result = ExecEvalExpr(gstate->arg, econtext, isnull);
+			*result = ExecEvalExpr((ExprState *) elem, econtext, isnull);
 			*isdone = ExprSingleResult;
 		}
-
-		argno++;
 	}
 
 	/* ProjectSet should not be used if there's no SRFs */
@@ -204,6 +200,8 @@ ProjectSetState *
 ExecInitProjectSet(ProjectSet *node, EState *estate, int eflags)
 {
 	ProjectSetState *state;
+	ListCell   *lc;
+	int			off;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_MARK | EXEC_FLAG_BACKWARD)));
@@ -229,12 +227,7 @@ ExecInitProjectSet(ProjectSet *node, EState *estate, int eflags)
 	 */
 	ExecInitResultTupleSlot(estate, &state->ps);
 
-	/*
-	 * initialize child expressions
-	 */
-	state->ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->plan.targetlist,
-					 (PlanState *) state);
+	/* We don't support any qual on ProjectSet nodes */
 	Assert(node->plan.qual == NIL);
 
 	/*
@@ -252,11 +245,41 @@ ExecInitProjectSet(ProjectSet *node, EState *estate, int eflags)
 	 */
 	ExecAssignResultTypeFromTL(&state->ps);
 
-	/* Create workspace for per-SRF is-done state */
+	/* Create workspace for per-tlist-entry expr state & SRF-is-done state */
 	state->nelems = list_length(node->plan.targetlist);
+	state->elems = (Node **)
+		palloc(sizeof(Node *) * state->nelems);
 	state->elemdone = (ExprDoneCond *)
 		palloc(sizeof(ExprDoneCond) * state->nelems);
 
+	/*
+	 * Build expressions to evaluate targetlist.  We can't use
+	 * ExecBuildProjectionInfo here, since that doesn't deal with SRFs.
+	 * Instead compile each expression separately, using
+	 * ExecInitFunctionResultSet where applicable.
+	 */
+	off = 0;
+	foreach(lc, node->plan.targetlist)
+	{
+		TargetEntry *te = (TargetEntry *) lfirst(lc);
+		Expr	   *expr = te->expr;
+
+		if ((IsA(expr, FuncExpr) &&((FuncExpr *) expr)->funcretset) ||
+			(IsA(expr, OpExpr) &&((OpExpr *) expr)->opretset))
+		{
+			state->elems[off] = (Node *)
+				ExecInitFunctionResultSet(expr, state->ps.ps_ExprContext,
+										  &state->ps);
+		}
+		else
+		{
+			Assert(!expression_returns_set((Node *) expr));
+			state->elems[off] = (Node *) ExecInitExpr(expr, &state->ps);
+		}
+
+		off++;
+	}
+
 	return state;
 }
 
diff --git a/src/backend/executor/nodeResult.c b/src/backend/executor/nodeResult.c
index b5b50b21e9af8db0e413c7d5aba20ffef60f3ff3..a753a53419183cefebf5c3e0ed9fe6a9f8e8ac37 100644
--- a/src/backend/executor/nodeResult.c
+++ b/src/backend/executor/nodeResult.c
@@ -77,9 +77,7 @@ ExecResult(ResultState *node)
 	 */
 	if (node->rs_checkqual)
 	{
-		bool		qualResult = ExecQual((List *) node->resconstantqual,
-										  econtext,
-										  false);
+		bool		qualResult = ExecQual(node->resconstantqual, econtext);
 
 		node->rs_checkqual = false;
 		if (!qualResult)
@@ -209,14 +207,10 @@ ExecInitResult(Result *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	resstate->ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->plan.targetlist,
-					 (PlanState *) resstate);
-	resstate->ps.qual = (List *)
-		ExecInitExpr((Expr *) node->plan.qual,
-					 (PlanState *) resstate);
-	resstate->resconstantqual = ExecInitExpr((Expr *) node->resconstantqual,
-											 (PlanState *) resstate);
+	resstate->ps.qual =
+		ExecInitQual(node->plan.qual, (PlanState *) resstate);
+	resstate->resconstantqual =
+		ExecInitQual((List *) node->resconstantqual, (PlanState *) resstate);
 
 	/*
 	 * initialize child nodes
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index d38265e8104825e382d3fb66dade78edc312858d..0247bd23479bf6c56e7121137e2657f392fb98c5 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -164,19 +164,12 @@ ExecInitSampleScan(SampleScan *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	scanstate->ss.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.targetlist,
-					 (PlanState *) scanstate);
-	scanstate->ss.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.qual,
-					 (PlanState *) scanstate);
-
-	scanstate->args = (List *)
-		ExecInitExpr((Expr *) tsc->args,
-					 (PlanState *) scanstate);
+	scanstate->ss.ps.qual =
+		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+
+	scanstate->args = ExecInitExprList(tsc->args, (PlanState *) scanstate);
 	scanstate->repeatable =
-		ExecInitExpr(tsc->repeatable,
-					 (PlanState *) scanstate);
+		ExecInitExpr(tsc->repeatable, (PlanState *) scanstate);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index e61895de0a80d3704b6ee89510b52a0d50d51d91..5680464fa273d554e0266572811e1ae5941e5677 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -188,12 +188,8 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	scanstate->ss.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->plan.targetlist,
-					 (PlanState *) scanstate);
-	scanstate->ss.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->plan.qual,
-					 (PlanState *) scanstate);
+	scanstate->ss.ps.qual =
+		ExecInitQual(node->plan.qual, (PlanState *) scanstate);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 8f419a13acd41a147d27c4b2822858392e55d3bf..b3a025879a1fdc38bde9f9b0203039e0ea2c6697 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -39,12 +39,6 @@
 #include "utils/memutils.h"
 
 
-static Datum ExecSubPlan(SubPlanState *node,
-			ExprContext *econtext,
-			bool *isNull);
-static Datum ExecAlternativeSubPlan(AlternativeSubPlanState *node,
-					   ExprContext *econtext,
-					   bool *isNull);
 static Datum ExecHashSubPlan(SubPlanState *node,
 				ExprContext *econtext,
 				bool *isNull);
@@ -64,12 +58,12 @@ static bool slotNoNulls(TupleTableSlot *slot);
  * This is the main entry point for execution of a regular SubPlan.
  * ----------------------------------------------------------------
  */
-static Datum
+Datum
 ExecSubPlan(SubPlanState *node,
 			ExprContext *econtext,
 			bool *isNull)
 {
-	SubPlan    *subplan = (SubPlan *) node->xprstate.expr;
+	SubPlan    *subplan = node->subplan;
 
 	/* Set non-null as default */
 	*isNull = false;
@@ -95,7 +89,7 @@ ExecHashSubPlan(SubPlanState *node,
 				ExprContext *econtext,
 				bool *isNull)
 {
-	SubPlan    *subplan = (SubPlan *) node->xprstate.expr;
+	SubPlan    *subplan = node->subplan;
 	PlanState  *planstate = node->planstate;
 	TupleTableSlot *slot;
 
@@ -217,7 +211,7 @@ ExecScanSubPlan(SubPlanState *node,
 				ExprContext *econtext,
 				bool *isNull)
 {
-	SubPlan    *subplan = (SubPlan *) node->xprstate.expr;
+	SubPlan    *subplan = node->subplan;
 	PlanState  *planstate = node->planstate;
 	SubLinkType subLinkType = subplan->subLinkType;
 	MemoryContext oldcontext;
@@ -462,7 +456,7 @@ ExecScanSubPlan(SubPlanState *node,
 static void
 buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
 {
-	SubPlan    *subplan = (SubPlan *) node->xprstate.expr;
+	SubPlan    *subplan = node->subplan;
 	PlanState  *planstate = node->planstate;
 	int			ncols = list_length(subplan->paramIds);
 	ExprContext *innerecontext = node->innerecontext;
@@ -596,7 +590,7 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
 	 * potential for a double free attempt.  (XXX possibly no longer needed,
 	 * but can't hurt.)
 	 */
-	ExecClearTuple(node->projRight->pi_slot);
+	ExecClearTuple(node->projRight->pi_state.resultslot);
 
 	MemoryContextSwitchTo(oldcontext);
 }
@@ -694,8 +688,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 	SubPlanState *sstate = makeNode(SubPlanState);
 	EState	   *estate = parent->state;
 
-	sstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecSubPlan;
-	sstate->xprstate.expr = (Expr *) subplan;
+	sstate->subplan = subplan;
 
 	/* Link the SubPlanState to already-initialized subplan */
 	sstate->planstate = (PlanState *) list_nth(estate->es_subplanstates,
@@ -706,7 +699,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 
 	/* Initialize subexpressions */
 	sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent);
-	sstate->args = (List *) ExecInitExpr((Expr *) subplan->args, parent);
+	sstate->args = ExecInitExprList(subplan->args, parent);
 
 	/*
 	 * initialize my state
@@ -763,9 +756,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 		TupleTableSlot *slot;
 		List	   *oplist,
 				   *lefttlist,
-				   *righttlist,
-				   *leftptlist,
-				   *rightptlist;
+				   *righttlist;
 		ListCell   *l;
 
 		/* We need a memory context to hold the hash table(s) */
@@ -792,35 +783,33 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 		 * use the sub-select's output tuples directly, but that is not the
 		 * case if we had to insert any run-time coercions of the sub-select's
 		 * output datatypes; anyway this avoids storing any resjunk columns
-		 * that might be in the sub-select's output.) Run through the
+		 * that might be in the sub-select's output.)  Run through the
 		 * combining expressions to build tlists for the lefthand and
-		 * righthand sides.  We need both the ExprState list (for ExecProject)
-		 * and the underlying parse Exprs (for ExecTypeFromTL).
+		 * righthand sides.
 		 *
 		 * We also extract the combining operators themselves to initialize
 		 * the equality and hashing functions for the hash tables.
 		 */
-		if (IsA(sstate->testexpr->expr, OpExpr))
+		if (IsA(subplan->testexpr, OpExpr))
 		{
 			/* single combining operator */
-			oplist = list_make1(sstate->testexpr);
+			oplist = list_make1(subplan->testexpr);
 		}
-		else if (and_clause((Node *) sstate->testexpr->expr))
+		else if (and_clause((Node *) subplan->testexpr))
 		{
 			/* multiple combining operators */
-			oplist = castNode(BoolExprState, sstate->testexpr)->args;
+			oplist = castNode(BoolExpr, subplan->testexpr)->args;
 		}
 		else
 		{
 			/* shouldn't see anything else in a hashable subplan */
 			elog(ERROR, "unrecognized testexpr type: %d",
-				 (int) nodeTag(sstate->testexpr->expr));
+				 (int) nodeTag(subplan->testexpr));
 			oplist = NIL;		/* keep compiler quiet */
 		}
 		Assert(list_length(oplist) == ncols);
 
 		lefttlist = righttlist = NIL;
-		leftptlist = rightptlist = NIL;
 		sstate->tab_hash_funcs = (FmgrInfo *) palloc(ncols * sizeof(FmgrInfo));
 		sstate->tab_eq_funcs = (FmgrInfo *) palloc(ncols * sizeof(FmgrInfo));
 		sstate->lhs_hash_funcs = (FmgrInfo *) palloc(ncols * sizeof(FmgrInfo));
@@ -828,45 +817,30 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 		i = 1;
 		foreach(l, oplist)
 		{
-			FuncExprState *fstate = castNode(FuncExprState, lfirst(l));
-			OpExpr	   *opexpr = castNode(OpExpr, fstate->xprstate.expr);
-			ExprState  *exstate;
+			OpExpr	   *opexpr = castNode(OpExpr, lfirst(l));
 			Expr	   *expr;
 			TargetEntry *tle;
-			GenericExprState *tlestate;
 			Oid			rhs_eq_oper;
 			Oid			left_hashfn;
 			Oid			right_hashfn;
 
-			Assert(list_length(fstate->args) == 2);
+			Assert(list_length(opexpr->args) == 2);
 
 			/* Process lefthand argument */
-			exstate = (ExprState *) linitial(fstate->args);
-			expr = exstate->expr;
+			expr = (Expr *) linitial(opexpr->args);
 			tle = makeTargetEntry(expr,
 								  i,
 								  NULL,
 								  false);
-			tlestate = makeNode(GenericExprState);
-			tlestate->xprstate.expr = (Expr *) tle;
-			tlestate->xprstate.evalfunc = NULL;
-			tlestate->arg = exstate;
-			lefttlist = lappend(lefttlist, tlestate);
-			leftptlist = lappend(leftptlist, tle);
+			lefttlist = lappend(lefttlist, tle);
 
 			/* Process righthand argument */
-			exstate = (ExprState *) lsecond(fstate->args);
-			expr = exstate->expr;
+			expr = (Expr *) lsecond(opexpr->args);
 			tle = makeTargetEntry(expr,
 								  i,
 								  NULL,
 								  false);
-			tlestate = makeNode(GenericExprState);
-			tlestate->xprstate.expr = (Expr *) tle;
-			tlestate->xprstate.evalfunc = NULL;
-			tlestate->arg = exstate;
-			righttlist = lappend(righttlist, tlestate);
-			rightptlist = lappend(rightptlist, tle);
+			righttlist = lappend(righttlist, tle);
 
 			/* Lookup the equality function (potentially cross-type) */
 			fmgr_info(opexpr->opfuncid, &sstate->cur_eq_funcs[i - 1]);
@@ -898,20 +872,22 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 		 * (hack alert!).  The righthand expressions will be evaluated in our
 		 * own innerecontext.
 		 */
-		tupDesc = ExecTypeFromTL(leftptlist, false);
+		tupDesc = ExecTypeFromTL(lefttlist, false);
 		slot = ExecInitExtraTupleSlot(estate);
 		ExecSetSlotDescriptor(slot, tupDesc);
 		sstate->projLeft = ExecBuildProjectionInfo(lefttlist,
 												   NULL,
 												   slot,
+												   parent,
 												   NULL);
 
-		tupDesc = ExecTypeFromTL(rightptlist, false);
+		tupDesc = ExecTypeFromTL(righttlist, false);
 		slot = ExecInitExtraTupleSlot(estate);
 		ExecSetSlotDescriptor(slot, tupDesc);
 		sstate->projRight = ExecBuildProjectionInfo(righttlist,
 													sstate->innerecontext,
 													slot,
+													sstate->planstate,
 													NULL);
 	}
 
@@ -934,7 +910,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 void
 ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 {
-	SubPlan    *subplan = (SubPlan *) node->xprstate.expr;
+	SubPlan    *subplan = node->subplan;
 	PlanState  *planstate = node->planstate;
 	SubLinkType subLinkType = subplan->subLinkType;
 	MemoryContext oldcontext;
@@ -1111,7 +1087,7 @@ void
 ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent)
 {
 	PlanState  *planstate = node->planstate;
-	SubPlan    *subplan = (SubPlan *) node->xprstate.expr;
+	SubPlan    *subplan = node->subplan;
 	EState	   *estate = parent->state;
 	ListCell   *l;
 
@@ -1162,16 +1138,22 @@ ExecInitAlternativeSubPlan(AlternativeSubPlan *asplan, PlanState *parent)
 	SubPlan    *subplan2;
 	Cost		cost1;
 	Cost		cost2;
+	ListCell   *lc;
 
-	asstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecAlternativeSubPlan;
-	asstate->xprstate.expr = (Expr *) asplan;
+	asstate->subplan = asplan;
 
 	/*
 	 * Initialize subplans.  (Can we get away with only initializing the one
 	 * we're going to use?)
 	 */
-	asstate->subplans = (List *) ExecInitExpr((Expr *) asplan->subplans,
-											  parent);
+	foreach(lc, asplan->subplans)
+	{
+		SubPlan    *sp = castNode(SubPlan, lfirst(lc));
+		SubPlanState *sps = ExecInitSubPlan(sp, parent);
+
+		asstate->subplans = lappend(asstate->subplans, sps);
+		parent->subPlan = lappend(parent->subPlan, sps);
+	}
 
 	/*
 	 * Select the one to be used.  For this, we need an estimate of the number
@@ -1209,7 +1191,7 @@ ExecInitAlternativeSubPlan(AlternativeSubPlan *asplan, PlanState *parent)
  * Note: in future we might consider changing to different subplans on the
  * fly, in case the original rowcount estimate turns out to be way off.
  */
-static Datum
+Datum
 ExecAlternativeSubPlan(AlternativeSubPlanState *node,
 					   ExprContext *econtext,
 					   bool *isNull)
diff --git a/src/backend/executor/nodeSubqueryscan.c b/src/backend/executor/nodeSubqueryscan.c
index 230a96f9d2ed266cc5891b4ac13be7755ab09f1f..ae184700a671a8d6bc0c90011831402088062bb8 100644
--- a/src/backend/executor/nodeSubqueryscan.c
+++ b/src/backend/executor/nodeSubqueryscan.c
@@ -120,12 +120,8 @@ ExecInitSubqueryScan(SubqueryScan *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	subquerystate->ss.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.targetlist,
-					 (PlanState *) subquerystate);
-	subquerystate->ss.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.qual,
-					 (PlanState *) subquerystate);
+	subquerystate->ss.ps.qual =
+		ExecInitQual(node->scan.plan.qual, (PlanState *) subquerystate);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c
index 628f1ba074d57e565ad169dd842aef5c9d02e8e9..e9df48044e36c6ed61f10ee85c885fce5d6622c2 100644
--- a/src/backend/executor/nodeTableFuncscan.c
+++ b/src/backend/executor/nodeTableFuncscan.c
@@ -139,12 +139,8 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	scanstate->ss.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.targetlist,
-					 (PlanState *) scanstate);
-	scanstate->ss.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.qual,
-					 (PlanState *) scanstate);
+	scanstate->ss.ps.qual =
+		ExecInitQual(node->scan.plan.qual, &scanstate->ss.ps);
 
 	/*
 	 * tuple table initialization
@@ -179,16 +175,16 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags)
 
 	scanstate->ns_names = tf->ns_names;
 
-	scanstate->ns_uris = (List *)
-		ExecInitExpr((Expr *) tf->ns_uris, (PlanState *) scanstate);
+	scanstate->ns_uris =
+		ExecInitExprList(tf->ns_uris, (PlanState *) scanstate);
 	scanstate->docexpr =
 		ExecInitExpr((Expr *) tf->docexpr, (PlanState *) scanstate);
 	scanstate->rowexpr =
 		ExecInitExpr((Expr *) tf->rowexpr, (PlanState *) scanstate);
-	scanstate->colexprs = (List *)
-		ExecInitExpr((Expr *) tf->colexprs, (PlanState *) scanstate);
-	scanstate->coldefexprs = (List *)
-		ExecInitExpr((Expr *) tf->coldefexprs, (PlanState *) scanstate);
+	scanstate->colexprs =
+		ExecInitExprList(tf->colexprs, (PlanState *) scanstate);
+	scanstate->coldefexprs =
+		ExecInitExprList(tf->coldefexprs, (PlanState *) scanstate);
 
 	scanstate->notnulls = tf->notnulls;
 
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index 13ed886577a6327665901478146c3034aea80168..4860ec0f4dac077fea06abff3fbbd127d40db639 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -38,11 +38,85 @@
 	 ((Var *) (node))->varattno == SelfItemPointerAttributeNumber && \
 	 ((Var *) (node))->varlevelsup == 0)
 
-static void TidListCreate(TidScanState *tidstate);
+/* one element in tss_tidexprs */
+typedef struct TidExpr
+{
+	ExprState  *exprstate;		/* ExprState for a TID-yielding subexpr */
+	bool		isarray;		/* if true, it yields tid[] not just tid */
+	CurrentOfExpr *cexpr;		/* alternatively, we can have CURRENT OF */
+} TidExpr;
+
+static void TidExprListCreate(TidScanState *tidstate);
+static void TidListEval(TidScanState *tidstate);
 static int	itemptr_comparator(const void *a, const void *b);
 static TupleTableSlot *TidNext(TidScanState *node);
 
 
+/*
+ * Extract the qual subexpressions that yield TIDs to search for,
+ * and compile them into ExprStates if they're ordinary expressions.
+ *
+ * CURRENT OF is a special case that we can't compile usefully;
+ * just drop it into the TidExpr list as-is.
+ */
+static void
+TidExprListCreate(TidScanState *tidstate)
+{
+	TidScan    *node = (TidScan *) tidstate->ss.ps.plan;
+	ListCell   *l;
+
+	tidstate->tss_tidexprs = NIL;
+	tidstate->tss_isCurrentOf = false;
+
+	foreach(l, node->tidquals)
+	{
+		Expr	   *expr = (Expr *) lfirst(l);
+		TidExpr    *tidexpr = (TidExpr *) palloc0(sizeof(TidExpr));
+
+		if (is_opclause(expr))
+		{
+			Node	   *arg1;
+			Node	   *arg2;
+
+			arg1 = get_leftop(expr);
+			arg2 = get_rightop(expr);
+			if (IsCTIDVar(arg1))
+				tidexpr->exprstate = ExecInitExpr((Expr *) arg2,
+												  &tidstate->ss.ps);
+			else if (IsCTIDVar(arg2))
+				tidexpr->exprstate = ExecInitExpr((Expr *) arg1,
+												  &tidstate->ss.ps);
+			else
+				elog(ERROR, "could not identify CTID variable");
+			tidexpr->isarray = false;
+		}
+		else if (expr && IsA(expr, ScalarArrayOpExpr))
+		{
+			ScalarArrayOpExpr *saex = (ScalarArrayOpExpr *) expr;
+
+			Assert(IsCTIDVar(linitial(saex->args)));
+			tidexpr->exprstate = ExecInitExpr(lsecond(saex->args),
+											  &tidstate->ss.ps);
+			tidexpr->isarray = true;
+		}
+		else if (expr && IsA(expr, CurrentOfExpr))
+		{
+			CurrentOfExpr *cexpr = (CurrentOfExpr *) expr;
+
+			tidexpr->cexpr = cexpr;
+			tidstate->tss_isCurrentOf = true;
+		}
+		else
+			elog(ERROR, "could not identify CTID expression");
+
+		tidstate->tss_tidexprs = lappend(tidstate->tss_tidexprs, tidexpr);
+	}
+
+	/* CurrentOfExpr could never appear OR'd with something else */
+	Assert(list_length(tidstate->tss_tidexprs) == 1 ||
+		   !tidstate->tss_isCurrentOf);
+}
+
 /*
  * Compute the list of TIDs to be visited, by evaluating the expressions
  * for them.
@@ -50,9 +124,8 @@ static TupleTableSlot *TidNext(TidScanState *node);
  * (The result is actually an array, not a list.)
  */
 static void
-TidListCreate(TidScanState *tidstate)
+TidListEval(TidScanState *tidstate)
 {
-	List	   *evalList = tidstate->tss_tidquals;
 	ExprContext *econtext = tidstate->ss.ps.ps_ExprContext;
 	BlockNumber nblocks;
 	ItemPointerData *tidList;
@@ -73,36 +146,21 @@ TidListCreate(TidScanState *tidstate)
 	 * are simple OpExprs or CurrentOfExprs.  If there are any
 	 * ScalarArrayOpExprs, we may have to enlarge the array.
 	 */
-	numAllocTids = list_length(evalList);
+	numAllocTids = list_length(tidstate->tss_tidexprs);
 	tidList = (ItemPointerData *)
 		palloc(numAllocTids * sizeof(ItemPointerData));
 	numTids = 0;
-	tidstate->tss_isCurrentOf = false;
 
-	foreach(l, evalList)
+	foreach(l, tidstate->tss_tidexprs)
 	{
-		ExprState  *exstate = (ExprState *) lfirst(l);
-		Expr	   *expr = exstate->expr;
+		TidExpr    *tidexpr = (TidExpr *) lfirst(l);
 		ItemPointer itemptr;
 		bool		isNull;
 
-		if (is_opclause(expr))
+		if (tidexpr->exprstate && !tidexpr->isarray)
 		{
-			FuncExprState *fexstate = (FuncExprState *) exstate;
-			Node	   *arg1;
-			Node	   *arg2;
-
-			arg1 = get_leftop(expr);
-			arg2 = get_rightop(expr);
-			if (IsCTIDVar(arg1))
-				exstate = (ExprState *) lsecond(fexstate->args);
-			else if (IsCTIDVar(arg2))
-				exstate = (ExprState *) linitial(fexstate->args);
-			else
-				elog(ERROR, "could not identify CTID variable");
-
 			itemptr = (ItemPointer)
-				DatumGetPointer(ExecEvalExprSwitchContext(exstate,
+				DatumGetPointer(ExecEvalExprSwitchContext(tidexpr->exprstate,
 														  econtext,
 														  &isNull));
 			if (!isNull &&
@@ -119,9 +177,8 @@ TidListCreate(TidScanState *tidstate)
 				tidList[numTids++] = *itemptr;
 			}
 		}
-		else if (expr && IsA(expr, ScalarArrayOpExpr))
+		else if (tidexpr->exprstate && tidexpr->isarray)
 		{
-			ScalarArrayOpExprState *saexstate = (ScalarArrayOpExprState *) exstate;
 			Datum		arraydatum;
 			ArrayType  *itemarray;
 			Datum	   *ipdatums;
@@ -129,8 +186,7 @@ TidListCreate(TidScanState *tidstate)
 			int			ndatums;
 			int			i;
 
-			exstate = (ExprState *) lsecond(saexstate->fxprstate.args);
-			arraydatum = ExecEvalExprSwitchContext(exstate,
+			arraydatum = ExecEvalExprSwitchContext(tidexpr->exprstate,
 												   econtext,
 												   &isNull);
 			if (isNull)
@@ -159,12 +215,12 @@ TidListCreate(TidScanState *tidstate)
 			pfree(ipdatums);
 			pfree(ipnulls);
 		}
-		else if (expr && IsA(expr, CurrentOfExpr))
+		else
 		{
-			CurrentOfExpr *cexpr = (CurrentOfExpr *) expr;
 			ItemPointerData cursor_tid;
 
-			if (execCurrentOf(cexpr, econtext,
+			Assert(tidexpr->cexpr);
+			if (execCurrentOf(tidexpr->cexpr, econtext,
 						   RelationGetRelid(tidstate->ss.ss_currentRelation),
 							  &cursor_tid))
 			{
@@ -176,11 +232,8 @@ TidListCreate(TidScanState *tidstate)
 								 numAllocTids * sizeof(ItemPointerData));
 				}
 				tidList[numTids++] = cursor_tid;
-				tidstate->tss_isCurrentOf = true;
 			}
 		}
-		else
-			elog(ERROR, "could not identify CTID expression");
 	}
 
 	/*
@@ -272,11 +325,15 @@ TidNext(TidScanState *node)
 	 * First time through, compute the list of TIDs to be visited
 	 */
 	if (node->tss_TidList == NULL)
-		TidListCreate(node);
+		TidListEval(node);
 
 	tidList = node->tss_TidList;
 	numTids = node->tss_NumTids;
 
+	/*
+	 * We use node->tss_htup as the tuple pointer; note this can't just be a
+	 * local variable here, as the scan tuple slot will keep a pointer to it.
+	 */
 	tuple = &(node->tss_htup);
 
 	/*
@@ -470,16 +527,10 @@ ExecInitTidScan(TidScan *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	tidstate->ss.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.targetlist,
-					 (PlanState *) tidstate);
-	tidstate->ss.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.qual,
-					 (PlanState *) tidstate);
-
-	tidstate->tss_tidquals = (List *)
-		ExecInitExpr((Expr *) node->tidquals,
-					 (PlanState *) tidstate);
+	tidstate->ss.ps.qual =
+		ExecInitQual(node->scan.plan.qual, (PlanState *) tidstate);
+
+	TidExprListCreate(tidstate);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c
index 9883a8b1301fd3c34a34affc6bab0e91697d0dbd..9ee776c4c320c72aad2a1c63ceec9c5eab47f5ed 100644
--- a/src/backend/executor/nodeValuesscan.c
+++ b/src/backend/executor/nodeValuesscan.c
@@ -120,7 +120,7 @@ ValuesNext(ValuesScanState *node)
 		 * is a SubPlan, and there shouldn't be any (any subselects in the
 		 * VALUES list should be InitPlans).
 		 */
-		exprstatelist = (List *) ExecInitExpr((Expr *) exprlist, NULL);
+		exprstatelist = ExecInitExprList(exprlist, NULL);
 
 		/* parser should have checked all sublists are the same length */
 		Assert(list_length(exprstatelist) == slot->tts_tupleDescriptor->natts);
@@ -242,12 +242,8 @@ ExecInitValuesScan(ValuesScan *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	scanstate->ss.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.targetlist,
-					 (PlanState *) scanstate);
-	scanstate->ss.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.qual,
-					 (PlanState *) scanstate);
+	scanstate->ss.ps.qual =
+		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
 
 	/*
 	 * get info about values list
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 2a123e8452688c09afda0901a7be0e222a17e131..628bc9f00b70197c8c2f522970e6000a7bf512ab 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -1826,16 +1826,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_1 = ExecInitExtraTupleSlot(estate);
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate);
 
-	winstate->ss.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->plan.targetlist,
-					 (PlanState *) winstate);
-
 	/*
 	 * WindowAgg nodes never have quals, since they can only occur at the
 	 * logical top level of a query (ie, after any WHERE or HAVING filters)
 	 */
 	Assert(node->plan.qual == NIL);
-	winstate->ss.ps.qual = NIL;
+	winstate->ss.ps.qual = NULL;
 
 	/*
 	 * initialize child nodes
@@ -1894,7 +1890,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	foreach(l, winstate->funcs)
 	{
 		WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l);
-		WindowFunc *wfunc = (WindowFunc *) wfuncstate->xprstate.expr;
+		WindowFunc *wfunc = wfuncstate->wfunc;
 		WindowStatePerFunc perfuncstate;
 		AclResult	aclresult;
 		int			i;
diff --git a/src/backend/executor/nodeWorktablescan.c b/src/backend/executor/nodeWorktablescan.c
index 23b5b9498576512067bddfbfdcae0f64208f7a2b..d7616be06527534d62537a882e381848eadcbd1e 100644
--- a/src/backend/executor/nodeWorktablescan.c
+++ b/src/backend/executor/nodeWorktablescan.c
@@ -156,12 +156,8 @@ ExecInitWorkTableScan(WorkTableScan *node, EState *estate, int eflags)
 	/*
 	 * initialize child expressions
 	 */
-	scanstate->ss.ps.targetlist = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.targetlist,
-					 (PlanState *) scanstate);
-	scanstate->ss.ps.qual = (List *)
-		ExecInitExpr((Expr *) node->scan.plan.qual,
-					 (PlanState *) scanstate);
+	scanstate->ss.ps.qual =
+		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index a129d1ecb3b2f94b912e3500428b782f3123c1b4..57229059bda29b53bce5bffd06e5724009c9ec54 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -3501,7 +3501,7 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 		/*
 		 * Aggref and WindowFunc nodes are (and should be) treated like Vars,
 		 * ie, zero execution cost in the current model, because they behave
-		 * essentially like Vars in execQual.c.  We disregard the costs of
+		 * essentially like Vars at execution.  We disregard the costs of
 		 * their input expressions for the same reason.  The actual execution
 		 * costs of the aggregate/window functions and their arguments have to
 		 * be factored into plan-node-specific costing of the Agg or WindowAgg
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 68d74cb4324ca3e172fce96de92034c2ef2e2bf7..90619509a2aa7d320a390929ea282a1e9b03fdf0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -5013,7 +5013,7 @@ make_pathkeys_for_window(PlannerInfo *root, WindowClause *wc,
  * bloat the sort dataset, and because it might cause unexpected output order
  * if the sort isn't stable.  However there's a constraint on that: all SRFs
  * in the tlist should be evaluated at the same plan step, so that they can
- * run in sync in ExecTargetList.  So if any SRFs are in sort columns, we
+ * run in sync in nodeProjectSet.  So if any SRFs are in sort columns, we
  * mustn't postpone any SRFs.  (Note that in principle that policy should
  * probably get applied to the group/window input targetlists too, but we
  * have not done that historically.)  Lastly, expensive expressions are
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index b19380e1b1ef640e62cde7a4b6f0231296c0a14a..42bba543e934e0a6b9b5a94747b315480baa1363 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -3395,7 +3395,7 @@ eval_const_expressions_mutator(Node *node,
 						 * Else, make a scalar (argisrow == false) NullTest
 						 * for this field.  Scalar semantics are required
 						 * because IS [NOT] NULL doesn't recurse; see comments
-						 * in ExecEvalNullTest().
+						 * in ExecEvalRowNullInt().
 						 */
 						newntest = makeNode(NullTest);
 						newntest->arg = (Expr *) relem;
@@ -3539,8 +3539,8 @@ eval_const_expressions_mutator(Node *node,
  *		FALSE: drop (does not affect result)
  *		TRUE: force result to TRUE
  *		NULL: keep only one
- * We must keep one NULL input because ExecEvalOr returns NULL when no input
- * is TRUE and at least one is NULL.  We don't actually include the NULL
+ * We must keep one NULL input because OR expressions evaluate to NULL when no
+ * input is TRUE and at least one is NULL.  We don't actually include the NULL
  * here, that's supposed to be done by the caller.
  *
  * The output arguments *haveNull and *forceTrue must be initialized FALSE
@@ -3651,9 +3651,9 @@ simplify_or_arguments(List *args,
  *		TRUE: drop (does not affect result)
  *		FALSE: force result to FALSE
  *		NULL: keep only one
- * We must keep one NULL input because ExecEvalAnd returns NULL when no input
- * is FALSE and at least one is NULL.  We don't actually include the NULL
- * here, that's supposed to be done by the caller.
+ * We must keep one NULL input because AND expressions evaluate to NULL when
+ * no input is FALSE and at least one is NULL.  We don't actually include the
+ * NULL here, that's supposed to be done by the caller.
  *
  * The output arguments *haveNull and *forceFalse must be initialized FALSE
  * by the caller.  They will be set TRUE if a null constant or false constant,
diff --git a/src/backend/utils/adt/domains.c b/src/backend/utils/adt/domains.c
index c2ad440013668aa77189d248891e1abbf193dfcc..73deaa7e1cf2d84b7db730d67a51b8abd67e7ce6 100644
--- a/src/backend/utils/adt/domains.c
+++ b/src/backend/utils/adt/domains.c
@@ -107,7 +107,7 @@ domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt)
 	fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, mcxt);
 
 	/* Look up constraints for domain */
-	InitDomainConstraintRef(domainType, &my_extra->constraint_ref, mcxt);
+	InitDomainConstraintRef(domainType, &my_extra->constraint_ref, mcxt, true);
 
 	/* We don't make an ExprContext until needed */
 	my_extra->econtext = NULL;
@@ -122,7 +122,9 @@ domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt)
 /*
  * domain_check_input - apply the cached checks.
  *
- * This is extremely similar to ExecEvalCoerceToDomain in execQual.c.
+ * This is roughly similar to the handling of CoerceToDomain nodes in
+ * execExpr*.c, but we execute each constraint separately, rather than
+ * compiling them in-line within a larger expression.
  */
 static void
 domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
@@ -149,9 +151,6 @@ domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
 				break;
 			case DOM_CONSTRAINT_CHECK:
 				{
-					Datum		conResult;
-					bool		conIsNull;
-
 					/* Make the econtext if we didn't already */
 					if (econtext == NULL)
 					{
@@ -165,24 +164,20 @@ domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
 
 					/*
 					 * Set up value to be returned by CoerceToDomainValue
-					 * nodes.  Unlike ExecEvalCoerceToDomain, this econtext
-					 * couldn't be shared with anything else, so no need to
-					 * save and restore fields.  But we do need to protect the
-					 * passed-in value against being changed by called
-					 * functions.  (It couldn't be a R/W expanded object for
-					 * most uses, but that seems possible for domain_check().)
+					 * nodes.  Unlike in the generic expression case, this
+					 * econtext couldn't be shared with anything else, so no
+					 * need to save and restore fields.  But we do need to
+					 * protect the passed-in value against being changed by
+					 * called functions.  (It couldn't be a R/W expanded
+					 * object for most uses, but that seems possible for
+					 * domain_check().)
 					 */
 					econtext->domainValue_datum =
 						MakeExpandedObjectReadOnly(value, isnull,
 									my_extra->constraint_ref.tcache->typlen);
 					econtext->domainValue_isNull = isnull;
 
-					conResult = ExecEvalExprSwitchContext(con->check_expr,
-														  econtext,
-														  &conIsNull);
-
-					if (!conIsNull &&
-						!DatumGetBool(conResult))
+					if (!ExecCheck(con->check_exprstate, econtext))
 						ereport(ERROR,
 								(errcode(ERRCODE_CHECK_VIOLATION),
 								 errmsg("value for domain %s violates check constraint \"%s\"",
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 81c91039e40925a155c5b4794e5b61516a2523b4..d57d5568b2800c698c7e5d68a03bef32de4bd367 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7000,7 +7000,7 @@ find_param_referent(Param *param, deparse_context *context,
 			foreach(lc2, ps->subPlan)
 			{
 				SubPlanState *sstate = (SubPlanState *) lfirst(lc2);
-				SubPlan    *subplan = (SubPlan *) sstate->xprstate.expr;
+				SubPlan    *subplan = sstate->subplan;
 				ListCell   *lc3;
 				ListCell   *lc4;
 
@@ -7041,7 +7041,7 @@ find_param_referent(Param *param, deparse_context *context,
 					continue;
 
 				/* No parameters to be had here. */
-				Assert(((SubPlan *) sstate->xprstate.expr)->parParam == NIL);
+				Assert(sstate->subplan->parParam == NIL);
 
 				/* Keep looking, but we are emerging from an initplan. */
 				in_same_plan_level = false;
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 1908b13db5c7f4328c25316decf6d28b34137db6..2f87151beccc7da1be09b077558303d860cac77a 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -72,7 +72,6 @@
 #include "catalog/pg_class.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
-#include "executor/executor.h"
 #include "executor/spi.h"
 #include "executor/tablefunc.h"
 #include "fmgr.h"
@@ -620,10 +619,11 @@ xmltotext_with_xmloption(xmltype *data, XmlOptionType xmloption_arg)
 
 
 xmltype *
-xmlelement(XmlExprState *xmlExpr, ExprContext *econtext)
+xmlelement(XmlExpr *xexpr,
+		   Datum *named_argvalue, bool *named_argnull,
+		   Datum *argvalue, bool *argnull)
 {
 #ifdef USE_LIBXML
-	XmlExpr    *xexpr = (XmlExpr *) xmlExpr->xprstate.expr;
 	xmltype    *result;
 	List	   *named_arg_strings;
 	List	   *arg_strings;
@@ -635,48 +635,47 @@ xmlelement(XmlExprState *xmlExpr, ExprContext *econtext)
 	volatile xmlTextWriterPtr writer = NULL;
 
 	/*
-	 * We first evaluate all the arguments, then start up libxml and create
-	 * the result.  This avoids issues if one of the arguments involves a call
-	 * to some other function or subsystem that wants to use libxml on its own
-	 * terms.
+	 * All arguments are already evaluated, and their values are passed in the
+	 * named_argvalue/named_argnull or argvalue/argnull arrays.  This avoids
+	 * issues if one of the arguments involves a call to some other function
+	 * or subsystem that wants to use libxml on its own terms.  We examine the
+	 * original XmlExpr to identify the numbers and types of the arguments.
 	 */
 	named_arg_strings = NIL;
 	i = 0;
-	foreach(arg, xmlExpr->named_args)
+	foreach(arg, xexpr->named_args)
 	{
-		ExprState  *e = (ExprState *) lfirst(arg);
-		Datum		value;
-		bool		isnull;
+		Expr	   *e = (Expr *) lfirst(arg);
 		char	   *str;
 
-		value = ExecEvalExpr(e, econtext, &isnull);
-		if (isnull)
+		if (named_argnull[i])
 			str = NULL;
 		else
-			str = map_sql_value_to_xml_value(value, exprType((Node *) e->expr), false);
+			str = map_sql_value_to_xml_value(named_argvalue[i],
+											 exprType((Node *) e),
+											 false);
 		named_arg_strings = lappend(named_arg_strings, str);
 		i++;
 	}
 
 	arg_strings = NIL;
-	foreach(arg, xmlExpr->args)
+	i = 0;
+	foreach(arg, xexpr->args)
 	{
-		ExprState  *e = (ExprState *) lfirst(arg);
-		Datum		value;
-		bool		isnull;
+		Expr	   *e = (Expr *) lfirst(arg);
 		char	   *str;
 
-		value = ExecEvalExpr(e, econtext, &isnull);
 		/* here we can just forget NULL elements immediately */
-		if (!isnull)
+		if (!argnull[i])
 		{
-			str = map_sql_value_to_xml_value(value,
-										   exprType((Node *) e->expr), true);
+			str = map_sql_value_to_xml_value(argvalue[i],
+											 exprType((Node *) e),
+											 true);
 			arg_strings = lappend(arg_strings, str);
 		}
+		i++;
 	}
 
-	/* now safe to run libxml */
 	xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
 
 	PG_TRY();
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 6992634c397095d87d0f384b492d2ebbcd691264..0cf5001a758175a8f0dc86f64bbc5a1a1ed28aeb 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -96,11 +96,11 @@ static TypeCacheEntry *firstDomainTypeEntry = NULL;
  * this struct for the common case of a constraint-less domain; we just set
  * domainData to NULL to indicate that.
  *
- * Within a DomainConstraintCache, we abuse the DomainConstraintState node
- * type a bit: check_expr fields point to expression plan trees, not plan
- * state trees.  When needed, expression state trees are built by flat-copying
- * the DomainConstraintState nodes and applying ExecInitExpr to check_expr.
- * Such a state tree is not part of the DomainConstraintCache, but is
+ * Within a DomainConstraintCache, we store expression plan trees, but the
+ * check_exprstate fields of the DomainConstraintState nodes are just NULL.
+ * When needed, expression evaluation nodes are built by flat-copying the
+ * DomainConstraintState nodes and applying ExecInitExpr to check_expr.
+ * Such a node tree is not part of the DomainConstraintCache, but is
  * considered to belong to a DomainConstraintRef.
  */
 struct DomainConstraintCache
@@ -779,8 +779,8 @@ load_domaintype_info(TypeCacheEntry *typentry)
 			r = makeNode(DomainConstraintState);
 			r->constrainttype = DOM_CONSTRAINT_CHECK;
 			r->name = pstrdup(NameStr(c->conname));
-			/* Must cast here because we're not storing an expr state node */
-			r->check_expr = (ExprState *) check_expr;
+			r->check_expr = check_expr;
+			r->check_exprstate = NULL;
 
 			MemoryContextSwitchTo(oldcxt);
 
@@ -859,6 +859,7 @@ load_domaintype_info(TypeCacheEntry *typentry)
 		r->constrainttype = DOM_CONSTRAINT_NOTNULL;
 		r->name = pstrdup("NOT NULL");
 		r->check_expr = NULL;
+		r->check_exprstate = NULL;
 
 		/* lcons to apply the nullness check FIRST */
 		dcc->constraints = lcons(r, dcc->constraints);
@@ -946,8 +947,8 @@ prep_domain_constraints(List *constraints, MemoryContext execctx)
 		newr = makeNode(DomainConstraintState);
 		newr->constrainttype = r->constrainttype;
 		newr->name = r->name;
-		/* Must cast here because cache items contain expr plan trees */
-		newr->check_expr = ExecInitExpr((Expr *) r->check_expr, NULL);
+		newr->check_expr = r->check_expr;
+		newr->check_exprstate = ExecInitExpr(r->check_expr, NULL);
 
 		result = lappend(result, newr);
 	}
@@ -962,13 +963,18 @@ prep_domain_constraints(List *constraints, MemoryContext execctx)
  *
  * Caller must tell us the MemoryContext in which the DomainConstraintRef
  * lives.  The ref will be cleaned up when that context is reset/deleted.
+ *
+ * Caller must also tell us whether it wants check_exprstate fields to be
+ * computed in the DomainConstraintState nodes attached to this ref.
+ * If it doesn't, we need not make a copy of the DomainConstraintState list.
  */
 void
 InitDomainConstraintRef(Oid type_id, DomainConstraintRef *ref,
-						MemoryContext refctx)
+						MemoryContext refctx, bool need_exprstate)
 {
 	/* Look up the typcache entry --- we assume it survives indefinitely */
 	ref->tcache = lookup_type_cache(type_id, TYPECACHE_DOMAIN_INFO);
+	ref->need_exprstate = need_exprstate;
 	/* For safety, establish the callback before acquiring a refcount */
 	ref->refctx = refctx;
 	ref->dcc = NULL;
@@ -980,8 +986,11 @@ InitDomainConstraintRef(Oid type_id, DomainConstraintRef *ref,
 	{
 		ref->dcc = ref->tcache->domainData;
 		ref->dcc->dccRefCount++;
-		ref->constraints = prep_domain_constraints(ref->dcc->constraints,
-												   ref->refctx);
+		if (ref->need_exprstate)
+			ref->constraints = prep_domain_constraints(ref->dcc->constraints,
+													   ref->refctx);
+		else
+			ref->constraints = ref->dcc->constraints;
 	}
 	else
 		ref->constraints = NIL;
@@ -1032,8 +1041,11 @@ UpdateDomainConstraintRef(DomainConstraintRef *ref)
 		{
 			ref->dcc = dcc;
 			dcc->dccRefCount++;
-			ref->constraints = prep_domain_constraints(dcc->constraints,
-													   ref->refctx);
+			if (ref->need_exprstate)
+				ref->constraints = prep_domain_constraints(dcc->constraints,
+														   ref->refctx);
+			else
+				ref->constraints = dcc->constraints;
 		}
 	}
 }
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
new file mode 100644
index 0000000000000000000000000000000000000000..a665388232197a9ed9abab4e08c4c31ccc94bfd4
--- /dev/null
+++ b/src/include/executor/execExpr.h
@@ -0,0 +1,642 @@
+/*-------------------------------------------------------------------------
+ *
+ * execExpr.h
+ *	  Low level infrastructure related to expression evaluation
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/execExpr.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef EXEC_EXPR_H
+#define EXEC_EXPR_H
+
+#include "nodes/execnodes.h"
+
+/* forward reference to avoid circularity */
+struct ArrayRefState;
+
+/* Bits in ExprState->flags (see also execnodes.h for public flag bits): */
+/* expression's interpreter has been initialized */
+#define EEO_FLAG_INTERPRETER_INITIALIZED	(1 << 1)
+/* jump-threading is in use */
+#define EEO_FLAG_DIRECT_THREADED			(1 << 2)
+
+/*
+ * Discriminator for ExprEvalSteps.
+ *
+ * Identifies the operation to be executed and which member in the
+ * ExprEvalStep->d union is valid.
+ *
+ * The order of entries needs to be kept in sync with the dispatch_table[]
+ * array in execExprInterp.c:ExecInterpExpr().
+ */
+typedef enum ExprEvalOp
+{
+	/* entire expression has been evaluated completely, return */
+	EEOP_DONE,
+
+	/* apply slot_getsomeattrs on corresponding tuple slot */
+	EEOP_INNER_FETCHSOME,
+	EEOP_OUTER_FETCHSOME,
+	EEOP_SCAN_FETCHSOME,
+
+	/* compute non-system Var value */
+	/* "FIRST" variants are used only the first time through */
+	EEOP_INNER_VAR_FIRST,
+	EEOP_INNER_VAR,
+	EEOP_OUTER_VAR_FIRST,
+	EEOP_OUTER_VAR,
+	EEOP_SCAN_VAR_FIRST,
+	EEOP_SCAN_VAR,
+
+	/* compute system Var value */
+	EEOP_INNER_SYSVAR,
+	EEOP_OUTER_SYSVAR,
+	EEOP_SCAN_SYSVAR,
+
+	/* compute wholerow Var */
+	EEOP_WHOLEROW,
+
+	/* compute non-system Var value, assign it into ExprState's resultslot */
+	/* (these are not used if _FIRST checks would be needed) */
+	EEOP_ASSIGN_INNER_VAR,
+	EEOP_ASSIGN_OUTER_VAR,
+	EEOP_ASSIGN_SCAN_VAR,
+
+	/* assign ExprState's resvalue/resnull to a column of its resultslot */
+	EEOP_ASSIGN_TMP,
+	/* ditto, applying MakeExpandedObjectReadOnly() */
+	EEOP_ASSIGN_TMP_MAKE_RO,
+
+	/* evaluate Const value */
+	EEOP_CONST,
+
+	/*
+	 * Evaluate function call (including OpExprs etc).  For speed, we
+	 * distinguish in the opcode whether the function is strict and/or
+	 * requires usage stats tracking.
+	 */
+	EEOP_FUNCEXPR,
+	EEOP_FUNCEXPR_STRICT,
+	EEOP_FUNCEXPR_FUSAGE,
+	EEOP_FUNCEXPR_STRICT_FUSAGE,
+
+	/*
+	 * Evaluate boolean AND expression, one step per subexpression. FIRST/LAST
+	 * subexpressions are special-cased for performance.  Since AND always has
+	 * at least two subexpressions, FIRST and LAST never apply to the same
+	 * subexpression.
+	 */
+	EEOP_BOOL_AND_STEP_FIRST,
+	EEOP_BOOL_AND_STEP,
+	EEOP_BOOL_AND_STEP_LAST,
+
+	/* similarly for boolean OR expression */
+	EEOP_BOOL_OR_STEP_FIRST,
+	EEOP_BOOL_OR_STEP,
+	EEOP_BOOL_OR_STEP_LAST,
+
+	/* evaluate boolean NOT expression */
+	EEOP_BOOL_NOT_STEP,
+
+	/* simplified version of BOOL_AND_STEP for use by ExecQual() */
+	EEOP_QUAL,
+
+	/* unconditional jump to another step */
+	EEOP_JUMP,
+
+	/* conditional jumps based on current result value */
+	EEOP_JUMP_IF_NULL,
+	EEOP_JUMP_IF_NOT_NULL,
+	EEOP_JUMP_IF_NOT_TRUE,
+
+	/* perform NULL tests for scalar values */
+	EEOP_NULLTEST_ISNULL,
+	EEOP_NULLTEST_ISNOTNULL,
+
+	/* perform NULL tests for row values */
+	EEOP_NULLTEST_ROWISNULL,
+	EEOP_NULLTEST_ROWISNOTNULL,
+
+	/* evaluate a BooleanTest expression */
+	EEOP_BOOLTEST_IS_TRUE,
+	EEOP_BOOLTEST_IS_NOT_TRUE,
+	EEOP_BOOLTEST_IS_FALSE,
+	EEOP_BOOLTEST_IS_NOT_FALSE,
+
+	/* evaluate PARAM_EXEC/EXTERN parameters */
+	EEOP_PARAM_EXEC,
+	EEOP_PARAM_EXTERN,
+
+	/* return CaseTestExpr value */
+	EEOP_CASE_TESTVAL,
+
+	/* apply MakeExpandedObjectReadOnly() to target value */
+	EEOP_MAKE_READONLY,
+
+	/* evaluate assorted special-purpose expression types */
+	EEOP_IOCOERCE,
+	EEOP_DISTINCT,
+	EEOP_NULLIF,
+	EEOP_SQLVALUEFUNCTION,
+	EEOP_CURRENTOFEXPR,
+	EEOP_ARRAYEXPR,
+	EEOP_ARRAYCOERCE,
+	EEOP_ROW,
+
+	/*
+	 * Compare two individual elements of each of two compared ROW()
+	 * expressions.  Skip to ROWCOMPARE_FINAL if elements are not equal.
+	 */
+	EEOP_ROWCOMPARE_STEP,
+
+	/* evaluate boolean value based on previous ROWCOMPARE_STEP operations */
+	EEOP_ROWCOMPARE_FINAL,
+
+	/* evaluate GREATEST() or LEAST() */
+	EEOP_MINMAX,
+
+	/* evaluate FieldSelect expression */
+	EEOP_FIELDSELECT,
+
+	/*
+	 * Deform tuple before evaluating new values for individual fields in a
+	 * FieldStore expression.
+	 */
+	EEOP_FIELDSTORE_DEFORM,
+
+	/*
+	 * Form the new tuple for a FieldStore expression.  Individual fields will
+	 * have been evaluated into columns of the tuple deformed by the preceding
+	 * DEFORM step.
+	 */
+	EEOP_FIELDSTORE_FORM,
+
+	/* Process an array subscript; short-circuit expression to NULL if NULL */
+	EEOP_ARRAYREF_SUBSCRIPT,
+
+	/*
+	 * Compute old array element/slice when an ArrayRef assignment expression
+	 * contains ArrayRef/FieldStore subexpressions.  Value is accessed using
+	 * the CaseTest mechanism.
+	 */
+	EEOP_ARRAYREF_OLD,
+
+	/* compute new value for ArrayRef assignment expression */
+	EEOP_ARRAYREF_ASSIGN,
+
+	/* compute element/slice for ArrayRef fetch expression */
+	EEOP_ARRAYREF_FETCH,
+
+	/* evaluate value for CoerceToDomainValue */
+	EEOP_DOMAIN_TESTVAL,
+
+	/* evaluate a domain's NOT NULL constraint */
+	EEOP_DOMAIN_NOTNULL,
+
+	/* evaluate a single domain CHECK constraint */
+	EEOP_DOMAIN_CHECK,
+
+	/* evaluate assorted special-purpose expression types */
+	EEOP_CONVERT_ROWTYPE,
+	EEOP_SCALARARRAYOP,
+	EEOP_XMLEXPR,
+	EEOP_AGGREF,
+	EEOP_GROUPING_FUNC,
+	EEOP_WINDOW_FUNC,
+	EEOP_SUBPLAN,
+	EEOP_ALTERNATIVE_SUBPLAN,
+
+	/* non-existent operation, used e.g. to check array lengths */
+	EEOP_LAST
+} ExprEvalOp;
+
+
+typedef struct ExprEvalStep
+{
+	/*
+	 * Instruction to be executed.  During instruction preparation this is an
+	 * enum ExprEvalOp, but later it can be changed to some other type, e.g. a
+	 * pointer for computed goto (that's why it's an intptr_t).
+	 */
+	intptr_t	opcode;
+
+	/* where to store the result of this step */
+	Datum	   *resvalue;
+	bool	   *resnull;
+
+	/*
+	 * Inline data for the operation.  Inline data is faster to access, but
+	 * also bloats the size of all instructions.  The union should be kept to
+	 * no more than 40 bytes on 64-bit systems (so that the entire struct is
+	 * no more than 64 bytes, a single cacheline on common systems).
+	 */
+	union
+	{
+		/* for EEOP_INNER/OUTER/SCAN_FETCHSOME */
+		struct
+		{
+			/* attribute number up to which to fetch (inclusive) */
+			int			last_var;
+		}			fetch;
+
+		/* for EEOP_INNER/OUTER/SCAN_[SYS]VAR[_FIRST] */
+		struct
+		{
+			/* attnum is attr number - 1 for regular VAR ... */
+			/* but it's just the normal (negative) attr number for SYSVAR */
+			int			attnum;
+			Oid			vartype;	/* type OID of variable */
+		}			var;
+
+		/* for EEOP_WHOLEROW */
+		struct
+		{
+			Var		   *var;	/* original Var node in plan tree */
+			bool		first;	/* first time through, need to initialize? */
+			bool		slow;	/* need runtime check for nulls? */
+			TupleDesc	tupdesc;	/* descriptor for resulting tuples */
+			JunkFilter *junkFilter;		/* JunkFilter to remove resjunk cols */
+		}			wholerow;
+
+		/* for EEOP_ASSIGN_*_VAR */
+		struct
+		{
+			/* target index in ExprState->resultslot->tts_values/nulls */
+			int			resultnum;
+			/* source attribute number - 1 */
+			int			attnum;
+		}			assign_var;
+
+		/* for EEOP_ASSIGN_TMP[_MAKE_RO] */
+		struct
+		{
+			/* target index in ExprState->resultslot->tts_values/nulls */
+			int			resultnum;
+		}			assign_tmp;
+
+		/* for EEOP_CONST */
+		struct
+		{
+			/* constant's value */
+			Datum		value;
+			bool		isnull;
+		}			constval;
+
+		/* for EEOP_FUNCEXPR_* / NULLIF / DISTINCT */
+		struct
+		{
+			FmgrInfo   *finfo;	/* function's lookup data */
+			FunctionCallInfo fcinfo_data;		/* arguments etc */
+			/* faster to access without additional indirection: */
+			PGFunction	fn_addr;	/* actual call address */
+			int			nargs;	/* number of arguments */
+		}			func;
+
+		/* for EEOP_BOOL_*_STEP */
+		struct
+		{
+			bool	   *anynull;	/* track if any input was NULL */
+			int			jumpdone;		/* jump here if result determined */
+		}			boolexpr;
+
+		/* for EEOP_QUAL */
+		struct
+		{
+			int			jumpdone;		/* jump here on false or null */
+		}			qualexpr;
+
+		/* for EEOP_JUMP[_CONDITION] */
+		struct
+		{
+			int			jumpdone;		/* target instruction's index */
+		}			jump;
+
+		/* for EEOP_NULLTEST_ROWIS[NOT]NULL */
+		struct
+		{
+			/* cached tupdesc pointer - filled at runtime */
+			TupleDesc	argdesc;
+		}			nulltest_row;
+
+		/* for EEOP_PARAM_EXEC/EXTERN */
+		struct
+		{
+			int			paramid;	/* numeric ID for parameter */
+			Oid			paramtype;		/* OID of parameter's datatype */
+		}			param;
+
+		/* for EEOP_CASE_TESTVAL/DOMAIN_TESTVAL */
+		struct
+		{
+			Datum	   *value;	/* value to return */
+			bool	   *isnull;
+		}			casetest;
+
+		/* for EEOP_MAKE_READONLY */
+		struct
+		{
+			Datum	   *value;	/* value to coerce to read-only */
+			bool	   *isnull;
+		}			make_readonly;
+
+		/* for EEOP_IOCOERCE */
+		struct
+		{
+			/* lookup and call info for source type's output function */
+			FmgrInfo   *finfo_out;
+			FunctionCallInfo fcinfo_data_out;
+			/* lookup and call info for result type's input function */
+			FmgrInfo   *finfo_in;
+			FunctionCallInfo fcinfo_data_in;
+		}			iocoerce;
+
+		/* for EEOP_SQLVALUEFUNCTION */
+		struct
+		{
+			SQLValueFunction *svf;
+		}			sqlvaluefunction;
+
+		/* for EEOP_ARRAYEXPR */
+		struct
+		{
+			Datum	   *elemvalues;		/* element values get stored here */
+			bool	   *elemnulls;
+			int			nelems; /* length of the above arrays */
+			Oid			elemtype;		/* array element type */
+			int16		elemlength;		/* typlen of the array element type */
+			bool		elembyval;		/* is the element type pass-by-value? */
+			char		elemalign;		/* typalign of the element type */
+			bool		multidims;		/* is array expression multi-D? */
+		}			arrayexpr;
+
+		/* for EEOP_ARRAYCOERCE */
+		struct
+		{
+			ArrayCoerceExpr *coerceexpr;
+			Oid			resultelemtype; /* element type of result array */
+			FmgrInfo   *elemfunc;		/* lookup info for element coercion
+										 * function */
+			struct ArrayMapState *amstate;		/* workspace for array_map */
+		}			arraycoerce;
+
+		/* for EEOP_ROW */
+		struct
+		{
+			TupleDesc	tupdesc;	/* descriptor for result tuples */
+			/* workspace for the values constituting the row: */
+			Datum	   *elemvalues;
+			bool	   *elemnulls;
+		}			row;
+
+		/* for EEOP_ROWCOMPARE_STEP */
+		struct
+		{
+			/* lookup and call data for column comparison function */
+			FmgrInfo   *finfo;
+			FunctionCallInfo fcinfo_data;
+			PGFunction	fn_addr;
+			/* target for comparison resulting in NULL */
+			int			jumpnull;
+			/* target for comparison yielding inequality */
+			int			jumpdone;
+		}			rowcompare_step;
+
+		/* for EEOP_ROWCOMPARE_FINAL */
+		struct
+		{
+			RowCompareType rctype;
+		}			rowcompare_final;
+
+		/* for EEOP_MINMAX */
+		struct
+		{
+			/* workspace for argument values */
+			Datum	   *values;
+			bool	   *nulls;
+			int			nelems;
+			/* is it GREATEST or LEAST? */
+			MinMaxOp	op;
+			/* lookup and call data for comparison function */
+			FmgrInfo   *finfo;
+			FunctionCallInfo fcinfo_data;
+		}			minmax;
+
+		/* for EEOP_FIELDSELECT */
+		struct
+		{
+			AttrNumber	fieldnum;		/* field number to extract */
+			Oid			resulttype;		/* field's type */
+			/* cached tupdesc pointer - filled at runtime */
+			TupleDesc	argdesc;
+		}			fieldselect;
+
+		/* for EEOP_FIELDSTORE_DEFORM / FIELDSTORE_FORM */
+		struct
+		{
+			/* original expression node */
+			FieldStore *fstore;
+
+			/* cached tupdesc pointer - filled at runtime */
+			/* note that a DEFORM and FORM pair share the same tupdesc */
+			TupleDesc  *argdesc;
+
+			/* workspace for column values */
+			Datum	   *values;
+			bool	   *nulls;
+			int			ncolumns;
+		}			fieldstore;
+
+		/* for EEOP_ARRAYREF_SUBSCRIPT */
+		struct
+		{
+			/* too big to have inline */
+			struct ArrayRefState *state;
+			int			off;	/* 0-based index of this subscript */
+			bool		isupper;	/* is it upper or lower subscript? */
+			int			jumpdone;		/* jump here on null */
+		}			arrayref_subscript;
+
+		/* for EEOP_ARRAYREF_OLD / ASSIGN / FETCH */
+		struct
+		{
+			/* too big to have inline */
+			struct ArrayRefState *state;
+		}			arrayref;
+
+		/* for EEOP_DOMAIN_NOTNULL / DOMAIN_CHECK */
+		struct
+		{
+			/* name of constraint */
+			char	   *constraintname;
+			/* where the result of a CHECK constraint will be stored */
+			Datum	   *checkvalue;
+			bool	   *checknull;
+			/* OID of domain type */
+			Oid			resulttype;
+		}			domaincheck;
+
+		/* for EEOP_CONVERT_ROWTYPE */
+		struct
+		{
+			ConvertRowtypeExpr *convert;		/* original expression */
+			/* these three fields are filled at runtime: */
+			TupleDesc	indesc; /* tupdesc for input type */
+			TupleDesc	outdesc;	/* tupdesc for output type */
+			TupleConversionMap *map;	/* column mapping */
+			bool		initialized;	/* initialized for current types? */
+		}			convert_rowtype;
+
+		/* for EEOP_SCALARARRAYOP */
+		struct
+		{
+			/* element_type/typlen/typbyval/typalign are filled at runtime */
+			Oid			element_type;	/* InvalidOid if not yet filled */
+			bool		useOr;	/* use OR or AND semantics? */
+			int16		typlen; /* array element type storage info */
+			bool		typbyval;
+			char		typalign;
+			FmgrInfo   *finfo;	/* function's lookup data */
+			FunctionCallInfo fcinfo_data;		/* arguments etc */
+			/* faster to access without additional indirection: */
+			PGFunction	fn_addr;	/* actual call address */
+		}			scalararrayop;
+
+		/* for EEOP_XMLEXPR */
+		struct
+		{
+			XmlExpr    *xexpr;	/* original expression node */
+			/* workspace for evaluating named args, if any */
+			Datum	   *named_argvalue;
+			bool	   *named_argnull;
+			/* workspace for evaluating unnamed args, if any */
+			Datum	   *argvalue;
+			bool	   *argnull;
+		}			xmlexpr;
+
+		/* for EEOP_AGGREF */
+		struct
+		{
+			/* out-of-line state, modified by nodeAgg.c */
+			AggrefExprState *astate;
+		}			aggref;
+
+		/* for EEOP_GROUPING_FUNC */
+		struct
+		{
+			AggState   *parent; /* parent Agg */
+			List	   *clauses;	/* integer list of column numbers */
+		}			grouping_func;
+
+		/* for EEOP_WINDOW_FUNC */
+		struct
+		{
+			/* out-of-line state, modified by nodeWindowFunc.c */
+			WindowFuncExprState *wfstate;
+		}			window_func;
+
+		/* for EEOP_SUBPLAN */
+		struct
+		{
+			/* out-of-line state, created by nodeSubplan.c */
+			SubPlanState *sstate;
+		}			subplan;
+
+		/* for EEOP_ALTERNATIVE_SUBPLAN */
+		struct
+		{
+			/* out-of-line state, created by nodeSubplan.c */
+			AlternativeSubPlanState *asstate;
+		}			alternative_subplan;
+	}			d;
+} ExprEvalStep;
+
+
+/* Non-inline data for array operations */
+typedef struct ArrayRefState
+{
+	bool		isassignment;	/* is it assignment, or just fetch? */
+
+	Oid			refelemtype;	/* OID of the array element type */
+	int16		refattrlength;	/* typlen of array type */
+	int16		refelemlength;	/* typlen of the array element type */
+	bool		refelembyval;	/* is the element type pass-by-value? */
+	char		refelemalign;	/* typalign of the element type */
+
+	/* numupper and upperprovided[] are filled at compile time */
+	/* at runtime, extracted subscript datums get stored in upperindex[] */
+	int			numupper;
+	bool		upperprovided[MAXDIM];
+	int			upperindex[MAXDIM];
+
+	/* similarly for lower indexes, if any */
+	int			numlower;
+	bool		lowerprovided[MAXDIM];
+	int			lowerindex[MAXDIM];
+
+	/* subscript expressions get evaluated into here */
+	Datum		subscriptvalue;
+	bool		subscriptnull;
+
+	/* for assignment, new value to assign is evaluated into here */
+	Datum		replacevalue;
+	bool		replacenull;
+
+	/* if we have a nested assignment, ARRAYREF_OLD puts old value here */
+	Datum		prevvalue;
+	bool		prevnull;
+} ArrayRefState;
+
+
+extern void ExecReadyInterpretedExpr(ExprState *state);
+
+extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
+
+/*
+ * Non fast-path execution functions. These are externs instead of statics in
+ * execExprInterp.c, because that allows them to be used by other methods of
+ * expression evaluation, reducing code duplication.
+ */
+extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
+				  ExprContext *econtext);
+extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
+					ExprContext *econtext);
+extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalRowNull(ExprState *state, ExprEvalStep *op,
+				ExprContext *econtext);
+extern void ExecEvalRowNotNull(ExprState *state, ExprEvalStep *op,
+				   ExprContext *econtext);
+extern void ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalRow(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalMinMax(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op,
+					ExprContext *econtext);
+extern void ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op,
+						 ExprContext *econtext);
+extern void ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op,
+					   ExprContext *econtext);
+extern bool ExecEvalArrayRefSubscript(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalArrayRefFetch(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalArrayRefOld(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalArrayRefAssign(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op,
+					   ExprContext *econtext);
+extern void ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalSubPlan(ExprState *state, ExprEvalStep *op,
+				ExprContext *econtext);
+extern void ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op,
+						   ExprContext *econtext);
+extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op,
+					ExprContext *econtext);
+
+#endif   /* EXEC_EXPR_H */
diff --git a/src/include/executor/execdebug.h b/src/include/executor/execdebug.h
index cf44c3edbbc49acf0240e5eb806a371db20519a7..8b61520e18960786d9d0240df493bed81be446b4 100644
--- a/src/include/executor/execdebug.h
+++ b/src/include/executor/execdebug.h
@@ -37,13 +37,6 @@
 #undef EXEC_NESTLOOPDEBUG
  */
 
-/* ----------------
- *		EXEC_EVALDEBUG is a flag which turns on debugging of
- *		ExecEval and ExecTargetList() stuff by EV_printf() in execQual.c
- * ----------------
-#undef EXEC_EVALDEBUG
- */
-
 /* ----------------
  *		EXEC_SORTDEBUG is a flag which turns on debugging of
  *		the ExecSort() stuff by SO_printf() in nodeSort.c
@@ -85,20 +78,6 @@
 #define ENL1_printf(message)
 #endif   /* EXEC_NESTLOOPDEBUG */
 
-/* ----------------
- *		exec eval / target list debugging defines
- * ----------------
- */
-#ifdef EXEC_EVALDEBUG
-#define EV_nodeDisplay(l)				nodeDisplay(l)
-#define EV_printf(s)					printf(s)
-#define EV1_printf(s, a)				printf(s, a)
-#else
-#define EV_nodeDisplay(l)
-#define EV_printf(s)
-#define EV1_printf(s, a)
-#endif   /* EXEC_EVALDEBUG */
-
 /* ----------------
  *		sort node debugging defines
  * ----------------
@@ -146,4 +125,4 @@
 #define MJ_DEBUG_PROC_NODE(slot)
 #endif   /* EXEC_MERGEJOINDEBUG */
 
-#endif   /* ExecDebugIncluded */
+#endif   /* EXECDEBUG_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index a5c75e771f0374e1c585491e745dcb96aed3a326..d3849b93eb19c49226c48b3f0fbb0323db1846a6 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -65,15 +65,6 @@
 #define EXEC_FLAG_WITH_NO_DATA	0x0080	/* rel scannability doesn't matter */
 
 
-/*
- * ExecEvalExpr was formerly a function containing a switch statement;
- * now it's just a macro invoking the function pointed to by an ExprState
- * node.  Beware of double evaluation of the ExprState argument!
- */
-#define ExecEvalExpr(expr, econtext, isNull) \
-	((*(expr)->evalfunc) (expr, econtext, isNull))
-
-
 /* Hook for plugins to get control in ExecutorStart() */
 typedef void (*ExecutorStart_hook_type) (QueryDesc *queryDesc, int eflags);
 extern PGDLLIMPORT ExecutorStart_hook_type ExecutorStart_hook;
@@ -242,29 +233,155 @@ extern void ExecEndNode(PlanState *node);
 extern bool ExecShutdownNode(PlanState *node);
 
 /*
- * prototypes from functions in execQual.c
+ * prototypes from functions in execExpr.c
  */
-extern Datum GetAttributeByNum(HeapTupleHeader tuple, AttrNumber attrno,
-				  bool *isNull);
-extern Datum GetAttributeByName(HeapTupleHeader tuple, const char *attname,
-				   bool *isNull);
-extern Tuplestorestate *ExecMakeTableFunctionResult(ExprState *funcexpr,
+extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitQual(List *qual, PlanState *parent);
+extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
+extern List *ExecInitExprList(List *nodes, PlanState *parent);
+extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList,
+						ExprContext *econtext,
+						TupleTableSlot *slot,
+						PlanState *parent,
+						TupleDesc inputDesc);
+extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
+extern ExprState *ExecPrepareQual(List *qual, EState *estate);
+extern ExprState *ExecPrepareCheck(List *qual, EState *estate);
+extern List *ExecPrepareExprList(List *nodes, EState *estate);
+
+/*
+ * ExecEvalExpr
+ *
+ * Evaluate expression identified by "state" in the execution context
+ * given by "econtext".  *isNull is set to the is-null flag for the result,
+ * and the Datum value is the function result.
+ *
+ * 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.
+ */
+#ifndef FRONTEND
+static inline Datum
+ExecEvalExpr(ExprState *state,
+			 ExprContext *econtext,
+			 bool *isNull)
+{
+	return (*state->evalfunc) (state, econtext, isNull);
+}
+#endif
+
+/*
+ * ExecEvalExprSwitchContext
+ *
+ * Same as ExecEvalExpr, but get into the right allocation context explicitly.
+ */
+#ifndef FRONTEND
+static inline Datum
+ExecEvalExprSwitchContext(ExprState *state,
+						  ExprContext *econtext,
+						  bool *isNull)
+{
+	Datum		retDatum;
+	MemoryContext oldContext;
+
+	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+	retDatum = (*state->evalfunc) (state, econtext, isNull);
+	MemoryContextSwitchTo(oldContext);
+	return retDatum;
+}
+#endif
+
+/*
+ * ExecProject
+ *
+ * Projects a tuple based on projection info and stores it in the slot passed
+ * to ExecBuildProjectInfo().
+ *
+ * Note: the result is always a virtual tuple; therefore it may reference
+ * the contents of the exprContext's scan tuples and/or temporary results
+ * constructed in the exprContext.  If the caller wishes the result to be
+ * valid longer than that data will be valid, he must call ExecMaterializeSlot
+ * on the result slot.
+ */
+#ifndef FRONTEND
+static inline TupleTableSlot *
+ExecProject(ProjectionInfo *projInfo)
+{
+	ExprContext *econtext = projInfo->pi_exprContext;
+	ExprState  *state = &projInfo->pi_state;
+	TupleTableSlot *slot = state->resultslot;
+	bool		isnull;
+
+	/*
+	 * Clear any former contents of the result slot.  This makes it safe for
+	 * us to use the slot's Datum/isnull arrays as workspace.
+	 */
+	ExecClearTuple(slot);
+
+	/* Run the expression, discarding scalar result from the last column. */
+	(void) ExecEvalExprSwitchContext(state, econtext, &isnull);
+
+	/*
+	 * Successfully formed a result row.  Mark the result slot as containing a
+	 * valid virtual tuple (inlined version of ExecStoreVirtualTuple()).
+	 */
+	slot->tts_isempty = false;
+	slot->tts_nvalid = slot->tts_tupleDescriptor->natts;
+
+	return slot;
+}
+#endif
+
+/*
+ * ExecQual - evaluate a qual prepared with ExecInitQual (possibly via
+ * ExecPrepareQual).  Returns true if qual is satisfied, else false.
+ *
+ * Note: ExecQual used to have a third argument "resultForNull".  The
+ * behavior of this function now corresponds to resultForNull == false.
+ * If you want the resultForNull == true behavior, see ExecCheck.
+ */
+#ifndef FRONTEND
+static inline bool
+ExecQual(ExprState *state, ExprContext *econtext)
+{
+	Datum		ret;
+	bool		isnull;
+
+	/* short-circuit (here and in ExecInitQual) for empty restriction list */
+	if (state == NULL)
+		return true;
+
+	/* verify that expression was compiled using ExecInitQual */
+	Assert(state->flags & EEO_FLAG_IS_QUAL);
+
+	ret = ExecEvalExprSwitchContext(state, econtext, &isnull);
+
+	/* EEOP_QUAL should never return NULL */
+	Assert(!isnull);
+
+	return DatumGetBool(ret);
+}
+#endif
+
+extern bool ExecCheck(ExprState *state, ExprContext *context);
+
+/*
+ * prototypes from functions in execSRF.c
+ */
+extern SetExprState *ExecInitTableFunctionResult(Expr *expr,
+							ExprContext *econtext, PlanState *parent);
+extern Tuplestorestate *ExecMakeTableFunctionResult(SetExprState *setexpr,
 							ExprContext *econtext,
 							MemoryContext argContext,
 							TupleDesc expectedDesc,
 							bool randomAccess);
-extern Datum ExecMakeFunctionResultSet(FuncExprState *fcache,
+extern SetExprState *ExecInitFunctionResultSet(Expr *expr,
+						  ExprContext *econtext, PlanState *parent);
+extern Datum ExecMakeFunctionResultSet(SetExprState *fcache,
 						  ExprContext *econtext,
 						  bool *isNull,
 						  ExprDoneCond *isDone);
-extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext,
-						  bool *isNull);
-extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
-extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
-extern bool ExecQual(List *qual, ExprContext *econtext, bool resultForNull);
-extern int	ExecTargetListLength(List *targetlist);
-extern int	ExecCleanTargetListLength(List *targetlist);
-extern TupleTableSlot *ExecProject(ProjectionInfo *projInfo);
 
 /*
  * prototypes from functions in execScan.c
@@ -355,10 +472,6 @@ extern void ExecAssignExprContext(EState *estate, PlanState *planstate);
 extern void ExecAssignResultType(PlanState *planstate, TupleDesc tupDesc);
 extern void ExecAssignResultTypeFromTL(PlanState *planstate);
 extern TupleDesc ExecGetResultType(PlanState *planstate);
-extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList,
-						ExprContext *econtext,
-						TupleTableSlot *slot,
-						TupleDesc inputDesc);
 extern void ExecAssignProjectionInfo(PlanState *planstate,
 						 TupleDesc inputDesc);
 extern void ExecFreeExprContext(PlanState *planstate);
@@ -376,8 +489,17 @@ extern void RegisterExprContextCallback(ExprContext *econtext,
 extern void UnregisterExprContextCallback(ExprContext *econtext,
 							  ExprContextCallbackFunction function,
 							  Datum arg);
+
 extern void ExecLockNonLeafAppendTables(List *partitioned_rels, EState *estate);
 
+extern Datum GetAttributeByName(HeapTupleHeader tuple, const char *attname,
+				   bool *isNull);
+extern Datum GetAttributeByNum(HeapTupleHeader tuple, AttrNumber attrno,
+				  bool *isNull);
+
+extern int	ExecTargetListLength(List *targetlist);
+extern int	ExecCleanTargetListLength(List *targetlist);
+
 /*
  * prototypes from functions in execIndexing.c
  */
diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h
index 0f821dc8f67b19f17d2bad42d41ddb27c8142e7d..0d3f52118b2b07791ce754c614828aa03c50df45 100644
--- a/src/include/executor/nodeSubplan.h
+++ b/src/include/executor/nodeSubplan.h
@@ -20,6 +20,10 @@ extern SubPlanState *ExecInitSubPlan(SubPlan *subplan, PlanState *parent);
 
 extern AlternativeSubPlanState *ExecInitAlternativeSubPlan(AlternativeSubPlan *asplan, PlanState *parent);
 
+extern Datum ExecSubPlan(SubPlanState *node, ExprContext *econtext, bool *isNull);
+
+extern Datum ExecAlternativeSubPlan(AlternativeSubPlanState *node, ExprContext *econtext, bool *isNull);
+
 extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent);
 
 extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext);
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index 0a155acee6290a1bcfc936b70e01beba35637be2..6128752ab19e8f690adbdd3460b312ed4d79563d 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -49,6 +49,9 @@ typedef Datum (*PGFunction) (FunctionCallInfo fcinfo);
  * arguments, rather than about the function itself.  But it's convenient
  * to store it here rather than in FunctionCallInfoData, where it might more
  * logically belong.
+ *
+ * fn_extra is available for use by the called function; all other fields
+ * should be treated as read-only after the struct is created.
  */
 typedef struct FmgrInfo
 {
@@ -65,6 +68,11 @@ typedef struct FmgrInfo
 
 /*
  * This struct is the data actually passed to an fmgr-called function.
+ *
+ * The called function is expected to set isnull, and possibly resultinfo or
+ * fields in whatever resultinfo points to.  It should not change any other
+ * fields.  (In particular, scribbling on the argument arrays is a bad idea,
+ * since some callers assume they can re-call with the same arguments.)
  */
 typedef struct FunctionCallInfoData
 {
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f856f6036f664f0b341106593a8d1eb77a6a3bf3..ff428951186007083f9bb35d4be6a57bdfb8e760 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -30,6 +30,72 @@
 #include "storage/condition_variable.h"
 
 
+/* ----------------
+ *		ExprState node
+ *
+ * ExprState is the top-level node for expression evaluation.
+ * It contains instructions (in ->steps) to evaluate the expression.
+ * ----------------
+ */
+struct ExprState;				/* forward references in this file */
+struct ExprContext;
+struct ExprEvalStep;			/* avoid including execExpr.h everywhere */
+
+typedef Datum (*ExprStateEvalFunc) (struct ExprState *expression,
+												struct ExprContext *econtext,
+												bool *isNull);
+
+/* Bits in ExprState->flags (see also execExpr.h for private flag bits): */
+/* expression is for use with ExecQual() */
+#define EEO_FLAG_IS_QUAL					(1 << 0)
+
+typedef struct ExprState
+{
+	Node		tag;
+
+	uint8		flags;			/* bitmask of EEO_FLAG_* bits, see above */
+
+	/*
+	 * Storage for result value of a scalar expression, or for individual
+	 * column results within expressions built by ExecBuildProjectionInfo().
+	 */
+	bool		resnull;
+	Datum		resvalue;
+
+	/*
+	 * If projecting a tuple result, this slot holds the result; else NULL.
+	 */
+	TupleTableSlot *resultslot;
+
+	/*
+	 * Instructions to compute expression's return value.
+	 */
+	struct ExprEvalStep *steps;
+
+	/*
+	 * Function that actually evaluates the expression.  This can be set to
+	 * different values depending on the complexity of the expression.
+	 */
+	ExprStateEvalFunc evalfunc;
+
+	/* original expression tree, for debugging only */
+	Expr	   *expr;
+
+	/*
+	 * XXX: following only needed during "compilation", could be thrown away.
+	 */
+
+	int			steps_len;		/* number of steps currently */
+	int			steps_alloc;	/* allocated length of steps array */
+
+	Datum	   *innermost_caseval;
+	bool	   *innermost_casenull;
+
+	Datum	   *innermost_domainval;
+	bool	   *innermost_domainnull;
+} ExprState;
+
+
 /* ----------------
  *	  IndexInfo information
  *
@@ -69,7 +135,7 @@ typedef struct IndexInfo
 	List	   *ii_Expressions; /* list of Expr */
 	List	   *ii_ExpressionsState;	/* list of ExprState */
 	List	   *ii_Predicate;	/* list of Expr */
-	List	   *ii_PredicateState;		/* list of ExprState */
+	ExprState  *ii_PredicateState;
 	Oid		   *ii_ExclusionOps;	/* array with one entry per column */
 	Oid		   *ii_ExclusionProcs;		/* array with one entry per column */
 	uint16	   *ii_ExclusionStrats;		/* array with one entry per column */
@@ -214,51 +280,21 @@ typedef struct ReturnSetInfo
  *		that is, form new tuples by evaluation of targetlist expressions.
  *		Nodes which need to do projections create one of these.
  *
+ *		The target tuple slot is kept in ProjectionInfo->pi_state.resultslot.
  *		ExecProject() evaluates the tlist, forms a tuple, and stores it
  *		in the given slot.  Note that the result will be a "virtual" tuple
  *		unless ExecMaterializeSlot() is then called to force it to be
  *		converted to a physical tuple.  The slot must have a tupledesc
  *		that matches the output of the tlist!
- *
- *		The planner very often produces tlists that consist entirely of
- *		simple Var references (lower levels of a plan tree almost always
- *		look like that).  And top-level tlists are often mostly Vars too.
- *		We therefore optimize execution of simple-Var tlist entries.
- *		The pi_targetlist list actually contains only the tlist entries that
- *		aren't simple Vars, while those that are Vars are processed using the
- *		varSlotOffsets/varNumbers/varOutputCols arrays.
- *
- *		The lastXXXVar fields are used to optimize fetching of fields from
- *		input tuples: they let us do a slot_getsomeattrs() call to ensure
- *		that all needed attributes are extracted in one pass.
- *
- *		targetlist		target list for projection (non-Var expressions only)
- *		exprContext		expression context in which to evaluate targetlist
- *		slot			slot to place projection result in
- *		directMap		true if varOutputCols[] is an identity map
- *		numSimpleVars	number of simple Vars found in original tlist
- *		varSlotOffsets	array indicating which slot each simple Var is from
- *		varNumbers		array containing input attr numbers of simple Vars
- *		varOutputCols	array containing output attr numbers of simple Vars
- *		lastInnerVar	highest attnum from inner tuple slot (0 if none)
- *		lastOuterVar	highest attnum from outer tuple slot (0 if none)
- *		lastScanVar		highest attnum from scan tuple slot (0 if none)
  * ----------------
  */
 typedef struct ProjectionInfo
 {
 	NodeTag		type;
-	List	   *pi_targetlist;
+	/* instructions to evaluate projection */
+	ExprState	pi_state;
+	/* expression context in which to evaluate expression */
 	ExprContext *pi_exprContext;
-	TupleTableSlot *pi_slot;
-	bool		pi_directMap;
-	int			pi_numSimpleVars;
-	int		   *pi_varSlotOffsets;
-	int		   *pi_varNumbers;
-	int		   *pi_varOutputCols;
-	int			pi_lastInnerVar;
-	int			pi_lastOuterVar;
-	int			pi_lastScanVar;
 } ProjectionInfo;
 
 /* ----------------
@@ -340,20 +376,20 @@ typedef struct ResultRelInfo
 	IndexInfo **ri_IndexRelationInfo;
 	TriggerDesc *ri_TrigDesc;
 	FmgrInfo   *ri_TrigFunctions;
-	List	  **ri_TrigWhenExprs;
+	ExprState **ri_TrigWhenExprs;
 	Instrumentation *ri_TrigInstrument;
 	struct FdwRoutine *ri_FdwRoutine;
 	void	   *ri_FdwState;
 	bool		ri_usesFdwDirectModify;
 	List	   *ri_WithCheckOptions;
 	List	   *ri_WithCheckOptionExprs;
-	List	  **ri_ConstraintExprs;
+	ExprState **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
-	List	   *ri_onConflictSetWhere;
+	ExprState  *ri_onConflictSetWhere;
 	List	   *ri_PartitionCheck;
-	List	   *ri_PartitionCheckExpr;
+	ExprState  *ri_PartitionCheckExpr;
 	Relation	ri_PartitionRoot;
 } ResultRelInfo;
 
@@ -564,139 +600,63 @@ typedef tuplehash_iterator TupleHashIterator;
 
 
 /* ----------------------------------------------------------------
- *				 Expression State Trees
- *
- * Each executable expression tree has a parallel ExprState tree.
- *
- * Unlike PlanState, there is not an exact one-for-one correspondence between
- * ExprState node types and Expr node types.  Many Expr node types have no
- * need for node-type-specific run-time state, and so they can use plain
- * ExprState or GenericExprState as their associated ExprState node type.
+ *				 Expression State Nodes
+ *
+ * Formerly, there was a separate executor expression state node corresponding
+ * to each node in a planned expression tree.  That's no longer the case; for
+ * common expression node types, all the execution info is embedded into
+ * step(s) in a single ExprState node.  But we still have a few executor state
+ * node types for selected expression node types, mostly those in which info
+ * has to be shared with other parts of the execution state tree.
  * ----------------------------------------------------------------
  */
 
-/* ----------------
- *		ExprState node
- *
- * ExprState is the common superclass for all ExprState-type nodes.
- *
- * It can also be instantiated directly for leaf Expr nodes that need no
- * local run-time state (such as Var, Const, or Param).
- *
- * To save on dispatch overhead, each ExprState node contains a function
- * pointer to the routine to execute to evaluate the node.
- * ----------------
- */
-
-typedef struct ExprState ExprState;
-
-typedef Datum (*ExprStateEvalFunc) (ExprState *expression,
-												ExprContext *econtext,
-												bool *isNull);
-
-struct ExprState
-{
-	NodeTag		type;
-	Expr	   *expr;			/* associated Expr node */
-	ExprStateEvalFunc evalfunc; /* routine to run to execute node */
-};
-
-/* ----------------
- *		GenericExprState node
- *
- * This is used for Expr node types that need no local run-time state,
- * but have one child Expr node.
- * ----------------
- */
-typedef struct GenericExprState
-{
-	ExprState	xprstate;
-	ExprState  *arg;			/* state of my child node */
-} GenericExprState;
-
-/* ----------------
- *		WholeRowVarExprState node
- * ----------------
- */
-typedef struct WholeRowVarExprState
-{
-	ExprState	xprstate;
-	struct PlanState *parent;	/* parent PlanState, or NULL if none */
-	TupleDesc	wrv_tupdesc;	/* descriptor for resulting tuples */
-	JunkFilter *wrv_junkFilter; /* JunkFilter to remove resjunk cols */
-} WholeRowVarExprState;
-
 /* ----------------
  *		AggrefExprState node
  * ----------------
  */
 typedef struct AggrefExprState
 {
-	ExprState	xprstate;
+	NodeTag		type;
+	Aggref	   *aggref;			/* expression plan node */
 	int			aggno;			/* ID number for agg within its plan node */
 } AggrefExprState;
 
-/* ----------------
- *		GroupingFuncExprState node
- *
- * The list of column numbers refers to the input tuples of the Agg node to
- * which the GroupingFunc belongs, and may contain 0 for references to columns
- * that are only present in grouping sets processed by different Agg nodes (and
- * which are therefore always considered "grouping" here).
- * ----------------
- */
-typedef struct GroupingFuncExprState
-{
-	ExprState	xprstate;
-	struct AggState *aggstate;
-	List	   *clauses;		/* integer list of column numbers */
-} GroupingFuncExprState;
-
 /* ----------------
  *		WindowFuncExprState node
  * ----------------
  */
 typedef struct WindowFuncExprState
 {
-	ExprState	xprstate;
-	List	   *args;			/* states of argument expressions */
+	NodeTag		type;
+	WindowFunc *wfunc;			/* expression plan node */
+	List	   *args;			/* ExprStates for argument expressions */
 	ExprState  *aggfilter;		/* FILTER expression */
 	int			wfuncno;		/* ID number for wfunc within its plan node */
 } WindowFuncExprState;
 
-/* ----------------
- *		ArrayRefExprState node
- *
- * Note: array types can be fixed-length (typlen > 0), but only when the
- * element type is itself fixed-length.  Otherwise they are varlena structures
- * and have typlen = -1.  In any case, an array type is never pass-by-value.
- * ----------------
- */
-typedef struct ArrayRefExprState
-{
-	ExprState	xprstate;
-	List	   *refupperindexpr;	/* states for child nodes */
-	List	   *reflowerindexpr;
-	ExprState  *refexpr;
-	ExprState  *refassgnexpr;
-	int16		refattrlength;	/* typlen of array type */
-	int16		refelemlength;	/* typlen of the array element type */
-	bool		refelembyval;	/* is the element type pass-by-value? */
-	char		refelemalign;	/* typalign of the element type */
-} ArrayRefExprState;
 
 /* ----------------
- *		FuncExprState node
+ *		SetExprState node
  *
- * Although named for FuncExpr, this is also used for OpExpr, DistinctExpr,
- * and NullIf nodes; be careful to check what xprstate.expr is actually
- * pointing at!
+ * State for evaluating a potentially set-returning expression (like FuncExpr
+ * or OpExpr).  In some cases, like some of the expressions in ROWS FROM(...)
+ * the expression might not be a SRF, but nonetheless it uses the same
+ * machinery as SRFs; it will be treated as a SRF returning a single row.
  * ----------------
  */
-typedef struct FuncExprState
+typedef struct SetExprState
 {
-	ExprState	xprstate;
-	List	   *args;			/* states of argument expressions */
+	NodeTag		type;
+	Expr	   *expr;			/* expression plan node */
+	List	   *args;			/* ExprStates for argument expressions */
+
+	/*
+	 * In ROWS FROM, functions can be inlined, removing the FuncExpr normally
+	 * inside.  In such a case this is the compiled expression (which cannot
+	 * return a set), which'll be evaluated using regular ExecEvalExpr().
+	 */
+	ExprState  *elidedFuncState;
 
 	/*
 	 * Function manager's lookup info for the target function.  If func.fn_oid
@@ -738,7 +698,7 @@ typedef struct FuncExprState
 
 	/*
 	 * Flag to remember whether we have registered a shutdown callback for
-	 * this FuncExprState.  We do so only if funcResultStore or setArgsValid
+	 * this SetExprState.  We do so only if funcResultStore or setArgsValid
 	 * has been set at least once (since all the callback is for is to release
 	 * the tuplestore or clear setArgsValid).
 	 */
@@ -750,33 +710,7 @@ typedef struct FuncExprState
 	 * argument values between calls, when setArgsValid is true.
 	 */
 	FunctionCallInfoData fcinfo_data;
-} FuncExprState;
-
-/* ----------------
- *		ScalarArrayOpExprState node
- *
- * This is a FuncExprState plus some additional data.
- * ----------------
- */
-typedef struct ScalarArrayOpExprState
-{
-	FuncExprState fxprstate;
-	/* Cached info about array element type */
-	Oid			element_type;
-	int16		typlen;
-	bool		typbyval;
-	char		typalign;
-} ScalarArrayOpExprState;
-
-/* ----------------
- *		BoolExprState node
- * ----------------
- */
-typedef struct BoolExprState
-{
-	ExprState	xprstate;
-	List	   *args;			/* states of argument expression(s) */
-} BoolExprState;
+} SetExprState;
 
 /* ----------------
  *		SubPlanState node
@@ -784,7 +718,8 @@ typedef struct BoolExprState
  */
 typedef struct SubPlanState
 {
-	ExprState	xprstate;
+	NodeTag		type;
+	SubPlan    *subplan;		/* expression plan node */
 	struct PlanState *planstate;	/* subselect plan's state tree */
 	struct PlanState *parent;	/* parent plan node's state tree */
 	ExprState  *testexpr;		/* state of combining expression */
@@ -814,203 +749,18 @@ typedef struct SubPlanState
  */
 typedef struct AlternativeSubPlanState
 {
-	ExprState	xprstate;
-	List	   *subplans;		/* states of alternative subplans */
+	NodeTag		type;
+	AlternativeSubPlan *subplan;	/* expression plan node */
+	List	   *subplans;		/* SubPlanStates of alternative subplans */
 	int			active;			/* list index of the one we're using */
 } AlternativeSubPlanState;
 
-/* ----------------
- *		FieldSelectState node
- * ----------------
- */
-typedef struct FieldSelectState
-{
-	ExprState	xprstate;
-	ExprState  *arg;			/* input expression */
-	TupleDesc	argdesc;		/* tupdesc for most recent input */
-} FieldSelectState;
-
-/* ----------------
- *		FieldStoreState node
- * ----------------
- */
-typedef struct FieldStoreState
-{
-	ExprState	xprstate;
-	ExprState  *arg;			/* input tuple value */
-	List	   *newvals;		/* new value(s) for field(s) */
-	TupleDesc	argdesc;		/* tupdesc for most recent input */
-} FieldStoreState;
-
-/* ----------------
- *		CoerceViaIOState node
- * ----------------
- */
-typedef struct CoerceViaIOState
-{
-	ExprState	xprstate;
-	ExprState  *arg;			/* input expression */
-	FmgrInfo	outfunc;		/* lookup info for source output function */
-	FmgrInfo	infunc;			/* lookup info for result input function */
-	Oid			intypioparam;	/* argument needed for input function */
-} CoerceViaIOState;
-
-/* ----------------
- *		ArrayCoerceExprState node
- * ----------------
- */
-typedef struct ArrayCoerceExprState
-{
-	ExprState	xprstate;
-	ExprState  *arg;			/* input array value */
-	Oid			resultelemtype; /* element type of result array */
-	FmgrInfo	elemfunc;		/* lookup info for element coercion function */
-	/* use struct pointer to avoid including array.h here */
-	struct ArrayMapState *amstate;		/* workspace for array_map */
-} ArrayCoerceExprState;
-
-/* ----------------
- *		ConvertRowtypeExprState node
- * ----------------
- */
-typedef struct ConvertRowtypeExprState
-{
-	ExprState	xprstate;
-	ExprState  *arg;			/* input tuple value */
-	TupleDesc	indesc;			/* tupdesc for source rowtype */
-	TupleDesc	outdesc;		/* tupdesc for result rowtype */
-	/* use "struct" so we needn't include tupconvert.h here */
-	struct TupleConversionMap *map;
-	bool		initialized;
-} ConvertRowtypeExprState;
-
-/* ----------------
- *		CaseExprState node
- * ----------------
- */
-typedef struct CaseExprState
-{
-	ExprState	xprstate;
-	ExprState  *arg;			/* implicit equality comparison argument */
-	List	   *args;			/* the arguments (list of WHEN clauses) */
-	ExprState  *defresult;		/* the default result (ELSE clause) */
-	int16		argtyplen;		/* if arg is provided, its typlen */
-} CaseExprState;
-
-/* ----------------
- *		CaseWhenState node
- * ----------------
- */
-typedef struct CaseWhenState
-{
-	ExprState	xprstate;
-	ExprState  *expr;			/* condition expression */
-	ExprState  *result;			/* substitution result */
-} CaseWhenState;
-
-/* ----------------
- *		ArrayExprState node
- *
- * Note: ARRAY[] expressions always produce varlena arrays, never fixed-length
- * arrays.
- * ----------------
- */
-typedef struct ArrayExprState
-{
-	ExprState	xprstate;
-	List	   *elements;		/* states for child nodes */
-	int16		elemlength;		/* typlen of the array element type */
-	bool		elembyval;		/* is the element type pass-by-value? */
-	char		elemalign;		/* typalign of the element type */
-} ArrayExprState;
-
-/* ----------------
- *		RowExprState node
- * ----------------
- */
-typedef struct RowExprState
-{
-	ExprState	xprstate;
-	List	   *args;			/* the arguments */
-	TupleDesc	tupdesc;		/* descriptor for result tuples */
-} RowExprState;
-
-/* ----------------
- *		RowCompareExprState node
- * ----------------
- */
-typedef struct RowCompareExprState
-{
-	ExprState	xprstate;
-	List	   *largs;			/* the left-hand input arguments */
-	List	   *rargs;			/* the right-hand input arguments */
-	FmgrInfo   *funcs;			/* array of comparison function info */
-	Oid		   *collations;		/* array of collations to use */
-} RowCompareExprState;
-
-/* ----------------
- *		CoalesceExprState node
- * ----------------
- */
-typedef struct CoalesceExprState
-{
-	ExprState	xprstate;
-	List	   *args;			/* the arguments */
-} CoalesceExprState;
-
-/* ----------------
- *		MinMaxExprState node
- * ----------------
- */
-typedef struct MinMaxExprState
-{
-	ExprState	xprstate;
-	List	   *args;			/* the arguments */
-	FmgrInfo	cfunc;			/* lookup info for comparison func */
-} MinMaxExprState;
-
-/* ----------------
- *		XmlExprState node
- * ----------------
- */
-typedef struct XmlExprState
-{
-	ExprState	xprstate;
-	List	   *named_args;		/* ExprStates for named arguments */
-	List	   *args;			/* ExprStates for other arguments */
-} XmlExprState;
-
-/* ----------------
- *		NullTestState node
- * ----------------
- */
-typedef struct NullTestState
-{
-	ExprState	xprstate;
-	ExprState  *arg;			/* input expression */
-	/* used only if input is of composite type: */
-	TupleDesc	argdesc;		/* tupdesc for most recent input */
-} NullTestState;
-
-/* ----------------
- *		CoerceToDomainState node
- * ----------------
- */
-typedef struct CoerceToDomainState
-{
-	ExprState	xprstate;
-	ExprState  *arg;			/* input expression */
-	/* Cached set of constraints that need to be checked */
-	/* use struct pointer to avoid including typcache.h here */
-	struct DomainConstraintRef *constraint_ref;
-} CoerceToDomainState;
-
 /*
  * DomainConstraintState - one item to check during CoerceToDomain
  *
- * Note: this is just a Node, and not an ExprState, because it has no
- * corresponding Expr to link to.  Nonetheless it is part of an ExprState
- * tree, so we give it a name following the xxxState convention.
+ * Note: we consider this to be part of an ExprState tree, so we give it
+ * a name following the xxxState convention.  But there's no directly
+ * associated plan-tree node.
  */
 typedef enum DomainConstraintType
 {
@@ -1023,7 +773,8 @@ typedef struct DomainConstraintState
 	NodeTag		type;
 	DomainConstraintType constrainttype;		/* constraint type */
 	char	   *name;			/* name of constraint (for error msgs) */
-	ExprState  *check_expr;		/* for CHECK, a boolean expression */
+	Expr	   *check_expr;		/* for CHECK, a boolean expression */
+	ExprState  *check_exprstate;	/* check_expr's eval state, or NULL */
 } DomainConstraintState;
 
 
@@ -1060,8 +811,7 @@ typedef struct PlanState
 	 * state trees parallel links in the associated plan tree (except for the
 	 * subPlan list, which does not exist in the plan tree).
 	 */
-	List	   *targetlist;		/* target list to be computed at this node */
-	List	   *qual;			/* implicitly-ANDed qual conditions */
+	ExprState  *qual;			/* boolean qual condition */
 	struct PlanState *lefttree; /* input plan tree(s) */
 	struct PlanState *righttree;
 	List	   *initPlan;		/* Init SubPlanState nodes (un-correlated expr
@@ -1133,11 +883,15 @@ typedef struct ResultState
 
 /* ----------------
  *	 ProjectSetState information
+ *
+ * Note: at least one of the "elems" will be a SetExprState; the rest are
+ * regular ExprStates.
  * ----------------
  */
 typedef struct ProjectSetState
 {
 	PlanState	ps;				/* its first field is NodeTag */
+	Node	  **elems;			/* array of expression states */
 	ExprDoneCond *elemdone;		/* array of per-SRF is-done states */
 	int			nelems;			/* length of elemdone[] array */
 	bool		pending_srf_tuples;		/* still evaluating srfs in tlist? */
@@ -1372,7 +1126,7 @@ typedef struct
 typedef struct IndexScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
-	List	   *indexqualorig;
+	ExprState  *indexqualorig;
 	List	   *indexorderbyorig;
 	ScanKey		iss_ScanKeys;
 	int			iss_NumScanKeys;
@@ -1418,7 +1172,7 @@ typedef struct IndexScanState
 typedef struct IndexOnlyScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
-	List	   *indexqual;
+	ExprState  *indexqual;
 	ScanKey		ioss_ScanKeys;
 	int			ioss_NumScanKeys;
 	ScanKey		ioss_OrderByKeys;
@@ -1534,7 +1288,7 @@ typedef struct ParallelBitmapHeapState
 typedef struct BitmapHeapScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
-	List	   *bitmapqualorig;
+	ExprState  *bitmapqualorig;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator;
 	TBMIterateResult *tbmres;
@@ -1554,16 +1308,18 @@ typedef struct BitmapHeapScanState
 /* ----------------
  *	 TidScanState information
  *
+ *		tidexprs	   list of TidExpr structs (see nodeTidscan.c)
  *		isCurrentOf    scan has a CurrentOfExpr qual
  *		NumTids		   number of tids in this scan
  *		TidPtr		   index of currently fetched tid
  *		TidList		   evaluated item pointers (array of size NumTids)
+ *		htup		   currently-fetched tuple, if any
  * ----------------
  */
 typedef struct TidScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
-	List	   *tss_tidquals;	/* list of ExprState nodes */
+	List	   *tss_tidexprs;
 	bool		tss_isCurrentOf;
 	int			tss_NumTids;
 	int			tss_TidPtr;
@@ -1712,7 +1468,7 @@ typedef struct WorkTableScanState
 typedef struct ForeignScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
-	List	   *fdw_recheck_quals;		/* original quals not in ss.ps.qual */
+	ExprState  *fdw_recheck_quals;		/* original quals not in ss.ps.qual */
 	Size		pscan_len;		/* size of parallel coordination information */
 	/* use struct pointer to avoid including fdwapi.h here */
 	struct FdwRoutine *fdwroutine;
@@ -1759,7 +1515,7 @@ typedef struct JoinState
 {
 	PlanState	ps;
 	JoinType	jointype;
-	List	   *joinqual;		/* JOIN quals (in addition to ps.qual) */
+	ExprState  *joinqual;		/* JOIN quals (in addition to ps.qual) */
 } JoinState;
 
 /* ----------------
@@ -1857,7 +1613,7 @@ typedef struct HashJoinTableData *HashJoinTable;
 typedef struct HashJoinState
 {
 	JoinState	js;				/* its first field is NodeTag */
-	List	   *hashclauses;	/* list of ExprState nodes */
+	ExprState  *hashclauses;
 	List	   *hj_OuterHashKeys;		/* list of ExprState nodes */
 	List	   *hj_InnerHashKeys;		/* list of ExprState nodes */
 	List	   *hj_HashOperators;		/* list of operator OIDs */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index fc883a6f3ec0744da50bebfb2eade049aa53a446..c83216943c1ab65554fd55106bf39a4c26e4e9fd 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -192,36 +192,18 @@ typedef enum NodeTag
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
 	 *
-	 * These correspond (not always one-for-one) to primitive nodes derived
-	 * from Expr.
+	 * ExprState represents the evaluation state for a whole expression tree.
+	 * Most Expr-based plan nodes do not have a corresponding expression state
+	 * node, they're fully handled within execExpr* - but sometimes the state
+	 * needs to be shared with other parts of the executor, as for example
+	 * with AggrefExprState, which nodeAgg.c has to modify.
 	 */
 	T_ExprState,
-	T_GenericExprState,
-	T_WholeRowVarExprState,
 	T_AggrefExprState,
-	T_GroupingFuncExprState,
 	T_WindowFuncExprState,
-	T_ArrayRefExprState,
-	T_FuncExprState,
-	T_ScalarArrayOpExprState,
-	T_BoolExprState,
+	T_SetExprState,
 	T_SubPlanState,
 	T_AlternativeSubPlanState,
-	T_FieldSelectState,
-	T_FieldStoreState,
-	T_CoerceViaIOState,
-	T_ArrayCoerceExprState,
-	T_ConvertRowtypeExprState,
-	T_CaseExprState,
-	T_CaseWhenState,
-	T_ArrayExprState,
-	T_RowExprState,
-	T_RowCompareExprState,
-	T_CoalesceExprState,
-	T_MinMaxExprState,
-	T_XmlExprState,
-	T_NullTestState,
-	T_CoerceToDomainState,
 	T_DomainConstraintState,
 
 	/*
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 90a1f6347a4cad2016def153ca869164587cac49..1bf94e2548da0e6ac9b906dc48ed983534686b66 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -132,6 +132,7 @@ typedef struct DomainConstraintRef
 	List	   *constraints;	/* list of DomainConstraintState nodes */
 	MemoryContext refctx;		/* context holding DomainConstraintRef */
 	TypeCacheEntry *tcache;		/* typcache entry for domain type */
+	bool		need_exprstate; /* does caller need check_exprstate? */
 
 	/* Management data --- treat these fields as private to typcache.c */
 	DomainConstraintCache *dcc; /* current constraints, or NULL if none */
@@ -142,7 +143,7 @@ typedef struct DomainConstraintRef
 extern TypeCacheEntry *lookup_type_cache(Oid type_id, int flags);
 
 extern void InitDomainConstraintRef(Oid type_id, DomainConstraintRef *ref,
-						MemoryContext refctx);
+						MemoryContext refctx, bool need_exprstate);
 
 extern void UpdateDomainConstraintRef(DomainConstraintRef *ref);
 
diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h
index e570b71c04f9da23fb96e9dbf8b016155fccb9d8..195b9b3a97a75dad01dce9762cd75477979174c0 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -61,7 +61,9 @@ extern void xml_ereport(PgXmlErrorContext *errcxt, int level, int sqlcode,
 			const char *msg);
 
 extern xmltype *xmlconcat(List *args);
-extern xmltype *xmlelement(XmlExprState *xmlExpr, ExprContext *econtext);
+extern xmltype *xmlelement(XmlExpr *xexpr,
+		   Datum *named_argvalue, bool *named_argnull,
+		   Datum *argvalue, bool *argnull);
 extern xmltype *xmlparse(text *data, XmlOptionType xmloption, bool preserve_whitespace);
 extern xmltype *xmlpi(char *target, text *arg, bool arg_is_null, bool *result_is_null);
 extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 664fc425143c886ec336deba4e5ae59751856375..84af81553751af794c7f977da774a20e34970444 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -145,7 +145,7 @@ typedef struct					/* cast_hash table entry */
 {
 	plpgsql_CastHashKey key;	/* hash key --- MUST BE FIRST */
 	Expr	   *cast_expr;		/* cast expression, or NULL if no-op cast */
-	/* The ExprState tree is valid only when cast_lxid matches current LXID */
+	/* ExprState is valid only when cast_lxid matches current LXID */
 	ExprState  *cast_exprstate; /* expression's eval tree */
 	bool		cast_in_use;	/* true while we're executing eval tree */
 	LocalTransactionId cast_lxid;
@@ -4710,7 +4710,8 @@ exec_assign_value(PLpgSQL_execstate *estate,
 
 				/*
 				 * Evaluate the subscripts, switch into left-to-right order.
-				 * Like ExecEvalArrayRef(), complain if any subscript is null.
+				 * Like the expression built by ExecInitArrayRef(), complain
+				 * if any subscript is null.
 				 */
 				for (i = 0; i < nsubscripts; i++)
 				{
diff --git a/src/test/regress/expected/case.out b/src/test/regress/expected/case.out
index 4cc4851475dd095abf9eda44641f7e1415a67dd6..36bf15c4acb7aa412da688baa9ea0a36b255377c 100644
--- a/src/test/regress/expected/case.out
+++ b/src/test/regress/expected/case.out
@@ -308,7 +308,7 @@ SELECT * FROM CASE_TBL;
 -- Nested CASE expressions
 --
 -- This test exercises a bug caused by aliasing econtext->caseValue_isNull
--- with the isNull argument of the inner CASE's ExecEvalCase() call.  After
+-- with the isNull argument of the inner CASE's CaseExpr evaluation.  After
 -- evaluating the vol(null) expression in the inner CASE's second WHEN-clause,
 -- the isNull flag for the case test value incorrectly became true, causing
 -- the third WHEN-clause not to match.  The volatile function calls are needed
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 720675032a566305264aeb6da0f11a9c27b53e73..f3499807596aee12babd30c2aa3907a0076929f1 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -586,14 +586,8 @@ ERROR:  must be owner of function testfunc1
 DROP FUNCTION testfunc1(int); -- ok
 -- restore to sanity
 GRANT ALL PRIVILEGES ON LANGUAGE sql TO PUBLIC;
--- verify privilege checks on coercions
+-- verify privilege checks on array-element coercions
 BEGIN;
-SELECT NULL::int4[]::int8[];
- int8 
-------
- 
-(1 row)
-
 SELECT '{1}'::int4[]::int8[];
  int8 
 ------
@@ -601,12 +595,6 @@ SELECT '{1}'::int4[]::int8[];
 (1 row)
 
 REVOKE ALL ON FUNCTION int8(integer) FROM PUBLIC;
-SELECT NULL::int4[]::int8[];
- int8 
-------
- 
-(1 row)
-
 SELECT '{1}'::int4[]::int8[]; --superuser, suceed
  int8 
 ------
@@ -614,12 +602,6 @@ SELECT '{1}'::int4[]::int8[]; --superuser, suceed
 (1 row)
 
 SET SESSION AUTHORIZATION regress_user4;
-SELECT NULL::int4[]::int8[];  --other user, no elements to convert
- int8 
-------
- 
-(1 row)
-
 SELECT '{1}'::int4[]::int8[]; --other user, fail
 ERROR:  permission denied for function int8
 ROLLBACK;
diff --git a/src/test/regress/sql/case.sql b/src/test/regress/sql/case.sql
index 59268f8cdfa6a81f476dad1fd2a9765b863cb882..66b6e98fb1869d7602cd3a157615760e2ace5145 100644
--- a/src/test/regress/sql/case.sql
+++ b/src/test/regress/sql/case.sql
@@ -166,7 +166,7 @@ SELECT * FROM CASE_TBL;
 --
 
 -- This test exercises a bug caused by aliasing econtext->caseValue_isNull
--- with the isNull argument of the inner CASE's ExecEvalCase() call.  After
+-- with the isNull argument of the inner CASE's CaseExpr evaluation.  After
 -- evaluating the vol(null) expression in the inner CASE's second WHEN-clause,
 -- the isNull flag for the case test value incorrectly became true, causing
 -- the third WHEN-clause not to match.  The volatile function calls are needed
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index e3275febea72f7b25f25da1c2c335db40fad608a..166e903012b2a71a367e0ee1c65f040840e95188 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -398,15 +398,12 @@ DROP FUNCTION testfunc1(int); -- ok
 -- restore to sanity
 GRANT ALL PRIVILEGES ON LANGUAGE sql TO PUBLIC;
 
--- verify privilege checks on coercions
+-- verify privilege checks on array-element coercions
 BEGIN;
-SELECT NULL::int4[]::int8[];
 SELECT '{1}'::int4[]::int8[];
 REVOKE ALL ON FUNCTION int8(integer) FROM PUBLIC;
-SELECT NULL::int4[]::int8[];
 SELECT '{1}'::int4[]::int8[]; --superuser, suceed
 SET SESSION AUTHORIZATION regress_user4;
-SELECT NULL::int4[]::int8[];  --other user, no elements to convert
 SELECT '{1}'::int4[]::int8[]; --other user, fail
 ROLLBACK;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3487f7becb72d017785dfb209a1330e5fc2be302..15c72f57651aa326548691b9e9e43955d7ad6b6d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -120,7 +120,7 @@ ArrayMapState
 ArrayMetaState
 ArrayParseState
 ArrayRef
-ArrayRefExprState
+ArrayRefState
 ArrayRemapInfo
 ArrayType
 AsyncQueueControl
@@ -578,6 +578,8 @@ ExprContext_CB
 ExprDoneCond
 ExprState
 ExprStateEvalFunc
+ExprEvalOp
+ExprEvalStep
 ExtensibleNode
 ExtensibleNodeEntry
 ExtensibleNodeMethods
@@ -1073,6 +1075,7 @@ LWLockPadded
 LWLockTranche
 LabelProvider
 LargeObjectDesc
+LastAttnumInfo
 Latch
 LerpFunc
 LexDescr
@@ -1908,6 +1911,7 @@ Session
 SetConstraintState
 SetConstraintStateData
 SetConstraintTriggerData
+SetExprState
 SetFunctionReturnMode
 SetOp
 SetOpCmd