From 2bbeb5fcaa04a57666830a125e8f08c884949b33 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Mon, 5 Dec 2016 18:10:04 -0800
Subject: [PATCH 4/4] WIP: Faster expression evaluation and targetlist
 projection.

This reimplements expression evaluation as a an opcode based
interpreter, instead of doing tree-traversal. This both saves on
once-per-execution overhead of expression initalization, but more
importantly makes execution faster.  It also has the distinct
advantage of making JITing of expression evaluation easier.
---
 contrib/postgres_fdw/postgres_fdw.c       |    2 +-
 src/backend/bootstrap/bootstrap.c         |    2 +-
 src/backend/catalog/index.c               |   43 +-
 src/backend/catalog/partition.c           |    4 +-
 src/backend/catalog/toasting.c            |    2 +-
 src/backend/commands/analyze.c            |   10 +-
 src/backend/commands/copy.c               |    2 +-
 src/backend/commands/explain.c            |    2 +-
 src/backend/commands/indexcmds.c          |    4 +-
 src/backend/commands/prepare.c            |    4 +-
 src/backend/commands/tablecmds.c          |   22 +-
 src/backend/commands/trigger.c            |    8 +-
 src/backend/executor/Makefile             |    6 +-
 src/backend/executor/execExpr.c           | 2231 ++++++++++++++
 src/backend/executor/execIndexing.c       |   20 +-
 src/backend/executor/execInterpExpr.c     | 2840 ++++++++++++++++++
 src/backend/executor/execMain.c           |   32 +-
 src/backend/executor/execQual.c           | 4539 +----------------------------
 src/backend/executor/execScan.c           |    4 +-
 src/backend/executor/execUtils.c          |  133 +-
 src/backend/executor/nodeAgg.c            |   20 +-
 src/backend/executor/nodeBitmapHeapscan.c |   17 +-
 src/backend/executor/nodeCtescan.c        |    8 +-
 src/backend/executor/nodeCustom.c         |    7 +-
 src/backend/executor/nodeForeignscan.c    |   15 +-
 src/backend/executor/nodeFunctionscan.c   |   18 +-
 src/backend/executor/nodeGather.c         |    8 +-
 src/backend/executor/nodeGroup.c          |   12 +-
 src/backend/executor/nodeHash.c           |   12 +-
 src/backend/executor/nodeHashjoin.c       |   43 +-
 src/backend/executor/nodeIndexonlyscan.c  |   15 +-
 src/backend/executor/nodeIndexscan.c      |   24 +-
 src/backend/executor/nodeMergejoin.c      |   33 +-
 src/backend/executor/nodeModifyTable.c    |   40 +-
 src/backend/executor/nodeNestloop.c       |   23 +-
 src/backend/executor/nodeProjectSet.c     |   52 +-
 src/backend/executor/nodeResult.c         |   16 +-
 src/backend/executor/nodeSamplescan.c     |   15 +-
 src/backend/executor/nodeSeqscan.c        |    8 +-
 src/backend/executor/nodeSubplan.c        |   91 +-
 src/backend/executor/nodeSubqueryscan.c   |    8 +-
 src/backend/executor/nodeTidscan.c        |   37 +-
 src/backend/executor/nodeValuesscan.c     |   10 +-
 src/backend/executor/nodeWindowAgg.c      |    8 +-
 src/backend/executor/nodeWorktablescan.c  |    8 +-
 src/backend/optimizer/util/clauses.c      |   12 +-
 src/backend/utils/adt/domains.c           |   18 +-
 src/backend/utils/adt/ruleutils.c         |    4 +-
 src/backend/utils/adt/xml.c               |   37 +-
 src/backend/utils/cache/typcache.c        |   10 +-
 src/include/executor/execExpr.h           |  454 +++
 src/include/executor/execdebug.h          |   21 -
 src/include/executor/executor.h           |   95 +-
 src/include/executor/nodeSubplan.h        |    3 +
 src/include/nodes/execnodes.h             |  562 +---
 src/include/nodes/nodes.h                 |   29 +-
 src/include/utils/xml.h                   |    2 +-
 src/pl/plpgsql/src/pl_exec.c              |    5 +-
 src/test/regress/expected/case.out        |    2 +-
 src/test/regress/sql/case.sql             |    2 +-
 60 files changed, 6254 insertions(+), 5460 deletions(-)
 create mode 100644 src/backend/executor/execExpr.c
 create mode 100644 src/backend/executor/execInterpExpr.c
 create mode 100644 src/include/executor/execExpr.h

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 5d270b948a..0ba2d09e76 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -3413,7 +3413,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 6511c6064b..6cfce4f8dd 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 8d42a347ea..d6be0915a7 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 e01ef864f0..1e75777acb 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1618,8 +1618,8 @@ 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 0e4231668d..29756eb14e 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 a70c760341..0f15cd5369 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -703,7 +703,7 @@ compute_index_stats(Relation onerel, double totalrows,
 		TupleTableSlot *slot;
 		EState	   *estate;
 		ExprContext *econtext;
-		List	   *predicate;
+		ExprState  *predicate;
 		Datum	   *exprvals;
 		bool	   *exprnulls;
 		int			numindexrows,
@@ -729,9 +729,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));
@@ -754,9 +752,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/copy.c b/src/backend/commands/copy.c
index 3102ab18c5..7295853e15 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -3406,7 +3406,7 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext,
 		Assert(CurrentMemoryContext == econtext->ecxt_per_tuple_memory);
 
 		values[defmap[i]] = ExecEvalExpr(defexprs[i], econtext,
-										 &nulls[defmap[i]]);
+										  &nulls[defmap[i]]);
 	}
 
 	return true;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index c9e0a3e42d..655be96eb2 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -2831,7 +2831,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 72bb06c760..fd0aeb06b9 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;
@@ -556,7 +556,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 116ed67547..fce3657298 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 1ddb72d164..07d0bb7af0 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 */
 } 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 = ExecPrepareQual((List *) 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 (!ExecQual(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")));
@@ -7786,7 +7783,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	Datum		val;
 	char	   *conbin;
 	Expr	   *origexpr;
-	List	   *exprstate;
+	ExprState  *exprstate;
 	TupleDesc	tupdesc;
 	HeapScanDesc scan;
 	HeapTuple	tuple;
@@ -7817,8 +7814,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 = ExecPrepareQual(make_ands_implicit(origexpr), estate);
 
 	econtext = GetPerTupleExprContext(estate);
 	tupdesc = RelationGetDescr(rel);
@@ -7838,7 +7834,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	{
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
-		if (!ExecQual(exprstate, econtext, true))
+		if (!ExecQual(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 d80bff671c..a13cce05ca 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;
 
@@ -3089,7 +3089,7 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 			ChangeVarNodes(tgqual, PRS2_NEW_VARNO, OUTER_VAR, 0);
 			/* ExecQual 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 2a2b7eb9bd..7b16c4218e 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -12,9 +12,9 @@ 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 execInterpExpr.o execGrouping.o \
+       execIndexing.o execJunk.o execMain.o execParallel.o execProcnode.o \
+       execQual.o execReplication.o execScan.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/execExpr.c b/src/backend/executor/execExpr.c
new file mode 100644
index 0000000000..ae0ad0c006
--- /dev/null
+++ b/src/backend/executor/execExpr.c
@@ -0,0 +1,2231 @@
+/*-------------------------------------------------------------------------
+ *
+ * execExpr.c
+ *	  Support for evaluating expressions.
+ *
+ *	Expression evaluation now works by first converting expression trees
+ *	(which went through planning first) into an ExprState using ExecInitExpr()
+ *	et al. This converts the tree into a opcode based program (ExprEvalStep
+ *	representing individual instructions); allocated as an array of stesps.
+ *
+ *	The ExprEvalStep representation is designed to be usable for interpreting
+ *	the expression, as well as compiling into native code. Thus, if possible,
+ *	as much complexity as possible should be handed by ExecInitExpr() (and
+ *	helpers), instead of handled at execution time where both interpreted and
+ *	compiled versions would need to deal with the complexity. Additionally
+ *	checks for initialization at run time have a small but noticeable cost at
+ *	every execution.
+ *
+ *	The next step is preparing the ExprState for execution, using
+ *	ExecInstantiateExpr(). This is internally done by ExecInitExpr() and other
+ *	functions that prepare for expression evaluation.  ExecInstantiateExpr()
+ *	initializes the expression tree for the relevant method chosen to evaluate
+ *	the expression.
+ *
+ *	Note that a lot of the more complex expression evaluation steps, which are
+ *	less performance critical than some of the simpler and more common ones,
+ *	are implemented as separate functions outside the fast-path of interpreted
+ *	expression, like e.g. ExecEvalRow(), so that the implementation can be
+ *	shared between interpreted and compiled expression evaluation.  That means
+ *	that ExecInstantiateExpr() always has to initialize the expression for
+ *	evaluation by execInterpExpr.c.
+ *
+ * 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/htup_details.h"
+#include "access/nbtree.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_type.h"
+#include "executor/execdebug.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/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"
+
+
+/*
+ * Support for building execution state.
+ */
+static void ExecInstantiateExpr(ExprState *state);
+static void ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state, Datum *resv, bool *resnull);
+static void ExprEvalPushStep(ExprState *es, ExprEvalStep *s);
+static void ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid, Oid inputcollid,
+						  PlanState *parent, ExprState *state,  Datum *resv, bool *resnull);
+static void ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, PlanState *parent, ExprState *state, Datum *resv, bool *resnull);
+static void ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, PlanState *parent, ExprState *state, Datum *resv, bool *resnull);
+static void ExecInitExprSlots(ExprState *state, Node *node);
+
+/* support functions */
+static bool isAssignmentIndirectionExpr(Expr *expr);
+
+
+/*
+ * 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.
+ *
+ * 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.
+ *
+ * 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 = makeNode(ExprState);
+	ExprEvalStep scratch;
+
+	if (node == NULL)
+		return NULL;
+
+	state->expr = node;
+	ExecInitExprSlots(state, (Node *) node);
+
+	ExecInitExprRec(node, parent, state, &state->resvalue, &state->resnull);
+
+	scratch.resvalue = &state->resvalue;
+	scratch.resnull = &state->resnull;
+	scratch.opcode = EEO_DONE;
+	ExprEvalPushStep(state, &scratch);
+
+	ExecInstantiateExpr(state);
+
+	return state;
+}
+
+/*
+ * ExecInitQual: prepare a qual for execution
+ *
+ * Prepares for the evaluation of a conjunctive boolean expression (qual
+ * list) that 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, then the result of the
+ * conjunction is false.  This makes this routine 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 = makeNode(ExprState);
+	ExprEvalStep scratch;
+	ListCell *lc;
+	List *adjust_bailout = NIL;
+
+	/* short-circuit (here and in ExecQual) for empty restriction list */
+	if (qual == NULL)
+		return NULL;
+
+	Assert(IsA(qual, List));
+
+	/*
+	 * ExecQual() needs to return false for expression returning NULL. That
+	 * allows 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).
+	 */
+	state->expr = (Expr *) qual;
+	ExecInitExprSlots(state, (Node *) qual);
+
+	scratch.opcode = EEO_QUAL;
+	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 check whether it's false or NULL */
+		scratch.d.qualexpr.jumpdone = -1;
+		ExprEvalPushStep(state, &scratch);
+		adjust_bailout = lappend_int(adjust_bailout,
+									 state->steps_len - 1);
+	}
+
+	/* adjust early bail out jump target */
+	foreach (lc, adjust_bailout)
+	{
+		ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+		Assert(as->d.qualexpr.jumpdone == -1);
+		as->d.qualexpr.jumpdone = state->steps_len;
+	}
+
+	scratch.opcode = EEO_DONE;
+	ExprEvalPushStep(state, &scratch);
+
+	ExecInstantiateExpr(state);
+
+	return state;
+}
+
+/*
+ * ExecInitCheck: prepare a check constraint for execution
+ *
+ * Prepares for the evaluation of a conjunctive boolean expression (qual
+ * list) that 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, then the result of the
+ * conjunction is true, since SQL specifies that NULL constraint conditions
+ * are not failures.
+ */
+ExprState *
+ExecInitCheck(List *qual, PlanState *parent)
+{
+	Expr *andExpr;
+
+	if (qual == NULL)
+		return NULL;
+
+	Assert(IsA(qual, List));
+
+	/* just whip-up a boolean AND expression, that behaves just as needed */
+	andExpr = makeBoolExpr(AND_EXPR, qual, -1);
+
+	return ExecInitExpr(andExpr, parent);
+}
+
+/* ----------------
+ *		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.
+ *
+ * This is implemented by internally building an ExprState that performs the
+ * projection. That way faster implementations of expression evaluation, e.g
+ * compiled to native code, can evaluate the whole projection in one go.
+ * ----------------
+ */
+ProjectionInfo *
+ExecBuildProjectionInfo(List *targetList,
+						ExprContext *econtext,
+						TupleTableSlot *slot,
+						PlanState *parent,
+						TupleDesc inputDesc)
+{
+	ProjectionInfo *projInfo = makeNode(ProjectionInfo);
+	ExprEvalStep scratch;
+	ListCell *lc;
+	ExprState *state;
+
+	projInfo->pi_slot = slot;
+	projInfo->pi_exprContext = econtext;
+	projInfo->pi_state.tag.type = T_ExprState;
+	state = &projInfo->pi_state;
+	ExecInitExprSlots(state, (Node *) targetList);
+
+	foreach(lc, targetList)
+	{
+		TargetEntry *tle;
+		Var		   *variable = NULL;
+		AttrNumber	attnum;
+		bool		isSimpleVar = false;
+
+		Assert(IsA(lfirst(lc), TargetEntry));
+
+		tle = (TargetEntry *) lfirst(lc);
+
+		if (tle->expr != NULL &&
+			IsA(tle->expr, Var) &&
+			((Var *)tle->expr)->varattno > 0)
+		{
+			variable = (Var *) tle->expr;
+			attnum = variable->varattno;
+
+			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)
+		{
+			switch (variable->varno)
+			{
+			case INNER_VAR: /* get the tuple from the inner node */
+				scratch.opcode = EEO_ASSIGN_INNER_VAR;
+			break;
+
+			case OUTER_VAR: /* get the tuple from the outer node */
+				scratch.opcode = EEO_ASSIGN_OUTER_VAR;
+				break;
+
+				/* INDEX_VAR is handled by default case */
+			default:				/* get the tuple from the relation being
+									 * scanned */
+				scratch.opcode = EEO_ASSIGN_SCAN_VAR;
+			break;
+			}
+
+			scratch.d.assign_var.attnum = attnum - 1;
+			scratch.d.assign_var.resultnum = tle->resno - 1;
+			ExprEvalPushStep(state, &scratch);
+		}
+		else
+		{
+			/*
+			 * We can't directly point into the result slot for the contained
+			 * expression, as the result slot (and the exprstate for that
+			 * matter) can change below us. So we instead evaluate into a
+			 * temporary value and then move.
+			 */
+			ExecInitExprRec(tle->expr, parent, state, &state->resvalue, &state->resnull);
+			if (get_typlen(exprType((Node *) tle->expr)) == -1)
+				scratch.opcode = EEO_ASSIGN_TMP_UNEXPAND;
+			else
+				scratch.opcode = EEO_ASSIGN_TMP;
+			scratch.d.assign_tmp.resultnum = tle->resno - 1;
+			ExprEvalPushStep(state, &scratch);
+		}
+	}
+
+	scratch.resvalue = &state->resvalue;
+	scratch.resnull = &state->resnull;
+	scratch.opcode = EEO_DONE;
+	ExprEvalPushStep(state, &scratch);
+
+	ExecInstantiateExpr(state);
+
+	return projInfo;
+}
+
+/*
+ * 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;
+}
+
+/*
+ * 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 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 *
+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 qual for execution outside a normal Plan
+ * tree context.
+ *
+ * See ExecPrepareExpr() and ExecInitQual() 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 nodes, and return list of
+ * ExprStates.
+ *
+ * See ExecPrepareExpr() for details.
+ */
+List *
+ExecPrepareExprList(List *nodes, EState *estate)
+{
+	List	   *result = NIL;
+	ListCell   *lc;
+
+	foreach (lc, nodes)
+	{
+		Expr   *e = lfirst(lc);
+
+		result = lappend(result, ExecPrepareExpr(e, estate));
+	}
+
+	return result;
+}
+
+/*
+ * ExecProjectIntoSlot
+ *
+ * Projects a tuple based on projection info and stores it in the 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.
+ */
+void
+ExecProjectIntoSlot(ProjectionInfo *projInfo, TupleTableSlot *slot)
+{
+	bool isnull;
+	MemoryContext oldcontext;
+	ExprContext *econtext = projInfo->pi_exprContext;
+	ExprState *state;
+
+	state = &projInfo->pi_state;
+
+	/* make conditional? */
+	oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+	/*
+	 * Clear any former contents of the result slot.  This makes it safe for
+	 * us to use the slot's Datum/isnull arrays as workspace. (Also, we can
+	 * return the slot as-is if we decide no rows can be projected.)
+	 */
+	ExecClearTuple(slot);
+
+	state->resultslot = slot;
+	ExecEvalExpr(state, econtext, &isnull);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/*
+	 * Successfully formed a result row.  Mark the result slot as containing a
+	 * valid virtual tuple.
+	 */
+	ExecStoreVirtualTuple(slot);
+}
+
+/*
+ * ExecCheck - evaluate a check constraint prepared with ExecInitCheck
+ * (possibly via ExecPrepareCheck).
+ */
+bool
+ExecCheck(ExprState *state, ExprContext *econtext)
+{
+	bool isnull;
+	Datum ret;
+
+	ret = ExecEvalExprSwitchContext(state, econtext, &isnull);
+
+	if (isnull)
+		return true;
+	return DatumGetBool(ret);
+}
+
+/*
+ * Prepare an expression for execution.
+ */
+static void
+ExecInstantiateExpr(ExprState *state)
+{
+	ExecInstantiateInterpretedExpr(state);
+}
+
+/*
+ * Add evaluation of node to ExprState, possibly recursing into
+ * sub-expressions of node.
+ */
+static void
+ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state, Datum *resv, bool *resnull)
+{
+	ExprEvalStep scratch;
+
+	/*
+	 * Guard against stack overflow due to overly complex expressions. Because
+	 * expression evaluation is not recursive, but expression planning is,
+	 * we'll hit stack limits due to recursion either here, or when recursing
+	 * into a separate expression evaluation inside a function call.  This
+	 * lets us avoid repeatedly doing the quite expensive stack depth checks
+	 * during expression evaluation.
+	 */
+	check_stack_depth();
+
+	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;
+
+				/* varattno == InvalidAttrNumber means it's a whole-row Var */
+				if (variable->varattno == InvalidAttrNumber)
+				{
+					ExecInitWholeRowVar(&scratch, variable, parent, state, resv, resnull);
+
+					scratch.opcode = EEO_WHOLEROW;
+				}
+				else if (variable->varattno <= 0)
+				{
+					scratch.d.var.attnum = variable->varattno;
+					switch (variable->varno)
+					{
+					case INNER_VAR:
+						scratch.opcode = EEO_INNER_SYSVAR;
+						break;
+					case OUTER_VAR:
+						scratch.opcode = EEO_OUTER_SYSVAR;
+						break;
+					default:
+						scratch.opcode = EEO_SCAN_SYSVAR;
+						break;
+					}
+				}
+				else
+				{
+					switch (variable->varno)
+					{
+						case INNER_VAR:
+							scratch.opcode = EEO_INNER_VAR;
+							break;
+						case OUTER_VAR:
+							scratch.opcode = EEO_OUTER_VAR;
+							break;
+						default:
+							scratch.opcode = EEO_SCAN_VAR;
+							break;
+					}
+					scratch.d.var.attnum = variable->varattno - 1;
+				}
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_Const:
+			{
+				Const *con = (Const *) node;
+
+				scratch.opcode = EEO_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 = EEO_PARAM_EXEC;
+							scratch.d.param.paramid = param->paramid;
+							scratch.d.param.paramtype = InvalidOid;
+							break;
+						}
+					case PARAM_EXTERN:
+						{
+							scratch.opcode = EEO_PARAM_EXTERN;
+							scratch.d.param.paramid = param->paramid;
+							scratch.d.param.paramtype = param->paramtype;
+							break;
+						}
+					default:
+						elog(ERROR, "unrecognized paramkind: %d",
+							 (int) ((Param *) node)->paramkind);
+						break;
+				}
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_Aggref:
+			{
+				Aggref *aggref = (Aggref *) node;
+				AggrefExprState *astate = makeNode(AggrefExprState);
+
+				scratch.opcode = EEO_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 = NULL;
+
+				if (!parent || !IsA(parent, AggState) ||!IsA(parent->plan, Agg))
+					elog(ERROR, "parent of GROUPING is not Agg node");
+
+				scratch.opcode = EEO_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 intialize agg using old style expressions */
+					wfstate->args = NIL;
+					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 = EEO_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, resv, resnull);
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_OpExpr:
+			{
+				OpExpr   *op = (OpExpr *) node;
+				ExecInitFunc(&scratch, node, op->args, op->opfuncid, op->inputcollid,
+							 parent, state, resv, resnull);
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_DistinctExpr:
+			{
+				DistinctExpr   *op = (DistinctExpr *) node;
+
+				ExecInitFunc(&scratch, node, op->args, op->opfuncid, op->inputcollid,
+							 parent, state, resv, resnull);
+
+				/* Can't use normal function call, override opcode for 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
+				 */
+				scratch.opcode = EEO_DISTINCT;
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_NullIfExpr:
+			{
+				NullIfExpr   *op = (NullIfExpr *) node;
+
+				ExecInitFunc(&scratch, node, op->args, op->opfuncid, op->inputcollid,
+							 parent, state, resv, resnull);
+
+				/* Can't use normal function call, override opcode for NULL() */
+				/*
+				 * 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
+				 */
+				scratch.opcode = EEO_NULLIF;
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_ScalarArrayOpExpr:
+			{
+				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
+				FmgrInfo	*finfo;
+				FunctionCallInfo fcinfo;
+
+				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);
+
+				scratch.opcode = EEO_SCALARARRAYOP;
+				scratch.d.scalararrayop.opexpr = opexpr;
+				scratch.d.scalararrayop.finfo = finfo;
+				scratch.d.scalararrayop.fcinfo_data = fcinfo;
+				scratch.d.scalararrayop.fn_addr = fcinfo->flinfo->fn_addr;
+
+				Assert(fcinfo->nargs == 2);
+				Assert(list_length(opexpr->args) == 2);
+
+				/* evaluate scalar directly into function argument */
+				ExecInitExprRec((Expr *) linitial(opexpr->args), parent, state,
+								&fcinfo->arg[0], &fcinfo->argnull[0]);
+
+				/* evaluate array argument into our return value, overwrite later */
+				ExecInitExprRec((Expr *) lsecond(opexpr->args), parent, state,
+								resv, resnull);
+
+				scratch.d.scalararrayop.element_type = InvalidOid;
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_BoolExpr:
+			{
+				BoolExpr   *boolexpr = (BoolExpr *) node;
+				ListCell   *lc;
+				List *adjust_bailout = NIL;
+				bool first = true;
+
+				scratch.d.boolexpr.value = palloc0(sizeof(Datum));
+				scratch.d.boolexpr.isnull = palloc0(sizeof(bool));
+				scratch.d.boolexpr.anynull = palloc0(sizeof(bool));
+
+				foreach (lc, boolexpr->args)
+				{
+					Expr *arg = (Expr *) lfirst(lc);
+
+					switch (boolexpr->boolop)
+					{
+						case AND_EXPR:
+							if (first)
+								scratch.opcode = EEO_BOOL_AND_STEP_FIRST;
+							else
+								scratch.opcode = EEO_BOOL_AND_STEP;
+							break;
+						case OR_EXPR:
+							if (first)
+								scratch.opcode = EEO_BOOL_OR_STEP_FIRST;
+							else
+								scratch.opcode = EEO_BOOL_OR_STEP;
+							break;
+						case NOT_EXPR:
+							Assert(list_length(boolexpr->args) == 1);
+							scratch.opcode = EEO_BOOL_NOT_STEP;
+							break;
+						default:
+							elog(ERROR, "unrecognized boolop: %d",
+								 (int) boolexpr->boolop);
+							break;
+					}
+
+					first = false;
+
+					ExecInitExprRec(arg, parent, state,
+									scratch.d.boolexpr.value,
+									scratch.d.boolexpr.isnull);
+					scratch.d.boolexpr.jumpdone = -1;
+					ExprEvalPushStep(state, &scratch);
+					adjust_bailout = lappend_int(adjust_bailout,
+												 state->steps_len - 1);
+				}
+
+				/* adjust early bail out jump target */
+				foreach (lc, adjust_bailout)
+				{
+					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 = EEO_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 = EEO_ALTERNATIVE_SUBPLAN;
+				scratch.d.alternative_subplan.asstate = asstate;
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_FieldSelect:
+			{
+				FieldSelect *fselect = (FieldSelect *) node;
+
+
+				/* evaluate argument */
+				ExecInitExprRec(fselect->arg, parent, state, resv, resnull);
+
+				scratch.opcode = EEO_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;
+				ListCell *l1, *l2;
+				Datum *values;
+				bool *nulls;
+				int biggest_fieldnum = 0;
+
+				foreach(l2, fstore->fieldnums)
+				{
+					AttrNumber	fieldnum = lfirst_int(l2);
+					if (fieldnum > biggest_fieldnum)
+						biggest_fieldnum = fieldnum;
+				}
+
+				/* FIXME: properly size workspace */
+				values = (Datum *) palloc(sizeof(Datum) * MaxTupleAttributeNumber);
+				nulls = (bool *) palloc(sizeof(bool) * MaxTupleAttributeNumber);
+
+				/* evaluate argument */
+				ExecInitExprRec(fstore->arg, parent, state, resv, resnull);
+
+				/* first deform the input tuple */
+				scratch.opcode = EEO_FIELDSTORE_DEFORM;
+				scratch.d.fieldstore.argdesc = NULL;
+				scratch.d.fieldstore.fstore = fstore;
+				scratch.d.fieldstore.nvalues = biggest_fieldnum;
+				scratch.d.fieldstore.values = values;
+				scratch.d.fieldstore.nulls = nulls;
+				ExprEvalPushStep(state, &scratch);
+
+				/* evaluate new values, one step for each arg */
+				forboth(l1, fstore->newvals, l2, fstore->fieldnums)
+				{
+					Expr	   *e = (Expr *) lfirst(l1);
+					AttrNumber	fieldnum = lfirst_int(l2);
+					Datum *save_innermost_caseval = NULL;
+					bool *save_innermost_casenull = NULL;
+
+					/*
+					 * 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.)
+					 */
+					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;
+				}
+
+				/* then form result tuple */
+				scratch.opcode = EEO_FIELDSTORE_FORM;
+				scratch.d.fieldstore.fstore = fstore;
+				scratch.d.fieldstore.argdesc = NULL;
+				scratch.d.fieldstore.nvalues = biggest_fieldnum;
+				scratch.d.fieldstore.values = values;
+				scratch.d.fieldstore.nulls = nulls;
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_RelabelType:
+			{
+				/* at runtime relabel doesn't need to do anything */
+				RelabelType *relabel = (RelabelType *) node;
+
+				ExecInitExprRec(relabel->arg, parent, state, resv, resnull);
+				break;
+			}
+
+		case T_CoerceViaIO:
+			{
+				CoerceViaIO *iocoerce = (CoerceViaIO *) node;
+				Oid			iofunc;
+				bool		typisvarlena;
+
+				/* evaluate argument */
+				ExecInitExprRec(iocoerce->arg, parent, state, resv, resnull);
+
+				scratch.opcode = EEO_IOCOERCE;
+
+				/* lookup the input type's output function */
+				scratch.d.iocoerce.finfo_out = palloc0(sizeof(*scratch.d.iocoerce.finfo_out));
+				scratch.d.iocoerce.fcinfo_data_out = palloc0(sizeof(*scratch.d.iocoerce.fcinfo_data_out));
+
+				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(*scratch.d.iocoerce.finfo_in));
+				scratch.d.iocoerce.fcinfo_data_in = palloc0(sizeof(*scratch.d.iocoerce.fcinfo_data_in));
+
+				getTypeInputInfo(iocoerce->resulttype, &iofunc,
+								 &scratch.d.iocoerce.intypioparam);
+				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);
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_ArrayCoerceExpr:
+			{
+				ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
+
+				/* evaluate argument */
+				ExecInitExprRec(acoerce->arg, parent, state, resv, resnull);
+
+				scratch.opcode = EEO_ARRAYCOERCE;
+				scratch.d.arraycoerce.coerceexpr = acoerce;
+				scratch.d.arraycoerce.resultelemtype =
+					get_element_type(acoerce->resulttype);
+				if (scratch.d.arraycoerce.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(scratch.d.arraycoerce.resultelemtype) ==
+					   scratch.d.arraycoerce.resultelemtype);
+				scratch.d.arraycoerce.elemfunc = (FmgrInfo *) palloc(sizeof(FmgrInfo));
+				scratch.d.arraycoerce.elemfunc->fn_oid = InvalidOid;	/* not initialized */
+				scratch.d.arraycoerce.amstate = (ArrayMapState *) palloc0(sizeof(ArrayMapState));
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_ConvertRowtypeExpr:
+			{
+				ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node;
+
+				/* evaluate argument */
+				ExecInitExprRec(convert->arg, parent, state, resv, resnull);
+
+				/* and push conversion step */
+				scratch.opcode = EEO_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 all CaseWhen expressions handled within this block */
+		case T_CaseExpr:
+			{
+				CaseExpr *caseExpr = (CaseExpr *) node;
+				ListCell   *clause;
+				List *adjust_bailout = NIL;
+				ListCell *lc;
+				Datum *casevalue = palloc0(sizeof(Datum));
+				bool *caseisnull = palloc0(sizeof(bool));
+				Datum *save_innermost_caseval = NULL;
+				bool *save_innermost_casenull = NULL;
+				Datum *caseval = NULL;
+				bool *casenull = NULL;
+
+				/* arg == NULL -> CASE WHEN foo */
+				/* arg != NULL -> CASE foo WHEN blarg */
+				if (caseExpr->arg != NULL)
+				{
+					caseval = palloc0(sizeof(Datum));
+					casenull = palloc0(sizeof(bool));
+
+					ExecInitExprRec(caseExpr->arg, parent, state,
+									caseval, casenull);
+				}
+
+				foreach(clause, caseExpr->args)
+				{
+					CaseWhen *when = (CaseWhen *) lfirst(clause);
+					int whenstep;
+
+					/* evaluate condition */
+					save_innermost_caseval = state->innermost_caseval;
+					save_innermost_casenull = state->innermost_casenull;
+					state->innermost_caseval = caseval;
+					state->innermost_casenull = casenull;
+
+					ExecInitExprRec(when->expr, parent, state,
+									casevalue, caseisnull);
+
+					state->innermost_caseval = save_innermost_caseval;
+					state->innermost_casenull = save_innermost_casenull;
+
+					scratch.opcode = EEO_CASE_WHEN_STEP;
+					scratch.d.casewhen.value = casevalue;
+					scratch.d.casewhen.isnull = caseisnull;
+					scratch.d.casewhen.jumpfalse = -1; /* computed later */
+					ExprEvalPushStep(state, &scratch);
+					whenstep = state->steps_len - 1;
+
+					/* evaluate result */
+					ExecInitExprRec(when->result, parent, state, resv, resnull);
+
+					scratch.opcode = EEO_CASE_THEN_STEP;
+					scratch.d.casewhen.value = casevalue;
+					scratch.d.casewhen.isnull = caseisnull;
+					scratch.d.casethen.jumpdone = -1; /* computed later */
+					ExprEvalPushStep(state, &scratch);
+					/*
+					 * Don't know "address" of jump target yet, compute once the
+					 * whole case expression is built.
+					 */
+					adjust_bailout = lappend_int(adjust_bailout,
+												 state->steps_len - 1);
+
+					/* adjust jump target for WHEN step, for the !match case */
+					state->steps[whenstep].d.casewhen.jumpfalse = state->steps_len;
+				}
+
+				if (caseExpr->defresult)
+				{
+					/* evaluate result, directly into result datum */
+					ExecInitExprRec(caseExpr->defresult, parent, state,
+									resv, resnull);
+				}
+				else
+				{
+					/* statically return NULL */
+					scratch.opcode = EEO_CONST;
+					scratch.d.constval.isnull = true;
+					scratch.d.constval.value = 0;
+					ExprEvalPushStep(state, &scratch);
+				}
+
+				/* adjust early bail out jump target */
+				foreach (lc, adjust_bailout)
+				{
+					ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+					Assert(as->d.casethen.jumpdone == -1);
+					as->d.casethen.jumpdone = state->steps_len;
+				}
+
+				break;
+			}
+
+		case T_CaseTestExpr:
+			{
+				CaseTestExpr *casetestexpr = (CaseTestExpr *) node;
+
+				scratch.d.casetest.value = state->innermost_caseval;
+				scratch.d.casetest.isnull = state->innermost_casenull;
+
+				if (get_typlen(casetestexpr->typeId) == -1)
+					scratch.opcode = EEO_CASE_TESTVAL_UNEXPAND;
+				else
+					scratch.opcode = EEO_CASE_TESTVAL;
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+
+		case T_ArrayExpr:
+			{
+				ArrayExpr  *arrayexpr = (ArrayExpr *) node;
+				int nelems = list_length(arrayexpr->elements);
+				ListCell *lc;
+				int elemoff;
+
+				scratch.opcode = EEO_ARRAYEXPR;
+				scratch.d.arrayexpr.arrayexpr = arrayexpr;
+				scratch.d.arrayexpr.nelems = nelems;
+				scratch.d.arrayexpr.elemvalues =
+					(Datum *) palloc(sizeof(Datum) * nelems);
+				scratch.d.arrayexpr.elemnulls =
+					(bool *) palloc(sizeof(bool) * nelems);
+
+				/* 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);
+
+				/* 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 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;
+
+				scratch.opcode = EEO_ROW;
+				scratch.d.row.elemvalues =
+					(Datum *) palloc(sizeof(Datum) * nelems);
+				scratch.d.row.elemnulls =
+					(bool *) palloc(sizeof(bool) * nelems);
+
+				/* 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);
+				}
+
+				scratch.d.row.tupdesc = tupdesc;
+
+				/* 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);
+
+				/* 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);
+					}
+
+					ExecInitExprRec(e, parent, state,
+									&scratch.d.row.elemvalues[i],
+									&scratch.d.row.elemnulls[i]);
+					i++;
+				}
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_RowCompareExpr:
+			{
+				RowCompareExpr *rcexpr = (RowCompareExpr *) node;
+				int			nopers = list_length(rcexpr->opnos);
+				List *adjust_bailout = NIL;
+				ListCell *l_left_expr, *l_right_expr, *l_opno, *l_opfamily, *l_inputcollid;
+				ListCell *lc;
+				int off;
+
+				/*
+				 * Iterate over each element, build comparisons. If result is
+				 * NULL, jump to after the expression. If expression yields a != 0
+				 * result jump to the final step.
+				 */
+				Assert(list_length(rcexpr->largs) == nopers);
+				Assert(list_length(rcexpr->rargs) == 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 neither does this
+					 * code.
+					 */
+
+					/* evaluate left and right expression 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 = EEO_ROWCOMPARE_STEP;
+
+					/* jump targets computed later */
+					scratch.d.rowcompare_step.jumpnull = -1;
+					scratch.d.rowcompare_step.jumpdone = -1;
+
+					scratch.d.rowcompare_step.finfo = finfo;
+					scratch.d.rowcompare_step.fcinfo_data = fcinfo;
+					scratch.d.rowcompare_step.fn_addr = fcinfo->flinfo->fn_addr;
+
+					ExprEvalPushStep(state, &scratch);
+					adjust_bailout = lappend_int(adjust_bailout,
+												 state->steps_len - 1);
+				}
+
+				/* and then compare the last result */
+				scratch.opcode = EEO_ROWCOMPARE_FINAL;
+				scratch.d.rowcompare_final.rctype = rcexpr->rctype;
+				ExprEvalPushStep(state, &scratch);
+
+				/* adjust early bail out jump target */
+				foreach (lc, adjust_bailout)
+				{
+					ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+					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_bailout = NIL;
+				ListCell   *lc;
+
+				Assert(list_length(coalesce->args) > 0);
+
+				foreach(lc, coalesce->args)
+				{
+					Expr	   *e = (Expr *) lfirst(lc);
+
+					/* evaluate result, directly into result datum */
+					ExecInitExprRec(e, parent, state, resv, resnull);
+
+					/* then push step checking for NULLs */
+					scratch.opcode = EEO_COALESCE;
+					scratch.d.coalesce.jumpdone = -1; /* adjust later */
+					ExprEvalPushStep(state, &scratch);
+
+					adjust_bailout = lappend_int(adjust_bailout,
+												 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 early bail out jump target */
+				foreach (lc, adjust_bailout)
+				{
+					ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+					Assert(as->d.coalesce.jumpdone == -1);
+					as->d.coalesce.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 neither does this code.
+				 */
+				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 = EEO_MINMAX;
+				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++;
+				}
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_SQLValueFunction:
+			{
+				SQLValueFunction *svf = (SQLValueFunction *) node;
+
+				scratch.opcode = EEO_SQLVALUEFUNCTION;
+				scratch.d.sqlvaluefunction.svf = svf;
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_XmlExpr:
+			{
+				XmlExpr    *xexpr = (XmlExpr *) node;
+				ListCell   *arg;
+				int nnamed = list_length(xexpr->named_args);
+				int nargs = list_length(xexpr->args);
+				int off;
+
+				scratch.opcode = EEO_XMLEXPR;
+				scratch.d.xmlexpr.xexpr = xexpr;
+				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;
+				}
+
+				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++;
+				}
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_NullTest:
+			{
+				NullTest   *ntest = (NullTest *) node;
+
+				if (ntest->nulltesttype == IS_NULL)
+				{
+					if (ntest->argisrow)
+						scratch.opcode = EEO_NULLTEST_ROWISNULL;
+					else
+						scratch.opcode = EEO_NULLTEST_ISNULL;
+				}
+				else if (ntest->nulltesttype == IS_NOT_NULL)
+				{
+					if (ntest->argisrow)
+						scratch.opcode = EEO_NULLTEST_ROWISNOTNULL;
+					else
+						scratch.opcode = EEO_NULLTEST_ISNOTNULL;
+				}
+				else
+				{
+					elog(ERROR, "unrecognized nulltesttype: %d",
+						 (int) ntest->nulltesttype);
+				}
+
+				/* first evaluate argument */
+				ExecInitExprRec(ntest->arg, parent, state,
+								resv, resnull);
+
+				/* then push check of argument */
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_BooleanTest:
+			{
+				BooleanTest *btest = (BooleanTest *) node;
+
+				/*
+				 * Evaluate argument, directly into result datum. That's a bit
+				 * debatable, because the types will be different, but it's
+				 * efficient...
+				 */
+				ExecInitExprRec(btest->arg, parent, state, resv, resnull);
+
+				switch (btest->booltesttype)
+				{
+					case IS_TRUE:
+						scratch.opcode = EEO_BOOLTEST_IS_TRUE;
+						break;
+					case IS_NOT_TRUE:
+						scratch.opcode = EEO_BOOLTEST_IS_NOT_TRUE;
+						break;
+					case IS_FALSE:
+						scratch.opcode = EEO_BOOLTEST_IS_FALSE;
+						break;
+					case IS_NOT_FALSE:
+						scratch.opcode = EEO_BOOLTEST_IS_NOT_FALSE;
+						break;
+					case IS_UNKNOWN:
+						scratch.opcode = EEO_BOOLTEST_IS_UNKNOWN;
+						break;
+					case IS_NOT_UNKNOWN:
+						scratch.opcode = EEO_BOOLTEST_IS_NOT_UNKNOWN;
+						break;
+					default:
+						elog(ERROR, "unrecognized booltesttype: %d",
+							 (int) btest->booltesttype);
+				}
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_CoerceToDomain:
+			{
+				CoerceToDomain *ctest = (CoerceToDomain *) node;
+				Datum *save_innermost_domainval = NULL;
+				bool *save_innermost_domainnull = NULL;
+				DomainConstraintRef *constraint_ref =
+					palloc(sizeof(DomainConstraintRef));
+				ListCell   *l;
+
+				scratch.d.domaincheck.resulttype = ctest->resulttype;
+				scratch.d.domaincheck.checkvalue = (Datum *) palloc(sizeof(Datum));
+				scratch.d.domaincheck.checknull = (bool *) palloc(sizeof(bool));
+
+				/* evaluate argument */
+				ExecInitExprRec(ctest->arg, parent, state, resv, resnull);
+
+				/*
+				 * XXX: In contrast to the old implementation we're evaluating the
+				 * set of to-be-checked constraints at query start - that seems
+				 * perfectly sensible to me. But perhaps there's a reason the
+				 * previous implementation did what it did? ISTM that was just a
+				 * side-effect of using the typecache (which is longer lived than
+				 * a single query).
+				 */
+
+				/* Make sure we have up-to-date constraints */
+				InitDomainConstraintRef(ctest->resulttype,
+										constraint_ref,
+										CurrentMemoryContext);
+				UpdateDomainConstraintRef(constraint_ref);
+
+				/*
+				 * 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 = resv;
+				state->innermost_domainnull = resnull;
+
+				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 = EEO_DOMAIN_NOTNULL;
+							ExprEvalPushStep(state, &scratch);
+							break;
+						case DOM_CONSTRAINT_CHECK:
+							/* evaluate check expression value */
+							ExecInitExprRec(con->check_expr, parent, state,
+											scratch.d.domaincheck.checkvalue,
+											scratch.d.domaincheck.checknull);
+
+							/* and then check result value */
+							scratch.opcode = EEO_DOMAIN_CHECK;
+							ExprEvalPushStep(state, &scratch);
+							break;
+						default:
+							elog(ERROR, "unrecognized constraint type: %d",
+								 (int) con->constrainttype);
+							break;
+					}
+				}
+
+				state->innermost_domainval = save_innermost_domainval;
+				state->innermost_domainnull = save_innermost_domainnull;
+
+				break;
+			}
+
+		case T_CoerceToDomainValue:
+			{
+				CoerceToDomainValue *domainval = (CoerceToDomainValue *) node;
+
+				/*
+				 * Share implementation with case testval, but different pointers.
+				 */
+				scratch.d.casetest.value = state->innermost_domainval;
+				scratch.d.casetest.isnull = state->innermost_domainnull;
+
+				if (get_typlen(domainval->typeId) == -1)
+					scratch.opcode = EEO_DOMAIN_TESTVAL_UNEXPAND;
+				else
+					scratch.opcode = EEO_DOMAIN_TESTVAL;
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		case T_CurrentOfExpr:
+			{
+				scratch.opcode = EEO_CURRENTOFEXPR;
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
+		default:
+			elog(ERROR, "unrecognized node type: %d",
+				 (int) nodeTag(node));
+			break;
+	}
+}
+
+static void
+ExprEvalPushStep(ExprState *es, 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));
+}
+
+static void
+ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid, Oid inputcollid,
+			  PlanState *parent, ExprState *state,  Datum *resv, bool *resnull)
+{
+	ListCell   *lc;
+	AclResult	aclresult;
+	int nargs = list_length(args);
+	FunctionCallInfo fcinfo;
+	int argno;
+
+	/* 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)));
+
+	/* Set up the primary fmgr lookup information */
+	scratch->d.func.finfo = palloc0(sizeof(*scratch->d.func.finfo));
+	scratch->d.func.fcinfo_data = palloc0(sizeof(*scratch->d.func.fcinfo_data));
+
+	fcinfo = scratch->d.func.fcinfo_data;
+	fmgr_info(funcid, scratch->d.func.finfo);
+	fmgr_info_set_expr((Node *) node, scratch->d.func.finfo);
+	InitFunctionCallInfoData(*fcinfo, scratch->d.func.finfo,
+							 nargs, inputcollid, NULL, NULL);
+	scratch->d.func.fn_addr = scratch->d.func.fcinfo_data->flinfo->fn_addr;
+	if (scratch->d.func.finfo->fn_retset)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	}
+
+	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++;
+	}
+
+	scratch->d.func.nargs = nargs;
+
+	if (pgstat_track_functions <= scratch->d.func.finfo->fn_stats)
+	{
+		if (scratch->d.func.finfo->fn_strict && nargs > 0)
+			scratch->opcode = EEO_FUNCEXPR_STRICT;
+		else
+			scratch->opcode = EEO_FUNCEXPR;
+	}
+	else
+	{
+		if (scratch->d.func.finfo->fn_strict && nargs > 0)
+			scratch->opcode = EEO_FUNCEXPR_STRICT_FUSAGE;
+		else
+			scratch->opcode = EEO_FUNCEXPR_FUSAGE;
+	}
+}
+
+static void
+ExecInitExprSlots(ExprState *state, Node *node)
+{
+	ExprEvalStep scratch;
+	int last_outer = -1;
+	int last_inner = -1;
+	int last_scan = -1;
+
+	/*
+	 * Figure out which attributes we're going to need.
+	 */
+	ExecGetLastAttnums((Node *) node,
+					   &last_outer,
+					   &last_inner,
+					   &last_scan);
+	if (last_inner > 0)
+	{
+		scratch.opcode = EEO_INNER_FETCHSOME;
+		scratch.d.fetch.last_var = last_inner;
+		ExprEvalPushStep(state, &scratch);
+	}
+	if (last_outer > 0)
+	{
+		scratch.opcode = EEO_OUTER_FETCHSOME;
+		scratch.d.fetch.last_var = last_outer;
+		ExprEvalPushStep(state, &scratch);
+	}
+	if (last_scan > 0)
+	{
+		scratch.opcode = EEO_SCAN_FETCHSOME;
+		scratch.d.fetch.last_var = last_scan;
+		ExprEvalPushStep(state, &scratch);
+	}
+}
+
+static void
+ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, PlanState *parent, ExprState *state, Datum *resv, bool *resnull)
+{
+	scratch->d.wholerow.tupdesc = NULL;
+	scratch->d.wholerow.junkFilter = NULL;
+	scratch->d.wholerow.var = variable;
+	scratch->d.wholerow.first = true;
+
+	/*
+	 * 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 (junk_filter_needed)
+			{
+				/* If so, build the junkfilter in the query memory context */
+				scratch->d.wholerow.junkFilter =
+					ExecInitJunkFilter(subplan->plan->targetlist,
+									   ExecGetResultType(subplan)->tdhasoid,
+									   ExecInitExtraTupleSlot(parent->state));
+			}
+		}
+	}
+}
+
+static void
+ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, PlanState *parent,
+				 ExprState *state, Datum *resv, bool *resnull)
+{
+	List *adjust_bailout = NIL;
+	ListCell *lc;
+	bool		isAssignment = (aref->refassgnexpr != NULL);
+	ArrayRefState *arefstate = palloc(sizeof(ArrayRefState));
+	int i;
+
+	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->refupperindexpr), MAXDIM)));
+
+	if (list_length(aref->reflowerindexpr) > 0 &&
+		list_length(aref->refupperindexpr) != list_length(aref->reflowerindexpr))
+		elog(ERROR, "upper and lower index lists are not same length");
+
+	arefstate->isassignment = isAssignment;
+	arefstate->refattrlength = get_typlen(aref->refarraytype);
+	arefstate->refelemtype = aref->refelemtype;
+	get_typlenbyvalalign(aref->refelemtype,
+						 &arefstate->refelemlength,
+						 &arefstate->refelembyval,
+						 &arefstate->refelemalign);
+
+	/* evaluate input */
+	ExecInitExprRec(aref->refexpr, parent, state,
+					resv, resnull);
+
+	/*
+	 * If refexpr yields NULL, and it's a fetch, then result is
+	 * NULL. In the assignment case, we'll create an empty input.
+	 */
+	if (!isAssignment)
+	{
+		scratch->opcode = EEO_ARRAYREF_CHECKINPUT;
+		scratch->d.arrayref.state = arefstate;
+		scratch->d.arrayref.jumpdone = -1; /* adjust later */
+		ExprEvalPushStep(state, scratch);
+		adjust_bailout = lappend_int(adjust_bailout,
+									 state->steps_len - 1);
+	}
+
+	/* evaluate upper subscripts */
+	i = 0;
+	foreach(lc, aref->refupperindexpr)
+	{
+		Expr  *e = (Expr *) lfirst(lc);
+
+		if (!e)
+		{
+			arefstate->upperprovided[i] = false;
+			i++;
+			continue;
+		}
+
+		arefstate->upperprovided[i] = true;
+
+		ExecInitExprRec(e, parent, state,
+						&arefstate->upper[i], &arefstate->uppernull[i]);
+
+		scratch->opcode = EEO_ARRAYREF_CHECKSUBSCRIPT;
+		scratch->d.arrayref_checksubscript.state = arefstate;
+		scratch->d.arrayref_checksubscript.off = i;
+		scratch->d.arrayref_checksubscript.isupper = true;
+		scratch->d.arrayref_checksubscript.jumpdone = -1; /* adjust later */
+		ExprEvalPushStep(state, scratch);
+		adjust_bailout = lappend_int(adjust_bailout,
+									 state->steps_len - 1);
+		i++;
+	}
+	arefstate->numupper = i;
+
+	/* evaluate lower subscripts */
+	i = 0;
+	foreach(lc, aref->reflowerindexpr)
+	{
+		Expr  *e = (Expr *) lfirst(lc);
+
+		if (!e)
+		{
+			arefstate->lowerprovided[i] = false;
+			i++;
+			continue;
+		}
+
+		arefstate->lowerprovided[i] = true;
+
+		ExecInitExprRec(e, parent, state,
+						&arefstate->lower[i], &arefstate->lowernull[i]);
+
+		scratch->opcode = EEO_ARRAYREF_CHECKSUBSCRIPT;
+		scratch->d.arrayref_checksubscript.state = arefstate;
+		scratch->d.arrayref_checksubscript.off = i;
+		scratch->d.arrayref_checksubscript.isupper = false;
+		scratch->d.arrayref_checksubscript.jumpdone = -1; /* adjust later */
+		ExprEvalPushStep(state, scratch);
+		adjust_bailout = lappend_int(adjust_bailout,
+									 state->steps_len - 1);
+		i++;
+	}
+	arefstate->numlower = i;
+
+	if (isAssignment)
+	{
+		Datum *save_innermost_caseval = NULL;
+		bool *save_innermost_casenull = NULL;
+
+		/*
+		 * 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.
+		 */
+		if (isAssignmentIndirectionExpr(aref->refassgnexpr))
+		{
+			scratch->opcode = EEO_ARRAYREF_OLD;
+			scratch->d.arrayref.state = arefstate;
+			ExprEvalPushStep(state, scratch);
+		}
+
+		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 */
+		ExecInitExprRec(aref->refassgnexpr, parent, state,
+						&arefstate->replacevalue, &arefstate->replacenull);
+
+		state->innermost_caseval = save_innermost_caseval;
+		state->innermost_casenull = save_innermost_casenull;
+
+		scratch->opcode = EEO_ARRAYREF_ASSIGN;
+		scratch->d.arrayref.state = arefstate;
+		ExprEvalPushStep(state, scratch);
+	}
+	else
+	{
+		scratch->opcode = EEO_ARRAYREF_FETCH;
+		scratch->d.arrayref.state = arefstate;
+		ExprEvalPushStep(state, scratch);
+	}
+
+	/* adjust early bail out jump target */
+	foreach (lc, adjust_bailout)
+	{
+		ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+		if (as->opcode == EEO_ARRAYREF_CHECKSUBSCRIPT)
+		{
+			Assert(as->d.arrayref_checksubscript.jumpdone == -1);
+			as->d.arrayref_checksubscript.jumpdone = state->steps_len;
+		}
+		else
+		{
+			Assert(as->d.arrayref.jumpdone == -1);
+			as->d.arrayref.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;
+}
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 5242dee006..108060ac0f 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/execInterpExpr.c b/src/backend/executor/execInterpExpr.c
new file mode 100644
index 0000000000..9387ef02a0
--- /dev/null
+++ b/src/backend/executor/execInterpExpr.c
@@ -0,0 +1,2840 @@
+/*-------------------------------------------------------------------------
+ *
+ * execInterpExpr.c
+ *	  Opcode based expression evaluation.
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/executor/execInterprExpr.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#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 "executor/execExpr.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 allow to disable locally in this file for development.
+ */
+#ifdef HAVE__COMPUTED_GOTO
+#	define EEO_USE_COMPUTED_GOTO
+#endif /* HAVE__COMPUTED_GOTO */
+
+/*
+ * Macros for opcode dispatch.
+ */
+#if defined(EEO_USE_COMPUTED_GOTO)
+static void **dispatch_table = NULL;
+#	define EEO_OPCODE(opc) dispatch_table[opc]
+#	define EEO_SWITCH(d)
+#	define EEO_DISPATCH_DIRECT(op) goto *((void *) op->opcode)
+#	define EEO_CASE(name) CASE_##name
+#else
+#	define EEO_OPCODE(opc) opc
+#	define EEO_SWITCH(d) switch ((ExprEvalOp) d)
+#	define EEO_DISPATCH_DIRECT(op) goto starteval
+#	define EEO_CASE(name) case name
+#endif /* EEO_USE_COMPUTED_GOTO */
+
+#define EEO_DISPATCH(op) \
+	do \
+	{ \
+		op++; \
+		EEO_DISPATCH_DIRECT(op); \
+	} \
+	while (0)
+
+static Datum ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull);
+static void ExecPrepareInterp(void);
+
+/* support functions */
+static void ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op, bool checkisnull);
+static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod,
+									TupleDesc *cache_field, ExprContext *econtext);
+static void ShutdownTupleDescRef(Datum arg);
+
+/* fast-path evaluation functions */
+static Datum ExecJustOuterVar(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustInnerVar(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustScanVar(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustConst(ExprState *state, ExprContext *econtext, bool *isnull);
+
+
+/*
+ * Prepare ExprState for interpreted execution.
+ */
+void
+ExecInstantiateInterpretedExpr(ExprState *state)
+{
+	ExecPrepareInterp();
+
+	Assert(state->steps_len >= 1);
+	Assert(ExecEvalStepOp(state, &state->steps[state->steps_len - 1]) == EEO_DONE);
+
+	/*
+	 * Fast-path 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 anyways.
+	 */
+	if (state->steps_len == 3)
+	{
+		ExprEvalOp step0 = ExecEvalStepOp(state, &state->steps[0]);
+		ExprEvalOp step1 = ExecEvalStepOp(state, &state->steps[1]);
+
+		if (step0 == EEO_INNER_FETCHSOME && step1 == EEO_INNER_VAR)
+		{
+			state->cb = ExecJustInnerVar;
+			return;
+		}
+		else if (step0 == EEO_OUTER_FETCHSOME && step1 == EEO_OUTER_VAR)
+		{
+			state->cb = ExecJustOuterVar;
+			return;
+		}
+		else if (step0 == EEO_SCAN_FETCHSOME && step1 == EEO_SCAN_VAR)
+		{
+			state->cb = ExecJustScanVar;
+			return;
+		}
+	}
+	else if (state->steps_len == 2 &&
+			 ExecEvalStepOp(state, &state->steps[0]) == EEO_CONST)
+	{
+		state->cb = ExecJustConst;
+		return;
+	}
+
+#if defined(EEO_USE_COMPUTED_GOTO)
+	{
+		int off;
+
+		for (off = 0; off < state->steps_len; off++)
+		{
+			ExprEvalStep *op = &state->steps[off];
+
+			op->opcode = (size_t) EEO_OPCODE(op->opcode);
+		}
+
+		state->flags |= EEO_FLAG_JUMP_THREADED;
+	}
+#endif /* defined(EEO_USE_COMPUTED_GOTO) */
+
+	state->cb = ExecInterpExpr;
+}
+
+
+static Datum
+ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+	ExprEvalStep   *op;
+	const TupleTableSlot *resultslot;
+	TupleTableSlot *innerslot;
+	TupleTableSlot *outerslot;
+	TupleTableSlot *scanslot;
+
+	/*
+	 * This array has to be in the same order as ExprEvalOp.
+	 */
+#if defined(EEO_USE_COMPUTED_GOTO)
+	static const void **dispatch_table[] = {
+		&&CASE_EEO_DONE,
+		&&CASE_EEO_INNER_FETCHSOME,
+		&&CASE_EEO_OUTER_FETCHSOME,
+		&&CASE_EEO_SCAN_FETCHSOME,
+		&&CASE_EEO_INNER_VAR,
+		&&CASE_EEO_OUTER_VAR,
+		&&CASE_EEO_SCAN_VAR,
+		&&CASE_EEO_ASSIGN_INNER_VAR,
+		&&CASE_EEO_ASSIGN_OUTER_VAR,
+		&&CASE_EEO_ASSIGN_SCAN_VAR,
+		&&CASE_EEO_ASSIGN_TMP,
+		&&CASE_EEO_ASSIGN_TMP_UNEXPAND,
+		&&CASE_EEO_INNER_SYSVAR,
+		&&CASE_EEO_OUTER_SYSVAR,
+		&&CASE_EEO_SCAN_SYSVAR,
+		&&CASE_EEO_CONST,
+		&&CASE_EEO_FUNCEXPR,
+		&&CASE_EEO_FUNCEXPR_STRICT,
+		&&CASE_EEO_FUNCEXPR_FUSAGE,
+		&&CASE_EEO_FUNCEXPR_STRICT_FUSAGE,
+		&&CASE_EEO_BOOL_AND_STEP_FIRST,
+		&&CASE_EEO_BOOL_AND_STEP,
+		&&CASE_EEO_BOOL_OR_STEP_FIRST,
+		&&CASE_EEO_BOOL_OR_STEP,
+		&&CASE_EEO_BOOL_NOT_STEP,
+		&&CASE_EEO_QUAL,
+		&&CASE_EEO_NULLTEST_ISNULL,
+		&&CASE_EEO_NULLTEST_ISNOTNULL,
+		&&CASE_EEO_NULLTEST_ROWISNULL,
+		&&CASE_EEO_NULLTEST_ROWISNOTNULL,
+		&&CASE_EEO_PARAM_EXEC,
+		&&CASE_EEO_PARAM_EXTERN,
+		&&CASE_EEO_CASE_WHEN_STEP,
+		&&CASE_EEO_CASE_THEN_STEP,
+		&&CASE_EEO_CASE_TESTVAL,
+		&&CASE_EEO_CASE_TESTVAL_UNEXPAND,
+		&&CASE_EEO_COALESCE,
+		&&CASE_EEO_BOOLTEST_IS_TRUE,
+		&&CASE_EEO_BOOLTEST_IS_NOT_TRUE,
+		&&CASE_EEO_BOOLTEST_IS_FALSE,
+		&&CASE_EEO_BOOLTEST_IS_NOT_FALSE,
+		&&CASE_EEO_BOOLTEST_IS_UNKNOWN,
+		&&CASE_EEO_BOOLTEST_IS_NOT_UNKNOWN,
+		&&CASE_EEO_WHOLEROW,
+		&&CASE_EEO_IOCOERCE,
+		&&CASE_EEO_DISTINCT,
+		&&CASE_EEO_NULLIF,
+		&&CASE_EEO_SQLVALUEFUNCTION,
+		&&CASE_EEO_CURRENTOFEXPR,
+		&&CASE_EEO_ARRAYEXPR,
+		&&CASE_EEO_ARRAYCOERCE,
+		&&CASE_EEO_ROW,
+		&&CASE_EEO_ROWCOMPARE_STEP,
+		&&CASE_EEO_ROWCOMPARE_FINAL,
+		&&CASE_EEO_MINMAX,
+		&&CASE_EEO_FIELDSELECT,
+		&&CASE_EEO_FIELDSTORE_DEFORM,
+		&&CASE_EEO_FIELDSTORE_FORM,
+		&&CASE_EEO_ARRAYREF_CHECKINPUT,
+		&&CASE_EEO_ARRAYREF_CHECKSUBSCRIPT,
+		&&CASE_EEO_ARRAYREF_OLD,
+		&&CASE_EEO_ARRAYREF_ASSIGN,
+		&&CASE_EEO_ARRAYREF_FETCH,
+		&&CASE_EEO_CONVERT_ROWTYPE,
+		&&CASE_EEO_SCALARARRAYOP,
+		&&CASE_EEO_DOMAIN_TESTVAL,
+		&&CASE_EEO_DOMAIN_TESTVAL_UNEXPAND,
+		&&CASE_EEO_DOMAIN_NOTNULL,
+		&&CASE_EEO_DOMAIN_CHECK,
+		&&CASE_EEO_XMLEXPR,
+		&&CASE_EEO_AGGREF,
+		&&CASE_EEO_GROUPING_FUNC,
+		&&CASE_EEO_WINDOW_FUNC,
+		&&CASE_EEO_SUBPLAN,
+		&&CASE_EEO_ALTERNATIVE_SUBPLAN,
+		&&CASE_EEO_LAST
+	};
+
+	StaticAssertStmt(EEO_LAST + 1 == lengthof(dispatch_table),
+					 "dispatch_table out of whack with ExprEvalOp");
+#endif
+
+#ifdef EEO_USE_COMPUTED_GOTO
+	if (unlikely(state == NULL))
+	{
+		return PointerGetDatum(dispatch_table);
+	}
+#endif
+
+	/* 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_DIRECT(op);
+#else
+starteval:
+#endif
+	EEO_SWITCH (op->opcode)
+	{
+		EEO_CASE(EEO_DONE):
+			goto out;
+
+			/* */
+		EEO_CASE(EEO_INNER_FETCHSOME):
+			/* XXX: worthwhile to check tts_nvalid inline first? */
+			slot_getsomeattrs(innerslot, op->d.fetch.last_var);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_OUTER_FETCHSOME):
+			slot_getsomeattrs(outerslot, op->d.fetch.last_var);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_SCAN_FETCHSOME):
+			slot_getsomeattrs(scanslot, op->d.fetch.last_var);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_INNER_VAR):
+			{
+				int attnum = op->d.var.attnum;
+
+				/*
+				 * Can't assert tts_nvalid, as wholerow var evaluation or such
+				 * could have materialized the slot - but the contents are
+				 * still valid :/
+				 */
+				Assert(op->d.var.attnum >= 0);
+				*op->resnull = innerslot->tts_isnull[attnum];
+				*op->resvalue = innerslot->tts_values[attnum];
+				EEO_DISPATCH(op);
+			}
+
+		EEO_CASE(EEO_OUTER_VAR):
+			{
+				int attnum = op->d.var.attnum;
+
+				/* See EEO_INNER_VAR comments */
+				Assert(op->d.var.attnum >= 0);
+				*op->resnull = outerslot->tts_isnull[attnum];
+				*op->resvalue = outerslot->tts_values[attnum];
+				EEO_DISPATCH(op);
+			}
+
+		EEO_CASE(EEO_SCAN_VAR):
+			{
+				int attnum = op->d.var.attnum;
+
+				/* See EEO_INNER_VAR comments */
+				Assert(op->d.var.attnum >= 0);
+				*op->resnull = scanslot->tts_isnull[attnum];
+				*op->resvalue = scanslot->tts_values[attnum];
+				EEO_DISPATCH(op);
+			}
+
+		EEO_CASE(EEO_ASSIGN_INNER_VAR):
+			{
+				size_t resultnum = op->d.assign_var.resultnum;
+				size_t attnum = op->d.assign_var.attnum;
+				resultslot->tts_values[resultnum] = innerslot->tts_values[attnum];
+				resultslot->tts_isnull[resultnum] = innerslot->tts_isnull[attnum];
+			}
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_ASSIGN_OUTER_VAR):
+			{
+				size_t resultnum = op->d.assign_var.resultnum;
+				size_t attnum = op->d.assign_var.attnum;
+				resultslot->tts_values[resultnum] = outerslot->tts_values[attnum];
+				resultslot->tts_isnull[resultnum] = outerslot->tts_isnull[attnum];
+			}
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_ASSIGN_SCAN_VAR):
+			{
+				size_t resultnum = op->d.assign_var.resultnum;
+				size_t attnum = op->d.assign_var.attnum;
+				resultslot->tts_values[resultnum] = scanslot->tts_values[attnum];
+				resultslot->tts_isnull[resultnum] = scanslot->tts_isnull[attnum];
+			}
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_ASSIGN_TMP):
+			{
+				size_t resultnum = op->d.assign_tmp.resultnum;
+				resultslot->tts_values[resultnum] = state->resvalue;
+				resultslot->tts_isnull[resultnum] = state->resnull;
+			}
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_ASSIGN_TMP_UNEXPAND):
+			{
+				size_t resultnum = op->d.assign_tmp.resultnum;
+				resultslot->tts_values[resultnum] = state->resvalue;
+				resultslot->tts_isnull[resultnum] = state->resnull;
+				if (!resultslot->tts_isnull[resultnum] &&
+					resultslot->tts_tupleDescriptor->attrs[resultnum]->attlen == -1)
+					resultslot->tts_values[resultnum] = MakeExpandedObjectReadOnlyInternal(resultslot->tts_values[resultnum]);
+
+			}
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_INNER_SYSVAR):
+			{
+				int attnum = op->d.var.attnum;
+				Assert(op->d.var.attnum < 0);
+				*op->resvalue = heap_getsysattr(innerslot->tts_tuple, attnum,
+												innerslot->tts_tupleDescriptor,
+												op->resnull);
+				EEO_DISPATCH(op);
+			}
+
+		EEO_CASE(EEO_OUTER_SYSVAR):
+			{
+				int attnum = op->d.var.attnum;
+				Assert(op->d.var.attnum < 0);
+				*op->resvalue = heap_getsysattr(scanslot->tts_tuple, attnum,
+												scanslot->tts_tupleDescriptor,
+												op->resnull);
+				EEO_DISPATCH(op);
+			}
+
+		EEO_CASE(EEO_SCAN_SYSVAR):
+			{
+				int attnum = op->d.var.attnum;
+				Assert(op->d.var.attnum < 0);
+				*op->resvalue = heap_getsysattr(scanslot->tts_tuple, attnum,
+												scanslot->tts_tupleDescriptor,
+												op->resnull);
+				EEO_DISPATCH(op);
+			}
+
+		EEO_CASE(EEO_CONST):
+			*op->resnull = op->d.constval.isnull;
+			*op->resvalue = op->d.constval.value;
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_FUNCEXPR):
+			{
+				FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+
+				fcinfo->isnull = false;
+				*op->resvalue = (op->d.func.fn_addr)(fcinfo);
+				*op->resnull = fcinfo->isnull;
+			}
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_FUNCEXPR_STRICT):
+			{
+				FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+				int argno;
+
+				/* strict function, check for NULL args */
+				for (argno = 0; argno < op->d.func.nargs;argno++)
+				{
+					if (fcinfo->argnull[argno])
+					{
+						*op->resnull = true;
+						goto strictfail;
+					}
+				}
+				fcinfo->isnull = false;
+				*op->resvalue = (op->d.func.fn_addr)(fcinfo);
+				*op->resnull = fcinfo->isnull;
+			}
+strictfail:
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_FUNCEXPR_FUSAGE):
+			{
+				PgStat_FunctionCallUsage fcusage;
+				FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+				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_DISPATCH(op);
+
+		EEO_CASE(EEO_FUNCEXPR_STRICT_FUSAGE):
+			{
+				PgStat_FunctionCallUsage fcusage;
+				FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+				int argno;
+
+				/* strict function, check for NULL args */
+				for (argno = 0; argno < op->d.func.nargs;argno++)
+				{
+					if (fcinfo->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;
+		strictfail_fusage:
+				pgstat_end_function_usage(&fcusage, true);
+			}
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_BOOL_AND_STEP_FIRST):
+			*op->d.boolexpr.anynull = false;
+			/* fallthrough */
+
+		EEO_CASE(EEO_BOOL_AND_STEP):
+			*op->resvalue = *op->d.boolexpr.value;
+			*op->resnull = *op->d.boolexpr.isnull;
+
+			if (*op->d.boolexpr.isnull)
+			{
+				*op->d.boolexpr.anynull = true;
+			}
+			else if (!DatumGetBool(*op->d.boolexpr.value))
+			{
+				*op->resnull = false;
+				*op->resvalue = BoolGetDatum(false);
+				/* bail out early */
+				op = &state->steps[op->d.boolexpr.jumpdone];
+				EEO_DISPATCH_DIRECT(op);
+			}
+
+			/* XXX: could skip this step till last AND branch */
+			if (*op->d.boolexpr.anynull)
+			{
+				*op->resnull = *op->d.boolexpr.anynull;
+				*op->resvalue = BoolGetDatum(false); /* or null */
+			}
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_BOOL_OR_STEP_FIRST):
+			*op->d.boolexpr.anynull = false;
+			/* fallthrough */
+
+		EEO_CASE(EEO_BOOL_OR_STEP):
+			*op->resvalue = *op->d.boolexpr.value;
+			*op->resnull = *op->d.boolexpr.isnull;
+
+			if (*op->d.boolexpr.isnull)
+			{
+				*op->d.boolexpr.anynull = true;
+			}
+			else if (DatumGetBool(*op->d.boolexpr.value))
+			{
+				*op->resnull = false;
+				*op->resvalue = BoolGetDatum(true);
+				/* bail out early */
+				op = &state->steps[op->d.boolexpr.jumpdone];
+				EEO_DISPATCH_DIRECT(op);
+			}
+
+			/* XXX: could skip this step till last OR branch */
+			if (*op->d.boolexpr.anynull)
+			{
+				*op->resnull = *op->d.boolexpr.anynull;
+				*op->resvalue = BoolGetDatum(false); /* or null */
+			}
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_BOOL_NOT_STEP):
+			*op->resvalue = BoolGetDatum(!DatumGetBool(*op->d.boolexpr.value));
+			*op->resnull = *op->d.boolexpr.isnull;
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_QUAL):
+			/* special case for ExecQual() */
+
+			if (*op->resnull ||
+				!DatumGetBool(*op->resvalue))
+			{
+				/* bail out early */
+				*op->resnull = false;
+				*op->resvalue = BoolGetDatum(false);
+				op = &state->steps[op->d.qualexpr.jumpdone];
+				EEO_DISPATCH_DIRECT(op);
+			}
+
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_NULLTEST_ISNULL):
+			*op->resvalue = BoolGetDatum(*op->resnull);
+			*op->resnull = false;
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_NULLTEST_ISNOTNULL):
+			*op->resvalue = BoolGetDatum(!*op->resnull);
+			*op->resnull = false;
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_NULLTEST_ROWISNULL):
+			/* out of line implementation: too large */
+			ExecEvalRowNull(state, op);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_NULLTEST_ROWISNOTNULL):
+			/* out of line implementation: too large */
+			ExecEvalRowNotNull(state, op);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_PARAM_EXEC):
+			ExecEvalParamExec(state, op, econtext);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_PARAM_EXTERN):
+			/* out of line implementation: too large */
+			ExecEvalParamExtern(state, op, econtext);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_CASE_WHEN_STEP):
+			if (!*op->d.casewhen.isnull
+				&& DatumGetBool(*op->d.casewhen.value))
+			{
+				EEO_DISPATCH(op);
+			}
+			else
+			{
+				op = &state->steps[op->d.casewhen.jumpfalse];
+				EEO_DISPATCH_DIRECT(op);
+			}
+
+		EEO_CASE(EEO_CASE_THEN_STEP):
+			/* results already placed in correct place during preceding steps */
+			op = &state->steps[op->d.casethen.jumpdone];
+			EEO_DISPATCH_DIRECT(op);
+
+		EEO_CASE(EEO_CASE_TESTVAL):
+			/*
+			 * Normally upper parts in 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 enoiugh to
+			 * worry about a 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_DISPATCH(op);
+
+		EEO_CASE(EEO_CASE_TESTVAL_UNEXPAND):
+			/*
+			 * See EEO_CASE_TESTVAL comment.
+			 */
+			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;
+			}
+
+			/* Since caseValue_datum may be read multiple times, force to R/O */
+			if (!*op->resnull)
+			{
+				*op->resvalue = MakeExpandedObjectReadOnlyInternal(*op->resvalue);
+			}
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_DOMAIN_TESTVAL):
+			/*
+			 * See EEO_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_DISPATCH(op);
+
+		EEO_CASE(EEO_DOMAIN_TESTVAL_UNEXPAND):
+			/*
+			 * See EEO_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;
+			}
+
+			/* Since domainValue_datum may be read multiple times, force to R/O */
+			if (!*op->resnull)
+			{
+				*op->resvalue = MakeExpandedObjectReadOnlyInternal(*op->resvalue);
+			}
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_COALESCE):
+			if (!*op->resnull)
+			{
+				op = &state->steps[op->d.coalesce.jumpdone];
+				EEO_DISPATCH_DIRECT(op);
+			}
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_BOOLTEST_IS_TRUE):
+		EEO_CASE(EEO_BOOLTEST_IS_NOT_FALSE):
+			if (*op->resnull)
+				*op->resvalue = false;
+			else
+				*op->resvalue = *op->resvalue;
+			*op->resnull = false;
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_BOOLTEST_IS_FALSE):
+		EEO_CASE(EEO_BOOLTEST_IS_NOT_TRUE):
+			if (*op->resnull)
+				*op->resvalue = false;
+			else
+				*op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue));
+			*op->resnull = false;
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_BOOLTEST_IS_UNKNOWN):
+			*op->resvalue = BoolGetDatum(*op->resnull);
+			*op->resnull = false;
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_BOOLTEST_IS_NOT_UNKNOWN):
+			*op->resvalue = BoolGetDatum(!*op->resnull);
+			*op->resnull = false;
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_WHOLEROW):
+			/* too complex for an inline implementation */
+			ExecEvalWholeRowVar(state, op, econtext);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_IOCOERCE):
+			{
+				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;
+
+					str = DatumGetPointer(FunctionCallInvoke(fcinfo_out));
+					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] = BoolGetDatum(*op->resnull);
+					fcinfo_in->arg[1] = op->d.iocoerce.intypioparam;
+					fcinfo_in->argnull[1] = false;
+					fcinfo_in->arg[2] = -1;
+					fcinfo_in->argnull[2] = false;
+
+					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_DISPATCH(op);
+			}
+
+		EEO_CASE(EEO_DISTINCT):
+			{
+				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->resnull = false;
+					*op->resvalue = BoolGetDatum(false);
+				}
+				else if (fcinfo->argnull[0] || fcinfo->argnull[1])
+				{
+					/* Only one is NULL? Then is distinct... */
+					*op->resnull = false;
+					*op->resvalue = BoolGetDatum(true);
+				}
+				else
+				{
+					fcinfo->isnull = false;
+					*op->resvalue = (op->d.func.fn_addr)(fcinfo);
+					/* Must invert result of "=" */
+					*op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue));
+					*op->resnull = fcinfo->isnull;
+				}
+				EEO_DISPATCH(op);
+			}
+
+		EEO_CASE(EEO_NULLIF):
+			{
+				FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+
+				/* if either argument is NULL they can't be equal */
+				if (!fcinfo->argnull[0] && !fcinfo->argnull[1])
+				{
+					bool result;
+
+					fcinfo->isnull = false;
+					result = (op->d.func.fn_addr)(fcinfo);
+
+					/* if the arguments are equal return null */
+					if (!fcinfo->isnull && DatumGetBool(result))
+					{
+						*op->resnull = true;
+						*op->resvalue = true;
+						EEO_DISPATCH(op);
+					}
+				}
+				*op->resnull = fcinfo->argnull[0];
+				*op->resvalue = fcinfo->arg[0];
+				EEO_DISPATCH(op);
+			}
+
+		EEO_CASE(EEO_SQLVALUEFUNCTION):
+			/*
+			 * Doesn't seem worthwhile to have an inline implementation
+			 * efficency-wise.
+			 */
+			ExecEvalSQLValueFunction(state, op);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_CURRENTOFEXPR):
+			/* error invocation uses space, and shouldn't ever occur */
+			ExecEvalCurrentOfExpr(state, op);
+
+		EEO_CASE(EEO_ARRAYEXPR):
+			/* too complex for an inline implementation */
+			ExecEvalArrayExpr(state, op);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_ARRAYCOERCE):
+			/* too complex for an inline implementation */
+			ExecEvalArrayCoerce(state, op);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_ROW):
+			/* too complex for an inline implementation */
+			ExecEvalRow(state, op);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_ROWCOMPARE_STEP):
+			{
+				FunctionCallInfo fcinfo = op->d.rowcompare_step.fcinfo_data;
+
+				/* force NULL result */
+				if (op->d.rowcompare_step.finfo->fn_strict &&
+					(fcinfo->argnull[0] || fcinfo->argnull[1]))
+				{
+					*op->resnull = true;
+					op = &state->steps[op->d.rowcompare_step.jumpnull];
+					EEO_DISPATCH_DIRECT(op);
+				}
+
+				fcinfo->isnull = false;
+				*op->resvalue = (op->d.rowcompare_step.fn_addr)(fcinfo);
+
+				/* force NULL result */
+				if (fcinfo->isnull)
+				{
+					*op->resnull = true;
+					op = &state->steps[op->d.rowcompare_step.jumpnull];
+					EEO_DISPATCH_DIRECT(op);
+				}
+
+				/* no need to compare remaining columns */
+				if (DatumGetInt32(*op->resvalue) != 0)
+				{
+					op = &state->steps[op->d.rowcompare_step.jumpdone];
+					EEO_DISPATCH_DIRECT(op);
+				}
+
+				EEO_DISPATCH(op);
+			}
+
+		EEO_CASE(EEO_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_DISPATCH(op);
+			}
+
+		EEO_CASE(EEO_MINMAX):
+			/* too complex for an inline implementation */
+			ExecEvalMinMax(state, op);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_FIELDSELECT):
+			/* too complex for an inline implementation */
+			ExecEvalFieldSelect(state, op, econtext);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_FIELDSTORE_DEFORM):
+			/* too complex for an inline implementation */
+			ExecEvalFieldStoreDeForm(state, op, econtext);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_FIELDSTORE_FORM):
+			/* too complex for an inline implementation */
+			ExecEvalFieldStoreForm(state, op, econtext);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_ARRAYREF_CHECKINPUT):
+			Assert(!op->d.arrayref.state->isassignment);
+			/*
+			 * If refexpr yields NULL, and it's a fetch, then result is NULL.
+			 */
+			if (*op->resnull &&
+				!op->d.arrayref.state->isassignment)
+			{
+				op = &state->steps[op->d.arrayref.jumpdone];
+				EEO_DISPATCH_DIRECT(op);
+			}
+
+			EEO_DISPATCH(op);
+
+		/*
+		 * Check whether a subscript is NULL and handle that.
+		 */
+		EEO_CASE(EEO_ARRAYREF_CHECKSUBSCRIPT):
+			if (ExecEvalArrayRefCheckSubscript(state, op))
+			{
+				op++;
+			}
+			else
+			{
+				op = &state->steps[op->d.arrayref_checksubscript.jumpdone];
+			}
+			EEO_DISPATCH_DIRECT(op);
+
+		/*
+		 * Fetch the old value in an arrayref, if referenced (via a
+		 * CaseTestExpr) inside the assignment expression.
+		 */
+		EEO_CASE(EEO_ARRAYREF_OLD):
+			ExecEvalArrayRefOld(state, op);
+			EEO_DISPATCH(op);
+
+		/*
+		 * Perform ArrayRef assignment
+		 */
+		EEO_CASE(EEO_ARRAYREF_ASSIGN):
+			ExecEvalArrayRefAssign(state, op);
+			EEO_DISPATCH(op);
+
+		/*
+		 * Fetch subset of an array.
+		 */
+		EEO_CASE(EEO_ARRAYREF_FETCH):
+			ExecEvalArrayRefFetch(state, op);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_CONVERT_ROWTYPE):
+			ExecEvalConvertRowtype(state, op, econtext);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_SCALARARRAYOP):
+			/* too complex for an inline implementation */
+			ExecEvalScalarArrayOp(state, op);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_DOMAIN_NOTNULL):
+			/* too complex for an inline implementation */
+			ExecEvalConstraintNotNull(state, op);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_DOMAIN_CHECK):
+			/* too complex for an inline implementation */
+			ExecEvalConstraintCheck(state, op);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_XMLEXPR):
+			/* too complex for an inline implementation */
+			ExecEvalXmlExpr(state, op);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_AGGREF):
+			{
+				AggrefExprState *aggref = op->d.aggref.astate;
+				Assert(econtext->ecxt_aggvalues != NULL);
+
+				*op->resnull = econtext->ecxt_aggnulls[aggref->aggno];
+				*op->resvalue = econtext->ecxt_aggvalues[aggref->aggno];
+				EEO_DISPATCH(op);
+			}
+
+		EEO_CASE(EEO_GROUPING_FUNC):
+			/* too complex/uncommon for an inline implementation */
+			ExecEvalGroupingFunc(state, op);
+
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_WINDOW_FUNC):
+			{
+				WindowFuncExprState *wfunc = op->d.window_func.wfstate;
+				Assert(econtext->ecxt_aggvalues != NULL);
+
+				*op->resnull = econtext->ecxt_aggnulls[wfunc->wfuncno];
+				*op->resvalue = econtext->ecxt_aggvalues[wfunc->wfuncno];
+				EEO_DISPATCH(op);
+			}
+
+		EEO_CASE(EEO_SUBPLAN):
+			/* too complex for an inline implementation */
+			ExecEvalSubPlan(state, op, econtext);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_ALTERNATIVE_SUBPLAN):
+			/* too complex for an inline implementation */
+			ExecEvalAlternativeSubPlan(state, op, econtext);
+			EEO_DISPATCH(op);
+
+		EEO_CASE(EEO_LAST):
+		{
+			Assert(false);
+		}
+	}
+out:
+	*isnull = state->resnull;
+	return state->resvalue;
+}
+
+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;
+	return slot_getattr(slot, attnum, isnull);
+}
+
+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;
+	return slot_getattr(slot, attnum, isnull);
+}
+
+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;
+	return slot_getattr(slot, attnum, isnull);
+}
+
+static Datum
+ExecJustConst(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+	ExprEvalStep *op = &state->steps[0];
+	*isnull = op->d.constval.isnull;
+	return op->d.constval.value;
+}
+
+static void
+ExecPrepareInterp(void)
+{
+	static bool prepared = false;
+	if (prepared)
+		return;
+
+#if defined(EEO_USE_COMPUTED_GOTO)
+	if (dispatch_table == NULL)
+		dispatch_table = (void **) DatumGetPointer(ExecInterpExpr(NULL, NULL, NULL));
+#endif
+
+	prepared = true;
+}
+
+ExprEvalOp
+ExecEvalStepOp(ExprState *state, ExprEvalStep *op)
+{
+#if defined(EEO_USE_COMPUTED_GOTO)
+	if (state->flags & EEO_FLAG_JUMP_THREADED)
+	{
+		int		i;
+		for (i = 0; i < EEO_LAST; i++)
+		{
+			if ((void *) op->opcode == dispatch_table[i])
+			{
+				return (ExprEvalOp) i;
+			}
+		}
+		elog(ERROR, "unknown opcode");
+	}
+#endif
+	return (ExprEvalOp) op->opcode;
+}
+
+/*
+ * Computes the value for a PARAM_EXEC parameter.
+ */
+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))
+	{
+		/*
+		 * XXX: Worthwhile to extract this into a separate opcode?
+		 */
+		ExecSetParamPlan(prm->execPlan, econtext);
+		/* ExecSetParamPlan should have processed this param... */
+		Assert(prm->execPlan == NULL);
+	}
+	*op->resvalue = prm->value;
+	*op->resnull = prm->isnull;
+}
+
+/*
+ * Computes the value for a PARAM_EXTERN parameter.
+ */
+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 */
+		/* XXX: separate opcode */
+		if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL)
+			(*paramInfo->paramFetch) (paramInfo, paramId);
+
+		if (likely(OidIsValid(prm->ptype)))
+		{
+			/* safety check in case hook did something unexpected */
+			/* XXX: assert instead? */
+			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 SQLValueFunction expressions.
+ */
+void
+ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
+{
+	FunctionCallInfoData fcinfo;
+	SQLValueFunction *svf = op->d.sqlvaluefunction.svf;
+
+	*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;
+	}
+}
+
+void
+ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op)
+{
+	/*
+	 * 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.
+	 */
+	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)
+{
+	ExecEvalRowNullInt(state, op, true);
+}
+
+/*
+ * Evaluate NullTest / IS NOT NULL for rows.
+ */
+void
+ExecEvalRowNotNull(ExprState *state, ExprEvalStep *op)
+{
+	ExecEvalRowNullInt(state, op, false);
+}
+
+static void
+ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op, 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;
+
+	if (isnull && checkisnull)
+	{
+		*op->resvalue = BoolGetDatum(true);
+		return;
+	}
+	else if (isnull)
+	{
+		*op->resvalue = BoolGetDatum(false);
+		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 = lookup_rowtype_tupdesc(tupType, tupTypmod);
+
+	/*
+	 * 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)
+			{
+				ReleaseTupleDesc(tupDesc);
+				*op->resvalue = BoolGetDatum(false);
+				return;
+			}
+		}
+		else
+		{
+			/* non-null field disproves IS NULL */
+			if (checkisnull)
+			{
+				ReleaseTupleDesc(tupDesc);
+				*op->resvalue = BoolGetDatum(false);
+				return;
+			}
+		}
+	}
+
+	ReleaseTupleDesc(tupDesc);
+	*op->resvalue = BoolGetDatum(true);
+}
+
+
+/*
+ * Evaluate ARRAY[] expressions.
+ */
+void
+ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op)
+{
+	ArrayType  *result;
+	ArrayExpr * arrayExpr = op->d.arrayexpr.arrayexpr;
+	Oid			element_type = arrayExpr->element_typeid;
+	int			ndims = 0;
+	int			dims[MAXDIM];
+	int			lbs[MAXDIM];
+	int			nelems = op->d.arrayexpr.nelems;
+
+	/* Set default values for result flags: non-null */
+	*op->resnull = false;
+
+	if (!arrayExpr->multidims)
+	{
+		/* Elements are presumably of scalar type */
+		Datum	   *dvalues = op->d.arrayexpr.elemvalues;
+		bool	   *dnulls = op->d.arrayexpr.elemnulls;
+
+		ndims = 1;
+
+		/* 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 */
+		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++)
+		{
+			bool		eisnull;
+			Datum		arraydatum;
+			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.
+ */
+void
+ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op)
+{
+	ArrayCoerceExpr *acoerce = op->d.arraycoerce.coerceexpr;
+	Datum		result;
+	FunctionCallInfoData locfcinfo;
+
+	/* input is in *step->resvalue/resnull */
+	result = *op->resvalue;
+	if (*op->resnull)
+	{
+		return;			/* 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) = op->d.arraycoerce.resultelemtype;
+		*op->resvalue = PointerGetDatum(array);
+		return;
+	}
+
+	/* Initialize function cache if first time through */
+	if (op->d.arraycoerce.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(acoerce->elemfuncid, op->d.arraycoerce.elemfunc);
+		fmgr_info_set_expr((Node *) acoerce, op->d.arraycoerce.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, op->d.arraycoerce.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;
+
+	*op->resvalue = array_map(&locfcinfo, op->d.arraycoerce.resultelemtype,
+							  op->d.arraycoerce.amstate);
+}
+
+/*
+ * Evaluate ROW() expressions.
+ *
+ * The individual columns have already been evaluated into
+ * op->d.row.elemvalues/nulls.
+ */
+void
+ExecEvalRow(ExprState *state, ExprEvalStep *op)
+{
+	HeapTuple	tuple;
+
+	/* Set default values for result flag: non-null */
+	*op->resnull = false;
+
+	/* preset to nulls in case rowtype has some later-added columns */
+	/* FIXME: unclear what's that supposed to do */
+	/* memset(isnull, true, natts * sizeof(bool)); */
+
+	/* 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);
+}
+
+/*
+ * Evaluate GREATEST()/LEAST() type 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)
+{
+	int off;
+	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;
+
+	/* set at initialization */
+	Assert(fcinfo->argnull[0] == false);
+	Assert(fcinfo->argnull[1] == false);
+
+	*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->isnull = false;
+			fcinfo->arg[0] = *op->resvalue;
+			fcinfo->arg[1] = values[off];
+			Assert(!fcinfo->isnull);
+
+			cmpresult = DatumGetInt32(FunctionCallInvoke(fcinfo));
+
+			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.
+ */
+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;
+
+	tupDatum = *op->resvalue;
+
+	/* this test covers the isDone exception too: */
+	if (*op->resnull)
+		return;
+
+	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 ExecEvalScalarVar, 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;
+
+	*op->resvalue = heap_getattr(&tmptup,
+								 fieldnum,
+								 tupDesc,
+								 op->resnull);
+}
+
+/*
+ * Deform tuple before evaluating the individual new values as part of a
+ * FieldStore expression.
+ */
+void
+ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	/* Lookup tupdesc if first time through or after rescan */
+	get_cached_rowtype(op->d.fieldstore.fstore->resulttype, -1,
+					   &op->d.fieldstore.argdesc, econtext);
+
+	if (*op->resnull)
+	{
+		/* Convert null input tuple into an all-nulls row */
+		memset(op->d.fieldstore.nulls, true,
+			   MaxTupleAttributeNumber * 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, op->d.fieldstore.argdesc,
+						  op->d.fieldstore.values,
+						  op->d.fieldstore.nulls);
+	}
+}
+
+/*
+ * Compute the new wholerow datum after each individual row part of a
+ * FieldStore expression has been evaluated.
+ */
+void
+ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	HeapTuple	tuple;
+
+	/* Lookup tupdesc if first time through or after rescan */
+	/*
+	 * FIXME: this currently can be hit due to invalidations, but effectively
+	 * is broken.
+	 */
+	get_cached_rowtype(op->d.fieldstore.fstore->resulttype, -1,
+					   &op->d.fieldstore.argdesc, econtext);
+
+	/* Result is never null */
+	*op->resnull = false;
+
+	tuple = heap_form_tuple(op->d.fieldstore.argdesc,
+							op->d.fieldstore.values,
+							op->d.fieldstore.nulls);
+
+	*op->resvalue = HeapTupleGetDatum(tuple);
+}
+
+void
+ExecEvalArrayRefOld(ExprState *state, ExprEvalStep *op)
+{
+	ArrayRefState *arefstate = op->d.arrayref.state;
+
+	if (*op->resnull)
+	{
+		arefstate->prevnull = true;
+		arefstate->prevvalue = (Datum) 0;
+	}
+	else if (arefstate->numlower == 0)
+	{
+		arefstate->prevvalue =
+			array_get_element(*op->resvalue,
+							  arefstate->numupper,
+							  arefstate->upperindex,
+							  arefstate->refattrlength,
+							  arefstate->refelemlength,
+							  arefstate->refelembyval,
+							  arefstate->refelemalign,
+							  &arefstate->prevnull);
+	}
+	else
+	{
+		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);
+	}
+}
+
+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 &&
+		(*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)
+	{
+		*op->resvalue =
+			array_set_element(*op->resvalue, arefstate->numupper,
+							  arefstate->upperindex,
+							  arefstate->replacevalue,
+							  arefstate->replacenull,
+							  arefstate->refattrlength,
+							  arefstate->refelemlength,
+							  arefstate->refelembyval,
+							  arefstate->refelemalign);
+	}
+	else
+	{
+		*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);
+	}
+}
+
+void
+ExecEvalArrayRefFetch(ExprState *state, ExprEvalStep *op)
+{
+	ArrayRefState *arefstate = op->d.arrayref.state;
+
+	if (arefstate->numlower == 0)
+	{
+
+		*op->resvalue =
+			array_get_element(*op->resvalue,
+							  arefstate->numupper,
+							  arefstate->upperindex,
+							  arefstate->refattrlength,
+							  arefstate->refelemlength,
+							  arefstate->refelembyval,
+							  arefstate->refelemalign,
+							  op->resnull);
+	}
+	else
+	{
+		*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);
+	}
+}
+
+bool
+ExecEvalArrayRefCheckSubscript(ExprState *state, ExprEvalStep *op)
+{
+	ArrayRefState *arefstate = op->d.arrayref_checksubscript.state;
+	int off = op->d.arrayref_checksubscript.off;
+	bool *nulls;
+	Datum *values;
+	int *indexes;
+
+	if (op->d.arrayref_checksubscript.isupper)
+	{
+		nulls = arefstate->uppernull;
+		values = arefstate->upper;
+		indexes = arefstate->upperindex;
+	}
+	else
+	{
+		nulls = arefstate->lowernull;
+		values = arefstate->lower;
+		indexes = arefstate->lowerindex;
+	}
+
+	/* If any index expr yields NULL, result is NULL or error */
+	if (nulls[off])
+	{
+		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 */
+	indexes[off] = values[off];
+
+	return true;
+}
+
+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;
+
+	tupDatum = *op->resvalue;
+
+	/* this test covers the isDone exception too: */
+	if (*op->resnull)
+		return;
+
+	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 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) == 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);
+
+}
+
+/*
+ * 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.
+ */
+void
+ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
+{
+	ScalarArrayOpExpr *opexpr = op->d.scalararrayop.opexpr;
+	bool		useOr = opexpr->useOr;
+	FunctionCallInfo fcinfo = op->d.scalararrayop.fcinfo_data;
+	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;
+	}
+
+	/* no "bad" nulls, 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->resnull = false;
+		*op->resvalue = BoolGetDatum(!useOr);
+		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;
+
+	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->resnull = resultnull;
+	*op->resvalue = result;
+}
+
+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)));
+}
+
+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.
+ */
+void
+ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
+{
+	XmlExpr    *xexpr = op->d.xmlexpr.xexpr;
+	Datum		value;
+	ListCell   *arg;
+	ListCell   *narg;
+	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 (list_length(values) > 0)
+				{
+					*op->resnull = false;
+					*op->resvalue = PointerGetDatum(xmlconcat(values));
+				}
+			}
+			break;
+
+		case IS_XMLFOREST:
+			{
+				Datum	   *argvalue = op->d.xmlexpr.named_argvalue;
+				bool	   *argnull = op->d.xmlexpr.named_argnull;
+				StringInfoData buf;
+
+				initStringInfo(&buf);
+
+				i = 0;
+				forboth(arg, xexpr->named_args, narg, xexpr->arg_names)
+				{
+					char	   *argname = strVal(lfirst(narg));
+					Expr	   *e = (Expr *) lfirst(arg);
+
+					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)
+				{
+					pfree(buf.data);
+				}
+				else
+				{
+					text	   *result;
+
+					result = cstring_to_text_with_len(buf.data, buf.len);
+					pfree(buf.data);
+
+					*op->resvalue = PointerGetDatum(result);
+				}
+			}
+			break;
+
+		case IS_XMLELEMENT:
+			*op->resnull = false;
+			*op->resvalue = PointerGetDatum(
+				xmlelement(xexpr,
+						   op->d.xmlexpr.named_argnull,
+						   op->d.xmlexpr.named_argvalue,
+						   op->d.xmlexpr.argnull,
+						   op->d.xmlexpr.argvalue));
+			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 = DatumGetTextP(value);
+
+				if (argnull[1])		/* probably can't happen */
+					return;
+				value = argvalue[1];
+				preserve_whitespace = DatumGetBool(value);
+
+				*op->resnull = false;
+				*op->resvalue = PointerGetDatum(
+					xmlparse(data, xexpr->xmloption, preserve_whitespace));
+			}
+			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 = DatumGetTextP(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 = DatumGetTextP(argvalue[1]);
+
+				/* FIXME: missing NULL check??? */
+				Assert(!argnull[2]);
+				standalone = DatumGetInt32(argvalue[2]);
+
+				*op->resnull = false;
+				*op->resvalue = PointerGetDatum(xmlroot(data,
+														version,
+														standalone));
+			}
+			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->resnull = false;
+				*op->resvalue = PointerGetDatum(
+					xmltotext_with_xmloption(DatumGetXmlP(value),
+											 xexpr->xmloption));
+			}
+			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])
+					break;
+
+				value = argvalue[0];
+				*op->resnull = false;
+				*op->resvalue =
+					BoolGetDatum(xml_is_document(DatumGetXmlP(value)));
+			}
+			break;
+		default:
+			elog(ERROR, "unrecognized XML operation");
+	}
+}
+
+/*
+ * 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;
+	int			attnum = 0;
+	Bitmapset  *grouped_cols = op->d.grouping_func.parent->grouped_cols;
+	ListCell   *lc;
+
+	*op->resnull = false;
+
+	foreach(lc, op->d.grouping_func.clauses)
+	{
+		attnum = lfirst_int(lc);
+
+		result = result << 1;
+
+		if (!bms_is_member(attnum, grouped_cols))
+			result = result | 1;
+	}
+
+	*op->resvalue = Int32GetDatum(result);
+}
+
+/*
+ * Hand off evaluation of a subplan to nodeSubplan.c
+ */
+void
+ExecEvalSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	SubPlanState *sstate = op->d.subplan.sstate;
+
+	*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;
+
+	*op->resvalue = ExecAlternativeSubPlan(asstate, econtext, op->resnull);
+}
+
+/*
+ * Evaluate a wholerow Var expression.
+ */
+void
+ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	Var		   *variable = op->d.wholerow.var;
+	TupleTableSlot *slot;
+	TupleDesc	output_tupdesc;
+	MemoryContext oldcontext;
+	bool		needslow = false;
+	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);
+
+	/*
+	 * First time through, perform initialization.
+	 *
+	 * XXX: It'd be great if this could be moved to the the expression
+	 * initialization phase, but due to using slots that's currently not
+	 * feasible.
+	 */
+	if (op->d.wholerow.first)
+	{
+		/*
+		 * 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 */
+		op->d.wholerow.tupdesc = BlessTupleDesc(output_tupdesc);
+
+		op->d.wholerow.first = false;
+	}
+
+	if (needslow)
+	{
+		TupleDesc tupleDesc = slot->tts_tupleDescriptor;
+		TupleDesc var_tupdesc = op->d.wholerow.tupdesc;
+		int i;
+
+		tuple = ExecFetchSlotTuple(slot);
+
+
+		/* 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, op->d.wholerow.tupdesc->tdtypeid);
+	HeapTupleHeaderSetTypMod(dtuple, op->d.wholerow.tupdesc->tdtypmod);
+
+	*op->resnull = false;
+	*op->resvalue = PointerGetDatum(dtuple);
+}
+
+/*
+ * 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;
+}
+
+/*
+ * 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;
+}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index f5cd65d8a0..ede675fa61 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1246,7 +1246,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 
 		resultRelInfo->ri_TrigFunctions = (FmgrInfo *)
 			palloc0(n * sizeof(FmgrInfo));
-		resultRelInfo->ri_TrigWhenExprs = (List **)
+		resultRelInfo->ri_TrigWhenExprs = (ExprState **)
 			palloc0(n * sizeof(List *));
 		if (instrument_options)
 			resultRelInfo->ri_TrigInstrument = InstrAlloc(n, instrument_options);
@@ -1689,7 +1689,6 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	ConstrCheck *check = rel->rd_att->constr->check;
 	ExprContext *econtext;
 	MemoryContext oldContext;
-	List	   *qual;
 	int			i;
 
 	/*
@@ -1701,13 +1700,15 @@ 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 */
+			List	   *qual;
+
+			/* ExecPrepareQual wants implicit-AND form */
 			qual = make_ands_implicit(stringToNode(check[i].ccbin));
-			resultRelInfo->ri_ConstraintExprs[i] = (List *)
-				ExecPrepareExpr((Expr *) qual, estate);
+			resultRelInfo->ri_ConstraintExprs[i] =
+				ExecPrepareCheck(qual, estate);
 		}
 		MemoryContextSwitchTo(oldContext);
 	}
@@ -1724,14 +1725,14 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	/* And evaluate the constraints */
 	for (i = 0; i < ncheck; i++)
 	{
-		qual = resultRelInfo->ri_ConstraintExprs[i];
+		ExprState  *qual = 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(qual, econtext))
 			return check[i].ccname;
 	}
 
@@ -1759,8 +1760,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);
 	}
 
 	/*
@@ -1776,7 +1776,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);
 }
 
 /*
@@ -1956,11 +1956,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 4566219ca8..d250627bbe 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * execQual.c
- *	  Routines to evaluate qualification and targetlist expressions
+ *	  Former 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
@@ -12,28 +12,6 @@
  *
  *-------------------------------------------------------------------------
  */
-/*
- *	 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"
@@ -62,1090 +40,18 @@
 
 
 /* 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 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(FuncExprState *fcache,
+static void ExecPrepareTuplestoreResult(SetExprState *sexpr,
 							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
-			{
-				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
@@ -1267,11 +173,11 @@ GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull)
 }
 
 /*
- * init_fcache - initialize a FuncExprState node during first use
+ * init_sexpr - initialize a SetExprState node during first use
  */
 static void
-init_fcache(Oid foid, Oid input_collation, FuncExprState *fcache,
-			MemoryContext fcacheCxt, bool allowSRF, bool needDescForSRF)
+init_sexpr(Oid foid, Oid input_collation, SetExprState *sexpr,
+		   MemoryContext sexprCxt, bool allowSRF, bool needDescForSRF)
 {
 	AclResult	aclresult;
 
@@ -1287,7 +193,7 @@ init_fcache(Oid foid, Oid input_collation, FuncExprState *fcache,
 	 * server has been compiled with FUNC_MAX_ARGS smaller than some functions
 	 * declared in pg_proc?
 	 */
-	if (list_length(fcache->args) > FUNC_MAX_ARGS)
+	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",
@@ -1296,45 +202,45 @@ init_fcache(Oid foid, Oid input_collation, FuncExprState *fcache,
 						   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));
+	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(fcache->fcinfo_data, &(fcache->func),
-							 list_length(fcache->args),
+	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 (fcache->func.fn_retset && !allowSRF)
+	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, ExecInitExpr should have marked the fcache correctly */
-	Assert(fcache->func.fn_retset == fcache->funcReturnsSet);
+	/* Otherwise, ExecInitExpr should have marked the sexpr correctly */
+	Assert(sexpr->func.fn_retset == sexpr->funcReturnsSet);
 
 	/* If function returns set, prepare expected tuple descriptor */
-	if (fcache->func.fn_retset && needDescForSRF)
+	if (sexpr->func.fn_retset && needDescForSRF)
 	{
 		TypeFuncClass functypclass;
 		Oid			funcrettype;
 		TupleDesc	tupdesc;
 		MemoryContext oldcontext;
 
-		functypclass = get_expr_result_type(fcache->func.fn_expr,
+		functypclass = get_expr_result_type(sexpr->func.fn_expr,
 											&funcrettype,
 											&tupdesc);
 
-		/* Must save tupdesc in fcache's context */
-		oldcontext = MemoryContextSwitchTo(fcacheCxt);
+		/* 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 */
-			fcache->funcResultDesc = CreateTupleDescCopy(tupdesc);
-			fcache->funcReturnsTuple = true;
+			sexpr->funcResultDesc = CreateTupleDescCopy(tupdesc);
+			sexpr->funcReturnsTuple = true;
 		}
 		else if (functypclass == TYPEFUNC_SCALAR)
 		{
@@ -1346,110 +252,55 @@ init_fcache(Oid foid, Oid input_collation, FuncExprState *fcache,
 							   funcrettype,
 							   -1,
 							   0);
-			fcache->funcResultDesc = tupdesc;
-			fcache->funcReturnsTuple = false;
+			sexpr->funcResultDesc = tupdesc;
+			sexpr->funcReturnsTuple = false;
 		}
 		else if (functypclass == TYPEFUNC_RECORD)
 		{
 			/* This will work if function doesn't need an expectedDesc */
-			fcache->funcResultDesc = NULL;
-			fcache->funcReturnsTuple = true;
+			sexpr->funcResultDesc = NULL;
+			sexpr->funcReturnsTuple = true;
 		}
 		else
 		{
 			/* Else, we will fail if function needs an expectedDesc */
-			fcache->funcResultDesc = NULL;
+			sexpr->funcResultDesc = NULL;
 		}
 
 		MemoryContextSwitchTo(oldcontext);
 	}
 	else
-		fcache->funcResultDesc = NULL;
+		sexpr->funcResultDesc = NULL;
 
 	/* Initialize additional state */
-	fcache->funcResultStore = NULL;
-	fcache->funcResultSlot = NULL;
-	fcache->shutdown_reg = false;
+	sexpr->funcResultStore = NULL;
+	sexpr->funcResultSlot = NULL;
+	sexpr->shutdown_reg = false;
 }
 
 /*
- * callback function in case a FuncExpr returning a set needs to be shut down
- * before it has been run to completion
+ * callback function in case a SetExpr needs to be shut down before it has
+ * been run to completion
  */
 static void
-ShutdownFuncExpr(Datum arg)
+ShutdownSetExpr(Datum arg)
 {
-	FuncExprState *fcache = (FuncExprState *) DatumGetPointer(arg);
+	SetExprState *sexpr = (SetExprState *) DatumGetPointer(arg);
 
 	/* If we have a slot, make sure it's let go of any tuplestore pointer */
-	if (fcache->funcResultSlot)
-		ExecClearTuple(fcache->funcResultSlot);
+	if (sexpr->funcResultSlot)
+		ExecClearTuple(sexpr->funcResultSlot);
 
 	/* Release any open tuplestore */
-	if (fcache->funcResultStore)
-		tuplestore_end(fcache->funcResultStore);
-	fcache->funcResultStore = NULL;
+	if (sexpr->funcResultStore)
+		tuplestore_end(sexpr->funcResultStore);
+	sexpr->funcResultStore = NULL;
 
 	/* Clear any active set-argument state */
-	fcache->setArgsValid = false;
+	sexpr->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;
+	sexpr->shutdown_reg = false;
 }
 
 /*
@@ -1486,27 +337,27 @@ ExecEvalFuncArgs(FunctionCallInfo fcinfo,
  * returned the expected tuple descriptor.
  */
 static void
-ExecPrepareTuplestoreResult(FuncExprState *fcache,
+ExecPrepareTuplestoreResult(SetExprState *sexpr,
 							ExprContext *econtext,
 							Tuplestorestate *resultStore,
 							TupleDesc resultDesc)
 {
-	fcache->funcResultStore = resultStore;
+	sexpr->funcResultStore = resultStore;
 
-	if (fcache->funcResultSlot == NULL)
+	if (sexpr->funcResultSlot == NULL)
 	{
 		/* Create a slot so we can read data out of the tuplestore */
 		TupleDesc	slotDesc;
 		MemoryContext oldcontext;
 
-		oldcontext = MemoryContextSwitchTo(fcache->func.fn_mcxt);
+		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 (fcache->funcResultDesc)
-			slotDesc = fcache->funcResultDesc;
+		if (sexpr->funcResultDesc)
+			slotDesc = sexpr->funcResultDesc;
 		else if (resultDesc)
 		{
 			/* don't assume resultDesc is long-lived */
@@ -1521,7 +372,7 @@ ExecPrepareTuplestoreResult(FuncExprState *fcache,
 			slotDesc = NULL;	/* keep compiler quiet */
 		}
 
-		fcache->funcResultSlot = MakeSingleTupleTableSlot(slotDesc);
+		sexpr->funcResultSlot = MakeSingleTupleTableSlot(slotDesc);
 		MemoryContextSwitchTo(oldcontext);
 	}
 
@@ -1531,8 +382,8 @@ ExecPrepareTuplestoreResult(FuncExprState *fcache,
 	 */
 	if (resultDesc)
 	{
-		if (fcache->funcResultDesc)
-			tupledesc_match(fcache->funcResultDesc, resultDesc);
+		if (sexpr->funcResultDesc)
+			tupledesc_match(sexpr->funcResultDesc, resultDesc);
 
 		/*
 		 * If it is a dynamically-allocated TupleDesc, free it: it is
@@ -1544,12 +395,12 @@ ExecPrepareTuplestoreResult(FuncExprState *fcache,
 	}
 
 	/* Register cleanup callback if we didn't already */
-	if (!fcache->shutdown_reg)
+	if (!sexpr->shutdown_reg)
 	{
 		RegisterExprContextCallback(econtext,
-									ShutdownFuncExpr,
-									PointerGetDatum(fcache));
-		fcache->shutdown_reg = true;
+									ShutdownSetExpr,
+									PointerGetDatum(sexpr));
+		sexpr->shutdown_reg = true;
 	}
 }
 
@@ -1611,7 +462,7 @@ tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc)
  * functions (the planner is supposed to have separated evaluation for those).
  */
 Datum
-ExecMakeFunctionResultSet(FuncExprState *fcache,
+ExecMakeFunctionResultSet(SetExprState *fcache,
 						  ExprContext *econtext,
 						  bool *isNull,
 						  ExprDoneCond *isDone)
@@ -1630,34 +481,6 @@ restart:
 	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.
@@ -1773,7 +596,7 @@ restart:
 				if (!fcache->shutdown_reg)
 				{
 					RegisterExprContextCallback(econtext,
-												ShutdownFuncExpr,
+												ShutdownSetExpr,
 												PointerGetDatum(fcache));
 					fcache->shutdown_reg = true;
 				}
@@ -1811,63 +634,100 @@ restart:
 }
 
 /*
- *		ExecMakeFunctionResultNoSets
- *
- * Evaluate a function or operator node with a non-set-returning function.
- * Assumes init_fcache() already done.  Hand-tuned for speed.
+ * Prepare function call in nodeProjectSet.c (taretlist SRF) for execution.
  */
-static Datum
-ExecMakeFunctionResultNoSets(FuncExprState *fcache,
-							 ExprContext *econtext,
-							 bool *isNull)
+SetExprState *
+ExecInitFunctionResultSet(Expr *expr, ExprContext *econtext, PlanState *parent)
 {
-	ListCell   *arg;
-	Datum		result;
-	FunctionCallInfo fcinfo;
-	PgStat_FunctionCallUsage fcusage;
-	int			i;
+	SetExprState *state = makeNode(SetExprState);
+	ListCell *lc;
 
-	/* 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++;
-	}
+	state->funcReturnsSet = true;
+	state->expr = expr;
+	state->func.fn_oid = InvalidOid;
 
 	/*
-	 * If function is strict, and there are any NULL arguments, skip calling
-	 * the function and return NULL.
+	 * Initialize metadata.  The expression node could be either a FuncExpr or
+	 * an OpExpr.
 	 */
-	if (fcache->func.fn_strict)
+	if (IsA(expr, FuncExpr))
 	{
-		while (--i >= 0)
+		FuncExpr   *func = (FuncExpr *) expr;
+
+		foreach(lc, func->args)
 		{
-			if (fcinfo->argnull[i])
-			{
-				*isNull = true;
-				return (Datum) 0;
-			}
+			state->args = lappend(state->args, ExecInitExpr(lfirst(lc), parent));
 		}
+
+		init_sexpr(func->funcid, func->inputcollid, state,
+				   econtext->ecxt_per_query_memory, true, true);
+	}
+	else if (IsA(expr, OpExpr))
+	{
+		OpExpr	   *op = (OpExpr *) expr;
+
+		foreach(lc, op->args)
+		{
+			state->args = lappend(state->args, ExecInitExpr(lfirst(lc), 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 otherwise */
+	Assert(state->func.fn_retset);
+
+	return state;
+}
+
+/*
+ * Prepare function call in nodeFunctionscan.c (FROM function/ROWS FROM) for
+ * execution.
+ */
+SetExprState *
+ExecInitTableFunctionResult(Expr *expr, ExprContext *econtext, PlanState *parent)
+{
+	SetExprState *state = makeNode(SetExprState);
+	ListCell *lc;
+
+	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; the only difference is that we
+	 * don't get a chance to pass a special ReturnSetInfo to any functions
+	 * buried in the expression.
+	 */
+	if (IsA(expr, FuncExpr))
+	{
+		FuncExpr   *func = (FuncExpr *) expr;
+
+		state->funcReturnsSet = func->funcretset;
+
+		foreach(lc, func->args)
+		{
+			state->args = lappend(state->args, ExecInitExpr(lfirst(lc), parent));
+		}
+
+		init_sexpr(func->funcid, func->inputcollid, state,
+				   econtext->ecxt_per_query_memory, func->funcretset, false);
+	}
+	else
+	{
+		state->elidedFuncState = ExecInitExpr(expr, parent);
 	}
 
-	pgstat_init_function_usage(fcinfo, &fcusage);
-
-	fcinfo->isnull = false;
-	result = FunctionCallInvoke(fcinfo);
-	*isNull = fcinfo->isnull;
-
-	pgstat_end_function_usage(&fcusage, true);
-
-	return result;
+	return state;
 }
 
 
@@ -1878,7 +738,7 @@ ExecMakeFunctionResultNoSets(FuncExprState *fcache,
  * object.
  */
 Tuplestorestate *
-ExecMakeTableFunctionResult(ExprState *funcexpr,
+ExecMakeTableFunctionResult(SetExprState *setexpr,
 							ExprContext *econtext,
 							MemoryContext argContext,
 							TupleDesc expectedDesc,
@@ -1895,12 +755,11 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 	HeapTupleData tmptup;
 	MemoryContext callerContext;
 	MemoryContext oldcontext;
-	bool		direct_function_call;
 	bool		first_time = true;
 
 	callerContext = CurrentMemoryContext;
 
-	funcrettype = exprType((Node *) funcexpr->expr);
+	funcrettype = exprType((Node *) setexpr->expr);
 
 	returnsTuple = type_is_rowtype(funcrettype);
 
@@ -1923,7 +782,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 	rsinfo.setDesc = NULL;
 
 	/*
-	 * Normally the passed expression tree will be a FuncExprState, since the
+	 * 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
@@ -1932,30 +791,15 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 	 * 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))
+	if (!setexpr->elidedFuncState)
 	{
-		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,
+		returnsSet = setexpr->funcReturnsSet;
+		InitFunctionCallInfoData(fcinfo, &(setexpr->func),
+								 list_length(setexpr->args),
+								 setexpr->fcinfo_data.fncollation,
 								 NULL, (Node *) &rsinfo);
 
 		/*
@@ -1970,7 +814,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 		 */
 		MemoryContextReset(argContext);
 		oldcontext = MemoryContextSwitchTo(argContext);
-		ExecEvalFuncArgs(&fcinfo, fcache->args, econtext);
+		ExecEvalFuncArgs(&fcinfo, setexpr->args, econtext);
 		MemoryContextSwitchTo(oldcontext);
 
 		/*
@@ -1978,7 +822,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 		 * calling the function and act like it returned NULL (or an empty
 		 * set, in the returns-set case).
 		 */
-		if (fcache->func.fn_strict)
+		if (setexpr->func.fn_strict)
 		{
 			int			i;
 
@@ -1991,8 +835,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 	}
 	else
 	{
-		/* Treat funcexpr as a generic expression */
-		direct_function_call = false;
+		/* Treat setexpr as a generic expression */
 		InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
 	}
 
@@ -2019,7 +862,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 		ResetExprContext(econtext);
 
 		/* Call the function or expression one time */
-		if (direct_function_call)
+		if (!setexpr->elidedFuncState)
 		{
 			pgstat_init_function_usage(&fcinfo, &fcusage);
 
@@ -2032,7 +875,8 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 		}
 		else
 		{
-			result = ExecEvalExpr(funcexpr, econtext, &fcinfo.isnull);
+			result =
+				ExecEvalExpr(setexpr->elidedFuncState, econtext, &fcinfo.isnull);
 			rsinfo.isDone = ExprSingleResult;
 		}
 
@@ -2210,2911 +1054,6 @@ no_function_result:
 	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 = DatumGetTextP(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 = DatumGetTextP(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 = DatumGetTextP(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!)
  */
@@ -5136,177 +1075,11 @@ ExecCleanTargetListLength(List *targetlist)
 
 	foreach(tl, targetlist)
 	{
-		TargetEntry *curTle = castNode(TargetEntry, lfirst(tl));
+		TargetEntry *curTle = (TargetEntry *) lfirst(tl);
 
+		Assert(IsA(curTle, TargetEntry));
 		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/execScan.c b/src/backend/executor/execScan.c
index 65196795d7..138a596743 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;
 
 	/*
@@ -187,7 +187,7 @@ ExecScan(ScanState *node,
 		 * when the qual is nil ... saves only a few cycles, but they add up
 		 * ...
 		 */
-		if (!qual || ExecQual(qual, econtext, false))
+		if (!qual || ExecQual(qual, econtext))
 		{
 			/*
 			 * Found a satisfactory scan tuple.
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index d205101b89..5bc89f8554 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -470,136 +470,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 */
-			ExecGetLastAttnums((Node *) variable,
-							   &projInfo->pi_lastOuterVar,
-							   &projInfo->pi_lastInnerVar,
-							   &projInfo->pi_lastScanVar);
-		}
-	}
-	projInfo->pi_targetlist = exprlist;
-	projInfo->pi_numSimpleVars = numSimpleVars;
-	projInfo->pi_directMap = directMap;
-
-	return projInfo;
-}
-
 /*
  * get_last_attnums_walker: expression walker for ExecBuildProjectionInfo
  *
@@ -680,9 +550,10 @@ ExecAssignProjectionInfo(PlanState *planstate,
 						 TupleDesc inputDesc)
 {
 	planstate->ps_ProjInfo =
-		ExecBuildProjectionInfo(planstate->targetlist,
+		ExecBuildProjectionInfo(planstate->plan->targetlist,
 								planstate->ps_ExprContext,
 								planstate->ps_ResultTupleSlot,
+								planstate,
 								inputDesc);
 }
 
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index aa08152350..2f3355636d 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
@@ -2506,13 +2506,10 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	 * 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.
+	 * FIXME: adjust comment (now added in projection)
 	 */
-	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.
@@ -2724,7 +2721,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 +3021,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 +3202,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 c1aa9f13bd..0d90172731 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -238,7 +238,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);
@@ -457,7 +457,7 @@ BitmapHeapRecheck(BitmapHeapScanState *node, TupleTableSlot *slot)
 
 	ResetExprContext(econtext);
 
-	return ExecQual(node->bitmapqualorig, econtext, false);
+	return ExecQual(node->bitmapqualorig, econtext);
 }
 
 /* ----------------------------------------------------------------
@@ -608,15 +608,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 8f4e0f527e..bed7949c5a 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 d464748290..57a4ce3218 100644
--- a/src/backend/executor/nodeCustom.c
+++ b/src/backend/executor/nodeCustom.c
@@ -49,11 +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,
+	css->ss.ps.qual =
+		ExecInitQual(cscan->scan.plan.qual,
 					 (PlanState *) css);
 
 	/* tuple table initialization */
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 3b6d1390eb..9ae1561404 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 972022784d..250efcb449 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,11 +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,
+	scanstate->ss.ps.qual =
+		ExecInitQual(node->scan.plan.qual,
 					 (PlanState *) scanstate);
 
 	scanstate->funcstates = palloc(nfuncs * sizeof(FunctionScanPerFuncState));
@@ -361,7 +358,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 32c97d390e..1e5b1b7675 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/nodeGroup.c b/src/backend/executor/nodeGroup.c
index 66c095bc72..af9ba4905e 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 e695d8834b..cfc6b96093 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 c50d93f43d..1aa133925f 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,13 @@ 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, ExecInitExpr(linitial(hclause->args), (PlanState *) hjstate));
+		rclauses = lappend(rclauses, ExecInitExpr(lsecond(hclause->args), (PlanState *) hjstate));
 
-		lclauses = lappend(lclauses, linitial(fstate->args));
-		rclauses = lappend(rclauses, lsecond(fstate->args));
 		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 4a7f39a7c7..54f6a873b3 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -179,7 +179,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);
@@ -456,15 +456,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 0a9dfdbaf3..ab1add9757 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -125,7 +125,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);
@@ -244,7 +244,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);
@@ -364,7 +364,7 @@ IndexRecheck(IndexScanState *node, TupleTableSlot *slot)
 
 	ResetExprContext(econtext);
 
-	return ExecQual(node->indexqualorig, econtext, false);
+	return ExecQual(node->indexqualorig, econtext);
 }
 
 
@@ -870,18 +870,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 105e2dcedb..62784af304 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 95e158970c..127a64eb31 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1151,7 +1151,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;
@@ -1270,7 +1270,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);
@@ -1645,7 +1645,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;
@@ -1765,8 +1764,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		foreach(ll, 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);
 		}
@@ -1805,7 +1804,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			foreach(ll, mapped_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);
@@ -1839,8 +1838,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.
@@ -1849,11 +1849,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++;
 		}
@@ -1870,17 +1868,15 @@ 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,
-									 resultRelInfo->ri_RelationDesc->rd_att);
+				ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+										resultRelInfo->ri_RelationDesc->rd_att);
 			resultRelInfo++;
 		}
 	}
@@ -1905,7 +1901,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 */
@@ -1931,11 +1926,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 */
@@ -1943,10 +1937,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 cac7ba1b9b..53977e0b32 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 eae0f1dad9..60862cf142 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"
 
 
@@ -122,7 +123,6 @@ ExecProjectSRF(ProjectSetState *node, bool continuing)
 	bool		hassrf PG_USED_FOR_ASSERTS_ONLY = false;
 	bool		hasresult;
 	int			argno;
-	ListCell   *lc;
 
 	ExecClearTuple(resultSlot);
 
@@ -133,10 +133,9 @@ ExecProjectSRF(ProjectSetState *node, bool continuing)
 	node->pending_srf_tuples = false;
 
 	hasresult = false;
-	argno = 0;
-	foreach(lc, node->ps.targetlist)
+	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,10 @@ 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 +201,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 +228,6 @@ 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);
 	Assert(node->plan.qual == NIL);
 
 	/*
@@ -254,9 +247,38 @@ ExecInitProjectSet(ProjectSet *node, EState *estate, int eflags)
 
 	/* Create workspace for per-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. Can't use
+	 * ExecBuildProjectionInfo here, since that doesn't deal with
+	 * SRFs. Instead evaluate all expressions individually, 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
+		{
+			state->elems[off] = (Node *) ExecInitExpr(expr, &state->ps);
+			Assert(!expression_returns_set((Node *) expr));
+		}
+
+		off++;
+	}
 	return state;
 }
 
diff --git a/src/backend/executor/nodeResult.c b/src/backend/executor/nodeResult.c
index b5b50b21e9..a753a53419 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 d38265e810..0247bd2347 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->ss.ps.qual =
+		ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
 
-	scanstate->args = (List *)
-		ExecInitExpr((Expr *) tsc->args,
-					 (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 e61895de0a..5680464fa2 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 8f419a13ac..01383fdc33 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;
@@ -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) */
@@ -800,27 +791,27 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 		 * 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;
+			Assert(IsA(subplan->testexpr, BoolExpr));
+			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 +819,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 +874,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,
+												   parent->ps_ExprContext,
 												   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 +912,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 +1089,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 +1140,21 @@ 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)
+	{
+		SubPlanState *sp = ExecInitSubPlan(lfirst(lc), parent);
+		asstate->subplans =
+			lappend(asstate->subplans, sp);
+		parent->subPlan = lappend(parent->subPlan, sp);
+	}
 
 	/*
 	 * Select the one to be used.  For this, we need an estimate of the number
@@ -1209,7 +1192,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 230a96f9d2..ae184700a6 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/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index 13ed886577..1763be5cf0 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -52,7 +52,8 @@ static TupleTableSlot *TidNext(TidScanState *node);
 static void
 TidListCreate(TidScanState *tidstate)
 {
-	List	   *evalList = tidstate->tss_tidquals;
+	TidScan	   *node = (TidScan *) tidstate->ss.ps.plan;
+	List	   *evalList = node->tidquals;
 	ExprContext *econtext = tidstate->ss.ps.ps_ExprContext;
 	BlockNumber nblocks;
 	ItemPointerData *tidList;
@@ -81,28 +82,29 @@ TidListCreate(TidScanState *tidstate)
 
 	foreach(l, evalList)
 	{
-		ExprState  *exstate = (ExprState *) lfirst(l);
-		Expr	   *expr = exstate->expr;
+		Expr	   *expr = (Expr*) lfirst(l);
 		ItemPointer itemptr;
 		bool		isNull;
 
 		if (is_opclause(expr))
 		{
-			FuncExprState *fexstate = (FuncExprState *) exstate;
+			FuncExpr   *fex = (FuncExpr *) expr;
+			ExprState  *exprstate;
 			Node	   *arg1;
 			Node	   *arg2;
 
 			arg1 = get_leftop(expr);
 			arg2 = get_rightop(expr);
 			if (IsCTIDVar(arg1))
-				exstate = (ExprState *) lsecond(fexstate->args);
+				exprstate = ExecInitExpr((Expr *) lsecond(fex->args),
+										  &tidstate->ss.ps);
 			else if (IsCTIDVar(arg2))
-				exstate = (ExprState *) linitial(fexstate->args);
-			else
+				exprstate = ExecInitExpr((Expr *) linitial(fex->args),
+										  &tidstate->ss.ps);
 				elog(ERROR, "could not identify CTID variable");
 
 			itemptr = (ItemPointer)
-				DatumGetPointer(ExecEvalExprSwitchContext(exstate,
+				DatumGetPointer(ExecEvalExprSwitchContext(exprstate,
 														  econtext,
 														  &isNull));
 			if (!isNull &&
@@ -121,7 +123,8 @@ TidListCreate(TidScanState *tidstate)
 		}
 		else if (expr && IsA(expr, ScalarArrayOpExpr))
 		{
-			ScalarArrayOpExprState *saexstate = (ScalarArrayOpExprState *) exstate;
+			ScalarArrayOpExpr *saex = (ScalarArrayOpExpr *) expr;
+			ExprState  *exprstate;
 			Datum		arraydatum;
 			ArrayType  *itemarray;
 			Datum	   *ipdatums;
@@ -129,8 +132,8 @@ TidListCreate(TidScanState *tidstate)
 			int			ndatums;
 			int			i;
 
-			exstate = (ExprState *) lsecond(saexstate->fxprstate.args);
-			arraydatum = ExecEvalExprSwitchContext(exstate,
+			exprstate = ExecInitExpr(lsecond(saex->args), &tidstate->ss.ps);
+			arraydatum = ExecEvalExprSwitchContext(exprstate,
 												   econtext,
 												   &isNull);
 			if (isNull)
@@ -470,16 +473,8 @@ 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);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c
index 9883a8b130..9ee776c4c3 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 2a123e8452..628bc9f00b 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 23b5b94985..d7616be065 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/util/clauses.c b/src/backend/optimizer/util/clauses.c
index b19380e1b1..42bba543e9 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 c2ad440013..f85d3c8111 100644
--- a/src/backend/utils/adt/domains.c
+++ b/src/backend/utils/adt/domains.c
@@ -122,7 +122,8 @@ 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 similar to the handling of CoerceToDomain nodes in
+ * execExpr.c/execInterpExpr.c.
  */
 static void
 domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
@@ -165,19 +166,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,
+					conResult = ExecEvalExprSwitchContext(con->check_exprstate,
 														  econtext,
 														  &conIsNull);
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index cf79f4126e..bef409694d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -6913,7 +6913,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;
 
@@ -6954,7 +6954,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 e8bce3b806..e5a5aa0101 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -71,7 +71,6 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
-#include "executor/executor.h"
 #include "executor/spi.h"
 #include "fmgr.h"
 #include "lib/stringinfo.h"
@@ -574,10 +573,9 @@ xmltotext_with_xmloption(xmltype *data, XmlOptionType xmloption_arg)
 
 
 xmltype *
-xmlelement(XmlExprState *xmlExpr, ExprContext *econtext)
+xmlelement(XmlExpr *xexpr, bool *named_argnull, Datum *named_argvalue, bool *argnull, Datum *argvalue)
 {
 #ifdef USE_LIBXML
-	XmlExpr    *xexpr = (XmlExpr *) xmlExpr->xprstate.expr;
 	xmltype    *result;
 	List	   *named_arg_strings;
 	List	   *arg_strings;
@@ -589,45 +587,40 @@ 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.  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.
 	 */
 	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 */
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 6992634c39..07cae44069 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -102,6 +102,7 @@ static TypeCacheEntry *firstDomainTypeEntry = NULL;
  * the DomainConstraintState nodes and applying ExecInitExpr to check_expr.
  * Such a state tree is not part of the DomainConstraintCache, but is
  * considered to belong to a DomainConstraintRef.
+ * FIXME: update.
  */
 struct DomainConstraintCache
 {
@@ -779,8 +780,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 +860,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 +948,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);
 	}
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
new file mode 100644
index 0000000000..44d09ba09e
--- /dev/null
+++ b/src/include/executor/execExpr.h
@@ -0,0 +1,454 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+struct ArrayRefState;
+
+#define EEO_FLAG_JUMP_THREADED (1 << 0)
+
+typedef enum ExprEvalOp
+{
+	EEO_DONE,
+	EEO_INNER_FETCHSOME,
+	EEO_OUTER_FETCHSOME,
+	EEO_SCAN_FETCHSOME,
+	EEO_INNER_VAR,
+	EEO_OUTER_VAR,
+	EEO_SCAN_VAR,
+	EEO_ASSIGN_INNER_VAR,
+	EEO_ASSIGN_OUTER_VAR,
+	EEO_ASSIGN_SCAN_VAR,
+	EEO_ASSIGN_TMP,
+	EEO_ASSIGN_TMP_UNEXPAND,
+	EEO_INNER_SYSVAR,
+	EEO_OUTER_SYSVAR,
+	EEO_SCAN_SYSVAR,
+	EEO_CONST,
+	EEO_FUNCEXPR,
+	EEO_FUNCEXPR_STRICT,
+	EEO_FUNCEXPR_FUSAGE,
+	EEO_FUNCEXPR_STRICT_FUSAGE,
+	EEO_BOOL_AND_STEP_FIRST,
+	EEO_BOOL_AND_STEP,
+	EEO_BOOL_OR_STEP_FIRST,
+	EEO_BOOL_OR_STEP,
+	EEO_BOOL_NOT_STEP,
+	EEO_QUAL,
+	EEO_NULLTEST_ISNULL,
+	EEO_NULLTEST_ISNOTNULL,
+	EEO_NULLTEST_ROWISNULL,
+	EEO_NULLTEST_ROWISNOTNULL,
+	EEO_PARAM_EXEC,
+	EEO_PARAM_EXTERN,
+	EEO_CASE_WHEN_STEP,
+	EEO_CASE_THEN_STEP,
+	EEO_CASE_TESTVAL,
+	EEO_CASE_TESTVAL_UNEXPAND,
+	EEO_COALESCE,
+	EEO_BOOLTEST_IS_TRUE,
+	EEO_BOOLTEST_IS_NOT_TRUE,
+	EEO_BOOLTEST_IS_FALSE,
+	EEO_BOOLTEST_IS_NOT_FALSE,
+	EEO_BOOLTEST_IS_UNKNOWN,
+	EEO_BOOLTEST_IS_NOT_UNKNOWN,
+	EEO_WHOLEROW,
+	EEO_IOCOERCE,
+	EEO_DISTINCT,
+	EEO_NULLIF,
+	EEO_SQLVALUEFUNCTION,
+	EEO_CURRENTOFEXPR,
+	EEO_ARRAYEXPR,
+	EEO_ARRAYCOERCE,
+	EEO_ROW,
+	EEO_ROWCOMPARE_STEP,
+	EEO_ROWCOMPARE_FINAL,
+	EEO_MINMAX,
+	EEO_FIELDSELECT,
+	EEO_FIELDSTORE_DEFORM,
+	EEO_FIELDSTORE_FORM,
+	EEO_ARRAYREF_CHECKINPUT,
+	EEO_ARRAYREF_CHECKSUBSCRIPT,
+	EEO_ARRAYREF_OLD,
+	EEO_ARRAYREF_ASSIGN,
+	EEO_ARRAYREF_FETCH,
+	EEO_CONVERT_ROWTYPE,
+	EEO_SCALARARRAYOP,
+	EEO_DOMAIN_TESTVAL,
+	EEO_DOMAIN_TESTVAL_UNEXPAND,
+	EEO_DOMAIN_NOTNULL,
+	EEO_DOMAIN_CHECK,
+	EEO_XMLEXPR,
+	EEO_AGGREF,
+	EEO_GROUPING_FUNC,
+	EEO_WINDOW_FUNC,
+	EEO_SUBPLAN,
+	EEO_ALTERNATIVE_SUBPLAN,
+	EEO_LAST
+} ExprEvalOp;
+
+
+typedef struct ExprEvalStep
+{
+	/*
+	 * Instruction to be executed. During instruction preparation this is an
+	 * ExprEvalOp, but during execution it can be swapped out to some other
+	 * type, e.g. a pointer for computed goto (that's why it's a size_t).
+	 */
+	size_t		opcode;
+
+	/* target for the result of the current instruction */
+	bool	   *resnull;
+	Datum	   *resvalue;
+
+	/*
+	 * Data for an operation. Inline stored data is faster to access, but also
+	 * bloats the size of all instructions. The union should be kept below 48
+	 * bytes (so the entire struct is below 64bytes, a single cacheline on
+	 * common systems).
+	 */
+	union
+	{
+		struct
+		{
+			int attnum;
+		} var;
+
+		struct
+		{
+			Var		   *var;
+			bool		first;			/* first time through, initialize */
+			TupleDesc	tupdesc;	/* descriptor for resulting tuples */
+			JunkFilter *junkFilter; /* JunkFilter to remove resjunk cols */
+		} wholerow;
+
+		struct
+		{
+			Datum value;
+			bool isnull;
+		} constval;
+
+		struct
+		{
+			FmgrInfo	*finfo;
+			FunctionCallInfo fcinfo_data;
+			/* faster to access without additional indirection */
+			PGFunction	fn_addr;
+			int nargs;
+		} func;
+
+		struct
+		{
+			Datum *value;
+			bool *isnull;
+			bool *anynull;
+			int jumpdone;
+		} boolexpr;
+
+		struct
+		{
+			int jumpdone;
+		} qualexpr;
+
+		struct
+		{
+			TupleDesc argdesc;
+		} nulltest_row;
+
+		struct
+		{
+			int paramid;
+			int paramtype;
+		} param;
+
+		struct
+		{
+			Datum *value;
+			bool *isnull;
+			int jumpfalse;
+		} casewhen;
+
+		struct
+		{
+			Datum *value;
+			bool *isnull;
+			int jumpdone;
+		} casethen;
+
+		struct
+		{
+			Datum *value;
+			bool *isnull;
+		} casetest;
+
+		struct
+		{
+			int jumpdone;
+		} coalesce;
+
+		struct
+		{
+			/* lookup info for source output function */
+			FmgrInfo	*finfo_out;
+			FunctionCallInfo fcinfo_data_out;
+			/* lookup info for result input function */
+			FmgrInfo	*finfo_in;
+			FunctionCallInfo fcinfo_data_in;
+			Oid			intypioparam;	/* argument needed for input function */
+		} iocoerce;
+
+		struct
+		{
+			SQLValueFunction *svf;
+		} sqlvaluefunction;
+
+		struct
+		{
+			ArrayExpr  *arrayexpr;
+			Datum	   *elemvalues;
+			bool	   *elemnulls;
+			int			nelems;
+			int16		elemlength;		/* typlen of the array element type */
+			bool		elembyval;		/* is the element type pass-by-value? */
+			char		elemalign;		/* typalign of the element type */
+		} arrayexpr;
+
+		struct
+		{
+			ArrayCoerceExpr *coerceexpr;
+			Oid			resultelemtype; /* element type of result array */
+			FmgrInfo   *elemfunc;		/* lookup info for element coercion function */
+			ArrayMapState *amstate;		/* workspace for array_map */
+		} arraycoerce;
+
+		struct
+		{
+			RowExpr	   *rowexpr;
+			/* the arguments */
+			Datum	   *elemvalues;
+			bool	   *elemnulls;
+			TupleDesc	tupdesc;		/* descriptor for result tuples */
+		} row;
+
+		struct
+		{
+			FmgrInfo	*finfo;
+			FunctionCallInfo fcinfo_data;
+			PGFunction	fn_addr;
+			int jumpnull;
+			int jumpdone;
+		} rowcompare_step;
+
+		struct
+		{
+			RowCompareType rctype;
+		} rowcompare_final;
+
+		struct
+		{
+			/* the arguments */
+			Datum	   *values;
+			bool	   *nulls;
+			int			nelems;
+
+			MinMaxOp	op;
+			FmgrInfo	*finfo;
+			FunctionCallInfo fcinfo_data;
+		} minmax;
+
+		struct
+		{
+			/* tupdesc for most recent input */
+			TupleDesc	argdesc;
+			AttrNumber	fieldnum;
+			Oid			resulttype;
+		} fieldselect;
+
+		struct
+		{
+			/* tupdesc for most recent input */
+			TupleDesc	argdesc;
+			FieldStore *fstore;
+
+			/* the arguments */
+			int			nvalues;
+			Datum	   *values;
+			bool	   *nulls;
+		} fieldstore;
+
+		struct
+		{
+			/* too big to have inline */
+			struct ArrayRefState *state;
+			int jumpdone;
+		} arrayref;
+
+		struct
+		{
+			/* too big to have inline */
+			struct ArrayRefState *state;
+			int off;
+			int jumpdone;
+			bool isupper;
+		} arrayref_checksubscript;
+
+		struct
+		{
+			ConvertRowtypeExpr *convert;
+			TupleDesc indesc;
+			TupleDesc outdesc;
+			TupleConversionMap *map;
+			bool initialized;
+		} convert_rowtype;
+
+		struct
+		{
+			ScalarArrayOpExpr *opexpr;
+			Oid			element_type;
+			int16		typlen;
+			bool		typbyval;
+			char		typalign;
+			FmgrInfo	*finfo;
+			FunctionCallInfo fcinfo_data;
+			PGFunction	fn_addr;
+		} scalararrayop;
+
+		struct
+		{
+			char *constraintname;
+			Datum *checkvalue;
+			bool *checknull;
+			Oid resulttype;
+		} domaincheck;
+
+		struct
+		{
+			XmlExpr *xexpr;
+			Datum *named_argvalue;
+			bool *named_argnull;
+			Datum *argvalue;
+			bool *argnull;
+		} xmlexpr;
+
+		struct
+		{
+			AggrefExprState *astate;
+		} aggref;
+
+		struct
+		{
+			AggState *parent;
+			List *clauses;
+		} grouping_func;
+
+		struct
+		{
+			WindowFuncExprState *wfstate;
+		} window_func;
+
+		struct
+		{
+			SubPlanState *sstate;
+		} subplan;
+
+		struct
+		{
+			AlternativeSubPlanState *asstate;
+		} alternative_subplan;
+
+		struct
+		{
+			size_t resultnum;
+			int attnum;
+		} assign_var;
+
+		struct
+		{
+			size_t resultnum;
+		} assign_tmp;
+
+		struct
+		{
+			int last_var;
+		} fetch;
+	} d;
+} ExprEvalStep;
+
+
+typedef struct ArrayRefState
+{
+	bool		isassignment;
+	int			numupper;
+	Datum		upper[MAXDIM];
+	int			upperindex[MAXDIM];
+	bool		uppernull[MAXDIM];
+	bool		upperprovided[MAXDIM];
+	int			numlower;
+	Datum		lower[MAXDIM];
+	int			lowerindex[MAXDIM];
+	bool		lowernull[MAXDIM];
+	bool		lowerprovided[MAXDIM];
+
+	Oid			refelemtype;
+	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 */
+
+	Datum		replacevalue;
+	bool		replacenull;
+
+	Datum		prevvalue;
+	bool		prevnull;
+} ArrayRefState;
+
+void ExecInstantiateInterpretedExpr(ExprState *state);
+
+ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
+
+/*
+ * Non fast-path execution functions. These are externs instead of static in
+ * execInterpExpr.c, because that allows them to be used by other methods of
+ * expression evaluation.
+ */
+extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext);
+extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext);
+extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op);
+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 ExecEvalArrayRefCheckSubscript(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalArrayRefFetch(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalArrayRefAssign(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalArrayRefOld(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalRowNull(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalRowNotNull(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 cf44c3edbb..d828a3f907 100644
--- a/src/include/executor/execdebug.h
+++ b/src/include/executor/execdebug.h
@@ -38,13 +38,6 @@
  */
 
 /* ----------------
- *		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
  * ----------------
@@ -86,20 +79,6 @@
 #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
  * ----------------
  */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 32e1838e15..0c3212153c 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -70,8 +70,8 @@
  * 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))
+#define ExecEvalExpr(state, econtext, isNull) \
+	(state)->cb((state), econtext, isNull)
 
 
 /* Hook for plugins to get control in ExecutorStart() */
@@ -247,23 +247,101 @@ 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 Tuplestorestate *ExecMakeTableFunctionResult(SetExprState *setexpr,
 							ExprContext *econtext,
 							MemoryContext argContext,
 							TupleDesc expectedDesc,
 							bool randomAccess);
-extern Datum ExecMakeFunctionResultSet(FuncExprState *fcache,
+extern Datum ExecMakeFunctionResultSet(SetExprState *setexpr,
 						  ExprContext *econtext,
 						  bool *isNull,
 						  ExprDoneCond *isDone);
-extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext,
-						  bool *isNull);
+extern SetExprState *ExecInitFunctionResultSet(Expr *expr, ExprContext *econtext, PlanState *parent);
+extern SetExprState *ExecInitTableFunctionResult(Expr *expr, ExprContext *econtext, PlanState *parent);
+
+/*
+ * prototypes from functions in execExpr.c
+ */
 extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern List *ExecInitExprList(List *nodes, PlanState *parent);
 extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
-extern bool ExecQual(List *qual, ExprContext *econtext, bool resultForNull);
+extern List *ExecPrepareExprList(List *nodes, EState *estate);
+extern ExprState *ExecPrepareQual(List *qual, EState *estate);
+extern ExprState *ExecPrepareCheck(List *qual, EState *estate);
+extern ExprState *ExecInitQual(List *qual, PlanState *parent);
+extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
+
+extern bool ExecCheck(ExprState *state, ExprContext *context);
 extern int	ExecTargetListLength(List *targetlist);
 extern int	ExecCleanTargetListLength(List *targetlist);
-extern TupleTableSlot *ExecProject(ProjectionInfo *projInfo);
+extern void ExecProjectIntoSlot(ProjectionInfo *projInfo,
+								TupleTableSlot *slot);
+/*
+ * 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.
+ */
+#ifndef FRONTEND
+static inline TupleTableSlot *
+ExecProject(ProjectionInfo *projInfo)
+{
+	ExecProjectIntoSlot(projInfo, projInfo->pi_slot);
+	return projInfo->pi_slot;
+}
+#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 = ExecEvalExpr(state, econtext, isNull);
+	MemoryContextSwitchTo(oldContext);
+	return retDatum;
+}
+#endif
+
+/*
+ * ExecQual - evaluate a qual prepared with ExecInitQual (possibly via
+ * ExecPrepareQual).
+ */
+#ifndef FRONTEND
+static inline bool
+ExecQual(ExprState *state, ExprContext *econtext)
+{
+	bool isnull;
+	Datum ret;
+
+	/* short-circuit (here and in ExecInitQual) for empty restriction list */
+	if (state == NULL)
+		return true;
+
+	ret = ExecEvalExprSwitchContext(state, econtext, &isnull);
+
+	/* EEO_QUAL should never return NULL */
+	Assert(!isnull);
+
+	return DatumGetBool(ret);
+}
+#endif
 
 /*
  * prototypes from functions in execScan.c
@@ -361,6 +439,7 @@ extern void ExecGetLastAttnums(Node *node,
 extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList,
 						ExprContext *econtext,
 						TupleTableSlot *slot,
+						PlanState *planstate,
 						TupleDesc inputDesc);
 extern void ExecAssignProjectionInfo(PlanState *planstate,
 						 TupleDesc inputDesc);
diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h
index 0f821dc8f6..0bdaa548df 100644
--- a/src/include/executor/nodeSubplan.h
+++ b/src/include/executor/nodeSubplan.h
@@ -20,6 +20,9 @@ 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/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 6332ea0620..bcf8060f7d 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -29,60 +29,6 @@
 
 
 /* ----------------
- *	  IndexInfo information
- *
- *		this struct holds the information needed to construct new index
- *		entries for a particular index.  Used for both index_build and
- *		retail creation of index entries.
- *
- *		NumIndexAttrs		number of columns in this index
- *		KeyAttrNumbers		underlying-rel attribute numbers used as keys
- *							(zeroes indicate expressions)
- *		Expressions			expr trees for expression entries, or NIL if none
- *		ExpressionsState	exec state for expressions, or NIL if none
- *		Predicate			partial-index predicate, or NIL if none
- *		PredicateState		exec state for predicate, or NIL if none
- *		ExclusionOps		Per-column exclusion operators, or NULL if none
- *		ExclusionProcs		Underlying function OIDs for ExclusionOps
- *		ExclusionStrats		Opclass strategy numbers for ExclusionOps
- *		UniqueOps			Theses are like Exclusion*, but for unique indexes
- *		UniqueProcs
- *		UniqueStrats
- *		Unique				is it a unique index?
- *		ReadyForInserts		is it valid for inserts?
- *		Concurrent			are we doing a concurrent index build?
- *		BrokenHotChain		did we detect any broken HOT chains?
- *		AmCache				private cache area for index AM
- *		Context				memory context holding this IndexInfo
- *
- * ii_Concurrent and ii_BrokenHotChain are used only during index build;
- * they're conventionally set to false otherwise.
- * ----------------
- */
-typedef struct IndexInfo
-{
-	NodeTag		type;
-	int			ii_NumIndexAttrs;
-	AttrNumber	ii_KeyAttrNumbers[INDEX_MAX_KEYS];
-	List	   *ii_Expressions; /* list of Expr */
-	List	   *ii_ExpressionsState;	/* list of ExprState */
-	List	   *ii_Predicate;	/* list of Expr */
-	List	   *ii_PredicateState;		/* list of ExprState */
-	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 */
-	Oid		   *ii_UniqueOps;	/* array with one entry per column */
-	Oid		   *ii_UniqueProcs; /* array with one entry per column */
-	uint16	   *ii_UniqueStrats;	/* array with one entry per column */
-	bool		ii_Unique;
-	bool		ii_ReadyForInserts;
-	bool		ii_Concurrent;
-	bool		ii_BrokenHotChain;
-	void	   *ii_AmCache;
-	MemoryContext ii_Context;
-} IndexInfo;
-
-/* ----------------
  *	  ExprContext_CB
  *
  *		List of callbacks to be called at ExprContext shutdown.
@@ -206,57 +152,60 @@ typedef struct ReturnSetInfo
 } ReturnSetInfo;
 
 /* ----------------
+ *		ExprState node
+ *
+ * ExprState is the top-level node for expression evaluation. It contains
+ * instructions (in ->steps) to evaluate the expression.
+ * ----------------
+ */
+struct ExprState;
+struct ExprContext;
+typedef Datum (*ExecEvalExprCB)(struct ExprState *, struct ExprContext *, bool *);
+
+typedef struct ExprState
+{
+	Node		tag;
+	bool		resnull;
+	int8		flags;
+	Datum		resvalue;
+	struct ExprEvalStep *steps;
+	TupleTableSlot *resultslot;
+
+	/* evaluate expression */
+	ExecEvalExprCB cb;
+
+	/* original expression */
+	Expr	   *expr;
+
+	/* FIXME: only needed during "compilation", should be thrown away */
+	size_t		steps_alloc;
+	size_t		steps_len;
+
+	Datum	   *innermost_caseval;
+	bool	   *innermost_casenull;
+
+	Datum	   *innermost_domainval;
+	bool	   *innermost_domainnull;
+} ExprState;
+
+/* ----------------
  *		ProjectionInfo node information
  *
  *		This is all the information needed to perform projections ---
  *		that is, form new tuples by evaluation of targetlist expressions.
  *		Nodes which need to do projections create one of these.
  *
- *		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)
+ *		pi_state		instructions to evaluate projection
  *		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;
+	ExprState	pi_state;
 	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;
 
 /* ----------------
@@ -298,6 +247,60 @@ typedef struct JunkFilter
 } JunkFilter;
 
 /* ----------------
+ *	  IndexInfo information
+ *
+ *		this struct holds the information needed to construct new index
+ *		entries for a particular index.  Used for both index_build and
+ *		retail creation of index entries.
+ *
+ *		NumIndexAttrs		number of columns in this index
+ *		KeyAttrNumbers		underlying-rel attribute numbers used as keys
+ *							(zeroes indicate expressions)
+ *		Expressions			expr trees for expression entries, or NIL if none
+ *		ExpressionsState	exec state for expressions, or NIL if none
+ *		Predicate			partial-index predicate, or NIL if none
+ *		PredicateState		exec state for predicate, or NIL if none
+ *		ExclusionOps		Per-column exclusion operators, or NULL if none
+ *		ExclusionProcs		Underlying function OIDs for ExclusionOps
+ *		ExclusionStrats		Opclass strategy numbers for ExclusionOps
+ *		UniqueOps			Theses are like Exclusion*, but for unique indexes
+ *		UniqueProcs
+ *		UniqueStrats
+ *		Unique				is it a unique index?
+ *		ReadyForInserts		is it valid for inserts?
+ *		Concurrent			are we doing a concurrent index build?
+ *		BrokenHotChain		did we detect any broken HOT chains?
+ *		AmCache				private cache area for index AM
+ *		Context				memory context holding this IndexInfo
+ *
+ * ii_Concurrent and ii_BrokenHotChain are used only during index build;
+ * they're conventionally set to false otherwise.
+ * ----------------
+ */
+typedef struct IndexInfo
+{
+	NodeTag		type;
+	int			ii_NumIndexAttrs;
+	AttrNumber	ii_KeyAttrNumbers[INDEX_MAX_KEYS];
+	List	   *ii_Expressions; /* list of Expr */
+	List	   *ii_ExpressionsState;	/* list of ExprState */
+	List	   *ii_Predicate;	/* list of Expr */
+	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 */
+	Oid		   *ii_UniqueOps;	/* array with one entry per column */
+	Oid		   *ii_UniqueProcs; /* array with one entry per column */
+	uint16	   *ii_UniqueStrats;	/* array with one entry per column */
+	bool		ii_Unique;
+	bool		ii_ReadyForInserts;
+	bool		ii_Concurrent;
+	bool		ii_BrokenHotChain;
+	void	   *ii_AmCache;
+	MemoryContext ii_Context;
+} IndexInfo;
+
+/* ----------------
  *	  ResultRelInfo information
  *
  *		Whenever we update an existing relation, we have to
@@ -338,20 +341,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;
 
@@ -560,143 +563,54 @@ typedef tuplehash_iterator TupleHashIterator;
 #define ScanTupleHashTable(htable, iter) \
 	tuplehash_iterate(htable->hashtab, iter)
 
-
-/* ----------------------------------------------------------------
- *				 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.
- * ----------------------------------------------------------------
- */
-
-/* ----------------
- *		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;
 	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;
+	NodeTag		type;
+	WindowFunc *wfunc;
 	List	   *args;			/* states of argument expressions */
-	ExprState  *aggfilter;		/* FILTER expression */
+	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 uses the same machinery as SRFs
+ * (i.e. are treated like a SRF returning a single row).
  * ----------------
  */
-typedef struct FuncExprState
+typedef struct SetExprState
 {
-	ExprState	xprstate;
+	NodeTag		type;
+	Expr	   *expr;
 	List	   *args;			/* states of argument expressions */
 
 	/*
+	 * In ROWS FROM functions possibly can be inlined removing the FuncExpr
+	 * normally inside, this is the containing expression (which cannot return
+	 * a set) which'll be evaluated using the normal ExecEvalExpr().
+	 */
+	ExprState	*elidedFuncState;
+
+	/*
 	 * Function manager's lookup info for the target function.  If func.fn_oid
 	 * is InvalidOid, we haven't initialized it yet (nor any of the following
 	 * fields, except funcReturnsSet).
@@ -748,33 +662,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
@@ -782,10 +670,11 @@ typedef struct BoolExprState
  */
 typedef struct SubPlanState
 {
-	ExprState	xprstate;
+	NodeTag		type;
+	SubPlan    *subplan;
 	struct PlanState *planstate;	/* subselect plan's state tree */
 	struct PlanState *parent;	/* parent plan node's state tree */
-	ExprState  *testexpr;		/* state of combining expression */
+	ExprState *testexpr;		/* state of combining expression */
 	List	   *args;			/* states of argument expression(s) */
 	HeapTuple	curTuple;		/* copy of most recent tuple from subplan */
 	Datum		curArray;		/* most recent array from ARRAY() subplan */
@@ -812,197 +701,12 @@ typedef struct SubPlanState
  */
 typedef struct AlternativeSubPlanState
 {
-	ExprState	xprstate;
+	NodeTag		type;
+	AlternativeSubPlan *subplan;
 	List	   *subplans;		/* states 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
  *
@@ -1021,7 +725,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; /* for CHECK, a boolean expression */
 } DomainConstraintState;
 
 
@@ -1058,8 +763,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;			/* implicitly-ANDed qual conditions */
 	struct PlanState *lefttree; /* input plan tree(s) */
 	struct PlanState *righttree;
 	List	   *initPlan;		/* Init SubPlanState nodes (un-correlated expr
@@ -1136,6 +840,7 @@ typedef struct ResultState
 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? */
@@ -1370,7 +1075,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;
@@ -1416,7 +1121,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;
@@ -1482,7 +1187,7 @@ typedef struct BitmapIndexScanState
 typedef struct BitmapHeapScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
-	List	   *bitmapqualorig;
+	ExprState  *bitmapqualorig;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator;
 	TBMIterateResult *tbmres;
@@ -1506,7 +1211,6 @@ typedef struct BitmapHeapScanState
 typedef struct TidScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
-	List	   *tss_tidquals;	/* list of ExprState nodes */
 	bool		tss_isCurrentOf;
 	int			tss_NumTids;
 	int			tss_TidPtr;
@@ -1630,7 +1334,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;
@@ -1677,7 +1381,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;
 
 /* ----------------
@@ -1775,7 +1479,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 ede7ace76b..b61b05683c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -187,36 +187,19 @@ 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 nodes do not have a corresponding expression state
+	 * node, they're solely handled within execExpr* - but sometimes the state
+	 * needs to be shared with other parts of the executor, as e.g. the case
+	 * for 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/xml.h b/src/include/utils/xml.h
index cc1fc390e8..fc7773e779 100644
--- a/src/include/utils/xml.h
+++ b/src/include/utils/xml.h
@@ -60,7 +60,7 @@ 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, bool *named_argnull, Datum *named_argvalue, bool *argnull, Datum *argvalue);
 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 6fc3db07fe..2044d0427a 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -143,7 +143,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;
@@ -4709,7 +4709,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 09d5516fb5..ca0877931b 100644
--- a/src/test/regress/expected/case.out
+++ b/src/test/regress/expected/case.out
@@ -300,7 +300,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/sql/case.sql b/src/test/regress/sql/case.sql
index a7ae7b4a9e..a948bb9d8f 100644
--- a/src/test/regress/sql/case.sql
+++ b/src/test/regress/sql/case.sql
@@ -161,7 +161,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
-- 
2.11.0.22.g8d7a455.dirty

