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..848a868 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.
@@ -567,6 +583,7 @@ AggregateCreate(const char *aggName,
 	values[Anum_pg_aggregate_aggnumdirectargs - 1] = Int16GetDatum(numDirectArgs);
 	values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
 	values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
+	values[Anum_pg_aggregate_aggcombinefn - 1] = ObjectIdGetDatum(combinefn);
 	values[Anum_pg_aggregate_aggmtransfn - 1] = ObjectIdGetDatum(mtransfn);
 	values[Anum_pg_aggregate_aggminvtransfn - 1] = ObjectIdGetDatum(minvtransfn);
 	values[Anum_pg_aggregate_aggmfinalfn - 1] = ObjectIdGetDatum(mfinalfn);
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 12dae77..4a92bfc 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -908,25 +908,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..a74a0fc 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
+ *	  transition states. In this case we use the aggregate's combinefunc to
+ *	  'add' the passed in trans state to the trans state 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,125 @@ 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)
+		{
+			/*
+			 * transValue has not been initialized. This is the first non-NULL
+			 * input value. We use it as the initial value for transValue. (We
+			 * already checked that the agg's input type is binary-compatible
+			 * with its transtype, so straight copy here is OK.)
+			 *
+			 * We must copy the datum into aggcontext if it is pass-by-ref. We
+			 * do not need to pfree the old transValue, since it's NULL.
+			 */
+			oldContext = MemoryContextSwitchTo(
+											   aggstate->aggcontexts[aggstate->current_set]->ecxt_per_tuple_memory);
+			pergroupstate->transValue = datumCopy(fcinfo->arg[1],
+												  pertrans->transtypeByVal,
+												  pertrans->transtypeLen);
+			pergroupstate->transValueIsNull = false;
+			pergroupstate->noTransValue = false;
+			MemoryContextSwitchTo(oldContext);
+
+			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 +1412,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 +1434,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 +1953,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 +2064,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 +2199,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 +2552,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 +2747,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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ba04b72..b2dc451 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -865,6 +865,8 @@ _copyAgg(const Agg *from)
 
 	COPY_SCALAR_FIELD(aggstrategy);
 	COPY_SCALAR_FIELD(numCols);
+	COPY_SCALAR_FIELD(combineStates);
+	COPY_SCALAR_FIELD(finalizeAggs);
 	if (from->numCols > 0)
 	{
 		COPY_POINTER_FIELD(grpColIdx, from->numCols * sizeof(AttrNumber));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 63fae82..6de4c88 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -690,11 +690,13 @@ _outAgg(StringInfo str, const Agg *node)
 
 	WRITE_ENUM_FIELD(aggstrategy, AggStrategy);
 	WRITE_INT_FIELD(numCols);
-
 	appendStringInfoString(str, " :grpColIdx");
 	for (i = 0; i < node->numCols; i++)
 		appendStringInfo(str, " %d", node->grpColIdx[i]);
 
+	WRITE_BOOL_FIELD(combineStates);
+	WRITE_BOOL_FIELD(finalizeAggs);
+
 	appendStringInfoString(str, " :grpOperators");
 	for (i = 0; i < node->numCols; i++)
 		appendStringInfo(str, " %u", node->grpOperators[i]);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 222e2ed..ec6790a 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1989,6 +1989,8 @@ _readAgg(void)
 	READ_ENUM_FIELD(aggstrategy, AggStrategy);
 	READ_INT_FIELD(numCols);
 	READ_ATTRNUMBER_ARRAY(grpColIdx, local_node->numCols);
+	READ_BOOL_FIELD(combineStates);
+	READ_BOOL_FIELD(finalizeAggs);
 	READ_OID_ARRAY(grpOperators, local_node->numCols);
 	READ_LONG_FIELD(numGroups);
 	READ_NODE_FIELD(groupingSets);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 990486c..b6f37a4 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -125,6 +125,7 @@ bool		enable_material = true;
 bool		enable_mergejoin = true;
 bool		enable_hashjoin = true;
 
+bool		enable_parallelagg = false;
 typedef struct
 {
 	PlannerInfo *root;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 32f903d..b34d635 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -1053,6 +1053,8 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
 								 groupOperators,
 								 NIL,
 								 numGroups,
+								 false,
+								 true,
 								 subplan);
 	}
 	else
@@ -4554,9 +4556,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;
@@ -4565,6 +4566,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/planner.c b/src/backend/optimizer/plan/planner.c
index 2c04f5c..1427847 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -49,6 +49,8 @@
 #include "utils/rel.h"
 #include "utils/selfuncs.h"
 
+#include "utils/syscache.h"
+#include "catalog/pg_aggregate.h"
 
 /* GUC parameter */
 double		cursor_tuple_fraction = DEFAULT_CURSOR_TUPLE_FRACTION;
@@ -77,6 +79,17 @@ typedef struct
 	List	   *groupClause;	/* overrides parse->groupClause */
 } standard_qp_extra;
 
+typedef struct
+{
+	bool	agguseparallel;
+} CheckParallelAggAvaiContext;
+
+typedef struct
+{
+	AttrNumber 	resno;
+	List		*targetlist;
+} AddQualInTListExprContext;
+
 /* Local functions */
 static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
 static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
@@ -134,8 +147,39 @@ static Plan *build_grouping_chain(PlannerInfo *root,
 					 AttrNumber *groupColIdx,
 					 AggClauseCosts *agg_costs,
 					 long numGroups,
+					 bool combineStates,
+					 bool finalizeAggs,
+					 Plan *result_plan);
+static bool check_parallel_agg_available(Plan *plan,
+						List *targetlist,
+						List *qual);
+static bool check_parallel_agg_available_walker (Node *node,
+						CheckParallelAggAvaiContext *context);
+static Plan *build_group_parallelagg(PlannerInfo *root,
+					 Query *parse,
+					 List *tlist,
+					 bool need_sort_for_grouping,
+					 List *rollup_groupclauses,
+					 List *rollup_lists,
+					 AttrNumber *groupColIdx,
+					 AggClauseCosts *agg_costs,
+					 long numGroups,
 					 Plan *result_plan);
 
+static Plan *get_plan(Plan *plan, NodeTag type);
+static AttrNumber*get_sortIdx_from_subPlan(PlannerInfo *root, List *tlist);
+static List *make_partial_agg_tlist(List *tlist,List *groupClause);
+static List* add_qual_in_tlist(List *targetlist, List *qual);
+static bool add_qual_in_tlist_walker (Node *node,
+					 AddQualInTListExprContext *context);
+static Plan *build_hash_parallelagg(PlannerInfo *root,
+								   Query *parse,
+								   List *tlist,
+								   AggClauseCosts *aggcosts,
+								   int numGroupCols,
+								   AttrNumber *grpColIdx,
+								   long numGroups,
+								   Plan *lefttree);
 /*****************************************************************************
  *
  *	   Query optimizer entry point
@@ -1333,6 +1377,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 	double		dNumGroups = 0;
 	bool		use_hashed_distinct = false;
 	bool		tested_hashed_distinct = false;
+	bool		parallelagg_available = false;
 
 	/* Tweak caller-supplied tuple_fraction if have LIMIT/OFFSET */
 	if (parse->limitCount || parse->limitOffset)
@@ -1893,6 +1938,14 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 			result_plan = create_plan(root, best_path);
 			current_pathkeys = best_path->pathkeys;
 
+			if(enable_parallelagg
+					&& check_parallel_agg_available(result_plan,
+												tlist,
+												(List *)parse->havingQual))
+			{
+				parallelagg_available = true;
+			}
+
 			/* Detect if we'll need an explicit sort for grouping */
 			if (parse->groupClause && !use_hashed_grouping &&
 			  !pathkeys_contained_in(root->group_pathkeys, current_pathkeys))
@@ -1912,7 +1965,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 			 * the top plan node.  However, we can skip that if we determined
 			 * that whatever create_plan chose to return will be good enough.
 			 */
-			if (need_tlist_eval)
+			if (need_tlist_eval & !parallelagg_available)
 			{
 				/*
 				 * If the top-level plan node is one that cannot do expression
@@ -1984,18 +2037,56 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 			 */
 			if (use_hashed_grouping)
 			{
-				/* Hashed aggregate plan --- no sort needed */
-				result_plan = (Plan *) make_agg(root,
-												tlist,
-												(List *) parse->havingQual,
-												AGG_HASHED,
-												&agg_costs,
-												numGroupCols,
-												groupColIdx,
-									extract_grouping_ops(parse->groupClause),
-												NIL,
-												numGroups,
-												result_plan);
+				if(parallelagg_available)
+				{
+					Plan *parallelagg_plan;
+
+					parallelagg_plan = build_hash_parallelagg(root,
+													parse,
+													tlist,
+													&agg_costs,
+													numGroupCols,
+													groupColIdx,
+													numGroups,
+													result_plan);
+
+					if(!parallelagg_plan)
+					{
+						/* Hashed aggregate plan --- no sort needed */
+						result_plan = (Plan *) make_agg(root,
+														tlist,
+														(List *) parse->havingQual,
+														AGG_HASHED,
+														&agg_costs,
+														numGroupCols,
+														groupColIdx,
+											extract_grouping_ops(parse->groupClause),
+														NIL,
+														numGroups,
+														false,
+														true,
+														result_plan);
+					}
+					else
+						result_plan = parallelagg_plan;
+				}
+				else
+				{
+					/* Hashed aggregate plan --- no sort needed */
+					result_plan = (Plan *) make_agg(root,
+													tlist,
+													(List *) parse->havingQual,
+													AGG_HASHED,
+													&agg_costs,
+													numGroupCols,
+													groupColIdx,
+										extract_grouping_ops(parse->groupClause),
+													NIL,
+													numGroups,
+													false,
+													true,
+													result_plan);
+				}
 				/* Hashed aggregation produces randomly-ordered results */
 				current_pathkeys = NIL;
 			}
@@ -2012,7 +2103,25 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 				else
 					current_pathkeys = NIL;
 
-				result_plan = build_grouping_chain(root,
+
+				if(parallelagg_available)
+				{
+					Plan *parallelagg_plan;
+
+					parallelagg_plan = build_group_parallelagg(root,
+												   parse,
+												   tlist,
+												   need_sort_for_grouping,
+												   rollup_groupclauses,
+												   rollup_lists,
+												   groupColIdx,
+												   &agg_costs,
+												   numGroups,
+												   result_plan);
+
+					if(parallelagg_plan == NULL)
+					{
+						result_plan = build_grouping_chain(root,
 												   parse,
 												   tlist,
 												   need_sort_for_grouping,
@@ -2021,7 +2130,29 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 												   groupColIdx,
 												   &agg_costs,
 												   numGroups,
+												   false,
+												   true,
 												   result_plan);
+					}
+					else
+						result_plan = parallelagg_plan;
+
+				}
+				else
+				{
+					result_plan = build_grouping_chain(root,
+													   parse,
+													   tlist,
+													   need_sort_for_grouping,
+													   rollup_groupclauses,
+													   rollup_lists,
+													   groupColIdx,
+													   &agg_costs,
+													   numGroups,
+													   false,
+													   true,
+													   result_plan);
+				}
 
 				/*
 				 * these are destroyed by build_grouping_chain, so make sure
@@ -2306,6 +2437,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 								 extract_grouping_ops(parse->distinctClause),
 											NIL,
 											numDistinctRows,
+											false,
+											true,
 											result_plan);
 			/* Hashed aggregation produces randomly-ordered results */
 			current_pathkeys = NIL;
@@ -2473,10 +2606,16 @@ build_grouping_chain(PlannerInfo *root,
 					 AttrNumber *groupColIdx,
 					 AggClauseCosts *agg_costs,
 					 long numGroups,
+					 bool combineStates,
+					 bool finalizeAggs,
 					 Plan *result_plan)
 {
-	AttrNumber *top_grpColIdx = groupColIdx;
-	List	   *chain = NIL;
+	AttrNumber 	*top_grpColIdx = groupColIdx;
+	List	   	*chain = NIL;
+	List		*qual = NIL;
+
+	if(finalizeAggs)
+		qual = (List *) parse->havingQual;
 
 	/*
 	 * Prepare the grpColIdx for the real Agg node first, because we may need
@@ -2531,7 +2670,7 @@ build_grouping_chain(PlannerInfo *root,
 
 		agg_plan = (Plan *) make_agg(root,
 									 tlist,
-									 (List *) parse->havingQual,
+									 qual,
 									 AGG_SORTED,
 									 agg_costs,
 									 list_length(linitial(gsets)),
@@ -2539,6 +2678,8 @@ build_grouping_chain(PlannerInfo *root,
 									 extract_grouping_ops(groupClause),
 									 gsets,
 									 numGroups,
+									 combineStates,
+									 finalizeAggs,
 									 sort_plan);
 
 		sort_plan->lefttree = NULL;
@@ -2567,7 +2708,7 @@ build_grouping_chain(PlannerInfo *root,
 
 		result_plan = (Plan *) make_agg(root,
 										tlist,
-										(List *) parse->havingQual,
+										qual,
 								 (numGroupCols > 0) ? AGG_SORTED : AGG_PLAIN,
 										agg_costs,
 										numGroupCols,
@@ -2575,6 +2716,8 @@ build_grouping_chain(PlannerInfo *root,
 										extract_grouping_ops(groupClause),
 										gsets,
 										numGroups,
+										combineStates,
+										finalizeAggs,
 										result_plan);
 
 		((Agg *) result_plan)->chain = chain;
@@ -4704,3 +4847,476 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 
 	return (seqScanAndSortPath.total_cost < indexScanPath->path.total_cost);
 }
+
+/*
+ * check_parallel_agg_available
+ *		The function is used to check whether parallel_agg can be used by checking
+ * 		the agg functions and formulas.
+ *
+ * If there is any agg function indicating no-using parallel_agg or the agg function
+ * is used in a formula as a entity of targetlist then the parallel_agg can't be used
+ * and the function return false;
+ */
+static bool
+check_parallel_agg_available(Plan *plan, List *targetlist, List *qual)
+{
+	CheckParallelAggAvaiContext context;
+
+#ifndef PAGG_TEST
+	if(IsA(plan, Gather) == false)
+		return false;
+#endif
+
+	context.agguseparallel = true;
+
+	check_parallel_agg_available_walker((Node*)targetlist, &context);
+	if(context.agguseparallel == false)
+		return false;
+
+	check_parallel_agg_available_walker((Node*)qual, &context);
+	if(context.agguseparallel == false)
+		return false;
+
+	return true;
+}
+
+/*
+ * check_parallel_agg_available_walker
+ *		Go through the list in the node to check every agg function or formulas.
+ *
+ */
+static bool
+check_parallel_agg_available_walker (Node *node, CheckParallelAggAvaiContext *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Aggref))
+	{
+		HeapTuple	aggTuple;
+		Aggref		*aggref = (Aggref *) node;
+		Form_pg_aggregate aggform;
+
+
+		aggTuple = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(aggref->aggfnoid));
+
+		if (!HeapTupleIsValid(aggTuple))
+			elog(ERROR, "cache lookup failed for parallel aggregate %u",
+				 aggref->aggfnoid);
+
+		aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
+
+		ReleaseSysCache(aggTuple);
+
+		if(!OidIsValid(aggform->aggcombinefn))
+		{
+			context->agguseparallel = false;
+			return true;
+		}
+
+		/*
+		 * If Distinct word in parameters in aggfunction, then the parallel-agg
+		 * policy will not be used.
+		 * */
+		if(aggref->aggdistinct != NULL)
+		{
+			context->agguseparallel = false;
+			return true;
+		}
+	}
+	else
+		return expression_tree_walker(node, check_parallel_agg_available_walker, context);
+
+	return false;
+}
+
+/*
+ * This function build a group parallelagg plan as result_plan as following :
+ * Finalize Group Aggregate
+ *   ->  Sort
+ * 	   -> Gather
+ * 	     -> Partial Group Aggregate
+ * 	       -> Sort
+ * 	         -> Partial Seq Scan
+ * The input result_plan will be
+ * Gather
+ *  -> Partial Seq Scan
+ * So this function will do the following steps:
+ * 1. Move up the Gather node and change its targetlist
+ * 2. Change the Group Aggregate to be Partial Group Aggregate
+ * 3. Add Finalize Group Aggregate and Sort node
+ */
+static Plan *
+build_group_parallelagg(PlannerInfo *root,
+					 Query *parse,
+					 List *tlist,
+					 bool need_sort_for_grouping,
+					 List *rollup_groupclauses,
+					 List *rollup_lists,
+					 AttrNumber *groupColIdx,
+					 AggClauseCosts *agg_costs,
+					 long numGroups,
+					 Plan *result_plan)
+{
+	Plan *parallel_seqscan = NULL;
+	Plan *partial_agg = NULL;
+	Gather *gather_plan = NULL;
+	List *qual = (List*)parse->havingQual;
+	List *partial_agg_tlist = NULL;
+
+	AttrNumber *topsortIdx = NULL;
+
+	gather_plan = (Gather*)get_plan(result_plan, T_Gather);
+	if(gather_plan == NULL)
+		return NULL;
+
+	/* Get the partial seqscan */
+	parallel_seqscan =gather_plan->plan.lefttree;
+//			get_plan((Plan*)gather_plan, T_PartialSeqScan);
+
+	/*
+	 * The underlying Agg targetlist should be a flat tlist of all Vars and Aggs
+	 * needed to evaluate the expressions and final values of aggregates present
+	 * in the main target list. The quals also should be included.
+	 */
+	partial_agg_tlist = make_partial_agg_tlist(add_qual_in_tlist(tlist, qual),
+												llast(rollup_groupclauses));
+
+	/* Add PartialAgg and Sort node above Partialseqscan*/
+	partial_agg = build_grouping_chain(root,
+								   parse,
+								   partial_agg_tlist,
+								   need_sort_for_grouping,
+								   rollup_groupclauses,
+								   rollup_lists,
+								   groupColIdx,
+								   agg_costs,
+								   numGroups,
+								   false,
+								   false,
+								   parallel_seqscan);
+
+
+
+	/* Let the Gather node as upper node of partial_agg node */
+	gather_plan->plan.targetlist = partial_agg->targetlist;
+	gather_plan->plan.lefttree = partial_agg;
+
+	/*
+	 * Get the sortIndex according the subplan
+	 */
+	topsortIdx = get_sortIdx_from_subPlan(root,partial_agg_tlist);
+
+	 /* Make the Finalize Group Aggregate node */
+	result_plan = build_grouping_chain(root,
+								   parse,
+								   tlist,
+								   need_sort_for_grouping,
+								   rollup_groupclauses,
+								   rollup_lists,
+								   topsortIdx,
+								   agg_costs,
+								   numGroups,
+								   true,
+								   true,
+								   (Plan*)gather_plan);
+
+	return result_plan;
+}
+
+/*
+ * This function try to find the type of sub plan in the plan and return it.
+ *
+ * If not found, return NULL. Otherwise return the subplan.
+ */
+static Plan *
+get_plan(Plan *plan, NodeTag type)
+{
+	if(plan == NULL)
+		return NULL;
+	else if(nodeTag(plan) == type)
+		return plan;
+	else
+		return get_plan(plan->lefttree, type);
+}
+
+
+static AttrNumber*
+get_sortIdx_from_subPlan(PlannerInfo *root, List *tlist)
+{
+	Query	   *parse = root->parse;
+	int			numCols;
+
+	AttrNumber *grpColIdx = NULL;
+
+	numCols = list_length(parse->groupClause);
+	if (numCols > 0)
+	{
+		ListCell   *tl;
+
+		grpColIdx = (AttrNumber *) palloc0(sizeof(AttrNumber) * numCols);
+
+		foreach(tl, tlist)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(tl);
+			int			colno;
+
+			colno = get_grouping_column_index(parse, tle);
+			if (colno >= 0)
+			{
+				Assert(grpColIdx[colno] == 0);	/* no dups expected */
+				grpColIdx[colno] = tle->resno;
+			}
+		}
+	}
+
+	return grpColIdx;
+}
+
+/*
+ * make_partial_agg_tlist
+ *	  Generate appropriate Agg node target list for input to ParallelAgg nodes.
+ *
+ * The initial target list passed to ParallelAgg node from the parser contains
+ * aggregates and GROUP BY columns. For the underlying agg node, we want to
+ * generate a tlist containing bare aggregate references (Aggref) and GROUP BY
+ * expressions. So we flatten all expressions except GROUP BY items into their
+ * component variables.
+ * For example, given a query like
+ *		SELECT a+b, 2 * SUM(c+d) , AVG(d)+SUM(c+d) FROM table GROUP BY a+b;
+ * we want to pass this targetlist to the Agg plan:
+ *		a+b, SUM(c+d), AVG(d)
+ * where the a+b target will be used by the Sort/Group steps, and the
+ * other targets will be used for computing the final results.
+ * Note that we don't flatten Aggref's , since those are to be computed
+ * by the underlying Agg node, and they will be referenced like Vars above it.
+ *
+ * 'tlist' is the ParallelAgg's final target list.
+ *
+ * The result is the targetlist to be computed by the Agg node below the
+ * ParallelAgg node.
+ */
+static List *
+make_partial_agg_tlist(List *tlist,List *groupClause)
+{
+	Bitmapset  *sgrefs;
+	List	   *new_tlist;
+	List	   *flattenable_cols;
+	List	   *flattenable_vars;
+	ListCell   *lc;
+
+	/*
+	 * Collect the sortgroupref numbers of GROUP BY clauses
+	 * into a bitmapset for convenient reference below.
+	 */
+	sgrefs = NULL;
+
+	/* Add in sortgroupref numbers of GROUP BY clauses */
+	foreach(lc, groupClause)
+	{
+		SortGroupClause *grpcl = (SortGroupClause *) lfirst(lc);
+
+		sgrefs = bms_add_member(sgrefs, grpcl->tleSortGroupRef);
+	}
+
+	/*
+	 * Construct a tlist containing all the non-flattenable tlist items, and
+	 * save aside the others for a moment.
+	 */
+	new_tlist = NIL;
+	flattenable_cols = NIL;
+
+	foreach(lc, tlist)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+		/* Don't want to deconstruct GROUP BY items. */
+		if (tle->ressortgroupref != 0 &&
+			bms_is_member(tle->ressortgroupref, sgrefs))
+		{
+			/* Don't want to deconstruct this value, so add to new_tlist */
+			TargetEntry *newtle;
+
+			newtle = makeTargetEntry(tle->expr,
+									 list_length(new_tlist) + 1,
+									 NULL,
+									 false);
+			/* Preserve its sortgroupref marking, in case it's volatile */
+			newtle->ressortgroupref = tle->ressortgroupref;
+			new_tlist = lappend(new_tlist, newtle);
+		}
+		else
+		{
+			/*
+			 * Column is to be flattened, so just remember the expression for
+			 * later call to pull_var_clause.  There's no need for
+			 * pull_var_clause to examine the TargetEntry node itself.
+			 */
+			flattenable_cols = lappend(flattenable_cols, tle->expr);
+		}
+	}
+
+	/*
+	 * Pull out all the Vars and Aggrefs mentioned in flattenable columns, and
+	 * add them to the result tlist if not already present.  (Some might be
+	 * there already because they're used directly as group clauses.)
+	 *
+	 * Note: it's essential to use PVC_INCLUDE_AGGREGATES here, so that the
+	 * Aggrefs are placed in the Agg node's tlist and not left to be computed
+	 * at higher levels.
+	 */
+	flattenable_vars = pull_var_clause((Node *) flattenable_cols,
+									   PVC_INCLUDE_AGGREGATES,
+									   PVC_INCLUDE_PLACEHOLDERS);
+	new_tlist = add_to_flat_tlist(new_tlist, flattenable_vars);
+
+	/* clean up cruft */
+	list_free(flattenable_vars);
+	list_free(flattenable_cols);
+
+	return new_tlist;
+}
+
+/*
+ * add_qual_in_tlist
+ *		Add the agg functions in qual into the target list used in agg plan
+ */
+static List*
+add_qual_in_tlist(List *targetlist, List *qual)
+{
+	AddQualInTListExprContext context;
+
+	if(qual == NULL)
+		return targetlist;
+
+	context.targetlist = copyObject(targetlist);
+	context.resno = list_length(context.targetlist) + 1;;
+
+	add_qual_in_tlist_walker((Node*)qual, &context);
+
+	return context.targetlist;
+}
+
+/*
+ * add_qual_in_tlist_walker
+ *		Go through the qual list to get the aggref and add it in targetlist
+ */
+static bool
+add_qual_in_tlist_walker (Node *node, AddQualInTListExprContext *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Aggref))
+	{
+		List *tlist = context->targetlist;
+//		Aggref *aggref = (Aggref *)node;
+
+		TargetEntry *te = makeNode(TargetEntry);
+
+//		aggref->resno = context->resno;
+
+		te = makeTargetEntry((Expr *) node,
+				  context->resno++,
+				  NULL,
+				  false);
+
+		tlist = lappend(tlist,te);
+	}
+	else
+		return expression_tree_walker(node, add_qual_in_tlist_walker, context);
+
+	return false;
+}
+
+/*
+ * This function build a hasg parallelagg plan as result_plan as following :
+ * Finalize Hash Aggregate
+ *   -> Gather
+ *     -> Partial Hash Aggregate
+ *       -> Partial Seq Scan
+ * The input result_plan will be
+ * Gather
+ *  -> Partial Seq Scan
+ * So this function will do the following steps:
+ * 1. Make a PartialHashAgg and set Gather node as above node
+ * 2. Change the targetlist of Gather node
+ * 3. Make a FinalizeHashAgg as top node above the Gather node
+ */
+
+static Plan *
+build_hash_parallelagg(PlannerInfo *root,
+					   Query *parse,
+					   List *tlist,
+					   AggClauseCosts *aggcosts,
+					   int numGroupCols,
+					   AttrNumber *grpColIdx,
+					   long numGroups,
+					   Plan *lefttree)
+{
+	Plan *result_plan = NULL;
+	Plan *parallel_seqscan = NULL;
+	Plan *partial_agg_plan = NULL;
+	Plan *gather_plan = NULL;
+	List *partial_agg_tlist = NIL;
+	List *qual = (List*)parse->havingQual;
+
+	AttrNumber *topsortIdx = NULL;
+
+	gather_plan = get_plan(lefttree, T_Gather);
+	if(gather_plan == NULL)
+		return NULL;
+
+	/* Get the partial seqscan */
+	parallel_seqscan = gather_plan->lefttree;
+	if(parallel_seqscan == NULL)
+		return NULL;
+
+	/*
+	 * The underlying Agg targetlist should be a flat tlist of all Vars and Aggs
+	 * needed to evaluate the expressions and final values of aggregates present
+	 * in the main target list. The quals also should be included.
+	 */
+	partial_agg_tlist = make_partial_agg_tlist(add_qual_in_tlist(tlist, qual),
+												parse->groupClause);
+
+	/* Make PartialHashAgg plan node */
+	partial_agg_plan = (Plan *) make_agg(root,
+									partial_agg_tlist,
+									NULL,
+									AGG_HASHED,
+									aggcosts,
+									numGroupCols,
+									grpColIdx,
+						extract_grouping_ops(parse->groupClause),
+									NIL,
+									numGroups,
+									false,
+									false,
+									parallel_seqscan);
+
+	gather_plan->lefttree = partial_agg_plan;
+	gather_plan->targetlist = partial_agg_plan->targetlist;
+
+	/*
+	 * Get the sortIndex according the subplan
+	 */
+	topsortIdx = get_sortIdx_from_subPlan(root,partial_agg_tlist);
+
+	/* Make FinalizeHashAgg plan node */
+	result_plan = (Plan *) make_agg(root,
+									tlist,
+									(List *) parse->havingQual,
+									AGG_HASHED,
+									aggcosts,
+									numGroupCols,
+									topsortIdx,
+						extract_grouping_ops(parse->groupClause),
+									NIL,
+									numGroups,
+									true,
+									true,
+									gather_plan);
+
+	return result_plan;
+}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 12e9290..78cfae9 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -140,6 +140,14 @@ static bool fix_opfuncids_walker(Node *node, void *context);
 static bool extract_query_dependencies_walker(Node *node,
 								  PlannerInfo *context);
 
+static void set_agg_references(PlannerInfo *root, Plan *plan, int rtoffset);
+static Node *fix_combine_agg_expr(PlannerInfo *root,
+			   Node *node,
+			   indexed_tlist *subplan_itlist,
+			   Index newvarno,
+			   int rtoffset);
+static Node * fix_combine_agg_expr_mutator(Node *node, fix_upper_expr_context *context);
+
 /*****************************************************************************
  *
  *		SUBPLAN REFERENCES
@@ -668,7 +676,8 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 		case T_Agg:
-			set_upper_references(root, plan, rtoffset);
+//			set_upper_references(root, plan, rtoffset);
+			set_agg_references(root, plan, rtoffset);
 			break;
 		case T_Group:
 			set_upper_references(root, plan, rtoffset);
@@ -2432,3 +2441,212 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
 	return expression_tree_walker(node, extract_query_dependencies_walker,
 								  (void *) context);
 }
+
+
+/*
+ * set_upper_references
+ *	  Update the targetlist and quals of an upper-level plan node
+ *	  to refer to the tuples returned by its lefttree subplan.
+ *	  Also perform opcode lookup for these expressions, and
+ *	  add regclass OIDs to root->glob->relationOids.
+ *
+ * This is used for single-input plan types like Agg, Group, Result.
+ *
+ * In most cases, we have to match up individual Vars in the tlist and
+ * qual expressions with elements of the subplan's tlist (which was
+ * generated by flatten_tlist() from these selfsame expressions, so it
+ * should have all the required variables).  There is an important exception,
+ * however: GROUP BY and ORDER BY expressions will have been pushed into the
+ * subplan tlist unflattened.  If these values are also needed in the output
+ * then we want to reference the subplan tlist element rather than recomputing
+ * the expression.
+ */
+static void
+set_agg_references(PlannerInfo *root, Plan *plan, int rtoffset)
+{
+	Agg 	   *agg = (Agg*)plan;
+	Plan	   *subplan = plan->lefttree;
+	indexed_tlist *subplan_itlist;
+	List	   *output_targetlist;
+	ListCell   *l;
+
+	subplan_itlist = build_tlist_index(subplan->targetlist);
+
+	output_targetlist = NIL;
+
+	if(agg->combineStates)
+	{
+		foreach(l, plan->targetlist)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(l);
+			Node	   *newexpr;
+
+			/* If it's a non-Var sort/group item, first try to match by sortref */
+			if (tle->ressortgroupref != 0 && !IsA(tle->expr, Var))
+			{
+				newexpr = (Node *)
+					search_indexed_tlist_for_sortgroupref((Node *) tle->expr,
+														  tle->ressortgroupref,
+														  subplan_itlist,
+														  OUTER_VAR);
+				if (!newexpr)
+					newexpr = fix_combine_agg_expr(root,
+												 (Node *) tle->expr,
+												 subplan_itlist,
+												 OUTER_VAR,
+												 rtoffset);
+			}
+			else
+				newexpr = fix_combine_agg_expr(root,
+											 (Node *) tle->expr,
+											 subplan_itlist,
+											 OUTER_VAR,
+											 rtoffset);
+			tle = flatCopyTargetEntry(tle);
+			tle->expr = (Expr *) newexpr;
+			output_targetlist = lappend(output_targetlist, tle);
+		}
+	}
+	else
+	{
+		foreach(l, plan->targetlist)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(l);
+			Node	   *newexpr;
+
+			/* If it's a non-Var sort/group item, first try to match by sortref */
+			if (tle->ressortgroupref != 0 && !IsA(tle->expr, Var))
+			{
+				newexpr = (Node *)
+					search_indexed_tlist_for_sortgroupref((Node *) tle->expr,
+														  tle->ressortgroupref,
+														  subplan_itlist,
+														  OUTER_VAR);
+				if (!newexpr)
+					newexpr = fix_upper_expr(root,
+											 (Node *) tle->expr,
+											 subplan_itlist,
+											 OUTER_VAR,
+											 rtoffset);
+			}
+			else
+				newexpr = fix_upper_expr(root,
+										 (Node *) tle->expr,
+										 subplan_itlist,
+										 OUTER_VAR,
+										 rtoffset);
+			tle = flatCopyTargetEntry(tle);
+			tle->expr = (Expr *) newexpr;
+			output_targetlist = lappend(output_targetlist, tle);
+		}
+	}
+
+	plan->targetlist = output_targetlist;
+
+	plan->qual = (List *)
+		fix_upper_expr(root,
+					   (Node *) plan->qual,
+					   subplan_itlist,
+					   OUTER_VAR,
+					   rtoffset);
+
+	pfree(subplan_itlist);
+}
+
+
+/*
+ * This function is only used by combineAgg to set the Var nodes as args of
+ * Aggref reference output of a Gather plan.
+ */
+static Node *
+fix_combine_agg_expr(PlannerInfo *root,
+			   Node *node,
+			   indexed_tlist *subplan_itlist,
+			   Index newvarno,
+			   int rtoffset)
+{
+	fix_upper_expr_context context;
+
+	context.root = root;
+	context.subplan_itlist = subplan_itlist;
+	context.newvarno = newvarno;
+	context.rtoffset = rtoffset;
+	return fix_combine_agg_expr_mutator(node, &context);
+}
+
+static Node *
+fix_combine_agg_expr_mutator(Node *node, fix_upper_expr_context *context)
+{
+	Var		   *newvar;
+
+	if (node == NULL)
+		return NULL;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+
+		newvar = search_indexed_tlist_for_var(var,
+											  context->subplan_itlist,
+											  context->newvarno,
+											  context->rtoffset);
+		if (!newvar)
+			elog(ERROR, "variable not found in subplan target list");
+		return (Node *) newvar;
+	}
+	if (IsA(node, Aggref))
+	{
+		TargetEntry *tle;
+		Aggref		*aggref = (Aggref*)node;
+		List	    *args = NIL;
+
+		tle = tlist_member(node, context->subplan_itlist->tlist);
+		if (tle)
+		{
+			/* Found a matching subplan output expression */
+			Var		   *newvar;
+			TargetEntry *newtle;
+
+			newvar = makeVarFromTargetEntry(context->newvarno, tle);
+			newvar->varnoold = 0;	/* wasn't ever a plain Var */
+			newvar->varoattno = 0;
+
+			/* update the args in the aggref */
+
+			/* makeTargetEntry ,always set resno to one for finialize agg */
+			newtle = makeTargetEntry((Expr*)newvar,1,NULL,false);
+			args = lappend(args,newtle);
+
+			/*
+			 * Updated the args, let the newvar refer to the right position of
+			 * the agg function in the subplan
+			 */
+			aggref->args = args;
+
+			return (Node *) aggref;
+		}
+	}
+	if (IsA(node, PlaceHolderVar))
+	{
+		PlaceHolderVar *phv = (PlaceHolderVar *) node;
+
+		/* See if the PlaceHolderVar has bubbled up from a lower plan node */
+		if (context->subplan_itlist->has_ph_vars)
+		{
+			newvar = search_indexed_tlist_for_non_var((Node *) phv,
+													  context->subplan_itlist,
+													  context->newvarno);
+			if (newvar)
+				return (Node *) newvar;
+		}
+		/* If not supplied by input plan, evaluate the contained expr */
+		return fix_upper_expr_mutator((Node *) phv->phexpr, context);
+	}
+	if (IsA(node, Param))
+		return fix_param_node(context->root, (Param *) node);
+
+	fix_expr_common(context->root, node);
+	return expression_tree_mutator(node,
+								   fix_combine_agg_expr_mutator,
+								   (void *) context);
+}
+
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 2e55131..45de122 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.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 2c45bd6..96a7386 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 a185749..63cde6b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -828,6 +828,15 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
+		{"enable_parallelagg", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enables the planner's use of parallel agg plans."),
+			NULL
+		},
+		&enable_parallelagg,
+		true,
+		NULL, NULL, NULL
+	},
+	{
 		{"enable_material", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of materialization."),
 			NULL
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 36863df..cb39107 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12279,6 +12279,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;
@@ -12295,6 +12296,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;
@@ -12325,7 +12327,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, "
@@ -12435,6 +12456,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");
@@ -12452,6 +12474,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);
@@ -12540,6 +12563,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 5ccf470..4243c0b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1851,6 +1851,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 37086c6..9ae2a1b 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -726,6 +726,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/optimizer/cost.h b/src/include/optimizer/cost.h
index ac21a3a..9bd6b07 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -62,6 +62,7 @@ extern bool enable_bitmapscan;
 extern bool enable_tidscan;
 extern bool enable_sort;
 extern bool enable_hashagg;
+extern bool enable_parallelagg;
 extern bool enable_nestloop;
 extern bool enable_material;
 extern bool enable_mergejoin;
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index f96e9ee..2989eac 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -60,9 +60,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/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..56643f2 100644
--- a/src/test/regress/expected/create_aggregate.out
+++ b/src/test/regress/expected/create_aggregate.out
@@ -101,6 +101,23 @@ CREATE AGGREGATE sumdouble (float8)
     msfunc = float8pl,
     minvfunc = float8mi
 );
+-- aggregate combine functions
+CREATE AGGREGATE mymax (int)
+(
+	stype = int4,
+	sfunc = int4larger,
+	cfunc = int4larger
+);
+-- Ensure all these functions made it into the catalog
+SELECT aggfnoid,aggtransfn,aggcombinefn,aggtranstype
+FROM pg_aggregate
+WHERE aggfnoid = 'mymax'::REGPROC;
+ aggfnoid | aggtransfn | aggcombinefn | aggtranstype 
+----------+------------+--------------+--------------
+ mymax    | int4larger | int4larger   |           23
+(1 row)
+
+DROP AGGREGATE mymax (int);
 -- 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/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql
index 0ec1572..0070382 100644
--- a/src/test/regress/sql/create_aggregate.sql
+++ b/src/test/regress/sql/create_aggregate.sql
@@ -115,6 +115,21 @@ CREATE AGGREGATE sumdouble (float8)
     minvfunc = float8mi
 );
 
+-- aggregate combine functions
+CREATE AGGREGATE mymax (int)
+(
+	stype = int4,
+	sfunc = int4larger,
+	cfunc = int4larger
+);
+
+-- Ensure all these functions made it into the catalog
+SELECT aggfnoid,aggtransfn,aggcombinefn,aggtranstype
+FROM pg_aggregate
+WHERE aggfnoid = 'mymax'::REGPROC;
+
+DROP AGGREGATE mymax (int);
+
 -- invalid: nonstrict inverse with strict forward function
 
 CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
