diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index ffff3c0..cfd0c35 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -350,16 +350,21 @@ cost_samplescan(Path *path, PlannerInfo *root,
  *
  * 'rel' is the relation to be operated upon
  * 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
+ * 'rows' may be used to point to a row estimate, this may be used when a rel
+ * is unavailable to retrieve row estimates from.
  */
 void
 cost_gather(GatherPath *path, PlannerInfo *root,
-			RelOptInfo *rel, ParamPathInfo *param_info)
+			RelOptInfo *rel, ParamPathInfo *param_info,
+			double *rows)
 {
 	Cost		startup_cost = 0;
 	Cost		run_cost = 0;
 
 	/* Mark the path with the correct row estimate */
-	if (param_info)
+	if (rows)
+		path->path.rows = *rows;
+	else if (param_info)
 		path->path.rows = param_info->ppi_rows;
 	else
 		path->path.rows = rel->rows;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 88c7279..c1deb32 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -1536,8 +1536,8 @@ create_agg_plan(PlannerInfo *root, AggPath *best_path)
 
 	plan = make_agg(tlist, quals,
 					best_path->aggstrategy,
-					false,
-					true,
+					best_path->combineStates,
+					best_path->finalizeAggs,
 					list_length(best_path->groupClause),
 					extract_grouping_cols(best_path->groupClause,
 										  subplan->targetlist),
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5fc8e5b..9352238 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1688,6 +1688,16 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 			}
 		}
 
+		/* Likewise for any partial paths. */
+		foreach(lc, current_rel->partial_pathlist)
+		{
+			Path	   *subpath = (Path *) lfirst(lc);
+
+			Assert(subpath->param_info == NULL);
+			lfirst(lc) = apply_projection_to_path(root, current_rel,
+											subpath, sub_target);
+		}
+
 		/*
 		 * Determine the tlist we need grouping paths to emit.  While we could
 		 * skip this if we're not going to call create_grouping_paths, it's
@@ -3102,6 +3112,10 @@ create_grouping_paths(PlannerInfo *root,
 	AggClauseCosts agg_costs;
 	double		dNumGroups;
 	bool		allow_hash;
+	bool		can_hash;
+	bool		can_sort;
+	bool		can_parallel;
+
 	ListCell   *lc;
 
 	/* For now, do all work in the (GROUP_AGG, NULL) upperrel */
@@ -3195,12 +3209,41 @@ create_grouping_paths(PlannerInfo *root,
 									  rollup_groupclauses);
 
 	/*
+	 * Here we consider performing aggregation in parallel using multiple
+	 * worker processes. We can permit this when there's at least one
+	 * partial_path in input_rel, but not if the query has grouping sets,
+	 * (although this likely just requires a bit more thought). We also
+	 * disallow parallel mode when the target list contains any volatile
+	 * functions, as this would cause a multiple evaluation hazard.
+	 */
+	can_parallel = false;
+
+	if ((parse->hasAggs || parse->groupClause != NIL) &&
+		input_rel->partial_pathlist != NIL &&
+		parse->groupingSets == NIL &&
+		!contain_volatile_functions((Node *) target->exprs))
+	{
+		/*
+		 * Check that all aggregate functions support partial mode,
+		 * however if there are no aggregate functions then we can skip
+		 * this check.
+		 */
+		if (!parse->hasAggs)
+			can_parallel = true;
+		else if (aggregates_allow_partial((Node *) target->exprs) == PAT_ANY &&
+				 aggregates_allow_partial(root->parse->havingQual) == PAT_ANY)
+			can_parallel = true;
+	}
+
+	/*
 	 * Consider sort-based implementations of grouping, if possible.  (Note
 	 * that if groupClause is empty, grouping_is_sortable() is trivially true,
 	 * and all the pathkeys_contained_in() tests will succeed too, so that
 	 * we'll consider every surviving input path.)
 	 */
-	if (grouping_is_sortable(parse->groupClause))
+	can_sort = grouping_is_sortable(parse->groupClause);
+
+	if (can_sort)
 	{
 		/*
 		 * Use any available suitably-sorted path as input, and also consider
@@ -3257,7 +3300,9 @@ create_grouping_paths(PlannerInfo *root,
 											 parse->groupClause,
 											 (List *) parse->havingQual,
 											 &agg_costs,
-											 dNumGroups));
+											 dNumGroups,
+											 false,
+											 true));
 				}
 				else if (parse->groupClause)
 				{
@@ -3281,6 +3326,45 @@ create_grouping_paths(PlannerInfo *root,
 				}
 			}
 		}
+		if (can_parallel)
+		{
+			AggStrategy aggstrategy;
+
+			if (list_length(parse->groupClause) > 0)
+				aggstrategy = AGG_SORTED;
+			else
+				aggstrategy = AGG_PLAIN;
+
+			foreach(lc, input_rel->partial_pathlist)
+			{
+				Path	   *path = (Path *) lfirst(lc);
+				bool		is_sorted;
+				int			parallel_degree = path->parallel_degree;
+
+				/*
+				 * XXX is this wasted effort? Currently no partial paths
+				 * are sorted.
+				 */
+				is_sorted = pathkeys_contained_in(root->group_pathkeys,
+													path->pathkeys);
+				if (!is_sorted)
+					path = (Path *) create_sort_path(root,
+													grouped_rel,
+													path,
+													root->group_pathkeys,
+													-1.0);
+				add_path(grouped_rel, (Path *)
+							create_parallelagg_path(root, grouped_rel,
+													path,
+													target,
+													aggstrategy,
+													aggstrategy,
+													parse->groupClause,
+													(List *) parse->havingQual,
+													&agg_costs,
+													dNumGroups));
+			}
+		}
 	}
 
 	/*
@@ -3329,7 +3413,9 @@ create_grouping_paths(PlannerInfo *root,
 		}
 	}
 
-	if (allow_hash && grouping_is_hashable(parse->groupClause))
+	can_hash = allow_hash && grouping_is_hashable(parse->groupClause);
+
+	if (can_hash)
 	{
 		/*
 		 * We just need an Agg over the cheapest-total input path, since input
@@ -3343,7 +3429,82 @@ create_grouping_paths(PlannerInfo *root,
 								 parse->groupClause,
 								 (List *) parse->havingQual,
 								 &agg_costs,
-								 dNumGroups));
+								 dNumGroups,
+								 false,
+								 true));
+
+		if (can_parallel)
+		{
+			/*
+			 * Consider parallel hash aggregate for each partial path.
+			 * XXX Should we fetch the cheapest of these and just consider that
+			 * one?
+			 */
+			foreach(lc, input_rel->partial_pathlist)
+			{
+				Path	   *path = (Path *) lfirst(lc);
+
+				add_path(grouped_rel, (Path *)
+						 create_parallelagg_path(root, grouped_rel,
+												 path,
+												 target,
+												 AGG_HASHED,
+												 AGG_HASHED,
+												 parse->groupClause,
+												 (List *) parse->havingQual,
+												 &agg_costs,
+												 dNumGroups));
+			}
+		}
+	}
+
+	/*
+	 * For parallel aggregation, since this happens in 2 phases we'll also try
+	 * a mixing the aggregate stragegies to see if that'll bring the cost down
+	 * any.
+	 */
+	if (can_parallel && can_hash && can_sort)
+	{
+		Assert(parse->groupClause == NIL);
+
+		foreach(lc, input_rel->partial_pathlist)
+		{
+			Path	   *path = (Path *) lfirst(lc);
+			bool		is_sorted;
+
+			/* Try hashing in the partial phase, and sorting in the final */
+			add_path(grouped_rel, (Path *)
+						create_parallelagg_path(root, grouped_rel,
+												path,
+												target,
+												AGG_HASHED,
+												AGG_SORTED,
+												parse->groupClause,
+												(List *) parse->havingQual,
+												&agg_costs,
+												dNumGroups));
+
+			is_sorted = pathkeys_contained_in(root->group_pathkeys,
+												path->pathkeys);
+			if (!is_sorted)
+				path = (Path *) create_sort_path(root,
+												grouped_rel,
+												path,
+												root->group_pathkeys,
+												-1.0);
+
+			/* Try sorting in the partial phase, and hashing in the final */
+			add_path(grouped_rel, (Path *)
+						create_parallelagg_path(root, grouped_rel,
+												path,
+												target,
+												AGG_SORTED,
+												AGG_HASHED,
+												parse->groupClause,
+												(List *) parse->havingQual,
+												&agg_costs,
+												dNumGroups));
+		}
 	}
 
 	/* Give a helpful error if we failed to find any implementation */
@@ -3666,7 +3827,9 @@ create_distinct_paths(PlannerInfo *root,
 								 parse->distinctClause,
 								 NIL,
 								 NULL,
-								 numDistinctRows));
+								 numDistinctRows,
+								 false,
+								 true));
 	}
 
 	/* Give a helpful error if we failed to find any implementation */
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index d296d09..a4c40ee 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -15,7 +15,9 @@
  */
 #include "postgres.h"
 
+#include "access/htup_details.h"
 #include "access/transam.h"
+#include "catalog/pg_aggregate.h"
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -139,6 +141,16 @@ static List *set_returning_clause_references(PlannerInfo *root,
 static bool fix_opfuncids_walker(Node *node, void *context);
 static bool extract_query_dependencies_walker(Node *node,
 								  PlannerInfo *context);
+static void set_combineagg_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);
+static void set_partialagg_aggref_types(PlannerInfo *root, Plan *plan);
 
 /*****************************************************************************
  *
@@ -667,8 +679,24 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 		case T_Agg:
-			set_upper_references(root, plan, rtoffset);
-			break;
+			{
+				Agg *aggplan = (Agg *) plan;
+
+				/*
+				 * For partial aggregation we must adjust the return types of
+				 * the Aggrefs
+				 * XXX TODO, this is broken.
+				 */
+				//if (!aggplan->finalizeAggs)
+				//	set_partialagg_aggref_types(root, plan);
+
+				if (aggplan->combineStates)
+					set_combineagg_references(root, plan, rtoffset);
+				else
+					set_upper_references(root, plan, rtoffset);
+
+				break;
+			}
 		case T_Group:
 			set_upper_references(root, plan, rtoffset);
 			break;
@@ -2478,3 +2506,188 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
 	return expression_tree_walker(node, extract_query_dependencies_walker,
 								  (void *) context);
 }
+
+static void
+set_combineagg_references(PlannerInfo *root, Plan *plan, int rtoffset)
+{
+	Plan	   *subplan = plan->lefttree;
+	indexed_tlist *subplan_itlist;
+	List	   *output_targetlist;
+	ListCell   *l;
+
+	Assert(IsA(plan, Agg));
+	Assert(((Agg *) plan)->combineStates);
+
+	subplan_itlist = build_tlist_index(subplan->targetlist);
+
+	output_targetlist = NIL;
+
+	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);
+	}
+
+	plan->targetlist = output_targetlist;
+
+	plan->qual = (List *)
+		fix_upper_expr(root,
+					   (Node *) plan->qual,
+					   subplan_itlist,
+					   OUTER_VAR,
+					   rtoffset);
+
+	pfree(subplan_itlist);
+}
+
+
+/*
+ * Adjust the Aggref'a args to reference the correct Aggref target in the outer
+ * subplan.
+ */
+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;
+
+		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);
+
+			/*
+			 * Updated the args, let the newvar refer to the right position of
+			 * the agg function in the subplan
+			 */
+			aggref->args = list_make1(newtle);
+
+			return (Node *) aggref;
+		}
+		else
+			elog(ERROR, "aggref not found in subplan target list");
+	}
+	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);
+}
+
+/* XXX is this really the best place and way to do this? */
+static void
+set_partialagg_aggref_types(PlannerInfo *root, Plan *plan)
+{
+	ListCell *l;
+
+	foreach(l, plan->targetlist)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+		if (IsA(tle->expr, Aggref))
+		{
+			Aggref *aggref = (Aggref *) tle->expr;
+			HeapTuple	aggTuple;
+			Form_pg_aggregate aggform;
+
+			aggTuple = SearchSysCache1(AGGFNOID,
+									   ObjectIdGetDatum(aggref->aggfnoid));
+			if (!HeapTupleIsValid(aggTuple))
+				elog(ERROR, "cache lookup failed for aggregate %u",
+					 aggref->aggfnoid);
+			aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
+
+			aggref->aggtype = aggform->aggtranstype;
+
+			ReleaseSysCache(aggTuple);
+		}
+	}
+}
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 6ea3319..fb139af 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -859,7 +859,9 @@ make_union_unique(SetOperationStmt *op, Path *path, List *tlist,
 										groupList,
 										NIL,
 										NULL,
-										dNumGroups);
+										dNumGroups,
+										false,
+										true);
 	}
 	else
 	{
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 6ac25dc..ff8ac19 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -52,6 +52,10 @@
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
+typedef struct
+{
+	PartialAggType allowedtype;
+} partial_agg_context;
 
 typedef struct
 {
@@ -93,6 +97,7 @@ typedef struct
 	bool		allow_restricted;
 } has_parallel_hazard_arg;
 
+static bool partial_aggregate_walker(Node *node, partial_agg_context *context);
 static bool contain_agg_clause_walker(Node *node, void *context);
 static bool count_agg_clauses_walker(Node *node,
 						 count_agg_clauses_context *context);
@@ -400,6 +405,81 @@ make_ands_implicit(Expr *clause)
  *****************************************************************************/
 
 /*
+ * aggregates_allow_partial
+ *		Recursively search for Aggref clauses and determine the maximum
+ *		'degree' of partial aggregation which can be supported. Partial
+ *		aggregation requires that each aggregate does not have a DISTINCT or
+ *		ORDER BY clause, and that it also has a combine function set.
+ */
+PartialAggType
+aggregates_allow_partial(Node *clause)
+{
+	partial_agg_context context;
+
+	/* initially any type is ok, until we find Aggrefs which say otherwise */
+	context.allowedtype = PAT_ANY;
+
+	if (!partial_aggregate_walker(clause, &context))
+		return context.allowedtype;
+	return context.allowedtype;
+}
+
+static bool
+partial_aggregate_walker(Node *node, partial_agg_context *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Aggref))
+	{
+		Aggref	   *aggref = (Aggref *) node;
+		HeapTuple	aggTuple;
+		Form_pg_aggregate aggform;
+
+		Assert(aggref->agglevelsup == 0);
+
+		/*
+		 * We can't perform partial aggregation with Aggrefs containing a
+		 * DISTINCT or ORDER BY clause.
+		 */
+		if (aggref->aggdistinct || aggref->aggorder)
+		{
+			context->allowedtype = PAT_DISABLED;
+			return true;	/* abort search */
+		}
+		aggTuple = SearchSysCache1(AGGFNOID,
+								   ObjectIdGetDatum(aggref->aggfnoid));
+		if (!HeapTupleIsValid(aggTuple))
+			elog(ERROR, "cache lookup failed for aggregate %u",
+				 aggref->aggfnoid);
+		aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
+
+		/*
+		 * If there is no combine func, then partial aggregation is not
+		 * possible.
+		 */
+		if (!OidIsValid(aggform->aggcombinefn))
+		{
+			ReleaseSysCache(aggTuple);
+			context->allowedtype = PAT_DISABLED;
+			return true;	/* abort search */
+		}
+
+		/*
+		 * If we find any aggs with an internal transtype then we must ensure
+		 * that pointers to aggregate states are not passed to other processes,
+		 * therefore we set the maximum degree to PAT_INTERNAL_ONLY.
+		 */
+		if (aggform->aggtranstype == INTERNALOID)
+			context->allowedtype = PAT_INTERNAL_ONLY;
+
+		ReleaseSysCache(aggTuple);
+		return false; /* continue searching */
+	}
+	return expression_tree_walker(node, partial_aggregate_walker,
+								  (void *) context);
+}
+
+/*
  * contain_agg_clause
  *	  Recursively search for Aggref/GroupingFunc nodes within a clause.
  *
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 19c1570..6227be2 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1674,7 +1674,7 @@ create_gather_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
 		pathnode->single_copy = true;
 	}
 
-	cost_gather(pathnode, root, rel, pathnode->path.param_info);
+	cost_gather(pathnode, root, rel, pathnode->path.param_info, NULL);
 
 	return pathnode;
 }
@@ -2384,6 +2384,8 @@ create_upper_unique_path(PlannerInfo *root,
  * 'qual' is the HAVING quals if any
  * 'aggcosts' contains cost info about the aggregate functions to be computed
  * 'numGroups' is the estimated number of groups (1 if not grouping)
+ * 'combineStates' is set to true if the Agg node should combine agg states
+ * 'finalizeAggs' is set to false if the Agg node should not call the finalfn
  */
 AggPath *
 create_agg_path(PlannerInfo *root,
@@ -2394,9 +2396,11 @@ create_agg_path(PlannerInfo *root,
 				List *groupClause,
 				List *qual,
 				const AggClauseCosts *aggcosts,
-				double numGroups)
+				double numGroups,
+				bool combineStates,
+				bool finalizeAggs)
 {
-	AggPath    *pathnode = makeNode(AggPath);
+	AggPath	   *pathnode = makeNode(AggPath);
 
 	pathnode->path.pathtype = T_Agg;
 	pathnode->path.parent = rel;
@@ -2416,7 +2420,10 @@ create_agg_path(PlannerInfo *root,
 	pathnode->aggstrategy = aggstrategy;
 	pathnode->numGroups = numGroups;
 	pathnode->groupClause = groupClause;
+
 	pathnode->qual = qual;
+	pathnode->finalizeAggs = finalizeAggs;
+	pathnode->combineStates = combineStates;
 
 	cost_agg(&pathnode->path, root,
 			 aggstrategy, aggcosts,
@@ -2428,6 +2435,112 @@ create_agg_path(PlannerInfo *root,
 	pathnode->path.startup_cost += target->cost.startup;
 	pathnode->path.total_cost += target->cost.startup +
 		target->cost.per_tuple * pathnode->path.rows;
+	return pathnode;
+}
+
+/*
+ * create_parallelagg_path
+ *	  Creates a chain of path nodes which represents the required executor
+ *	  nodes to perform aggregation in parallel. This series of nodes consists
+ *	  of a partial aggregation phase which is intended to be executed on
+ *	  multiple worker processes. This aggregation phase does not execute the
+ *	  aggregate's final function, it instead returns the aggregate state. A
+ *	  Gather path is then added to bring these aggregated states back into the
+ *	  master process, where the final aggregate node combines these
+ *	  intermediate states with other states which belong to the same group,
+ *	  it's in this phase that the aggregate's final function is called, if
+ *	  present.
+ *
+ * 'rel' is the parent relation associated with the result
+ * 'subpath' is the path representing the source of data
+ * 'target' is the PathTarget to be computed
+ * 'partialstrategy' is the Agg node's implementation strategy for 1st stage
+ * 'finalstrategy' is the Agg node's implementation strategy for 2nd stage
+ * 'groupClause' is a list of SortGroupClause's representing the grouping
+ * 'qual' is the HAVING quals if any
+ * 'aggcosts' contains cost info about the aggregate functions to be computed
+ * 'numGroups' is the estimated number of groups (1 if not grouping)
+ */
+AggPath *
+create_parallelagg_path(PlannerInfo *root,
+						RelOptInfo *rel,
+						Path *subpath,
+						PathTarget *target,
+						AggStrategy partialstrategy,
+						AggStrategy finalstrategy,
+						List *groupClause,
+						List *qual,
+						const AggClauseCosts *aggcosts,
+						double numGroups)
+{
+	GatherPath	   *gatherpath = makeNode(GatherPath);
+	AggPath		   *pathnode;
+	Path		   *currentpath;
+	double			numPartialGroups;
+
+	pathnode = create_agg_path(root,
+							   rel,
+							   subpath,
+							   target,
+							   partialstrategy,
+							   groupClause,
+							   NIL, /* don't apply qual until final stage */
+							   aggcosts,
+							   numGroups,
+							   false,
+							   false);
+
+	gatherpath->path.pathtype = T_Gather;
+	gatherpath->path.parent = rel;
+	gatherpath->path.pathtarget = target;
+	gatherpath->path.param_info = NULL;
+	gatherpath->path.parallel_aware = false;
+	gatherpath->path.parallel_safe = false;
+	gatherpath->path.parallel_degree = subpath->parallel_degree;
+	gatherpath->path.pathkeys = NIL;	/* output is unordered */
+	gatherpath->subpath = (Path *) pathnode;
+	gatherpath->single_copy = false;
+
+	/*
+	 * Estimate the total number of groups which the gather will receive
+	 * from the aggregate worker processes. We'll assume that each worker
+	 * will produce every possible group, this might be an overestimate,
+	 * although it seems safer to over estimate here rather than
+	 * underestimate. To keep this number sane we cap the number of groups
+	 * so it's never larger than the number of rows in the input path. This
+	 * covers the case when there are less than an average of
+	 * parallel_degree input tuples per group.
+	 */
+	numPartialGroups = Min(numGroups, subpath->rows) *
+						(subpath->parallel_degree + 1);
+
+	cost_gather(gatherpath, root, NULL, NULL, &numPartialGroups);
+
+	currentpath = &gatherpath->path;
+
+	if (finalstrategy == AGG_SORTED)
+	{
+		SortPath *sortpath;
+
+		sortpath =  create_sort_path(root,
+										rel,
+										&gatherpath->path,
+										root->query_pathkeys,
+										-1.0);
+		currentpath = &sortpath->path;
+	}
+
+	pathnode = create_agg_path(root,
+							   rel,
+							   currentpath,
+							   currentpath->pathtarget,
+							   partialstrategy,
+							   groupClause,
+							   qual,
+							   aggcosts,
+							   numPartialGroups,
+							   true,
+							   true);
 
 	return pathnode;
 }
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 098a486..97236fb 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -1299,6 +1299,8 @@ typedef struct AggPath
 	double		numGroups;		/* estimated number of groups in input */
 	List	   *groupClause;	/* a list of SortGroupClause's */
 	List	   *qual;			/* quals (HAVING quals), if any */
+	bool		combineStates;	/* input is partially aggregated agg states */
+	bool		finalizeAggs;	/* should the executor call the finalfn? */
 } AggPath;
 
 /*
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 3b3fd0f..d381ff0 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -27,6 +27,25 @@ typedef struct
 	List	  **windowFuncs;	/* lists of WindowFuncs for each winref */
 } WindowFuncLists;
 
+/*
+ * PartialAggType
+ *	PartialAggType stores whether partial aggregation is allowed and
+ *	which context it is allowed in. We require three states here as there are
+ *	two different contexts in which partial aggregation is safe. For aggregates
+ *	which have an 'stype' of INTERNAL, within a single backend process it is
+ *	okay to pass a pointer to the aggregate state, as the memory to which the
+ *	pointer points to will belong to the same process. In cases where the
+ *	aggregate state must be passed between different processes, for example
+ *	during parallel aggregation, passing the pointer is not okay due to the
+ *	fact that the memory being referenced won't be accessible from another
+ *	process.
+ */
+typedef enum
+{
+	PAT_ANY = 0,		/* Any type of partial aggregation is ok. */
+	PAT_INTERNAL_ONLY,	/* Some aggregates support only internal mode. */
+	PAT_DISABLED		/* Some aggregates don't support partial mode at all */
+} PartialAggType;
 
 extern Expr *make_opclause(Oid opno, Oid opresulttype, bool opretset,
 			  Expr *leftop, Expr *rightop,
@@ -47,6 +66,7 @@ extern Node *make_and_qual(Node *qual1, Node *qual2);
 extern Expr *make_ands_explicit(List *andclauses);
 extern List *make_ands_implicit(Expr *clause);
 
+extern PartialAggType aggregates_allow_partial(Node *clause);
 extern bool contain_agg_clause(Node *clause);
 extern void count_agg_clauses(PlannerInfo *root, Node *clause,
 				  AggClauseCosts *costs);
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index fea2bb7..d4adca6 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -150,7 +150,7 @@ extern void final_cost_hashjoin(PlannerInfo *root, HashPath *path,
 					SpecialJoinInfo *sjinfo,
 					SemiAntiJoinFactors *semifactors);
 extern void cost_gather(GatherPath *path, PlannerInfo *root,
-			RelOptInfo *baserel, ParamPathInfo *param_info);
+			RelOptInfo *baserel, ParamPathInfo *param_info, double *rows);
 extern void cost_subplan(PlannerInfo *root, SubPlan *subplan, Plan *plan);
 extern void cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root);
 extern void cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root);
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 37744bf..b0bd808 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -167,7 +167,19 @@ extern AggPath *create_agg_path(PlannerInfo *root,
 				List *groupClause,
 				List *qual,
 				const AggClauseCosts *aggcosts,
-				double numGroups);
+				double numGroups,
+				bool combineStates,
+				bool finalizeAggs);
+extern AggPath *create_parallelagg_path(PlannerInfo *root,
+										RelOptInfo *rel,
+										Path *subpath,
+										PathTarget *target,
+										AggStrategy partialstrategy,
+										AggStrategy finalstrategy,
+										List *groupClause,
+										List *qual,
+										const AggClauseCosts *aggcosts,
+										double numGroups);
 extern GroupingSetsPath *create_groupingsets_path(PlannerInfo *root,
 						 RelOptInfo *rel,
 						 Path *subpath,
