diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml
index eaa410b..f0e4407 100644
--- a/doc/src/sgml/ref/create_aggregate.sgml
+++ b/doc/src/sgml/ref/create_aggregate.sgml
@@ -27,6 +27,7 @@ CREATE AGGREGATE <replaceable class="parameter">name</replaceable> ( [ <replacea
     [ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
     [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
     [ , FINALFUNC_EXTRA ]
+    [ , CFUNC = <replaceable class="PARAMETER">cfunc</replaceable> ]
     [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
     [ , MSFUNC = <replaceable class="PARAMETER">msfunc</replaceable> ]
     [ , MINVFUNC = <replaceable class="PARAMETER">minvfunc</replaceable> ]
@@ -45,6 +46,7 @@ CREATE AGGREGATE <replaceable class="parameter">name</replaceable> ( [ [ <replac
     [ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
     [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
     [ , FINALFUNC_EXTRA ]
+    [ , CFUNC = <replaceable class="PARAMETER">cfunc</replaceable> ]
     [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
     [ , HYPOTHETICAL ]
 )
@@ -58,6 +60,7 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
     [ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
     [ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
     [ , FINALFUNC_EXTRA ]
+    [ , CFUNC = <replaceable class="PARAMETER">cfunc</replaceable> ]
     [ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
     [ , MSFUNC = <replaceable class="PARAMETER">msfunc</replaceable> ]
     [ , MINVFUNC = <replaceable class="PARAMETER">minvfunc</replaceable> ]
@@ -105,12 +108,15 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
    functions:
    a state transition function
    <replaceable class="PARAMETER">sfunc</replaceable>,
-   and an optional final calculation function
-   <replaceable class="PARAMETER">ffunc</replaceable>.
+   an optional final calculation function
+   <replaceable class="PARAMETER">ffunc</replaceable>,
+   and an optional combine function
+   <replaceable class="PARAMETER">cfunc</replaceable>.
    These are used as follows:
 <programlisting>
 <replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state
 <replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value
+<replaceable class="PARAMETER">cfunc</replaceable>( internal-state, internal-state ) ---> next-internal-state
 </programlisting>
   </para>
 
@@ -128,6 +134,13 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
   </para>
 
   <para>
+   An aggregate function may also supply a combining function, which allows
+   the aggregation process to be broken down into multiple steps.  This
+   facilitates query optimization techniques such as parallel query,
+   pre-join aggregation and aggregation while sorting.
+  </para>
+
+  <para>
    An aggregate function can provide an initial condition,
    that is, an initial value for the internal state value.
    This is specified and stored in the database as a value of type
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 121c27f..820d982 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -57,6 +57,7 @@ AggregateCreate(const char *aggName,
 				Oid variadicArgType,
 				List *aggtransfnName,
 				List *aggfinalfnName,
+				List *aggcombinefnName,
 				List *aggmtransfnName,
 				List *aggminvtransfnName,
 				List *aggmfinalfnName,
@@ -77,6 +78,7 @@ AggregateCreate(const char *aggName,
 	Form_pg_proc proc;
 	Oid			transfn;
 	Oid			finalfn = InvalidOid;	/* can be omitted */
+	Oid			combinefn = InvalidOid;	/* can be omitted */
 	Oid			mtransfn = InvalidOid;	/* can be omitted */
 	Oid			minvtransfn = InvalidOid;		/* can be omitted */
 	Oid			mfinalfn = InvalidOid;	/* can be omitted */
@@ -396,6 +398,20 @@ AggregateCreate(const char *aggName,
 	}
 	Assert(OidIsValid(finaltype));
 
+	/* handle the combinefn, if supplied */
+	if (aggcombinefnName)
+	{
+		/*
+		 * Combine function must have 2 argument, each of which is the
+		 * trans type
+		 */
+		fnArgs[0] = aggTransType;
+		fnArgs[1] = aggTransType;
+
+		combinefn = lookup_agg_function(aggcombinefnName, 2, fnArgs,
+										variadicArgType, &finaltype);
+	}
+
 	/*
 	 * If finaltype (i.e. aggregate return type) is polymorphic, inputs must
 	 * be polymorphic also, else parser will fail to deduce result type.
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 894c89d..035882e 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -61,6 +61,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
 	char		aggKind = AGGKIND_NORMAL;
 	List	   *transfuncName = NIL;
 	List	   *finalfuncName = NIL;
+	List	   *combinefuncName = NIL;
 	List	   *mtransfuncName = NIL;
 	List	   *minvtransfuncName = NIL;
 	List	   *mfinalfuncName = NIL;
@@ -124,6 +125,8 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
 			transfuncName = defGetQualifiedName(defel);
 		else if (pg_strcasecmp(defel->defname, "finalfunc") == 0)
 			finalfuncName = defGetQualifiedName(defel);
+		else if (pg_strcasecmp(defel->defname, "cfunc") == 0)
+			combinefuncName = defGetQualifiedName(defel);
 		else if (pg_strcasecmp(defel->defname, "msfunc") == 0)
 			mtransfuncName = defGetQualifiedName(defel);
 		else if (pg_strcasecmp(defel->defname, "minvfunc") == 0)
@@ -383,6 +386,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
 						   variadicArgType,
 						   transfuncName,		/* step function name */
 						   finalfuncName,		/* final function name */
+						   combinefuncName,		/* combine function name */
 						   mtransfuncName,		/* fwd trans function name */
 						   minvtransfuncName,	/* inv trans function name */
 						   mfinalfuncName,		/* final function name */
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index f0d9e94..df9d2e8 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -904,25 +904,38 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			pname = sname = "Group";
 			break;
 		case T_Agg:
-			sname = "Aggregate";
-			switch (((Agg *) plan)->aggstrategy)
 			{
-				case AGG_PLAIN:
-					pname = "Aggregate";
-					strategy = "Plain";
-					break;
-				case AGG_SORTED:
-					pname = "GroupAggregate";
-					strategy = "Sorted";
-					break;
-				case AGG_HASHED:
-					pname = "HashAggregate";
-					strategy = "Hashed";
-					break;
-				default:
-					pname = "Aggregate ???";
-					strategy = "???";
-					break;
+				char	   *modifier;
+				Agg		   *agg = (Agg *) plan;
+
+				sname = "Aggregate";
+
+				if (agg->finalizeAggs == false)
+					modifier = "Partial ";
+				else if (agg->combineStates == true)
+					modifier = "Finalize ";
+				else
+					modifier = "";
+
+				switch (agg->aggstrategy)
+				{
+					case AGG_PLAIN:
+						pname = psprintf("%sAggregate", modifier);
+						strategy = "Plain";
+						break;
+					case AGG_SORTED:
+						pname = psprintf("%sGroupAggregate", modifier);
+						strategy = "Sorted";
+						break;
+					case AGG_HASHED:
+						pname = psprintf("%sHashAggregate", modifier);
+						strategy = "Hashed";
+						break;
+					default:
+						pname = "Aggregate ???";
+						strategy = "???";
+						break;
+				}
 			}
 			break;
 		case T_WindowAgg:
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 2e36855..0336220 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3,15 +3,24 @@
  * nodeAgg.c
  *	  Routines to handle aggregate nodes.
  *
- *	  ExecAgg evaluates each aggregate in the following steps:
+ *	  ExecAgg normally evaluates each aggregate in the following steps:
  *
  *		 transvalue = initcond
  *		 foreach input_tuple do
  *			transvalue = transfunc(transvalue, input_value(s))
  *		 result = finalfunc(transvalue, direct_argument(s))
  *
- *	  If a finalfunc is not supplied then the result is just the ending
- *	  value of transvalue.
+ *	  If a finalfunc is not supplied or finalizeAggs is false, then the result
+ *	  is just the ending value of transvalue.
+ *
+ *	  If combineStates is true then we assume that input values are other
+ *	  transvalues. In this case we use the aggregate's combinefunc to 'add'
+ *	  the passed in transvalue to the transvalue being operated on. This allows
+ *	  aggregation to happen in multiple stages. 'combineStates' will only be
+ *	  true if another nodeAgg is below this one in the plan tree.
+ *
+ *	  'finalizeAggs' should be false for all nodeAggs apart from the upper most
+ *	  one in the plan tree.
  *
  *	  If a normal aggregate call specifies DISTINCT or ORDER BY, we sort the
  *	  input tuples and eliminate duplicates (if required) before performing
@@ -197,7 +206,7 @@ typedef struct AggStatePerTransData
 	 */
 	int			numTransInputs;
 
-	/* Oid of the state transition function */
+	/* Oid of the state transition or combine function */
 	Oid			transfn_oid;
 
 	/* Oid of state value's datatype */
@@ -209,8 +218,8 @@ typedef struct AggStatePerTransData
 	List	   *aggdirectargs;	/* states of direct-argument expressions */
 
 	/*
-	 * fmgr lookup data for transition function.  Note in particular that the
-	 * fn_strict flag is kept here.
+	 * fmgr lookup data for transition function or combination function.  Note
+	 * in particular that the fn_strict flag is kept here.
 	 */
 	FmgrInfo	transfn;
 
@@ -421,6 +430,10 @@ static void advance_transition_function(AggState *aggstate,
 							AggStatePerTrans pertrans,
 							AggStatePerGroup pergroupstate);
 static void advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup);
+static void advance_combination_function(AggState *aggstate,
+							AggStatePerTrans pertrans,
+							AggStatePerGroup pergroupstate);
+static void combine_aggregates(AggState *aggstate, AggStatePerGroup pergroup);
 static void process_ordered_aggregate_single(AggState *aggstate,
 								 AggStatePerTrans pertrans,
 								 AggStatePerGroup pergroupstate);
@@ -796,6 +809,8 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
 	int			numGroupingSets = Max(aggstate->phase->numsets, 1);
 	int			numTrans = aggstate->numtrans;
 
+	Assert(!aggstate->combineStates);
+
 	for (transno = 0; transno < numTrans; transno++)
 	{
 		AggStatePerTrans pertrans = &aggstate->pertrans[transno];
@@ -879,6 +894,109 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
 	}
 }
 
+static void
+combine_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
+{
+	int			transno;
+	int			numTrans = aggstate->numtrans;
+
+	/* combine not supported with grouping sets */
+	Assert(aggstate->phase->numsets == 0);
+	Assert(aggstate->combineStates);
+
+	for (transno = 0; transno < numTrans; transno++)
+	{
+		AggStatePerTrans pertrans = &aggstate->pertrans[transno];
+		TupleTableSlot *slot;
+		FunctionCallInfo fcinfo = &pertrans->transfn_fcinfo;
+		AggStatePerGroup pergroupstate = &pergroup[transno];
+
+		/* Evaluate the current input expressions for this aggregate */
+		slot = ExecProject(pertrans->evalproj, NULL);
+		Assert(slot->tts_nvalid >= 1);
+
+		fcinfo->arg[1] = slot->tts_values[0];
+		fcinfo->argnull[1] = slot->tts_isnull[0];
+
+		advance_combination_function(aggstate, pertrans, pergroupstate);
+	}
+}
+
+/*
+ * 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_combination_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)
+		{
+			pergroupstate->transValue = fcinfo->arg[1];
+			pergroupstate->transValueIsNull = false;
+			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
+	 * pfree the prior transValue.  But if the combine function returned a
+	 * pointer to its first input, we don't need to do anything.
+	 */
+	if (!pertrans->transtypeByVal &&
+		DatumGetPointer(newVal) != DatumGetPointer(pergroupstate->transValue))
+	{
+		if (!fcinfo->isnull)
+		{
+			MemoryContextSwitchTo(aggstate->aggcontexts[aggstate->current_set]->ecxt_per_tuple_memory);
+			newVal = datumCopy(newVal,
+							   pertrans->transtypeByVal,
+							   pertrans->transtypeLen);
+		}
+		if (!pergroupstate->transValueIsNull)
+			pfree(DatumGetPointer(pergroupstate->transValue));
+	}
+
+	pergroupstate->transValue = newVal;
+	pergroupstate->transValueIsNull = fcinfo->isnull;
+
+	MemoryContextSwitchTo(oldContext);
+
+}
+
 
 /*
  * Run the transition function for a DISTINCT or ORDER BY aggregate
@@ -1278,8 +1396,14 @@ finalize_aggregates(AggState *aggstate,
 												pergroupstate);
 		}
 
-		finalize_aggregate(aggstate, peragg, pergroupstate,
-						   &aggvalues[aggno], &aggnulls[aggno]);
+		if (aggstate->finalizeAggs)
+			finalize_aggregate(aggstate, peragg, pergroupstate,
+							   &aggvalues[aggno], &aggnulls[aggno]);
+		else
+		{
+			aggvalues[aggno] = pergroupstate->transValue;
+			aggnulls[aggno] = pergroupstate->transValueIsNull;
+		}
 	}
 }
 
@@ -1294,9 +1418,11 @@ project_aggregates(AggState *aggstate)
 	ExprContext *econtext = aggstate->ss.ps.ps_ExprContext;
 
 	/*
-	 * Check the qual (HAVING clause); if the group does not match, ignore it.
+	 * iif performing the final aggregate stage we'll check the qual (HAVING
+	 * clause); if the group does not match, ignore it.
 	 */
-	if (ExecQual(aggstate->ss.ps.qual, econtext, false))
+	if (aggstate->finalizeAggs == false ||
+		ExecQual(aggstate->ss.ps.qual, econtext, false))
 	{
 		/*
 		 * Form and return or store a projection tuple using the aggregate
@@ -1811,7 +1937,10 @@ agg_retrieve_direct(AggState *aggstate)
 				 */
 				for (;;)
 				{
-					advance_aggregates(aggstate, pergroup);
+					if (!aggstate->combineStates)
+						advance_aggregates(aggstate, pergroup);
+					else
+						combine_aggregates(aggstate, pergroup);
 
 					/* Reset per-input-tuple context after each tuple */
 					ResetExprContext(tmpcontext);
@@ -1919,7 +2048,10 @@ agg_fill_hash_table(AggState *aggstate)
 		entry = lookup_hash_entry(aggstate, outerslot);
 
 		/* Advance the aggregates */
-		advance_aggregates(aggstate, entry->pergroup);
+		if (!aggstate->combineStates)
+			advance_aggregates(aggstate, entry->pergroup);
+		else
+			combine_aggregates(aggstate, entry->pergroup);
 
 		/* Reset per-input-tuple context after each tuple */
 		ResetExprContext(tmpcontext);
@@ -2051,6 +2183,8 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	aggstate->pertrans = NULL;
 	aggstate->curpertrans = NULL;
 	aggstate->agg_done = false;
+	aggstate->combineStates = node->combineStates;
+	aggstate->finalizeAggs = node->finalizeAggs;
 	aggstate->input_done = false;
 	aggstate->pergroup = NULL;
 	aggstate->grp_firstTuple = NULL;
@@ -2402,7 +2536,21 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 						   get_func_name(aggref->aggfnoid));
 		InvokeFunctionExecuteHook(aggref->aggfnoid);
 
-		transfn_oid = aggform->aggtransfn;
+		/*
+		 * if this aggregation is performing state combines, then instead of
+		 * using the transition function, we'll use the combine function
+		 */
+		if (aggstate->combineStates)
+		{
+			transfn_oid = aggform->aggcombinefn;
+
+			/* If not set then the planner messed up */
+			if (!OidIsValid(transfn_oid))
+				elog(ERROR, "combinefn not set during aggregate state combine phase");
+		}
+		else
+			transfn_oid = aggform->aggtransfn;
+
 		peragg->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
 
 		/* Check that aggregate owner has permission to call component fns */
@@ -2583,44 +2731,69 @@ build_pertrans_for_aggref(AggStatePerTrans pertrans,
 		pertrans->numTransInputs = numArguments;
 
 	/*
-	 * Set up infrastructure for calling the transfn
+	 * When combining states, we have no use at all for the aggregate
+	 * function's transfn. Instead we use the combinefn. However we do
+	 * reuse the transfnexpr for the combinefn, perhaps this should change
 	 */
-	build_aggregate_transfn_expr(inputTypes,
-								 numArguments,
-								 numDirectArgs,
-								 aggref->aggvariadic,
-								 aggtranstype,
-								 aggref->inputcollid,
-								 aggtransfn,
-								 InvalidOid,	/* invtrans is not needed here */
-								 &transfnexpr,
-								 NULL);
-	fmgr_info(aggtransfn, &pertrans->transfn);
-	fmgr_info_set_expr((Node *) transfnexpr, &pertrans->transfn);
-
-	InitFunctionCallInfoData(pertrans->transfn_fcinfo,
-							 &pertrans->transfn,
-							 pertrans->numTransInputs + 1,
-							 pertrans->aggCollation,
-							 (void *) aggstate, NULL);
+	if (aggstate->combineStates)
+	{
+		build_aggregate_combinefn_expr(aggref->aggvariadic,
+									   aggtranstype,
+									   aggref->inputcollid,
+									   aggtransfn,
+									   &transfnexpr);
+		fmgr_info(aggtransfn, &pertrans->transfn);
+		fmgr_info_set_expr((Node *) transfnexpr, &pertrans->transfn);
+
+		InitFunctionCallInfoData(pertrans->transfn_fcinfo,
+								 &pertrans->transfn,
+								 2,
+								 pertrans->aggCollation,
+								 (void *) aggstate, NULL);
 
-	/*
-	 * If the transfn is strict and the initval is NULL, make sure input type
-	 * and transtype are the same (or at least binary-compatible), so that
-	 * it's OK to use the first aggregated input value as the initial
-	 * transValue.  This should have been checked at agg definition time, but
-	 * we must check again in case the transfn's strictness property has been
-	 * changed.
-	 */
-	if (pertrans->transfn.fn_strict && pertrans->initValueIsNull)
+	}
+	else
 	{
-		if (numArguments <= numDirectArgs ||
-			!IsBinaryCoercible(inputTypes[numDirectArgs],
-							   aggtranstype))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
-					 errmsg("aggregate %u needs to have compatible input type and transition type",
-							aggref->aggfnoid)));
+		/*
+		 * Set up infrastructure for calling the transfn
+		 */
+		build_aggregate_transfn_expr(inputTypes,
+									 numArguments,
+									 numDirectArgs,
+									 aggref->aggvariadic,
+									 aggtranstype,
+									 aggref->inputcollid,
+									 aggtransfn,
+									 InvalidOid,	/* invtrans is not needed here */
+									 &transfnexpr,
+									 NULL);
+		fmgr_info(aggtransfn, &pertrans->transfn);
+		fmgr_info_set_expr((Node *) transfnexpr, &pertrans->transfn);
+
+		InitFunctionCallInfoData(pertrans->transfn_fcinfo,
+								 &pertrans->transfn,
+								 pertrans->numTransInputs + 1,
+								 pertrans->aggCollation,
+								 (void *) aggstate, NULL);
+
+		/*
+		 * If the transfn is strict and the initval is NULL, make sure input type
+		 * and transtype are the same (or at least binary-compatible), so that
+		 * it's OK to use the first aggregated input value as the initial
+		 * transValue.  This should have been checked at agg definition time, but
+		 * we must check again in case the transfn's strictness property has been
+		 * changed.
+		 */
+		if (pertrans->transfn.fn_strict && pertrans->initValueIsNull)
+		{
+			if (numArguments <= numDirectArgs ||
+				!IsBinaryCoercible(inputTypes[numDirectArgs],
+								   aggtranstype))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+						 errmsg("aggregate %u needs to have compatible input type and transition type",
+								aggref->aggfnoid)));
+		}
 	}
 
 	/* get info about the state value's datatype */
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8fc1cfd..3040ca7 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -1426,8 +1426,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	/* Generate the plan for the subquery */
 	rel->subplan = subquery_planner(root->glob, subquery,
 									root,
-									false, tuple_fraction,
-									&subroot);
+									false, !rel->partial_aggregate,
+									tuple_fraction, &subroot);
 	rel->subroot = subroot;
 
 	/* Isolate the params needed by this specific subplan */
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index d107d76..6f0a6f6 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -118,6 +118,7 @@ bool		enable_nestloop = true;
 bool		enable_material = true;
 bool		enable_mergejoin = true;
 bool		enable_hashjoin = true;
+bool		enable_earlygrouping = true;
 
 typedef struct
 {
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index a35c881..c177ca5 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -16,11 +16,25 @@
 
 #include <math.h>
 
+#include "access/htup_details.h"
+#include "catalog/pg_aggregate.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
+#include "optimizer/planmain.h"
+#include "optimizer/planner.h"
+#include "optimizer/tlist.h"
+#include "optimizer/var.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_oper.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteManip.h"
+#include "utils/syscache.h"
 
 /* Hook for plugins to get control in add_paths_to_joinrel() */
 set_join_pathlist_hook_type set_join_pathlist_hook = NULL;
@@ -1474,3 +1488,594 @@ select_mergejoin_clauses(PlannerInfo *root,
 
 	return result_list;
 }
+
+static List *
+remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved)
+{
+	List	   *result = NIL;
+	ListCell   *jl;
+
+	foreach(jl, joinlist)
+	{
+		Node	   *jlnode = (Node *) lfirst(jl);
+
+		if (IsA(jlnode, RangeTblRef))
+		{
+			int			varno = ((RangeTblRef *) jlnode)->rtindex;
+
+			if (varno == relid)
+				(*nremoved)++;
+			else
+				result = lappend(result, jlnode);
+		}
+		else if (IsA(jlnode, List))
+		{
+			/* Recurse to handle subproblem */
+			List	   *sublist;
+
+			sublist = remove_rel_from_joinlist((List *) jlnode,
+											   relid, nremoved);
+			/* Avoid including empty sub-lists in the result */
+			if (sublist)
+				result = lappend(result, sublist);
+		}
+		else
+		{
+			elog(ERROR, "unrecognized joinlist node type: %d",
+				 (int) nodeTag(jlnode));
+		}
+	}
+
+	return result;
+}
+
+static Expr *
+substitute_expr_vars(Expr *expr, int srcrelid, int tarrelid)
+{
+	Expr *newexpr = (Expr *) copyObject(expr);
+	List *vars = pull_vars_of_level((Node *) newexpr, 0);
+	ListCell *l;
+
+	foreach(l, vars)
+	{
+		Var *var = lfirst(l);
+		if (var->varno == srcrelid)
+			var->varno = tarrelid;
+	}
+	return newexpr;
+}
+
+static void
+duplicate_eclass_exprs(PlannerInfo *root, int srcrelid, int tarrelid, List *tlist)
+{
+	ListCell *lc;
+
+	foreach(lc, root->eq_classes)
+	{
+		EquivalenceClass *ec = lfirst(lc);
+		ListCell *l2;
+		List *newmembers = NIL;
+
+		/* Skip classes which don't contain any members for srcrelid */
+		if (!bms_is_member(srcrelid, ec->ec_relids))
+			continue;
+
+		/*
+		 * Now look at each member to find out if there's Vars for the
+		 * srcrelid. All expression which have Vars belonging to the source
+		 * rel must be copied, and the Vars replaced with Vars from the target
+		 * relation.
+		 */
+		foreach(l2, ec->ec_members)
+		{
+			EquivalenceMember *em = lfirst(l2);
+			Expr *newexpr;
+			EquivalenceMember *newem;
+			ListCell *l3;
+			List *vars;
+
+			/*
+			 * No point in looking at Consts, and skip if no Vars belong to
+			 * srcrelid.
+			 */
+			if (em->em_is_const || !bms_is_member(srcrelid, em->em_relids))
+				continue;
+
+			newexpr = (Expr *) copyObject(em->em_expr);
+			vars = pull_vars_of_level((Node *) newexpr, 0);
+
+			foreach(l3, vars)
+			{
+				Var *var = lfirst(l3);
+				if (var->varno == srcrelid)
+				{
+					AttrNumber resno = 1;
+					ListCell *l4;
+					bool	found = false;
+					foreach(l4, tlist)
+					{
+						Expr *expr = lfirst(l4);
+
+						if (equal(expr, em->em_expr))
+						{
+							var->varattno = resno;
+							found = true;
+							break;
+						}
+
+						resno++;
+					}
+
+					if (!found)
+						elog(ERROR, "missing item from tlist");
+
+					var->varno = tarrelid;
+				}
+			}
+
+			/*
+			 * Ok, now we just need to generate a new member for the new
+			 * expression. We can't append it to the members list of the eclass
+			 * just yet, as we're looping over it. Instead we'll just generate
+			 * a new list of members to append, and we'll do that after we're
+			 * done looping.
+			 */
+			newem = makeNode(EquivalenceMember);
+			newem->em_datatype = em->em_datatype;
+			newem->em_expr = newexpr;
+			newem->em_is_child = em->em_is_child;
+			newem->em_is_const = false;
+			newem->em_nullable_relids = bms_copy(em->em_nullable_relids);
+			newem->em_relids = pull_varnos((Node *) newexpr);
+
+			newmembers = lappend(newmembers, newem);
+		}
+
+		/* There should be new members, let's check anyway... */
+		if (newmembers != NIL)
+		{
+			ec->ec_members = list_concat(ec->ec_members, newmembers);
+			ec->ec_relids = bms_add_member(ec->ec_relids, tarrelid);
+		}
+	}
+}
+
+/*
+ * setup_grouping_rel
+ *		Build a new subquery RTE which performs the aggregation required of the
+ *		outer query, but without joining to any of the rels which are not
+ *		required of the aggregate functions.
+ *
+ * Note: For now this removes the original RTE from the joinlist. This will
+ * be changed in a more complete version.
+ */
+List *
+setup_grouping_rel(PlannerInfo *root, List *joinlist)
+{
+	ListCell	   *lc;
+	int				aggregaterel = root->aggregaterel;
+	int				groupbyrel = root->groupbyrel;
+	int				groupbeforejoinrel = root->groupbeforejoinrel;
+	List		   *joinconditions = NIL;
+	List		   *groupexprs = NIL;
+	Query		   *parse;
+	RangeTblEntry  *rte;
+	RangeTblEntry  *subrte;
+	RangeTblRef	   *rtr;
+	RelOptInfo	   *rel;
+	List		   *sub_tlist;
+	List		   *tlist;
+	List		   *oldtlist;
+	AttrNumber		resno;
+	int				nremoved;
+
+	/*
+	 * If the query's GROUP BY clause does not contain columns from the
+	 * aggregate relation, then we must be sure that the GROUP BY clause is
+	 * functionally dependent on the join clause to that relation. The reason
+	 * for this is that we must ensure that we do not perform a partial
+	 * grouping which creates a more tightly grouped set than what is required
+	 * of the query's main GROUP BY clause. We can test this simply by looking
+	 * for a unique index in the join expressions to the grouprel. If we find
+	 * a supporting unique index then we can be certain that early grouping is
+	 * legal.
+	 */
+	if (groupbyrel != 0 && groupbyrel != aggregaterel)
+	{
+		RelOptInfo *grouprel = find_base_rel(root, groupbyrel);
+
+		foreach(lc, joinlist)
+		{
+			RangeTblRef	   *rtr = (RangeTblRef *) lfirst(lc);
+			RelOptInfo	   *joinrel;
+			List		   *joininfo;
+			Relids			joinrelids;
+
+			if (!IsA(rtr, RangeTblRef) || rtr->rtindex == groupbyrel)
+				continue;
+
+			joinrel = find_base_rel(root, rtr->rtindex);
+			joinrelids = bms_union(joinrel->relids, grouprel->relids);
+
+			joininfo = generate_join_implied_equalities(root, joinrelids,
+							joinrel->relids, grouprel);
+
+			if (joininfo != NIL)
+			{
+				ListCell *lc2;
+
+				foreach(lc2, joininfo)
+				{
+					RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc2);
+					Node	   *expr;
+					clause_sides_match_join(rinfo, joinrel, grouprel);
+
+					if (rinfo->outer_is_left)
+						expr = get_rightop(rinfo->clause);
+					else
+						expr = get_leftop(rinfo->clause);
+
+					groupexprs = list_append_unique(groupexprs, expr);
+
+					joinconditions = lappend(joinconditions, rinfo);
+				}
+			}
+		}
+
+		/* If no unique index is found then we cannot perform the group before join */
+		if (!relation_has_unique_index_for(root, grouprel, joinconditions, NIL, NIL))
+			return joinlist;
+
+		foreach(lc, joinconditions)
+		{
+			RestrictInfo   *rinfo = (RestrictInfo *) lfirst(lc);
+			Node		   *rexpr;
+
+			if (rinfo->outer_is_left)
+				rexpr = get_rightop(rinfo->clause);
+			else
+				rexpr = get_leftop(rinfo->clause);
+
+			groupexprs = list_append_unique(groupexprs, rexpr);
+		}
+	}
+	else if (root->parse->groupClause)
+	{
+		List *tempexprs = get_sortgrouplist_exprs(root->parse->groupClause,
+											 root->parse->targetList);
+
+		/*
+		 * All grouping expressions come from the aggregate relation. We can
+		 * simply just copy these expressions and replace the varno of each
+		 * var with the first RTE in the new subquery that we're creating
+		 */
+		foreach(lc, tempexprs)
+		{
+			Expr *expr = (Expr *) lfirst(lc);
+			Expr *newexpr = substitute_expr_vars(expr, aggregaterel, 1);
+			groupexprs = lappend(groupexprs, newexpr);
+		}
+	}
+
+	parse = makeNode(Query);
+	/************ The following code is still under construction! ************/
+	rte = copyObject(root->simple_rte_array[aggregaterel]);
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = parse;
+	root->simple_rte_array[groupbeforejoinrel] = rte;
+	list_nth_cell(root->parse->rtable, groupbeforejoinrel - 1)->data.ptr_value = rte;
+
+	rel = build_simple_rel(root, groupbeforejoinrel, RELOPT_BASEREL);
+	rel->rtekind = RTE_SUBQUERY;
+	rel->partial_aggregate = true;
+	root->simple_rel_array[groupbeforejoinrel] = rel;
+
+	subrte = copyObject(root->simple_rte_array[aggregaterel]);
+
+	parse->rtable = list_make1(subrte);
+	parse->jointree = makeNode(FromExpr);
+	parse->canSetTag = true;
+	parse->hasAggs = root->parse->hasAggs;
+
+	joinlist = remove_rel_from_joinlist(joinlist, aggregaterel, &nremoved);
+
+	rtr = makeNode(RangeTblRef);
+	rtr->rtindex = groupbeforejoinrel;
+	joinlist = lappend(joinlist, rtr);
+	/* for now don't bother considering the non group before join version */
+	root->simple_rel_array[aggregaterel]->reloptkind = RELOPT_DEADREL;
+
+	oldtlist = tlist = sub_tlist = NIL;
+	resno = 1;
+
+	foreach(lc, root->parse->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+		Relids relids = pull_varnos((Node *) tle->expr);
+
+		if (bms_is_member(aggregaterel, relids))
+		{
+			TargetEntry	   *newtle;
+			Var			   *var;
+			Expr		   *newexpr;
+
+			/* We should have ensured this earlier */
+			Assert(bms_membership(relids) == BMS_SINGLETON);
+
+			newexpr = substitute_expr_vars(tle->expr, aggregaterel, 1);
+			newtle = makeTargetEntry(newexpr, resno, tle->resname ? pstrdup(tle->resname) : NULL, false);
+
+			sub_tlist = lappend(sub_tlist, newtle);
+			/* we need to copy this as the tle will be modified later */
+			oldtlist = lappend(oldtlist, copyObject(tle->expr));
+			var = makeVarFromTargetEntry(groupbeforejoinrel, newtle);
+			tlist = lappend(tlist, var);
+			resno++;
+		}
+	}
+
+	parse->targetList = sub_tlist;
+	add_vars_to_targetlist(root, tlist, bms_make_singleton(0), false);
+
+	pull_varattnos((Node *) sub_tlist, aggregaterel, &rte->selectedCols);
+
+	foreach(lc, groupexprs)
+	{
+		Node	   *node = (Node *) lfirst(lc);
+		TargetEntry *tle;
+		Oid			sortop;
+		Oid			eqop;
+		bool		hashable;
+		SortGroupClause *sgc;
+
+		/* determine the eqop and optional sortop */
+		get_sort_group_operators(exprType(node),
+								 false, true, false,
+								 &sortop, &eqop, NULL,
+								 &hashable);
+
+		tle = tlist_member(node, parse->targetList);
+
+		if (!tle)				/* shouldn't happen */
+			elog(ERROR, "failed to find group expression in subplan tlist");
+
+		sgc = makeNode(SortGroupClause);
+		sgc->eqop = eqop;
+		sgc->hashable = hashable;
+		sgc->nulls_first = false;
+		sgc->sortop = sortop;
+		tle->ressortgroupref = sgc->tleSortGroupRef = assignSortGroupRef(tle, parse->targetList);
+
+		parse->groupClause = lappend(parse->groupClause, sgc);
+	}
+
+	rtr = makeNode(RangeTblRef);
+	rtr->rtindex = 1;
+	parse->jointree->fromlist = list_make1(rtr);
+	parse->resultRelation = 0;
+	parse->commandType = CMD_SELECT;
+
+	//root->parse = (Query *) ReplaceVarsFromTargetList((Node *) root->parse,
+	//									 aggregaterel, 0,
+	//							 rt_fetch(aggregaterel, root->parse->rtable),
+	//									 parse->targetList,
+	//									 REPLACEVARS_SUBSTITUTE_NULL, 0,
+	//									 &parse->hasSubLinks);
+
+	//root->parse->groupClause = NIL;
+	/*** TEMP HACKS SECTION ***/
+	{
+		List *vars = pull_vars_of_level((Node *) root->parse->targetList, 0);
+		foreach(lc, vars)
+		{
+			Var *v = lfirst(lc);
+			if (v->varno == aggregaterel)
+				v->varno = groupbeforejoinrel;
+		}
+	}
+
+	duplicate_eclass_exprs(root, aggregaterel, groupbeforejoinrel, oldtlist);
+	return joinlist;
+}
+
+/*
+ * prepare_group_before_join
+ *		Phase 1 in determining if GROUP BY and/or aggregation may be possible
+ *		before joining to other relations. This determination is performed in
+ *		multiple phases. In this phase we perform some simple checks in order
+ *		to eliminated cases where this is absolutely not possible.
+ *		If we determine the optimization maybe possible we add a dummy item to
+ *		the rtable so as to reserve a slot in the simple rel arrays for this
+ *		alternative way of performing the query. We return false if group
+ *		before join is absolutely not possible, and true if it may be possible.
+ */
+bool
+prepare_group_before_join(PlannerInfo *root, Query *parse)
+{
+	List	   *tlist = parse->targetList;
+	Index		aggregaterel = 0;
+	Index		grouprel = 0;
+	ListCell   *l;
+	Relids		relids;
+
+	/* bail if earlygrouping GUC is disabled */
+	if (!enable_earlygrouping)
+		return false;
+
+	/* Nothing to do for queries which don't need aggregation */
+	if (!parse->hasAggs &&
+		parse->havingQual == NULL &&
+		parse->groupClause == NIL)
+		return false;
+
+	/* No joins? It'll be hard to group before that! */
+	if (list_length(parse->rtable) == 1)
+		return false;
+
+	/* XXX do we need to disable in this case? */
+	if (expression_returns_set((Node *) parse->targetList))
+		return false;
+
+	/*
+	 * For queries with aggregate functions, we're only able to perform this
+	 * group before join optimization when all aggregate function parameters
+	 * contain Vars from a single relation. If they do, then this relation will
+	 * become the relation which we perform the grouping on.
+	 */
+
+	/*
+	 * We can skip performing any analysis on aggregate functions for queries
+	 * which have none.
+	 */
+	if (parse->hasAggs)
+	{
+		Index		curaggrel;
+
+		foreach(l, tlist)
+		{
+			TargetEntry	   *tle = (TargetEntry *) lfirst(l);
+			Aggref		   *aggref = (Aggref *) tle->expr;
+			Oid				aggcombinefn;
+			HeapTuple		aggTuple;
+			Form_pg_aggregate aggform;
+
+			/* Skip over anything that's not an Aggref */
+			if (!IsA(aggref, Aggref))
+				continue;
+
+			/*
+			 * We're unable to apply the optimization if any of the aggregates
+			 * have a DISTINCT or ORDER BY. The problem with this is that we
+			 * currently have no infrastructure to ensure that the groups are
+			 * not duplicated by some join that takes place after the grouping
+			 * is performed.
+			 */
+			if (aggref->aggdistinct != NIL || aggref->aggorder != NIL)
+				return false;
+
+			/*
+			 * Now we must check if the aggregate function has a state combine
+			 * function set. It's not possible to perform the final aggregate
+			 * stage for aggregates which don't have one of these.
+			 */
+			aggTuple = SearchSysCache1(AGGFNOID,
+									   ObjectIdGetDatum(aggref->aggfnoid));
+			if (!HeapTupleIsValid(aggTuple))
+				elog(ERROR, "cache lookup failed for aggregate %u",
+					 aggref->aggfnoid);
+			aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
+			aggcombinefn = aggform->aggcombinefn;
+			ReleaseSysCache(aggTuple);
+
+			if (!OidIsValid(aggcombinefn))
+				return false;
+
+			if (!aggref->aggstar)
+			{
+				relids = pull_varnos((Node *) aggref);
+
+				/*
+				 * Check if the aggregate's parameters contain Vars from more
+				 * than one rel, if it does then we'll need to disable the
+				 * optimization, as we can only optimize cases where there's
+				 * a single grouping relation.
+				 */
+				if (relids && !bms_get_singleton_member(relids, &curaggrel))
+					return false;
+
+				bms_free(relids);
+
+				/*
+				 * If we've found an Aggref where the parameter contains rels
+				 * different from the last aggregate function call then we can't
+				 * optimize this either, for the same reason as above.
+				 */
+				if (aggregaterel != 0 && aggregaterel != curaggrel)
+					return false;
+
+				aggregaterel = curaggrel;
+			}
+		}
+	}
+
+	/*
+	 * If the query has no aggs, or the aggs contain no vars, e.g COUNT(*),
+	 * then we'll just select any relation as the one to aggregate on
+	 */
+	if (aggregaterel == 0)
+		aggregaterel = 1;
+
+	/*
+	 * We'll make another pass over the targetlist, this time we know which
+	 * relation that we might like to try grouping on. We'll need to ensure
+	 * that no targets expression exist which have Vars which belong to our
+	 * aggregate relation, and also contain Vars which don't belong to our
+	 * aggregate relation. We'll also ensure that any Vars belonging to the
+	 * aggregate relation are also present in the GROUP BY clause. This is
+	 * possible as the SQL standard does allow columns which are functionally
+	 * dependant on the GROUP BY clause.
+	 */
+	foreach(l, tlist)
+	{
+		TargetEntry	   *tle = (TargetEntry *) lfirst(l);
+		Expr		   *expr = tle->expr;
+
+		/* Skip over any Aggrefs, we've already checked these above. */
+		if (IsA(expr, Aggref))
+			continue;
+
+		relids = pull_varnos((Node *) expr);
+
+		/* Does this expression require the aggregate relation? */
+		if (bms_is_member(aggregaterel, relids))
+		{
+			if (bms_membership(relids) == BMS_MULTIPLE)
+				return false;
+
+			if (tle->ressortgroupref == 0)
+				return false;
+		}
+	}
+
+	/*
+	 * Now we'll have a look at the GROUP BY clause if one exists. We need to
+	 * ensure that all of the grouping expressions only contain Vars from a
+	 * single rel. This does not have to be the same rel as the one we may have
+	 * seen in the Aggref parameters above. The reason for this is that later
+	 * we need to perform some analysis to ensure that the GROUP BY columns are
+	 * functionally dependant on the join clause to our grouping relation, and
+	 * the only way we currently have to test functional dependency is by
+	 * looking at unique indexes, and a unique index can only be on a single
+	 * relation.
+	 */
+	if (root->parse->groupClause != NIL)
+	{
+		List	   *groupExprs;
+		Relids		grouprels;
+
+		groupExprs = get_sortgrouplist_exprs(parse->groupClause, tlist);
+		grouprels = pull_varnos((Node *) groupExprs);
+
+		if (!bms_get_singleton_member(grouprels, &grouprel))
+			return false;
+	}
+
+	/*
+	 * At this stage we cannot be sure that performing GROUP BY early is
+	 * actually possible as we still need to analyze the join condition for
+	 * relations being joined to this one as we need to ensure we don't
+	 * accidentally generate a smaller number of groups than is required of the
+	 * main GROUP BY clause.
+	 */
+	root->aggregaterel = aggregaterel;
+	root->groupbyrel = grouprel;
+	root->groupbeforejoinrel = list_length(parse->rtable) + 1;
+	/*
+	 * Another thing we must also do now is add an item to the query's rtable.
+	 * We'll need this so we can consider a grouped RelOptInfo during the join
+	 * search. We need to do this before setup_simple_rel_arrays() is called.
+	 */
+	parse->rtable = lappend(parse->rtable, makeNode(RangeTblEntry));
+
+	return true;		/* Group before join may be possible */
+}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 404c6f5..3ccb5ef 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -1045,6 +1045,8 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
 								 groupOperators,
 								 NIL,
 								 numGroups,
+								 false,
+								 true,
 								 subplan);
 	}
 	else
@@ -4501,9 +4503,8 @@ Agg *
 make_agg(PlannerInfo *root, List *tlist, List *qual,
 		 AggStrategy aggstrategy, const AggClauseCosts *aggcosts,
 		 int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
-		 List *groupingSets,
-		 long numGroups,
-		 Plan *lefttree)
+		 List *groupingSets, long numGroups, bool combineStates,
+		 bool finalizeAggs, Plan *lefttree)
 {
 	Agg		   *node = makeNode(Agg);
 	Plan	   *plan = &node->plan;
@@ -4512,6 +4513,8 @@ make_agg(PlannerInfo *root, List *tlist, List *qual,
 
 	node->aggstrategy = aggstrategy;
 	node->numCols = numGroupCols;
+	node->combineStates = combineStates;
+	node->finalizeAggs = finalizeAggs;
 	node->grpColIdx = grpColIdx;
 	node->grpOperators = grpOperators;
 	node->numGroups = numGroups;
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 848df97..3651de6 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -20,11 +20,14 @@
  */
 #include "postgres.h"
 
+#include "nodes/nodeFuncs.h"
 #include "optimizer/orclauses.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/placeholder.h"
 #include "optimizer/planmain.h"
+#include "optimizer/tlist.h"
+#include "optimizer/var.h"
 
 
 /*
@@ -60,6 +63,7 @@ query_planner(PlannerInfo *root, List *tlist,
 	RelOptInfo *final_rel;
 	Index		rti;
 	double		total_pages;
+	bool		try_group_before_join;
 
 	/*
 	 * If the query has an empty join tree, then it's something easy like
@@ -107,6 +111,12 @@ query_planner(PlannerInfo *root, List *tlist,
 	root->initial_rels = NIL;
 
 	/*
+	 * Determine if it might be possible to apply grouping of one relation
+	 * before joining it to any other relations.
+	 */
+	try_group_before_join = prepare_group_before_join(root, parse);
+
+	/*
 	 * Make a flattened version of the rangetable for faster access (this is
 	 * OK because the rangetable won't change any more), and set up an empty
 	 * array for indexing base relations.
@@ -201,6 +211,10 @@ query_planner(PlannerInfo *root, List *tlist,
 	 */
 	extract_restriction_or_clauses(root);
 
+	/* run the final phase to determine if group before join is possible */
+	if (try_group_before_join)
+		joinlist = setup_grouping_rel(root, joinlist);
+
 	/*
 	 * We should now have size estimates for every actual table involved in
 	 * the query, and we also know which if any have been deleted from the
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 06be922..4861093 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -80,7 +80,8 @@ typedef struct
 static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
 static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
 static Plan *inheritance_planner(PlannerInfo *root);
-static Plan *grouping_planner(PlannerInfo *root, double tuple_fraction);
+static Plan *grouping_planner(PlannerInfo *root, double tuple_fraction,
+							  bool finalize_aggregates);
 static void preprocess_rowmarks(PlannerInfo *root);
 static double preprocess_limit(PlannerInfo *root,
 				 double tuple_fraction,
@@ -271,7 +272,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 
 	/* primary planning entry point (may recurse for subqueries) */
 	top_plan = subquery_planner(glob, parse, NULL,
-								false, tuple_fraction, &root);
+								false, true, tuple_fraction, &root);
 
 	/*
 	 * If creating a plan for a scrollable cursor, make sure it can run
@@ -373,8 +374,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 Plan *
 subquery_planner(PlannerGlobal *glob, Query *parse,
 				 PlannerInfo *parent_root,
-				 bool hasRecursion, double tuple_fraction,
-				 PlannerInfo **subroot)
+				 bool hasRecursion, bool finalize_aggregates,
+				 double tuple_fraction, PlannerInfo **subroot)
 {
 	PlannerInfo *root;
 	Plan	   *plan;
@@ -673,7 +674,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 		plan = inheritance_planner(root);
 	else
 	{
-		plan = grouping_planner(root, tuple_fraction);
+		plan = grouping_planner(root, tuple_fraction, finalize_aggregates);
 		/* If it's not SELECT, we need a ModifyTable node */
 		if (parse->commandType != CMD_SELECT)
 		{
@@ -1129,7 +1130,7 @@ inheritance_planner(PlannerInfo *root)
 		subroot.hasInheritedTarget = true;
 
 		/* Generate plan */
-		subplan = grouping_planner(&subroot, 0.0 /* retrieve all tuples */ );
+		subplan = grouping_planner(&subroot, 0.0 /* retrieve all tuples */ ,true);
 
 		/*
 		 * Planning may have modified the query result relation (if there were
@@ -1310,7 +1311,8 @@ inheritance_planner(PlannerInfo *root)
  *--------------------
  */
 static Plan *
-grouping_planner(PlannerInfo *root, double tuple_fraction)
+grouping_planner(PlannerInfo *root, double tuple_fraction,
+				 bool finalize_aggregates)
 {
 	Query	   *parse = root->parse;
 	List	   *tlist = parse->targetList;
@@ -1425,6 +1427,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 		Path	   *cheapest_path;
 		Path	   *sorted_path;
 		Path	   *best_path;
+		bool		combine_aggregates;
 
 		MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
 
@@ -1603,6 +1606,19 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 		final_rel = query_planner(root, sub_tlist,
 								  standard_qp_callback, &qp_extra);
 
+		if (bms_is_member(root->groupbeforejoinrel, final_rel->relids))
+		{
+			combine_aggregates = true;
+			tlist = (List *) ReplaceVarsFromTargetList((Node *) tlist,
+										root->aggregaterel, 0,
+								rt_fetch(root->aggregaterel, root->parse->rtable),
+										sub_tlist,
+										REPLACEVARS_SUBSTITUTE_NULL, 0,
+										&parse->hasSubLinks);
+		}
+		else
+			combine_aggregates = false;
+
 		/*
 		 * Extract rowcount and width estimates for use below.  If final_rel
 		 * has been proven dummy, its rows estimate will be zero; clamp it to
@@ -1983,6 +1999,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 									extract_grouping_ops(parse->groupClause),
 												NIL,
 												numGroups,
+												combine_aggregates,
+												finalize_aggregates,
 												result_plan);
 				/* Hashed aggregation produces randomly-ordered results */
 				current_pathkeys = NIL;
@@ -2294,6 +2312,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 								 extract_grouping_ops(parse->distinctClause),
 											NIL,
 											numDistinctRows,
+											false,
+											false,
 											result_plan);
 			/* Hashed aggregation produces randomly-ordered results */
 			current_pathkeys = NIL;
@@ -2527,6 +2547,8 @@ build_grouping_chain(PlannerInfo *root,
 									 extract_grouping_ops(groupClause),
 									 gsets,
 									 numGroups,
+									 false,
+									 true,
 									 sort_plan);
 
 		sort_plan->lefttree = NULL;
@@ -2563,6 +2585,8 @@ build_grouping_chain(PlannerInfo *root,
 										extract_grouping_ops(groupClause),
 										gsets,
 										numGroups,
+										false,
+										true,
 										result_plan);
 
 		((Agg *) result_plan)->chain = chain;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index d0bc412..9da104d 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -532,7 +532,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
 	 */
 	plan = subquery_planner(root->glob, subquery,
 							root,
-							false, tuple_fraction,
+							false, true, tuple_fraction,
 							&subroot);
 
 	/* Isolate the params needed by this specific subplan */
@@ -571,7 +571,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
 			/* Generate the plan for the ANY subquery; we'll need all rows */
 			plan = subquery_planner(root->glob, subquery,
 									root,
-									false, 0.0,
+									false, true, 0.0,
 									&subroot);
 
 			/* Isolate the params needed by this specific subplan */
@@ -1163,7 +1163,7 @@ SS_process_ctes(PlannerInfo *root)
 		 */
 		plan = subquery_planner(root->glob, subquery,
 								root,
-								cte->cterecursive, 0.0,
+								cte->cterecursive, true, 0.0,
 								&subroot);
 
 		/*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 8884fb1..224c575 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -247,7 +247,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		 */
 		subplan = subquery_planner(root->glob, subquery,
 								   root,
-								   false, tuple_fraction,
+								   false, true, tuple_fraction,
 								   &subroot);
 
 		/* Save subroot and subplan in RelOptInfo for setrefs.c */
@@ -775,6 +775,8 @@ make_union_unique(SetOperationStmt *op, Plan *plan,
 								 extract_grouping_ops(groupList),
 								 NIL,
 								 numGroups,
+								 false,
+								 true,
 								 plan);
 		/* Hashed aggregation produces randomly-ordered results */
 		*sortClauses = NIL;
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 5b0d568..1dde1a7 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -1929,6 +1929,43 @@ build_aggregate_transfn_expr(Oid *agg_input_types,
 
 /*
  * Like build_aggregate_transfn_expr, but creates an expression tree for the
+ * combine function of an aggregate, rather than the transition function.
+ */
+void
+build_aggregate_combinefn_expr(bool agg_variadic,
+							   Oid agg_state_type,
+							   Oid agg_input_collation,
+							   Oid combinefn_oid,
+							   Expr **combinefnexpr)
+{
+	Param	   *argp;
+	List	   *args;
+	FuncExpr   *fexpr;
+
+	/* Build arg list to use in the combinefn FuncExpr node. */
+	argp = makeNode(Param);
+	argp->paramkind = PARAM_EXEC;
+	argp->paramid = -1;
+	argp->paramtype = agg_state_type;
+	argp->paramtypmod = -1;
+	argp->paramcollid = agg_input_collation;
+	argp->location = -1;
+
+	/* trans state type is arg 1 and 2 */
+	args = list_make2(argp, argp);
+
+	fexpr = makeFuncExpr(combinefn_oid,
+						 agg_state_type,
+						 args,
+						 InvalidOid,
+						 agg_input_collation,
+						 COERCE_EXPLICIT_CALL);
+	fexpr->funcvariadic = agg_variadic;
+	*combinefnexpr = (Expr *) fexpr;
+}
+
+/*
+ * Like build_aggregate_transfn_expr, but creates an expression tree for the
  * final function of an aggregate, rather than the transition function.
  */
 void
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 17053af..87052f2 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -854,6 +854,15 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
+		{ "enable_earlygrouping", PGC_USERSET, QUERY_TUNING_METHOD,
+		gettext_noop("Enables the planner's ability to perform GROUP BY before joining to unrelated tables."),
+		NULL
+		},
+		&enable_earlygrouping,
+		true,
+		NULL, NULL, NULL
+	},
+	{
 		{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("Enables genetic query optimization."),
 			gettext_noop("This algorithm attempts to do planning without "
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7e75bc2..c180730 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12265,6 +12265,7 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
 	PGresult   *res;
 	int			i_aggtransfn;
 	int			i_aggfinalfn;
+	int			i_aggcombinefn;
 	int			i_aggmtransfn;
 	int			i_aggminvtransfn;
 	int			i_aggmfinalfn;
@@ -12281,6 +12282,7 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
 	int			i_convertok;
 	const char *aggtransfn;
 	const char *aggfinalfn;
+	const char *aggcombinefn;
 	const char *aggmtransfn;
 	const char *aggminvtransfn;
 	const char *aggmfinalfn;
@@ -12311,7 +12313,26 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
 	selectSourceSchema(fout, agginfo->aggfn.dobj.namespace->dobj.name);
 
 	/* Get aggregate-specific details */
-	if (fout->remoteVersion >= 90400)
+	if (fout->remoteVersion >= 90600)
+	{
+		appendPQExpBuffer(query, "SELECT aggtransfn, "
+			"aggfinalfn, aggtranstype::pg_catalog.regtype, "
+			"aggcombinefn, aggmtransfn, aggminvtransfn, "
+			"aggmfinalfn, aggmtranstype::pg_catalog.regtype, "
+			"aggfinalextra, aggmfinalextra, "
+			"aggsortop::pg_catalog.regoperator, "
+			"(aggkind = 'h') AS hypothetical, "
+			"aggtransspace, agginitval, "
+			"aggmtransspace, aggminitval, "
+			"true AS convertok, "
+			"pg_catalog.pg_get_function_arguments(p.oid) AS funcargs, "
+			"pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs "
+			"FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
+			"WHERE a.aggfnoid = p.oid "
+			"AND p.oid = '%u'::pg_catalog.oid",
+			agginfo->aggfn.dobj.catId.oid);
+	}
+	else if (fout->remoteVersion >= 90400)
 	{
 		appendPQExpBuffer(query, "SELECT aggtransfn, "
 						  "aggfinalfn, aggtranstype::pg_catalog.regtype, "
@@ -12421,6 +12442,7 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
 
 	i_aggtransfn = PQfnumber(res, "aggtransfn");
 	i_aggfinalfn = PQfnumber(res, "aggfinalfn");
+	i_aggcombinefn = PQfnumber(res, "aggcombinefn");
 	i_aggmtransfn = PQfnumber(res, "aggmtransfn");
 	i_aggminvtransfn = PQfnumber(res, "aggminvtransfn");
 	i_aggmfinalfn = PQfnumber(res, "aggmfinalfn");
@@ -12438,6 +12460,7 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
 
 	aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
 	aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
+	aggcombinefn = PQgetvalue(res, 0, i_aggcombinefn);
 	aggmtransfn = PQgetvalue(res, 0, i_aggmtransfn);
 	aggminvtransfn = PQgetvalue(res, 0, i_aggminvtransfn);
 	aggmfinalfn = PQgetvalue(res, 0, i_aggmfinalfn);
@@ -12526,6 +12549,11 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
 			appendPQExpBufferStr(details, ",\n    FINALFUNC_EXTRA");
 	}
 
+	if (strcmp(aggcombinefn, "-") != 0)
+	{
+		appendPQExpBuffer(details, ",\n    CFUNC = %s",	aggcombinefn);
+	}
+
 	if (strcmp(aggmtransfn, "-") != 0)
 	{
 		appendPQExpBuffer(details, ",\n    MSFUNC = %s,\n    MINVFUNC = %s,\n    MSTYPE = %s",
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index dd6079f..b306f9b 100644
--- a/src/include/catalog/pg_aggregate.h
+++ b/src/include/catalog/pg_aggregate.h
@@ -33,6 +33,7 @@
  *	aggnumdirectargs	number of arguments that are "direct" arguments
  *	aggtransfn			transition function
  *	aggfinalfn			final function (0 if none)
+ *	aggcombinefn		combine function (0 if none)
  *	aggmtransfn			forward function for moving-aggregate mode (0 if none)
  *	aggminvtransfn		inverse function for moving-aggregate mode (0 if none)
  *	aggmfinalfn			final function for moving-aggregate mode (0 if none)
@@ -56,6 +57,7 @@ CATALOG(pg_aggregate,2600) BKI_WITHOUT_OIDS
 	int16		aggnumdirectargs;
 	regproc		aggtransfn;
 	regproc		aggfinalfn;
+	regproc		aggcombinefn;
 	regproc		aggmtransfn;
 	regproc		aggminvtransfn;
 	regproc		aggmfinalfn;
@@ -85,24 +87,25 @@ typedef FormData_pg_aggregate *Form_pg_aggregate;
  * ----------------
  */
 
-#define Natts_pg_aggregate					17
+#define Natts_pg_aggregate					18
 #define Anum_pg_aggregate_aggfnoid			1
 #define Anum_pg_aggregate_aggkind			2
 #define Anum_pg_aggregate_aggnumdirectargs	3
 #define Anum_pg_aggregate_aggtransfn		4
 #define Anum_pg_aggregate_aggfinalfn		5
-#define Anum_pg_aggregate_aggmtransfn		6
-#define Anum_pg_aggregate_aggminvtransfn	7
-#define Anum_pg_aggregate_aggmfinalfn		8
-#define Anum_pg_aggregate_aggfinalextra		9
-#define Anum_pg_aggregate_aggmfinalextra	10
-#define Anum_pg_aggregate_aggsortop			11
-#define Anum_pg_aggregate_aggtranstype		12
-#define Anum_pg_aggregate_aggtransspace		13
-#define Anum_pg_aggregate_aggmtranstype		14
-#define Anum_pg_aggregate_aggmtransspace	15
-#define Anum_pg_aggregate_agginitval		16
-#define Anum_pg_aggregate_aggminitval		17
+#define Anum_pg_aggregate_aggcombinefn		6
+#define Anum_pg_aggregate_aggmtransfn		7
+#define Anum_pg_aggregate_aggminvtransfn	8
+#define Anum_pg_aggregate_aggmfinalfn		9
+#define Anum_pg_aggregate_aggfinalextra		10
+#define Anum_pg_aggregate_aggmfinalextra	11
+#define Anum_pg_aggregate_aggsortop			12
+#define Anum_pg_aggregate_aggtranstype		13
+#define Anum_pg_aggregate_aggtransspace		14
+#define Anum_pg_aggregate_aggmtranstype		15
+#define Anum_pg_aggregate_aggmtransspace	16
+#define Anum_pg_aggregate_agginitval		17
+#define Anum_pg_aggregate_aggminitval		18
 
 /*
  * Symbolic values for aggkind column.  We distinguish normal aggregates
@@ -126,184 +129,184 @@ typedef FormData_pg_aggregate *Form_pg_aggregate;
  */
 
 /* avg */
-DATA(insert ( 2100	n 0 int8_avg_accum	numeric_poly_avg		int8_avg_accum	int8_avg_accum_inv	numeric_poly_avg	f f 0	2281	48	2281	48	_null_ _null_ ));
-DATA(insert ( 2101	n 0 int4_avg_accum	int8_avg		int4_avg_accum	int4_avg_accum_inv	int8_avg					f f 0	1016	0	1016	0	"{0,0}" "{0,0}" ));
-DATA(insert ( 2102	n 0 int2_avg_accum	int8_avg		int2_avg_accum	int2_avg_accum_inv	int8_avg					f f 0	1016	0	1016	0	"{0,0}" "{0,0}" ));
-DATA(insert ( 2103	n 0 numeric_avg_accum numeric_avg	numeric_avg_accum numeric_accum_inv numeric_avg					f f 0	2281	128 2281	128 _null_ _null_ ));
-DATA(insert ( 2104	n 0 float4_accum	float8_avg		-				-				-								f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
-DATA(insert ( 2105	n 0 float8_accum	float8_avg		-				-				-								f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
-DATA(insert ( 2106	n 0 interval_accum	interval_avg	interval_accum	interval_accum_inv interval_avg					f f 0	1187	0	1187	0	"{0 second,0 second}" "{0 second,0 second}" ));
+DATA(insert ( 2100	n 0 int8_avg_accum	numeric_poly_avg	-	int8_avg_accum	int8_avg_accum_inv	numeric_poly_avg	f f 0	2281	48	2281	48	_null_ _null_ ));
+DATA(insert ( 2101	n 0 int4_avg_accum	int8_avg			-	int4_avg_accum	int4_avg_accum_inv	int8_avg			f f 0	1016	0	1016	0	"{0,0}" "{0,0}" ));
+DATA(insert ( 2102	n 0 int2_avg_accum	int8_avg			-	int2_avg_accum	int2_avg_accum_inv	int8_avg			f f 0	1016	0	1016	0	"{0,0}" "{0,0}" ));
+DATA(insert ( 2103	n 0 numeric_avg_accum numeric_avg		-	numeric_avg_accum numeric_accum_inv numeric_avg			f f 0	2281	128 2281	128 _null_ _null_ ));
+DATA(insert ( 2104	n 0 float4_accum	float8_avg			-	-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
+DATA(insert ( 2105	n 0 float8_accum	float8_avg			-	-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
+DATA(insert ( 2106	n 0 interval_accum	interval_avg		-	interval_accum	interval_accum_inv interval_avg			f f 0	1187	0	1187	0	"{0 second,0 second}" "{0 second,0 second}" ));
 
 /* sum */
-DATA(insert ( 2107	n 0 int8_avg_accum	numeric_poly_sum		int8_avg_accum	int8_avg_accum_inv numeric_poly_sum f f 0	2281	48	2281	48	_null_ _null_ ));
-DATA(insert ( 2108	n 0 int4_sum		-				int4_avg_accum	int4_avg_accum_inv int2int4_sum					f f 0	20		0	1016	0	_null_ "{0,0}" ));
-DATA(insert ( 2109	n 0 int2_sum		-				int2_avg_accum	int2_avg_accum_inv int2int4_sum					f f 0	20		0	1016	0	_null_ "{0,0}" ));
-DATA(insert ( 2110	n 0 float4pl		-				-				-				-								f f 0	700		0	0		0	_null_ _null_ ));
-DATA(insert ( 2111	n 0 float8pl		-				-				-				-								f f 0	701		0	0		0	_null_ _null_ ));
-DATA(insert ( 2112	n 0 cash_pl			-				cash_pl			cash_mi			-								f f 0	790		0	790		0	_null_ _null_ ));
-DATA(insert ( 2113	n 0 interval_pl		-				interval_pl		interval_mi		-								f f 0	1186	0	1186	0	_null_ _null_ ));
-DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_sum numeric_avg_accum numeric_accum_inv numeric_sum					f f 0	2281	128 2281	128 _null_ _null_ ));
+DATA(insert ( 2107	n 0 int8_avg_accum	numeric_poly_sum	-			int8_avg_accum	int8_avg_accum_inv	numeric_poly_sum	f f 0	2281	48	2281	48	_null_ _null_ ));
+DATA(insert ( 2108	n 0 int4_sum		-					int8pl		int4_avg_accum	int4_avg_accum_inv	int2int4_sum		f f 0	20		0	1016	0	_null_ "{0,0}" ));
+DATA(insert ( 2109	n 0 int2_sum		-					int8pl		int2_avg_accum	int2_avg_accum_inv	int2int4_sum		f f 0	20		0	1016	0	_null_ "{0,0}" ));
+DATA(insert ( 2110	n 0 float4pl		-					float4pl	-				-					-					f f 0	700		0	0		0	_null_ _null_ ));
+DATA(insert ( 2111	n 0 float8pl		-					float8pl	-				-					-					f f 0	701		0	0		0	_null_ _null_ ));
+DATA(insert ( 2112	n 0 cash_pl			-					cash_pl		cash_pl			cash_mi				-					f f 0	790		0	790		0	_null_ _null_ ));
+DATA(insert ( 2113	n 0 interval_pl		-					interval_pl	interval_pl		interval_mi			-					f f 0	1186	0	1186	0	_null_ _null_ ));
+DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_sum		-		numeric_avg_accum	numeric_accum_inv	numeric_sum			f f 0	2281	128 2281	128 _null_ _null_ ));
 
 /* max */
-DATA(insert ( 2115	n 0 int8larger		-				-				-				-				f f 413		20		0	0		0	_null_ _null_ ));
-DATA(insert ( 2116	n 0 int4larger		-				-				-				-				f f 521		23		0	0		0	_null_ _null_ ));
-DATA(insert ( 2117	n 0 int2larger		-				-				-				-				f f 520		21		0	0		0	_null_ _null_ ));
-DATA(insert ( 2118	n 0 oidlarger		-				-				-				-				f f 610		26		0	0		0	_null_ _null_ ));
-DATA(insert ( 2119	n 0 float4larger	-				-				-				-				f f 623		700		0	0		0	_null_ _null_ ));
-DATA(insert ( 2120	n 0 float8larger	-				-				-				-				f f 674		701		0	0		0	_null_ _null_ ));
-DATA(insert ( 2121	n 0 int4larger		-				-				-				-				f f 563		702		0	0		0	_null_ _null_ ));
-DATA(insert ( 2122	n 0 date_larger		-				-				-				-				f f 1097	1082	0	0		0	_null_ _null_ ));
-DATA(insert ( 2123	n 0 time_larger		-				-				-				-				f f 1112	1083	0	0		0	_null_ _null_ ));
-DATA(insert ( 2124	n 0 timetz_larger	-				-				-				-				f f 1554	1266	0	0		0	_null_ _null_ ));
-DATA(insert ( 2125	n 0 cashlarger		-				-				-				-				f f 903		790		0	0		0	_null_ _null_ ));
-DATA(insert ( 2126	n 0 timestamp_larger	-			-				-				-				f f 2064	1114	0	0		0	_null_ _null_ ));
-DATA(insert ( 2127	n 0 timestamptz_larger	-			-				-				-				f f 1324	1184	0	0		0	_null_ _null_ ));
-DATA(insert ( 2128	n 0 interval_larger -				-				-				-				f f 1334	1186	0	0		0	_null_ _null_ ));
-DATA(insert ( 2129	n 0 text_larger		-				-				-				-				f f 666		25		0	0		0	_null_ _null_ ));
-DATA(insert ( 2130	n 0 numeric_larger	-				-				-				-				f f 1756	1700	0	0		0	_null_ _null_ ));
-DATA(insert ( 2050	n 0 array_larger	-				-				-				-				f f 1073	2277	0	0		0	_null_ _null_ ));
-DATA(insert ( 2244	n 0 bpchar_larger	-				-				-				-				f f 1060	1042	0	0		0	_null_ _null_ ));
-DATA(insert ( 2797	n 0 tidlarger		-				-				-				-				f f 2800	27		0	0		0	_null_ _null_ ));
-DATA(insert ( 3526	n 0 enum_larger		-				-				-				-				f f 3519	3500	0	0		0	_null_ _null_ ));
-DATA(insert ( 3564	n 0 network_larger	-				-				-				-				f f 1205	869		0	0		0	_null_ _null_ ));
+DATA(insert ( 2115	n 0 int8larger		-				int8larger			-				-				-				f f 413		20		0	0		0	_null_ _null_ ));
+DATA(insert ( 2116	n 0 int4larger		-				int4larger			-				-				-				f f 521		23		0	0		0	_null_ _null_ ));
+DATA(insert ( 2117	n 0 int2larger		-				int2larger			-				-				-				f f 520		21		0	0		0	_null_ _null_ ));
+DATA(insert ( 2118	n 0 oidlarger		-				oidlarger			-				-				-				f f 610		26		0	0		0	_null_ _null_ ));
+DATA(insert ( 2119	n 0 float4larger	-				float4larger		-				-				-				f f 623		700		0	0		0	_null_ _null_ ));
+DATA(insert ( 2120	n 0 float8larger	-				float8larger		-				-				-				f f 674		701		0	0		0	_null_ _null_ ));
+DATA(insert ( 2121	n 0 int4larger		-				int4larger			-				-				-				f f 563		702		0	0		0	_null_ _null_ ));
+DATA(insert ( 2122	n 0 date_larger		-				date_larger			-				-				-				f f 1097	1082	0	0		0	_null_ _null_ ));
+DATA(insert ( 2123	n 0 time_larger		-				time_larger			-				-				-				f f 1112	1083	0	0		0	_null_ _null_ ));
+DATA(insert ( 2124	n 0 timetz_larger	-				timetz_larger		-				-				-				f f 1554	1266	0	0		0	_null_ _null_ ));
+DATA(insert ( 2125	n 0 cashlarger		-				cashlarger			-				-				-				f f 903		790		0	0		0	_null_ _null_ ));
+DATA(insert ( 2126	n 0 timestamp_larger	-			timestamp_larger	-				-				-				f f 2064	1114	0	0		0	_null_ _null_ ));
+DATA(insert ( 2127	n 0 timestamptz_larger	-			timestamptz_larger	-				-				-				f f 1324	1184	0	0		0	_null_ _null_ ));
+DATA(insert ( 2128	n 0 interval_larger -				interval_larger		-				-				-				f f 1334	1186	0	0		0	_null_ _null_ ));
+DATA(insert ( 2129	n 0 text_larger		-				text_larger			-				-				-				f f 666		25		0	0		0	_null_ _null_ ));
+DATA(insert ( 2130	n 0 numeric_larger	-				numeric_larger		-				-				-				f f 1756	1700	0	0		0	_null_ _null_ ));
+DATA(insert ( 2050	n 0 array_larger	-				array_larger		-				-				-				f f 1073	2277	0	0		0	_null_ _null_ ));
+DATA(insert ( 2244	n 0 bpchar_larger	-				bpchar_larger		-				-				-				f f 1060	1042	0	0		0	_null_ _null_ ));
+DATA(insert ( 2797	n 0 tidlarger		-				tidlarger			-				-				-				f f 2800	27		0	0		0	_null_ _null_ ));
+DATA(insert ( 3526	n 0 enum_larger		-				enum_larger			-				-				-				f f 3519	3500	0	0		0	_null_ _null_ ));
+DATA(insert ( 3564	n 0 network_larger	-				network_larger		-				-				-				f f 1205	869		0	0		0	_null_ _null_ ));
 
 /* min */
-DATA(insert ( 2131	n 0 int8smaller		-				-				-				-				f f 412		20		0	0		0	_null_ _null_ ));
-DATA(insert ( 2132	n 0 int4smaller		-				-				-				-				f f 97		23		0	0		0	_null_ _null_ ));
-DATA(insert ( 2133	n 0 int2smaller		-				-				-				-				f f 95		21		0	0		0	_null_ _null_ ));
-DATA(insert ( 2134	n 0 oidsmaller		-				-				-				-				f f 609		26		0	0		0	_null_ _null_ ));
-DATA(insert ( 2135	n 0 float4smaller	-				-				-				-				f f 622		700		0	0		0	_null_ _null_ ));
-DATA(insert ( 2136	n 0 float8smaller	-				-				-				-				f f 672		701		0	0		0	_null_ _null_ ));
-DATA(insert ( 2137	n 0 int4smaller		-				-				-				-				f f 562		702		0	0		0	_null_ _null_ ));
-DATA(insert ( 2138	n 0 date_smaller	-				-				-				-				f f 1095	1082	0	0		0	_null_ _null_ ));
-DATA(insert ( 2139	n 0 time_smaller	-				-				-				-				f f 1110	1083	0	0		0	_null_ _null_ ));
-DATA(insert ( 2140	n 0 timetz_smaller	-				-				-				-				f f 1552	1266	0	0		0	_null_ _null_ ));
-DATA(insert ( 2141	n 0 cashsmaller		-				-				-				-				f f 902		790		0	0		0	_null_ _null_ ));
-DATA(insert ( 2142	n 0 timestamp_smaller	-			-				-				-				f f 2062	1114	0	0		0	_null_ _null_ ));
-DATA(insert ( 2143	n 0 timestamptz_smaller -			-				-				-				f f 1322	1184	0	0		0	_null_ _null_ ));
-DATA(insert ( 2144	n 0 interval_smaller	-			-				-				-				f f 1332	1186	0	0		0	_null_ _null_ ));
-DATA(insert ( 2145	n 0 text_smaller	-				-				-				-				f f 664		25		0	0		0	_null_ _null_ ));
-DATA(insert ( 2146	n 0 numeric_smaller -				-				-				-				f f 1754	1700	0	0		0	_null_ _null_ ));
-DATA(insert ( 2051	n 0 array_smaller	-				-				-				-				f f 1072	2277	0	0		0	_null_ _null_ ));
-DATA(insert ( 2245	n 0 bpchar_smaller	-				-				-				-				f f 1058	1042	0	0		0	_null_ _null_ ));
-DATA(insert ( 2798	n 0 tidsmaller		-				-				-				-				f f 2799	27		0	0		0	_null_ _null_ ));
-DATA(insert ( 3527	n 0 enum_smaller	-				-				-				-				f f 3518	3500	0	0		0	_null_ _null_ ));
-DATA(insert ( 3565	n 0 network_smaller -				-				-				-				f f 1203	869		0	0		0	_null_ _null_ ));
+DATA(insert ( 2131	n 0 int8smaller		-				int8smaller			-				-				-				f f 412		20		0	0		0	_null_ _null_ ));
+DATA(insert ( 2132	n 0 int4smaller		-				int4smaller			-				-				-				f f 97		23		0	0		0	_null_ _null_ ));
+DATA(insert ( 2133	n 0 int2smaller		-				int2smaller			-				-				-				f f 95		21		0	0		0	_null_ _null_ ));
+DATA(insert ( 2134	n 0 oidsmaller		-				oidsmaller			-				-				-				f f 609		26		0	0		0	_null_ _null_ ));
+DATA(insert ( 2135	n 0 float4smaller	-				float4smaller		-				-				-				f f 622		700		0	0		0	_null_ _null_ ));
+DATA(insert ( 2136	n 0 float8smaller	-				float8smaller		-				-				-				f f 672		701		0	0		0	_null_ _null_ ));
+DATA(insert ( 2137	n 0 int4smaller		-				int4smaller			-				-				-				f f 562		702		0	0		0	_null_ _null_ ));
+DATA(insert ( 2138	n 0 date_smaller	-				date_smaller		-				-				-				f f 1095	1082	0	0		0	_null_ _null_ ));
+DATA(insert ( 2139	n 0 time_smaller	-				time_smaller		-				-				-				f f 1110	1083	0	0		0	_null_ _null_ ));
+DATA(insert ( 2140	n 0 timetz_smaller	-				timetz_smaller		-				-				-				f f 1552	1266	0	0		0	_null_ _null_ ));
+DATA(insert ( 2141	n 0 cashsmaller		-				cashsmaller			-				-				-				f f 902		790		0	0		0	_null_ _null_ ));
+DATA(insert ( 2142	n 0 timestamp_smaller	-			timestamp_smaller	-				-				-				f f 2062	1114	0	0		0	_null_ _null_ ));
+DATA(insert ( 2143	n 0 timestamptz_smaller -			timestamptz_smaller	-				-				-				f f 1322	1184	0	0		0	_null_ _null_ ));
+DATA(insert ( 2144	n 0 interval_smaller	-			interval_smaller	-				-				-				f f 1332	1186	0	0		0	_null_ _null_ ));
+DATA(insert ( 2145	n 0 text_smaller	-				text_smaller		-				-				-				f f 664		25		0	0		0	_null_ _null_ ));
+DATA(insert ( 2146	n 0 numeric_smaller -				numeric_smaller		-				-				-				f f 1754	1700	0	0		0	_null_ _null_ ));
+DATA(insert ( 2051	n 0 array_smaller	-				array_smaller		-				-				-				f f 1072	2277	0	0		0	_null_ _null_ ));
+DATA(insert ( 2245	n 0 bpchar_smaller	-				bpchar_smaller		-				-				-				f f 1058	1042	0	0		0	_null_ _null_ ));
+DATA(insert ( 2798	n 0 tidsmaller		-				tidsmaller			-				-				-				f f 2799	27		0	0		0	_null_ _null_ ));
+DATA(insert ( 3527	n 0 enum_smaller	-				enum_smaller		-				-				-				f f 3518	3500	0	0		0	_null_ _null_ ));
+DATA(insert ( 3565	n 0 network_smaller -				network_smaller		-				-				-				f f 1203	869		0	0		0	_null_ _null_ ));
 
 /* count */
-DATA(insert ( 2147	n 0 int8inc_any		-				int8inc_any		int8dec_any		-				f f 0		20		0	20		0	"0" "0" ));
-DATA(insert ( 2803	n 0 int8inc			-				int8inc			int8dec			-				f f 0		20		0	20		0	"0" "0" ));
+DATA(insert ( 2147	n 0 int8inc_any		-				int8pl	int8inc_any		int8dec_any		-				f f 0		20		0	20		0	"0" "0" ));
+DATA(insert ( 2803	n 0 int8inc			-				int8pl	int8inc			int8dec			-				f f 0		20		0	20		0	"0" "0" ));
 
 /* var_pop */
-DATA(insert ( 2718	n 0 int8_accum	numeric_var_pop		int8_accum		int8_accum_inv	numeric_var_pop					f f 0	2281	128 2281	128 _null_ _null_ ));
-DATA(insert ( 2719	n 0 int4_accum	numeric_poly_var_pop		int4_accum		int4_accum_inv	numeric_poly_var_pop	f f 0	2281	48	2281	48	_null_ _null_ ));
-DATA(insert ( 2720	n 0 int2_accum	numeric_poly_var_pop		int2_accum		int2_accum_inv	numeric_poly_var_pop	f f 0	2281	48	2281	48	_null_ _null_ ));
-DATA(insert ( 2721	n 0 float4_accum	float8_var_pop	-				-				-								f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
-DATA(insert ( 2722	n 0 float8_accum	float8_var_pop	-				-				-								f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
-DATA(insert ( 2723	n 0 numeric_accum	numeric_var_pop numeric_accum numeric_accum_inv numeric_var_pop					f f 0	2281	128 2281	128 _null_ _null_ ));
+DATA(insert ( 2718	n 0 int8_accum	numeric_var_pop			-	int8_accum		int8_accum_inv	numeric_var_pop			f f 0	2281	128 2281	128 _null_ _null_ ));
+DATA(insert ( 2719	n 0 int4_accum	numeric_poly_var_pop	-	int4_accum		int4_accum_inv	numeric_poly_var_pop	f f 0	2281	48	2281	48	_null_ _null_ ));
+DATA(insert ( 2720	n 0 int2_accum	numeric_poly_var_pop	-	int2_accum		int2_accum_inv	numeric_poly_var_pop	f f 0	2281	48	2281	48	_null_ _null_ ));
+DATA(insert ( 2721	n 0 float4_accum	float8_var_pop		-	-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
+DATA(insert ( 2722	n 0 float8_accum	float8_var_pop		-	-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
+DATA(insert ( 2723	n 0 numeric_accum	numeric_var_pop		-	numeric_accum numeric_accum_inv numeric_var_pop			f f 0	2281	128 2281	128 _null_ _null_ ));
 
 /* var_samp */
-DATA(insert ( 2641	n 0 int8_accum	numeric_var_samp	int8_accum		int8_accum_inv	numeric_var_samp				f f 0	2281	128 2281	128 _null_ _null_ ));
-DATA(insert ( 2642	n 0 int4_accum	numeric_poly_var_samp		int4_accum		int4_accum_inv	numeric_poly_var_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
-DATA(insert ( 2643	n 0 int2_accum	numeric_poly_var_samp		int2_accum		int2_accum_inv	numeric_poly_var_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
-DATA(insert ( 2644	n 0 float4_accum	float8_var_samp -				-				-								f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
-DATA(insert ( 2645	n 0 float8_accum	float8_var_samp -				-				-								f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
-DATA(insert ( 2646	n 0 numeric_accum	numeric_var_samp numeric_accum numeric_accum_inv numeric_var_samp				f f 0	2281	128 2281	128 _null_ _null_ ));
+DATA(insert ( 2641	n 0 int8_accum	numeric_var_samp		-	int8_accum		int8_accum_inv	numeric_var_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
+DATA(insert ( 2642	n 0 int4_accum	numeric_poly_var_samp	-	int4_accum		int4_accum_inv	numeric_poly_var_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
+DATA(insert ( 2643	n 0 int2_accum	numeric_poly_var_samp	-	int2_accum		int2_accum_inv	numeric_poly_var_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
+DATA(insert ( 2644	n 0 float4_accum	float8_var_samp		-	-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
+DATA(insert ( 2645	n 0 float8_accum	float8_var_samp		-	-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
+DATA(insert ( 2646	n 0 numeric_accum	numeric_var_samp	-	numeric_accum	numeric_accum_inv numeric_var_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
 
 /* variance: historical Postgres syntax for var_samp */
-DATA(insert ( 2148	n 0 int8_accum	numeric_var_samp	int8_accum		int8_accum_inv	numeric_var_samp				f f 0	2281	128 2281	128 _null_ _null_ ));
-DATA(insert ( 2149	n 0 int4_accum	numeric_poly_var_samp		int4_accum		int4_accum_inv	numeric_poly_var_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
-DATA(insert ( 2150	n 0 int2_accum	numeric_poly_var_samp		int2_accum		int2_accum_inv	numeric_poly_var_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
-DATA(insert ( 2151	n 0 float4_accum	float8_var_samp -				-				-								f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
-DATA(insert ( 2152	n 0 float8_accum	float8_var_samp -				-				-								f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
-DATA(insert ( 2153	n 0 numeric_accum	numeric_var_samp numeric_accum numeric_accum_inv numeric_var_samp				f f 0	2281	128 2281	128 _null_ _null_ ));
+DATA(insert ( 2148	n 0 int8_accum	numeric_var_samp		-	int8_accum		int8_accum_inv	numeric_var_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
+DATA(insert ( 2149	n 0 int4_accum	numeric_poly_var_samp	-	int4_accum		int4_accum_inv	numeric_poly_var_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
+DATA(insert ( 2150	n 0 int2_accum	numeric_poly_var_samp	-	int2_accum		int2_accum_inv	numeric_poly_var_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
+DATA(insert ( 2151	n 0 float4_accum	float8_var_samp		-	-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
+DATA(insert ( 2152	n 0 float8_accum	float8_var_samp		-	-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
+DATA(insert ( 2153	n 0 numeric_accum	numeric_var_samp	-	numeric_accum numeric_accum_inv numeric_var_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
 
 /* stddev_pop */
-DATA(insert ( 2724	n 0 int8_accum	numeric_stddev_pop	int8_accum	int8_accum_inv	numeric_stddev_pop					f f 0	2281	128 2281	128 _null_ _null_ ));
-DATA(insert ( 2725	n 0 int4_accum	numeric_poly_stddev_pop int4_accum	int4_accum_inv	numeric_poly_stddev_pop f f 0	2281	48	2281	48	_null_ _null_ ));
-DATA(insert ( 2726	n 0 int2_accum	numeric_poly_stddev_pop int2_accum	int2_accum_inv	numeric_poly_stddev_pop f f 0	2281	48	2281	48	_null_ _null_ ));
-DATA(insert ( 2727	n 0 float4_accum	float8_stddev_pop	-				-				-							f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
-DATA(insert ( 2728	n 0 float8_accum	float8_stddev_pop	-				-				-							f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
-DATA(insert ( 2729	n 0 numeric_accum	numeric_stddev_pop numeric_accum numeric_accum_inv numeric_stddev_pop			f f 0	2281	128 2281	128 _null_ _null_ ));
+DATA(insert ( 2724	n 0 int8_accum	numeric_stddev_pop		-	int8_accum	int8_accum_inv	numeric_stddev_pop		f f 0	2281	128 2281	128 _null_ _null_ ));
+DATA(insert ( 2725	n 0 int4_accum	numeric_poly_stddev_pop	-	int4_accum	int4_accum_inv	numeric_poly_stddev_pop	f f 0	2281	48	2281	48	_null_ _null_ ));
+DATA(insert ( 2726	n 0 int2_accum	numeric_poly_stddev_pop	-	int2_accum	int2_accum_inv	numeric_poly_stddev_pop	f f 0	2281	48	2281	48	_null_ _null_ ));
+DATA(insert ( 2727	n 0 float4_accum	float8_stddev_pop	-	-				-				-					f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
+DATA(insert ( 2728	n 0 float8_accum	float8_stddev_pop	-	-				-				-					f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
+DATA(insert ( 2729	n 0 numeric_accum	numeric_stddev_pop	-	numeric_accum numeric_accum_inv numeric_stddev_pop	f f 0	2281	128 2281	128 _null_ _null_ ));
 
 /* stddev_samp */
-DATA(insert ( 2712	n 0 int8_accum	numeric_stddev_samp		int8_accum	int8_accum_inv	numeric_stddev_samp				f f 0	2281	128 2281	128 _null_ _null_ ));
-DATA(insert ( 2713	n 0 int4_accum	numeric_poly_stddev_samp	int4_accum	int4_accum_inv	numeric_poly_stddev_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
-DATA(insert ( 2714	n 0 int2_accum	numeric_poly_stddev_samp	int2_accum	int2_accum_inv	numeric_poly_stddev_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
-DATA(insert ( 2715	n 0 float4_accum	float8_stddev_samp	-				-				-							f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
-DATA(insert ( 2716	n 0 float8_accum	float8_stddev_samp	-				-				-							f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
-DATA(insert ( 2717	n 0 numeric_accum	numeric_stddev_samp numeric_accum numeric_accum_inv numeric_stddev_samp			f f 0	2281	128 2281	128 _null_ _null_ ));
+DATA(insert ( 2712	n 0 int8_accum	numeric_stddev_samp			-	int8_accum	int8_accum_inv	numeric_stddev_samp			f f 0	2281	128 2281	128 _null_ _null_ ));
+DATA(insert ( 2713	n 0 int4_accum	numeric_poly_stddev_samp	-	int4_accum	int4_accum_inv	numeric_poly_stddev_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
+DATA(insert ( 2714	n 0 int2_accum	numeric_poly_stddev_samp	-	int2_accum	int2_accum_inv	numeric_poly_stddev_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
+DATA(insert ( 2715	n 0 float4_accum	float8_stddev_samp		-	-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
+DATA(insert ( 2716	n 0 float8_accum	float8_stddev_samp		-	-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
+DATA(insert ( 2717	n 0 numeric_accum	numeric_stddev_samp		-	numeric_accum numeric_accum_inv numeric_stddev_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
 
 /* stddev: historical Postgres syntax for stddev_samp */
-DATA(insert ( 2154	n 0 int8_accum	numeric_stddev_samp		int8_accum	int8_accum_inv	numeric_stddev_samp				f f 0	2281	128 2281	128 _null_ _null_ ));
-DATA(insert ( 2155	n 0 int4_accum	numeric_poly_stddev_samp	int4_accum	int4_accum_inv	numeric_poly_stddev_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
-DATA(insert ( 2156	n 0 int2_accum	numeric_poly_stddev_samp	int2_accum	int2_accum_inv	numeric_poly_stddev_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
-DATA(insert ( 2157	n 0 float4_accum	float8_stddev_samp	-				-				-							f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
-DATA(insert ( 2158	n 0 float8_accum	float8_stddev_samp	-				-				-							f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
-DATA(insert ( 2159	n 0 numeric_accum	numeric_stddev_samp numeric_accum numeric_accum_inv numeric_stddev_samp			f f 0	2281	128 2281	128 _null_ _null_ ));
+DATA(insert ( 2154	n 0 int8_accum	numeric_stddev_samp			-	int8_accum		int8_accum_inv	numeric_stddev_samp			f f 0	2281	128 2281	128 _null_ _null_ ));
+DATA(insert ( 2155	n 0 int4_accum	numeric_poly_stddev_samp	-	int4_accum		int4_accum_inv	numeric_poly_stddev_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
+DATA(insert ( 2156	n 0 int2_accum	numeric_poly_stddev_samp	-	int2_accum		int2_accum_inv	numeric_poly_stddev_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
+DATA(insert ( 2157	n 0 float4_accum	float8_stddev_samp		-	-				-				-							f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
+DATA(insert ( 2158	n 0 float8_accum	float8_stddev_samp		-	-				-				-							f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
+DATA(insert ( 2159	n 0 numeric_accum	numeric_stddev_samp		-	numeric_accum	numeric_accum_inv numeric_stddev_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
 
 /* SQL2003 binary regression aggregates */
-DATA(insert ( 2818	n 0 int8inc_float8_float8	-					-				-				-				f f 0	20		0	0		0	"0" _null_ ));
-DATA(insert ( 2819	n 0 float8_regr_accum	float8_regr_sxx			-				-				-				f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2820	n 0 float8_regr_accum	float8_regr_syy			-				-				-				f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2821	n 0 float8_regr_accum	float8_regr_sxy			-				-				-				f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2822	n 0 float8_regr_accum	float8_regr_avgx		-				-				-				f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2823	n 0 float8_regr_accum	float8_regr_avgy		-				-				-				f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2824	n 0 float8_regr_accum	float8_regr_r2			-				-				-				f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2825	n 0 float8_regr_accum	float8_regr_slope		-				-				-				f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2826	n 0 float8_regr_accum	float8_regr_intercept	-				-				-				f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2827	n 0 float8_regr_accum	float8_covar_pop		-				-				-				f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2828	n 0 float8_regr_accum	float8_covar_samp		-				-				-				f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2829	n 0 float8_regr_accum	float8_corr				-				-				-				f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2818	n 0 int8inc_float8_float8	-					-	-				-				-			f f 0	20		0	0		0	"0" _null_ ));
+DATA(insert ( 2819	n 0 float8_regr_accum	float8_regr_sxx			-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2820	n 0 float8_regr_accum	float8_regr_syy			-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2821	n 0 float8_regr_accum	float8_regr_sxy			-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2822	n 0 float8_regr_accum	float8_regr_avgx		-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2823	n 0 float8_regr_accum	float8_regr_avgy		-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2824	n 0 float8_regr_accum	float8_regr_r2			-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2825	n 0 float8_regr_accum	float8_regr_slope		-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2826	n 0 float8_regr_accum	float8_regr_intercept	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2827	n 0 float8_regr_accum	float8_covar_pop		-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2828	n 0 float8_regr_accum	float8_covar_samp		-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2829	n 0 float8_regr_accum	float8_corr				-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
 
 /* boolean-and and boolean-or */
-DATA(insert ( 2517	n 0 booland_statefunc	-			bool_accum		bool_accum_inv	bool_alltrue	f f 58	16		0	2281	16	_null_ _null_ ));
-DATA(insert ( 2518	n 0 boolor_statefunc	-			bool_accum		bool_accum_inv	bool_anytrue	f f 59	16		0	2281	16	_null_ _null_ ));
-DATA(insert ( 2519	n 0 booland_statefunc	-			bool_accum		bool_accum_inv	bool_alltrue	f f 58	16		0	2281	16	_null_ _null_ ));
+DATA(insert ( 2517	n 0 booland_statefunc	-	-	bool_accum	bool_accum_inv	bool_alltrue	f f 58	16		0	2281	16	_null_ _null_ ));
+DATA(insert ( 2518	n 0 boolor_statefunc	-	-	bool_accum	bool_accum_inv	bool_anytrue	f f 59	16		0	2281	16	_null_ _null_ ));
+DATA(insert ( 2519	n 0 booland_statefunc	-	-	bool_accum	bool_accum_inv	bool_alltrue	f f 58	16		0	2281	16	_null_ _null_ ));
 
 /* bitwise integer */
-DATA(insert ( 2236	n 0 int2and		-					-				-				-				f f 0	21		0	0		0	_null_ _null_ ));
-DATA(insert ( 2237	n 0 int2or		-					-				-				-				f f 0	21		0	0		0	_null_ _null_ ));
-DATA(insert ( 2238	n 0 int4and		-					-				-				-				f f 0	23		0	0		0	_null_ _null_ ));
-DATA(insert ( 2239	n 0 int4or		-					-				-				-				f f 0	23		0	0		0	_null_ _null_ ));
-DATA(insert ( 2240	n 0 int8and		-					-				-				-				f f 0	20		0	0		0	_null_ _null_ ));
-DATA(insert ( 2241	n 0 int8or		-					-				-				-				f f 0	20		0	0		0	_null_ _null_ ));
-DATA(insert ( 2242	n 0 bitand		-					-				-				-				f f 0	1560	0	0		0	_null_ _null_ ));
-DATA(insert ( 2243	n 0 bitor		-					-				-				-				f f 0	1560	0	0		0	_null_ _null_ ));
+DATA(insert ( 2236	n 0 int2and		-				int2and	-				-				-				f f 0	21		0	0		0	_null_ _null_ ));
+DATA(insert ( 2237	n 0 int2or		-				int2or	-				-				-				f f 0	21		0	0		0	_null_ _null_ ));
+DATA(insert ( 2238	n 0 int4and		-				int4and	-				-				-				f f 0	23		0	0		0	_null_ _null_ ));
+DATA(insert ( 2239	n 0 int4or		-				int4or	-				-				-				f f 0	23		0	0		0	_null_ _null_ ));
+DATA(insert ( 2240	n 0 int8and		-				int8and	-				-				-				f f 0	20		0	0		0	_null_ _null_ ));
+DATA(insert ( 2241	n 0 int8or		-				int8or	-				-				-				f f 0	20		0	0		0	_null_ _null_ ));
+DATA(insert ( 2242	n 0 bitand		-				bitand	-				-				-				f f 0	1560	0	0		0	_null_ _null_ ));
+DATA(insert ( 2243	n 0 bitor		-				bitor	-				-				-				f f 0	1560	0	0		0	_null_ _null_ ));
 
 /* xml */
-DATA(insert ( 2901	n 0 xmlconcat2	-					-				-				-				f f 0	142		0	0		0	_null_ _null_ ));
+DATA(insert ( 2901	n 0 xmlconcat2	-				-		-				-				-				f f 0	142		0	0		0	_null_ _null_ ));
 
 /* array */
-DATA(insert ( 2335	n 0 array_agg_transfn	array_agg_finalfn	-				-				-				t f 0	2281	0	0		0	_null_ _null_ ));
-DATA(insert ( 4053	n 0 array_agg_array_transfn array_agg_array_finalfn -		-				-				t f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 2335	n 0 array_agg_transfn		array_agg_finalfn		-	-		-				-				t f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 4053	n 0 array_agg_array_transfn array_agg_array_finalfn	-	-		-				-				t f 0	2281	0	0		0	_null_ _null_ ));
 
 /* text */
-DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_finalfn	-				-				-				f f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_finalfn	-	-				-				-				f f 0	2281	0	0		0	_null_ _null_ ));
 
 /* bytea */
-DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_finalfn	-				-				-		f f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_finalfn	-	-				-				-		f f 0	2281	0	0		0	_null_ _null_ ));
 
 /* json */
-DATA(insert ( 3175	n 0 json_agg_transfn	json_agg_finalfn			-				-				-				f f 0	2281	0	0		0	_null_ _null_ ));
-DATA(insert ( 3197	n 0 json_object_agg_transfn json_object_agg_finalfn -				-				-				f f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 3175	n 0 json_agg_transfn	json_agg_finalfn			-	-				-				-				f f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 3197	n 0 json_object_agg_transfn json_object_agg_finalfn -	-				-				-				f f 0	2281	0	0		0	_null_ _null_ ));
 
 /* jsonb */
-DATA(insert ( 3267	n 0 jsonb_agg_transfn	jsonb_agg_finalfn			-				-				-				f f 0	2281	0	0		0	_null_ _null_ ));
-DATA(insert ( 3270	n 0 jsonb_object_agg_transfn jsonb_object_agg_finalfn -				-				-				f f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 3267	n 0 jsonb_agg_transfn	jsonb_agg_finalfn				-	-				-				-			f f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 3270	n 0 jsonb_object_agg_transfn jsonb_object_agg_finalfn	-	-				-				-			f f 0	2281	0	0		0	_null_ _null_ ));
 
 /* ordered-set and hypothetical-set aggregates */
-DATA(insert ( 3972	o 1 ordered_set_transition			percentile_disc_final					-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
-DATA(insert ( 3974	o 1 ordered_set_transition			percentile_cont_float8_final			-		-		-		f f 0	2281	0	0		0	_null_ _null_ ));
-DATA(insert ( 3976	o 1 ordered_set_transition			percentile_cont_interval_final			-		-		-		f f 0	2281	0	0		0	_null_ _null_ ));
-DATA(insert ( 3978	o 1 ordered_set_transition			percentile_disc_multi_final				-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
-DATA(insert ( 3980	o 1 ordered_set_transition			percentile_cont_float8_multi_final		-		-		-		f f 0	2281	0	0		0	_null_ _null_ ));
-DATA(insert ( 3982	o 1 ordered_set_transition			percentile_cont_interval_multi_final	-		-		-		f f 0	2281	0	0		0	_null_ _null_ ));
-DATA(insert ( 3984	o 0 ordered_set_transition			mode_final								-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
-DATA(insert ( 3986	h 1 ordered_set_transition_multi	rank_final								-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
-DATA(insert ( 3988	h 1 ordered_set_transition_multi	percent_rank_final						-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
-DATA(insert ( 3990	h 1 ordered_set_transition_multi	cume_dist_final							-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
-DATA(insert ( 3992	h 1 ordered_set_transition_multi	dense_rank_final						-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 3972	o 1 ordered_set_transition			percentile_disc_final					-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 3974	o 1 ordered_set_transition			percentile_cont_float8_final			-	-		-		-		f f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 3976	o 1 ordered_set_transition			percentile_cont_interval_final			-	-		-		-		f f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 3978	o 1 ordered_set_transition			percentile_disc_multi_final				-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 3980	o 1 ordered_set_transition			percentile_cont_float8_multi_final		-	-		-		-		f f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 3982	o 1 ordered_set_transition			percentile_cont_interval_multi_final	-	-		-		-		f f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 3984	o 0 ordered_set_transition			mode_final								-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 3986	h 1 ordered_set_transition_multi	rank_final								-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 3988	h 1 ordered_set_transition_multi	percent_rank_final						-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 3990	h 1 ordered_set_transition_multi	cume_dist_final							-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
+DATA(insert ( 3992	h 1 ordered_set_transition_multi	dense_rank_final						-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
 
 
 /*
@@ -322,6 +325,7 @@ extern ObjectAddress AggregateCreate(const char *aggName,
 				Oid variadicArgType,
 				List *aggtransfnName,
 				List *aggfinalfnName,
+				List *aggcombinefnName,
 				List *aggmtransfnName,
 				List *aggminvtransfnName,
 				List *aggmfinalfnName,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4ae2f3e..70e88c7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1845,6 +1845,8 @@ typedef struct AggState
 	AggStatePerTrans curpertrans;	/* currently active trans state */
 	bool		input_done;		/* indicates end of input */
 	bool		agg_done;		/* indicates completion of Agg scan */
+	bool		combineStates;	/* input tuples contain transition states */
+	bool		finalizeAggs;	/* should we call the finalfn on agg states? */
 	int			projected_set;	/* The last projected grouping set */
 	int			current_set;	/* The current grouping set being evaluated */
 	Bitmapset  *grouped_cols;	/* grouped cols in current projection */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index cc259f1..6a1353d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -715,6 +715,8 @@ typedef struct Agg
 	AggStrategy aggstrategy;
 	int			numCols;		/* number of grouping columns */
 	AttrNumber *grpColIdx;		/* their indexes in the target list */
+	bool		combineStates;	/* input tuples contain transition states */
+	bool		finalizeAggs;	/* should we call the finalfn on agg states? */
 	Oid		   *grpOperators;	/* equality operators to compare with */
 	long		numGroups;		/* estimated number of groups in input */
 	List	   *groupingSets;	/* grouping sets to use */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 79bed33..e8ca077 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -16,6 +16,7 @@
 
 #include "access/sdir.h"
 #include "lib/stringinfo.h"
+#include "nodes/plannodes.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
 #include "storage/block.h"
@@ -260,7 +261,14 @@ typedef struct PlannerInfo
 	bool		hasPseudoConstantQuals; /* true if any RestrictInfo has
 										 * pseudoconstant = true */
 	bool		hasRecursion;	/* true if planning a recursive WITH item */
-
+	Index		aggregaterel;	/* Index of relation to perform early
+								 * aggregation on, or 0 if not enabled */
+	Index		groupbyrel;		/* Index of single relation to perform group by
+								 * on, or 0 if not enabled. */
+	Index		groupbeforejoinrel; /* Index of additional rel which was added
+									 * to rewrite the group by to happen before
+									 * joining to any other relations.
+									 */
 	/* These fields are used only when hasRecursion is true: */
 	int			wt_param_id;	/* PARAM_EXEC ID for the work table */
 	struct Plan *non_recursive_plan;	/* plan for non-recursive term */
@@ -450,7 +458,8 @@ typedef struct RelOptInfo
 	/* per-relation planner control flags */
 	bool		consider_startup;		/* keep cheap-startup-cost paths? */
 	bool		consider_param_startup; /* ditto, for parameterized paths? */
-
+	bool		partial_aggregate;	/* true if aggregates should not be
+									 * finalized. */
 	/* materialization information */
 	List	   *reltargetlist;	/* Vars to be output by scan of relation */
 	List	   *pathlist;		/* Path structures */
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index dd43e45..aaa1e08 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -61,6 +61,7 @@ extern bool enable_nestloop;
 extern bool enable_material;
 extern bool enable_mergejoin;
 extern bool enable_hashjoin;
+extern bool enable_earlygrouping;
 extern int	constraint_exclusion;
 
 extern double clamp_row_est(double nrows);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 52b077a..9540061 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -36,6 +36,8 @@ extern RelOptInfo *query_planner(PlannerInfo *root, List *tlist,
 extern void preprocess_minmax_aggregates(PlannerInfo *root, List *tlist);
 extern Plan *optimize_minmax_aggregates(PlannerInfo *root, List *tlist,
 						   const AggClauseCosts *aggcosts, Path *best_path);
+extern List *setup_grouping_rel(PlannerInfo *root, List *joinlist);
+extern bool prepare_group_before_join(PlannerInfo *root, Query *parse);
 
 /*
  * prototypes for plan/createplan.c
@@ -59,9 +61,8 @@ extern Sort *make_sort_from_groupcols(PlannerInfo *root, List *groupcls,
 extern Agg *make_agg(PlannerInfo *root, List *tlist, List *qual,
 		 AggStrategy aggstrategy, const AggClauseCosts *aggcosts,
 		 int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
-		 List *groupingSets,
-		 long numGroups,
-		 Plan *lefttree);
+		 List *groupingSets, long numGroups, bool combineStates,
+		 bool finalizeAggs, Plan *lefttree);
 extern WindowAgg *make_windowagg(PlannerInfo *root, List *tlist,
 			   List *windowFuncs, Index winref,
 			   int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index b10a504..cfedf1f 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -32,8 +32,8 @@ extern PlannedStmt *standard_planner(Query *parse, int cursorOptions,
 
 extern Plan *subquery_planner(PlannerGlobal *glob, Query *parse,
 				 PlannerInfo *parent_root,
-				 bool hasRecursion, double tuple_fraction,
-				 PlannerInfo **subroot);
+				 bool hasRecursion, bool finalize_aggregates,
+				 double tuple_fraction, PlannerInfo **subroot);
 
 extern void add_tlist_costs_to_plan(PlannerInfo *root, Plan *plan,
 						List *tlist);
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index e2b3894..621b6b9 100644
--- a/src/include/parser/parse_agg.h
+++ b/src/include/parser/parse_agg.h
@@ -46,6 +46,12 @@ extern void build_aggregate_transfn_expr(Oid *agg_input_types,
 						Expr **transfnexpr,
 						Expr **invtransfnexpr);
 
+extern void build_aggregate_combinefn_expr(bool agg_variadic,
+										   Oid agg_state_type,
+										   Oid agg_input_collation,
+										   Oid combinefn_oid,
+										   Expr **combinefnexpr);
+
 extern void build_aggregate_finalfn_expr(Oid *agg_input_types,
 						int num_finalfn_inputs,
 						Oid agg_state_type,
diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out
index 82a34fb..906c2b5 100644
--- a/src/test/regress/expected/create_aggregate.out
+++ b/src/test/regress/expected/create_aggregate.out
@@ -101,6 +101,13 @@ CREATE AGGREGATE sumdouble (float8)
     msfunc = float8pl,
     minvfunc = float8mi
 );
+-- aggregate combine functions
+CREATE AGGREGATE mymax (int)
+(
+	stype = int4,
+	sfunc = int4larger,
+	cfunc = int4larger
+);
 -- invalid: nonstrict inverse with strict forward function
 CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
 $$ SELECT $1 - $2; $$
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 00ef421..af3f9b7 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -2,6 +2,7 @@ SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
          name         | setting 
 ----------------------+---------
  enable_bitmapscan    | on
+ enable_earlygrouping | on
  enable_hashagg       | on
  enable_hashjoin      | on
  enable_indexonlyscan | on
@@ -12,7 +13,7 @@ SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
  enable_seqscan       | on
  enable_sort          | on
  enable_tidscan       | on
-(11 rows)
+(12 rows)
 
 CREATE TABLE foo2(fooid int, f2 int);
 INSERT INTO foo2 VALUES(1, 11);
diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql
index 0ec1572..0ebd0d9 100644
--- a/src/test/regress/sql/create_aggregate.sql
+++ b/src/test/regress/sql/create_aggregate.sql
@@ -115,6 +115,14 @@ CREATE AGGREGATE sumdouble (float8)
     minvfunc = float8mi
 );
 
+-- aggregate combine functions
+CREATE AGGREGATE mymax (int)
+(
+	stype = int4,
+	sfunc = int4larger,
+	cfunc = int4larger
+);
+
 -- invalid: nonstrict inverse with strict forward function
 
 CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
