Hi,

On 2016-06-24 16:29:53 -0700, Andres Freund wrote:
> 2) Baring 1) tuple deforming is the biggest bottleneck in nearly all
>    queries I looked at. There's various places we trigger deforming,
>    most ending in either slot_deform_tuple(), heap_getattr(),
>    heap_deform_tuple().
>
>    This can be optimized significantly, but even after that it's still a
>    major issue.
>
>    Part of the problem is that we sometimes don't know how many elements
>    to access (especially in index and hashing related code), the other
>    part that we're deforming a lot of columns that we'll never need,
>    just because we need a subsequent one.
>
>    The other part is that our tuple format requires a number of
>    relatively expensive operations to access data (e.g. alignment
>    computations, checking the null bitmap).

> 6) The use of linked lists adds noticeable #instructions overhead and
>    branch misses. Just replacing {FuncExprState,BoolExprState}->args
>    with an array gives a ~10%ish boost for queries with a bunch of
>    quals.  Another example that appears to hurts noticeably, but which
>    I've not experimentally fixed, is AggState->hash_needed.
>
>    To a large degree these seem fairly easily fixable; it's kinda boring
>    work, but not really hard.

As previously discussed many of the "architectural" changes show limited
success until a few other bottlenecks are fixed. Most prominently slot
deforming and, what I'm planning to primarily discuss in this email,
expression evaluation.

Even in the current state, profiles of queries evaluating a large number
of tuples very commonly show expression evaluation to be one of the
major costs. Due to the number of recursive calls that's not always easy
to pinpoint though, the easiest thing to spot is usually
MakeFunctionResultNoSet showing up prominently.

While, as in 6) above, removing linked lists from the relevant
structures helps, it's not that much. Staring at this for a long while
made me realize that, somewhat surprisingly to me, is that one of the
major problems is that we are bottlenecked on stack usage. Constantly
entering and leaving this many functions for trivial expressions
evaluations hurts considerably. Some of that is the additional numbers
of instructions, some of that is managing return jump addresses, and
some of that the actual bus traffic. It's also rather expensive to check
for stack limits at a very high rate.


Attached (in patch 0003) is a proof-of-concept implementing an
expression evalution framework that doesn't use recursion. Instead
ExecInitExpr2 computes a number of 'steps' necessary to compute an
expression. These steps are stored in a linear array, and executed one
after another (save boolean expressions, which can jump to later steps).
E.g. to compute colname = 1 the steps are 1) fetch var, 2) evaluate
const, 3) call function.

Expression evaluation then is a switch statement over the opcodes of
each of these steps.

Using the machinery of those 'steps' to compute projections, quals, and
constraint evaluation then allows to reduce the number of indirect
function calls/jumps further.

My preliminary implementation so far implements only function calls,
boolean expression, constant evaluation and variable evaluation. For
everything else I'm falling back to the current expression machinery.

By combining expression, qual and target list processing, we can also
always generate the appropriate slot_getsomeattrs() calls.

Note that the patch currently does *NOT* support targetlist SRFs, and
instead just errors out. This is not a fundamental issue. I just didn't
want to invest time in supporting something we want to reimplement
anyway.   Similarly subplans currently don't work because
of:
        /*
         * Evaluate lefthand expressions and form a projection tuple. First we
         * have to set the econtext to use (hack alert!).
which doesn't work quite like that atm.


I've used (0004) a similar method to reduce the number of branches and
pipeline stalls in slot_deform_tuple considerably. For each attribute a
'step' is generated, which contains exactly the computations required to
deform that individual datum. That e.g. allows to separate cases for
different alignments and null-checks.


Having expression evaluation and slot deforming as a series of simple
sequential steps, instead of complex recursive calls, would also make it
fairly straightforward to optionally just-in-time compile those.


For motivation, here's some random performance differences:
SELECT SUM(l_quantity * l_extendedprice) FROM lineitem;
master: 5038.382 4965.310 4983.146
patches: 4194.593 4153.759 4168.986
tpch-q1
master: 21274.896
dev: 17952.678

For queries involving more complex expressions, the difference can be
a larger.


Both, expression processing and tuple deforming, can use considerable
additional improvements. But I think the approaches presented here are
the biggest step that I can see.


The reason that I'm bringing this up before submitting actual 'batch
operation' patches is that the architectural improvements are quickly
hidden behind these bottlenecks.


Before spending time polishing up these approaches, I'd like if anybody
fundamentally disagrees with either, or has a better proposal. If not
I'm hoping to first "properly" submit the slot deforming for review, and
then the expression evaluation. The latter would obviously need to be a
lot more complete than now; and we'd likely want to the targetlist SRF
rearchitecting beforehand.

Comments?

Regards,

Andres
>From 13b77f0068ff2b5dd02f0b64a8f0f0ad2e60a4cf Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Thu, 7 Jul 2016 19:57:27 -0700
Subject: [PATCH 01/20] WIP: make get_last_attnums more generic.

---
 src/backend/executor/execUtils.c | 48 +++++++++++++++++++++++++++++-----------
 src/include/executor/executor.h  |  4 ++++
 2 files changed, 39 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index e937cf8..a3a59c0 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -47,7 +47,14 @@
 #include "utils/rel.h"
 
 
-static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
+typedef struct LastLastAttnumInfo
+{
+	AttrNumber last_outer;
+	AttrNumber last_inner;
+	AttrNumber last_scan;
+} LastAttnumInfo;
+
+
 static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
 
 
@@ -585,7 +592,10 @@ ExecBuildProjectionInfo(List *targetList,
 			/* Not a simple variable, add it to generic targetlist */
 			exprlist = lappend(exprlist, gstate);
 			/* Examine expr to include contained Vars in lastXXXVar counts */
-			get_last_attnums((Node *) variable, projInfo);
+			ExecGetLastAttnums((Node *) variable,
+							   &projInfo->pi_lastOuterVar,
+							   &projInfo->pi_lastInnerVar,
+							   &projInfo->pi_lastScanVar);
 		}
 	}
 	projInfo->pi_targetlist = exprlist;
@@ -602,13 +612,13 @@ ExecBuildProjectionInfo(List *targetList,
 }
 
 /*
- * get_last_attnums: expression walker for ExecBuildProjectionInfo
+ * get_last_attnums_walker: expression walker for ExecBuildProjectionInfo
  *
  *	Update the lastXXXVar counts to be at least as large as the largest
  *	attribute numbers found in the expression
  */
 static bool
-get_last_attnums(Node *node, ProjectionInfo *projInfo)
+get_last_attnums_walker(Node *node, LastAttnumInfo *info)
 {
 	if (node == NULL)
 		return false;
@@ -620,21 +630,17 @@ get_last_attnums(Node *node, ProjectionInfo *projInfo)
 		switch (variable->varno)
 		{
 			case INNER_VAR:
-				if (projInfo->pi_lastInnerVar < attnum)
-					projInfo->pi_lastInnerVar = attnum;
+				info->last_inner = Max(info->last_inner, attnum);
 				break;
 
 			case OUTER_VAR:
-				if (projInfo->pi_lastOuterVar < attnum)
-					projInfo->pi_lastOuterVar = attnum;
+				info->last_outer = Max(info->last_outer, attnum);
 				break;
 
 				/* INDEX_VAR is handled by default case */
 
 			default:
-				if (projInfo->pi_lastScanVar < attnum)
-					projInfo->pi_lastScanVar = attnum;
-				break;
+				info->last_scan = Max(info->last_scan, attnum);
 		}
 		return false;
 	}
@@ -651,10 +657,26 @@ get_last_attnums(Node *node, ProjectionInfo *projInfo)
 		return false;
 	if (IsA(node, GroupingFunc))
 		return false;
-	return expression_tree_walker(node, get_last_attnums,
-								  (void *) projInfo);
+	return expression_tree_walker(node, get_last_attnums_walker,
+								  (void *) info);
 }
 
+void
+ExecGetLastAttnums(Node *node, int *last_outer, int *last_inner,
+				   int *last_scan)
+{
+	LastAttnumInfo info = {0,0,0};
+
+	get_last_attnums_walker(node, &info);
+	if (last_outer && *last_outer < info.last_outer)
+		*last_outer = info.last_outer;
+	if (last_inner && *last_inner < info.last_inner)
+		*last_inner = info.last_inner;
+	if (last_scan && *last_scan < info.last_scan)
+		*last_scan = info.last_scan;
+}
+
+
 /* ----------------
  *		ExecAssignProjectionInfo
  *
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 39521ed..e5c0a56 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -338,6 +338,10 @@ extern void ExecAssignExprContext(EState *estate, PlanState *planstate);
 extern void ExecAssignResultType(PlanState *planstate, TupleDesc tupDesc);
 extern void ExecAssignResultTypeFromTL(PlanState *planstate);
 extern TupleDesc ExecGetResultType(PlanState *planstate);
+extern void ExecGetLastAttnums(Node *node,
+							   int *last_outer,
+							   int *last_inner,
+							   int *last_scan);
 extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList,
 						ExprContext *econtext,
 						TupleTableSlot *slot,
-- 
2.8.1

>From 39f8becdef728a0f24b42ce17372cbda8dda2bde Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Sun, 3 Jul 2016 15:05:18 -0700
Subject: [PATCH 02/20] WIP: Add likely/unlikely() macros.

---
 src/include/c.h | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/src/include/c.h b/src/include/c.h
index 4ab3f80..182066d 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -939,6 +939,15 @@ typedef NameData *Name;
 #endif
 
 
+#ifdef __GNUC__
+#define likely(x)	__builtin_expect(!!(x), 1)
+#define unlikely(x)	__builtin_expect(!!(x), 0)
+#else
+#define likely(x)	!!(x)
+#define unlikely(x)	!!(x)
+#endif
+
+
 /* ----------------------------------------------------------------
  *				Section 8:	random stuff
  * ----------------------------------------------------------------
-- 
2.8.1

>From addda965619e928c934fcacf78ad4e7e2f74d338 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Tue, 12 Jul 2016 21:06:28 -0700
Subject: [PATCH 03/20] WIP: Faster expression processing and targetlist
 projection.

---
 src/backend/bootstrap/bootstrap.c         |   2 +-
 src/backend/catalog/index.c               |  38 +-
 src/backend/catalog/toasting.c            |   2 +-
 src/backend/commands/analyze.c            |  10 +-
 src/backend/commands/indexcmds.c          |   4 +-
 src/backend/commands/tablecmds.c          |  14 +-
 src/backend/commands/trigger.c            |   8 +-
 src/backend/executor/Makefile             |   4 +-
 src/backend/executor/execExpr.c           | 915 ++++++++++++++++++++++++++++++
 src/backend/executor/execIndexing.c       |  21 +-
 src/backend/executor/execMain.c           |  17 +-
 src/backend/executor/execQual.c           | 414 +-------------
 src/backend/executor/execScan.c           |   4 +-
 src/backend/executor/execUtils.c          | 140 +----
 src/backend/executor/nodeAgg.c            |  14 +-
 src/backend/executor/nodeBitmapHeapscan.c |  18 +-
 src/backend/executor/nodeCtescan.c        |   7 +-
 src/backend/executor/nodeCustom.c         |   7 +-
 src/backend/executor/nodeForeignscan.c    |  16 +-
 src/backend/executor/nodeFunctionscan.c   |   7 +-
 src/backend/executor/nodeGather.c         |   7 +-
 src/backend/executor/nodeGroup.c          |  11 +-
 src/backend/executor/nodeHash.c           |  11 +-
 src/backend/executor/nodeHashjoin.c       |  47 +-
 src/backend/executor/nodeIndexonlyscan.c  |  16 +-
 src/backend/executor/nodeIndexscan.c      |  20 +-
 src/backend/executor/nodeMergejoin.c      |  32 +-
 src/backend/executor/nodeModifyTable.c    |  36 +-
 src/backend/executor/nodeNestloop.c       |  29 +-
 src/backend/executor/nodeResult.c         |  17 +-
 src/backend/executor/nodeSamplescan.c     |   7 +-
 src/backend/executor/nodeSeqscan.c        |   7 +-
 src/backend/executor/nodeSubplan.c        |  12 +-
 src/backend/executor/nodeSubqueryscan.c   |   7 +-
 src/backend/executor/nodeTidscan.c        |   7 +-
 src/backend/executor/nodeValuesscan.c     |   7 +-
 src/backend/executor/nodeWindowAgg.c      |   2 +-
 src/backend/executor/nodeWorktablescan.c  |   7 +-
 src/bin/initdb/initdb.c                   |   4 +-
 src/include/executor/executor.h           |  37 +-
 src/include/nodes/execnodes.h             | 177 ++++--
 src/include/nodes/nodes.h                 |   2 +
 42 files changed, 1353 insertions(+), 811 deletions(-)
 create mode 100644 src/backend/executor/execExpr.c

diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index e518e17..5b805b5 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -1088,7 +1088,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 7b30e46..053a116 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1645,7 +1645,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)
@@ -2193,7 +2193,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
 	double		reltuples;
-	List	   *predicate;
+	ExprState2  *predicate;
 	TupleTableSlot *slot;
 	EState	   *estate;
 	ExprContext *econtext;
@@ -2232,9 +2232,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
@@ -2537,9 +2535,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))
 				continue;
 		}
 
@@ -2604,7 +2602,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;
 }
@@ -2631,7 +2629,7 @@ IndexCheckExclusion(Relation heapRelation,
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
-	List	   *predicate;
+	ExprState2 *predicate;
 	TupleTableSlot *slot;
 	EState	   *estate;
 	ExprContext *econtext;
@@ -2657,9 +2655,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.
@@ -2684,9 +2680,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))
 				continue;
 		}
 
@@ -2717,7 +2713,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;
 }
 
 
@@ -2947,7 +2943,7 @@ validate_index_heapscan(Relation heapRelation,
 	HeapTuple	heapTuple;
 	Datum		values[INDEX_MAX_KEYS];
 	bool		isnull[INDEX_MAX_KEYS];
-	List	   *predicate;
+	ExprState2 *predicate;
 	TupleTableSlot *slot;
 	EState	   *estate;
 	ExprContext *econtext;
@@ -2977,9 +2973,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
@@ -3106,9 +3100,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))
 					continue;
 			}
 
@@ -3161,7 +3155,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/toasting.c b/src/backend/catalog/toasting.c
index 564e10e..c618dc1 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 5fcedd7..00c9d30 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -701,7 +701,7 @@ compute_index_stats(Relation onerel, double totalrows,
 		TupleTableSlot *slot;
 		EState	   *estate;
 		ExprContext *econtext;
-		List	   *predicate;
+		ExprState2 *predicate;
 		Datum	   *exprvals;
 		bool	   *exprnulls;
 		int			numindexrows,
@@ -727,9 +727,7 @@ compute_index_stats(Relation onerel, double totalrows,
 		econtext->ecxt_scantuple = slot;
 
 		/* Set up execution state for predicate. */
-		predicate = (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));
@@ -752,9 +750,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))
 					continue;
 			}
 			numindexrows++;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index d14d540..508130e 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -180,7 +180,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;
@@ -549,7 +549,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/tablecmds.c b/src/backend/commands/tablecmds.c
index 86e9814..804ca92 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -179,7 +179,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 */
+	ExprState2 *qualstate;		/* Execution state for CHECK */
 } NewConstraint;
 
 /*
@@ -4010,8 +4010,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 */
@@ -4195,7 +4194,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				switch (con->contype)
 				{
 					case CONSTR_CHECK:
-						if (!ExecQual(con->qualstate, econtext, true))
+						if (!ExecQual(con->qualstate))
 							ereport(ERROR,
 									(errcode(ERRCODE_CHECK_VIOLATION),
 									 errmsg("check constraint \"%s\" is violated by some row",
@@ -7308,7 +7307,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	Datum		val;
 	char	   *conbin;
 	Expr	   *origexpr;
-	List	   *exprstate;
+	ExprState2 *exprstate;
 	TupleDesc	tupdesc;
 	HeapScanDesc scan;
 	HeapTuple	tuple;
@@ -7339,8 +7338,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);
@@ -7360,7 +7358,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 	{
 		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 
-		if (!ExecQual(exprstate, econtext, true))
+		if (!ExecQual(exprstate))
 			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 99a659a..0929652 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2867,7 +2867,7 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 	if (trigger->tgqual)
 	{
 		TupleDesc	tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
-		List	  **predicate;
+		ExprState2 **predicate;
 		ExprContext *econtext;
 		TupleTableSlot *oldslot = NULL;
 		TupleTableSlot *newslot = NULL;
@@ -2888,7 +2888,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;
 
@@ -2899,7 +2899,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);
 		}
 
@@ -2947,7 +2947,7 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 		 */
 		econtext->ecxt_innertuple = oldslot;
 		econtext->ecxt_outertuple = newslot;
-		if (!ExecQual(*predicate, econtext, false))
+		if (!ExecQual(*predicate))
 			return false;
 	}
 
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 51edd4c..ba7eb50 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -12,8 +12,8 @@ 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 \
+OBJS = execAmi.o execCurrent.o execExpr.o execGrouping.o execIndexing.o \
+       execJunk.o execMain.o execParallel.o execProcnode.o execQual.o \
        execScan.o execTuples.o \
        execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
        nodeBitmapAnd.o nodeBitmapOr.o \
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
new file mode 100644
index 0000000..fe5575f
--- /dev/null
+++ b/src/backend/executor/execExpr.c
@@ -0,0 +1,915 @@
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/tupconvert.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_type.h"
+#include "executor/execdebug.h"
+#include "executor/nodeSubplan.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
+#include "parser/parse_coerce.h"
+#include "parser/parsetree.h"
+#include "pgstat.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/typcache.h"
+#include "utils/xml.h"
+
+static bool ExecInitExprRec(Expr *node, PlanState *parent, ExprContext *context, ExprState2 *state, Datum *resv, bool *resnull);
+
+static void
+ExprEvalPushStep(ExprState2 *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 TupleTableSlot** varslot(Var *variable, ExprContext *econtext)
+{
+	TupleTableSlot **slot;
+
+	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;
+	}
+
+	return slot;
+}
+
+static void
+ExecInitOld(ExprState2 *state, PlanState *parent, Expr *expr, Datum *resv, bool *resnull)
+{
+	ExprEvalStep scratch;
+	ExprState *es = ExecInitExpr(expr, parent);
+	scratch.resvalue = resv;
+	scratch.resnull = resnull;
+	scratch.opcode = EEO_EVAL1;
+	scratch.d.eval1.expr = es;
+	ExprEvalPushStep(state, &scratch);
+}
+
+static bool
+ExecInitFunc2(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid, Oid inputcollid,
+			  PlanState *parent, ExprContext *econtext, ExprState2 *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_cxt(funcid, scratch->d.func.finfo, econtext->ecxt_per_query_memory);
+	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)
+	{
+		elog(ERROR, "targetlist SRFs are not supported");
+		state->failed = true;
+		return false;
+	}
+
+	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 if (!ExecInitExprRec(arg, parent, econtext, state, &fcinfo->arg[argno], &fcinfo->argnull[argno]))
+		{
+			state->failed = true;
+			return false;
+		}
+		argno++;
+	}
+
+	scratch->d.func.nargs = nargs;
+	/*
+	 * FIXME: introduce separate opcodes for functioncallusage
+	 * enabled/disabled.
+	 */
+	if (pgstat_track_functions <= scratch->d.func.finfo->fn_stats)
+	{
+		if (scratch->d.func.finfo->fn_strict)
+			scratch->opcode = EEO_FUNCEXPR_STRICT;
+		else
+			scratch->opcode = EEO_FUNCEXPR;
+	}
+	else
+	{
+		if (scratch->d.func.finfo->fn_strict)
+			scratch->opcode = EEO_FUNCEXPR_STRICT_FUSAGE;
+		else
+			scratch->opcode = EEO_FUNCEXPR_FUSAGE;
+	}
+	return true;
+}
+
+static void
+ExecInitExprSlots(ExprState2 *state, ExprContext *econtext, 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_outer > 0)
+	{
+		scratch.opcode = EEO_FETCHSOMEATTRS;
+		scratch.d.fetch.slot = &econtext->ecxt_outertuple;
+		scratch.d.fetch.last_var = last_outer;
+		ExprEvalPushStep(state, &scratch);
+	}
+	if (last_inner > 0)
+	{
+		scratch.opcode = EEO_FETCHSOMEATTRS;
+		scratch.d.fetch.slot = &econtext->ecxt_innertuple;
+		scratch.d.fetch.last_var = last_inner;
+		ExprEvalPushStep(state, &scratch);
+	}
+	if (last_scan > 0)
+	{
+		scratch.opcode = EEO_FETCHSOMEATTRS;
+		scratch.d.fetch.slot = &econtext->ecxt_scantuple;
+		scratch.d.fetch.last_var = last_scan;
+		ExprEvalPushStep(state, &scratch);
+	}
+}
+
+ExprState2 *
+ExecInitQual(List *qual, PlanState *parent, ExprContext *econtext)
+{
+	BoolExpr *andExpr;
+
+	if (qual == NULL)
+		return NULL;
+
+	Assert(IsA(qual, List));
+
+	if (list_length(qual) > 1)
+	{
+		andExpr = makeNode(BoolExpr);
+
+		/* FIXME: ExecQual(false) shortcuts for NULL, this doesn't */
+		andExpr->boolop = AND_EXPR;
+		andExpr->args = qual;
+		andExpr->location = -1;
+		return ExecInitExpr2((Expr *) andExpr, parent, econtext);
+	}
+	else
+	{
+		return ExecInitExpr2((Expr *) linitial(qual), parent, econtext);
+	}
+}
+
+
+ExprState2 *
+ExecInitCheck(List *qual, PlanState *parent, ExprContext *econtext)
+{
+	BoolExpr *andExpr;
+
+	if (qual == NULL)
+		return NULL;
+
+	Assert(IsA(qual, List));
+
+	andExpr = makeNode(BoolExpr);
+
+	andExpr->boolop = AND_EXPR;
+	andExpr->args = qual;
+	andExpr->location = -1;
+
+	return ExecInitExpr2((Expr *) andExpr, parent, econtext);
+}
+
+/* ----------------
+ *		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,
+						PlanState *parent,
+						TupleDesc inputDesc)
+{
+	ProjectionInfo *projInfo = makeNode(ProjectionInfo);
+	ExprEvalStep scratch;
+	ListCell *lc;
+	ExprState2 *state;
+
+	projInfo->pi_slot = slot;
+	projInfo->pi_exprContext = econtext;
+	projInfo->pi_state.econtext = econtext;
+	projInfo->pi_state.tag.type = T_ExprState2;
+	state = &projInfo->pi_state;
+	ExecInitExprSlots(state, econtext, (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, 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)
+		{
+			scratch.opcode = EEO_ASSIGN_VAR;
+			scratch.d.assign_var.slot = varslot(variable, econtext);
+			scratch.d.assign_var.attnum = attnum - 1;
+			scratch.d.assign_var.resultnum = tle->resno - 1;
+			ExprEvalPushStep(state, &scratch);
+		}
+		else
+		{
+			if (!ExecInitExprRec(tle->expr, parent, econtext, state, &state->resvalue, &state->resnull))
+			{
+				elog(ERROR, "could not compute target list");
+			}
+			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);
+	return projInfo;
+}
+
+
+ExprState2 *
+ExecInitExpr2(Expr *node, PlanState *parent, ExprContext *econtext)
+{
+	ExprState2 *state = makeNode(ExprState2);
+	ExprEvalStep scratch;
+
+	if (node == NULL)
+		return NULL;
+
+	state->econtext = econtext;
+
+	ExecInitExprSlots(state, econtext, (Node *) node);
+
+	if (!ExecInitExprRec(node, parent, econtext, state, &state->resvalue, &state->resnull))
+		return NULL;
+	scratch.resvalue = &state->resvalue;
+	scratch.resnull = &state->resnull;
+	scratch.opcode = EEO_DONE;
+	ExprEvalPushStep(state, &scratch);
+	return state;
+}
+
+static bool
+ExecInitExprRec(Expr *node, PlanState *parent, ExprContext *econtext, ExprState2 *state, Datum *resv, bool *resnull)
+{
+	ExprEvalStep scratch;
+
+	Assert(resv != NULL && resnull != NULL);
+	scratch.resvalue = resv;
+	scratch.resnull = resnull;
+	Assert(econtext != NULL);
+
+	switch (nodeTag(node))
+	{
+		/* suppported */
+		case T_Var:
+			/* varattno == InvalidAttrNumber means it's a whole-row Var */
+			if (((Var *) node)->varattno == InvalidAttrNumber)
+			{
+				ExecInitOld(state, parent, node, resv, resnull);
+			}
+			else
+			{
+				Var *variable = (Var *) node;
+				TupleTableSlot **slot = varslot(variable, econtext);
+
+				/* FIXME: consider re-adding type checks */
+				if (variable->varattno <= 0)
+				{
+					scratch.d.var.slot = slot;
+					scratch.d.var.attnum = variable->varattno;
+					scratch.opcode = EEO_SYSVAR;
+				}
+				else
+				{
+					scratch.d.var.slot = slot;
+					scratch.d.var.attnum = variable->varattno - 1;
+					scratch.opcode = EEO_VAR;
+				}
+
+				ExprEvalPushStep(state, &scratch);
+			}
+			break;
+		case T_Const:
+			{
+				Const *con = (Const *) node;
+
+				/* FIXME: skip evaluation */
+				scratch.opcode = EEO_CONST;
+				scratch.d.constval.value = con->constvalue;
+				scratch.d.constval.isnull = con->constisnull;
+
+				ExprEvalPushStep(state, &scratch);
+			}
+			break;
+		case T_FuncExpr:
+			{
+				FuncExpr   *func = (FuncExpr *) node;
+				if (!ExecInitFunc2(&scratch, node, func->args, func->funcid, func->inputcollid,
+								   parent, econtext, state, resv, resnull))
+				{
+					Assert(false);
+					return false;
+				}
+
+				ExprEvalPushStep(state, &scratch);
+			}
+			break;
+		case T_OpExpr:
+			{
+				OpExpr   *op = (OpExpr *) node;
+				if (!ExecInitFunc2(&scratch, node, op->args, op->opfuncid, op->inputcollid,
+								   parent, econtext, state, resv, resnull))
+				{
+					Assert(false);
+					return false;
+				}
+				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;
+
+					if (!ExecInitExprRec(arg, parent, econtext, state,
+										 scratch.d.boolexpr.value,
+										 scratch.d.boolexpr.isnull))
+						return false;
+					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->opcode >= EEO_BOOL_AND_STEP_FIRST &&
+						   as->opcode <= EEO_BOOL_NOT_STEP);
+					Assert(as->d.boolexpr.jumpdone == -1);
+					as->d.boolexpr.jumpdone = state->steps_len;
+				}
+			}
+			break;
+		/* unsupported */
+		case T_Param:
+		case T_CoerceToDomainValue:
+		case T_CaseTestExpr:
+		case T_Aggref:
+		case T_GroupingFunc:
+		case T_WindowFunc:
+		case T_ArrayRef:
+		case T_DistinctExpr:
+		case T_NullIfExpr:
+		case T_ScalarArrayOpExpr:
+		case T_SubPlan:
+		case T_AlternativeSubPlan:
+		case T_FieldSelect:
+		case T_FieldStore:
+		case T_RelabelType:
+		case T_CoerceViaIO:
+		case T_ArrayCoerceExpr:
+		case T_ConvertRowtypeExpr:
+		case T_CaseExpr:
+		case T_ArrayExpr:
+		case T_RowExpr:
+		case T_RowCompareExpr:
+		case T_CoalesceExpr:
+		case T_MinMaxExpr:
+		case T_XmlExpr:
+		case T_NullTest:
+		case T_BooleanTest:
+		case T_CoerceToDomain:
+		case T_CurrentOfExpr:
+			ExecInitOld(state, parent, node, resv, resnull);
+			break;
+		case T_TargetEntry:
+		case T_List:
+			elog(ERROR, "unexpected");
+		default:
+			elog(ERROR, "unrecognized node type: %d",
+				 (int) nodeTag(node));
+			break;
+	}
+	return true;
+}
+
+/*
+ * 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;
+	ExprState2 *state;
+
+	state = &projInfo->pi_state;
+	econtext = state->econtext;
+
+	/* 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;
+	ExecEvalExpr2(state, &isnull);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/*
+	 * Successfully formed a result row.  Mark the result slot as containing a
+	 * valid virtual tuple.
+	 */
+	ExecStoreVirtualTuple(slot);
+}
+
+bool
+ExecQual(ExprState2 *state)
+{
+	MemoryContext oldContext;
+	bool isnull;
+	Datum ret;
+
+	/* error out? */
+	if (state == NULL)
+		return true;
+
+	oldContext = MemoryContextSwitchTo(state->econtext->ecxt_per_tuple_memory);
+
+	ret = ExecEvalExpr2(state, &isnull);
+
+	MemoryContextSwitchTo(oldContext);
+
+	if (isnull)
+		return false;
+	return DatumGetBool(ret);
+}
+
+
+bool
+ExecCheck(ExprState2 *state)
+{
+	MemoryContext oldContext;
+	bool isnull;
+	Datum ret;
+
+	oldContext = MemoryContextSwitchTo(state->econtext->ecxt_per_tuple_memory);
+
+	ret = ExecEvalExpr2(state, &isnull);
+
+	MemoryContextSwitchTo(oldContext);
+
+	if (isnull)
+		return true;
+	return DatumGetBool(ret);
+}
+
+/* FIXME: use computed goto only on gcc / clang */
+#define USE_COMPUTED_GOTO
+
+#ifdef USE_COMPUTED_GOTO
+#define EEO_SWITCH(d)
+#define EEO_DISPATCH(op) goto *dispatch_table[op->opcode];
+#define EEO_CASE(name) CASE_##name
+#else
+#define EEO_SWITCH(d) switch (d)
+#define EEO_DISPATCH(op) goto starteval
+#define EEO_CASE(name) case name
+#endif /* USE_COMPUTED_GOTO */
+
+Datum
+ExecEvalExpr2(ExprState2 *state, bool *isnull)
+{
+	ExprEvalStep *op = state->steps;
+	TupleTableSlot *resultslot = state->resultslot;
+
+#ifdef USE_COMPUTED_GOTO
+	static const void **dispatch_table[] = {
+		&&CASE_EEO_VAR,
+		&&CASE_EEO_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_FETCHSOMEATTRS,
+		&&CASE_EEO_ASSIGN_VAR,
+		&&CASE_EEO_ASSIGN_TMP,
+		&&CASE_EEO_EVAL1,
+		&&CASE_EEO_ASSIGN_EVAL1,
+		&&CASE_EEO_DONE
+	};
+#endif
+
+#ifdef USE_COMPUTED_GOTO
+	EEO_DISPATCH(op);
+#else
+starteval:
+#endif
+	EEO_SWITCH (op->opcode)
+	{
+		EEO_CASE(EEO_VAR):
+			{
+				TupleTableSlot *slot = *op->d.var.slot;
+				int attnum = op->d.var.attnum;
+				/*
+				 * Yuck): can't assert tts_nvalid, wholrow vars or such
+				 * could have materialized the slot - but the contents are
+				 * still valid :/
+				 *
+				 * Assert(op->d.var.attnum <= slot->tts_nvalid);
+				 */
+				Assert(op->d.var.attnum >= 0);
+				*op->resnull = slot->tts_isnull[attnum];
+				*op->resvalue = slot->tts_values[attnum];
+				op++;
+			}
+			EEO_DISPATCH(op);
+		EEO_CASE(EEO_SYSVAR):
+			{
+				TupleTableSlot *slot = *op->d.var.slot;
+				int attnum = op->d.var.attnum;
+				Assert(op->d.var.attnum < 0);
+				*op->resvalue = heap_getsysattr(slot->tts_tuple, attnum,
+												slot->tts_tupleDescriptor,
+												op->resnull);
+				op++;
+			}
+			EEO_DISPATCH(op);
+		EEO_CASE(EEO_CONST):
+			*op->resnull = op->d.constval.isnull;
+			*op->resvalue = op->d.constval.value;
+			op++;
+			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;
+
+				op++;
+			}
+			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:
+				op++;
+			}
+			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);
+				op++;
+			}
+			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);
+				op++;
+			}
+			EEO_DISPATCH(op);
+		EEO_CASE(EEO_BOOL_AND_STEP_FIRST):
+			*op->d.boolexpr.anynull = false;
+		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(op);
+			}
+
+			/* XXX: could skip this step till last */
+			if (*op->d.boolexpr.anynull)
+			{
+				*op->resnull = *op->d.boolexpr.anynull;
+				*op->resvalue = BoolGetDatum(false); /* or null */
+			}
+			op++;
+			EEO_DISPATCH(op);
+		EEO_CASE(EEO_BOOL_OR_STEP_FIRST):
+			*op->d.boolexpr.anynull = false;
+		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;
+			}
+
+			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(op);
+			}
+
+			/* XXX: could skip this step till last */
+			if (*op->d.boolexpr.anynull)
+			{
+				*op->resnull = *op->d.boolexpr.anynull;
+				*op->resvalue = BoolGetDatum(false); /* or null */
+			}
+			op++;
+			EEO_DISPATCH(op);
+		EEO_CASE(EEO_BOOL_NOT_STEP):
+			*op->resvalue = BoolGetDatum(!DatumGetBool(*op->d.boolexpr.value));
+			*op->resnull = *op->d.boolexpr.isnull;
+			op++;
+			EEO_DISPATCH(op);
+		EEO_CASE(EEO_FETCHSOMEATTRS):
+			/* XXX: add check for tts_nvalid here? */
+			slot_getsomeattrs(*op->d.fetch.slot,
+							  op->d.fetch.last_var);
+			op++;
+			EEO_DISPATCH(op);
+		EEO_CASE(EEO_ASSIGN_VAR):
+			{
+				size_t resultnum = op->d.assign_var.resultnum;
+				size_t attnum = op->d.assign_var.attnum;
+				TupleTableSlot *source = *op->d.assign_var.slot;
+				resultslot->tts_values[resultnum] = source->tts_values[attnum];
+				resultslot->tts_isnull[resultnum] = source->tts_isnull[attnum];
+				op++;
+			}
+			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;
+				op++;
+			}
+			EEO_DISPATCH(op);
+		EEO_CASE(EEO_EVAL1):
+			{
+				*op->resvalue = ExecEvalExpr(op->d.eval1.expr,
+											 state->econtext,
+											 op->resnull,
+											 NULL);
+				op++;
+				EEO_DISPATCH(op);
+			}
+		EEO_CASE(EEO_ASSIGN_EVAL1):
+			{
+				size_t resultnum = op->d.assign_eval1.resultnum;
+				resultslot->tts_values[resultnum] =
+					ExecEvalExpr(op->d.assign_eval1.expr,
+								 state->econtext,
+								 &resultslot->tts_isnull[resultnum],
+								 NULL);
+				op++;
+				EEO_DISPATCH(op);
+			}
+		EEO_CASE(EEO_DONE):
+			goto out;
+	}
+out:
+	*isnull = state->resnull;
+	return state->resvalue;
+}
+
+#undef EEO_SWITCH
+#undef EEO_DISPATCH
+#undef EEO_CASE
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 0e2d834..8e1cbba 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;
+			ExprState2 *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))
 				continue;
 		}
 
@@ -550,23 +548,22 @@ ExecCheckIndexConstraints(TupleTableSlot *slot,
 		/* Check for partial index */
 		if (indexInfo->ii_Predicate != NIL)
 		{
-			List	   *predicate;
+			ExprState2 *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))
 				continue;
 		}
 
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..66003c9 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1229,7 +1229,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 
 		resultRelInfo->ri_TrigFunctions = (FmgrInfo *)
 			palloc0(n * sizeof(FmgrInfo));
-		resultRelInfo->ri_TrigWhenExprs = (List **)
+		resultRelInfo->ri_TrigWhenExprs = (ExprState2 **)
 			palloc0(n * sizeof(List *));
 		if (instrument_options)
 			resultRelInfo->ri_TrigInstrument = InstrAlloc(n, instrument_options);
@@ -1640,7 +1640,6 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	ConstrCheck *check = rel->rd_att->constr->check;
 	ExprContext *econtext;
 	MemoryContext oldContext;
-	List	   *qual;
 	int			i;
 
 	/*
@@ -1652,13 +1651,13 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	{
 		oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
 		resultRelInfo->ri_ConstraintExprs =
-			(List **) palloc(ncheck * sizeof(List *));
+			(ExprState2 **) palloc(ncheck * sizeof(ExprState2 *));
 		for (i = 0; i < ncheck; i++)
 		{
+			List *qual;
 			/* ExecQual 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] = ExecPrepareQual(qual, estate);
 		}
 		MemoryContextSwitchTo(oldContext);
 	}
@@ -1675,14 +1674,14 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 	/* And evaluate the constraints */
 	for (i = 0; i < ncheck; i++)
 	{
-		qual = resultRelInfo->ri_ConstraintExprs[i];
+		ExprState2 *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.
 		 */
-		if (!ExecQual(qual, econtext, true))
+		if (!ExecCheck(qual))
 			return check[i].ccname;
 	}
 
@@ -1793,7 +1792,7 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 			l2, resultRelInfo->ri_WithCheckOptionExprs)
 	{
 		WithCheckOption *wco = (WithCheckOption *) lfirst(l1);
-		ExprState  *wcoExpr = (ExprState *) lfirst(l2);
+		ExprState2 *wcoExpr = (ExprState2 *) lfirst(l2);
 
 		/*
 		 * Skip any WCOs which are not the kind we are looking for at this
@@ -1811,7 +1810,7 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 		 * need ExecQual to return FALSE for NULL to handle the view case (the
 		 * opposite of what we do above for CHECK constraints).
 		 */
-		if (!ExecQual((List *) wcoExpr, econtext, false))
+		if (!ExecQual(wcoExpr))
 		{
 			char	   *val_desc;
 			Bitmapset  *modifiedCols;
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index d04d1a8..fb070e4 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -5194,103 +5194,40 @@ ExecPrepareExpr(Expr *node, EState *estate)
 }
 
 
+/*
+ * 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.)
+ */
+ExprState2 *
+ExecPrepareQual(List *qual, EState *estate)
+{
+	ExprState2  *result;
+	MemoryContext oldcontext;
+
+	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+	qual = (List *) expression_planner((Expr *) qual);
+
+	result = ExecInitQual(qual, NULL, GetPerTupleExprContext(estate));
+
+	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, NULL);
-
-		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!)
  */
@@ -5320,296 +5257,3 @@ ExecCleanTargetListLength(List *targetlist)
 	}
 	return len;
 }
-
-/*
- * ExecTargetList
- *		Evaluates a targetlist with respect to the given
- *		expression context.  Returns TRUE if we were able to create
- *		a result, FALSE if we have exhausted a set-valued expression.
- *
- * Results are stored into the passed values and isnull arrays.
- * The caller must provide an itemIsDone array that persists across calls.
- *
- * As with ExecEvalExpr, the caller should pass isDone = NULL if not
- * prepared to deal with sets of result tuples.  Otherwise, a return
- * of *isDone = ExprMultipleResult signifies a set element, and a return
- * of *isDone = ExprEndResult signifies end of the set of tuple.
- * We assume that *isDone has been initialized to ExprSingleResult by caller.
- *
- * 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 bool
-ExecTargetList(List *targetlist,
-			   TupleDesc tupdesc,
-			   ExprContext *econtext,
-			   Datum *values,
-			   bool *isnull,
-			   ExprDoneCond *itemIsDone,
-			   ExprDoneCond *isDone)
-{
-	Form_pg_attribute *att = tupdesc->attrs;
-	MemoryContext oldContext;
-	ListCell   *tl;
-	bool		haveDoneSets;
-
-	/*
-	 * 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
-	 */
-	haveDoneSets = false;		/* any exhausted set exprs in tlist? */
-
-	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],
-									  &itemIsDone[resind]);
-
-		values[resind] = MakeExpandedObjectReadOnly(values[resind],
-													isnull[resind],
-													att[resind]->attlen);
-
-		if (itemIsDone[resind] != ExprSingleResult)
-		{
-			/* We have a set-valued expression in the tlist */
-			if (isDone == NULL)
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("set-valued function called in context that cannot accept a set")));
-			if (itemIsDone[resind] == ExprMultipleResult)
-			{
-				/* we have undone sets in the tlist, set flag */
-				*isDone = ExprMultipleResult;
-			}
-			else
-			{
-				/* we have done sets in the tlist, set flag for that */
-				haveDoneSets = true;
-			}
-		}
-	}
-
-	if (haveDoneSets)
-	{
-		/*
-		 * note: can't get here unless we verified isDone != NULL
-		 */
-		if (*isDone == ExprSingleResult)
-		{
-			/*
-			 * all sets are done, so report that tlist expansion is complete.
-			 */
-			*isDone = ExprEndResult;
-			MemoryContextSwitchTo(oldContext);
-			return false;
-		}
-		else
-		{
-			/*
-			 * We have some done and some undone sets.  Restart the done ones
-			 * so that we can deliver a tuple (if possible).
-			 */
-			foreach(tl, targetlist)
-			{
-				GenericExprState *gstate = (GenericExprState *) lfirst(tl);
-				TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr;
-				AttrNumber	resind = tle->resno - 1;
-
-				if (itemIsDone[resind] == ExprEndResult)
-				{
-					values[resind] = ExecEvalExpr(gstate->arg,
-												  econtext,
-												  &isnull[resind],
-												  &itemIsDone[resind]);
-
-					values[resind] = MakeExpandedObjectReadOnly(values[resind],
-															  isnull[resind],
-														att[resind]->attlen);
-
-					if (itemIsDone[resind] == ExprEndResult)
-					{
-						/*
-						 * Oh dear, this item is returning an empty set. Guess
-						 * we can't make a tuple after all.
-						 */
-						*isDone = ExprEndResult;
-						break;
-					}
-				}
-			}
-
-			/*
-			 * If we cannot make a tuple because some sets are empty, we still
-			 * have to cycle the nonempty sets to completion, else resources
-			 * will not be released from subplans etc.
-			 *
-			 * XXX is that still necessary?
-			 */
-			if (*isDone == ExprEndResult)
-			{
-				foreach(tl, targetlist)
-				{
-					GenericExprState *gstate = (GenericExprState *) lfirst(tl);
-					TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr;
-					AttrNumber	resind = tle->resno - 1;
-
-					while (itemIsDone[resind] == ExprMultipleResult)
-					{
-						values[resind] = ExecEvalExpr(gstate->arg,
-													  econtext,
-													  &isnull[resind],
-													  &itemIsDone[resind]);
-						/* no need for MakeExpandedObjectReadOnly */
-					}
-				}
-
-				MemoryContextSwitchTo(oldContext);
-				return false;
-			}
-		}
-	}
-
-	/* Report success */
-	MemoryContextSwitchTo(oldContext);
-
-	return true;
-}
-
-/*
- * 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, ExprDoneCond *isDone)
-{
-	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;
-
-	/* Assume single result row until proven otherwise */
-	if (isDone)
-		*isDone = ExprSingleResult;
-
-	/*
-	 * 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);
-
-	/*
-	 * 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.  It's possible
-	 * that there are set-returning functions in such expressions; if so and
-	 * we have reached the end of the set, we return the result slot, which we
-	 * already marked empty.
-	 */
-	if (projInfo->pi_targetlist)
-	{
-		if (!ExecTargetList(projInfo->pi_targetlist,
-							slot->tts_tupleDescriptor,
-							econtext,
-							slot->tts_values,
-							slot->tts_isnull,
-							projInfo->pi_itemIsDone,
-							isDone))
-			return slot;		/* no more result rows, return empty slot */
-	}
-
-	/*
-	 * Successfully formed a result row.  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 fb0013d..1cb33ee 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;
+	ExprState2 *qual;
 	ProjectionInfo *projInfo;
 	ExprDoneCond isDone;
 	TupleTableSlot *resultSlot;
@@ -205,7 +205,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))
 		{
 			/*
 			 * Found a satisfactory scan tuple.
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index a3a59c0..0fa402f 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -54,7 +54,6 @@ typedef struct LastLastAttnumInfo
 	AttrNumber last_scan;
 } LastAttnumInfo;
 
-
 static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
 
 
@@ -475,142 +474,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;
-
-	if (exprlist == NIL)
-		projInfo->pi_itemIsDone = NULL; /* not needed */
-	else
-		projInfo->pi_itemIsDone = (ExprDoneCond *)
-			palloc(len * sizeof(ExprDoneCond));
-
-	return projInfo;
-}
-
 /*
  * get_last_attnums_walker: expression walker for ExecBuildProjectionInfo
  *
@@ -691,9 +554,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 b3187e6..5046da4 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -1571,12 +1571,10 @@ finalize_aggregates(AggState *aggstate,
 static TupleTableSlot *
 project_aggregates(AggState *aggstate)
 {
-	ExprContext *econtext = aggstate->ss.ps.ps_ExprContext;
-
 	/*
 	 * 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))
 	{
 		/*
 		 * Form and return or store a projection tuple using the aggregate
@@ -2414,9 +2412,10 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	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,
+					 aggstate->ss.ps.ps_ExprContext);
 
 	/*
 	 * Initialize child nodes.
@@ -3094,9 +3093,10 @@ build_pertrans_for_aggref(AggStatePerTrans pertrans,
 				 errmsg("aggregate function calls cannot be nested")));
 
 	/* Set up projection info for evaluation */
-	pertrans->evalproj = ExecBuildProjectionInfo(pertrans->args,
+	pertrans->evalproj = ExecBuildProjectionInfo(aggref->args,
 												 aggstate->tmpcontext,
 												 pertrans->evalslot,
+												 (PlanState *) aggstate,
 												 NULL);
 
 	/*
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 449aacb..f6cc996 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -286,7 +286,7 @@ BitmapHeapNext(BitmapHeapScanState *node)
 			econtext->ecxt_scantuple = slot;
 			ResetExprContext(econtext);
 
-			if (!ExecQual(node->bitmapqualorig, econtext, false))
+			if (!ExecQual(node->bitmapqualorig))
 			{
 				/* Fails recheck, so drop it and loop back for another */
 				InstrCountFiltered2(node, 1);
@@ -427,7 +427,7 @@ BitmapHeapRecheck(BitmapHeapScanState *node, TupleTableSlot *slot)
 
 	ResetExprContext(econtext);
 
-	return ExecQual(node->bitmapqualorig, econtext, false);
+	return ExecQual(node->bitmapqualorig);
 }
 
 /* ----------------------------------------------------------------
@@ -583,12 +583,14 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
 	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->ss.ps.ps_ExprContext);
+	scanstate->bitmapqualorig =
+		ExecInitQual(node->bitmapqualorig,
+					 (PlanState *) scanstate,
+					 scanstate->ss.ps.ps_ExprContext);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeCtescan.c b/src/backend/executor/nodeCtescan.c
index 3c2f684..f723f37 100644
--- a/src/backend/executor/nodeCtescan.c
+++ b/src/backend/executor/nodeCtescan.c
@@ -242,9 +242,10 @@ ExecInitCteScan(CteScan *node, EState *estate, int eflags)
 	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->ss.ps.ps_ExprContext);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
index 322abca..bdd7039 100644
--- a/src/backend/executor/nodeCustom.c
+++ b/src/backend/executor/nodeCustom.c
@@ -54,9 +54,10 @@ ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags)
 	css->ss.ps.targetlist = (List *)
 		ExecInitExpr((Expr *) cscan->scan.plan.targetlist,
 					 (PlanState *) css);
-	css->ss.ps.qual = (List *)
-		ExecInitExpr((Expr *) cscan->scan.plan.qual,
-					 (PlanState *) css);
+	css->ss.ps.qual =
+		ExecInitQual(cscan->scan.plan.qual,
+					 (PlanState *) css,
+					 css->ss.ps.ps_ExprContext);
 
 	/* tuple table initialization */
 	ExecInitScanTupleSlot(estate, &css->ss);
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index d886aaf..387320e 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);
 }
 
 /* ----------------------------------------------------------------
@@ -160,12 +160,14 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 	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->ss.ps.ps_ExprContext);
+	scanstate->fdw_recheck_quals =
+		ExecInitQual(node->fdw_recheck_quals,
+					 (PlanState *) scanstate,
+					 scanstate->ss.ps.ps_ExprContext);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index a03f6e7..47b890a 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -345,9 +345,10 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	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->ss.ps.ps_ExprContext);
 
 	scanstate->funcstates = palloc(nfuncs * sizeof(FunctionScanPerFuncState));
 
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 313b234..1b933ae 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -83,9 +83,10 @@ ExecInitGather(Gather *node, EState *estate, int eflags)
 	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,
+					 gatherstate->ps.ps_ExprContext);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c
index dcf5175..1d61bc2 100644
--- a/src/backend/executor/nodeGroup.c
+++ b/src/backend/executor/nodeGroup.c
@@ -102,7 +102,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))
 		{
 			/*
 			 * Form and return a projection tuple using the first input tuple.
@@ -165,7 +165,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))
 		{
 			/*
 			 * Form and return a projection tuple using the first input tuple.
@@ -226,9 +226,10 @@ ExecInitGroup(Group *node, EState *estate, int eflags)
 	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,
+					 grpstate->ss.ps.ps_ExprContext);
 
 	/*
 	 * initialize child nodes
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index 9ed09a7..85be8c2 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -193,9 +193,10 @@ ExecInitHash(Hash *node, EState *estate, int eflags)
 	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,
+					 hashstate->ps.ps_ExprContext);
 
 	/*
 	 * initialize child nodes
@@ -1064,7 +1065,7 @@ bool
 ExecScanHashBucket(HashJoinState *hjstate,
 				   ExprContext *econtext)
 {
-	List	   *hjclauses = hjstate->hashclauses;
+	ExprState2 *hjclauses = hjstate->hashclauses;
 	HashJoinTable hashtable = hjstate->hj_HashTable;
 	HashJoinTuple hashTuple = hjstate->hj_CurTuple;
 	uint32		hashvalue = hjstate->hj_CurHashValue;
@@ -1098,7 +1099,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))
 			{
 				hjstate->hj_CurTuple = hashTuple;
 				return true;
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 369e666..72044b6 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;
+	ExprState2 *joinqual;
+	ExprState2 *otherqual;
 	ExprContext *econtext;
 	ExprDoneCond isDone;
 	HashJoinTable hashtable;
@@ -293,7 +293,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))
 				{
 					node->hj_MatchedOuter = true;
 					HeapTupleHeaderSetMatch(HJTUPLE_MINTUPLE(node->hj_CurTuple));
@@ -312,8 +312,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))
 					{
 						TupleTableSlot *result;
 
@@ -351,8 +350,7 @@ ExecHashJoin(HashJoinState *node)
 					 */
 					econtext->ecxt_innertuple = node->hj_NullInnerTupleSlot;
 
-					if (otherqual == NIL ||
-						ExecQual(otherqual, econtext, false))
+					if (otherqual == NULL || ExecQual(otherqual))
 					{
 						TupleTableSlot *result;
 
@@ -390,8 +388,7 @@ ExecHashJoin(HashJoinState *node)
 				 */
 				econtext->ecxt_outertuple = node->hj_NullOuterTupleSlot;
 
-				if (otherqual == NIL ||
-					ExecQual(otherqual, econtext, false))
+				if (otherqual == NULL || ExecQual(otherqual))
 				{
 					TupleTableSlot *result;
 
@@ -465,16 +462,19 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
 	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.ps.ps_ExprContext);
 	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->js.ps.ps_ExprContext);
+	hjstate->hashclauses =
+		ExecInitQual(node->hashclauses,
+					 (PlanState *) hjstate,
+					 hjstate->js.ps.ps_ExprContext);
 
 	/*
 	 * initialize child nodes
@@ -568,16 +568,13 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
 	lclauses = NIL;
 	rclauses = NIL;
 	hoperators = NIL;
-	foreach(l, hjstate->hashclauses)
+	foreach(l, node->hashclauses)
 	{
-		FuncExprState *fstate = (FuncExprState *) lfirst(l);
-		OpExpr	   *hclause;
+		OpExpr	   *hclause =  (OpExpr *) lfirst(l);;
 
-		Assert(IsA(fstate, FuncExprState));
-		hclause = (OpExpr *) fstate->xprstate.expr;
 		Assert(IsA(hclause, OpExpr));
-		lclauses = lappend(lclauses, linitial(fstate->args));
-		rclauses = lappend(rclauses, lsecond(fstate->args));
+		lclauses = lappend(lclauses, ExecInitExpr(linitial(hclause->args), (PlanState *) hjstate));
+		rclauses = lappend(rclauses, ExecInitExpr(lsecond(hclause->args), (PlanState *) hjstate));
 		hoperators = lappend_oid(hoperators, hclause->opno);
 	}
 	hjstate->hj_OuterHashKeys = lclauses;
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index 4f6f91c..a3e38df 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -157,7 +157,7 @@ IndexOnlyNext(IndexOnlyScanState *node)
 		{
 			econtext->ecxt_scantuple = slot;
 			ResetExprContext(econtext);
-			if (!ExecQual(node->indexqual, econtext, false))
+			if (!ExecQual(node->indexqual))
 			{
 				/* Fails recheck, so drop it and loop back for another */
 				InstrCountFiltered2(node, 1);
@@ -423,12 +423,14 @@ ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags)
 	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->ss.ps.ps_ExprContext);
+	indexstate->indexqual =
+		ExecInitQual(node->indexqual,
+					 (PlanState *) indexstate,
+					 indexstate->ss.ps.ps_ExprContext);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 3143bd9..b487119 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -122,7 +122,7 @@ IndexNext(IndexScanState *node)
 		{
 			econtext->ecxt_scantuple = slot;
 			ResetExprContext(econtext);
-			if (!ExecQual(node->indexqualorig, econtext, false))
+			if (!ExecQual(node->indexqualorig))
 			{
 				/* Fails recheck, so drop it and loop back for another */
 				InstrCountFiltered2(node, 1);
@@ -241,7 +241,7 @@ next_indextuple:
 		{
 			econtext->ecxt_scantuple = slot;
 			ResetExprContext(econtext);
-			if (!ExecQual(node->indexqualorig, econtext, false))
+			if (!ExecQual(node->indexqualorig))
 			{
 				/* Fails recheck, so drop it and loop back for another */
 				InstrCountFiltered2(node, 1);
@@ -362,7 +362,7 @@ IndexRecheck(IndexScanState *node, TupleTableSlot *slot)
 
 	ResetExprContext(econtext);
 
-	return ExecQual(node->indexqualorig, econtext, false);
+	return ExecQual(node->indexqualorig);
 }
 
 
@@ -852,12 +852,14 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
 	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->ss.ps.qual =
+		ExecInitQual(node->scan.plan.qual,
+					 (PlanState *) indexstate,
+					 indexstate->ss.ps.ps_ExprContext);
+	indexstate->indexqualorig =
+		ExecInitQual(node->indexqualorig,
+					 (PlanState *) indexstate,
+					 indexstate->ss.ps.ps_ExprContext);
 	indexstate->indexorderbyorig = (List *)
 		ExecInitExpr((Expr *) node->indexorderbyorig,
 					 (PlanState *) indexstate);
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index 6db09b8..3e7c500 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;
+	ExprState2 *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))
 	{
 		/*
 		 * qualification succeeded.  now form the desired projection tuple and
@@ -493,14 +493,14 @@ static TupleTableSlot *
 MJFillInner(MergeJoinState *node)
 {
 	ExprContext *econtext = node->js.ps.ps_ExprContext;
-	List	   *otherqual = node->js.ps.qual;
+	ExprState2 *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))
 	{
 		/*
 		 * qualification succeeded.  now form the desired projection tuple and
@@ -618,8 +618,8 @@ ExecMergeTupleDump(MergeJoinState *mergestate)
 TupleTableSlot *
 ExecMergeJoin(MergeJoinState *node)
 {
-	List	   *joinqual;
-	List	   *otherqual;
+	ExprState2 *joinqual;
+	ExprState2 *otherqual;
 	bool		qualResult;
 	int			compareResult;
 	PlanState  *innerPlan;
@@ -823,8 +823,7 @@ ExecMergeJoin(MergeJoinState *node)
 				innerTupleSlot = node->mj_InnerTupleSlot;
 				econtext->ecxt_innertuple = innerTupleSlot;
 
-				qualResult = (joinqual == NIL ||
-							  ExecQual(joinqual, econtext, false));
+				qualResult = (joinqual == NULL || ExecQual(joinqual));
 				MJ_DEBUG_QUAL(joinqual, qualResult);
 
 				if (qualResult)
@@ -846,8 +845,7 @@ 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));
 					MJ_DEBUG_QUAL(otherqual, qualResult);
 
 					if (qualResult)
@@ -1507,13 +1505,15 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 	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.ps.ps_ExprContext);
 	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->js.ps.ps_ExprContext);
 	mergestate->mj_ConstFalseJoin = false;
 	/* mergeclauses are handled below */
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..8255688 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1067,7 +1067,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 {
 	ExprContext *econtext = mtstate->ps.ps_ExprContext;
 	Relation	relation = resultRelInfo->ri_RelationDesc;
-	List	   *onConflictSetWhere = resultRelInfo->ri_onConflictSetWhere;
+	ExprState2 *onConflictSetWhere = resultRelInfo->ri_onConflictSetWhere;
 	HeapTupleData tuple;
 	HeapUpdateFailureData hufd;
 	LockTupleMode lockmode;
@@ -1186,7 +1186,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	econtext->ecxt_innertuple = excludedSlot;
 	econtext->ecxt_outertuple = NULL;
 
-	if (!ExecQual(onConflictSetWhere, econtext, false))
+	if (!ExecQual(onConflictSetWhere))
 	{
 		ReleaseBuffer(buffer);
 		InstrCountFiltered1(&mtstate->ps, 1);
@@ -1654,8 +1654,9 @@ 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]);
+			ExprState2  *wcoExpr = ExecInitQual((List *) wco->qual,
+												mtstate->mt_plans[i],
+												GetPerTupleExprContext(estate));
 
 			wcoExprs = lappend(wcoExprs, wcoExpr);
 		}
@@ -1687,8 +1688,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.
@@ -1697,12 +1699,10 @@ 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,
-									 resultRelInfo->ri_RelationDesc->rd_att);
+				ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
+										resultRelInfo->ri_RelationDesc->rd_att);
 			resultRelInfo++;
 		}
 	}
@@ -1727,7 +1727,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 */
@@ -1753,22 +1752,21 @@ 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 */
 		if (node->onConflictWhere)
 		{
-			ExprState  *qualexpr;
+			ExprState2 *qualexpr;
 
-			qualexpr = ExecInitExpr((Expr *) node->onConflictWhere,
-									&mtstate->ps);
+			qualexpr = ExecInitQual((List *) node->onConflictWhere,
+									&mtstate->ps, econtext);
 
-			resultRelInfo->ri_onConflictSetWhere = (List *) qualexpr;
+			resultRelInfo->ri_onConflictSetWhere = qualexpr;
 		}
 	}
 
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 555fa09..6a8b674 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;
+	ExprState2 *joinqual;
+	ExprState2 *otherqual;
 	ExprContext *econtext;
 	ListCell   *lc;
 
@@ -194,7 +194,7 @@ ExecNestLoop(NestLoopState *node)
 
 				ENL1_printf("testing qualification for outer-join tuple");
 
-				if (otherqual == NIL || ExecQual(otherqual, econtext, false))
+				if (otherqual == NULL || ExecQual(otherqual))
 				{
 					/*
 					 * qualification was satisfied so we project and return
@@ -202,11 +202,12 @@ ExecNestLoop(NestLoopState *node)
 					 * ExecProject().
 					 */
 					TupleTableSlot *result;
-					ExprDoneCond isDone;
+					ExprDoneCond isDone = ExprSingleResult;
 
 					ENL1_printf("qualification succeeded, projecting tuple");
 
-					result = ExecProject(node->js.ps.ps_ProjInfo, &isDone);
+					result = node->js.ps.ps_ResultTupleSlot;
+					ExecProjectIntoSlot(node->js.ps.ps_ProjInfo, result);
 
 					if (isDone != ExprEndResult)
 					{
@@ -235,7 +236,7 @@ ExecNestLoop(NestLoopState *node)
 		 */
 		ENL1_printf("testing qualification");
 
-		if (ExecQual(joinqual, econtext, false))
+		if (ExecQual(joinqual))
 		{
 			node->nl_MatchedOuter = true;
 
@@ -253,7 +254,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))
 			{
 				/*
 				 * qualification was satisfied so we project and return the
@@ -323,13 +324,15 @@ ExecInitNestLoop(NestLoop *node, EState *estate, int eflags)
 	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.ps.ps_ExprContext);
 	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,
+					 nlstate->js.ps.ps_ExprContext);
 
 	/*
 	 * initialize child nodes
diff --git a/src/backend/executor/nodeResult.c b/src/backend/executor/nodeResult.c
index 4007b76..41d53f2 100644
--- a/src/backend/executor/nodeResult.c
+++ b/src/backend/executor/nodeResult.c
@@ -79,9 +79,7 @@ ExecResult(ResultState *node)
 	 */
 	if (node->rs_checkqual)
 	{
-		bool		qualResult = ExecQual((List *) node->resconstantqual,
-										  econtext,
-										  false);
+		bool		qualResult = ExecQual(node->resconstantqual);
 
 		node->rs_checkqual = false;
 		if (!qualResult)
@@ -241,11 +239,14 @@ ExecInitResult(Result *node, EState *estate, int eflags)
 	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->ps.ps_ExprContext);
+	resstate->resconstantqual =
+		ExecInitQual((List *) node->resconstantqual,
+					  (PlanState *) resstate,
+					  resstate->ps.ps_ExprContext);
 
 	/*
 	 * initialize child nodes
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 9ce7c02..6ebc330 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -166,9 +166,10 @@ ExecInitSampleScan(SampleScan *node, EState *estate, int eflags)
 	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->ss.ps.ps_ExprContext);
 
 	scanstate->args = (List *)
 		ExecInitExpr((Expr *) tsc->args,
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 00bf3a5..13a84c5 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -191,9 +191,10 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags)
 	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,
+					 scanstate->ss.ps.ps_ExprContext);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index e503494..30c0e33 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -126,7 +126,9 @@ ExecHashSubPlan(SubPlanState *node,
 	/*
 	 * Evaluate lefthand expressions and form a projection tuple. First we
 	 * have to set the econtext to use (hack alert!).
+	 * FIXME: unlikely to still work
 	 */
+	elog(ERROR, "borked atm");
 	node->projLeft->pi_exprContext = econtext;
 	slot = ExecProject(node->projLeft, NULL);
 
@@ -908,21 +910,25 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 		 * Fortunately we can just pass NULL for now and fill it in later
 		 * (hack alert!).  The righthand expressions will be evaluated in our
 		 * own innerecontext.
+		 *
+		 * FIXME
 		 */
 		tupDesc = ExecTypeFromTL(leftptlist, false);
 		slot = ExecInitExtraTupleSlot(estate);
 		ExecSetSlotDescriptor(slot, tupDesc);
-		sstate->projLeft = ExecBuildProjectionInfo(lefttlist,
-												   NULL,
+		sstate->projLeft = ExecBuildProjectionInfo(leftptlist,
+												   sstate->innerecontext /* FIXME */,
 												   slot,
+												   sstate->planstate,
 												   NULL);
 
 		tupDesc = ExecTypeFromTL(rightptlist, false);
 		slot = ExecInitExtraTupleSlot(estate);
 		ExecSetSlotDescriptor(slot, tupDesc);
-		sstate->projRight = ExecBuildProjectionInfo(righttlist,
+		sstate->projRight = ExecBuildProjectionInfo(rightptlist,
 													sstate->innerecontext,
 													slot,
+													sstate->planstate,
 													NULL);
 	}
 
diff --git a/src/backend/executor/nodeSubqueryscan.c b/src/backend/executor/nodeSubqueryscan.c
index 9bafc62..1e3a228 100644
--- a/src/backend/executor/nodeSubqueryscan.c
+++ b/src/backend/executor/nodeSubqueryscan.c
@@ -123,9 +123,10 @@ ExecInitSubqueryScan(SubqueryScan *node, EState *estate, int eflags)
 	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,
+					 subquerystate->ss.ps.ps_ExprContext);
 
 	/*
 	 * tuple table initialization
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index 2604103..e47e280 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -477,9 +477,10 @@ ExecInitTidScan(TidScan *node, EState *estate, int eflags)
 	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->ss.ps.qual =
+		ExecInitQual(node->scan.plan.qual,
+					 (PlanState *) tidstate,
+					 tidstate->ss.ps.ps_ExprContext);
 
 	tidstate->tss_tidquals = (List *)
 		ExecInitExpr((Expr *) node->tidquals,
diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c
index 9c03f8a..0f3ef05 100644
--- a/src/backend/executor/nodeValuesscan.c
+++ b/src/backend/executor/nodeValuesscan.c
@@ -246,9 +246,10 @@ ExecInitValuesScan(ValuesScan *node, EState *estate, int eflags)
 	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->ss.ps.ps_ExprContext);
 
 	/*
 	 * get info about values list
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index d4c88a1..d2dfcb4 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -1838,7 +1838,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	 * 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
diff --git a/src/backend/executor/nodeWorktablescan.c b/src/backend/executor/nodeWorktablescan.c
index cfed6e6..352d1cd 100644
--- a/src/backend/executor/nodeWorktablescan.c
+++ b/src/backend/executor/nodeWorktablescan.c
@@ -159,9 +159,10 @@ ExecInitWorkTableScan(WorkTableScan *node, EState *estate, int eflags)
 	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->ss.ps.ps_ExprContext);
 
 	/*
 	 * tuple table initialization
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index d4a5e7c..4b9d0a2 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -2012,8 +2012,8 @@ setup_privileges(FILE *cmdfd)
 		"UPDATE pg_class "
 		"  SET relacl = (SELECT array_agg(a.acl) FROM "
 		" (SELECT E'=r/\"$POSTGRES_SUPERUSERNAME\"' as acl "
-		"  UNION SELECT unnest(pg_catalog.acldefault("
-		"    CASE WHEN relkind = 'S' THEN 's' ELSE 'r' END::\"char\",10::oid))"
+		"  UNION SELECT * FROM unnest(pg_catalog.acldefault("
+		"    CASE WHEN relkind = 'S' THEN 's' ELSE 'r' END::\"char\",10::oid)) un"
 		" ) as a) "
 		"  WHERE relkind IN ('r', 'v', 'm', 'S') AND relacl IS NULL;\n\n",
 		"GRANT USAGE ON SCHEMA pg_catalog TO PUBLIC;\n\n",
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index e5c0a56..8766d09 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -243,11 +243,41 @@ extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econt
 						  bool *isNull, ExprDoneCond *isDone);
 extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
 extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
-extern bool ExecQual(List *qual, ExprContext *econtext, bool resultForNull);
+extern ExprState2 *ExecInitExpr2(Expr *node, PlanState *parent, ExprContext *context);
+extern ExprState2 *ExecPrepareQual(List *qual, EState *estate);
+extern ExprState2 *ExecPrepareCheck(List *qual, EState *estate);
+extern ExprState2 *ExecInitQual(List *qual, PlanState *parent, ExprContext *context);
+extern ExprState2 *ExecInitCheck(List *qual, PlanState *parent, ExprContext *context);
+
+extern Datum ExecEvalExpr2(ExprState2 *state, bool *isnull);
+extern bool ExecQual(ExprState2 *state);
+extern bool ExecCheck(ExprState2 *state);
 extern int	ExecTargetListLength(List *targetlist);
 extern int	ExecCleanTargetListLength(List *targetlist);
-extern TupleTableSlot *ExecProject(ProjectionInfo *projInfo,
-			ExprDoneCond *isDone);
+
+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.
+ */
+static inline TupleTableSlot *
+ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone)
+{
+	if (isDone)
+		*isDone = ExprSingleResult;
+	ExecProjectIntoSlot(projInfo, projInfo->pi_slot);
+	return projInfo->pi_slot;
+}
 
 /*
  * prototypes from functions in execScan.c
@@ -345,6 +375,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/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e7fd7bd..b083629 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -26,6 +26,112 @@
 #include "utils/tuplesort.h"
 
 
+typedef struct ExprState ExprState;
+typedef struct ExprState2 ExprState2;
+
+
+
+typedef enum ExprEvalOp
+{
+	EEO_VAR,
+	EEO_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_FETCHSOMEATTRS,
+	EEO_ASSIGN_VAR,
+	EEO_ASSIGN_TMP,
+	EEO_EVAL1,
+	EEO_ASSIGN_EVAL1,
+	EEO_DONE
+} ExprEvalOp;
+
+typedef struct ExprEvalStep
+{
+	ExprEvalOp opcode;
+	bool *resnull;
+	Datum *resvalue;
+	union
+	{
+		struct
+		{
+			TupleTableSlot **slot;
+			int attnum;
+		} var;
+
+		struct
+		{
+			Datum value;
+			bool isnull;
+		} constval;
+
+		struct
+		{
+			FmgrInfo	*finfo;
+			FunctionCallInfo fcinfo_data;
+			PGFunction	fn_addr;
+			int nargs;
+		} func;
+
+		struct
+		{
+			Datum *value;
+			bool *isnull;
+			bool *anynull;
+			int jumpdone;
+		} boolexpr;
+
+		struct
+		{
+			TupleTableSlot **slot;
+			size_t resultnum;
+			int attnum;
+		} assign_var;
+
+		struct
+		{
+			size_t resultnum;
+		} assign_tmp;
+
+		struct
+		{
+			TupleTableSlot **slot;
+			int last_var;
+		} fetch;
+
+		struct
+		{
+			ExprState *expr;
+		} eval1;
+
+		struct
+		{
+			ExprState *expr;
+			size_t resultnum;
+		} assign_eval1;
+	} d;
+} ExprEvalStep;
+
+struct ExprState2
+{
+	Node tag;
+	bool failed;
+	size_t steps_alloc;
+	size_t steps_len;
+	ExprEvalStep *steps;
+	Datum resvalue;
+	bool resnull;
+	struct ExprContext *econtext;
+	TupleTableSlot *resultslot;
+};
+
 /* ----------------
  *	  IndexInfo information
  *
@@ -63,7 +169,7 @@ typedef struct IndexInfo
 	List	   *ii_Expressions; /* list of Expr */
 	List	   *ii_ExpressionsState;	/* list of ExprState */
 	List	   *ii_Predicate;	/* list of Expr */
-	List	   *ii_PredicateState;		/* list of ExprState */
+	ExprState2 *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 */
@@ -205,53 +311,16 @@ typedef struct ReturnSetInfo
  *		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)
  *		exprContext		expression context in which to evaluate targetlist
  *		slot			slot to place projection result in
- *		itemIsDone		workspace array for ExecProject
- *		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;
+	ExprState2 pi_state;
 	ExprContext *pi_exprContext;
 	TupleTableSlot *pi_slot;
-	ExprDoneCond *pi_itemIsDone;
-	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;
 
 /* ----------------
@@ -331,18 +400,18 @@ typedef struct ResultRelInfo
 	IndexInfo **ri_IndexRelationInfo;
 	TriggerDesc *ri_TrigDesc;
 	FmgrInfo   *ri_TrigFunctions;
-	List	  **ri_TrigWhenExprs;
+	ExprState2 **ri_TrigWhenExprs;
 	Instrumentation *ri_TrigInstrument;
 	struct FdwRoutine *ri_FdwRoutine;
 	void	   *ri_FdwState;
 	bool		ri_usesFdwDirectModify;
 	List	   *ri_WithCheckOptions;
 	List	   *ri_WithCheckOptionExprs;
-	List	  **ri_ConstraintExprs;
+	ExprState2 **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
 	ProjectionInfo *ri_onConflictSetProj;
-	List	   *ri_onConflictSetWhere;
+	ExprState2 *ri_onConflictSetWhere;
 } ResultRelInfo;
 
 /* ----------------
@@ -1039,7 +1108,7 @@ typedef struct PlanState
 	 * 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 */
+	ExprState2 *qual;			/* implicitly-ANDed qual conditions */
 	struct PlanState *lefttree; /* input plan tree(s) */
 	struct PlanState *righttree;
 	List	   *initPlan;		/* Init SubPlanState nodes (un-correlated expr
@@ -1106,7 +1175,7 @@ typedef struct EPQState
 typedef struct ResultState
 {
 	PlanState	ps;				/* its first field is NodeTag */
-	ExprState  *resconstantqual;
+	ExprState2 *resconstantqual;
 	bool		rs_done;		/* are we done? */
 	bool		rs_checkqual;	/* do we need to check the qual? */
 } ResultState;
@@ -1329,7 +1398,7 @@ typedef struct
 typedef struct IndexScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
-	List	   *indexqualorig;
+	ExprState2 *indexqualorig;
 	List	   *indexorderbyorig;
 	ScanKey		iss_ScanKeys;
 	int			iss_NumScanKeys;
@@ -1373,7 +1442,7 @@ typedef struct IndexScanState
 typedef struct IndexOnlyScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
-	List	   *indexqual;
+	ExprState2 *indexqual;
 	ScanKey		ioss_ScanKeys;
 	int			ioss_NumScanKeys;
 	ScanKey		ioss_OrderByKeys;
@@ -1438,7 +1507,7 @@ typedef struct BitmapIndexScanState
 typedef struct BitmapHeapScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
-	List	   *bitmapqualorig;
+	ExprState2 *bitmapqualorig;
 	TIDBitmap  *tbm;
 	TBMIterator *tbmiterator;
 	TBMIterateResult *tbmres;
@@ -1462,7 +1531,7 @@ typedef struct BitmapHeapScanState
 typedef struct TidScanState
 {
 	ScanState	ss;				/* its first field is NodeTag */
-	List	   *tss_tidquals;	/* list of ExprState nodes */
+	List	   *tss_tidquals;
 	bool		tss_isCurrentOf;
 	int			tss_NumTids;
 	int			tss_TidPtr;
@@ -1586,7 +1655,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 */
+	ExprState2 *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;
@@ -1632,7 +1701,7 @@ typedef struct JoinState
 {
 	PlanState	ps;
 	JoinType	jointype;
-	List	   *joinqual;		/* JOIN quals (in addition to ps.qual) */
+	ExprState2 *joinqual;		/* JOIN quals (in addition to ps.qual) */
 } JoinState;
 
 /* ----------------
@@ -1730,7 +1799,7 @@ typedef struct HashJoinTableData *HashJoinTable;
 typedef struct HashJoinState
 {
 	JoinState	js;				/* its first field is NodeTag */
-	List	   *hashclauses;	/* list of ExprState nodes */
+	ExprState2 *hashclauses;
 	List	   *hj_OuterHashKeys;		/* list of ExprState nodes */
 	List	   *hj_InnerHashKeys;		/* list of ExprState nodes */
 	List	   *hj_HashOperators;		/* list of operator OIDs */
@@ -1852,6 +1921,12 @@ typedef struct AggState
 	/* these fields are used in AGG_HASHED mode: */
 	TupleHashTable hashtable;	/* hash table with one entry per group */
 	TupleTableSlot *hashslot;	/* slot for loading hash table */
+
+	/* Stuff for evaluation of agg inputs. */
+	TupleTableSlot *evalslot;	/* slot for agg inputs */
+	ProjectionInfo *evalproj;	/* projection machinery */
+	TupleDesc	evaldesc;		/* descriptor of input tuples */
+
 	List	   *hash_needed;	/* list of columns needed in hash table */
 	bool		table_filled;	/* hash table filled yet? */
 	TupleHashIterator hashiter; /* for iterating through hash table */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6b850e4..0f503d3 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -33,6 +33,7 @@ typedef enum NodeTag
 	T_IndexInfo = 10,
 	T_ExprContext,
 	T_ProjectionInfo,
+	T_FastProjectionInfo,
 	T_JunkFilter,
 	T_ResultRelInfo,
 	T_EState,
@@ -188,6 +189,7 @@ typedef enum NodeTag
 	 * from Expr.
 	 */
 	T_ExprState = 400,
+	T_ExprState2,
 	T_GenericExprState,
 	T_WholeRowVarExprState,
 	T_AggrefExprState,
-- 
2.8.1

>From 24017512fbf34efaf48961fae3798407ac59a390 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Sun, 3 Jul 2016 15:10:13 -0700
Subject: [PATCH 04/20] WIP: Optimize slot_deform_tuple() significantly.

Todo:
* benchmark computed goto vs. switch
* if computed goto comes ahead, only optionally use it
---
 src/backend/access/common/heaptuple.c | 562 ++++++++++++++++++++++++++++++++--
 src/backend/executor/execMain.c       |   3 +
 src/backend/executor/execTuples.c     |  21 ++
 src/include/executor/tuptable.h       |  54 ++++
 4 files changed, 614 insertions(+), 26 deletions(-)

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 6d0f3f3..256243a 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -70,6 +70,8 @@
 #define VARLENA_ATT_IS_PACKABLE(att) \
 	((att)->attstorage != 'p')
 
+static void slot_deform_tuple(TupleTableSlot *slot, int natts);
+
 
 /* ----------------------------------------------------------------
  *						misc support routines
@@ -953,6 +955,192 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 	}
 }
 
+static void
+slot_push_deform_step(TupleTableSlot *slot, DeparseSlotState *dp)
+{
+	if (slot->tts_dp_alloc == 0)
+	{
+		slot->tts_dp =  MemoryContextAllocZero(slot->tts_mcxt,
+											   sizeof(DeparseSlotState) * 16);
+		slot->tts_dp_alloc = 16;
+	}
+	else if (slot->tts_dp_len == slot->tts_dp_alloc)
+	{
+		slot->tts_dp = repalloc(slot->tts_dp,
+								sizeof(DeparseSlotState) * slot->tts_dp_alloc * 2);
+		slot->tts_dp_alloc *= 2;
+	}
+
+	memcpy(&slot->tts_dp[slot->tts_dp_len++], dp, sizeof(DeparseSlotState));
+}
+
+void
+slot_prepare_deform(TupleTableSlot *slot)
+{
+	DeparseSlotState curdp;
+	TupleDesc desc = slot->tts_tupleDescriptor;
+	int natt;
+	int attcuralign = 0;
+
+	for (natt = 0; natt < desc->natts; natt++)
+	{
+		Form_pg_attribute att = desc->attrs[natt];
+
+		/* reset */
+		memset(&curdp, 0, sizeof(curdp));
+
+		curdp.attlen = att->attlen;
+		curdp.attbyval = att->attbyval;
+
+		if (att->attbyval)
+		{
+			switch (att->attlen)
+			{
+				case 1:
+					Assert(att->attalign = 'c');
+					curdp.attopcode = DO_BYVAL_1;
+					curdp.alignto = 1;
+					break;
+				case 2:
+					Assert(att->attalign = 's');
+					curdp.attopcode = DO_BYVAL_2;
+					curdp.alignto = ALIGNOF_SHORT;
+					break;
+				case 4:
+					Assert(att->attalign = 'i');
+					curdp.attopcode = DO_BYVAL_4;
+					curdp.alignto = ALIGNOF_INT;
+					break;
+				case 8:
+					Assert(att->attalign = 'd');
+					curdp.attopcode = DO_BYVAL_8;
+					curdp.alignto = ALIGNOF_DOUBLE;
+					break;
+				default:
+					Assert(false);
+			}
+
+			/* if not guaranteed to be correctly aligned, include alignment code */
+			if (attcuralign < 0)
+				curdp.attopcode += DO_ALIGN_IND_FIXED_LENGTH;
+			else if (attcuralign != TYPEALIGN(curdp.alignto, attcuralign))
+			{
+				curdp.attopcode += DO_ALIGN_IND_FIXED_LENGTH;
+				attcuralign += TYPEALIGN(curdp.alignto, attcuralign);
+			}
+
+			if (attcuralign >= 0)
+				attcuralign += att->attlen;
+		}
+		else
+		{
+			/* compute alignment */
+			if (att->attalign == 'i')
+			{
+				curdp.alignto = ALIGNOF_INT;
+			}
+			else if (att->attalign == 'c')
+			{
+				curdp.alignto = 1;
+			}
+			else if (att->attalign == 'd')
+			{
+				curdp.alignto = ALIGNOF_DOUBLE;
+			}
+			else if (att->attalign == 's')
+			{
+				curdp.alignto = ALIGNOF_SHORT;
+			}
+			else
+			{
+				Assert(false);
+			}
+
+
+			/* compute optcode */
+			if (att->attlen >= 0)
+			{
+				curdp.attopcode = DO_IND_FIXED_LENGTH;
+			}
+			else if (att->attlen == -1)
+			{
+				curdp.attopcode = DO_VARLENA;
+			}
+			else if (att->attlen == -2)
+			{
+				curdp.attopcode = DO_CSTRING;
+			}
+			else
+			{
+				Assert(false);
+			}
+
+			/* if not guaranteed to be correctly aligned, include alignment code */
+			if (attcuralign < 0 ||
+				attcuralign != TYPEALIGN(curdp.alignto, attcuralign))
+			{
+				curdp.attopcode += DO_ALIGN_IND_FIXED_LENGTH;
+				attcuralign = TYPEALIGN(curdp.alignto, attcuralign);
+			}
+
+			if (attcuralign >= 0 && att->attlen >= 0)
+				attcuralign += att->attlen;
+
+			if (att->attlen <= 0)
+				attcuralign = -1;
+		}
+
+		if (!att->attnotnull)
+		{
+			curdp.attopcode += 7;
+			attcuralign = -1;
+		}
+
+		slot_push_deform_step(slot, &curdp);
+	}
+
+	curdp.attopcode = DO_DONE;
+	slot_push_deform_step(slot, &curdp);
+}
+
+/* FIXME: use computed goto on gcc / clang */
+// #define USE_COMPUTED_GOTO
+
+#ifdef USE_COMPUTED_GOTO
+#define SD_SWITCH(d)
+#define SD_DISPATCH() \
+	do \
+	{ \
+		attnum++; \
+		if (attnum >= natts) \
+			goto out; \
+		dp++; \
+		goto *(((void**)dispatch_table)[dp->attopcode]); \
+	} \
+	while (0)
+#define SD_CASE(name) CASE_##name
+#else
+#define SD_SWITCH(d) switch (d)
+#define SD_DISPATCH() \
+	do \
+	{ \
+		attnum++; \
+		dp++; \
+		goto starteval; \
+	} \
+	while (0)
+#define SD_CASE(name) case name
+
+#endif /* USE_COMPUTED_GOTO */
+
+#define SD_CHECKNULL \
+	if (hasnulls && att_isnull(attnum, bp)) \
+	{ \
+		isnull[attnum] = true; \
+		values[attnum] = (Datum) 0; \
+		SD_DISPATCH(); \
+	}
+
 /*
  * slot_deform_tuple
  *		Given a TupleTableSlot, extract data from the slot's physical tuple
@@ -963,11 +1151,343 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
  *		on each call we extract attributes up to the one needed, without
  *		re-computing information about previously extracted attributes.
  *		slot->tts_nvalid is the number of attributes already extracted.
+ *
+ *		NB: This requires that only nullable columns are NULL, which makes
+ *		this unsuitable for e.g. constraint evaluation.
  */
 static void
 slot_deform_tuple(TupleTableSlot *slot, int natts)
 {
 	HeapTuple	tuple = slot->tts_tuple;
+	Datum	   *values = slot->tts_values;
+	bool	   *isnull = slot->tts_isnull;
+	HeapTupleHeader tup = tuple->t_data;
+	bool		hasnulls = HeapTupleHasNulls(tuple);
+	int			attnum;
+	char	   *tpc;				/* ptr to tuple data */
+	bits8	   *bp = tup->t_bits;		/* ptr to null bitmap in tuple */
+	DeparseSlotState *dp = slot->tts_dp;
+	int			oldop;
+
+#ifdef USE_COMPUTED_GOTO
+	static const void* dispatch_table[] = {
+		&&CASE_DO_IND_FIXED_LENGTH,
+		&&CASE_DO_VARLENA,
+		&&CASE_DO_CSTRING,
+		&&CASE_DO_BYVAL_1,
+		&&CASE_DO_BYVAL_2,
+		&&CASE_DO_BYVAL_4,
+		&&CASE_DO_BYVAL_8,
+
+		&&CASE_DO_NULLABLE_IND_FIXED_LENGTH,
+		&&CASE_DO_NULLABLE_VARLENA,
+		&&CASE_DO_NULLABLE_CSTRING,
+		&&CASE_DO_NULLABLE_BYVAL_1,
+		&&CASE_DO_NULLABLE_BYVAL_2,
+		&&CASE_DO_NULLABLE_BYVAL_4,
+		&&CASE_DO_NULLABLE_BYVAL_8,
+
+		&&CASE_DO_ALIGN_IND_FIXED_LENGTH,
+		&&CASE_DO_ALIGN_VARLENA,
+		&&CASE_DO_ALIGN_CSTRING,
+		&&CASE_DO_ALIGN_BYVAL_1,
+		&&CASE_DO_ALIGN_BYVAL_2,
+		&&CASE_DO_ALIGN_BYVAL_4,
+		&&CASE_DO_ALIGN_BYVAL_8,
+
+		&&CASE_DO_ALIGN_NULLABLE_IND_FIXED_LENGTH,
+		&&CASE_DO_ALIGN_NULLABLE_VARLENA,
+		&&CASE_DO_ALIGN_NULLABLE_CSTRING,
+		&&CASE_DO_ALIGN_NULLABLE_BYVAL_1,
+		&&CASE_DO_ALIGN_NULLABLE_BYVAL_2,
+		&&CASE_DO_ALIGN_NULLABLE_BYVAL_4,
+		&&CASE_DO_ALIGN_NULLABLE_BYVAL_8,
+		&&CASE_DO_DONE
+	};
+#endif
+
+	Assert(slot->tts_dp != NULL);
+
+	tpc = (char *) tup + tup->t_hoff;
+
+	/*
+	 * Check whether the first call for this tuple, and initialize or restore
+	 * loop state.
+	 */
+	attnum = slot->tts_nvalid;
+	if (attnum > 0)
+	{
+		/* Restore state from previous execution */
+		tpc += slot->tts_off;
+		dp += attnum;
+	}
+
+	/*
+	 * To avoid checking "progress" in every loop iteration, temporarily set
+	 * the last step + 1 to DONE. That'll cause the loop below to exit.
+	 */
+
+	oldop = slot->tts_dp[natts].attopcode;
+	slot->tts_dp[natts].attopcode = DO_DONE;
+
+	/*
+	 * FIXME: Instead of checking for attnum in SD_DISPATCH, temporarily set
+	 * the opcode of the relevant step to a new 'DONE' opcode.
+	 */
+#ifdef USE_COMPUTED_GOTO
+	goto *(((void**)dispatch_table)[dp->attopcode]);
+#else
+starteval:
+#endif
+	SD_SWITCH(dp->attopcode)
+	{
+	SD_CASE(DO_IND_FIXED_LENGTH):
+		Assert(tpc == (char *) TYPEALIGN(dp->alignto, tpc));
+		isnull[attnum] = false;
+		values[attnum] = PointerGetDatum((char *) tpc);
+		tpc += dp->attlen;
+		SD_DISPATCH();
+	SD_CASE(DO_VARLENA):
+		if (!VARATT_NOT_PAD_BYTE(tpc))
+		{
+			Assert(tpc == (char *) TYPEALIGN(dp->alignto, tpc));
+		}
+		isnull[attnum] = false;
+		values[attnum] = PointerGetDatum((char *) tpc);
+		tpc += VARSIZE_ANY(tpc);
+		SD_DISPATCH();
+	SD_CASE(DO_CSTRING):
+		Assert(tpc == (char *) TYPEALIGN(dp->alignto, tpc));
+		isnull[attnum] = false;
+		values[attnum] = PointerGetDatum((char *) tpc);
+		tpc += strlen(tpc) + 1;
+		SD_DISPATCH();
+	SD_CASE(DO_BYVAL_1):
+		Assert(dp->alignto == 1);
+		isnull[attnum] = false;
+		values[attnum] = CharGetDatum(*(char *) tpc);
+		tpc += sizeof(char);
+		SD_DISPATCH();
+	SD_CASE(DO_BYVAL_2):
+		Assert(dp->alignto == ALIGNOF_SHORT);
+		Assert(tpc == (char *) TYPEALIGN(ALIGNOF_SHORT, tpc));
+		isnull[attnum] = false;
+		values[attnum] = Int16GetDatum(*(int32 *) tpc);
+		tpc += sizeof(int16);
+		SD_DISPATCH();
+	SD_CASE(DO_BYVAL_4):
+		Assert(dp->alignto == ALIGNOF_INT);
+		Assert(tpc == (char *) TYPEALIGN(ALIGNOF_INT, tpc));
+		isnull[attnum] = false;
+		values[attnum] = Int32GetDatum(*(int32 *) tpc);
+		tpc += sizeof(int32);
+		SD_DISPATCH();
+	SD_CASE(DO_BYVAL_8):
+		Assert(SIZEOF_DATUM == 8);
+		Assert(dp->alignto == ALIGNOF_DOUBLE);
+		Assert(tpc == (char *) TYPEALIGN(ALIGNOF_DOUBLE, tpc));
+		isnull[attnum] = false;
+		values[attnum] = *(Datum *) tpc;
+		tpc += sizeof(void*);
+		SD_DISPATCH();
+
+	SD_CASE(DO_NULLABLE_IND_FIXED_LENGTH):
+		SD_CHECKNULL;
+		tpc = (char *) TYPEALIGN(dp->alignto, tpc);
+		isnull[attnum] = false;
+		values[attnum] = PointerGetDatum((char *) tpc);
+		tpc += dp->attlen;
+		SD_DISPATCH();
+	SD_CASE(DO_NULLABLE_VARLENA):
+		SD_CHECKNULL;
+		if (!VARATT_NOT_PAD_BYTE(tpc))
+			tpc = (char *) TYPEALIGN(dp->alignto, tpc);
+		isnull[attnum] = false;
+		values[attnum] = PointerGetDatum((char *) tpc);
+		tpc += VARSIZE_ANY(tpc);
+		SD_DISPATCH();
+	SD_CASE(DO_NULLABLE_CSTRING):
+		SD_CHECKNULL;
+		tpc = (char *) TYPEALIGN(dp->alignto, tpc);
+		isnull[attnum] = false;
+		values[attnum] = PointerGetDatum((char *) tpc);
+		tpc += strlen(tpc) + 1;
+		SD_DISPATCH();
+	SD_CASE(DO_NULLABLE_BYVAL_1):
+		SD_CHECKNULL;
+		Assert(dp->alignto == 1);
+		tpc = (char *) TYPEALIGN(1, tpc);
+		isnull[attnum] = false;
+		values[attnum] = CharGetDatum(*(char *) tpc);
+		tpc += sizeof(char);
+		SD_DISPATCH();
+	SD_CASE(DO_NULLABLE_BYVAL_2):
+		SD_CHECKNULL;
+		Assert(dp->alignto == ALIGNOF_SHORT);
+		tpc = (char *) TYPEALIGN(ALIGNOF_SHORT, tpc);
+		isnull[attnum] = false;
+		values[attnum] = Int16GetDatum(*(int32 *) tpc);
+		tpc += sizeof(int16);
+		SD_DISPATCH();
+	SD_CASE(DO_NULLABLE_BYVAL_4):
+		SD_CHECKNULL;
+		Assert(dp->alignto == ALIGNOF_INT);
+		tpc = (char *) TYPEALIGN(ALIGNOF_INT, tpc);
+		isnull[attnum] = false;
+		values[attnum] = Int32GetDatum(*(int32 *) tpc);
+		tpc += sizeof(int32);
+		SD_DISPATCH();
+	SD_CASE(DO_NULLABLE_BYVAL_8):
+		SD_CHECKNULL;
+		Assert(SIZEOF_DATUM == 8);
+		Assert(dp->alignto == ALIGNOF_DOUBLE);
+		tpc = (char *) TYPEALIGN(ALIGNOF_DOUBLE, tpc);
+		isnull[attnum] = false;
+		values[attnum] = *(Datum *) tpc;
+		tpc += sizeof(void*);
+		SD_DISPATCH();
+
+	SD_CASE(DO_ALIGN_IND_FIXED_LENGTH):
+		tpc = (char *) TYPEALIGN(dp->alignto, tpc);
+		isnull[attnum] = false;
+		values[attnum] = PointerGetDatum((char *) tpc);
+		tpc += dp->attlen;
+		SD_DISPATCH();
+	SD_CASE(DO_ALIGN_VARLENA):
+		if (!VARATT_NOT_PAD_BYTE(tpc))
+			tpc = (char *) TYPEALIGN(dp->alignto, tpc);
+		isnull[attnum] = false;
+		values[attnum] = PointerGetDatum((char *) tpc);
+		tpc += VARSIZE_ANY(tpc);
+		SD_DISPATCH();
+	SD_CASE(DO_ALIGN_CSTRING):
+		tpc = (char *) TYPEALIGN(dp->alignto, tpc);
+		isnull[attnum] = false;
+		values[attnum] = PointerGetDatum((char *) tpc);
+		tpc += strlen(tpc) + 1;
+		SD_DISPATCH();
+	SD_CASE(DO_ALIGN_BYVAL_1):
+		Assert(dp->alignto == 1);
+		tpc = (char *) TYPEALIGN(1, tpc);
+		isnull[attnum] = false;
+		values[attnum] = CharGetDatum(*(char *) tpc);
+		tpc += sizeof(char);
+		SD_DISPATCH();
+	SD_CASE(DO_ALIGN_BYVAL_2):
+		Assert(dp->alignto == ALIGNOF_SHORT);
+		tpc = (char *) TYPEALIGN(ALIGNOF_SHORT, tpc);
+		isnull[attnum] = false;
+		values[attnum] = Int16GetDatum(*(int32 *) tpc);
+		tpc += sizeof(int16);
+		SD_DISPATCH();
+	SD_CASE(DO_ALIGN_BYVAL_4):
+		Assert(dp->alignto == ALIGNOF_INT);
+		tpc = (char *) TYPEALIGN(ALIGNOF_INT, tpc);
+		isnull[attnum] = false;
+		values[attnum] = Int32GetDatum(*(int32 *) tpc);
+		tpc += sizeof(int32);
+		SD_DISPATCH();
+	SD_CASE(DO_ALIGN_BYVAL_8):
+		Assert(SIZEOF_DATUM == 8);
+		Assert(dp->alignto == ALIGNOF_DOUBLE);
+		tpc = (char *) TYPEALIGN(ALIGNOF_DOUBLE, tpc);
+		isnull[attnum] = false;
+		values[attnum] = *(Datum *) tpc;
+		tpc += sizeof(void*);
+		SD_DISPATCH();
+
+	SD_CASE(DO_ALIGN_NULLABLE_IND_FIXED_LENGTH):
+		SD_CHECKNULL;
+		tpc = (char *) TYPEALIGN(dp->alignto, tpc);
+		isnull[attnum] = false;
+		values[attnum] = PointerGetDatum((char *) tpc);
+		tpc += dp->attlen;
+		SD_DISPATCH();
+	SD_CASE(DO_ALIGN_NULLABLE_VARLENA):
+		SD_CHECKNULL;
+		if (!VARATT_NOT_PAD_BYTE(tpc))
+			tpc = (char *) TYPEALIGN(dp->alignto, tpc);
+		isnull[attnum] = false;
+		values[attnum] = PointerGetDatum((char *) tpc);
+		tpc += VARSIZE_ANY(tpc);
+		SD_DISPATCH();
+	SD_CASE(DO_ALIGN_NULLABLE_CSTRING):
+		SD_CHECKNULL;
+		tpc = (char *) TYPEALIGN(dp->alignto, tpc);
+		isnull[attnum] = false;
+		values[attnum] = PointerGetDatum((char *) tpc);
+		tpc += strlen(tpc) + 1;
+		SD_DISPATCH();
+	SD_CASE(DO_ALIGN_NULLABLE_BYVAL_1):
+		SD_CHECKNULL;
+		Assert(dp->alignto == 1);
+		tpc = (char *) TYPEALIGN(1, tpc);
+		isnull[attnum] = false;
+		values[attnum] = CharGetDatum(*(char *) tpc);
+		tpc += sizeof(char);
+		SD_DISPATCH();
+	SD_CASE(DO_ALIGN_NULLABLE_BYVAL_2):
+		SD_CHECKNULL;
+		Assert(dp->alignto == ALIGNOF_SHORT);
+		tpc = (char *) TYPEALIGN(ALIGNOF_SHORT, tpc);
+		isnull[attnum] = false;
+		values[attnum] = Int16GetDatum(*(int32 *) tpc);
+		tpc += sizeof(int16);
+		SD_DISPATCH();
+	SD_CASE(DO_ALIGN_NULLABLE_BYVAL_4):
+		SD_CHECKNULL;
+		Assert(dp->alignto == ALIGNOF_INT);
+		tpc = (char *) TYPEALIGN(ALIGNOF_INT, tpc);
+		isnull[attnum] = false;
+		values[attnum] = Int32GetDatum(*(int32 *) tpc);
+		tpc += sizeof(int32);
+		SD_DISPATCH();
+	SD_CASE(DO_ALIGN_NULLABLE_BYVAL_8):
+		SD_CHECKNULL;
+		Assert(SIZEOF_DATUM == 8);
+		Assert(dp->alignto == ALIGNOF_DOUBLE);
+		tpc = (char *) TYPEALIGN(ALIGNOF_DOUBLE, tpc);
+		isnull[attnum] = false;
+		values[attnum] = *(Datum *) tpc;
+		tpc += sizeof(void*);
+		SD_DISPATCH();
+	SD_CASE(DO_DONE):
+		goto out;
+	}
+out:
+
+	/* reset end marker */
+	slot->tts_dp[natts].attopcode = oldop;
+
+	/*
+	 * Save state for next execution
+	 */
+	slot->tts_nvalid = attnum;
+	slot->tts_off = tpc - ((char *) tup + tup->t_hoff);
+	slot->tts_slow = true;
+
+}
+
+#undef SD_CHECKNULL
+#undef SD_SWITCH
+#undef SD_DISPATCH
+#undef SD_CASE
+
+/*
+ * slot_deform_tuple_safe
+ *		Given a TupleTableSlot, extract data from the slot's physical tuple
+ *		into its Datum/isnull arrays.  Data is extracted up through the
+ *		natts'th column (caller must ensure this is a legal column number).
+ *
+ *		This is essentially an incremental version of heap_deform_tuple:
+ *		on each call we extract attributes up to the one needed, without
+ *		re-computing information about previously extracted attributes.
+ *		slot->tts_nvalid is the number of attributes already extracted.
+ */
+void
+slot_deform_tuple_safe(TupleTableSlot *slot, int natts)
+{
+	HeapTuple	tuple = slot->tts_tuple;
 	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
 	Datum	   *values = slot->tts_values;
 	bool	   *isnull = slot->tts_isnull;
@@ -980,23 +1500,11 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 	bits8	   *bp = tup->t_bits;		/* ptr to null bitmap in tuple */
 	bool		slow;			/* can we use/set attcacheoff? */
 
-	/*
-	 * Check whether the first call for this tuple, and initialize or restore
-	 * loop state.
-	 */
-	attnum = slot->tts_nvalid;
-	if (attnum == 0)
-	{
-		/* Start from the first attribute */
-		off = 0;
-		slow = false;
-	}
-	else
-	{
-		/* Restore state from previous execution */
-		off = slot->tts_off;
-		slow = slot->tts_slow;
-	}
+	slot->tts_nvalid = 0;
+	/* always start from the first attribute */
+	off = 0;
+	slow = false;
+	attnum = 0;
 
 	tp = (char *) tup + tup->t_hoff;
 
@@ -1059,6 +1567,7 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 	slot->tts_slow = slow;
 }
 
+
 /*
  * slot_getattr
  *		This function fetches an attribute of the slot's current tuple.
@@ -1076,16 +1585,16 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 {
 	HeapTuple	tuple = slot->tts_tuple;
 	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-	HeapTupleHeader tup;
+//	HeapTupleHeader tup;
 
 	/*
 	 * system attributes are handled by heap_getsysattr
 	 */
-	if (attnum <= 0)
+	if (unlikely(attnum <= 0))
 	{
-		if (tuple == NULL)		/* internal error */
+		if (unlikely(tuple == NULL))		/* internal error */
 			elog(ERROR, "cannot extract system attribute from virtual tuple");
-		if (tuple == &(slot->tts_minhdr))		/* internal error */
+		if (unlikely(tuple == &(slot->tts_minhdr)))		/* internal error */
 			elog(ERROR, "cannot extract system attribute from minimal tuple");
 		return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
 	}
@@ -1093,7 +1602,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 	/*
 	 * fast path if desired attribute already cached
 	 */
-	if (attnum <= slot->tts_nvalid)
+	if (likely(attnum <= slot->tts_nvalid))
 	{
 		*isnull = slot->tts_isnull[attnum - 1];
 		return slot->tts_values[attnum - 1];
@@ -1102,7 +1611,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 	/*
 	 * return NULL if attnum is out of range according to the tupdesc
 	 */
-	if (attnum > tupleDesc->natts)
+	if (unlikely(attnum > tupleDesc->natts))
 	{
 		*isnull = true;
 		return (Datum) 0;
@@ -1112,9 +1621,9 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 	 * otherwise we had better have a physical tuple (tts_nvalid should equal
 	 * natts in all virtual-tuple cases)
 	 */
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
+	Assert(tuple != NULL);
 
+#if 0
 	/*
 	 * return NULL if attnum is out of range according to the tuple
 	 *
@@ -1123,7 +1632,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 	 * than the tupdesc.)
 	 */
 	tup = tuple->t_data;
-	if (attnum > HeapTupleHeaderGetNatts(tup))
+	if (unlikely(attnum > HeapTupleHeaderGetNatts(tup)))
 	{
 		*isnull = true;
 		return (Datum) 0;
@@ -1137,6 +1646,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 		*isnull = true;
 		return (Datum) 0;
 	}
+#endif
 
 	/*
 	 * If the attribute's column has been dropped, we force a NULL result.
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 66003c9..7443bab 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1707,6 +1707,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		int			natts = tupdesc->natts;
 		int			attrChk;
 
+		/* re-parse, disregarding NOT NULL constraints */
+		slot_deform_tuple_safe(slot, slot->tts_tupleDescriptor->natts);
+
 		for (attrChk = 1; attrChk <= natts; attrChk++)
 		{
 			if (tupdesc->attrs[attrChk - 1]->attnotnull &&
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 533050d..9e00649 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -123,6 +123,7 @@ MakeTupleTableSlot(void)
 	slot->tts_values = NULL;
 	slot->tts_isnull = NULL;
 	slot->tts_mintuple = NULL;
+	slot->tts_off = 0;
 
 	return slot;
 }
@@ -173,6 +174,13 @@ ExecResetTupleTable(List *tupleTable,	/* tuple table */
 			slot->tts_tupleDescriptor = NULL;
 		}
 
+		if (slot->tts_dp)
+		{
+			pfree(slot->tts_dp);
+			slot->tts_dp = NULL;
+			slot->tts_dp_alloc = slot->tts_dp_len = 0;
+		}
+
 		/* If shouldFree, release memory occupied by the slot itself */
 		if (shouldFree)
 		{
@@ -222,7 +230,10 @@ ExecDropSingleTupleTableSlot(TupleTableSlot *slot)
 	Assert(IsA(slot, TupleTableSlot));
 	ExecClearTuple(slot);
 	if (slot->tts_tupleDescriptor)
+	{
 		ReleaseTupleDesc(slot->tts_tupleDescriptor);
+		pfree(slot->tts_dp);
+	}
 	if (slot->tts_values)
 		pfree(slot->tts_values);
 	if (slot->tts_isnull)
@@ -258,7 +269,12 @@ ExecSetSlotDescriptor(TupleTableSlot *slot,		/* slot to change */
 	 * present (we don't bother to check if they could be re-used).
 	 */
 	if (slot->tts_tupleDescriptor)
+	{
 		ReleaseTupleDesc(slot->tts_tupleDescriptor);
+		pfree(slot->tts_dp);
+		slot->tts_dp = NULL;
+		slot->tts_dp_alloc = slot->tts_dp_len = 0;
+	}
 
 	if (slot->tts_values)
 		pfree(slot->tts_values);
@@ -270,6 +286,7 @@ ExecSetSlotDescriptor(TupleTableSlot *slot,		/* slot to change */
 	 */
 	slot->tts_tupleDescriptor = tupdesc;
 	PinTupleDesc(tupdesc);
+	slot_prepare_deform(slot);
 
 	/*
 	 * Allocate Datum/isnull arrays of the appropriate size.  These must have
@@ -353,6 +370,7 @@ ExecStoreTuple(HeapTuple tuple,
 
 	/* Mark extracted state invalid */
 	slot->tts_nvalid = 0;
+	slot->tts_off = 0;
 
 	/*
 	 * If tuple is on a disk page, keep the page pinned as long as we hold a
@@ -426,6 +444,7 @@ ExecStoreMinimalTuple(MinimalTuple mtup,
 
 	/* Mark extracted state invalid */
 	slot->tts_nvalid = 0;
+	slot->tts_off = 0;
 
 	return slot;
 }
@@ -472,6 +491,7 @@ ExecClearTuple(TupleTableSlot *slot)	/* slot in which to store tuple */
 	 */
 	slot->tts_isempty = true;
 	slot->tts_nvalid = 0;
+	slot->tts_off = 0;
 
 	return slot;
 }
@@ -499,6 +519,7 @@ ExecStoreVirtualTuple(TupleTableSlot *slot)
 
 	slot->tts_isempty = false;
 	slot->tts_nvalid = slot->tts_tupleDescriptor->natts;
+	slot->tts_off = 0;
 
 	return slot;
 }
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index 5ac0b6a..98993d9 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -18,6 +18,54 @@
 #include "access/tupdesc.h"
 #include "storage/buf.h"
 
+typedef enum DeparseOpcode
+{
+	/* not nullable, properly aligned */
+	DO_IND_FIXED_LENGTH = 0,
+	DO_VARLENA,
+	DO_CSTRING,
+	DO_BYVAL_1,
+	DO_BYVAL_2,
+	DO_BYVAL_4,
+	DO_BYVAL_8,
+
+	/* nullable, properly aligned */
+	DO_NULLABLE_IND_FIXED_LENGTH,
+	DO_NULLABLE_VARLENA,
+	DO_NULLABLE_CSTRING,
+	DO_NULLABLE_BYVAL_1,
+	DO_NULLABLE_BYVAL_2,
+	DO_NULLABLE_BYVAL_4,
+	DO_NULLABLE_BYVAL_8,
+
+	/* not nullable, possibly aligned */
+	DO_ALIGN_IND_FIXED_LENGTH,
+	DO_ALIGN_VARLENA,
+	DO_ALIGN_CSTRING,
+	DO_ALIGN_BYVAL_1,
+	DO_ALIGN_BYVAL_2,
+	DO_ALIGN_BYVAL_4,
+	DO_ALIGN_BYVAL_8,
+
+	/* nullable, possibly aligned */
+	DO_ALIGN_NULLABLE_IND_FIXED_LENGTH,
+	DO_ALIGN_NULLABLE_VARLENA,
+	DO_ALIGN_NULLABLE_CSTRING,
+	DO_ALIGN_NULLABLE_BYVAL_1,
+	DO_ALIGN_NULLABLE_BYVAL_2,
+	DO_ALIGN_NULLABLE_BYVAL_4,
+	DO_ALIGN_NULLABLE_BYVAL_8,
+	DO_DONE
+} DeparseOpcode;
+
+typedef struct DeparseSlotState
+{
+	int attlen;
+	DeparseOpcode attopcode;
+	bool attbyval;
+	uintptr_t alignto;
+} DeparseSlotState;
+
 /*----------
  * The executor stores tuples in a "tuple table" which is a List of
  * independent TupleTableSlots.  There are several cases we need to handle:
@@ -124,6 +172,9 @@ typedef struct TupleTableSlot
 	int			tts_nvalid;		/* # of valid values in tts_values */
 	Datum	   *tts_values;		/* current per-attribute values */
 	bool	   *tts_isnull;		/* current per-attribute isnull flags */
+	DeparseSlotState *tts_dp;
+	int			tts_dp_len;
+	int			tts_dp_alloc;
 	MinimalTuple tts_mintuple;	/* minimal tuple, or NULL if none */
 	HeapTupleData tts_minhdr;	/* workspace for minimal-tuple-only case */
 	long		tts_off;		/* saved state for slot_deform_tuple */
@@ -169,5 +220,8 @@ extern Datum slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull);
 extern void slot_getallattrs(TupleTableSlot *slot);
 extern void slot_getsomeattrs(TupleTableSlot *slot, int attnum);
 extern bool slot_attisnull(TupleTableSlot *slot, int attnum);
+extern void slot_deform_tuple_safe(TupleTableSlot *slot, int natts);
+
+extern void slot_prepare_deform(TupleTableSlot *slot);
 
 #endif   /* TUPTABLE_H */
-- 
2.8.1

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to