Hi,

Similar to [1] (Expression based aggregate transition / combine function
invocation), this patch provides small-medium performance benefits in
order to later enable larger performance benefits with JIT compilation.

What this basically does is to move execGrouping.c functions that
compare tuples for equality (i.e. execTuplesMatch() and the tuplehash
tables themselves) to use the expression evaluation engine. That turns
out to be beneficial on its own because it makes sure we deform all
needed tuples at once, avoid repeated slot_getattr() calls, and because
the branch prediction is better.   Instead of, as previously done,
calling execTuplesMatch() one calls ExecQual(), and sets up the tuples
in the ExprContext.

I'm not yet 100% happy with this:
- currently the function building the comparator is named
  execTuplesMatchPrepare - which ain't quite apt anymore.
- there's now a bit of additional code at callsites to reset the
  ExprContext - was tempted to put that that in a ExecQualAndReset()
  inline wrapper, but that's not entirely trivial because executor.h
  doesn't include memutils.h and ResetExprContext() is declared late.
- I wonder if the APIs for execGrouping.h should get whacked around a
  bit more aggressively - it'd probably be better if the TupleHashTable
  struct were created once instead of being recreated during every
  rescan.
- currently only equality is done via the expression mechanism, I've for
  now just moved execTuplesUnequal() to it's only caller. The semantics
  seems so closely matched to NOT IN() that I don't reallyexpect other
  users.
- A bunch of the pointers to ExprStates are still named *function - that
  seems ok to me, but somebody else might protest.
- some cleanup.

But some comments would be welcome!

I've included the work from [1] here as the patches do conflict.

Greetings,

Andres Freund

[1] 
https://www.postgresql.org/message-id/20171128003121.nmxbm2ounxzb6...@alap3.anarazel.de
>From 3ff1371aae4615c15f6bcdc232170a3a295e8021 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Fri, 24 Nov 2017 14:14:35 -0800
Subject: [PATCH 1/4] Simplify representation of aggregate transition values a
 bit.

Previously aggregate transition values for hash and direct (sort + no
group by) aggregates were represented differently. That made upcoming
changes hard, so represent both as grouping set indexed array of
per-group data.

Author: Andres Freund
---
 src/backend/executor/nodeAgg.c | 94 ++++++++++++++++++++----------------------
 src/include/nodes/execnodes.h  |  6 ++-
 2 files changed, 48 insertions(+), 52 deletions(-)

diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index da6ef1a94c4..a939ae99fa8 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -532,13 +532,13 @@ static void select_current_set(AggState *aggstate, int setno, bool is_hash);
 static void initialize_phase(AggState *aggstate, int newphase);
 static TupleTableSlot *fetch_input_tuple(AggState *aggstate);
 static void initialize_aggregates(AggState *aggstate,
-					  AggStatePerGroup pergroup,
-					  int numReset);
+					  AggStatePerGroup *pergroups,
+					  bool isHash, int numReset);
 static void advance_transition_function(AggState *aggstate,
 							AggStatePerTrans pertrans,
 							AggStatePerGroup pergroupstate);
-static void advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup,
-				   AggStatePerGroup *pergroups);
+static void advance_aggregates(AggState *aggstate, AggStatePerGroup *sort_pergroups,
+				   AggStatePerGroup *hash_pergroups);
 static void advance_combine_function(AggState *aggstate,
 						 AggStatePerTrans pertrans,
 						 AggStatePerGroup pergroupstate);
@@ -793,15 +793,14 @@ initialize_aggregate(AggState *aggstate, AggStatePerTrans pertrans,
  * If there are multiple grouping sets, we initialize only the first numReset
  * of them (the grouping sets are ordered so that the most specific one, which
  * is reset most often, is first). As a convenience, if numReset is 0, we
- * reinitialize all sets. numReset is -1 to initialize a hashtable entry, in
- * which case the caller must have used select_current_set appropriately.
+ * reinitialize all sets.
  *
  * When called, CurrentMemoryContext should be the per-query context.
  */
 static void
 initialize_aggregates(AggState *aggstate,
-					  AggStatePerGroup pergroup,
-					  int numReset)
+					  AggStatePerGroup *pergroups,
+					  bool isHash, int numReset)
 {
 	int			transno;
 	int			numGroupingSets = Max(aggstate->phase->numsets, 1);
@@ -812,31 +811,19 @@ initialize_aggregates(AggState *aggstate,
 	if (numReset == 0)
 		numReset = numGroupingSets;
 
-	for (transno = 0; transno < numTrans; transno++)
+	for (setno = 0; setno < numReset; setno++)
 	{
-		AggStatePerTrans pertrans = &transstates[transno];
+		AggStatePerGroup pergroup = pergroups[setno];
 
-		if (numReset < 0)
+		select_current_set(aggstate, setno, isHash);
+
+		for (transno = 0; transno < numTrans; transno++)
 		{
-			AggStatePerGroup pergroupstate;
-
-			pergroupstate = &pergroup[transno];
+			AggStatePerTrans pertrans = &transstates[transno];
+			AggStatePerGroup pergroupstate = &pergroup[transno];
 
 			initialize_aggregate(aggstate, pertrans, pergroupstate);
 		}
-		else
-		{
-			for (setno = 0; setno < numReset; setno++)
-			{
-				AggStatePerGroup pergroupstate;
-
-				pergroupstate = &pergroup[transno + (setno * numTrans)];
-
-				select_current_set(aggstate, setno, false);
-
-				initialize_aggregate(aggstate, pertrans, pergroupstate);
-			}
-		}
 	}
 }
 
@@ -976,7 +963,7 @@ advance_transition_function(AggState *aggstate,
  * When called, CurrentMemoryContext should be the per-query context.
  */
 static void
-advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup, AggStatePerGroup *pergroups)
+advance_aggregates(AggState *aggstate, AggStatePerGroup *sort_pergroups, AggStatePerGroup *hash_pergroups)
 {
 	int			transno;
 	int			setno = 0;
@@ -1019,7 +1006,7 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup, AggStatePerGro
 		{
 			/* DISTINCT and/or ORDER BY case */
 			Assert(slot->tts_nvalid >= (pertrans->numInputs + inputoff));
-			Assert(!pergroups);
+			Assert(!hash_pergroups);
 
 			/*
 			 * If the transfn is strict, we want to check for nullity before
@@ -1090,9 +1077,9 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup, AggStatePerGro
 				fcinfo->argnull[i + 1] = slot->tts_isnull[i + inputoff];
 			}
 
-			if (pergroup)
+			if (sort_pergroups)
 			{
-				/* advance transition states for ordered grouping */
+				/* advance transition states for ordered grouping  */
 
 				for (setno = 0; setno < numGroupingSets; setno++)
 				{
@@ -1100,13 +1087,13 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup, AggStatePerGro
 
 					select_current_set(aggstate, setno, false);
 
-					pergroupstate = &pergroup[transno + (setno * numTrans)];
+					pergroupstate = &sort_pergroups[setno][transno];
 
 					advance_transition_function(aggstate, pertrans, pergroupstate);
 				}
 			}
 
-			if (pergroups)
+			if (hash_pergroups)
 			{
 				/* advance transition states for hashed grouping */
 
@@ -1116,7 +1103,7 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup, AggStatePerGro
 
 					select_current_set(aggstate, setno, true);
 
-					pergroupstate = &pergroups[setno][transno];
+					pergroupstate = &hash_pergroups[setno][transno];
 
 					advance_transition_function(aggstate, pertrans, pergroupstate);
 				}
@@ -2099,8 +2086,8 @@ lookup_hash_entry(AggState *aggstate)
 			MemoryContextAlloc(perhash->hashtable->tablecxt,
 							   sizeof(AggStatePerGroupData) * aggstate->numtrans);
 		/* initialize aggregates for new tuple group */
-		initialize_aggregates(aggstate, (AggStatePerGroup) entry->additional,
-							  -1);
+		initialize_aggregates(aggstate, (AggStatePerGroup*) &entry->additional,
+							  true, 1);
 	}
 
 	return entry;
@@ -2184,7 +2171,7 @@ agg_retrieve_direct(AggState *aggstate)
 	ExprContext *econtext;
 	ExprContext *tmpcontext;
 	AggStatePerAgg peragg;
-	AggStatePerGroup pergroup;
+	AggStatePerGroup *pergroups;
 	AggStatePerGroup *hash_pergroups = NULL;
 	TupleTableSlot *outerslot;
 	TupleTableSlot *firstSlot;
@@ -2207,7 +2194,7 @@ agg_retrieve_direct(AggState *aggstate)
 	tmpcontext = aggstate->tmpcontext;
 
 	peragg = aggstate->peragg;
-	pergroup = aggstate->pergroup;
+	pergroups = aggstate->pergroups;
 	firstSlot = aggstate->ss.ss_ScanTupleSlot;
 
 	/*
@@ -2409,7 +2396,7 @@ agg_retrieve_direct(AggState *aggstate)
 			/*
 			 * Initialize working state for a new input tuple group.
 			 */
-			initialize_aggregates(aggstate, pergroup, numReset);
+			initialize_aggregates(aggstate, pergroups, false, numReset);
 
 			if (aggstate->grp_firstTuple != NULL)
 			{
@@ -2446,9 +2433,9 @@ agg_retrieve_direct(AggState *aggstate)
 						hash_pergroups = NULL;
 
 					if (DO_AGGSPLIT_COMBINE(aggstate->aggsplit))
-						combine_aggregates(aggstate, pergroup);
+						combine_aggregates(aggstate, pergroups[0]);
 					else
-						advance_aggregates(aggstate, pergroup, hash_pergroups);
+						advance_aggregates(aggstate, pergroups, hash_pergroups);
 
 					/* Reset per-input-tuple context after each tuple */
 					ResetExprContext(tmpcontext);
@@ -2512,7 +2499,7 @@ agg_retrieve_direct(AggState *aggstate)
 
 		finalize_aggregates(aggstate,
 							peragg,
-							pergroup + (currentSet * aggstate->numtrans));
+							pergroups[currentSet]);
 
 		/*
 		 * If there's no row to project right now, we must continue rather
@@ -2756,7 +2743,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	aggstate->curpertrans = NULL;
 	aggstate->input_done = false;
 	aggstate->agg_done = false;
-	aggstate->pergroup = NULL;
+	aggstate->pergroups = NULL;
 	aggstate->grp_firstTuple = NULL;
 	aggstate->sort_in = NULL;
 	aggstate->sort_out = NULL;
@@ -3052,13 +3039,17 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 
 	if (node->aggstrategy != AGG_HASHED)
 	{
-		AggStatePerGroup pergroup;
+		AggStatePerGroup *pergroups =
+			(AggStatePerGroup*) palloc0(sizeof(AggStatePerGroup)
+										* numGroupingSets);
 
-		pergroup = (AggStatePerGroup) palloc0(sizeof(AggStatePerGroupData)
-											  * numaggs
-											  * numGroupingSets);
+		for (i = 0; i < numGroupingSets; i++)
+		{
+			pergroups[i] = (AggStatePerGroup) palloc0(sizeof(AggStatePerGroupData)
+													 * numaggs);
+		}
 
-		aggstate->pergroup = pergroup;
+		aggstate->pergroups = pergroups;
 	}
 
 	/*
@@ -4086,8 +4077,11 @@ ExecReScanAgg(AggState *node)
 		/*
 		 * Reset the per-group state (in particular, mark transvalues null)
 		 */
-		MemSet(node->pergroup, 0,
-			   sizeof(AggStatePerGroupData) * node->numaggs * numGroupingSets);
+		for (setno = 0; setno < numGroupingSets; setno++)
+		{
+			MemSet(node->pergroups[setno], 0,
+				   sizeof(AggStatePerGroupData) * node->numaggs);
+		}
 
 		/* reset to phase 1 */
 		initialize_phase(node, 1);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e05bc04f525..34a4a3d24f9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1833,13 +1833,15 @@ typedef struct AggState
 	Tuplesortstate *sort_out;	/* input is copied here for next phase */
 	TupleTableSlot *sort_slot;	/* slot for sort results */
 	/* these fields are used in AGG_PLAIN and AGG_SORTED modes: */
-	AggStatePerGroup pergroup;	/* per-Aggref-per-group working state */
+	AggStatePerGroup *pergroups;	/* grouping set indexed array of per-group
+									 * pointers */
 	HeapTuple	grp_firstTuple; /* copy of first tuple of current group */
 	/* these fields are used in AGG_HASHED and AGG_MIXED modes: */
 	bool		table_filled;	/* hash table filled yet? */
 	int			num_hashes;
 	AggStatePerHash perhash;
-	AggStatePerGroup *hash_pergroup;	/* array of per-group pointers */
+	AggStatePerGroup *hash_pergroup;	/* grouping set indexed array of
+										 * per-group pointers */
 	/* support for evaluation of agg input expressions: */
 	ProjectionInfo *combinedproj;	/* projection machinery */
 } AggState;
-- 
2.14.1.536.g6867272d5b.dirty

>From a30fa52fb58b518faa5d7c87c5ab6a37236d5c73 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Tue, 3 Oct 2017 23:45:44 -0700
Subject: [PATCH 2/4] More efficient AggState->pertrans iteration.

Turns out AggStatePerTrans is so large that multiplications are needed
to access elements of AggState->pertrans on x86.

Author: Andres Freund
---
 src/backend/executor/nodeAgg.c | 28 +++++++++++++++-------------
 1 file changed, 15 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index a939ae99fa8..0408142764c 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -971,13 +971,14 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup *sort_pergroups, AggStat
 	int			numHashes = aggstate->num_hashes;
 	int			numTrans = aggstate->numtrans;
 	TupleTableSlot *combinedslot;
+	AggStatePerTrans pertrans;
 
 	/* compute required inputs for all aggregates */
 	combinedslot = ExecProject(aggstate->combinedproj);
 
-	for (transno = 0; transno < numTrans; transno++)
+	for (transno = 0, pertrans = &aggstate->pertrans[0];
+		 transno < numTrans; transno++, pertrans++)
 	{
-		AggStatePerTrans pertrans = &aggstate->pertrans[transno];
 		int			numTransInputs = pertrans->numTransInputs;
 		int			inputoff = pertrans->inputoff;
 		TupleTableSlot *slot;
@@ -1125,6 +1126,7 @@ combine_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
 	int			transno;
 	int			numTrans = aggstate->numtrans;
 	TupleTableSlot *slot;
+	AggStatePerTrans pertrans;
 
 	/* combine not supported with grouping sets */
 	Assert(aggstate->phase->numsets <= 1);
@@ -1132,9 +1134,9 @@ combine_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
 	/* compute input for all aggregates */
 	slot = ExecProject(aggstate->combinedproj);
 
-	for (transno = 0; transno < numTrans; transno++)
+	for (transno = 0, pertrans = &aggstate->pertrans[0];
+		 transno < numTrans; transno++, pertrans++)
 	{
-		AggStatePerTrans pertrans = &aggstate->pertrans[transno];
 		AggStatePerGroup pergroupstate = &pergroup[transno];
 		FunctionCallInfo fcinfo = &pertrans->transfn_fcinfo;
 		int			inputoff = pertrans->inputoff;
@@ -2697,6 +2699,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	AggState   *aggstate;
 	AggStatePerAgg peraggs;
 	AggStatePerTrans pertransstates;
+	AggStatePerTrans pertrans;
 	Plan	   *outerPlan;
 	ExprContext *econtext;
 	int			numaggs,
@@ -3399,10 +3402,9 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	 */
 	combined_inputeval = NIL;
 	column_offset = 0;
-	for (transno = 0; transno < aggstate->numtrans; transno++)
+	for (transno = 0, pertrans = &pertransstates[0];
+		 transno < aggstate->numtrans; transno++, pertrans++)
 	{
-		AggStatePerTrans pertrans = &pertransstates[transno];
-
 		/*
 		 * Mark this per-trans state with its starting column in the combined
 		 * slot.
@@ -3940,6 +3942,7 @@ ExecEndAgg(AggState *node)
 	int			transno;
 	int			numGroupingSets = Max(node->maxsets, 1);
 	int			setno;
+	AggStatePerTrans pertrans;
 
 	/* Make sure we have closed any open tuplesorts */
 
@@ -3948,10 +3951,9 @@ ExecEndAgg(AggState *node)
 	if (node->sort_out)
 		tuplesort_end(node->sort_out);
 
-	for (transno = 0; transno < node->numtrans; transno++)
+	for (transno = 0, pertrans = &node->pertrans[0];
+		 transno < node->numtrans; transno++, pertrans++)
 	{
-		AggStatePerTrans pertrans = &node->pertrans[transno];
-
 		for (setno = 0; setno < numGroupingSets; setno++)
 		{
 			if (pertrans->sortstates[setno])
@@ -3988,6 +3990,7 @@ ExecReScanAgg(AggState *node)
 	int			transno;
 	int			numGroupingSets = Max(node->maxsets, 1);
 	int			setno;
+	AggStatePerTrans pertrans;
 
 	node->agg_done = false;
 
@@ -4019,12 +4022,11 @@ ExecReScanAgg(AggState *node)
 	}
 
 	/* Make sure we have closed any open tuplesorts */
-	for (transno = 0; transno < node->numtrans; transno++)
+	for (transno = 0, pertrans = &node->pertrans[0];
+		 transno < node->numtrans; transno++, pertrans++)
 	{
 		for (setno = 0; setno < numGroupingSets; setno++)
 		{
-			AggStatePerTrans pertrans = &node->pertrans[transno];
-
 			if (pertrans->sortstates[setno])
 			{
 				tuplesort_end(pertrans->sortstates[setno]);
-- 
2.14.1.536.g6867272d5b.dirty

>From dff0a006829a7cf47a0575bd1bdec0fd0ce82594 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Mon, 27 Nov 2017 15:53:56 -0800
Subject: [PATCH 3/4] Expression evaluatation based agg transition invocation.

Previously aggregate transition and combination functions were invoked
by special case code in nodeAgg.c, evaluting input and filters
separately using the expression evaluation machinery. That turns out
to not be great for performance for several reasons:
- repeated expression evaluations have some cost
- the transition functions invocations are poorly predicted
- filter and input computation had to be done separately
- the special case code made it hard to implement JITing of the whole
  transition function invocation

Address this by building one large expression that computes input,
evaluates filters, and invokes transition functions.

This leads to moderate speedups in queries bottlenecked by aggregate
computations, and enables large speedups for similar cases once JITing
is done.

Todo / open Qs:
- location of transition function building functions (?)
- reduce number/simplify accesses to aggstate->all_pergroup?

Author: Andres Freund
Discussion: https://postgr.es/m/20170901064131.tazjxwus3k2w3...@alap3.anarazel.de
---
 src/backend/executor/execExpr.c       | 391 ++++++++++++++-
 src/backend/executor/execExprInterp.c | 325 +++++++++++++
 src/backend/executor/nodeAgg.c        | 879 ++++------------------------------
 src/include/executor/execExpr.h       |  73 +++
 src/include/executor/executor.h       |   2 +
 src/include/executor/nodeAgg.h        | 300 ++++++++++++
 src/include/nodes/execnodes.h         |   5 +-
 7 files changed, 1189 insertions(+), 786 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index e0839616e19..a0dd9d8a069 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -43,6 +43,7 @@
 #include "optimizer/planner.h"
 #include "pgstat.h"
 #include "utils/builtins.h"
+#include "utils/datum.h"
 #include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
@@ -62,6 +63,7 @@ static void ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args,
 			 Oid funcid, Oid inputcollid, PlanState *parent,
 			 ExprState *state);
 static void ExecInitExprSlots(ExprState *state, Node *node);
+static void ExecPushExprSlots(ExprState *state, LastAttnumInfo *info);
 static bool get_last_attnums_walker(Node *node, LastAttnumInfo *info);
 static void ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable,
 					PlanState *parent);
@@ -2179,30 +2181,42 @@ static void
 ExecInitExprSlots(ExprState *state, Node *node)
 {
 	LastAttnumInfo info = {0, 0, 0};
-	ExprEvalStep scratch;
 
 	/*
 	 * Figure out which attributes we're going to need.
 	 */
 	get_last_attnums_walker(node, &info);
 
+	ExecPushExprSlots(state, &info);
+}
+
+/*
+ * Add steps deforming the ExprState's inner/out/scan slots as much as
+ * indicated by info. This is useful when building an ExprState covering more
+ * than one expression.
+ */
+static void
+ExecPushExprSlots(ExprState *state, LastAttnumInfo *info)
+{
+	ExprEvalStep scratch;
+
 	/* Emit steps as needed */
-	if (info.last_inner > 0)
+	if (info->last_inner > 0)
 	{
 		scratch.opcode = EEOP_INNER_FETCHSOME;
-		scratch.d.fetch.last_var = info.last_inner;
+		scratch.d.fetch.last_var = info->last_inner;
 		ExprEvalPushStep(state, &scratch);
 	}
-	if (info.last_outer > 0)
+	if (info->last_outer > 0)
 	{
 		scratch.opcode = EEOP_OUTER_FETCHSOME;
-		scratch.d.fetch.last_var = info.last_outer;
+		scratch.d.fetch.last_var = info->last_outer;
 		ExprEvalPushStep(state, &scratch);
 	}
-	if (info.last_scan > 0)
+	if (info->last_scan > 0)
 	{
 		scratch.opcode = EEOP_SCAN_FETCHSOME;
-		scratch.d.fetch.last_var = info.last_scan;
+		scratch.d.fetch.last_var = info->last_scan;
 		ExprEvalPushStep(state, &scratch);
 	}
 }
@@ -2703,3 +2717,366 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
 		}
 	}
 }
+
+/*
+ * Build transition/combine function invocation for a single pertrans. This is
+ * separated from ExecBuildAggTrans() because there are multiple callsites.
+ */
+static void
+ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
+					  ExprEvalStep *scratch,
+					  FunctionCallInfo fcinfo, AggStatePerTrans pertrans,
+					  int transno, int setno, int setoff, bool ishash)
+{
+	int adjust_init_jumpnull = -1;
+	int adjust_strict_jumpnull = -1;
+	ExprContext *aggcontext;
+
+	if (ishash)
+		aggcontext = aggstate->hashcontext;
+	else
+		aggcontext = aggstate->aggcontexts[setno];
+
+	/*
+	 * If the initial value for the transition state doesn't exist in the
+	 * pg_aggregate table then we will let the first non-NULL value
+	 * returned from the outer procNode become the initial value. (This is
+	 * useful for aggregates like max() and min().) The noTransValue flag
+	 * signals that we still need to do this.
+	 */
+	if (pertrans->numSortCols == 0 &&
+		fcinfo->flinfo->fn_strict &&
+		pertrans->initValueIsNull)
+	{
+		scratch->opcode = EEOP_AGG_INIT_TRANS;
+		scratch->d.agg_init_trans.aggstate = aggstate;
+		scratch->d.agg_init_trans.pertrans = pertrans;
+		scratch->d.agg_init_trans.setno = setno;
+		scratch->d.agg_init_trans.setoff = setoff;
+		scratch->d.agg_init_trans.transno = transno;
+		scratch->d.agg_init_trans.aggcontext = aggcontext;
+		scratch->d.agg_init_trans.jumpnull = -1; /* adjust later */
+		ExprEvalPushStep(state, scratch);
+
+		adjust_init_jumpnull = state->steps_len - 1;
+	}
+
+	if (pertrans->numSortCols == 0 &&
+		fcinfo->flinfo->fn_strict)
+	{
+		scratch->opcode = EEOP_AGG_STRICT_TRANS_CHECK;
+		scratch->d.agg_strict_trans_check.aggstate = aggstate;
+		scratch->d.agg_strict_trans_check.setno = setno;
+		scratch->d.agg_strict_trans_check.setoff = setoff;
+		scratch->d.agg_strict_trans_check.transno = transno;
+		scratch->d.agg_strict_trans_check.jumpnull = -1; /* adjust later */
+		ExprEvalPushStep(state, scratch);
+
+		/*
+		 * Note, we don't push into adjust_bailout here - those jump
+		 * to the end of all transition value computations.
+		 */
+		adjust_strict_jumpnull = state->steps_len - 1;
+	}
+
+	/* invoke appropriate transition implementation */
+	if (pertrans->numSortCols == 0 && pertrans->transtypeByVal)
+		scratch->opcode = EEOP_AGG_PLAIN_TRANS_BYVAL;
+	else if (pertrans->numSortCols == 0)
+		scratch->opcode = EEOP_AGG_PLAIN_TRANS;
+	else if (pertrans->numInputs == 1)
+		scratch->opcode = EEOP_AGG_ORDERED_TRANS_DATUM;
+	else
+		scratch->opcode = EEOP_AGG_ORDERED_TRANS_TUPLE;
+
+	scratch->d.agg_trans.aggstate = aggstate;
+	scratch->d.agg_trans.pertrans = pertrans;
+	scratch->d.agg_trans.setno = setno;
+	scratch->d.agg_trans.setoff = setoff;
+	scratch->d.agg_trans.transno = transno;
+	scratch->d.agg_trans.aggcontext = aggcontext;
+	ExprEvalPushStep(state, scratch);
+
+	if (adjust_init_jumpnull != -1 )
+	{
+		ExprEvalStep *as = &state->steps[adjust_init_jumpnull];
+		Assert(as->d.agg_init_trans.jumpnull == -1);
+		as->d.agg_init_trans.jumpnull = state->steps_len;
+	}
+
+	if (adjust_strict_jumpnull != -1 )
+	{
+		ExprEvalStep *as = &state->steps[adjust_strict_jumpnull];
+		Assert(as->d.agg_strict_trans_check.jumpnull == -1);
+		as->d.agg_strict_trans_check.jumpnull = state->steps_len;
+	}
+}
+
+/*
+ * Build transition/combine function invocations for all aggregate transition
+ * / combination function invocations in a grouping sets phase. This has to
+ * invoke all sorted based transitions in a phase (if doSort is true), all
+ * hash based transitions if (if doHash is true), or both (both true).
+ */
+ExprState *
+ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
+				  bool doSort, bool doHash)
+{
+	ExprState *state = makeNode(ExprState);
+	PlanState *parent = &aggstate->ss.ps;
+	ExprEvalStep scratch;
+	int transno = 0;
+	int setoff = 0;
+	bool isCombine = DO_AGGSPLIT_COMBINE(aggstate->aggsplit);
+	LastAttnumInfo deform = {0, 0, 0};
+
+	state->expr = (Expr *) aggstate;
+
+	scratch.resvalue = &state->resvalue;
+	scratch.resnull = &state->resnull;
+
+	/*
+	 * First figure out which slots, and how many columns from each, we're
+	 * going to need.
+	 */
+	for (transno = 0; transno < aggstate->numtrans; transno++)
+	{
+		AggStatePerTrans pertrans = &aggstate->pertrans[transno];
+
+		get_last_attnums_walker((Node *) pertrans->aggref->aggdirectargs,
+								&deform);
+		get_last_attnums_walker((Node *) pertrans->aggref->args,
+								&deform);
+		get_last_attnums_walker((Node *) pertrans->aggref->aggorder,
+								&deform);
+		get_last_attnums_walker((Node *) pertrans->aggref->aggdistinct,
+								&deform);
+		get_last_attnums_walker((Node *) pertrans->aggref->aggfilter,
+								&deform);
+	}
+	ExecPushExprSlots(state, &deform);
+
+	/*
+	 * Emit instructions for each transition value / grouping set combination.
+	 */
+	for (transno = 0; transno < aggstate->numtrans; transno++)
+	{
+		AggStatePerTrans pertrans = &aggstate->pertrans[transno];
+		int			numInputs = pertrans->numInputs;
+		int			argno;
+		int			setno;
+		FunctionCallInfo trans_fcinfo = &pertrans->transfn_fcinfo;
+		ListCell *arg, *bail;
+		List *adjust_bailout = NIL;
+		bool *strictnulls = NULL;
+
+		/*
+		 * If filter present, emit. Do so before evaluating the input, to
+		 * avoid potentially unneeded computations, or even worse unintended
+		 * sideeffects.  When combining all the necessary filtering has
+		 * already been done.
+		 */
+		if (pertrans->aggref->aggfilter && !isCombine)
+		{
+			/* evaluate filter expression */
+			ExecInitExprRec(pertrans->aggref->aggfilter, parent, state,
+							&state->resvalue, &state->resnull);
+			/* and jump out if false */
+			scratch.opcode = EEOP_JUMP_IF_NOT_TRUE;
+			scratch.d.jump.jumpdone = -1; /* adjust later */
+			ExprEvalPushStep(state, &scratch);
+			adjust_bailout = lappend_int(adjust_bailout,
+										 state->steps_len - 1);
+		}
+
+		/*
+		 * Evaluate aggregate input into the user of that information.
+		 */
+		argno = 0;
+
+		if (isCombine)
+		{
+			TargetEntry *source_tle;
+			Assert(pertrans->numSortCols == 0);
+			Assert(list_length(pertrans->aggref->args) == 1);
+
+			strictnulls = trans_fcinfo->argnull + 1;
+			source_tle = (TargetEntry *) linitial(pertrans->aggref->args);
+
+			/*
+			 * deserialfn_oid will be set if we must deserialize the input state
+			 * before calling the combine function.
+			 */
+			if (!OidIsValid(pertrans->deserialfn_oid))
+			{
+				/* Start from 1, since the 0th arg will be the transition value */
+				ExecInitExprRec(source_tle->expr, parent, state,
+								&trans_fcinfo->arg[argno + 1],
+								&trans_fcinfo->argnull[argno + 1]);
+			}
+			else
+			{
+				FunctionCallInfo ds_fcinfo = &pertrans->deserialfn_fcinfo;
+
+				/* evaluate argument */
+				ExecInitExprRec(source_tle->expr, parent, state,
+								&ds_fcinfo->arg[0],
+								&ds_fcinfo->argnull[0]);
+
+				/* Dummy second argument for type-safety reasons */
+				ds_fcinfo->arg[1] = PointerGetDatum(NULL);
+				ds_fcinfo->argnull[1] = false;
+
+				/* Don't call a strict deserialization function with NULL input */
+				if (pertrans->deserialfn.fn_strict)
+					scratch.opcode = EEOP_AGG_STRICT_DESERIALIZE;
+				else
+					scratch.opcode = EEOP_AGG_DESERIALIZE;
+
+				scratch.d.agg_deserialize.aggstate = aggstate;
+				scratch.d.agg_deserialize.fcinfo_data = ds_fcinfo;
+				scratch.d.agg_deserialize.jumpnull = -1; /* adjust later */
+				scratch.resvalue = &trans_fcinfo->arg[argno + 1];
+				scratch.resnull = &trans_fcinfo->argnull[argno + 1];
+
+				ExprEvalPushStep(state, &scratch);
+				adjust_bailout = lappend_int(adjust_bailout,
+											 state->steps_len - 1);
+
+				/* restore normal settings of scratch fields */
+				scratch.resvalue = &state->resvalue;
+				scratch.resnull = &state->resnull;
+			}
+			argno++;
+		}
+		else if (pertrans->numSortCols == 0)
+		{
+			/* normal transition function without ORDER BY / DISTINCT */
+			strictnulls = trans_fcinfo->argnull + 1;
+
+			foreach (arg, pertrans->aggref->args)
+			{
+				TargetEntry *source_tle = (TargetEntry *) lfirst(arg);
+
+				/* Start from 1, since the 0th arg will be the transition value */
+				ExecInitExprRec(source_tle->expr, parent, state,
+								&trans_fcinfo->arg[argno + 1],
+								&trans_fcinfo->argnull[argno + 1]);
+				argno++;
+			}
+		}
+		else if (pertrans->numInputs == 1)
+		{
+			/*
+			 * DISTINCT and/or ORDER BY case, with a single column sorted on.
+			 */
+			TargetEntry *source_tle =
+				(TargetEntry *) linitial(pertrans->aggref->args);
+			Assert(list_length(pertrans->aggref->args) == 1);
+
+			ExecInitExprRec(source_tle->expr, parent, state,
+							&state->resvalue,
+							&state->resnull);
+			strictnulls = &state->resnull;
+			argno++;
+		}
+		else
+		{
+			/*
+			 * DISTINCT and/or ORDER BY case, with multiple columns sorted on.
+			 */
+			Datum *values = pertrans->sortslot->tts_values;
+			bool *nulls = pertrans->sortslot->tts_isnull;
+
+			strictnulls = nulls;
+
+			foreach (arg, pertrans->aggref->args)
+			{
+				TargetEntry *source_tle = (TargetEntry *) lfirst(arg);
+
+				ExecInitExprRec(source_tle->expr, parent, state,
+								&values[argno], &nulls[argno]);
+				argno++;
+			}
+		}
+		Assert(numInputs == argno);
+
+		/*
+		 * For a strict transfn, nothing happens when there's a NULL input; we
+		 * just keep the prior transValue. This is true for both plain and
+		 * sorted/distinct aggregates.
+		 */
+		if (trans_fcinfo->flinfo->fn_strict && numInputs > 0)
+		{
+			scratch.opcode = EEOP_AGG_STRICT_INPUT_CHECK;
+			scratch.d.agg_strict_input_check.nulls = strictnulls;
+			scratch.d.agg_strict_input_check.jumpnull = -1; /* adjust later */
+			scratch.d.agg_strict_input_check.nargs = numInputs;
+			ExprEvalPushStep(state, &scratch);
+			adjust_bailout = lappend_int(adjust_bailout,
+										 state->steps_len - 1);
+		}
+
+
+		/* and call transition function (once for each grouping set) */
+		setoff = 0;
+		if (doSort)
+		{
+			int processGroupingSets = Max(phase->numsets, 1);
+
+			for (setno = 0; setno < processGroupingSets; setno++)
+			{
+				ExecBuildAggTransCall(state, aggstate, &scratch, trans_fcinfo,
+									  pertrans, transno, setno, setoff, false);
+				setoff++;
+			}
+		}
+
+		if (doHash)
+		{
+			int numHashes = aggstate->num_hashes;
+
+			if (aggstate->aggstrategy != AGG_HASHED)
+				setoff = aggstate->maxsets;
+			else
+				setoff = 0;
+
+			for (setno = 0; setno < numHashes; setno++)
+			{
+				ExecBuildAggTransCall(state, aggstate, &scratch, trans_fcinfo,
+									  pertrans, transno, setno, setoff, true);
+				setoff++;
+			}
+		}
+
+		/* adjust early bail out jump target(s) */
+		foreach (bail, adjust_bailout)
+		{
+			ExprEvalStep *as = &state->steps[lfirst_int(bail)];
+			if (as->opcode == EEOP_JUMP_IF_NOT_TRUE)
+			{
+				Assert(as->d.jump.jumpdone == -1);
+				as->d.jump.jumpdone = state->steps_len;
+			}
+			else if (as->opcode == EEOP_AGG_STRICT_INPUT_CHECK)
+			{
+				Assert(as->d.agg_strict_input_check.jumpnull == -1);
+				as->d.agg_strict_input_check.jumpnull = state->steps_len;
+			}
+			else if (as->opcode == EEOP_AGG_STRICT_DESERIALIZE)
+			{
+				Assert(as->d.agg_deserialize.jumpnull == -1);
+				as->d.agg_deserialize.jumpnull = state->steps_len;
+			}
+		}
+	}
+
+	scratch.resvalue = NULL;
+	scratch.resnull = NULL;
+	scratch.opcode = EEOP_DONE;
+	ExprEvalPushStep(state, &scratch);
+
+	ExecReadyExpr(state);
+
+	return state;
+}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 6c4612dad4a..4a949ec8b70 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -62,12 +62,14 @@
 #include "executor/execExpr.h"
 #include "executor/nodeSubplan.h"
 #include "funcapi.h"
+#include "utils/memutils.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
 #include "pgstat.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
+#include "utils/datum.h"
 #include "utils/lsyscache.h"
 #include "utils/timestamp.h"
 #include "utils/typcache.h"
@@ -367,6 +369,15 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_WINDOW_FUNC,
 		&&CASE_EEOP_SUBPLAN,
 		&&CASE_EEOP_ALTERNATIVE_SUBPLAN,
+		&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
+		&&CASE_EEOP_AGG_DESERIALIZE,
+		&&CASE_EEOP_AGG_STRICT_INPUT_CHECK,
+		&&CASE_EEOP_AGG_INIT_TRANS,
+		&&CASE_EEOP_AGG_STRICT_TRANS_CHECK,
+		&&CASE_EEOP_AGG_PLAIN_TRANS_BYVAL,
+		&&CASE_EEOP_AGG_PLAIN_TRANS,
+		&&CASE_EEOP_AGG_ORDERED_TRANS_DATUM,
+		&&CASE_EEOP_AGG_ORDERED_TRANS_TUPLE,
 		&&CASE_EEOP_LAST
 	};
 
@@ -1535,6 +1546,235 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		/* evaluate a strict aggregate deserialization function */
+		EEO_CASE(EEOP_AGG_STRICT_DESERIALIZE)
+		{
+			bool *nulls = op->d.agg_deserialize.fcinfo_data->argnull;
+
+			/* Don't call a strict deserialization function with NULL input */
+			if (nulls[0])
+				EEO_JUMP(op->d.agg_deserialize.jumpnull);
+
+			/* fallthrough */
+		}
+
+		/* evaluate aggregate deserialization function (non-strict portion) */
+		EEO_CASE(EEOP_AGG_DESERIALIZE)
+		{
+			FunctionCallInfo fcinfo = op->d.agg_deserialize.fcinfo_data;
+			AggState *aggstate = op->d.agg_deserialize.aggstate;
+			MemoryContext oldContext;
+
+			/*
+			 * We run the deserialization functions in per-input-tuple memory
+			 * context.
+			 */
+			oldContext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
+			fcinfo->isnull = false;
+			*op->resvalue = FunctionCallInvoke(fcinfo);
+			*op->resnull = fcinfo->isnull;
+			MemoryContextSwitchTo(oldContext);
+
+			EEO_NEXT();
+		}
+
+		/*
+		 * Check that a strict aggregate transition / combination function's
+		 * input is not NULL.
+		 */
+		EEO_CASE(EEOP_AGG_STRICT_INPUT_CHECK)
+		{
+			int argno;
+			bool *nulls = op->d.agg_strict_input_check.nulls;
+			int nargs = op->d.agg_strict_input_check.nargs;
+
+			for (argno = 0; argno < nargs; argno++)
+			{
+				if (nulls[argno])
+					EEO_JUMP(op->d.agg_strict_input_check.jumpnull);
+			}
+			EEO_NEXT();
+		}
+
+		/*
+		 * Initialize an aggregate's first value if necessary.
+		 */
+		EEO_CASE(EEOP_AGG_INIT_TRANS)
+		{
+			AggState *aggstate;
+			AggStatePerGroup pergroup;
+
+			aggstate = op->d.agg_init_trans.aggstate;
+			pergroup = &aggstate->all_pergroups
+				[op->d.agg_init_trans.setoff]
+				[op->d.agg_init_trans.transno];
+
+			/* If transValue has not yet been initialized, do so now. */
+			if (pergroup->noTransValue)
+			{
+				AggStatePerTrans pertrans = op->d.agg_init_trans.pertrans;
+
+				aggstate->curaggcontext = op->d.agg_init_trans.aggcontext;
+				aggstate->current_set = op->d.agg_init_trans.setno;
+
+				ExecAggInitGroup(aggstate, pertrans, pergroup);
+
+				/* copied trans value from input, done this round */
+				EEO_JUMP(op->d.agg_init_trans.jumpnull);
+			}
+
+			EEO_NEXT();
+		}
+
+		/* check that a strict aggregate's input isn't NULL */
+		EEO_CASE(EEOP_AGG_STRICT_TRANS_CHECK)
+		{
+			AggState *aggstate;
+			AggStatePerGroup pergroup;
+
+			aggstate = op->d.agg_strict_trans_check.aggstate;
+			pergroup = &aggstate->all_pergroups
+				[op->d.agg_strict_trans_check.setoff]
+				[op->d.agg_strict_trans_check.transno];
+
+			if (unlikely(pergroup->transValueIsNull))
+				EEO_JUMP(op->d.agg_strict_trans_check.jumpnull);
+
+			EEO_NEXT();
+		}
+
+		/*
+		 * Evaluate aggregate transition / combine function that has a
+		 * by-value transition type. That's a seperate case from the
+		 * by-reference implementation because it's a bit simpler.
+		 */
+		EEO_CASE(EEOP_AGG_PLAIN_TRANS_BYVAL)
+		{
+			AggState *aggstate;
+			AggStatePerTrans pertrans;
+			AggStatePerGroup pergroup;
+			FunctionCallInfo fcinfo;
+			MemoryContext oldContext;
+			Datum newVal;
+
+			aggstate = op->d.agg_trans.aggstate;
+			pertrans = op->d.agg_trans.pertrans;
+
+			pergroup = &aggstate->all_pergroups
+				[op->d.agg_trans.setoff]
+				[op->d.agg_trans.transno];
+
+			Assert(pertrans->transtypeByVal);
+
+			fcinfo = &pertrans->transfn_fcinfo;
+
+			/* cf. select_current_set() */
+			aggstate->curaggcontext = op->d.agg_trans.aggcontext;
+			aggstate->current_set = op->d.agg_trans.setno;
+
+			/* set up aggstate->curpertrans for AggGetAggref() */
+			aggstate->curpertrans = pertrans;
+
+			/* invoke transition function in per-tuple context */
+			oldContext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
+
+			fcinfo->arg[0] = pergroup->transValue;
+			fcinfo->argnull[0] = pergroup->transValueIsNull;
+			fcinfo->isnull = false;		/* just in case transfn doesn't set it */
+
+			newVal = FunctionCallInvoke(fcinfo);
+
+			pergroup->transValue = newVal;
+			pergroup->transValueIsNull = fcinfo->isnull;
+
+			MemoryContextSwitchTo(oldContext);
+
+			EEO_NEXT();
+		}
+
+		/*
+		 * Evaluate aggregate transition / combine function that has a
+		 * by-reference transition type.
+		 *
+		 * Could optimize a bit further by splitting off by-reference
+		 * fixed-length types, but currently that doesn't seem worth it.
+		 */
+		EEO_CASE(EEOP_AGG_PLAIN_TRANS)
+		{
+			AggState *aggstate;
+			AggStatePerTrans pertrans;
+			AggStatePerGroup pergroup;
+			FunctionCallInfo fcinfo;
+			MemoryContext oldContext;
+			Datum newVal;
+
+			aggstate = op->d.agg_trans.aggstate;
+			pertrans = op->d.agg_trans.pertrans;
+
+			pergroup = &aggstate->all_pergroups
+				[op->d.agg_trans.setoff]
+				[op->d.agg_trans.transno];
+
+			Assert(!pertrans->transtypeByVal);
+
+			fcinfo = &pertrans->transfn_fcinfo;
+
+			/* cf. select_current_set() */
+			aggstate->curaggcontext = op->d.agg_trans.aggcontext;
+			aggstate->current_set = op->d.agg_trans.setno;
+
+			/* set up aggstate->curpertrans for AggGetAggref() */
+			aggstate->curpertrans = pertrans;
+
+			/* invoke transition function in per-tuple context */
+			oldContext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
+
+			fcinfo->arg[0] = pergroup->transValue;
+			fcinfo->argnull[0] = pergroup->transValueIsNull;
+			fcinfo->isnull = false;		/* just in case transfn doesn't set it */
+
+			newVal = FunctionCallInvoke(fcinfo);
+
+			/*
+			 * For pass-by-ref datatype, must copy the new value into
+			 * aggcontext and free the prior transValue.  But if transfn
+			 * returned a pointer to its first input, we don't need to do
+			 * anything.  Also, if transfn returned a pointer to a R/W
+			 * expanded object that is already a child of the aggcontext,
+			 * assume we can adopt that value without copying it.
+			 */
+			if (DatumGetPointer(newVal) != DatumGetPointer(pergroup->transValue))
+				newVal = ExecAggTransReparent(aggstate, pertrans,
+											  newVal, fcinfo->isnull,
+											  pergroup->transValue,
+											  pergroup->transValueIsNull);
+
+			pergroup->transValue = newVal;
+			pergroup->transValueIsNull = fcinfo->isnull;
+
+			MemoryContextSwitchTo(oldContext);
+
+			EEO_NEXT();
+		}
+
+		/* process single-column ordered aggregate datum */
+		EEO_CASE(EEOP_AGG_ORDERED_TRANS_DATUM)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalAggOrderedTransDatum(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
+		/* process multi-column ordered aggregate tuple */
+		EEO_CASE(EEOP_AGG_ORDERED_TRANS_TUPLE)
+		{
+			/* too complex for an inline implementation */
+			ExecEvalAggOrderedTransTuple(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_LAST)
 		{
 			/* unreachable */
@@ -3655,3 +3895,88 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 	*op->resvalue = PointerGetDatum(dtuple);
 	*op->resnull = false;
 }
+
+void
+ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup)
+{
+   FunctionCallInfo fcinfo = &pertrans->transfn_fcinfo;
+   MemoryContext oldContext;
+
+   /*
+	* transValue has not been initialized. This is the first non-NULL
+	* input value. We use it as the initial value for transValue. (We
+	* already checked that the agg's input type is binary-compatible
+	* with its transtype, so straight copy here is OK.)
+	*
+	* We must copy the datum into aggcontext if it is pass-by-ref. We
+	* do not need to pfree the old transValue, since it's NULL.
+	*/
+   oldContext = MemoryContextSwitchTo(
+	   aggstate->curaggcontext->ecxt_per_tuple_memory);
+   pergroup->transValue = datumCopy(fcinfo->arg[1],
+									pertrans->transtypeByVal,
+									pertrans->transtypeLen);
+   pergroup->transValueIsNull = false;
+   pergroup->noTransValue = false;
+   MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Ensure that the current transition value is a child of the aggcontext,
+ * rather than the per-tuple context.
+ *
+ * NB: This can change the current memory context.
+ */
+Datum
+ExecAggTransReparent(AggState *aggstate, AggStatePerTrans pertrans,
+					 Datum newValue, bool newValueIsNull,
+					 Datum oldValue, bool oldValueIsNull)
+{
+	if (!newValueIsNull)
+	{
+		MemoryContextSwitchTo(aggstate->curaggcontext->ecxt_per_tuple_memory);
+		if (DatumIsReadWriteExpandedObject(newValue,
+										   false,
+										   pertrans->transtypeLen) &&
+			MemoryContextGetParent(DatumGetEOHP(newValue)->eoh_context) == CurrentMemoryContext)
+			/* do nothing */ ;
+		else
+			newValue = datumCopy(newValue,
+								 pertrans->transtypeByVal,
+								 pertrans->transtypeLen);
+	}
+	if (!oldValueIsNull)
+	{
+		if (DatumIsReadWriteExpandedObject(oldValue,
+										   false,
+										   pertrans->transtypeLen))
+			DeleteExpandedObject(oldValue);
+		else
+			pfree(DatumGetPointer(oldValue));
+	}
+
+	return newValue;
+}
+
+void
+ExecEvalAggOrderedTransDatum(ExprState *state, ExprEvalStep *op,
+							 ExprContext *econtext)
+{
+	AggStatePerTrans pertrans = op->d.agg_trans.pertrans;
+	int setno = op->d.agg_trans.setno;
+
+	tuplesort_putdatum(pertrans->sortstates[setno],
+					   *op->resvalue, *op->resnull);
+}
+
+void ExecEvalAggOrderedTransTuple(ExprState *state, ExprEvalStep *op,
+								  ExprContext *econtext)
+{
+	AggStatePerTrans pertrans = op->d.agg_trans.pertrans;
+	int setno = op->d.agg_trans.setno;
+
+	ExecClearTuple(pertrans->sortslot);
+	pertrans->sortslot->tts_nvalid = pertrans->numInputs;
+	ExecStoreVirtualTuple(pertrans->sortslot);
+	tuplesort_puttupleslot(pertrans->sortstates[setno], pertrans->sortslot);
+}
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 0408142764c..863caaaf118 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -90,7 +90,7 @@
  *	  but in the aggregate case we know the left input is either the initial
  *	  transition value or a previous function result, and in either case its
  *	  value need not be preserved.  See int8inc() for an example.  Notice that
- *	  advance_transition_function() is coded to avoid a data copy step when
+ *	  the EEOP_AGG_PLAIN_TRANS step is coded to avoid a data copy step when
  *	  the previous transition value pointer is returned.  It is also possible
  *	  to avoid repeated data copying when the transition value is an expanded
  *	  object: to do that, the transition function must take care to return
@@ -194,6 +194,16 @@
  *	  transition values.  hashcontext is the single context created to support
  *	  all hash tables.
  *
+ *    Transition / Combine function invocation:
+ *
+ *    For performance reasons transition functions, including combine
+ *    functions, aren't invoked one-by-one from nodeAgg after computing
+ *    arguments using the expression evaluation engine. Instead
+ *    ExecBuildAggTrans() builds one large expression that does both argument
+ *    evaluation and transition function invocation. That avoids performance
+ *    issues due to repeated uses of expression evaluation, complications due
+ *    to filter expressions having to be evaluated early, and will allow to
+ *    JIT the entire expression into one native function.
  *
  * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -229,305 +239,6 @@
 #include "utils/datum.h"
 
 
-/*
- * AggStatePerTransData - per aggregate state value information
- *
- * Working state for updating the aggregate's state value, by calling the
- * transition function with an input row. This struct does not store the
- * information needed to produce the final aggregate result from the transition
- * state, that's stored in AggStatePerAggData instead. This separation allows
- * multiple aggregate results to be produced from a single state value.
- */
-typedef struct AggStatePerTransData
-{
-	/*
-	 * These values are set up during ExecInitAgg() and do not change
-	 * thereafter:
-	 */
-
-	/*
-	 * Link to an Aggref expr this state value is for.
-	 *
-	 * There can be multiple Aggref's sharing the same state value, so long as
-	 * the inputs and transition functions are identical and the final
-	 * functions are not read-write.  This points to the first one of them.
-	 */
-	Aggref	   *aggref;
-
-	/*
-	 * Is this state value actually being shared by more than one Aggref?
-	 */
-	bool		aggshared;
-
-	/*
-	 * Number of aggregated input columns.  This includes ORDER BY expressions
-	 * in both the plain-agg and ordered-set cases.  Ordered-set direct args
-	 * are not counted, though.
-	 */
-	int			numInputs;
-
-	/*
-	 * Number of aggregated input columns to pass to the transfn.  This
-	 * includes the ORDER BY columns for ordered-set aggs, but not for plain
-	 * aggs.  (This doesn't count the transition state value!)
-	 */
-	int			numTransInputs;
-
-	/*
-	 * At each input row, we perform a single ExecProject call to evaluate all
-	 * argument expressions that will certainly be needed at this row; that
-	 * includes this aggregate's filter expression if it has one, or its
-	 * regular argument expressions (including any ORDER BY columns) if it
-	 * doesn't.  inputoff is the starting index of this aggregate's required
-	 * expressions in the resulting tuple.
-	 */
-	int			inputoff;
-
-	/* Oid of the state transition or combine function */
-	Oid			transfn_oid;
-
-	/* Oid of the serialization function or InvalidOid */
-	Oid			serialfn_oid;
-
-	/* Oid of the deserialization function or InvalidOid */
-	Oid			deserialfn_oid;
-
-	/* Oid of state value's datatype */
-	Oid			aggtranstype;
-
-	/*
-	 * fmgr lookup data for transition function or combine function.  Note in
-	 * particular that the fn_strict flag is kept here.
-	 */
-	FmgrInfo	transfn;
-
-	/* fmgr lookup data for serialization function */
-	FmgrInfo	serialfn;
-
-	/* fmgr lookup data for deserialization function */
-	FmgrInfo	deserialfn;
-
-	/* Input collation derived for aggregate */
-	Oid			aggCollation;
-
-	/* number of sorting columns */
-	int			numSortCols;
-
-	/* number of sorting columns to consider in DISTINCT comparisons */
-	/* (this is either zero or the same as numSortCols) */
-	int			numDistinctCols;
-
-	/* deconstructed sorting information (arrays of length numSortCols) */
-	AttrNumber *sortColIdx;
-	Oid		   *sortOperators;
-	Oid		   *sortCollations;
-	bool	   *sortNullsFirst;
-
-	/*
-	 * fmgr lookup data for input columns' equality operators --- only
-	 * set/used when aggregate has DISTINCT flag.  Note that these are in
-	 * order of sort column index, not parameter index.
-	 */
-	FmgrInfo   *equalfns;		/* array of length numDistinctCols */
-
-	/*
-	 * initial value from pg_aggregate entry
-	 */
-	Datum		initValue;
-	bool		initValueIsNull;
-
-	/*
-	 * We need the len and byval info for the agg's input and transition data
-	 * types in order to know how to copy/delete values.
-	 *
-	 * Note that the info for the input type is used only when handling
-	 * DISTINCT aggs with just one argument, so there is only one input type.
-	 */
-	int16		inputtypeLen,
-				transtypeLen;
-	bool		inputtypeByVal,
-				transtypeByVal;
-
-	/*
-	 * Stuff for evaluation of aggregate inputs, when they must be evaluated
-	 * separately because there's a FILTER expression.  In such cases we will
-	 * create a sortslot and the result will be stored there, whether or not
-	 * we're actually sorting.
-	 */
-	ProjectionInfo *evalproj;	/* projection machinery */
-
-	/*
-	 * Slots for holding the evaluated input arguments.  These are set up
-	 * during ExecInitAgg() and then used for each input row requiring either
-	 * FILTER or ORDER BY/DISTINCT processing.
-	 */
-	TupleTableSlot *sortslot;	/* current input tuple */
-	TupleTableSlot *uniqslot;	/* used for multi-column DISTINCT */
-	TupleDesc	sortdesc;		/* descriptor of input tuples */
-
-	/*
-	 * These values are working state that is initialized at the start of an
-	 * input tuple group and updated for each input tuple.
-	 *
-	 * For a simple (non DISTINCT/ORDER BY) aggregate, we just feed the input
-	 * values straight to the transition function.  If it's DISTINCT or
-	 * requires ORDER BY, we pass the input values into a Tuplesort object;
-	 * then at completion of the input tuple group, we scan the sorted values,
-	 * eliminate duplicates if needed, and run the transition function on the
-	 * rest.
-	 *
-	 * We need a separate tuplesort for each grouping set.
-	 */
-
-	Tuplesortstate **sortstates;	/* sort objects, if DISTINCT or ORDER BY */
-
-	/*
-	 * This field is a pre-initialized FunctionCallInfo struct used for
-	 * calling this aggregate's transfn.  We save a few cycles per row by not
-	 * re-initializing the unchanging fields; which isn't much, but it seems
-	 * worth the extra space consumption.
-	 */
-	FunctionCallInfoData transfn_fcinfo;
-
-	/* Likewise for serialization and deserialization functions */
-	FunctionCallInfoData serialfn_fcinfo;
-
-	FunctionCallInfoData deserialfn_fcinfo;
-}			AggStatePerTransData;
-
-/*
- * AggStatePerAggData - per-aggregate information
- *
- * This contains the information needed to call the final function, to produce
- * a final aggregate result from the state value. If there are multiple
- * identical Aggrefs in the query, they can all share the same per-agg data.
- *
- * These values are set up during ExecInitAgg() and do not change thereafter.
- */
-typedef struct AggStatePerAggData
-{
-	/*
-	 * Link to an Aggref expr this state value is for.
-	 *
-	 * There can be multiple identical Aggref's sharing the same per-agg. This
-	 * points to the first one of them.
-	 */
-	Aggref	   *aggref;
-
-	/* index to the state value which this agg should use */
-	int			transno;
-
-	/* Optional Oid of final function (may be InvalidOid) */
-	Oid			finalfn_oid;
-
-	/*
-	 * fmgr lookup data for final function --- only valid when finalfn_oid is
-	 * not InvalidOid.
-	 */
-	FmgrInfo	finalfn;
-
-	/*
-	 * Number of arguments to pass to the finalfn.  This is always at least 1
-	 * (the transition state value) plus any ordered-set direct args. If the
-	 * finalfn wants extra args then we pass nulls corresponding to the
-	 * aggregated input columns.
-	 */
-	int			numFinalArgs;
-
-	/* ExprStates for any direct-argument expressions */
-	List	   *aggdirectargs;
-
-	/*
-	 * We need the len and byval info for the agg's result data type in order
-	 * to know how to copy/delete values.
-	 */
-	int16		resulttypeLen;
-	bool		resulttypeByVal;
-
-	/*
-	 * "sharable" is false if this agg cannot share state values with other
-	 * aggregates because the final function is read-write.
-	 */
-	bool		sharable;
-}			AggStatePerAggData;
-
-/*
- * AggStatePerGroupData - per-aggregate-per-group working state
- *
- * These values are working state that is initialized at the start of
- * an input tuple group and updated for each input tuple.
- *
- * In AGG_PLAIN and AGG_SORTED modes, we have a single array of these
- * structs (pointed to by aggstate->pergroup); we re-use the array for
- * each input group, if it's AGG_SORTED mode.  In AGG_HASHED mode, the
- * hash table contains an array of these structs for each tuple group.
- *
- * Logically, the sortstate field belongs in this struct, but we do not
- * keep it here for space reasons: we don't support DISTINCT aggregates
- * in AGG_HASHED mode, so there's no reason to use up a pointer field
- * in every entry of the hashtable.
- */
-typedef struct AggStatePerGroupData
-{
-	Datum		transValue;		/* current transition value */
-	bool		transValueIsNull;
-
-	bool		noTransValue;	/* true if transValue not set yet */
-
-	/*
-	 * Note: noTransValue initially has the same value as transValueIsNull,
-	 * and if true both are cleared to false at the same time.  They are not
-	 * the same though: if transfn later returns a NULL, we want to keep that
-	 * NULL and not auto-replace it with a later input value. Only the first
-	 * non-NULL input will be auto-substituted.
-	 */
-}			AggStatePerGroupData;
-
-/*
- * AggStatePerPhaseData - per-grouping-set-phase state
- *
- * Grouping sets are divided into "phases", where a single phase can be
- * processed in one pass over the input. If there is more than one phase, then
- * at the end of input from the current phase, state is reset and another pass
- * taken over the data which has been re-sorted in the mean time.
- *
- * Accordingly, each phase specifies a list of grouping sets and group clause
- * information, plus each phase after the first also has a sort order.
- */
-typedef struct AggStatePerPhaseData
-{
-	AggStrategy aggstrategy;	/* strategy for this phase */
-	int			numsets;		/* number of grouping sets (or 0) */
-	int		   *gset_lengths;	/* lengths of grouping sets */
-	Bitmapset **grouped_cols;	/* column groupings for rollup */
-	FmgrInfo   *eqfunctions;	/* per-grouping-field equality fns */
-	Agg		   *aggnode;		/* Agg node for phase data */
-	Sort	   *sortnode;		/* Sort node for input ordering for phase */
-}			AggStatePerPhaseData;
-
-/*
- * AggStatePerHashData - per-hashtable state
- *
- * When doing grouping sets with hashing, we have one of these for each
- * grouping set. (When doing hashing without grouping sets, we have just one of
- * them.)
- */
-typedef struct AggStatePerHashData
-{
-	TupleHashTable hashtable;	/* hash table with one entry per group */
-	TupleHashIterator hashiter; /* for iterating through hash table */
-	TupleTableSlot *hashslot;	/* slot for loading hash table */
-	FmgrInfo   *hashfunctions;	/* per-grouping-field hash fns */
-	FmgrInfo   *eqfunctions;	/* per-grouping-field equality fns */
-	int			numCols;		/* number of hash key columns */
-	int			numhashGrpCols; /* number of columns in hash table */
-	int			largestGrpColIdx;	/* largest col required for hashing */
-	AttrNumber *hashGrpColIdxInput; /* hash col indices in input slot */
-	AttrNumber *hashGrpColIdxHash;	/* indices in hashtbl tuples */
-	Agg		   *aggnode;		/* original Agg node, for numGroups etc. */
-}			AggStatePerHashData;
-
-
 static void select_current_set(AggState *aggstate, int setno, bool is_hash);
 static void initialize_phase(AggState *aggstate, int newphase);
 static TupleTableSlot *fetch_input_tuple(AggState *aggstate);
@@ -537,12 +248,7 @@ static void initialize_aggregates(AggState *aggstate,
 static void advance_transition_function(AggState *aggstate,
 							AggStatePerTrans pertrans,
 							AggStatePerGroup pergroupstate);
-static void advance_aggregates(AggState *aggstate, AggStatePerGroup *sort_pergroups,
-				   AggStatePerGroup *hash_pergroups);
-static void advance_combine_function(AggState *aggstate,
-						 AggStatePerTrans pertrans,
-						 AggStatePerGroup pergroupstate);
-static void combine_aggregates(AggState *aggstate, AggStatePerGroup pergroup);
+static void advance_aggregates(AggState *aggstate);
 static void process_ordered_aggregate_single(AggState *aggstate,
 								 AggStatePerTrans pertrans,
 								 AggStatePerGroup pergroupstate);
@@ -568,7 +274,7 @@ static Bitmapset *find_unaggregated_cols(AggState *aggstate);
 static bool find_unaggregated_cols_walker(Node *node, Bitmapset **colnos);
 static void build_hash_table(AggState *aggstate);
 static TupleHashEntryData *lookup_hash_entry(AggState *aggstate);
-static AggStatePerGroup *lookup_hash_entries(AggState *aggstate);
+static void lookup_hash_entries(AggState *aggstate);
 static TupleTableSlot *agg_retrieve_direct(AggState *aggstate);
 static void agg_fill_hash_table(AggState *aggstate);
 static TupleTableSlot *agg_retrieve_hash_table(AggState *aggstate);
@@ -589,21 +295,6 @@ static int find_compatible_pertrans(AggState *aggstate, Aggref *newagg,
 						 List *transnos);
 
 
-/*
- * Select the current grouping set; affects current_set and
- * curaggcontext.
- */
-static void
-select_current_set(AggState *aggstate, int setno, bool is_hash)
-{
-	if (is_hash)
-		aggstate->curaggcontext = aggstate->hashcontext;
-	else
-		aggstate->curaggcontext = aggstate->aggcontexts[setno];
-
-	aggstate->current_set = setno;
-}
-
 /*
  * Switch to phase "newphase", which must either be 0 or 1 (to reset) or
  * current_phase + 1. Juggle the tuplesorts accordingly.
@@ -961,159 +652,7 @@ advance_transition_function(AggState *aggstate,
  * the inputs.
  *
  * When called, CurrentMemoryContext should be the per-query context.
- */
-static void
-advance_aggregates(AggState *aggstate, AggStatePerGroup *sort_pergroups, AggStatePerGroup *hash_pergroups)
-{
-	int			transno;
-	int			setno = 0;
-	int			numGroupingSets = Max(aggstate->phase->numsets, 1);
-	int			numHashes = aggstate->num_hashes;
-	int			numTrans = aggstate->numtrans;
-	TupleTableSlot *combinedslot;
-	AggStatePerTrans pertrans;
-
-	/* compute required inputs for all aggregates */
-	combinedslot = ExecProject(aggstate->combinedproj);
-
-	for (transno = 0, pertrans = &aggstate->pertrans[0];
-		 transno < numTrans; transno++, pertrans++)
-	{
-		int			numTransInputs = pertrans->numTransInputs;
-		int			inputoff = pertrans->inputoff;
-		TupleTableSlot *slot;
-		int			i;
-
-		/* Skip anything FILTERed out */
-		if (pertrans->aggref->aggfilter)
-		{
-			/* Check the result of the filter expression */
-			if (combinedslot->tts_isnull[inputoff] ||
-				!DatumGetBool(combinedslot->tts_values[inputoff]))
-				continue;
-
-			/* Now it's safe to evaluate this agg's arguments */
-			slot = ExecProject(pertrans->evalproj);
-			/* There's no offset needed in this slot, of course */
-			inputoff = 0;
-		}
-		else
-		{
-			/* arguments are already evaluated into combinedslot @ inputoff */
-			slot = combinedslot;
-		}
-
-		if (pertrans->numSortCols > 0)
-		{
-			/* DISTINCT and/or ORDER BY case */
-			Assert(slot->tts_nvalid >= (pertrans->numInputs + inputoff));
-			Assert(!hash_pergroups);
-
-			/*
-			 * If the transfn is strict, we want to check for nullity before
-			 * storing the row in the sorter, to save space if there are a lot
-			 * of nulls.  Note that we must only check numTransInputs columns,
-			 * not numInputs, since nullity in columns used only for sorting
-			 * is not relevant here.
-			 */
-			if (pertrans->transfn.fn_strict)
-			{
-				for (i = 0; i < numTransInputs; i++)
-				{
-					if (slot->tts_isnull[i + inputoff])
-						break;
-				}
-				if (i < numTransInputs)
-					continue;
-			}
-
-			for (setno = 0; setno < numGroupingSets; setno++)
-			{
-				/* OK, put the tuple into the tuplesort object */
-				if (pertrans->numInputs == 1)
-					tuplesort_putdatum(pertrans->sortstates[setno],
-									   slot->tts_values[inputoff],
-									   slot->tts_isnull[inputoff]);
-				else if (pertrans->aggref->aggfilter)
-				{
-					/*
-					 * When filtering and ordering, we already have a slot
-					 * containing just the argument columns.
-					 */
-					Assert(slot == pertrans->sortslot);
-					tuplesort_puttupleslot(pertrans->sortstates[setno], slot);
-				}
-				else
-				{
-					/*
-					 * Copy argument columns from combined slot, starting at
-					 * inputoff, into sortslot, so that we can store just the
-					 * columns we want.
-					 */
-					ExecClearTuple(pertrans->sortslot);
-					memcpy(pertrans->sortslot->tts_values,
-						   &slot->tts_values[inputoff],
-						   pertrans->numInputs * sizeof(Datum));
-					memcpy(pertrans->sortslot->tts_isnull,
-						   &slot->tts_isnull[inputoff],
-						   pertrans->numInputs * sizeof(bool));
-					ExecStoreVirtualTuple(pertrans->sortslot);
-					tuplesort_puttupleslot(pertrans->sortstates[setno],
-										   pertrans->sortslot);
-				}
-			}
-		}
-		else
-		{
-			/* We can apply the transition function immediately */
-			FunctionCallInfo fcinfo = &pertrans->transfn_fcinfo;
-
-			/* Load values into fcinfo */
-			/* Start from 1, since the 0th arg will be the transition value */
-			Assert(slot->tts_nvalid >= (numTransInputs + inputoff));
-
-			for (i = 0; i < numTransInputs; i++)
-			{
-				fcinfo->arg[i + 1] = slot->tts_values[i + inputoff];
-				fcinfo->argnull[i + 1] = slot->tts_isnull[i + inputoff];
-			}
-
-			if (sort_pergroups)
-			{
-				/* advance transition states for ordered grouping  */
-
-				for (setno = 0; setno < numGroupingSets; setno++)
-				{
-					AggStatePerGroup pergroupstate;
-
-					select_current_set(aggstate, setno, false);
-
-					pergroupstate = &sort_pergroups[setno][transno];
-
-					advance_transition_function(aggstate, pertrans, pergroupstate);
-				}
-			}
-
-			if (hash_pergroups)
-			{
-				/* advance transition states for hashed grouping */
-
-				for (setno = 0; setno < numHashes; setno++)
-				{
-					AggStatePerGroup pergroupstate;
-
-					select_current_set(aggstate, setno, true);
-
-					pergroupstate = &hash_pergroups[setno][transno];
-
-					advance_transition_function(aggstate, pertrans, pergroupstate);
-				}
-			}
-		}
-	}
-}
-
-/*
+ *
  * combine_aggregates replaces advance_aggregates in DO_AGGSPLIT_COMBINE
  * mode.  The principal difference is that here we may need to apply the
  * deserialization function before running the transfn (which, in this mode,
@@ -1121,192 +660,15 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup *sort_pergroups, AggStat
  * handle FILTER, DISTINCT, ORDER BY, or grouping sets.
  */
 static void
-combine_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
+advance_aggregates(AggState *aggstate)
 {
-	int			transno;
-	int			numTrans = aggstate->numtrans;
-	TupleTableSlot *slot;
-	AggStatePerTrans pertrans;
-
-	/* combine not supported with grouping sets */
-	Assert(aggstate->phase->numsets <= 1);
-
-	/* compute input for all aggregates */
-	slot = ExecProject(aggstate->combinedproj);
-
-	for (transno = 0, pertrans = &aggstate->pertrans[0];
-		 transno < numTrans; transno++, pertrans++)
-	{
-		AggStatePerGroup pergroupstate = &pergroup[transno];
-		FunctionCallInfo fcinfo = &pertrans->transfn_fcinfo;
-		int			inputoff = pertrans->inputoff;
-
-		Assert(slot->tts_nvalid > inputoff);
-
-		/*
-		 * deserialfn_oid will be set if we must deserialize the input state
-		 * before calling the combine function
-		 */
-		if (OidIsValid(pertrans->deserialfn_oid))
-		{
-			/* Don't call a strict deserialization function with NULL input */
-			if (pertrans->deserialfn.fn_strict && slot->tts_isnull[inputoff])
-			{
-				fcinfo->arg[1] = slot->tts_values[inputoff];
-				fcinfo->argnull[1] = slot->tts_isnull[inputoff];
-			}
-			else
-			{
-				FunctionCallInfo dsinfo = &pertrans->deserialfn_fcinfo;
-				MemoryContext oldContext;
-
-				dsinfo->arg[0] = slot->tts_values[inputoff];
-				dsinfo->argnull[0] = slot->tts_isnull[inputoff];
-				/* Dummy second argument for type-safety reasons */
-				dsinfo->arg[1] = PointerGetDatum(NULL);
-				dsinfo->argnull[1] = false;
-
-				/*
-				 * We run the deserialization functions in per-input-tuple
-				 * memory context.
-				 */
-				oldContext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
-
-				fcinfo->arg[1] = FunctionCallInvoke(dsinfo);
-				fcinfo->argnull[1] = dsinfo->isnull;
-
-				MemoryContextSwitchTo(oldContext);
-			}
-		}
-		else
-		{
-			fcinfo->arg[1] = slot->tts_values[inputoff];
-			fcinfo->argnull[1] = slot->tts_isnull[inputoff];
-		}
-
-		advance_combine_function(aggstate, pertrans, pergroupstate);
-	}
+	bool dummynull;
+	ExecEvalExprSwitchContext(aggstate->phase->evaltrans,
+							  aggstate->tmpcontext,
+							  &dummynull);
+	return;
 }
 
-/*
- * Perform combination of states between 2 aggregate states. Effectively this
- * 'adds' two states together by whichever logic is defined in the aggregate
- * function's combine function.
- *
- * Note that in this case transfn is set to the combination function. This
- * perhaps should be changed to avoid confusion, but one field is ok for now
- * as they'll never be needed at the same time.
- */
-static void
-advance_combine_function(AggState *aggstate,
-						 AggStatePerTrans pertrans,
-						 AggStatePerGroup pergroupstate)
-{
-	FunctionCallInfo fcinfo = &pertrans->transfn_fcinfo;
-	MemoryContext oldContext;
-	Datum		newVal;
-
-	if (pertrans->transfn.fn_strict)
-	{
-		/* if we're asked to merge to a NULL state, then do nothing */
-		if (fcinfo->argnull[1])
-			return;
-
-		if (pergroupstate->noTransValue)
-		{
-			/*
-			 * transValue has not yet been initialized.  If pass-by-ref
-			 * datatype we must copy the combining state value into
-			 * aggcontext.
-			 */
-			if (!pertrans->transtypeByVal)
-			{
-				oldContext = MemoryContextSwitchTo(
-												   aggstate->curaggcontext->ecxt_per_tuple_memory);
-				pergroupstate->transValue = datumCopy(fcinfo->arg[1],
-													  pertrans->transtypeByVal,
-													  pertrans->transtypeLen);
-				MemoryContextSwitchTo(oldContext);
-			}
-			else
-				pergroupstate->transValue = fcinfo->arg[1];
-
-			pergroupstate->transValueIsNull = false;
-			pergroupstate->noTransValue = false;
-			return;
-		}
-
-		if (pergroupstate->transValueIsNull)
-		{
-			/*
-			 * Don't call a strict function with NULL inputs.  Note it is
-			 * possible to get here despite the above tests, if the combinefn
-			 * is strict *and* returned a NULL on a prior cycle. If that
-			 * happens we will propagate the NULL all the way to the end.
-			 */
-			return;
-		}
-	}
-
-	/* We run the combine functions in per-input-tuple memory context */
-	oldContext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
-
-	/* set up aggstate->curpertrans for AggGetAggref() */
-	aggstate->curpertrans = pertrans;
-
-	/*
-	 * OK to call the combine function
-	 */
-	fcinfo->arg[0] = pergroupstate->transValue;
-	fcinfo->argnull[0] = pergroupstate->transValueIsNull;
-	fcinfo->isnull = false;		/* just in case combine func doesn't set it */
-
-	newVal = FunctionCallInvoke(fcinfo);
-
-	aggstate->curpertrans = NULL;
-
-	/*
-	 * If pass-by-ref datatype, must copy the new value into aggcontext and
-	 * free the prior transValue.  But if the combine function returned a
-	 * pointer to its first input, we don't need to do anything.  Also, if the
-	 * combine function returned a pointer to a R/W expanded object that is
-	 * already a child of the aggcontext, assume we can adopt that value
-	 * without copying it.
-	 */
-	if (!pertrans->transtypeByVal &&
-		DatumGetPointer(newVal) != DatumGetPointer(pergroupstate->transValue))
-	{
-		if (!fcinfo->isnull)
-		{
-			MemoryContextSwitchTo(aggstate->curaggcontext->ecxt_per_tuple_memory);
-			if (DatumIsReadWriteExpandedObject(newVal,
-											   false,
-											   pertrans->transtypeLen) &&
-				MemoryContextGetParent(DatumGetEOHP(newVal)->eoh_context) == CurrentMemoryContext)
-				 /* do nothing */ ;
-			else
-				newVal = datumCopy(newVal,
-								   pertrans->transtypeByVal,
-								   pertrans->transtypeLen);
-		}
-		if (!pergroupstate->transValueIsNull)
-		{
-			if (DatumIsReadWriteExpandedObject(pergroupstate->transValue,
-											   false,
-											   pertrans->transtypeLen))
-				DeleteExpandedObject(pergroupstate->transValue);
-			else
-				pfree(DatumGetPointer(pergroupstate->transValue));
-		}
-	}
-
-	pergroupstate->transValue = newVal;
-	pergroupstate->transValueIsNull = fcinfo->isnull;
-
-	MemoryContextSwitchTo(oldContext);
-}
-
-
 /*
  * Run the transition function for a DISTINCT or ORDER BY aggregate
  * with only one input.  This is called after we have completed
@@ -2101,7 +1463,7 @@ lookup_hash_entry(AggState *aggstate)
  *
  * Be aware that lookup_hash_entry can reset the tmpcontext.
  */
-static AggStatePerGroup *
+static void
 lookup_hash_entries(AggState *aggstate)
 {
 	int			numHashes = aggstate->num_hashes;
@@ -2113,8 +1475,6 @@ lookup_hash_entries(AggState *aggstate)
 		select_current_set(aggstate, setno, true);
 		pergroup[setno] = lookup_hash_entry(aggstate)->additional;
 	}
-
-	return pergroup;
 }
 
 /*
@@ -2174,7 +1534,6 @@ agg_retrieve_direct(AggState *aggstate)
 	ExprContext *tmpcontext;
 	AggStatePerAgg peragg;
 	AggStatePerGroup *pergroups;
-	AggStatePerGroup *hash_pergroups = NULL;
 	TupleTableSlot *outerslot;
 	TupleTableSlot *firstSlot;
 	TupleTableSlot *result;
@@ -2429,15 +1788,11 @@ agg_retrieve_direct(AggState *aggstate)
 					if (aggstate->aggstrategy == AGG_MIXED &&
 						aggstate->current_phase == 1)
 					{
-						hash_pergroups = lookup_hash_entries(aggstate);
+						lookup_hash_entries(aggstate);
 					}
-					else
-						hash_pergroups = NULL;
 
-					if (DO_AGGSPLIT_COMBINE(aggstate->aggsplit))
-						combine_aggregates(aggstate, pergroups[0]);
-					else
-						advance_aggregates(aggstate, pergroups, hash_pergroups);
+					/* Advance the aggregates (or combine functions) */
+					advance_aggregates(aggstate);
 
 					/* Reset per-input-tuple context after each tuple */
 					ResetExprContext(tmpcontext);
@@ -2531,8 +1886,6 @@ agg_fill_hash_table(AggState *aggstate)
 	 */
 	for (;;)
 	{
-		AggStatePerGroup *pergroups;
-
 		outerslot = fetch_input_tuple(aggstate);
 		if (TupIsNull(outerslot))
 			break;
@@ -2541,13 +1894,10 @@ agg_fill_hash_table(AggState *aggstate)
 		tmpcontext->ecxt_outertuple = outerslot;
 
 		/* Find or build hashtable entries */
-		pergroups = lookup_hash_entries(aggstate);
+		lookup_hash_entries(aggstate);
 
-		/* Advance the aggregates */
-		if (DO_AGGSPLIT_COMBINE(aggstate->aggsplit))
-			combine_aggregates(aggstate, pergroups[0]);
-		else
-			advance_aggregates(aggstate, NULL, pergroups);
+		/* Advance the aggregates (or combine functions) */
+		advance_aggregates(aggstate);
 
 		/*
 		 * Reset per-input-tuple context after each tuple, but note that the
@@ -2699,7 +2049,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	AggState   *aggstate;
 	AggStatePerAgg peraggs;
 	AggStatePerTrans pertransstates;
-	AggStatePerTrans pertrans;
+	AggStatePerGroup *pergroups;
 	Plan	   *outerPlan;
 	ExprContext *econtext;
 	int			numaggs,
@@ -2707,15 +2057,11 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 				aggno;
 	int			phase;
 	int			phaseidx;
-	List	   *combined_inputeval;
-	TupleDesc	combineddesc;
-	TupleTableSlot *combinedslot;
 	ListCell   *l;
 	Bitmapset  *all_grouped_cols = NULL;
 	int			numGroupingSets = 1;
 	int			numPhases;
 	int			numHashes;
-	int			column_offset;
 	int			i = 0;
 	int			j = 0;
 	bool		use_hashing = (node->aggstrategy == AGG_HASHED ||
@@ -3017,6 +2363,24 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	aggstate->peragg = peraggs;
 	aggstate->pertrans = pertransstates;
 
+
+	aggstate->all_pergroups =
+		(AggStatePerGroup *) palloc0(sizeof(AggStatePerGroup)
+									* (numGroupingSets + numHashes));
+	pergroups = aggstate->all_pergroups;
+
+	if (node->aggstrategy != AGG_HASHED)
+	{
+		for (i = 0; i < numGroupingSets; i++)
+		{
+			pergroups[i] = (AggStatePerGroup) palloc0(sizeof(AggStatePerGroupData)
+													 * numaggs);
+		}
+
+		aggstate->pergroups = pergroups;
+		pergroups += numGroupingSets;
+	}
+
 	/*
 	 * Hashing can only appear in the initial phase.
 	 */
@@ -3033,28 +2397,13 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 		}
 
 		/* this is an array of pointers, not structures */
-		aggstate->hash_pergroup = palloc0(sizeof(AggStatePerGroup) * numHashes);
+		aggstate->hash_pergroup = pergroups;
 
 		find_hash_columns(aggstate);
 		build_hash_table(aggstate);
 		aggstate->table_filled = false;
 	}
 
-	if (node->aggstrategy != AGG_HASHED)
-	{
-		AggStatePerGroup *pergroups =
-			(AggStatePerGroup*) palloc0(sizeof(AggStatePerGroup)
-										* numGroupingSets);
-
-		for (i = 0; i < numGroupingSets; i++)
-		{
-			pergroups[i] = (AggStatePerGroup) palloc0(sizeof(AggStatePerGroupData)
-													 * numaggs);
-		}
-
-		aggstate->pergroups = pergroups;
-	}
-
 	/*
 	 * Initialize current phase-dependent values to initial phase. The initial
 	 * phase is 1 (first sort pass) for all strategies that use sorting (if
@@ -3393,84 +2742,6 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	aggstate->numaggs = aggno + 1;
 	aggstate->numtrans = transno + 1;
 
-	/*
-	 * Build a single projection computing the required arguments for all
-	 * aggregates at once; if there's more than one, that's considerably
-	 * faster than doing it separately for each.
-	 *
-	 * First create a targetlist representing the values to compute.
-	 */
-	combined_inputeval = NIL;
-	column_offset = 0;
-	for (transno = 0, pertrans = &pertransstates[0];
-		 transno < aggstate->numtrans; transno++, pertrans++)
-	{
-		/*
-		 * Mark this per-trans state with its starting column in the combined
-		 * slot.
-		 */
-		pertrans->inputoff = column_offset;
-
-		/*
-		 * If the aggregate has a FILTER, we can only evaluate the filter
-		 * expression, not the actual input expressions, during the combined
-		 * eval step --- unless we're ignoring the filter because this node is
-		 * running combinefns not transfns.
-		 */
-		if (pertrans->aggref->aggfilter &&
-			!DO_AGGSPLIT_COMBINE(aggstate->aggsplit))
-		{
-			TargetEntry *tle;
-
-			tle = makeTargetEntry(pertrans->aggref->aggfilter,
-								  column_offset + 1, NULL, false);
-			combined_inputeval = lappend(combined_inputeval, tle);
-			column_offset++;
-
-			/*
-			 * We'll need separate projection machinery for the real args.
-			 * Arrange to evaluate them into the sortslot previously created.
-			 */
-			Assert(pertrans->sortslot);
-			pertrans->evalproj = ExecBuildProjectionInfo(pertrans->aggref->args,
-														 aggstate->tmpcontext,
-														 pertrans->sortslot,
-														 &aggstate->ss.ps,
-														 NULL);
-		}
-		else
-		{
-			/*
-			 * Add agg's input expressions to combined_inputeval, adjusting
-			 * resnos in the copied target entries to match the combined slot.
-			 */
-			ListCell   *arg;
-
-			foreach(arg, pertrans->aggref->args)
-			{
-				TargetEntry *source_tle = lfirst_node(TargetEntry, arg);
-				TargetEntry *tle;
-
-				tle = flatCopyTargetEntry(source_tle);
-				tle->resno += column_offset;
-
-				combined_inputeval = lappend(combined_inputeval, tle);
-			}
-
-			column_offset += list_length(pertrans->aggref->args);
-		}
-	}
-
-	/* Now create a projection for the combined targetlist */
-	combineddesc = ExecTypeFromTL(combined_inputeval, false);
-	combinedslot = ExecInitExtraTupleSlot(estate);
-	ExecSetSlotDescriptor(combinedslot, combineddesc);
-	aggstate->combinedproj = ExecBuildProjectionInfo(combined_inputeval,
-													 aggstate->tmpcontext,
-													 combinedslot,
-													 &aggstate->ss.ps,
-													 NULL);
-
 	/*
 	 * Last, check whether any more aggregates got added onto the node while
 	 * we processed the expressions for the aggregate arguments (including not
@@ -3486,6 +2757,60 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 				(errcode(ERRCODE_GROUPING_ERROR),
 				 errmsg("aggregate function calls cannot be nested")));
 
+	/*
+	 * Build expressions doing all the transition work at once. We build a
+	 * different one for each phase, as the number of transition function
+	 * invocation can change. Note this'll work both for transition and
+	 * combination functions (although there'll only be one phase in the
+	 * latter case).
+	 */
+	for (phaseidx = 0; phaseidx < aggstate->numphases; phaseidx++)
+	{
+		AggStatePerPhase phase = &aggstate->phases[phaseidx];
+		bool		dohash = false;
+		bool		dosort = false;
+
+		if (!phase->aggnode)
+			continue;
+
+		if (aggstate->aggstrategy == AGG_MIXED &&
+			phaseidx == 1)
+		{
+			dohash = true;
+			dosort = true;
+		}
+		else if (aggstate->aggstrategy == AGG_MIXED &&
+			phaseidx == 0)
+		{
+			dohash = true;
+			dosort = false;
+		}
+		else if (phase->aggstrategy == AGG_PLAIN ||
+				 phase->aggstrategy == AGG_SORTED)
+		{
+			dohash = false;
+			dosort = true;
+		}
+		else if (phase->aggstrategy == AGG_HASHED)
+		{
+			dohash = true;
+			dosort = false;
+		}
+		else if (phase->aggstrategy == AGG_MIXED)
+		{
+			dohash = true;
+			dosort = true;
+		}
+		else
+		{
+			Assert(false);
+		}
+
+		phase->evaltrans = ExecBuildAggTrans(aggstate, phase,
+											 dosort, dohash);
+
+	}
+
 	return aggstate;
 }
 
@@ -3541,8 +2866,6 @@ build_pertrans_for_aggref(AggStatePerTrans pertrans,
 	else
 		pertrans->numTransInputs = numArguments;
 
-	/* inputoff and evalproj will be set up later, in ExecInitAgg */
-
 	/*
 	 * When combining states, we have no use at all for the aggregate
 	 * function's transfn. Instead we use the combinefn.  In this case, the
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 5bbb63a9d80..d345c3ff280 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -15,6 +15,7 @@
 #define EXEC_EXPR_H
 
 #include "nodes/execnodes.h"
+#include "executor/nodeAgg.h"
 
 /* forward reference to avoid circularity */
 struct ArrayRefState;
@@ -212,6 +213,17 @@ typedef enum ExprEvalOp
 	EEOP_SUBPLAN,
 	EEOP_ALTERNATIVE_SUBPLAN,
 
+	/* aggregation related nodes */
+	EEOP_AGG_STRICT_DESERIALIZE,
+	EEOP_AGG_DESERIALIZE,
+	EEOP_AGG_STRICT_INPUT_CHECK,
+	EEOP_AGG_INIT_TRANS,
+	EEOP_AGG_STRICT_TRANS_CHECK,
+	EEOP_AGG_PLAIN_TRANS_BYVAL,
+	EEOP_AGG_PLAIN_TRANS,
+	EEOP_AGG_ORDERED_TRANS_DATUM,
+	EEOP_AGG_ORDERED_TRANS_TUPLE,
+
 	/* non-existent operation, used e.g. to check array lengths */
 	EEOP_LAST
 } ExprEvalOp;
@@ -558,6 +570,55 @@ typedef struct ExprEvalStep
 			/* out-of-line state, created by nodeSubplan.c */
 			AlternativeSubPlanState *asstate;
 		}			alternative_subplan;
+
+		/* for EEOP_AGG_*DESERIALIZE */
+		struct
+		{
+			AggState *aggstate;
+			FunctionCallInfo fcinfo_data;
+			int jumpnull;
+		} agg_deserialize;
+
+		/* for EEOP_AGG_STRICT_INPUT_CHECK */
+		struct
+		{
+			bool *nulls;
+			int nargs;
+			int jumpnull;
+		} agg_strict_input_check;
+
+		/* for EEOP_AGG_INIT_TRANS */
+		struct
+		{
+			AggState *aggstate;
+			AggStatePerTrans pertrans;
+			ExprContext *aggcontext;
+			int setno;
+			int transno;
+			int setoff;
+			int jumpnull;
+		} agg_init_trans;
+
+		/* for EEOP_AGG_STRICT_TRANS_CHECK */
+		struct
+		{
+			AggState *aggstate;
+			int setno;
+			int transno;
+			int setoff;
+			int jumpnull;
+		} agg_strict_trans_check;
+
+		/* for EEOP_AGG_{PLAIN,ORDERED}_TRANS* */
+		struct
+		{
+			AggState *aggstate;
+			AggStatePerTrans pertrans;
+			ExprContext *aggcontext;
+			int setno;
+			int transno;
+			int setoff;
+		} agg_trans;
 	}			d;
 } ExprEvalStep;
 
@@ -648,4 +709,16 @@ extern void ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op,
 extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op,
 					ExprContext *econtext);
 
+extern void ExecEvalAggOrderedTransDatum(ExprState *state, ExprEvalStep *op,
+										 ExprContext *econtext);
+extern void ExecEvalAggOrderedTransTuple(ExprState *state, ExprEvalStep *op,
+										 ExprContext *econtext);
+
+
+extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup);
+
+extern Datum ExecAggTransReparent(AggState *aggstate, AggStatePerTrans pertrans,
+								  Datum newValue, bool newValueIsNull,
+								  Datum oldValue, bool oldValueIsNull);
+
 #endif							/* EXEC_EXPR_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index bee4ebf2693..3f9085bc9f2 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -249,6 +249,8 @@ extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
 extern ExprState *ExecInitQual(List *qual, PlanState *parent);
 extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
 extern List *ExecInitExprList(List *nodes, PlanState *parent);
+extern ExprState *ExecBuildAggTrans(AggState *aggstate, struct AggStatePerPhaseData *phase,
+									bool doSort, bool doHash);
 extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList,
 						ExprContext *econtext,
 						TupleTableSlot *slot,
diff --git a/src/include/executor/nodeAgg.h b/src/include/executor/nodeAgg.h
index eff5af9c2a4..20364530d55 100644
--- a/src/include/executor/nodeAgg.h
+++ b/src/include/executor/nodeAgg.h
@@ -16,6 +16,289 @@
 
 #include "nodes/execnodes.h"
 
+
+/*
+ * AggStatePerTransData - per aggregate state value information
+ *
+ * Working state for updating the aggregate's state value, by calling the
+ * transition function with an input row. This struct does not store the
+ * information needed to produce the final aggregate result from the transition
+ * state, that's stored in AggStatePerAggData instead. This separation allows
+ * multiple aggregate results to be produced from a single state value.
+ */
+typedef struct AggStatePerTransData
+{
+	/*
+	 * These values are set up during ExecInitAgg() and do not change
+	 * thereafter:
+	 */
+
+	/*
+	 * Link to an Aggref expr this state value is for.
+	 *
+	 * There can be multiple Aggref's sharing the same state value, so long as
+	 * the inputs and transition functions are identical and the final
+	 * functions are not read-write.  This points to the first one of them.
+	 */
+	Aggref	   *aggref;
+
+	/*
+	 * Is this state value actually being shared by more than one Aggref?
+	 */
+	bool		aggshared;
+
+	/*
+	 * Number of aggregated input columns.  This includes ORDER BY expressions
+	 * in both the plain-agg and ordered-set cases.  Ordered-set direct args
+	 * are not counted, though.
+	 */
+	int			numInputs;
+
+	/*
+	 * Number of aggregated input columns to pass to the transfn.  This
+	 * includes the ORDER BY columns for ordered-set aggs, but not for plain
+	 * aggs.  (This doesn't count the transition state value!)
+	 */
+	int			numTransInputs;
+
+	/* Oid of the state transition or combine function */
+	Oid			transfn_oid;
+
+	/* Oid of the serialization function or InvalidOid */
+	Oid			serialfn_oid;
+
+	/* Oid of the deserialization function or InvalidOid */
+	Oid			deserialfn_oid;
+
+	/* Oid of state value's datatype */
+	Oid			aggtranstype;
+
+	/*
+	 * fmgr lookup data for transition function or combine function.  Note in
+	 * particular that the fn_strict flag is kept here.
+	 */
+	FmgrInfo	transfn;
+
+	/* fmgr lookup data for serialization function */
+	FmgrInfo	serialfn;
+
+	/* fmgr lookup data for deserialization function */
+	FmgrInfo	deserialfn;
+
+	/* Input collation derived for aggregate */
+	Oid			aggCollation;
+
+	/* number of sorting columns */
+	int			numSortCols;
+
+	/* number of sorting columns to consider in DISTINCT comparisons */
+	/* (this is either zero or the same as numSortCols) */
+	int			numDistinctCols;
+
+	/* deconstructed sorting information (arrays of length numSortCols) */
+	AttrNumber *sortColIdx;
+	Oid		   *sortOperators;
+	Oid		   *sortCollations;
+	bool	   *sortNullsFirst;
+
+	/*
+	 * fmgr lookup data for input columns' equality operators --- only
+	 * set/used when aggregate has DISTINCT flag.  Note that these are in
+	 * order of sort column index, not parameter index.
+	 */
+	FmgrInfo   *equalfns;		/* array of length numDistinctCols */
+
+	/*
+	 * initial value from pg_aggregate entry
+	 */
+	Datum		initValue;
+	bool		initValueIsNull;
+
+	/*
+	 * We need the len and byval info for the agg's input and transition data
+	 * types in order to know how to copy/delete values.
+	 *
+	 * Note that the info for the input type is used only when handling
+	 * DISTINCT aggs with just one argument, so there is only one input type.
+	 */
+	int16		inputtypeLen,
+				transtypeLen;
+	bool		inputtypeByVal,
+				transtypeByVal;
+
+	/*
+	 * Slots for holding the evaluated input arguments.  These are set up
+	 * during ExecInitAgg() and then used for each input row requiring either
+	 * FILTER or ORDER BY/DISTINCT processing.
+	 */
+	TupleTableSlot *sortslot;	/* current input tuple */
+	TupleTableSlot *uniqslot;	/* used for multi-column DISTINCT */
+	TupleDesc	sortdesc;		/* descriptor of input tuples */
+
+	/*
+	 * These values are working state that is initialized at the start of an
+	 * input tuple group and updated for each input tuple.
+	 *
+	 * For a simple (non DISTINCT/ORDER BY) aggregate, we just feed the input
+	 * values straight to the transition function.  If it's DISTINCT or
+	 * requires ORDER BY, we pass the input values into a Tuplesort object;
+	 * then at completion of the input tuple group, we scan the sorted values,
+	 * eliminate duplicates if needed, and run the transition function on the
+	 * rest.
+	 *
+	 * We need a separate tuplesort for each grouping set.
+	 */
+
+	Tuplesortstate **sortstates;	/* sort objects, if DISTINCT or ORDER BY */
+
+	/*
+	 * This field is a pre-initialized FunctionCallInfo struct used for
+	 * calling this aggregate's transfn.  We save a few cycles per row by not
+	 * re-initializing the unchanging fields; which isn't much, but it seems
+	 * worth the extra space consumption.
+	 */
+	FunctionCallInfoData transfn_fcinfo;
+
+	/* Likewise for serialization and deserialization functions */
+	FunctionCallInfoData serialfn_fcinfo;
+
+	FunctionCallInfoData deserialfn_fcinfo;
+}			AggStatePerTransData;
+
+/*
+ * AggStatePerAggData - per-aggregate information
+ *
+ * This contains the information needed to call the final function, to produce
+ * a final aggregate result from the state value. If there are multiple
+ * identical Aggrefs in the query, they can all share the same per-agg data.
+ *
+ * These values are set up during ExecInitAgg() and do not change thereafter.
+ */
+typedef struct AggStatePerAggData
+{
+	/*
+	 * Link to an Aggref expr this state value is for.
+	 *
+	 * There can be multiple identical Aggref's sharing the same per-agg. This
+	 * points to the first one of them.
+	 */
+	Aggref	   *aggref;
+
+	/* index to the state value which this agg should use */
+	int			transno;
+
+	/* Optional Oid of final function (may be InvalidOid) */
+	Oid			finalfn_oid;
+
+	/*
+	 * fmgr lookup data for final function --- only valid when finalfn_oid is
+	 * not InvalidOid.
+	 */
+	FmgrInfo	finalfn;
+
+	/*
+	 * Number of arguments to pass to the finalfn.  This is always at least 1
+	 * (the transition state value) plus any ordered-set direct args. If the
+	 * finalfn wants extra args then we pass nulls corresponding to the
+	 * aggregated input columns.
+	 */
+	int			numFinalArgs;
+
+	/* ExprStates for any direct-argument expressions */
+	List	   *aggdirectargs;
+
+	/*
+	 * We need the len and byval info for the agg's result data type in order
+	 * to know how to copy/delete values.
+	 */
+	int16		resulttypeLen;
+	bool		resulttypeByVal;
+
+	/*
+	 * "sharable" is false if this agg cannot share state values with other
+	 * aggregates because the final function is read-write.
+	 */
+	bool		sharable;
+}			AggStatePerAggData;
+
+/*
+ * AggStatePerGroupData - per-aggregate-per-group working state
+ *
+ * These values are working state that is initialized at the start of
+ * an input tuple group and updated for each input tuple.
+ *
+ * In AGG_PLAIN and AGG_SORTED modes, we have a single array of these
+ * structs (pointed to by aggstate->pergroup); we re-use the array for
+ * each input group, if it's AGG_SORTED mode.  In AGG_HASHED mode, the
+ * hash table contains an array of these structs for each tuple group.
+ *
+ * Logically, the sortstate field belongs in this struct, but we do not
+ * keep it here for space reasons: we don't support DISTINCT aggregates
+ * in AGG_HASHED mode, so there's no reason to use up a pointer field
+ * in every entry of the hashtable.
+ */
+typedef struct AggStatePerGroupData
+{
+	Datum		transValue;		/* current transition value */
+	bool		transValueIsNull;
+
+	bool		noTransValue;	/* true if transValue not set yet */
+
+	/*
+	 * Note: noTransValue initially has the same value as transValueIsNull,
+	 * and if true both are cleared to false at the same time.  They are not
+	 * the same though: if transfn later returns a NULL, we want to keep that
+	 * NULL and not auto-replace it with a later input value. Only the first
+	 * non-NULL input will be auto-substituted.
+	 */
+}			AggStatePerGroupData;
+
+/*
+ * AggStatePerPhaseData - per-grouping-set-phase state
+ *
+ * Grouping sets are divided into "phases", where a single phase can be
+ * processed in one pass over the input. If there is more than one phase, then
+ * at the end of input from the current phase, state is reset and another pass
+ * taken over the data which has been re-sorted in the mean time.
+ *
+ * Accordingly, each phase specifies a list of grouping sets and group clause
+ * information, plus each phase after the first also has a sort order.
+ */
+typedef struct AggStatePerPhaseData
+{
+	AggStrategy aggstrategy;	/* strategy for this phase */
+	int			numsets;		/* number of grouping sets (or 0) */
+	int		   *gset_lengths;	/* lengths of grouping sets */
+	Bitmapset **grouped_cols;	/* column groupings for rollup */
+	FmgrInfo   *eqfunctions;	/* per-grouping-field equality fns */
+	Agg		   *aggnode;		/* Agg node for phase data */
+	Sort	   *sortnode;		/* Sort node for input ordering for phase */
+
+	ExprState  *evaltrans;		/* evaluation of transition functions  */
+}			AggStatePerPhaseData;
+
+/*
+ * AggStatePerHashData - per-hashtable state
+ *
+ * When doing grouping sets with hashing, we have one of these for each
+ * grouping set. (When doing hashing without grouping sets, we have just one of
+ * them.)
+ */
+typedef struct AggStatePerHashData
+{
+	TupleHashTable hashtable;	/* hash table with one entry per group */
+	TupleHashIterator hashiter; /* for iterating through hash table */
+	TupleTableSlot *hashslot;	/* slot for loading hash table */
+	FmgrInfo   *hashfunctions;	/* per-grouping-field hash fns */
+	FmgrInfo   *eqfunctions;	/* per-grouping-field equality fns */
+	int			numCols;		/* number of hash key columns */
+	int			numhashGrpCols; /* number of columns in hash table */
+	int			largestGrpColIdx;	/* largest col required for hashing */
+	AttrNumber *hashGrpColIdxInput; /* hash col indices in input slot */
+	AttrNumber *hashGrpColIdxHash;	/* indices in hashtbl tuples */
+	Agg		   *aggnode;		/* original Agg node, for numGroups etc. */
+}			AggStatePerHashData;
+
 extern AggState *ExecInitAgg(Agg *node, EState *estate, int eflags);
 extern void ExecEndAgg(AggState *node);
 extern void ExecReScanAgg(AggState *node);
@@ -24,4 +307,21 @@ extern Size hash_agg_entry_size(int numAggs);
 
 extern Datum aggregate_dummy(PG_FUNCTION_ARGS);
 
+/*
+ * Select the current grouping set; affects current_set and
+ * curaggcontext.
+ */
+static inline void
+select_current_set(AggState *aggstate, int setno, bool is_hash)
+{
+	/* when changing this, also adapt ExecInterpExpr() and friends */
+	if (is_hash)
+		aggstate->curaggcontext = aggstate->hashcontext;
+	else
+		aggstate->curaggcontext = aggstate->aggcontexts[setno];
+
+	aggstate->current_set = setno;
+}
+
+
 #endif							/* NODEAGG_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 34a4a3d24f9..9cc5a105539 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1839,10 +1839,13 @@ typedef struct AggState
 	/* these fields are used in AGG_HASHED and AGG_MIXED modes: */
 	bool		table_filled;	/* hash table filled yet? */
 	int			num_hashes;
-	AggStatePerHash perhash;
+	AggStatePerHash perhash;	/* array of per-hashtable data */
 	AggStatePerGroup *hash_pergroup;	/* grouping set indexed array of
 										 * per-group pointers */
+
 	/* support for evaluation of agg input expressions: */
+	AggStatePerGroup *all_pergroups;	/* array of first ->pergroups, than
+										 * ->hash_pergroup */
 	ProjectionInfo *combinedproj;	/* projection machinery */
 } AggState;
 
-- 
2.14.1.536.g6867272d5b.dirty

>From 9554751a1dfd1f46f799b33b406a1ecd6c14abba Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Tue, 3 Oct 2017 23:45:44 -0700
Subject: [PATCH 4/4] WIP: Do execGrouping.c via expression eval machinery.

This is beneficial performance wise on its own, although not hugely
so. But the primary benefit is that it allows JITing the deforming.

Author: Andres Freund
---
 src/backend/executor/execExpr.c           | 101 +++++++++++++
 src/backend/executor/execExprInterp.c     |  29 ++++
 src/backend/executor/execGrouping.c       | 238 +++++++-----------------------
 src/backend/executor/nodeAgg.c            | 109 ++++++++------
 src/backend/executor/nodeGroup.c          |  31 ++--
 src/backend/executor/nodeRecursiveunion.c |   5 +-
 src/backend/executor/nodeSetOp.c          |  50 ++++---
 src/backend/executor/nodeSubplan.c        |  84 ++++++++++-
 src/backend/executor/nodeUnique.c         |  35 +++--
 src/backend/executor/nodeWindowAgg.c      |  41 +++--
 src/backend/utils/adt/orderedsetaggs.c    |  60 ++++----
 src/include/executor/execExpr.h           |   1 +
 src/include/executor/executor.h           |  28 ++--
 src/include/executor/nodeAgg.h            |  12 +-
 src/include/nodes/execnodes.h             |  14 +-
 15 files changed, 474 insertions(+), 364 deletions(-)

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index a0dd9d8a069..d3eaf8fb00f 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3080,3 +3080,104 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
 
 	return state;
 }
+
+/*
+ * Build equality expression that can be evaluated using ExecQual(), returning
+ * true if the expression context's inner/outer tuple are NOT DISTINCT. I.e
+ * two nulls match, a null and a not-null don't match.
+ *
+ * desc: tuple descriptor of the to-be-compared tuples
+ * numCols: the number of attributes to be examined
+ * keyColIdx: array of attribute column numbers
+ * eqFunctions: array of function oids of the equality functions to use
+ * parent: parent executor node
+ */
+ExprState *
+ExecBuildGroupingEqual(TupleDesc desc,
+					  int numCols,
+					  AttrNumber *keyColIdx,
+					  Oid *eqfunctions,
+					  PlanState *parent)
+{
+	ExprState *state = makeNode(ExprState);
+	ExprEvalStep scratch;
+	int			natt;
+	int			maxatt = -1;
+	List	   *adjust_jumps = NIL;
+	ListCell   *lc;
+
+	state->expr = NULL;
+	state->flags = EEO_FLAG_IS_QUAL;
+
+	scratch.resvalue = &state->resvalue;
+	scratch.resnull = &state->resnull;
+
+	/* compute max needed attribute */
+	for (natt = 0; natt < numCols; natt++)
+	{
+		int			attno = keyColIdx[natt];
+
+		if (attno > maxatt)
+			maxatt = attno;
+	}
+	Assert(maxatt >= 0);
+
+	/* push deform steps */
+	scratch.opcode = EEOP_INNER_FETCHSOME;
+	scratch.d.fetch.last_var = maxatt;
+	ExprEvalPushStep(state, &scratch);
+
+	scratch.opcode = EEOP_OUTER_FETCHSOME;
+	scratch.d.fetch.last_var = maxatt;
+	ExprEvalPushStep(state, &scratch);
+
+	/* FIXME: Old code did this backwards, do we want to keep that? */
+	for (natt = 0; natt < numCols; natt++)
+	{
+		int attno = keyColIdx[natt];
+		Form_pg_attribute att = TupleDescAttr(desc, attno - 1);
+		Var *larg, *rarg;
+		List *args;
+
+		/* left arg */
+		larg = makeVar(INNER_VAR, attno, att->atttypid,
+					   att->atttypmod, InvalidOid, 0);
+		/* right arg */
+		rarg = makeVar(OUTER_VAR, attno, att->atttypid,
+					   att->atttypmod, InvalidOid, 0);
+		args = list_make2(larg, rarg);
+
+		/* evaluate distinctness */
+		ExecInitFunc(&scratch, NULL,
+					 args, eqfunctions[natt], InvalidOid,
+					 parent, state);
+		scratch.opcode = EEOP_NOT_DISTINCT;
+		ExprEvalPushStep(state, &scratch);
+
+		/* then emit EEOP_QUAL to detect if result is false (or null) */
+		scratch.opcode = EEOP_QUAL;
+		scratch.d.qualexpr.jumpdone = -1;
+		ExprEvalPushStep(state, &scratch);
+		adjust_jumps = lappend_int(adjust_jumps,
+								   state->steps_len - 1);
+	}
+
+	/* adjust jump targets */
+	foreach(lc, adjust_jumps)
+	{
+		ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+
+		Assert(as->opcode == EEOP_QUAL);
+		Assert(as->d.qualexpr.jumpdone == -1);
+		as->d.qualexpr.jumpdone = state->steps_len;
+	}
+
+	scratch.resvalue = NULL;
+	scratch.resnull = NULL;
+	scratch.opcode = EEOP_DONE;
+	ExprEvalPushStep(state, &scratch);
+
+	ExecReadyExpr(state);
+
+	return state;
+}
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 4a949ec8b70..425a462fd9c 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -341,6 +341,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_MAKE_READONLY,
 		&&CASE_EEOP_IOCOERCE,
 		&&CASE_EEOP_DISTINCT,
+		&&CASE_EEOP_NOT_DISTINCT,
 		&&CASE_EEOP_NULLIF,
 		&&CASE_EEOP_SQLVALUEFUNCTION,
 		&&CASE_EEOP_CURRENTOFEXPR,
@@ -1217,6 +1218,34 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		/* see EEOP_DISTINCT for comments, this is just inverted  */
+		EEO_CASE(EEOP_NOT_DISTINCT)
+		{
+			FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+
+			if (fcinfo->argnull[0] && fcinfo->argnull[1])
+			{
+				*op->resvalue = BoolGetDatum(true);
+				*op->resnull = false;
+			}
+			else if (fcinfo->argnull[0] || fcinfo->argnull[1])
+			{
+				*op->resvalue = BoolGetDatum(false);
+				*op->resnull = false;
+			}
+			else
+			{
+				Datum		eqresult;
+
+				fcinfo->isnull = false;
+				eqresult = op->d.func.fn_addr(fcinfo);
+				*op->resvalue = BoolGetDatum(DatumGetBool(eqresult));
+				*op->resnull = fcinfo->isnull;
+			}
+
+			EEO_NEXT();
+		}
+
 		EEO_CASE(EEOP_NULLIF)
 		{
 			/*
diff --git a/src/backend/executor/execGrouping.c b/src/backend/executor/execGrouping.c
index 07c8852fca8..2e3f793add0 100644
--- a/src/backend/executor/execGrouping.c
+++ b/src/backend/executor/execGrouping.c
@@ -50,173 +50,34 @@ static int	TupleHashTableMatch(struct tuplehash_hash *tb, const MinimalTuple tup
  *		Utility routines for grouping tuples together
  *****************************************************************************/
 
-/*
- * execTuplesMatch
- *		Return true if two tuples match in all the indicated fields.
- *
- * This actually implements SQL's notion of "not distinct".  Two nulls
- * match, a null and a not-null don't match.
- *
- * slot1, slot2: the tuples to compare (must have same columns!)
- * numCols: the number of attributes to be examined
- * matchColIdx: array of attribute column numbers
- * eqFunctions: array of fmgr lookup info for the equality functions to use
- * evalContext: short-term memory context for executing the functions
- *
- * NB: evalContext is reset each time!
- */
-bool
-execTuplesMatch(TupleTableSlot *slot1,
-				TupleTableSlot *slot2,
-				int numCols,
-				AttrNumber *matchColIdx,
-				FmgrInfo *eqfunctions,
-				MemoryContext evalContext)
-{
-	MemoryContext oldContext;
-	bool		result;
-	int			i;
-
-	/* Reset and switch into the temp context. */
-	MemoryContextReset(evalContext);
-	oldContext = MemoryContextSwitchTo(evalContext);
-
-	/*
-	 * We cannot report a match without checking all the fields, but we can
-	 * report a non-match as soon as we find unequal fields.  So, start
-	 * comparing at the last field (least significant sort key). That's the
-	 * most likely to be different if we are dealing with sorted input.
-	 */
-	result = true;
-
-	for (i = numCols; --i >= 0;)
-	{
-		AttrNumber	att = matchColIdx[i];
-		Datum		attr1,
-					attr2;
-		bool		isNull1,
-					isNull2;
-
-		attr1 = slot_getattr(slot1, att, &isNull1);
-
-		attr2 = slot_getattr(slot2, att, &isNull2);
-
-		if (isNull1 != isNull2)
-		{
-			result = false;		/* one null and one not; they aren't equal */
-			break;
-		}
-
-		if (isNull1)
-			continue;			/* both are null, treat as equal */
-
-		/* Apply the type-specific equality function */
-
-		if (!DatumGetBool(FunctionCall2(&eqfunctions[i],
-										attr1, attr2)))
-		{
-			result = false;		/* they aren't equal */
-			break;
-		}
-	}
-
-	MemoryContextSwitchTo(oldContext);
-
-	return result;
-}
-
-/*
- * execTuplesUnequal
- *		Return true if two tuples are definitely unequal in the indicated
- *		fields.
- *
- * Nulls are neither equal nor unequal to anything else.  A true result
- * is obtained only if there are non-null fields that compare not-equal.
- *
- * Parameters are identical to execTuplesMatch.
- */
-bool
-execTuplesUnequal(TupleTableSlot *slot1,
-				  TupleTableSlot *slot2,
-				  int numCols,
-				  AttrNumber *matchColIdx,
-				  FmgrInfo *eqfunctions,
-				  MemoryContext evalContext)
-{
-	MemoryContext oldContext;
-	bool		result;
-	int			i;
-
-	/* Reset and switch into the temp context. */
-	MemoryContextReset(evalContext);
-	oldContext = MemoryContextSwitchTo(evalContext);
-
-	/*
-	 * We cannot report a match without checking all the fields, but we can
-	 * report a non-match as soon as we find unequal fields.  So, start
-	 * comparing at the last field (least significant sort key). That's the
-	 * most likely to be different if we are dealing with sorted input.
-	 */
-	result = false;
-
-	for (i = numCols; --i >= 0;)
-	{
-		AttrNumber	att = matchColIdx[i];
-		Datum		attr1,
-					attr2;
-		bool		isNull1,
-					isNull2;
-
-		attr1 = slot_getattr(slot1, att, &isNull1);
-
-		if (isNull1)
-			continue;			/* can't prove anything here */
-
-		attr2 = slot_getattr(slot2, att, &isNull2);
-
-		if (isNull2)
-			continue;			/* can't prove anything here */
-
-		/* Apply the type-specific equality function */
-
-		if (!DatumGetBool(FunctionCall2(&eqfunctions[i],
-										attr1, attr2)))
-		{
-			result = true;		/* they are unequal */
-			break;
-		}
-	}
-
-	MemoryContextSwitchTo(oldContext);
-
-	return result;
-}
-
-
 /*
  * execTuplesMatchPrepare
- *		Look up the equality functions needed for execTuplesMatch or
- *		execTuplesUnequal, given an array of equality operator OIDs.
- *
- * The result is a palloc'd array.
+ *		Build expression that can be evaluated using ExecQual(), returning
+ *		whether an ExprContext's inner/outer tuples are NOT DISTINCT
  */
-FmgrInfo *
-execTuplesMatchPrepare(int numCols,
-					   Oid *eqOperators)
+ExprState *
+execTuplesMatchPrepare(TupleDesc desc,
+					   int numCols,
+					   AttrNumber *keyColIdx,
+					   Oid *eqOperators,
+					   PlanState *parent)
 {
-	FmgrInfo   *eqFunctions = (FmgrInfo *) palloc(numCols * sizeof(FmgrInfo));
+	Oid		   *eqFunctions = (Oid *) palloc(numCols * sizeof(Oid));
 	int			i;
+	ExprState	*expr;
 
+	if (numCols == 0)
+		return NULL;
+
+	/* lookup equality functions */
 	for (i = 0; i < numCols; i++)
-	{
-		Oid			eq_opr = eqOperators[i];
-		Oid			eq_function;
+		eqFunctions[i] = get_opcode(eqOperators[i]);
 
-		eq_function = get_opcode(eq_opr);
-		fmgr_info(eq_function, &eqFunctions[i]);
-	}
+	/* build actual expression */
+	expr = ExecBuildGroupingEqual(desc, numCols, keyColIdx, eqFunctions,
+								  parent);
 
-	return eqFunctions;
+	return expr;
 }
 
 /*
@@ -287,7 +148,9 @@ execTuplesHashPrepare(int numCols,
  * storage that will live as long as the hashtable does.
  */
 TupleHashTable
-BuildTupleHashTable(int numCols, AttrNumber *keyColIdx,
+BuildTupleHashTable(PlanState *parent,
+					TupleDesc inputDesc,
+					int numCols, AttrNumber *keyColIdx,
 					FmgrInfo *eqfunctions,
 					FmgrInfo *hashfunctions,
 					long nbuckets, Size additionalsize,
@@ -296,6 +159,9 @@ BuildTupleHashTable(int numCols, AttrNumber *keyColIdx,
 {
 	TupleHashTable hashtable;
 	Size		entrysize = sizeof(TupleHashEntryData) + additionalsize;
+	MemoryContext oldcontext;
+	Oid		   *eqoids = (Oid *) palloc(numCols * sizeof(Oid));
+	int			i;
 
 	Assert(nbuckets > 0);
 
@@ -332,6 +198,25 @@ BuildTupleHashTable(int numCols, AttrNumber *keyColIdx,
 
 	hashtable->hashtab = tuplehash_create(tablecxt, nbuckets, hashtable);
 
+	oldcontext = MemoryContextSwitchTo(hashtable->tablecxt);
+	/*
+	 * We copy the input tuple descriptor just for safety --- we assume
+	 * all input tuples will have equivalent descriptors.
+	 */
+	hashtable->tableslot = MakeSingleTupleTableSlot(CreateTupleDescCopy(inputDesc));
+
+	/* build comparator for all columns */
+	for (i = 0; i < numCols; i++)
+		eqoids[i] = eqfunctions[i].fn_oid;
+	hashtable->eq_func = ExecBuildGroupingEqual(inputDesc,
+												numCols,
+												keyColIdx, eqoids,
+												parent);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	hashtable->exprcontext = CreateExprContext(parent->state);
+
 	return hashtable;
 }
 
@@ -356,22 +241,6 @@ LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot *slot,
 	bool		found;
 	MinimalTuple key;
 
-	/* If first time through, clone the input slot to make table slot */
-	if (hashtable->tableslot == NULL)
-	{
-		TupleDesc	tupdesc;
-
-		oldContext = MemoryContextSwitchTo(hashtable->tablecxt);
-
-		/*
-		 * We copy the input tuple descriptor just for safety --- we assume
-		 * all input tuples will have equivalent descriptors.
-		 */
-		tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor);
-		hashtable->tableslot = MakeSingleTupleTableSlot(tupdesc);
-		MemoryContextSwitchTo(oldContext);
-	}
-
 	/* Need to run the hash functions in short-lived context */
 	oldContext = MemoryContextSwitchTo(hashtable->tempcxt);
 
@@ -517,9 +386,6 @@ TupleHashTableHash(struct tuplehash_hash *tb, const MinimalTuple tuple)
  * See whether two tuples (presumably of the same hash value) match
  *
  * As above, the passed pointers are pointers to TupleHashEntryData.
- *
- * Also, the caller must select an appropriate memory context for running
- * the compare functions.  (dynahash.c doesn't change CurrentMemoryContext.)
  */
 static int
 TupleHashTableMatch(struct tuplehash_hash *tb, const MinimalTuple tuple1, const MinimalTuple tuple2)
@@ -527,6 +393,8 @@ TupleHashTableMatch(struct tuplehash_hash *tb, const MinimalTuple tuple1, const
 	TupleTableSlot *slot1;
 	TupleTableSlot *slot2;
 	TupleHashTable hashtable = (TupleHashTable) tb->private_data;
+	ExprContext *econtext = hashtable->exprcontext;
+	Datum result;
 
 	/*
 	 * We assume that simplehash.h will only ever call us with the first
@@ -541,13 +409,9 @@ TupleHashTableMatch(struct tuplehash_hash *tb, const MinimalTuple tuple1, const
 	slot2 = hashtable->inputslot;
 
 	/* For crosstype comparisons, the inputslot must be first */
-	if (execTuplesMatch(slot2,
-						slot1,
-						hashtable->numCols,
-						hashtable->keyColIdx,
-						hashtable->cur_eq_funcs,
-						hashtable->tempcxt))
-		return 0;
-	else
-		return 1;
+	econtext->ecxt_innertuple = slot1;
+	econtext->ecxt_outertuple = slot2;
+	result = ExecQual(hashtable->eq_func, econtext);
+	ResetExprContext(econtext);
+	return !DatumGetBool(result);
 }
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 863caaaf118..7c02ed274cf 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -742,7 +742,7 @@ process_ordered_aggregate_single(AggState *aggstate,
 			((oldIsNull && *isNull) ||
 			 (!oldIsNull && !*isNull &&
 			  oldAbbrevVal == newAbbrevVal &&
-			  DatumGetBool(FunctionCall2(&pertrans->equalfns[0],
+			  DatumGetBool(FunctionCall2(&pertrans->equalfnOne,
 										 oldVal, *newVal)))))
 		{
 			/* equal to prior, so forget this one */
@@ -789,7 +789,7 @@ process_ordered_aggregate_multi(AggState *aggstate,
 								AggStatePerTrans pertrans,
 								AggStatePerGroup pergroupstate)
 {
-	MemoryContext workcontext = aggstate->tmpcontext->ecxt_per_tuple_memory;
+	ExprContext *tmpcontext = aggstate->tmpcontext;
 	FunctionCallInfo fcinfo = &pertrans->transfn_fcinfo;
 	TupleTableSlot *slot1 = pertrans->sortslot;
 	TupleTableSlot *slot2 = pertrans->uniqslot;
@@ -798,6 +798,7 @@ process_ordered_aggregate_multi(AggState *aggstate,
 	Datum		newAbbrevVal = (Datum) 0;
 	Datum		oldAbbrevVal = (Datum) 0;
 	bool		haveOldValue = false;
+	TupleTableSlot *save = aggstate->tmpcontext->ecxt_outertuple;
 	int			i;
 
 	tuplesort_performsort(pertrans->sortstates[aggstate->current_set]);
@@ -818,14 +819,13 @@ process_ordered_aggregate_multi(AggState *aggstate,
 		 */
 		slot_getsomeattrs(slot1, numTransInputs);
 
+		tmpcontext->ecxt_outertuple = slot1;
+		tmpcontext->ecxt_innertuple = slot2;
+
 		if (numDistinctCols == 0 ||
 			!haveOldValue ||
 			newAbbrevVal != oldAbbrevVal ||
-			!execTuplesMatch(slot1, slot2,
-							 numDistinctCols,
-							 pertrans->sortColIdx,
-							 pertrans->equalfns,
-							 workcontext))
+			!ExecQual(pertrans->equalfnMulti, tmpcontext))
 		{
 			/* Load values into fcinfo */
 			/* Start from 1, since the 0th arg will be the transition value */
@@ -844,15 +844,14 @@ process_ordered_aggregate_multi(AggState *aggstate,
 
 				slot2 = slot1;
 				slot1 = tmpslot;
-				/* avoid execTuplesMatch() calls by reusing abbreviated keys */
+				/* avoid ExecQual() calls by reusing abbreviated keys */
 				oldAbbrevVal = newAbbrevVal;
 				haveOldValue = true;
 			}
 		}
 
-		/* Reset context each time, unless execTuplesMatch did it for us */
-		if (numDistinctCols == 0)
-			MemoryContextReset(workcontext);
+		/* Reset context each time */
+		ResetExprContext(tmpcontext);
 
 		ExecClearTuple(slot1);
 	}
@@ -862,6 +861,9 @@ process_ordered_aggregate_multi(AggState *aggstate,
 
 	tuplesort_end(pertrans->sortstates[aggstate->current_set]);
 	pertrans->sortstates[aggstate->current_set] = NULL;
+
+	/* restore previous slot, potentially in use for grouping sets */
+	tmpcontext->ecxt_outertuple = save;
 }
 
 /*
@@ -1263,7 +1265,9 @@ build_hash_table(AggState *aggstate)
 
 		Assert(perhash->aggnode->numGroups > 0);
 
-		perhash->hashtable = BuildTupleHashTable(perhash->numCols,
+		perhash->hashtable = BuildTupleHashTable(&aggstate->ss.ps,
+												 perhash->hashslot->tts_tupleDescriptor,
+												 perhash->numCols,
 												 perhash->hashGrpColIdxHash,
 												 perhash->eqfunctions,
 												 perhash->hashfunctions,
@@ -1301,6 +1305,7 @@ find_hash_columns(AggState *aggstate)
 	Bitmapset  *base_colnos;
 	List	   *outerTlist = outerPlanState(aggstate)->plan->targetlist;
 	int			numHashes = aggstate->num_hashes;
+	EState *estate = aggstate->ss.ps.state;
 	int			j;
 
 	/* Find Vars that will be needed in tlist and qual */
@@ -1380,6 +1385,12 @@ find_hash_columns(AggState *aggstate)
 		}
 
 		hashDesc = ExecTypeFromTL(hashTlist, false);
+
+		execTuplesHashPrepare(perhash->numCols,
+							  perhash->aggnode->grpOperators,
+							  &perhash->eqfunctions,
+							  &perhash->hashfunctions);
+		perhash->hashslot = ExecAllocTableSlot(&estate->es_tupleTable);
 		ExecSetSlotDescriptor(perhash->hashslot, hashDesc);
 
 		list_free(hashTlist);
@@ -1668,17 +1679,15 @@ agg_retrieve_direct(AggState *aggstate)
 		 *		of the next grouping set
 		 *----------
 		 */
+		ResetExprContext(tmpcontext);
+		tmpcontext->ecxt_innertuple = econtext->ecxt_outertuple;
+
 		if (aggstate->input_done ||
 			(node->aggstrategy != AGG_PLAIN &&
 			 aggstate->projected_set != -1 &&
 			 aggstate->projected_set < (numGroupingSets - 1) &&
 			 nextSetSize > 0 &&
-			 !execTuplesMatch(econtext->ecxt_outertuple,
-							  tmpcontext->ecxt_outertuple,
-							  nextSetSize,
-							  node->grpColIdx,
-							  aggstate->phase->eqfunctions,
-							  tmpcontext->ecxt_per_tuple_memory)))
+			 !ExecQual(aggstate->phase->eqfunctions[nextSetSize - 1], tmpcontext)))
 		{
 			aggstate->projected_set += 1;
 
@@ -1821,12 +1830,9 @@ agg_retrieve_direct(AggState *aggstate)
 					 */
 					if (node->aggstrategy != AGG_PLAIN)
 					{
-						if (!execTuplesMatch(firstSlot,
-											 outerslot,
-											 node->numCols,
-											 node->grpColIdx,
-											 aggstate->phase->eqfunctions,
-											 tmpcontext->ecxt_per_tuple_memory))
+						tmpcontext->ecxt_innertuple = firstSlot;
+						if (!ExecQual(aggstate->phase->eqfunctions[node->numCols - 1],
+									  tmpcontext))
 						{
 							aggstate->grp_firstTuple = ExecCopySlotTuple(outerslot);
 							break;
@@ -2329,11 +2335,22 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 			 */
 			if (aggnode->aggstrategy == AGG_SORTED)
 			{
+				int i = 0;
+
 				Assert(aggnode->numCols > 0);
 
 				phasedata->eqfunctions =
-					execTuplesMatchPrepare(aggnode->numCols,
-										   aggnode->grpOperators);
+					(ExprState **) palloc0(aggnode->numCols * sizeof(ExprState*));
+
+				for (i = 0; i < aggnode->numCols; i++)
+				{
+					phasedata->eqfunctions[i] =
+						execTuplesMatchPrepare(aggstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor,
+											   i + 1,
+											   aggnode->grpColIdx,
+											   aggnode->grpOperators,
+											   (PlanState *) aggnode);
+				}
 			}
 
 			phasedata->aggnode = aggnode;
@@ -2386,16 +2403,6 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	 */
 	if (use_hashing)
 	{
-		for (i = 0; i < numHashes; ++i)
-		{
-			aggstate->perhash[i].hashslot = ExecInitExtraTupleSlot(estate);
-
-			execTuplesHashPrepare(aggstate->perhash[i].numCols,
-								  aggstate->perhash[i].aggnode->grpOperators,
-								  &aggstate->perhash[i].eqfunctions,
-								  &aggstate->perhash[i].hashfunctions);
-		}
-
 		/* this is an array of pointers, not structures */
 		aggstate->hash_pergroup = pergroups;
 
@@ -3076,24 +3083,28 @@ build_pertrans_for_aggref(AggStatePerTrans pertrans,
 
 	if (aggref->aggdistinct)
 	{
-		Assert(numArguments > 0);
+		Oid *ops;
 
-		/*
-		 * We need the equal function for each DISTINCT comparison we will
-		 * make.
-		 */
-		pertrans->equalfns =
-			(FmgrInfo *) palloc(numDistinctCols * sizeof(FmgrInfo));
+		Assert(numArguments > 0);
+		Assert(list_length(aggref->aggdistinct) == numDistinctCols);
+
+		ops = palloc(numDistinctCols * sizeof(Oid));
 
 		i = 0;
 		foreach(lc, aggref->aggdistinct)
-		{
-			SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
+			ops[i++] = ((SortGroupClause *) lfirst(lc))->eqop;
 
-			fmgr_info(get_opcode(sortcl->eqop), &pertrans->equalfns[i]);
-			i++;
-		}
-		Assert(i == numDistinctCols);
+		/* lookup / build the necessary comparators */
+		if (numDistinctCols == 1)
+			fmgr_info(get_opcode(ops[0]), &pertrans->equalfnOne);
+		else
+			pertrans->equalfnMulti =
+				execTuplesMatchPrepare(pertrans->sortdesc,
+									   numDistinctCols,
+									   pertrans->sortColIdx,
+									   ops,
+									   &aggstate->ss.ps);
+		pfree(ops);
 	}
 
 	pertrans->sortstates = (Tuplesortstate **)
diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c
index 6b68835ca19..b9ba0f7c702 100644
--- a/src/backend/executor/nodeGroup.c
+++ b/src/backend/executor/nodeGroup.c
@@ -25,6 +25,7 @@
 #include "executor/executor.h"
 #include "executor/nodeGroup.h"
 #include "miscadmin.h"
+#include "utils/memutils.h"
 
 
 /*
@@ -37,8 +38,6 @@ ExecGroup(PlanState *pstate)
 {
 	GroupState *node = castNode(GroupState, pstate);
 	ExprContext *econtext;
-	int			numCols;
-	AttrNumber *grpColIdx;
 	TupleTableSlot *firsttupleslot;
 	TupleTableSlot *outerslot;
 
@@ -50,19 +49,12 @@ ExecGroup(PlanState *pstate)
 	if (node->grp_done)
 		return NULL;
 	econtext = node->ss.ps.ps_ExprContext;
-	numCols = ((Group *) node->ss.ps.plan)->numCols;
-	grpColIdx = ((Group *) node->ss.ps.plan)->grpColIdx;
 
 	/*
 	 * The ScanTupleSlot holds the (copied) first tuple of each group.
 	 */
 	firsttupleslot = node->ss.ss_ScanTupleSlot;
 
-	/*
-	 * We need not call ResetExprContext here because execTuplesMatch will
-	 * reset the per-tuple memory context once per input tuple.
-	 */
-
 	/*
 	 * If first time through, acquire first input tuple and determine whether
 	 * to return it or not.
@@ -112,6 +104,8 @@ ExecGroup(PlanState *pstate)
 		 */
 		for (;;)
 		{
+			bool		isequal;
+
 			outerslot = ExecProcNode(outerPlanState(node));
 			if (TupIsNull(outerslot))
 			{
@@ -124,10 +118,11 @@ ExecGroup(PlanState *pstate)
 			 * Compare with first tuple and see if this tuple is of the same
 			 * group.  If so, ignore it and keep scanning.
 			 */
-			if (!execTuplesMatch(firsttupleslot, outerslot,
-								 numCols, grpColIdx,
-								 node->eqfunctions,
-								 econtext->ecxt_per_tuple_memory))
+			econtext->ecxt_innertuple = firsttupleslot;
+			econtext->ecxt_outertuple = outerslot;
+			isequal = ExecQual(node->eqfunction, econtext);
+			ResetExprContext(econtext);
+			if (!isequal)
 				break;
 		}
 
@@ -166,6 +161,7 @@ GroupState *
 ExecInitGroup(Group *node, EState *estate, int eflags)
 {
 	GroupState *grpstate;
+	AttrNumber *grpColIdx = grpColIdx = node->grpColIdx;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -215,9 +211,12 @@ ExecInitGroup(Group *node, EState *estate, int eflags)
 	/*
 	 * Precompute fmgr lookup data for inner loop
 	 */
-	grpstate->eqfunctions =
-		execTuplesMatchPrepare(node->numCols,
-							   node->grpOperators);
+	grpstate->eqfunction =
+		execTuplesMatchPrepare(ExecGetResultType(outerPlanState(grpstate)),
+							   node->numCols,
+							   grpColIdx,
+							   node->grpOperators,
+							   &grpstate->ss.ps);
 
 	return grpstate;
 }
diff --git a/src/backend/executor/nodeRecursiveunion.c b/src/backend/executor/nodeRecursiveunion.c
index a64dd1397a4..ed229158038 100644
--- a/src/backend/executor/nodeRecursiveunion.c
+++ b/src/backend/executor/nodeRecursiveunion.c
@@ -32,11 +32,14 @@ static void
 build_hash_table(RecursiveUnionState *rustate)
 {
 	RecursiveUnion *node = (RecursiveUnion *) rustate->ps.plan;
+	TupleDesc desc = ExecGetResultType(outerPlanState(rustate));
 
 	Assert(node->numCols > 0);
 	Assert(node->numGroups > 0);
 
-	rustate->hashtable = BuildTupleHashTable(node->numCols,
+	rustate->hashtable = BuildTupleHashTable(&rustate->ps,
+											 desc,
+											 node->numCols,
 											 node->dupColIdx,
 											 rustate->eqfunctions,
 											 rustate->hashfunctions,
diff --git a/src/backend/executor/nodeSetOp.c b/src/backend/executor/nodeSetOp.c
index 571cbf86b1d..e5300c20692 100644
--- a/src/backend/executor/nodeSetOp.c
+++ b/src/backend/executor/nodeSetOp.c
@@ -120,18 +120,22 @@ static void
 build_hash_table(SetOpState *setopstate)
 {
 	SetOp	   *node = (SetOp *) setopstate->ps.plan;
+	ExprContext *econtext = setopstate->ps.ps_ExprContext;
+	TupleDesc	desc = ExecGetResultType(outerPlanState(setopstate));
 
 	Assert(node->strategy == SETOP_HASHED);
 	Assert(node->numGroups > 0);
 
-	setopstate->hashtable = BuildTupleHashTable(node->numCols,
+	setopstate->hashtable = BuildTupleHashTable(&setopstate->ps,
+												desc,
+												node->numCols,
 												node->dupColIdx,
 												setopstate->eqfunctions,
 												setopstate->hashfunctions,
 												node->numGroups,
 												0,
 												setopstate->tableContext,
-												setopstate->tempContext,
+												econtext->ecxt_per_tuple_memory,
 												false);
 }
 
@@ -220,11 +224,11 @@ ExecSetOp(PlanState *pstate)
 static TupleTableSlot *
 setop_retrieve_direct(SetOpState *setopstate)
 {
-	SetOp	   *node = (SetOp *) setopstate->ps.plan;
 	PlanState  *outerPlan;
 	SetOpStatePerGroup pergroup;
 	TupleTableSlot *outerslot;
 	TupleTableSlot *resultTupleSlot;
+	ExprContext *econtext = setopstate->ps.ps_ExprContext;
 
 	/*
 	 * get state info from node
@@ -281,6 +285,8 @@ setop_retrieve_direct(SetOpState *setopstate)
 		 */
 		for (;;)
 		{
+			bool isequal;
+
 			outerslot = ExecProcNode(outerPlan);
 			if (TupIsNull(outerslot))
 			{
@@ -292,11 +298,11 @@ setop_retrieve_direct(SetOpState *setopstate)
 			/*
 			 * Check whether we've crossed a group boundary.
 			 */
-			if (!execTuplesMatch(resultTupleSlot,
-								 outerslot,
-								 node->numCols, node->dupColIdx,
-								 setopstate->eqfunctions,
-								 setopstate->tempContext))
+			econtext->ecxt_outertuple = resultTupleSlot;
+			econtext->ecxt_innertuple = outerslot;
+			isequal = ExecQual(setopstate->eqfunction, econtext);
+			ResetExprContext(econtext);
+			if (!isequal)
 			{
 				/*
 				 * Save the first input tuple of the next group.
@@ -338,6 +344,7 @@ setop_fill_hash_table(SetOpState *setopstate)
 	PlanState  *outerPlan;
 	int			firstFlag;
 	bool		in_first_rel PG_USED_FOR_ASSERTS_ONLY;
+	ExprContext *econtext = setopstate->ps.ps_ExprContext;
 
 	/*
 	 * get state info from node
@@ -404,8 +411,8 @@ setop_fill_hash_table(SetOpState *setopstate)
 				advance_counts((SetOpStatePerGroup) entry->additional, flag);
 		}
 
-		/* Must reset temp context after each hashtable lookup */
-		MemoryContextReset(setopstate->tempContext);
+		/* Must expression context after each hashtable lookup */
+		ResetExprContext(econtext);
 	}
 
 	setopstate->table_filled = true;
@@ -498,16 +505,9 @@ ExecInitSetOp(SetOp *node, EState *estate, int eflags)
 	setopstate->tableContext = NULL;
 
 	/*
-	 * Miscellaneous initialization
-	 *
-	 * SetOp nodes have no ExprContext initialization because they never call
-	 * ExecQual or ExecProject.  But they do need a per-tuple memory context
-	 * anyway for calling execTuplesMatch.
+	 * create expression context
 	 */
-	setopstate->tempContext =
-		AllocSetContextCreate(CurrentMemoryContext,
-							  "SetOp",
-							  ALLOCSET_DEFAULT_SIZES);
+	ExecAssignExprContext(estate, &setopstate->ps);
 
 	/*
 	 * If hashing, we also need a longer-lived context to store the hash
@@ -553,9 +553,12 @@ ExecInitSetOp(SetOp *node, EState *estate, int eflags)
 							  &setopstate->eqfunctions,
 							  &setopstate->hashfunctions);
 	else
-		setopstate->eqfunctions =
-			execTuplesMatchPrepare(node->numCols,
-								   node->dupOperators);
+		setopstate->eqfunction =
+			execTuplesMatchPrepare(ExecGetResultType(outerPlanState(setopstate)),
+								   node->numCols,
+								   node->dupColIdx,
+								   node->dupOperators,
+								   &setopstate->ps);
 
 	if (node->strategy == SETOP_HASHED)
 	{
@@ -585,10 +588,11 @@ ExecEndSetOp(SetOpState *node)
 	ExecClearTuple(node->ps.ps_ResultTupleSlot);
 
 	/* free subsidiary stuff including hashtable */
-	MemoryContextDelete(node->tempContext);
 	if (node->tableContext)
 		MemoryContextDelete(node->tableContext);
 
+	ExecFreeExprContext(&node->ps);
+
 	ExecEndNode(outerPlanState(node));
 }
 
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index a93fbf646cb..499bd5c5b2a 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -469,6 +469,9 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
 
 	Assert(subplan->subLinkType == ANY_SUBLINK);
 
+	slot = planstate->ps_ResultTupleSlot;
+	Assert(slot);
+
 	/*
 	 * If we already had any hash tables, destroy 'em; then create empty hash
 	 * table(s).
@@ -494,7 +497,9 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
 	if (nbuckets < 1)
 		nbuckets = 1;
 
-	node->hashtable = BuildTupleHashTable(ncols,
+	node->hashtable = BuildTupleHashTable(node->parent,
+										  node->descRight,
+										  ncols,
 										  node->keyColIdx,
 										  node->tab_eq_funcs,
 										  node->tab_hash_funcs,
@@ -514,7 +519,9 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
 			if (nbuckets < 1)
 				nbuckets = 1;
 		}
-		node->hashnulls = BuildTupleHashTable(ncols,
+		node->hashnulls = BuildTupleHashTable(node->parent,
+											  node->descRight,
+											  ncols,
 											  node->keyColIdx,
 											  node->tab_eq_funcs,
 											  node->tab_hash_funcs,
@@ -598,6 +605,78 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
 	MemoryContextSwitchTo(oldcontext);
 }
 
+
+/*
+ * execTuplesUnequal
+ *		Return true if two tuples are definitely unequal in the indicated
+ *		fields.
+ *
+ * Nulls are neither equal nor unequal to anything else.  A true result
+ * is obtained only if there are non-null fields that compare not-equal.
+ *
+ * slot1, slot2: the tuples to compare (must have same columns!)
+ * numCols: the number of attributes to be examined
+ * matchColIdx: array of attribute column numbers
+ * eqFunctions: array of fmgr lookup info for the equality functions to use
+ * evalContext: short-term memory context for executing the functions
+ */
+static bool
+execTuplesUnequal(TupleTableSlot *slot1,
+				  TupleTableSlot *slot2,
+				  int numCols,
+				  AttrNumber *matchColIdx,
+				  FmgrInfo *eqfunctions,
+				  MemoryContext evalContext)
+{
+	MemoryContext oldContext;
+	bool		result;
+	int			i;
+
+	/* Reset and switch into the temp context. */
+	MemoryContextReset(evalContext);
+	oldContext = MemoryContextSwitchTo(evalContext);
+
+	/*
+	 * We cannot report a match without checking all the fields, but we can
+	 * report a non-match as soon as we find unequal fields.  So, start
+	 * comparing at the last field (least significant sort key). That's the
+	 * most likely to be different if we are dealing with sorted input.
+	 */
+	result = false;
+
+	for (i = numCols; --i >= 0;)
+	{
+		AttrNumber	att = matchColIdx[i];
+		Datum		attr1,
+					attr2;
+		bool		isNull1,
+					isNull2;
+
+		attr1 = slot_getattr(slot1, att, &isNull1);
+
+		if (isNull1)
+			continue;			/* can't prove anything here */
+
+		attr2 = slot_getattr(slot2, att, &isNull2);
+
+		if (isNull2)
+			continue;			/* can't prove anything here */
+
+		/* Apply the type-specific equality function */
+
+		if (!DatumGetBool(FunctionCall2(&eqfunctions[i],
+										attr1, attr2)))
+		{
+			result = true;		/* they are unequal */
+			break;
+		}
+	}
+
+	MemoryContextSwitchTo(oldContext);
+
+	return result;
+}
+
 /*
  * findPartialMatch: does the hashtable contain an entry that is not
  * provably distinct from the tuple?
@@ -887,6 +966,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 												   NULL);
 
 		tupDesc = ExecTypeFromTL(righttlist, false);
+		sstate->descRight = tupDesc;
 		slot = ExecInitExtraTupleSlot(estate);
 		ExecSetSlotDescriptor(slot, tupDesc);
 		sstate->projRight = ExecBuildProjectionInfo(righttlist,
diff --git a/src/backend/executor/nodeUnique.c b/src/backend/executor/nodeUnique.c
index 621fdd9b9c1..7baaf3847f2 100644
--- a/src/backend/executor/nodeUnique.c
+++ b/src/backend/executor/nodeUnique.c
@@ -47,7 +47,7 @@ static TupleTableSlot *			/* return: a tuple or NULL */
 ExecUnique(PlanState *pstate)
 {
 	UniqueState *node = castNode(UniqueState, pstate);
-	Unique	   *plannode = (Unique *) node->ps.plan;
+	ExprContext *econtext = node->ps.ps_ExprContext;
 	TupleTableSlot *resultTupleSlot;
 	TupleTableSlot *slot;
 	PlanState  *outerPlan;
@@ -67,6 +67,8 @@ ExecUnique(PlanState *pstate)
 	 */
 	for (;;)
 	{
+		bool		isequal;
+
 		/*
 		 * fetch a tuple from the outer subplan
 		 */
@@ -89,10 +91,11 @@ ExecUnique(PlanState *pstate)
 		 * If so then we loop back and fetch another new tuple from the
 		 * subplan.
 		 */
-		if (!execTuplesMatch(slot, resultTupleSlot,
-							 plannode->numCols, plannode->uniqColIdx,
-							 node->eqfunctions,
-							 node->tempContext))
+		econtext->ecxt_innertuple = slot;
+		econtext->ecxt_outertuple = resultTupleSlot;
+		isequal = ExecQual(node->eqfunction, econtext);
+		ResetExprContext(econtext);
+		if (!isequal)
 			break;
 	}
 
@@ -129,16 +132,9 @@ ExecInitUnique(Unique *node, EState *estate, int eflags)
 	uniquestate->ps.ExecProcNode = ExecUnique;
 
 	/*
-	 * Miscellaneous initialization
-	 *
-	 * Unique nodes have no ExprContext initialization because they never call
-	 * ExecQual or ExecProject.  But they do need a per-tuple memory context
-	 * anyway for calling execTuplesMatch.
+	 * create expression context
 	 */
-	uniquestate->tempContext =
-		AllocSetContextCreate(CurrentMemoryContext,
-							  "Unique",
-							  ALLOCSET_DEFAULT_SIZES);
+	ExecAssignExprContext(estate, &uniquestate->ps);
 
 	/*
 	 * Tuple table initialization
@@ -160,9 +156,12 @@ ExecInitUnique(Unique *node, EState *estate, int eflags)
 	/*
 	 * Precompute fmgr lookup data for inner loop
 	 */
-	uniquestate->eqfunctions =
-		execTuplesMatchPrepare(node->numCols,
-							   node->uniqOperators);
+	uniquestate->eqfunction =
+		execTuplesMatchPrepare(ExecGetResultType(outerPlanState(uniquestate)),
+							   node->numCols,
+							   node->uniqColIdx,
+							   node->uniqOperators,
+							   &uniquestate->ps);
 
 	return uniquestate;
 }
@@ -180,7 +179,7 @@ ExecEndUnique(UniqueState *node)
 	/* clean up tuple table */
 	ExecClearTuple(node->ps.ps_ResultTupleSlot);
 
-	MemoryContextDelete(node->tempContext);
+	ExecFreeExprContext(&node->ps);
 
 	ExecEndNode(outerPlanState(node));
 }
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 02868749f60..75de7728a46 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -1203,12 +1203,13 @@ spool_tuples(WindowAggState *winstate, int64 pos)
 
 		if (node->partNumCols > 0)
 		{
+			ExprContext *econtext = winstate->tmpcontext;
+
+			econtext->ecxt_innertuple = winstate->first_part_slot;
+			econtext->ecxt_outertuple = outerslot;
+
 			/* Check if this tuple still belongs to the current partition */
-			if (!execTuplesMatch(winstate->first_part_slot,
-								 outerslot,
-								 node->partNumCols, node->partColIdx,
-								 winstate->partEqfunctions,
-								 winstate->tmpcontext->ecxt_per_tuple_memory))
+			if (!ExecQual(winstate->partEqfunction, econtext))
 			{
 				/*
 				 * end of partition; copy the tuple for the next cycle.
@@ -1216,8 +1217,10 @@ spool_tuples(WindowAggState *winstate, int64 pos)
 				ExecCopySlot(winstate->first_part_slot, outerslot);
 				winstate->partition_spooled = true;
 				winstate->more_partitions = true;
+				ResetExprContext(econtext);
 				break;
 			}
+			ResetExprContext(econtext);
 		}
 
 		/* Still in partition, so save it into the tuplestore */
@@ -1867,11 +1870,20 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 
 	/* Set up data for comparing tuples */
 	if (node->partNumCols > 0)
-		winstate->partEqfunctions = execTuplesMatchPrepare(node->partNumCols,
-														   node->partOperators);
+		winstate->partEqfunction = execTuplesMatchPrepare(
+			winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor,
+			node->partNumCols,
+			node->partColIdx,
+			node->partOperators,
+			&winstate->ss.ps);
+
 	if (node->ordNumCols > 0)
-		winstate->ordEqfunctions = execTuplesMatchPrepare(node->ordNumCols,
-														  node->ordOperators);
+		winstate->ordEqfunction = execTuplesMatchPrepare(
+			winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor,
+			node->ordNumCols,
+			node->ordColIdx,
+			node->ordOperators,
+			&winstate->ss.ps);
 
 	/*
 	 * WindowAgg nodes use aggvalues and aggnulls as well as Agg nodes.
@@ -2378,15 +2390,18 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 		  TupleTableSlot *slot2)
 {
 	WindowAgg  *node = (WindowAgg *) winstate->ss.ps.plan;
+	ExprContext *econtext = winstate->tmpcontext;
+	bool ret;
 
 	/* If no ORDER BY, all rows are peers with each other */
 	if (node->ordNumCols == 0)
 		return true;
 
-	return execTuplesMatch(slot1, slot2,
-						   node->ordNumCols, node->ordColIdx,
-						   winstate->ordEqfunctions,
-						   winstate->tmpcontext->ecxt_per_tuple_memory);
+	econtext->ecxt_outertuple = slot1;
+	econtext->ecxt_innertuple = slot2;
+	ret = ExecQual(winstate->ordEqfunction, econtext);
+	ResetExprContext(econtext);
+	return ret;
 }
 
 /*
diff --git a/src/backend/utils/adt/orderedsetaggs.c b/src/backend/utils/adt/orderedsetaggs.c
index 1e323d94445..a7a8c704511 100644
--- a/src/backend/utils/adt/orderedsetaggs.c
+++ b/src/backend/utils/adt/orderedsetaggs.c
@@ -27,6 +27,7 @@
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/timestamp.h"
 #include "utils/tuplesort.h"
 
@@ -54,6 +55,8 @@ typedef struct OSAPerQueryState
 	Aggref	   *aggref;
 	/* Memory context containing this struct and other per-query data: */
 	MemoryContext qcontext;
+	/* Context for expression evaluation */
+	ExprContext *econtext;
 	/* Do we expect multiple final-function calls within one group? */
 	bool		rescan_needed;
 
@@ -71,7 +74,7 @@ typedef struct OSAPerQueryState
 	Oid		   *sortCollations;
 	bool	   *sortNullsFirsts;
 	/* Equality operator call info, created only if needed: */
-	FmgrInfo   *equalfns;
+	ExprState   *compareTuple;
 
 	/* These fields are used only when accumulating datums: */
 
@@ -1285,19 +1288,18 @@ hypothetical_cume_dist_final(PG_FUNCTION_ARGS)
 Datum
 hypothetical_dense_rank_final(PG_FUNCTION_ARGS)
 {
+	OSAPerGroupState *osastate;
+	ExprContext *econtext;
+	ExprState  *compareTuple;
+	int			numDistinctCols;
 	int			nargs = PG_NARGS() - 1;
 	int64		rank = 1;
 	int64		duplicate_count = 0;
-	OSAPerGroupState *osastate;
-	int			numDistinctCols;
 	Datum		abbrevVal = (Datum) 0;
 	Datum		abbrevOld = (Datum) 0;
-	AttrNumber *sortColIdx;
-	FmgrInfo   *equalfns;
 	TupleTableSlot *slot;
 	TupleTableSlot *extraslot;
 	TupleTableSlot *slot2;
-	MemoryContext tmpcontext;
 	int			i;
 
 	Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
@@ -1307,6 +1309,9 @@ hypothetical_dense_rank_final(PG_FUNCTION_ARGS)
 		PG_RETURN_INT64(rank);
 
 	osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
+	econtext = osastate->qstate->econtext;
+	if (!econtext)
+		osastate->qstate->econtext = econtext = CreateStandaloneExprContext();
 
 	/* Adjust nargs to be the number of direct (or aggregated) args */
 	if (nargs % 2 != 0)
@@ -1321,26 +1326,22 @@ hypothetical_dense_rank_final(PG_FUNCTION_ARGS)
 	 */
 	numDistinctCols = osastate->qstate->numSortCols - 1;
 
-	/* Look up the equality function(s), if we didn't already */
-	equalfns = osastate->qstate->equalfns;
-	if (equalfns == NULL)
+	/* Build tuple comparator, if we didn't already */
+	compareTuple = osastate->qstate->compareTuple;
+	if (compareTuple == NULL)
 	{
-		MemoryContext qcontext = osastate->qstate->qcontext;
+		AttrNumber *sortColIdx = osastate->qstate->sortColIdx;
+		MemoryContext oldContext;
 
-		equalfns = (FmgrInfo *)
-			MemoryContextAlloc(qcontext, numDistinctCols * sizeof(FmgrInfo));
-		for (i = 0; i < numDistinctCols; i++)
-		{
-			fmgr_info_cxt(get_opcode(osastate->qstate->eqOperators[i]),
-						  &equalfns[i],
-						  qcontext);
-		}
-		osastate->qstate->equalfns = equalfns;
+		oldContext = MemoryContextSwitchTo(osastate->qstate->qcontext);
+		compareTuple = execTuplesMatchPrepare(osastate->qstate->tupdesc,
+											  numDistinctCols,
+											  sortColIdx,
+											  osastate->qstate->eqOperators,
+											  NULL);
+		MemoryContextSwitchTo(oldContext);
+		osastate->qstate->compareTuple = compareTuple;
 	}
-	sortColIdx = osastate->qstate->sortColIdx;
-
-	/* Get short-term context we can use for execTuplesMatch */
-	tmpcontext = AggGetTempMemoryContext(fcinfo);
 
 	/* because we need a hypothetical row, we can't share transition state */
 	Assert(!osastate->sort_done);
@@ -1383,19 +1384,20 @@ hypothetical_dense_rank_final(PG_FUNCTION_ARGS)
 			break;
 
 		/* count non-distinct tuples */
+		econtext->ecxt_outertuple = slot;
+		econtext->ecxt_innertuple = slot2;
+
 		if (!TupIsNull(slot2) &&
 			abbrevVal == abbrevOld &&
-			execTuplesMatch(slot, slot2,
-							numDistinctCols,
-							sortColIdx,
-							equalfns,
-							tmpcontext))
+			ExecQual(compareTuple, econtext))
 			duplicate_count++;
 
+		ResetExprContext(econtext);
+
 		tmpslot = slot2;
 		slot2 = slot;
 		slot = tmpslot;
-		/* avoid execTuplesMatch() calls by reusing abbreviated keys */
+		/* avoid ExecQual() calls by reusing abbreviated keys */
 		abbrevOld = abbrevVal;
 
 		rank++;
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index d345c3ff280..3b51189678f 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -142,6 +142,7 @@ typedef enum ExprEvalOp
 	/* evaluate assorted special-purpose expression types */
 	EEOP_IOCOERCE,
 	EEOP_DISTINCT,
+	EEOP_NOT_DISTINCT,
 	EEOP_NULLIF,
 	EEOP_SQLVALUEFUNCTION,
 	EEOP_CURRENTOFEXPR,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 3f9085bc9f2..7abcd48dd89 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -112,25 +112,18 @@ extern bool execCurrentOf(CurrentOfExpr *cexpr,
 /*
  * prototypes from functions in execGrouping.c
  */
-extern bool execTuplesMatch(TupleTableSlot *slot1,
-				TupleTableSlot *slot2,
-				int numCols,
-				AttrNumber *matchColIdx,
-				FmgrInfo *eqfunctions,
-				MemoryContext evalContext);
-extern bool execTuplesUnequal(TupleTableSlot *slot1,
-				  TupleTableSlot *slot2,
-				  int numCols,
-				  AttrNumber *matchColIdx,
-				  FmgrInfo *eqfunctions,
-				  MemoryContext evalContext);
-extern FmgrInfo *execTuplesMatchPrepare(int numCols,
-					   Oid *eqOperators);
+extern ExprState *execTuplesMatchPrepare(TupleDesc desc,
+										 int numCols,
+										 AttrNumber *keyColIdx,
+										 Oid *eqOperators,
+										 PlanState *parent);
 extern void execTuplesHashPrepare(int numCols,
 					  Oid *eqOperators,
 					  FmgrInfo **eqFunctions,
 					  FmgrInfo **hashFunctions);
-extern TupleHashTable BuildTupleHashTable(int numCols, AttrNumber *keyColIdx,
+extern TupleHashTable BuildTupleHashTable(PlanState *parent,
+					TupleDesc inputDesc,
+					int numCols, AttrNumber *keyColIdx,
 					FmgrInfo *eqfunctions,
 					FmgrInfo *hashfunctions,
 					long nbuckets, Size additionalsize,
@@ -251,6 +244,11 @@ extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
 extern List *ExecInitExprList(List *nodes, PlanState *parent);
 extern ExprState *ExecBuildAggTrans(AggState *aggstate, struct AggStatePerPhaseData *phase,
 									bool doSort, bool doHash);
+extern ExprState *ExecBuildGroupingEqual(TupleDesc desc,
+										int numCols,
+										AttrNumber *keyColIdx,
+										Oid *eqfunctions,
+										PlanState *parent);
 extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList,
 						ExprContext *econtext,
 						TupleTableSlot *slot,
diff --git a/src/include/executor/nodeAgg.h b/src/include/executor/nodeAgg.h
index 20364530d55..1ec46e69a02 100644
--- a/src/include/executor/nodeAgg.h
+++ b/src/include/executor/nodeAgg.h
@@ -102,11 +102,12 @@ typedef struct AggStatePerTransData
 	bool	   *sortNullsFirst;
 
 	/*
-	 * fmgr lookup data for input columns' equality operators --- only
-	 * set/used when aggregate has DISTINCT flag.  Note that these are in
-	 * order of sort column index, not parameter index.
+	 * Comparators for input columns --- only set/used when aggregate has
+	 * DISTINCT flag. equalfnOne version is used for single-column
+	 * commparisons, equalfnMulti for the case of multiple columns.
 	 */
-	FmgrInfo   *equalfns;		/* array of length numDistinctCols */
+	FmgrInfo	equalfnOne;
+	ExprState  *equalfnMulti;
 
 	/*
 	 * initial value from pg_aggregate entry
@@ -270,7 +271,8 @@ typedef struct AggStatePerPhaseData
 	int			numsets;		/* number of grouping sets (or 0) */
 	int		   *gset_lengths;	/* lengths of grouping sets */
 	Bitmapset **grouped_cols;	/* column groupings for rollup */
-	FmgrInfo   *eqfunctions;	/* per-grouping-field equality fns */
+	ExprState **eqfunctions;	/* expression returning equality, indexed by
+								 * nr of cols to compare */
 	Agg		   *aggnode;		/* Agg node for phase data */
 	Sort	   *sortnode;		/* Sort node for input ordering for phase */
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 9cc5a105539..9acbfb3b0ee 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -618,6 +618,8 @@ typedef struct TupleHashTableData
 	FmgrInfo   *in_hash_funcs;	/* hash functions for input datatype(s) */
 	FmgrInfo   *cur_eq_funcs;	/* equality functions for input vs. table */
 	uint32		hash_iv;		/* hash-function IV */
+	ExprState  *eq_func;		/* tuple equality comparator */
+	ExprContext *exprcontext;	/* expression context */
 }			TupleHashTableData;
 
 typedef tuplehash_iterator TupleHashIterator;
@@ -764,6 +766,7 @@ typedef struct SubPlanState
 	HeapTuple	curTuple;		/* copy of most recent tuple from subplan */
 	Datum		curArray;		/* most recent array from ARRAY() subplan */
 	/* these are used when hashing the subselect's output: */
+	TupleDesc	descRight;		/* subselect desc after projection */
 	ProjectionInfo *projLeft;	/* for projecting lefthand exprs */
 	ProjectionInfo *projRight;	/* for projecting subselect output */
 	TupleHashTable hashtable;	/* hash table for no-nulls subselect rows */
@@ -1778,7 +1781,7 @@ typedef struct SortState
 typedef struct GroupState
 {
 	ScanState	ss;				/* its first field is NodeTag */
-	FmgrInfo   *eqfunctions;	/* per-field lookup data for equality fns */
+	ExprState  *eqfunction;		/* equality function */
 	bool		grp_done;		/* indicates completion of Group scan */
 } GroupState;
 
@@ -1868,8 +1871,8 @@ typedef struct WindowAggState
 
 	WindowStatePerFunc perfunc; /* per-window-function information */
 	WindowStatePerAgg peragg;	/* per-plain-aggregate information */
-	FmgrInfo   *partEqfunctions;	/* equality funcs for partition columns */
-	FmgrInfo   *ordEqfunctions; /* equality funcs for ordering columns */
+	ExprState  *partEqfunction;	/* equality funcs for partition columns */
+	ExprState  *ordEqfunction; /* equality funcs for ordering columns */
 	Tuplestorestate *buffer;	/* stores rows of current partition */
 	int			current_ptr;	/* read pointer # for current */
 	int64		spooled_rows;	/* total # of rows in buffer */
@@ -1926,8 +1929,7 @@ typedef struct WindowAggState
 typedef struct UniqueState
 {
 	PlanState	ps;				/* its first field is NodeTag */
-	FmgrInfo   *eqfunctions;	/* per-field lookup data for equality fns */
-	MemoryContext tempContext;	/* short-term context for comparisons */
+	ExprState   *eqfunction;		/* tuple equality qual */
 } UniqueState;
 
 /* ----------------
@@ -2012,11 +2014,11 @@ typedef struct SetOpStatePerGroupData *SetOpStatePerGroup;
 typedef struct SetOpState
 {
 	PlanState	ps;				/* its first field is NodeTag */
+	ExprState  *eqfunction;		/* equality comparator */
 	FmgrInfo   *eqfunctions;	/* per-grouping-field equality fns */
 	FmgrInfo   *hashfunctions;	/* per-grouping-field hash fns */
 	bool		setop_done;		/* indicates completion of output scan */
 	long		numOutput;		/* number of dups left to output */
-	MemoryContext tempContext;	/* short-term context for comparisons */
 	/* these fields are used in SETOP_SORTED mode: */
 	SetOpStatePerGroup pergroup;	/* per-group working state */
 	HeapTuple	grp_firstTuple; /* copy of first tuple of current group */
-- 
2.14.1.536.g6867272d5b.dirty

Reply via email to