diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
new file mode 100644
index 5a84742..d7afcea
*** a/src/backend/executor/execExpr.c
--- b/src/backend/executor/execExpr.c
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 717,722 ****
--- 717,749 ----
  				break;
  			}
  
+ 		case T_GroupedVar:
+ 			/*
+ 			 * GroupedVar is treated as an aggregate if it appears in the
+ 			 * targetlist of Agg node, but as a normal variable elsewhere.
+ 			 */
+ 			if (parent && (IsA(parent, AggState)))
+ 			{
+ 				GroupedVar *gvar = (GroupedVar *) node;
+ 
+ 				/*
+ 				 * Currently GroupedVar can only represent partial aggregate.
+ 				 */
+ 				Assert(gvar->agg_partial != NULL);
+ 
+ 				ExecInitExprRec((Expr *) gvar->agg_partial, parent, state,
+ 								resv, resnull);
+ 				break;
+ 			}
+ 			else
+ 			{
+ 				/*
+ 				 * set_plan_refs should have replaced GroupedVar in the
+ 				 * targetlist with an ordinary Var.
+ 				 */
+ 				elog(ERROR, "parent of GroupedVar is not Agg node");
+ 			}
+ 
  		case T_GroupingFunc:
  			{
  				GroupingFunc *grp_node = (GroupingFunc *) node;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
new file mode 100644
index ef35da6..70d2367
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** find_unaggregated_cols_walker(Node *node
*** 1826,1831 ****
--- 1826,1842 ----
  		/* do not descend into aggregate exprs */
  		return false;
  	}
+ 	if (IsA(node, GroupedVar))
+ 	{
+ 		GroupedVar	   *gvar = (GroupedVar *) node;
+ 
+ 		/*
+ 		 * GroupedVar is currently used only for partial aggregation, so treat
+ 		 * it like an Aggref above.
+ 		 */
+ 		Assert(gvar->agg_partial != NULL);
+ 		return false;
+ 	}
  	return expression_tree_walker(node, find_unaggregated_cols_walker,
  								  (void *) colnos);
  }
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index 61bc502..5f5bb4f
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyPlaceHolderVar(const PlaceHolderVar
*** 2189,2194 ****
--- 2189,2209 ----
  }
  
  /*
+  * _copyGroupedVar
+  */
+ static GroupedVar *
+ _copyGroupedVar(const GroupedVar *from)
+ {
+ 	GroupedVar *newnode = makeNode(GroupedVar);
+ 
+ 	COPY_NODE_FIELD(gvexpr);
+ 	COPY_NODE_FIELD(agg_partial);
+ 	COPY_SCALAR_FIELD(gvid);
+ 
+ 	return newnode;
+ }
+ 
+ /*
   * _copySpecialJoinInfo
   */
  static SpecialJoinInfo *
*************** copyObjectImpl(const void *from)
*** 4958,4963 ****
--- 4973,4981 ----
  		case T_PlaceHolderVar:
  			retval = _copyPlaceHolderVar(from);
  			break;
+ 		case T_GroupedVar:
+ 			retval = _copyGroupedVar(from);
+ 			break;
  		case T_SpecialJoinInfo:
  			retval = _copySpecialJoinInfo(from);
  			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index 5941b7a..4fe3aa8
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalPlaceHolderVar(const PlaceHolderVa
*** 865,870 ****
--- 865,878 ----
  }
  
  static bool
+ _equalGroupedVar(const GroupedVar *a, const GroupedVar *b)
+ {
+ 	COMPARE_SCALAR_FIELD(gvid);
+ 
+ 	return true;
+ }
+ 
+ static bool
  _equalSpecialJoinInfo(const SpecialJoinInfo *a, const SpecialJoinInfo *b)
  {
  	COMPARE_BITMAPSET_FIELD(min_lefthand);
*************** equal(const void *a, const void *b)
*** 3130,3135 ****
--- 3138,3146 ----
  		case T_PlaceHolderVar:
  			retval = _equalPlaceHolderVar(a, b);
  			break;
+ 		case T_GroupedVar:
+ 			retval = _equalGroupedVar(a, b);
+ 			break;
  		case T_SpecialJoinInfo:
  			retval = _equalSpecialJoinInfo(a, b);
  			break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
new file mode 100644
index d5293a1..da517ce
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** exprType(const Node *expr)
*** 256,261 ****
--- 256,264 ----
  		case T_PlaceHolderVar:
  			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
  			break;
+ 		case T_GroupedVar:
+ 			type = exprType((Node *) ((const GroupedVar *) expr)->agg_partial);
+ 			break;
  		default:
  			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
  			type = InvalidOid;	/* keep compiler quiet */
*************** exprCollation(const Node *expr)
*** 925,930 ****
--- 928,936 ----
  		case T_PlaceHolderVar:
  			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
  			break;
+ 		case T_GroupedVar:
+ 			coll = exprCollation((Node *) ((const GroupedVar *) expr)->gvexpr);
+ 			break;
  		default:
  			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
  			coll = InvalidOid;	/* keep compiler quiet */
*************** expression_tree_walker(Node *node,
*** 2188,2193 ****
--- 2194,2201 ----
  			break;
  		case T_PlaceHolderVar:
  			return walker(((PlaceHolderVar *) node)->phexpr, context);
+ 		case T_GroupedVar:
+ 			return walker(((GroupedVar *) node)->gvexpr, context);
  		case T_InferenceElem:
  			return walker(((InferenceElem *) node)->expr, context);
  		case T_AppendRelInfo:
*************** expression_tree_mutator(Node *node,
*** 2978,2983 ****
--- 2986,3001 ----
  				return (Node *) newnode;
  			}
  			break;
+ 		case T_GroupedVar:
+ 			{
+ 				GroupedVar *gv = (GroupedVar *) node;
+ 				GroupedVar *newnode;
+ 
+ 				FLATCOPY(newnode, gv, GroupedVar);
+ 				MUTATE(newnode->gvexpr, gv->gvexpr, Expr *);
+ 				MUTATE(newnode->agg_partial, gv->agg_partial, Aggref *);
+ 				return (Node *) newnode;
+ 			}
  		case T_InferenceElem:
  			{
  				InferenceElem *inferenceelemdexpr = (InferenceElem *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index 766ca49..81da091
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outPlannerInfo(StringInfo str, const Pl
*** 2181,2186 ****
--- 2181,2187 ----
  	WRITE_NODE_FIELD(pcinfo_list);
  	WRITE_NODE_FIELD(rowMarks);
  	WRITE_NODE_FIELD(placeholder_list);
+ 	WRITE_NODE_FIELD(grouped_var_list);
  	WRITE_NODE_FIELD(fkey_list);
  	WRITE_NODE_FIELD(query_pathkeys);
  	WRITE_NODE_FIELD(group_pathkeys);
*************** _outParamPathInfo(StringInfo str, const
*** 2401,2406 ****
--- 2402,2417 ----
  }
  
  static void
+ _outGroupedPathInfo(StringInfo str, const GroupedPathInfo *node)
+ {
+ 	WRITE_NODE_TYPE("GROUPEDPATHINFO");
+ 
+ 	WRITE_NODE_FIELD(target);
+ 	WRITE_NODE_FIELD(pathlist);
+ 	WRITE_NODE_FIELD(partial_pathlist);
+ }
+ 
+ static void
  _outRestrictInfo(StringInfo str, const RestrictInfo *node)
  {
  	WRITE_NODE_TYPE("RESTRICTINFO");
*************** _outPlaceHolderVar(StringInfo str, const
*** 2444,2449 ****
--- 2455,2470 ----
  }
  
  static void
+ _outGroupedVar(StringInfo str, const GroupedVar *node)
+ {
+ 	WRITE_NODE_TYPE("GROUPEDVAR");
+ 
+ 	WRITE_NODE_FIELD(gvexpr);
+ 	WRITE_NODE_FIELD(agg_partial);
+ 	WRITE_UINT_FIELD(gvid);
+ }
+ 
+ static void
  _outSpecialJoinInfo(StringInfo str, const SpecialJoinInfo *node)
  {
  	WRITE_NODE_TYPE("SPECIALJOININFO");
*************** outNode(StringInfo str, const void *obj)
*** 3980,3991 ****
--- 4001,4018 ----
  			case T_ParamPathInfo:
  				_outParamPathInfo(str, obj);
  				break;
+ 			case T_GroupedPathInfo:
+ 				_outGroupedPathInfo(str, obj);
+ 				break;
  			case T_RestrictInfo:
  				_outRestrictInfo(str, obj);
  				break;
  			case T_PlaceHolderVar:
  				_outPlaceHolderVar(str, obj);
  				break;
+ 			case T_GroupedVar:
+ 				_outGroupedVar(str, obj);
+ 				break;
  			case T_SpecialJoinInfo:
  				_outSpecialJoinInfo(str, obj);
  				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
new file mode 100644
index 766f2d8..fdf4edd
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readVar(void)
*** 521,526 ****
--- 521,541 ----
  }
  
  /*
+  * _readGroupedVar
+  */
+ static GroupedVar *
+ _readGroupedVar(void)
+ {
+ 	READ_LOCALS(GroupedVar);
+ 
+ 	READ_NODE_FIELD(gvexpr);
+ 	READ_NODE_FIELD(agg_partial);
+ 	READ_UINT_FIELD(gvid);
+ 
+ 	READ_DONE();
+ }
+ 
+ /*
   * _readConst
   */
  static Const *
*************** parseNodeString(void)
*** 2436,2441 ****
--- 2451,2458 ----
  		return_value = _readTableFunc();
  	else if (MATCH("VAR", 3))
  		return_value = _readVar();
+ 	else if (MATCH("GROUPEDVAR", 10))
+ 		return_value = _readGroupedVar();
  	else if (MATCH("CONST", 5))
  		return_value = _readConst();
  	else if (MATCH("PARAM", 5))
diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c
new file mode 100644
index b5cab0c..f89406d
*** a/src/backend/optimizer/geqo/geqo_eval.c
--- b/src/backend/optimizer/geqo/geqo_eval.c
*************** merge_clump(PlannerInfo *root, List *clu
*** 265,271 ****
  			if (joinrel)
  			{
  				/* Create GatherPaths for any useful partial paths for rel */
! 				generate_gather_paths(root, joinrel);
  
  				/* Find and save the cheapest paths for this joinrel */
  				set_cheapest(joinrel);
--- 265,271 ----
  			if (joinrel)
  			{
  				/* Create GatherPaths for any useful partial paths for rel */
! 				generate_gather_paths(root, joinrel, false);
  
  				/* Find and save the cheapest paths for this joinrel */
  				set_cheapest(joinrel);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
new file mode 100644
index 343b35a..fca4727
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** set_rel_pathlist(PlannerInfo *root, RelO
*** 486,492 ****
  	 * we'll consider gathering partial paths for the parent appendrel.)
  	 */
  	if (rel->reloptkind == RELOPT_BASEREL)
! 		generate_gather_paths(root, rel);
  
  	/*
  	 * Allow a plugin to editorialize on the set of Paths for this base
--- 486,495 ----
  	 * we'll consider gathering partial paths for the parent appendrel.)
  	 */
  	if (rel->reloptkind == RELOPT_BASEREL)
! 	{
! 		generate_gather_paths(root, rel, false);
! 		generate_gather_paths(root, rel, true);
! 	}
  
  	/*
  	 * Allow a plugin to editorialize on the set of Paths for this base
*************** static void
*** 687,692 ****
--- 690,696 ----
  set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
  {
  	Relids		required_outer;
+ 	Path		*seq_path;
  
  	/*
  	 * We don't support pushing join clauses into the quals of a seqscan, but
*************** set_plain_rel_pathlist(PlannerInfo *root
*** 695,709 ****
  	 */
  	required_outer = rel->lateral_relids;
  
! 	/* Consider sequential scan */
! 	add_path(rel, create_seqscan_path(root, rel, required_outer, 0));
  
! 	/* If appropriate, consider parallel sequential scan */
  	if (rel->consider_parallel && required_outer == NULL)
  		create_plain_partial_paths(root, rel);
  
  	/* Consider index scans */
! 	create_index_paths(root, rel);
  
  	/* Consider TID scans */
  	create_tidscan_paths(root, rel);
--- 699,726 ----
  	 */
  	required_outer = rel->lateral_relids;
  
! 	/* Consider sequential scan, both plain and grouped. */
! 	seq_path = create_seqscan_path(root, rel, required_outer, 0);
! 	add_path(rel, seq_path, false);
! 	if (rel->gpi != NULL && required_outer == NULL)
! 		create_grouped_path(root, rel, seq_path, false, false, AGG_HASHED);
  
! 	/* If appropriate, consider parallel sequential scan (plain or grouped) */
  	if (rel->consider_parallel && required_outer == NULL)
  		create_plain_partial_paths(root, rel);
  
  	/* Consider index scans */
! 	create_index_paths(root, rel, false);
! 	if (rel->gpi != NULL)
! 	{
! 		/*
! 		 * TODO Instead of calling the whole clause-matching machinery twice
! 		 * (there should be no difference between plain and grouped paths from
! 		 * this point of view), consider returning a separate list of paths
! 		 * usable as grouped ones.
! 		 */
! 		create_index_paths(root, rel, true);
! 	}
  
  	/* Consider TID scans */
  	create_tidscan_paths(root, rel);
*************** static void
*** 717,722 ****
--- 734,740 ----
  create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel)
  {
  	int			parallel_workers;
+ 	Path		*path;
  
  	parallel_workers = compute_parallel_worker(rel, rel->pages, -1);
  
*************** create_plain_partial_paths(PlannerInfo *
*** 725,731 ****
  		return;
  
  	/* Add an unordered partial path based on a parallel sequential scan. */
! 	add_partial_path(rel, create_seqscan_path(root, rel, NULL, parallel_workers));
  }
  
  /*
--- 743,850 ----
  		return;
  
  	/* Add an unordered partial path based on a parallel sequential scan. */
! 	path = create_seqscan_path(root, rel, NULL, parallel_workers);
! 	add_partial_path(rel, path, false);
! 
! 	/*
! 	 * Do partial aggregation at base relation level if the relation is
! 	 * eligible for it.
! 	 */
! 	if (rel->gpi != NULL)
! 		create_grouped_path(root, rel, path, false, true, AGG_HASHED);
! }
! 
! /*
!  * Apply partial aggregation to a subpath and add the AggPath to the
!  * appropriate pathlist.
!  *
!  * "precheck" tells whether the aggregation path should first be checked using
!  * add_path_precheck().
!  *
!  * If "partial" is true, the resulting path is considered partial in terms of
!  * parallel execution.
!  *
!  * The path we create here shouldn't be parameterized because of supposedly
!  * high startup cost of aggregation (whether due to build of hash table for
!  * AGG_HASHED strategy or due to explicit sort for AGG_SORTED).
!  *
!  * XXX IndexPath as an input for AGG_SORTED might seem to be an exception, but
!  * aggregation of its output is only beneficial if it's performed by multiple
!  * workers, i.e. the resulting path is partial (Besides parallel aggregation,
!  * the other use case of aggregation push-down is aggregation performed on
!  * remote database, but that has nothing to do with IndexScan). And partial
!  * path cannot be parameterized because it's semantically wrong to use it on
!  * the inner side of NL join.
!  */
! void
! create_grouped_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
! 					bool precheck, bool partial, AggStrategy aggstrategy)
! {
! 	List    *group_clauses = NIL;
! 	List	*group_exprs = NIL;
! 	List	*agg_exprs = NIL;
! 	Path	*agg_path;
! 
! 	/*
! 	 * If the AggPath should be partial, the subpath must be too, and
! 	 * therefore the subpath is essentially parallel_safe.
! 	 */
! 	Assert(subpath->parallel_safe || !partial);
! 
! 	/*
! 	 * Grouped path should never be parameterized, so we're not supposed to
! 	 * receive parameterized subpath.
! 	 */
! 	Assert(subpath->param_info == NULL);
! 
! 	/*
! 	 * Note that "partial" in the following function names refers to 2-stage
! 	 * aggregation, not to parallel processing.
! 	 */
! 	if (aggstrategy == AGG_HASHED)
! 		agg_path = (Path *) create_partial_agg_hashed_path(root, subpath,
! 														   true,
! 														   &group_clauses,
! 														   &group_exprs,
! 														   &agg_exprs,
! 														   subpath->rows);
! 	else if (aggstrategy == AGG_SORTED)
! 		agg_path = (Path *) create_partial_agg_sorted_path(root, subpath,
! 														   true,
! 														   &group_clauses,
! 														   &group_exprs,
! 														   &agg_exprs,
! 														   subpath->rows);
! 	else
! 		elog(ERROR, "unexpected strategy %d", aggstrategy);
! 
! 	/* Add the grouped path to the list of grouped base paths. */
! 	if (agg_path != NULL)
! 	{
! 		if (precheck)
! 		{
! 			List	*pathkeys;
! 
! 			/* AGG_HASH is not supposed to generate sorted output. */
! 			pathkeys = aggstrategy == AGG_SORTED ? subpath->pathkeys : NIL;
! 
! 			if (!partial &&
! 				!add_path_precheck(rel, agg_path->startup_cost,
! 								   agg_path->total_cost, pathkeys, NULL,
! 								   true))
! 				return;
! 
! 			if (partial &&
! 				!add_partial_path_precheck(rel, agg_path->total_cost, pathkeys,
! 										   true))
! 				return;
! 		}
! 
! 		if (!partial)
! 			add_path(rel, (Path *) agg_path, true);
! 		else
! 			add_partial_path(rel, (Path *) agg_path, true);
! 	}
  }
  
  /*
*************** set_tablesample_rel_pathlist(PlannerInfo
*** 811,817 ****
  		path = (Path *) create_material_path(rel, path);
  	}
  
! 	add_path(rel, path);
  
  	/* For the moment, at least, there are no other paths to consider */
  }
--- 930,936 ----
  		path = (Path *) create_material_path(rel, path);
  	}
  
! 	add_path(rel, path, false);
  
  	/* For the moment, at least, there are no other paths to consider */
  }
*************** set_append_rel_size(PlannerInfo *root, R
*** 1066,1071 ****
--- 1185,1233 ----
  								   appinfo);
  
  		/*
+ 		 * If grouping is applicable to the parent relation, it should be
+ 		 * applicable to the children too. Make sure the child rel has valid
+ 		 * sortgrouprefs.
+ 		 *
+ 		 * TODO Consider if this is really needed --- child rel is not joined
+ 		 * to grouped rel itself, so it might not participate on creation of
+ 		 * the grouped path target that upper joins will see.
+ 		 */
+ 		if (rel->reltarget->sortgrouprefs)
+ 		{
+ 			Assert(childrel->reltarget->sortgrouprefs == NULL);
+ 			childrel->reltarget->sortgrouprefs = rel->reltarget->sortgrouprefs;
+ 		}
+ 
+ 		/*
+ 		 * Also the grouped target needs to be adjusted, if one exists.
+ 		 */
+ 		if (rel->gpi != NULL)
+ 		{
+ 			PathTarget	*target = rel->gpi->target;
+ 
+ 			Assert(target->sortgrouprefs != NULL);
+ 
+ 			Assert(childrel->gpi == NULL);
+ 			childrel->gpi = makeNode(GroupedPathInfo);
+ 			memcpy(childrel->gpi, rel->gpi, sizeof(GroupedPathInfo));
+ 
+ 			/*
+ 			 * add_grouping_info_to_base_rels was not sure if grouping makes
+ 			 * sense for the parent rel, so create a separate copy of the
+ 			 * target now.
+ 			 */
+ 			childrel->gpi->target = copy_pathtarget(childrel->gpi->target);
+ 
+ 			/* Translate vars of the grouping target. */
+ 			Assert(childrel->gpi->target->exprs != NIL);
+ 			childrel->gpi->target->exprs = (List *)
+ 				adjust_appendrel_attrs(root,
+ 									   (Node *) childrel->gpi->target->exprs,
+ 									   appinfo);
+ 		}
+ 
+ 		/*
  		 * We have to make child entries in the EquivalenceClass data
  		 * structures as well.  This is needed either if the parent
  		 * participates in some eclass joins (because we will want to consider
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1280,1285 ****
--- 1442,1449 ----
  	bool		subpaths_valid = true;
  	List	   *partial_subpaths = NIL;
  	bool		partial_subpaths_valid = true;
+ 	List	   *grouped_subpaths = NIL;
+ 	bool		grouped_subpaths_valid = true;
  	List	   *all_child_pathkeys = NIL;
  	List	   *all_child_outers = NIL;
  	ListCell   *l;
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1323,1328 ****
--- 1487,1515 ----
  			partial_subpaths_valid = false;
  
  		/*
+ 		 * For grouped paths, use only the unparameterized subpaths.
+ 		 *
+ 		 * XXX Consider if the parameterized subpaths should be processed
+ 		 * below. It's probably not useful for sequential scans (due to
+ 		 * repeated aggregation), but might be worthwhile for other child
+ 		 * nodes.
+ 		 */
+ 		if (childrel->gpi != NULL && childrel->gpi->pathlist != NIL)
+ 		{
+ 			Path	*path;
+ 
+ 			path = (Path *) linitial(childrel->gpi->pathlist);
+ 			if (path->param_info == NULL)
+ 				grouped_subpaths = accumulate_append_subpath(grouped_subpaths,
+ 															 path);
+ 			else
+ 				grouped_subpaths_valid = false;
+ 		}
+ 		else
+ 			grouped_subpaths_valid = false;
+ 
+ 
+ 		/*
  		 * Collect lists of all the available path orderings and
  		 * parameterizations for all the children.  We use these as a
  		 * heuristic to indicate which sort orderings and parameterizations we
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1394,1400 ****
  	 */
  	if (subpaths_valid)
  		add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0,
! 												  partitioned_rels));
  
  	/*
  	 * Consider an append of partial unordered, unparameterized partial paths.
--- 1581,1588 ----
  	 */
  	if (subpaths_valid)
  		add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0,
! 					 partitioned_rels),
! 				 false);
  
  	/*
  	 * Consider an append of partial unordered, unparameterized partial paths.
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1421,1428 ****
  
  		/* Generate a partial append path. */
  		appendpath = create_append_path(rel, partial_subpaths, NULL,
! 										parallel_workers, partitioned_rels);
! 		add_partial_path(rel, (Path *) appendpath);
  	}
  
  	/*
--- 1609,1629 ----
  
  		/* Generate a partial append path. */
  		appendpath = create_append_path(rel, partial_subpaths, NULL,
! 										parallel_workers,
! 										partitioned_rels);
! 		add_partial_path(rel, (Path *) appendpath, false);
! 	}
! 
! 	/* TODO Also partial grouped paths? */
! 	if (grouped_subpaths_valid)
! 	{
! 		Path	*path;
! 
! 		path = (Path *) create_append_path(rel, grouped_subpaths, NULL, 0,
! 			partitioned_rels);
! 		/* pathtarget will produce the grouped relation.. */
! 		path->pathtarget = rel->gpi->target;
! 		add_path(rel, path, true);
  	}
  
  	/*
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1475,1481 ****
  		if (subpaths_valid)
  			add_path(rel, (Path *)
  					 create_append_path(rel, subpaths, required_outer, 0,
! 										partitioned_rels));
  	}
  }
  
--- 1676,1683 ----
  		if (subpaths_valid)
  			add_path(rel, (Path *)
  					 create_append_path(rel, subpaths, required_outer, 0,
! 						 partitioned_rels),
! 					 false);
  	}
  }
  
*************** generate_mergeappend_paths(PlannerInfo *
*** 1571,1584 ****
  														startup_subpaths,
  														pathkeys,
  														NULL,
! 														partitioned_rels));
  		if (startup_neq_total)
  			add_path(rel, (Path *) create_merge_append_path(root,
  															rel,
  															total_subpaths,
  															pathkeys,
  															NULL,
! 															partitioned_rels));
  	}
  }
  
--- 1773,1788 ----
  														startup_subpaths,
  														pathkeys,
  														NULL,
! 														partitioned_rels),
! 				 false);
  		if (startup_neq_total)
  			add_path(rel, (Path *) create_merge_append_path(root,
  															rel,
  															total_subpaths,
  															pathkeys,
  															NULL,
! 															partitioned_rels),
! 					 false);
  	}
  }
  
*************** set_dummy_rel_pathlist(RelOptInfo *rel)
*** 1711,1717 ****
  	rel->pathlist = NIL;
  	rel->partial_pathlist = NIL;
  
! 	add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL));
  
  	/*
  	 * We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
--- 1915,1921 ----
  	rel->pathlist = NIL;
  	rel->partial_pathlist = NIL;
  
! 	add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL), false);
  
  	/*
  	 * We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
*************** set_subquery_pathlist(PlannerInfo *root,
*** 1925,1931 ****
  		/* Generate outer path using this subpath */
  		add_path(rel, (Path *)
  				 create_subqueryscan_path(root, rel, subpath,
! 										  pathkeys, required_outer));
  	}
  }
  
--- 2129,2135 ----
  		/* Generate outer path using this subpath */
  		add_path(rel, (Path *)
  				 create_subqueryscan_path(root, rel, subpath,
! 										  pathkeys, required_outer), false);
  	}
  }
  
*************** set_function_pathlist(PlannerInfo *root,
*** 1994,2000 ****
  
  	/* Generate appropriate path */
  	add_path(rel, create_functionscan_path(root, rel,
! 										   pathkeys, required_outer));
  }
  
  /*
--- 2198,2204 ----
  
  	/* Generate appropriate path */
  	add_path(rel, create_functionscan_path(root, rel,
! 										   pathkeys, required_outer), false);
  }
  
  /*
*************** set_values_pathlist(PlannerInfo *root, R
*** 2014,2020 ****
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_valuesscan_path(root, rel, required_outer));
  }
  
  /*
--- 2218,2224 ----
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_valuesscan_path(root, rel, required_outer), false);
  }
  
  /*
*************** set_tablefunc_pathlist(PlannerInfo *root
*** 2035,2041 ****
  
  	/* Generate appropriate path */
  	add_path(rel, create_tablefuncscan_path(root, rel,
! 											required_outer));
  }
  
  /*
--- 2239,2245 ----
  
  	/* Generate appropriate path */
  	add_path(rel, create_tablefuncscan_path(root, rel,
! 											required_outer), false);
  }
  
  /*
*************** set_cte_pathlist(PlannerInfo *root, RelO
*** 2101,2107 ****
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_ctescan_path(root, rel, required_outer));
  }
  
  /*
--- 2305,2311 ----
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_ctescan_path(root, rel, required_outer), false);
  }
  
  /*
*************** set_namedtuplestore_pathlist(PlannerInfo
*** 2128,2134 ****
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_namedtuplestorescan_path(root, rel, required_outer));
  
  	/* Select cheapest path (pretty easy in this case...) */
  	set_cheapest(rel);
--- 2332,2339 ----
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_namedtuplestorescan_path(root, rel, required_outer),
! 			 false);
  
  	/* Select cheapest path (pretty easy in this case...) */
  	set_cheapest(rel);
*************** set_worktable_pathlist(PlannerInfo *root
*** 2181,2187 ****
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_worktablescan_path(root, rel, required_outer));
  }
  
  /*
--- 2386,2393 ----
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_worktablescan_path(root, rel, required_outer),
! 			 false);
  }
  
  /*
*************** set_worktable_pathlist(PlannerInfo *root
*** 2194,2207 ****
   * path that some GatherPath or GatherMergePath has a reference to.)
   */
  void
! generate_gather_paths(PlannerInfo *root, RelOptInfo *rel)
  {
  	Path	   *cheapest_partial_path;
  	Path	   *simple_gather_path;
  	ListCell   *lc;
  
  	/* If there are no partial paths, there's nothing to do here. */
! 	if (rel->partial_pathlist == NIL)
  		return;
  
  	/*
--- 2400,2420 ----
   * path that some GatherPath or GatherMergePath has a reference to.)
   */
  void
! generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool grouped)
  {
  	Path	   *cheapest_partial_path;
  	Path	   *simple_gather_path;
+ 	List	   *pathlist = NIL;
+ 	PathTarget *partial_target;
  	ListCell   *lc;
  
+ 	if (!grouped)
+ 		pathlist = rel->partial_pathlist;
+ 	else if (rel->gpi != NULL)
+ 		pathlist = rel->gpi->partial_pathlist;
+ 
  	/* If there are no partial paths, there's nothing to do here. */
! 	if (pathlist == NIL)
  		return;
  
  	/*
*************** generate_gather_paths(PlannerInfo *root,
*** 2209,2225 ****
  	 * path of interest: the cheapest one.  That will be the one at the front
  	 * of partial_pathlist because of the way add_partial_path works.
  	 */
! 	cheapest_partial_path = linitial(rel->partial_pathlist);
  	simple_gather_path = (Path *)
! 		create_gather_path(root, rel, cheapest_partial_path, rel->reltarget,
  						   NULL, NULL);
! 	add_path(rel, simple_gather_path);
  
  	/*
  	 * For each useful ordering, we can consider an order-preserving Gather
  	 * Merge.
  	 */
! 	foreach (lc, rel->partial_pathlist)
  	{
  		Path   *subpath = (Path *) lfirst(lc);
  		GatherMergePath   *path;
--- 2422,2444 ----
  	 * path of interest: the cheapest one.  That will be the one at the front
  	 * of partial_pathlist because of the way add_partial_path works.
  	 */
! 	cheapest_partial_path = linitial(pathlist);
! 
! 	if (!grouped)
! 		partial_target = rel->reltarget;
! 	else if (rel->gpi != NULL)
! 		partial_target = rel->gpi->target;
! 
  	simple_gather_path = (Path *)
! 		create_gather_path(root, rel, cheapest_partial_path, partial_target,
  						   NULL, NULL);
! 	add_path(rel, simple_gather_path, grouped);
  
  	/*
  	 * For each useful ordering, we can consider an order-preserving Gather
  	 * Merge.
  	 */
! 	foreach (lc, pathlist)
  	{
  		Path   *subpath = (Path *) lfirst(lc);
  		GatherMergePath   *path;
*************** generate_gather_paths(PlannerInfo *root,
*** 2227,2235 ****
  		if (subpath->pathkeys == NIL)
  			continue;
  
! 		path = create_gather_merge_path(root, rel, subpath, rel->reltarget,
  										subpath->pathkeys, NULL, NULL);
! 		add_path(rel, &path->path);
  	}
  }
  
--- 2446,2454 ----
  		if (subpath->pathkeys == NIL)
  			continue;
  
! 		path = create_gather_merge_path(root, rel, subpath, partial_target,
  										subpath->pathkeys, NULL, NULL);
! 		add_path(rel, &path->path, grouped);
  	}
  }
  
*************** standard_join_search(PlannerInfo *root,
*** 2395,2401 ****
  			rel = (RelOptInfo *) lfirst(lc);
  
  			/* Create GatherPaths for any useful partial paths for rel */
! 			generate_gather_paths(root, rel);
  
  			/* Find and save the cheapest paths for this rel */
  			set_cheapest(rel);
--- 2614,2621 ----
  			rel = (RelOptInfo *) lfirst(lc);
  
  			/* Create GatherPaths for any useful partial paths for rel */
! 			generate_gather_paths(root, rel, false);
! 			generate_gather_paths(root, rel, true);
  
  			/* Find and save the cheapest paths for this rel */
  			set_cheapest(rel);
*************** create_partial_bitmap_paths(PlannerInfo
*** 3046,3052 ****
  		return;
  
  	add_partial_path(rel, (Path *) create_bitmap_heap_path(root, rel,
! 					bitmapqual, rel->lateral_relids, 1.0, parallel_workers));
  }
  
  /*
--- 3266,3272 ----
  		return;
  
  	add_partial_path(rel, (Path *) create_bitmap_heap_path(root, rel,
! 				   bitmapqual, rel->lateral_relids, 1.0, parallel_workers), false);
  }
  
  /*
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
new file mode 100644
index a5d19f9..89f4308
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
***************
*** 32,37 ****
--- 32,38 ----
  #include "optimizer/predtest.h"
  #include "optimizer/prep.h"
  #include "optimizer/restrictinfo.h"
+ #include "optimizer/tlist.h"
  #include "optimizer/var.h"
  #include "utils/builtins.h"
  #include "utils/bytea.h"
*************** static bool eclass_already_used(Equivale
*** 107,119 ****
  static bool bms_equal_any(Relids relids, List *relids_list);
  static void get_index_paths(PlannerInfo *root, RelOptInfo *rel,
  				IndexOptInfo *index, IndexClauseSet *clauses,
! 				List **bitindexpaths);
  static List *build_index_paths(PlannerInfo *root, RelOptInfo *rel,
  				  IndexOptInfo *index, IndexClauseSet *clauses,
  				  bool useful_predicate,
  				  ScanTypeControl scantype,
  				  bool *skip_nonnative_saop,
! 				  bool *skip_lower_saop);
  static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
  				   List *clauses, List *other_clauses);
  static List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
--- 108,121 ----
  static bool bms_equal_any(Relids relids, List *relids_list);
  static void get_index_paths(PlannerInfo *root, RelOptInfo *rel,
  				IndexOptInfo *index, IndexClauseSet *clauses,
! 				List **bitindexpaths, bool grouped);
  static List *build_index_paths(PlannerInfo *root, RelOptInfo *rel,
  				  IndexOptInfo *index, IndexClauseSet *clauses,
  				  bool useful_predicate,
  				  ScanTypeControl scantype,
  				  bool *skip_nonnative_saop,
! 				   bool *skip_lower_saop,
! 				   bool grouped);
  static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
  				   List *clauses, List *other_clauses);
  static List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
*************** static Const *string_to_const(const char
*** 229,235 ****
   * as meaning "unparameterized so far as the indexquals are concerned".
   */
  void
! create_index_paths(PlannerInfo *root, RelOptInfo *rel)
  {
  	List	   *indexpaths;
  	List	   *bitindexpaths;
--- 231,237 ----
   * as meaning "unparameterized so far as the indexquals are concerned".
   */
  void
! create_index_paths(PlannerInfo *root, RelOptInfo *rel, bool grouped)
  {
  	List	   *indexpaths;
  	List	   *bitindexpaths;
*************** create_index_paths(PlannerInfo *root, Re
*** 274,281 ****
  		 * non-parameterized paths.  Plain paths go directly to add_path(),
  		 * bitmap paths are added to bitindexpaths to be handled below.
  		 */
! 		get_index_paths(root, rel, index, &rclauseset,
! 						&bitindexpaths);
  
  		/*
  		 * Identify the join clauses that can match the index.  For the moment
--- 276,283 ----
  		 * non-parameterized paths.  Plain paths go directly to add_path(),
  		 * bitmap paths are added to bitindexpaths to be handled below.
  		 */
! 		get_index_paths(root, rel, index, &rclauseset, &bitindexpaths,
! 						grouped);
  
  		/*
  		 * Identify the join clauses that can match the index.  For the moment
*************** create_index_paths(PlannerInfo *root, Re
*** 338,344 ****
  		bitmapqual = choose_bitmap_and(root, rel, bitindexpaths);
  		bpath = create_bitmap_heap_path(root, rel, bitmapqual,
  										rel->lateral_relids, 1.0, 0);
! 		add_path(rel, (Path *) bpath);
  
  		/* create a partial bitmap heap path */
  		if (rel->consider_parallel && rel->lateral_relids == NULL)
--- 340,346 ----
  		bitmapqual = choose_bitmap_and(root, rel, bitindexpaths);
  		bpath = create_bitmap_heap_path(root, rel, bitmapqual,
  										rel->lateral_relids, 1.0, 0);
! 		add_path(rel, (Path *) bpath, false);
  
  		/* create a partial bitmap heap path */
  		if (rel->consider_parallel && rel->lateral_relids == NULL)
*************** create_index_paths(PlannerInfo *root, Re
*** 415,421 ****
  			loop_count = get_loop_count(root, rel->relid, required_outer);
  			bpath = create_bitmap_heap_path(root, rel, bitmapqual,
  											required_outer, loop_count, 0);
! 			add_path(rel, (Path *) bpath);
  		}
  	}
  }
--- 417,423 ----
  			loop_count = get_loop_count(root, rel->relid, required_outer);
  			bpath = create_bitmap_heap_path(root, rel, bitmapqual,
  											required_outer, loop_count, 0);
! 			add_path(rel, (Path *) bpath, false);
  		}
  	}
  }
*************** get_join_index_paths(PlannerInfo *root,
*** 667,673 ****
  	Assert(clauseset.nonempty);
  
  	/* Build index path(s) using the collected set of clauses */
! 	get_index_paths(root, rel, index, &clauseset, bitindexpaths);
  
  	/*
  	 * Remember we considered paths for this set of relids.  We use lcons not
--- 669,675 ----
  	Assert(clauseset.nonempty);
  
  	/* Build index path(s) using the collected set of clauses */
! 	get_index_paths(root, rel, index, &clauseset, bitindexpaths, false);
  
  	/*
  	 * Remember we considered paths for this set of relids.  We use lcons not
*************** bms_equal_any(Relids relids, List *relid
*** 736,742 ****
  static void
  get_index_paths(PlannerInfo *root, RelOptInfo *rel,
  				IndexOptInfo *index, IndexClauseSet *clauses,
! 				List **bitindexpaths)
  {
  	List	   *indexpaths;
  	bool		skip_nonnative_saop = false;
--- 738,744 ----
  static void
  get_index_paths(PlannerInfo *root, RelOptInfo *rel,
  				IndexOptInfo *index, IndexClauseSet *clauses,
! 				List **bitindexpaths, bool grouped)
  {
  	List	   *indexpaths;
  	bool		skip_nonnative_saop = false;
*************** get_index_paths(PlannerInfo *root, RelOp
*** 754,760 ****
  								   index->predOK,
  								   ST_ANYSCAN,
  								   &skip_nonnative_saop,
! 								   &skip_lower_saop);
  
  	/*
  	 * If we skipped any lower-order ScalarArrayOpExprs on an index with an AM
--- 756,762 ----
  								   index->predOK,
  								   ST_ANYSCAN,
  								   &skip_nonnative_saop,
! 								   &skip_lower_saop, grouped);
  
  	/*
  	 * If we skipped any lower-order ScalarArrayOpExprs on an index with an AM
*************** get_index_paths(PlannerInfo *root, RelOp
*** 769,775 ****
  												   index->predOK,
  												   ST_ANYSCAN,
  												   &skip_nonnative_saop,
! 												   NULL));
  	}
  
  	/*
--- 771,777 ----
  												   index->predOK,
  												   ST_ANYSCAN,
  												   &skip_nonnative_saop,
! 												   NULL, grouped));
  	}
  
  	/*
*************** get_index_paths(PlannerInfo *root, RelOp
*** 789,797 ****
  		IndexPath  *ipath = (IndexPath *) lfirst(lc);
  
  		if (index->amhasgettuple)
! 			add_path(rel, (Path *) ipath);
  
! 		if (index->amhasgetbitmap &&
  			(ipath->path.pathkeys == NIL ||
  			 ipath->indexselectivity < 1.0))
  			*bitindexpaths = lappend(*bitindexpaths, ipath);
--- 791,799 ----
  		IndexPath  *ipath = (IndexPath *) lfirst(lc);
  
  		if (index->amhasgettuple)
! 			add_path(rel, (Path *) ipath, grouped);
  
! 		if (!grouped && index->amhasgetbitmap &&
  			(ipath->path.pathkeys == NIL ||
  			 ipath->indexselectivity < 1.0))
  			*bitindexpaths = lappend(*bitindexpaths, ipath);
*************** get_index_paths(PlannerInfo *root, RelOp
*** 802,815 ****
  	 * natively, generate bitmap scan paths relying on executor-managed
  	 * ScalarArrayOpExpr.
  	 */
! 	if (skip_nonnative_saop)
  	{
  		indexpaths = build_index_paths(root, rel,
  									   index, clauses,
  									   false,
  									   ST_BITMAPSCAN,
  									   NULL,
! 									   NULL);
  		*bitindexpaths = list_concat(*bitindexpaths, indexpaths);
  	}
  }
--- 804,818 ----
  	 * natively, generate bitmap scan paths relying on executor-managed
  	 * ScalarArrayOpExpr.
  	 */
! 	if (!grouped && skip_nonnative_saop)
  	{
  		indexpaths = build_index_paths(root, rel,
  									   index, clauses,
  									   false,
  									   ST_BITMAPSCAN,
  									   NULL,
! 									   NULL,
! 									   false);
  		*bitindexpaths = list_concat(*bitindexpaths, indexpaths);
  	}
  }
*************** build_index_paths(PlannerInfo *root, Rel
*** 861,867 ****
  				  bool useful_predicate,
  				  ScanTypeControl scantype,
  				  bool *skip_nonnative_saop,
! 				  bool *skip_lower_saop)
  {
  	List	   *result = NIL;
  	IndexPath  *ipath;
--- 864,870 ----
  				  bool useful_predicate,
  				  ScanTypeControl scantype,
  				  bool *skip_nonnative_saop,
! 				  bool *skip_lower_saop, bool grouped)
  {
  	List	   *result = NIL;
  	IndexPath  *ipath;
*************** build_index_paths(PlannerInfo *root, Rel
*** 878,883 ****
--- 881,890 ----
  	bool		index_is_ordered;
  	bool		index_only_scan;
  	int			indexcol;
+ 	bool		can_agg_sorted;
+ 	List		*group_clauses, *group_exprs, *agg_exprs;
+ 	AggPath		*agg_path;
+ 	double		agg_input_rows;
  
  	/*
  	 * Check that index supports the desired scan type(s)
*************** build_index_paths(PlannerInfo *root, Rel
*** 891,896 ****
--- 898,906 ----
  		case ST_BITMAPSCAN:
  			if (!index->amhasgetbitmap)
  				return NIL;
+ 
+ 			if (grouped)
+ 				return NIL;
  			break;
  		case ST_ANYSCAN:
  			/* either or both are OK */
*************** build_index_paths(PlannerInfo *root, Rel
*** 1032,1037 ****
--- 1042,1051 ----
  	 * later merging or final output ordering, OR the index has a useful
  	 * predicate, OR an index-only scan is possible.
  	 */
+ 	can_agg_sorted = true;
+ 	group_clauses = NIL;
+ 	group_exprs = NIL;
+ 	agg_exprs = NIL;
  	if (index_clauses != NIL || useful_pathkeys != NIL || useful_predicate ||
  		index_only_scan)
  	{
*************** build_index_paths(PlannerInfo *root, Rel
*** 1048,1054 ****
  								  outer_relids,
  								  loop_count,
  								  false);
! 		result = lappend(result, ipath);
  
  		/*
  		 * If appropriate, consider parallel index scan.  We don't allow
--- 1062,1086 ----
  								  outer_relids,
  								  loop_count,
  								  false);
! 		if (!grouped)
! 			result = lappend(result, ipath);
! 		else
! 		{
! 			/* TODO Double-check if this is the correct input value. */
! 			agg_input_rows =  rel->rows * ipath->indexselectivity;
! 
! 			agg_path = create_partial_agg_sorted_path(root, (Path *) ipath,
! 													  true,
! 													  &group_clauses,
! 													  &group_exprs,
! 													  &agg_exprs,
! 													  agg_input_rows);
! 
! 			if (agg_path != NULL)
! 				result = lappend(result, agg_path);
! 			else
! 				can_agg_sorted = false;
! 		}
  
  		/*
  		 * If appropriate, consider parallel index scan.  We don't allow
*************** build_index_paths(PlannerInfo *root, Rel
*** 1077,1083 ****
  			 * using parallel workers, just free it.
  			 */
  			if (ipath->path.parallel_workers > 0)
! 				add_partial_path(rel, (Path *) ipath);
  			else
  				pfree(ipath);
  		}
--- 1109,1139 ----
  			 * using parallel workers, just free it.
  			 */
  			if (ipath->path.parallel_workers > 0)
! 			{
! 				if (!grouped)
! 					add_partial_path(rel, (Path *) ipath, grouped);
! 				else if (can_agg_sorted && outer_relids == NULL)
! 				{
! 					/* TODO Double-check if this is the correct input value. */
! 					agg_input_rows =  rel->rows * ipath->indexselectivity;
! 
! 					agg_path = create_partial_agg_sorted_path(root,
! 															  (Path *) ipath,
! 															  false,
! 															  &group_clauses,
! 															  &group_exprs,
! 															  &agg_exprs,
! 															  agg_input_rows);
! 
! 					/*
! 					 * If create_agg_sorted_path succeeded once, it should
! 					 * always do.
! 					 */
! 					Assert(agg_path != NULL);
! 
! 					add_partial_path(rel, (Path *) agg_path, grouped);
! 				}
! 			}
  			else
  				pfree(ipath);
  		}
*************** build_index_paths(PlannerInfo *root, Rel
*** 1105,1111 ****
  									  outer_relids,
  									  loop_count,
  									  false);
! 			result = lappend(result, ipath);
  
  			/* If appropriate, consider parallel index scan */
  			if (index->amcanparallel &&
--- 1161,1185 ----
  									  outer_relids,
  									  loop_count,
  									  false);
! 
! 			if (!grouped)
! 				result = lappend(result, ipath);
! 			else if (can_agg_sorted)
! 			{
! 				/* TODO Double-check if this is the correct input value. */
! 				agg_input_rows =  rel->rows * ipath->indexselectivity;
! 
! 				agg_path = create_partial_agg_sorted_path(root,
! 														  (Path *) ipath,
! 														  true,
! 														  &group_clauses,
! 														  &group_exprs,
! 														  &agg_exprs,
! 														  agg_input_rows);
! 
! 				Assert(agg_path != NULL);
! 				result = lappend(result, agg_path);
! 			}
  
  			/* If appropriate, consider parallel index scan */
  			if (index->amcanparallel &&
*************** build_index_paths(PlannerInfo *root, Rel
*** 1129,1135 ****
  				 * using parallel workers, just free it.
  				 */
  				if (ipath->path.parallel_workers > 0)
! 					add_partial_path(rel, (Path *) ipath);
  				else
  					pfree(ipath);
  			}
--- 1203,1227 ----
  				 * using parallel workers, just free it.
  				 */
  				if (ipath->path.parallel_workers > 0)
! 				{
! 					if (!grouped)
! 						add_partial_path(rel, (Path *) ipath, grouped);
! 					else if (can_agg_sorted && outer_relids == NULL)
! 					{
! 						/* TODO Double-check if this is the correct input value. */
! 						agg_input_rows =  rel->rows * ipath->indexselectivity;
! 
! 						agg_path = create_partial_agg_sorted_path(root,
! 																  (Path *) ipath,
! 																  false,
! 																  &group_clauses,
! 																  &group_exprs,
! 																  &agg_exprs,
! 																  agg_input_rows);
! 						Assert(agg_path != NULL);
! 						add_partial_path(rel, (Path *) agg_path, grouped);
! 					}
! 				}
  				else
  					pfree(ipath);
  			}
*************** build_paths_for_OR(PlannerInfo *root, Re
*** 1244,1250 ****
  									   useful_predicate,
  									   ST_BITMAPSCAN,
  									   NULL,
! 									   NULL);
  		result = list_concat(result, indexpaths);
  	}
  
--- 1336,1343 ----
  									   useful_predicate,
  									   ST_BITMAPSCAN,
  									   NULL,
! 									   NULL,
! 									   false);
  		result = list_concat(result, indexpaths);
  	}
  
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
new file mode 100644
index de7044d..212b40c
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
***************
*** 21,26 ****
--- 21,27 ----
  #include "optimizer/cost.h"
  #include "optimizer/pathnode.h"
  #include "optimizer/paths.h"
+ #include "optimizer/tlist.h"
  
  /* Hook for plugins to get control in add_paths_to_joinrel() */
  set_join_pathlist_hook_type set_join_pathlist_hook = NULL;
*************** static void try_partial_mergejoin_path(P
*** 37,65 ****
  						   List *outersortkeys,
  						   List *innersortkeys,
  						   JoinType jointype,
! 						   JoinPathExtraData *extra);
  static void sort_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
! 					 RelOptInfo *outerrel, RelOptInfo *innerrel,
! 					 JoinType jointype, JoinPathExtraData *extra);
  static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel,
  					 RelOptInfo *outerrel, RelOptInfo *innerrel,
! 					 JoinType jointype, JoinPathExtraData *extra);
  static void consider_parallel_nestloop(PlannerInfo *root,
  						   RelOptInfo *joinrel,
  						   RelOptInfo *outerrel,
  						   RelOptInfo *innerrel,
  						   JoinType jointype,
! 						   JoinPathExtraData *extra);
  static void consider_parallel_mergejoin(PlannerInfo *root,
  							RelOptInfo *joinrel,
  							RelOptInfo *outerrel,
  							RelOptInfo *innerrel,
  							JoinType jointype,
  							JoinPathExtraData *extra,
! 							Path *inner_cheapest_total);
  static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
  					 RelOptInfo *outerrel, RelOptInfo *innerrel,
! 					 JoinType jointype, JoinPathExtraData *extra);
  static List *select_mergejoin_clauses(PlannerInfo *root,
  						 RelOptInfo *joinrel,
  						 RelOptInfo *outerrel,
--- 38,86 ----
  						   List *outersortkeys,
  						   List *innersortkeys,
  						   JoinType jointype,
! 						   JoinPathExtraData *extra,
! 						   bool grouped,
! 						   bool do_aggregate);
  static void sort_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
! 								 RelOptInfo *outerrel, RelOptInfo *innerrel,
! 								 JoinType jointype, JoinPathExtraData *extra,
! 								 bool grouped);
! static void sort_inner_and_outer_common(PlannerInfo *root,
! 										RelOptInfo *joinrel,
! 										RelOptInfo *outerrel,
! 										RelOptInfo *innerrel,
! 										JoinType jointype,
! 										JoinPathExtraData *extra,
! 										bool grouped_outer,
! 										bool grouped_inner,
! 										bool do_aggregate);
  static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel,
  					 RelOptInfo *outerrel, RelOptInfo *innerrel,
! 					 JoinType jointype, JoinPathExtraData *extra,
! 					 bool grouped);
  static void consider_parallel_nestloop(PlannerInfo *root,
  						   RelOptInfo *joinrel,
  						   RelOptInfo *outerrel,
  						   RelOptInfo *innerrel,
  						   JoinType jointype,
! 						   JoinPathExtraData *extra,
! 						   bool grouped, bool do_aggregate);
  static void consider_parallel_mergejoin(PlannerInfo *root,
  							RelOptInfo *joinrel,
  							RelOptInfo *outerrel,
  							RelOptInfo *innerrel,
  							JoinType jointype,
  							JoinPathExtraData *extra,
! 							Path *inner_cheapest_total,
! 							bool grouped);
  static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
  					 RelOptInfo *outerrel, RelOptInfo *innerrel,
! 					 JoinType jointype, JoinPathExtraData *extra,
! 					 bool grouped);
! static bool is_grouped_join_target_complete(PlannerInfo *root,
! 											PathTarget *jointarget,
! 											Path *outer_path,
! 											Path *inner_path);
  static List *select_mergejoin_clauses(PlannerInfo *root,
  						 RelOptInfo *joinrel,
  						 RelOptInfo *outerrel,
*************** static void generate_mergejoin_paths(Pla
*** 76,82 ****
  						 bool useallclauses,
  						 Path *inner_cheapest_total,
  						 List *merge_pathkeys,
! 						 bool is_partial);
  
  
  /*
--- 97,106 ----
  						 bool useallclauses,
  						 Path *inner_cheapest_total,
  						 List *merge_pathkeys,
! 						 bool is_partial,
!  						 bool grouped_outer,
! 						 bool grouped_inner,
! 						 bool do_aggregate);
  
  
  /*
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 197,204 ****
  	 * sorted.  Skip this if we can't mergejoin.
  	 */
  	if (mergejoin_allowed)
  		sort_inner_and_outer(root, joinrel, outerrel, innerrel,
! 							 jointype, &extra);
  
  	/*
  	 * 2. Consider paths where the outer relation need not be explicitly
--- 221,232 ----
  	 * sorted.  Skip this if we can't mergejoin.
  	 */
  	if (mergejoin_allowed)
+ 	{
  		sort_inner_and_outer(root, joinrel, outerrel, innerrel,
! 							 jointype, &extra, false);
! 		sort_inner_and_outer(root, joinrel, outerrel, innerrel,
! 							 jointype, &extra, true);
! 	}
  
  	/*
  	 * 2. Consider paths where the outer relation need not be explicitly
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 208,215 ****
  	 * joins at all, so it wouldn't work in the prohibited cases either.)
  	 */
  	if (mergejoin_allowed)
  		match_unsorted_outer(root, joinrel, outerrel, innerrel,
! 							 jointype, &extra);
  
  #ifdef NOT_USED
  
--- 236,247 ----
  	 * joins at all, so it wouldn't work in the prohibited cases either.)
  	 */
  	if (mergejoin_allowed)
+ 	{
  		match_unsorted_outer(root, joinrel, outerrel, innerrel,
! 							 jointype, &extra, false);
! 		match_unsorted_outer(root, joinrel, outerrel, innerrel,
! 							 jointype, &extra, true);
! 	}
  
  #ifdef NOT_USED
  
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 235,242 ****
  	 * joins, because there may be no other alternative.
  	 */
  	if (enable_hashjoin || jointype == JOIN_FULL)
  		hash_inner_and_outer(root, joinrel, outerrel, innerrel,
! 							 jointype, &extra);
  
  	/*
  	 * 5. If inner and outer relations are foreign tables (or joins) belonging
--- 267,278 ----
  	 * joins, because there may be no other alternative.
  	 */
  	if (enable_hashjoin || jointype == JOIN_FULL)
+ 	{
  		hash_inner_and_outer(root, joinrel, outerrel, innerrel,
! 							 jointype, &extra, false);
! 		hash_inner_and_outer(root, joinrel, outerrel, innerrel,
! 							 jointype, &extra, true);
! 	}
  
  	/*
  	 * 5. If inner and outer relations are foreign tables (or joins) belonging
*************** try_nestloop_path(PlannerInfo *root,
*** 300,309 ****
  				  Path *inner_path,
  				  List *pathkeys,
  				  JoinType jointype,
! 				  JoinPathExtraData *extra)
  {
  	Relids		required_outer;
  	JoinCostWorkspace workspace;
  
  	/*
  	 * Check to see if proposed path is still parameterized, and reject if the
--- 336,355 ----
  				  Path *inner_path,
  				  List *pathkeys,
  				  JoinType jointype,
! 				  JoinPathExtraData *extra,
! 				  bool grouped,
! 				  bool do_aggregate)
  {
  	Relids		required_outer;
  	JoinCostWorkspace workspace;
+ 	Path		*join_path;
+ 	PathTarget	*join_target;
+ 
+ 	/* Caller should not request aggregation w/o grouped output. */
+ 	Assert(!do_aggregate || grouped);
+ 
+ 	/* GroupedPathInfo is necessary for us to produce a grouped set. */
+ 	Assert(joinrel->gpi != NULL || !grouped);
  
  	/*
  	 * Check to see if proposed path is still parameterized, and reject if the
*************** try_nestloop_path(PlannerInfo *root,
*** 311,329 ****
  	 * says to allow it anyway.  Also, we must reject if have_dangerous_phv
  	 * doesn't like the look of it, which could only happen if the nestloop is
  	 * still parameterized.
  	 */
! 	required_outer = calc_nestloop_required_outer(outer_path,
! 												  inner_path);
! 	if (required_outer &&
! 		((!bms_overlap(required_outer, extra->param_source_rels) &&
! 		  !allow_star_schema_join(root, outer_path, inner_path)) ||
! 		 have_dangerous_phv(root,
! 							outer_path->parent->relids,
! 							PATH_REQ_OUTER(inner_path))))
  	{
! 		/* Waste no memory when we reject a path here */
! 		bms_free(required_outer);
! 		return;
  	}
  
  	/*
--- 357,379 ----
  	 * says to allow it anyway.  Also, we must reject if have_dangerous_phv
  	 * doesn't like the look of it, which could only happen if the nestloop is
  	 * still parameterized.
+ 	 *
+ 	 * Grouped path should never be parameterized.
  	 */
! 	required_outer = calc_nestloop_required_outer(outer_path, inner_path);
! 	if (required_outer)
  	{
! 		if (grouped ||
! 			(!bms_overlap(required_outer, extra->param_source_rels) &&
! 			 !allow_star_schema_join(root, outer_path, inner_path)) ||
! 			have_dangerous_phv(root,
! 							   outer_path->parent->relids,
! 							   PATH_REQ_OUTER(inner_path)))
! 		{
! 			/* Waste no memory when we reject a path here */
! 			bms_free(required_outer);
! 			return;
! 		}
  	}
  
  	/*
*************** try_nestloop_path(PlannerInfo *root,
*** 339,360 ****
  						  outer_path, inner_path,
  						  extra->sjinfo, &extra->semifactors);
  
! 	if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  pathkeys, required_outer))
  	{
! 		add_path(joinrel, (Path *)
! 				 create_nestloop_path(root,
! 									  joinrel,
! 									  jointype,
! 									  &workspace,
! 									  extra->sjinfo,
! 									  &extra->semifactors,
! 									  outer_path,
! 									  inner_path,
! 									  extra->restrictlist,
! 									  pathkeys,
! 									  required_outer));
  	}
  	else
  	{
--- 389,425 ----
  						  outer_path, inner_path,
  						  extra->sjinfo, &extra->semifactors);
  
! 	/*
! 	 * Determine which target the join should produce.
! 	 *
! 	 * In the case of explicit aggregation, output of the join itself is
! 	 * plain.
! 	 */
! 	if (!grouped || do_aggregate)
! 		join_target = joinrel->reltarget;
! 	else
! 		join_target = joinrel->gpi->target;
! 
! 	join_path = (Path *) create_nestloop_path(root, joinrel, jointype,
! 											  &workspace, extra->sjinfo,
! 											  &extra->semifactors,
! 											  outer_path, inner_path,
! 											  extra->restrictlist, pathkeys,
! 											  required_outer, join_target);
! 
! 	/* Do partial aggregation if needed. */
! 	if (do_aggregate && required_outer == NULL)
! 	{
! 		create_grouped_path(root, joinrel, join_path, true, false,
! 							AGG_HASHED);
! 		create_grouped_path(root, joinrel, join_path, true, false,
! 							AGG_SORTED);
! 	}
! 	else if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  pathkeys, required_outer, grouped))
  	{
! 		add_path(joinrel, join_path, grouped);
  	}
  	else
  	{
*************** try_partial_nestloop_path(PlannerInfo *r
*** 375,383 ****
  						  Path *inner_path,
  						  List *pathkeys,
  						  JoinType jointype,
! 						  JoinPathExtraData *extra)
  {
  	JoinCostWorkspace workspace;
  
  	/*
  	 * If the inner path is parameterized, the parameterization must be fully
--- 440,456 ----
  						  Path *inner_path,
  						  List *pathkeys,
  						  JoinType jointype,
! 						  JoinPathExtraData *extra,
! 						  bool grouped,
! 						  bool do_aggregate)
  {
  	JoinCostWorkspace workspace;
+ 	Path		*join_path;
+ 	PathTarget	*join_target;
+ 
+ 	/* The same checks we do in try_nestloop_path. */
+ 	Assert(!do_aggregate || grouped);
+ 	Assert(joinrel->gpi != NULL || !grouped);
  
  	/*
  	 * If the inner path is parameterized, the parameterization must be fully
*************** try_partial_nestloop_path(PlannerInfo *r
*** 401,422 ****
  	initial_cost_nestloop(root, &workspace, jointype,
  						  outer_path, inner_path,
  						  extra->sjinfo, &extra->semifactors);
! 	if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys))
  		return;
  
! 	/* Might be good enough to be worth trying, so let's try it. */
! 	add_partial_path(joinrel, (Path *)
! 					 create_nestloop_path(root,
! 										  joinrel,
! 										  jointype,
! 										  &workspace,
! 										  extra->sjinfo,
! 										  &extra->semifactors,
! 										  outer_path,
! 										  inner_path,
! 										  extra->restrictlist,
! 										  pathkeys,
! 										  NULL));
  }
  
  /*
--- 474,555 ----
  	initial_cost_nestloop(root, &workspace, jointype,
  						  outer_path, inner_path,
  						  extra->sjinfo, &extra->semifactors);
! 
! 	/*
! 	 * Determine which target the join should produce.
! 	 *
! 	 * In the case of explicit aggregation, output of the join itself is
! 	 * plain.
! 	 */
! 	if (!grouped || do_aggregate)
! 		join_target = joinrel->reltarget;
! 	else
! 	{
! 		Assert(joinrel->gpi != NULL);
! 		join_target = joinrel->gpi->target;
! 	}
! 
! 	join_path = (Path *) create_nestloop_path(root, joinrel, jointype,
! 											  &workspace, extra->sjinfo,
! 											  &extra->semifactors,
! 											  outer_path, inner_path,
! 											  extra->restrictlist, pathkeys,
! 											  NULL, join_target);
! 
! 	if (do_aggregate)
! 	{
! 		create_grouped_path(root, joinrel, join_path, true, true, AGG_HASHED);
! 		create_grouped_path(root, joinrel, join_path, true, true, AGG_SORTED);
! 	}
! 	else if (add_partial_path_precheck(joinrel, workspace.total_cost,
! 									   pathkeys, grouped))
! 	{
! 		/* Might be good enough to be worth trying, so let's try it. */
! 		add_partial_path(joinrel, (Path *) join_path, grouped);
! 	}
! }
! 
! static void
! try_grouped_nestloop_path(PlannerInfo *root,
! 						  RelOptInfo *joinrel,
! 						  Path *outer_path,
! 						  Path *inner_path,
! 						  List *pathkeys,
! 						  JoinType jointype,
! 						  JoinPathExtraData *extra,
! 						  bool do_aggregate,
! 						  bool partial)
! {
! 	/*
! 	 * Missing GroupedPathInfo indicates that we should not try to create a
! 	 * grouped join.
! 	 */
! 	if (joinrel->gpi == NULL)
  		return;
  
! 	/*
! 	 * Reject the path if we're supposed to combine grouped and plain relation
! 	 * but the grouped one does not evaluate all the relevant aggregates.
! 	 */
! 	if (!do_aggregate &&
! 		!is_grouped_join_target_complete(root, joinrel->gpi->target,
! 										 outer_path, inner_path))
! 		return;
! 
! 	/*
! 	 * As repeated aggregation doesn't seem to be attractive, make sure that
! 	 * the resulting grouped relation is not parameterized.
! 	 */
! 	if (outer_path->param_info != NULL || inner_path->param_info != NULL)
! 		return;
! 
! 	if (!partial)
! 		try_nestloop_path(root, joinrel, outer_path, inner_path, pathkeys,
! 						  jointype, extra, true, do_aggregate);
! 	else
! 		try_partial_nestloop_path(root, joinrel, outer_path, inner_path,
! 								  pathkeys, jointype, extra, true,
! 								  do_aggregate);
  }
  
  /*
*************** try_mergejoin_path(PlannerInfo *root,
*** 435,444 ****
  				   List *innersortkeys,
  				   JoinType jointype,
  				   JoinPathExtraData *extra,
! 				   bool is_partial)
  {
  	Relids		required_outer;
  	JoinCostWorkspace workspace;
  
  	if (is_partial)
  	{
--- 568,587 ----
  				   List *innersortkeys,
  				   JoinType jointype,
  				   JoinPathExtraData *extra,
! 				   bool is_partial,
! 				   bool grouped,
! 				   bool do_aggregate)
  {
  	Relids		required_outer;
  	JoinCostWorkspace workspace;
+ 	Path		*join_path;
+ 	PathTarget	*join_target;
+ 
+ 	/* Caller should not request aggregation w/o grouped output. */
+ 	Assert(!do_aggregate || grouped);
+ 
+ 	/* GroupedPathInfo is necessary for us to produce a grouped set. */
+ 	Assert(joinrel->gpi != NULL || !grouped);
  
  	if (is_partial)
  	{
*************** try_mergejoin_path(PlannerInfo *root,
*** 451,472 ****
  								   outersortkeys,
  								   innersortkeys,
  								   jointype,
! 								   extra);
  		return;
  	}
  
  	/*
! 	 * Check to see if proposed path is still parameterized, and reject if the
! 	 * parameterization wouldn't be sensible.
  	 */
! 	required_outer = calc_non_nestloop_required_outer(outer_path,
! 													  inner_path);
! 	if (required_outer &&
! 		!bms_overlap(required_outer, extra->param_source_rels))
  	{
! 		/* Waste no memory when we reject a path here */
! 		bms_free(required_outer);
! 		return;
  	}
  
  	/*
--- 594,618 ----
  								   outersortkeys,
  								   innersortkeys,
  								   jointype,
! 								   extra,
! 								   grouped,
! 								   do_aggregate);
  		return;
  	}
  
  	/*
! 	 * Check to see if proposed path is still parameterized, and reject if
! 	 * it's grouped or if the parameterization wouldn't be sensible.
  	 */
! 	required_outer = calc_non_nestloop_required_outer(outer_path, inner_path);
! 	if (required_outer)
  	{
! 		if (grouped || !bms_overlap(required_outer, extra->param_source_rels))
! 		{
! 			/* Waste no memory when we reject a path here */
! 			bms_free(required_outer);
! 			return;
! 		}
  	}
  
  	/*
*************** try_mergejoin_path(PlannerInfo *root,
*** 488,511 ****
  						   outersortkeys, innersortkeys,
  						   extra->sjinfo);
  
! 	if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  pathkeys, required_outer))
  	{
! 		add_path(joinrel, (Path *)
! 				 create_mergejoin_path(root,
! 									   joinrel,
! 									   jointype,
! 									   &workspace,
! 									   extra->sjinfo,
! 									   outer_path,
! 									   inner_path,
! 									   extra->restrictlist,
! 									   pathkeys,
! 									   required_outer,
! 									   mergeclauses,
! 									   outersortkeys,
! 									   innersortkeys));
  	}
  	else
  	{
--- 634,679 ----
  						   outersortkeys, innersortkeys,
  						   extra->sjinfo);
  
! 	/*
! 	 * Determine which target the join should produce.
! 	 *
! 	 * In the case of explicit aggregation, output of the join itself is
! 	 * plain.
! 	 */
! 	if (!grouped || do_aggregate)
! 		join_target = joinrel->reltarget;
! 	else
! 		join_target = joinrel->gpi->target;
! 
! 
! 	join_path = (Path *) create_mergejoin_path(root,
! 											   joinrel,
! 											   jointype,
! 											   &workspace,
! 											   extra->sjinfo,
! 											   outer_path,
! 											   inner_path,
! 											   extra->restrictlist,
! 											   pathkeys,
! 											   required_outer,
! 											   mergeclauses,
! 											   outersortkeys,
! 											   innersortkeys,
! 											   join_target);
! 
! 	/* Do partial aggregation if needed. */
! 	if (do_aggregate)
! 	{
! 		create_grouped_path(root, joinrel, join_path, true, false,
! 								  AGG_HASHED);
! 		create_grouped_path(root, joinrel, join_path, true, false,
! 								  AGG_SORTED);
! 	}
! 	else if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  pathkeys, required_outer, grouped))
  	{
! 		add_path(joinrel, (Path *) join_path, grouped);
  	}
  	else
  	{
*************** try_partial_mergejoin_path(PlannerInfo *
*** 529,537 ****
  						   List *outersortkeys,
  						   List *innersortkeys,
  						   JoinType jointype,
! 						   JoinPathExtraData *extra)
  {
  	JoinCostWorkspace workspace;
  
  	/*
  	 * See comments in try_partial_hashjoin_path().
--- 697,713 ----
  						   List *outersortkeys,
  						   List *innersortkeys,
  						   JoinType jointype,
! 						   JoinPathExtraData *extra,
! 						   bool grouped,
! 						   bool do_aggregate)
  {
  	JoinCostWorkspace workspace;
+ 	Path		*join_path;
+ 	PathTarget	*join_target;
+ 
+ 	/* The same checks we do in try_mergejoin_path. */
+ 	Assert(!do_aggregate || grouped);
+ 	Assert(joinrel->gpi != NULL || !grouped);
  
  	/*
  	 * See comments in try_partial_hashjoin_path().
*************** try_partial_mergejoin_path(PlannerInfo *
*** 564,587 ****
  						   outersortkeys, innersortkeys,
  						   extra->sjinfo);
  
! 	if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys))
  		return;
  
! 	/* Might be good enough to be worth trying, so let's try it. */
! 	add_partial_path(joinrel, (Path *)
! 					 create_mergejoin_path(root,
! 										   joinrel,
! 										   jointype,
! 										   &workspace,
! 										   extra->sjinfo,
! 										   outer_path,
! 										   inner_path,
! 										   extra->restrictlist,
! 										   pathkeys,
! 										   NULL,
! 										   mergeclauses,
! 										   outersortkeys,
! 										   innersortkeys));
  }
  
  /*
--- 740,910 ----
  						   outersortkeys, innersortkeys,
  						   extra->sjinfo);
  
! 	/*
! 	 * Determine which target the join should produce.
! 	 *
! 	 * In the case of explicit aggregation, output of the join itself is
! 	 * plain.
! 	 */
! 	if (!grouped || do_aggregate)
! 		join_target = joinrel->reltarget;
! 	else
! 	{
! 		Assert(joinrel->gpi != NULL);
! 		join_target = joinrel->gpi->target;
! 	}
! 
! 	join_path = (Path *) create_mergejoin_path(root,
! 											   joinrel,
! 											   jointype,
! 											   &workspace,
! 											   extra->sjinfo,
! 											   outer_path,
! 											   inner_path,
! 											   extra->restrictlist,
! 											   pathkeys,
! 											   NULL,
! 											   mergeclauses,
! 											   outersortkeys,
! 											   innersortkeys,
! 											   join_target);
! 
! 	if (do_aggregate)
! 	{
! 		create_grouped_path(root, joinrel, join_path, true, true, AGG_HASHED);
! 		create_grouped_path(root, joinrel, join_path, true, true, AGG_SORTED);
! 	}
! 	else if (add_partial_path_precheck(joinrel, workspace.total_cost,
! 									   pathkeys, grouped))
! 	{
! 		/* Might be good enough to be worth trying, so let's try it. */
! 		add_partial_path(joinrel, (Path *) join_path, grouped);
! 	}
! }
! 
! static void
! try_grouped_mergejoin_path(PlannerInfo *root,
! 						   RelOptInfo *joinrel,
! 						   Path *outer_path,
! 						   Path *inner_path,
! 						   List *pathkeys,
! 						   List *mergeclauses,
! 						   List *outersortkeys,
! 						   List *innersortkeys,
! 						   JoinType jointype,
! 						   JoinPathExtraData *extra,
! 						   bool partial,
! 						   bool do_aggregate)
! {
! 	/*
! 	 * Missing GroupedPathInfo indicates that we should not try to create a
! 	 * grouped join.
! 	 */
! 	if (joinrel->gpi == NULL)
  		return;
  
! 	/*
! 	 * Reject the path if we're supposed to combine grouped and plain relation
! 	 * but the grouped one does not evaluate all the relevant aggregates.
! 	 */
! 	if (!do_aggregate &&
! 		!is_grouped_join_target_complete(root, joinrel->gpi->target,
! 										 outer_path, inner_path))
! 		return;
! 
! 	/*
! 	 * As repeated aggregation doesn't seem to be attractive, make sure that
! 	 * the resulting grouped relation is not parameterized.
! 	 */
! 	if (outer_path->param_info != NULL || inner_path->param_info != NULL)
! 		return;
! 
! 	if (!partial)
! 		try_mergejoin_path(root, joinrel, outer_path, inner_path, pathkeys,
! 						   mergeclauses, outersortkeys, innersortkeys,
! 						   jointype, extra, false, true, do_aggregate);
! 	else
! 		try_partial_mergejoin_path(root, joinrel, outer_path, inner_path,
! 								   pathkeys,
! 								   mergeclauses, outersortkeys, innersortkeys,
! 								   jointype, extra, true, do_aggregate);
! }
! 
! static void
! try_mergejoin_path_common(PlannerInfo *root,
! 						  RelOptInfo *joinrel,
! 						  Path *outer_path,
! 						  Path *inner_path,
! 						  List *pathkeys,
! 						  List *mergeclauses,
! 						  List *outersortkeys,
! 						  List *innersortkeys,
! 						  JoinType jointype,
! 						  JoinPathExtraData *extra,
! 						  bool partial,
! 						  bool grouped_outer,
! 						  bool grouped_inner,
! 						  bool do_aggregate)
! {
! 	bool		grouped_join;
! 
! 	grouped_join = grouped_outer || grouped_inner || do_aggregate;
! 
! 	/* Join of two grouped paths is not supported. */
! 	Assert(!(grouped_outer && grouped_inner));
! 
! 	if (!grouped_join)
! 	{
! 		/* Only join plain paths. */
! 		try_mergejoin_path(root,
! 						   joinrel,
! 						   outer_path,
! 						   inner_path,
! 						   pathkeys,
! 						   mergeclauses,
! 						   outersortkeys,
! 						   innersortkeys,
! 						   jointype,
! 						   extra,
! 						   partial,
! 						   false, false);
! 	}
! 	else if (grouped_outer || grouped_inner)
! 	{
! 		Assert(!do_aggregate);
! 
! 		/*
! 		 * Exactly one of the input paths is grouped, so create a grouped join
! 		 * path.
! 		 */
! 		try_grouped_mergejoin_path(root,
! 								   joinrel,
! 								   outer_path,
! 								   inner_path,
! 								   pathkeys,
! 								   mergeclauses,
! 								   outersortkeys,
! 								   innersortkeys,
! 								   jointype,
! 								   extra,
! 								   partial,
! 								   false);
! 	}
! 	/* Preform explicit aggregation only if suitable target exists. */
! 	else if (joinrel->gpi != NULL)
! 	{
! 		try_grouped_mergejoin_path(root,
! 								   joinrel,
! 								   outer_path,
! 								   inner_path,
! 								   pathkeys,
! 								   mergeclauses,
! 								   outersortkeys,
! 								   innersortkeys,
! 								   jointype,
! 								   extra,
! 								   partial, true);
! 	}
  }
  
  /*
*************** try_hashjoin_path(PlannerInfo *root,
*** 596,644 ****
  				  Path *inner_path,
  				  List *hashclauses,
  				  JoinType jointype,
! 				  JoinPathExtraData *extra)
  {
  	Relids		required_outer;
  	JoinCostWorkspace workspace;
  
  	/*
! 	 * Check to see if proposed path is still parameterized, and reject if the
! 	 * parameterization wouldn't be sensible.
  	 */
! 	required_outer = calc_non_nestloop_required_outer(outer_path,
! 													  inner_path);
! 	if (required_outer &&
! 		!bms_overlap(required_outer, extra->param_source_rels))
  	{
! 		/* Waste no memory when we reject a path here */
! 		bms_free(required_outer);
! 		return;
  	}
  
  	/*
  	 * See comments in try_nestloop_path().  Also note that hashjoin paths
  	 * never have any output pathkeys, per comments in create_hashjoin_path.
  	 */
  	initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
! 						  outer_path, inner_path,
! 						  extra->sjinfo, &extra->semifactors);
  
! 	if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  NIL, required_outer))
  	{
! 		add_path(joinrel, (Path *)
! 				 create_hashjoin_path(root,
! 									  joinrel,
! 									  jointype,
! 									  &workspace,
! 									  extra->sjinfo,
! 									  &extra->semifactors,
! 									  outer_path,
! 									  inner_path,
! 									  extra->restrictlist,
! 									  required_outer,
! 									  hashclauses));
  	}
  	else
  	{
--- 919,994 ----
  				  Path *inner_path,
  				  List *hashclauses,
  				  JoinType jointype,
! 				  JoinPathExtraData *extra,
! 				  bool grouped,
! 				  bool do_aggregate)
  {
  	Relids		required_outer;
  	JoinCostWorkspace workspace;
+ 	Path		*join_path;
+ 	PathTarget	*join_target;
+ 
+ 	/* Caller should not request aggregation w/o grouped output. */
+ 	Assert(!do_aggregate || grouped);
+ 
+ 	/* GroupedPathInfo is necessary for us to produce a grouped set. */
+ 	Assert(joinrel->gpi != NULL || !grouped);
  
  	/*
! 	 * Check to see if proposed path is still parameterized, and reject if
! 	 * it's grouped or if the parameterization wouldn't be sensible.
  	 */
! 	required_outer = calc_non_nestloop_required_outer(outer_path, inner_path);
! 	if (required_outer)
  	{
! 		if (grouped || !bms_overlap(required_outer, extra->param_source_rels))
! 		{
! 			/* Waste no memory when we reject a path here */
! 			bms_free(required_outer);
! 			return;
! 		}
  	}
  
  	/*
  	 * See comments in try_nestloop_path().  Also note that hashjoin paths
  	 * never have any output pathkeys, per comments in create_hashjoin_path.
+ 	 *
+ 	 * TODO Need to consider aggregation here?
  	 */
  	initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
! 						  outer_path, inner_path, extra->sjinfo, &extra->semifactors);
  
! 	/*
! 	 * Determine which target the join should produce.
! 	 *
! 	 * In the case of explicit aggregation, output of the join itself is
! 	 * plain.
! 	 */
! 	if (!grouped || do_aggregate)
! 		join_target = joinrel->reltarget;
! 	else
! 		join_target = joinrel->gpi->target;
! 
! 	join_path = (Path *) create_hashjoin_path(root, joinrel, jointype,
! 											  &workspace,
! 											  extra->sjinfo,
! 											  &extra->semifactors,
! 											  outer_path, inner_path,
! 											  extra->restrictlist,
! 											  required_outer, hashclauses,
! 											  join_target);
! 
! 	/* Do partial aggregation if needed. */
! 	if (do_aggregate)
! 	{
! 		create_grouped_path(root, joinrel, join_path, true, false,
! 								  AGG_HASHED);
! 	}
! 	else if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  NIL, required_outer, grouped))
  	{
! 		add_path(joinrel, (Path *) join_path, grouped);
  	}
  	else
  	{
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 659,667 ****
  						  Path *inner_path,
  						  List *hashclauses,
  						  JoinType jointype,
! 						  JoinPathExtraData *extra)
  {
  	JoinCostWorkspace workspace;
  
  	/*
  	 * If the inner path is parameterized, the parameterization must be fully
--- 1009,1025 ----
  						  Path *inner_path,
  						  List *hashclauses,
  						  JoinType jointype,
! 						  JoinPathExtraData *extra,
! 						  bool grouped,
! 						  bool do_aggregate)
  {
  	JoinCostWorkspace workspace;
+ 	Path		*join_path;
+ 	PathTarget	*join_target;
+ 
+ 	/* The same checks we do in try_hashjoin_path. */
+ 	Assert(!do_aggregate || grouped);
+ 	Assert(joinrel->gpi != NULL || !grouped);
  
  	/*
  	 * If the inner path is parameterized, the parameterization must be fully
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 685,706 ****
  	initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
  						  outer_path, inner_path,
  						  extra->sjinfo, &extra->semifactors);
! 	if (!add_partial_path_precheck(joinrel, workspace.total_cost, NIL))
  		return;
  
! 	/* Might be good enough to be worth trying, so let's try it. */
! 	add_partial_path(joinrel, (Path *)
! 					 create_hashjoin_path(root,
! 										  joinrel,
! 										  jointype,
! 										  &workspace,
! 										  extra->sjinfo,
! 										  &extra->semifactors,
! 										  outer_path,
! 										  inner_path,
! 										  extra->restrictlist,
! 										  NULL,
! 										  hashclauses));
  }
  
  /*
--- 1043,1139 ----
  	initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
  						  outer_path, inner_path,
  						  extra->sjinfo, &extra->semifactors);
! 
! 	/*
! 	 * Determine which target the join should produce.
! 	 *
! 	 * In the case of explicit aggregation, output of the join itself is
! 	 * plain.
! 	 */
! 	if (!grouped || do_aggregate)
! 		join_target = joinrel->reltarget;
! 	else
! 	{
! 		Assert(joinrel->gpi != NULL);
! 		join_target = joinrel->gpi->target;
! 	}
! 
! 	join_path = (Path *) create_hashjoin_path(root, joinrel, jointype,
! 											  &workspace,
! 											  extra->sjinfo,
! 											  &extra->semifactors,
! 											  outer_path, inner_path,
! 											  extra->restrictlist, NULL,
! 											  hashclauses, join_target);
! 
! 	/* Do partial aggregation if needed. */
! 	if (do_aggregate)
! 	{
! 		create_grouped_path(root, joinrel, join_path, true, true, AGG_HASHED);
! 	}
! 	else if (add_partial_path_precheck(joinrel, workspace.total_cost,
! 									   NIL, grouped))
! 	{
! 		add_partial_path(joinrel, (Path *) join_path , grouped);
! 	}
! }
! 
! /*
!  * Create a new grouped hash join path by joining a grouped path to plain
!  * (non-grouped) one, or by joining 2 plain relations and applying grouping on
!  * the result.
!  *
!  * Joining of 2 grouped paths is not supported. If a grouped relation A was
!  * joined to grouped relation B, then the grouping of B reduces the number of
!  * times each group of A is appears in the join output. This makes difference
!  * for some aggregates, e.g. sum().
!  *
!  * If do_aggregate is true, neither input rel is grouped so we need to
!  * aggregate the join result explicitly.
!  *
!  * partial argument tells whether the join path should be considered partial.
!  */
! static void
! try_grouped_hashjoin_path(PlannerInfo *root,
! 						  RelOptInfo *joinrel,
! 						  Path *outer_path,
! 						  Path *inner_path,
! 						  List *hashclauses,
! 						  JoinType jointype,
! 						  JoinPathExtraData *extra,
! 						  bool do_aggregate,
! 						  bool partial)
! {
! 	/*
! 	 * Missing GroupedPathInfo indicates that we should not try to create a
! 	 * grouped join.
! 	 */
! 	if (joinrel->gpi == NULL)
  		return;
  
! 	/*
! 	 * Reject the path if we're supposed to combine grouped and plain relation
! 	 * but the grouped one does not evaluate all the relevant aggregates.
! 	 */
! 	if (!do_aggregate &&
! 		!is_grouped_join_target_complete(root, joinrel->gpi->target,
! 										 outer_path, inner_path))
! 		return;
! 
! 	/*
! 	 * As repeated aggregation doesn't seem to be attractive, make sure that
! 	 * the resulting grouped relation is not parameterized.
! 	 */
! 	if (outer_path->param_info != NULL || inner_path->param_info != NULL)
! 		return;
! 
! 	if (!partial)
! 		try_hashjoin_path(root, joinrel, outer_path, inner_path, hashclauses,
! 						  jointype, extra, true, do_aggregate);
! 	else
! 		try_partial_hashjoin_path(root, joinrel, outer_path, inner_path,
! 								  hashclauses, jointype, extra, true,
! 								  do_aggregate);
  }
  
  /*
*************** sort_inner_and_outer(PlannerInfo *root,
*** 751,757 ****
  					 RelOptInfo *outerrel,
  					 RelOptInfo *innerrel,
  					 JoinType jointype,
! 					 JoinPathExtraData *extra)
  {
  	JoinType	save_jointype = jointype;
  	Path	   *outer_path;
--- 1184,1223 ----
  					 RelOptInfo *outerrel,
  					 RelOptInfo *innerrel,
  					 JoinType jointype,
! 					 JoinPathExtraData *extra,
! 					 bool grouped)
! {
! 	if (!grouped)
! 	{
! 		sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! 									jointype, extra, false, false, false);
! 	}
! 	else
! 	{
! 		/* Use all the supported strategies to generate grouped join. */
! 		sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! 									jointype, extra, true, false, false);
! 		sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! 									jointype, extra, false, true, false);
! 		sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! 									jointype, extra, false, false, true);
! 	}
! }
! 
! /*
!  * TODO As merge_pathkeys shouldn't differ across execution, use a separate
!  * function to derive them and pass them here in a list.
!  */
! static void
! sort_inner_and_outer_common(PlannerInfo *root,
! 							RelOptInfo *joinrel,
! 							RelOptInfo *outerrel,
! 							RelOptInfo *innerrel,
! 							JoinType jointype,
! 							JoinPathExtraData *extra,
! 							bool grouped_outer,
! 							bool grouped_inner,
! 							bool do_aggregate)
  {
  	JoinType	save_jointype = jointype;
  	Path	   *outer_path;
*************** sort_inner_and_outer(PlannerInfo *root,
*** 760,765 ****
--- 1226,1232 ----
  	Path	   *cheapest_safe_inner = NULL;
  	List	   *all_pathkeys;
  	ListCell   *l;
+ 	bool	grouped_result;
  
  	/*
  	 * We only consider the cheapest-total-cost input paths, since we are
*************** sort_inner_and_outer(PlannerInfo *root,
*** 774,781 ****
  	 * against mergejoins with parameterized inputs; see comments in
  	 * src/backend/optimizer/README.
  	 */
! 	outer_path = outerrel->cheapest_total_path;
! 	inner_path = innerrel->cheapest_total_path;
  
  	/*
  	 * If either cheapest-total path is parameterized by the other rel, we
--- 1241,1267 ----
  	 * against mergejoins with parameterized inputs; see comments in
  	 * src/backend/optimizer/README.
  	 */
! 	if (grouped_outer)
! 	{
! 		if (outerrel->gpi != NULL && outerrel->gpi->pathlist != NIL)
! 			outer_path = linitial(outerrel->gpi->pathlist);
! 		else
! 			return;
! 	}
! 	else
! 		outer_path = outerrel->cheapest_total_path;
! 
! 	if (grouped_inner)
! 	{
! 		if (innerrel->gpi != NULL && innerrel->gpi->pathlist != NIL)
! 			inner_path = linitial(innerrel->gpi->pathlist);
! 		else
! 			return;
! 	}
! 	else
! 		inner_path = innerrel->cheapest_total_path;
! 
! 	grouped_result = grouped_outer || grouped_inner || do_aggregate;
  
  	/*
  	 * If either cheapest-total path is parameterized by the other rel, we
*************** sort_inner_and_outer(PlannerInfo *root,
*** 821,833 ****
  		outerrel->partial_pathlist != NIL &&
  		bms_is_empty(joinrel->lateral_relids))
  	{
! 		cheapest_partial_outer = (Path *) linitial(outerrel->partial_pathlist);
  
  		if (inner_path->parallel_safe)
  			cheapest_safe_inner = inner_path;
  		else if (save_jointype != JOIN_UNIQUE_INNER)
  			cheapest_safe_inner =
! 				get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
  	}
  
  	/*
--- 1307,1356 ----
  		outerrel->partial_pathlist != NIL &&
  		bms_is_empty(joinrel->lateral_relids))
  	{
! 		if (grouped_outer)
! 		{
! 			if (outerrel->gpi != NULL && outerrel->gpi->partial_pathlist != NIL)
! 				cheapest_partial_outer = (Path *)
! 					linitial(outerrel->gpi->partial_pathlist);
! 			else
! 				return;
! 		}
! 		else
! 			cheapest_partial_outer = (Path *)
! 				linitial(outerrel->partial_pathlist);
! 
! 		if (grouped_inner)
! 		{
! 			if (innerrel->gpi != NULL && innerrel->gpi->pathlist != NIL)
! 				inner_path = linitial(innerrel->gpi->pathlist);
! 			else
! 				return;
! 		}
! 		else
! 			inner_path = innerrel->cheapest_total_path;
  
  		if (inner_path->parallel_safe)
  			cheapest_safe_inner = inner_path;
  		else if (save_jointype != JOIN_UNIQUE_INNER)
+ 		{
+ 			List	*inner_pathlist;
+ 
+ 			if (!grouped_inner)
+ 				inner_pathlist = innerrel->pathlist;
+ 			else
+ 			{
+ 				Assert(innerrel->gpi != NULL);
+ 				inner_pathlist = innerrel->gpi->pathlist;
+ 			}
+ 
+ 			/*
+ 			 * All the grouped paths should be unparameterized, so the
+ 			 * function is overly stringent in the grouped_inner case, but
+ 			 * still useful.
+ 			 */
  			cheapest_safe_inner =
! 				get_cheapest_parallel_safe_total_inner(inner_pathlist);
! 		}
  	}
  
  	/*
*************** sort_inner_and_outer(PlannerInfo *root,
*** 903,935 ****
  		 * properly.  try_mergejoin_path will detect that case and suppress an
  		 * explicit sort step, so we needn't do so here.
  		 */
! 		try_mergejoin_path(root,
! 						   joinrel,
! 						   outer_path,
! 						   inner_path,
! 						   merge_pathkeys,
! 						   cur_mergeclauses,
! 						   outerkeys,
! 						   innerkeys,
! 						   jointype,
! 						   extra,
! 						   false);
  
  		/*
  		 * If we have partial outer and parallel safe inner path then try
  		 * partial mergejoin path.
  		 */
  		if (cheapest_partial_outer && cheapest_safe_inner)
! 			try_partial_mergejoin_path(root,
! 									   joinrel,
! 									   cheapest_partial_outer,
! 									   cheapest_safe_inner,
! 									   merge_pathkeys,
! 									   cur_mergeclauses,
! 									   outerkeys,
! 									   innerkeys,
! 									   jointype,
! 									   extra);
  	}
  }
  
--- 1426,1484 ----
  		 * properly.  try_mergejoin_path will detect that case and suppress an
  		 * explicit sort step, so we needn't do so here.
  		 */
! 		if (!grouped_result)
! 			try_mergejoin_path(root,
! 							   joinrel,
! 							   outer_path,
! 							   inner_path,
! 							   merge_pathkeys,
! 							   cur_mergeclauses,
! 							   outerkeys,
! 							   innerkeys,
! 							   jointype,
! 							   extra,
! 							   false, false, false);
! 		else
! 		{
! 			try_mergejoin_path_common(root, joinrel, outer_path, inner_path,
! 									  merge_pathkeys, cur_mergeclauses,
! 									  outerkeys, innerkeys, jointype, extra,
! 									  false,
! 									  grouped_outer, grouped_inner,
! 									  do_aggregate);
! 		}
  
  		/*
  		 * If we have partial outer and parallel safe inner path then try
  		 * partial mergejoin path.
  		 */
  		if (cheapest_partial_outer && cheapest_safe_inner)
! 		{
! 			if (!grouped_result)
! 			{
! 				try_partial_mergejoin_path(root,
! 										   joinrel,
! 										   cheapest_partial_outer,
! 										   cheapest_safe_inner,
! 										   merge_pathkeys,
! 										   cur_mergeclauses,
! 										   outerkeys,
! 										   innerkeys,
! 										   jointype,
! 										   extra, false, false);
! 			}
! 			else
! 			{
! 				try_mergejoin_path_common(root, joinrel,
! 										  cheapest_partial_outer,
! 										  cheapest_safe_inner,
! 										  merge_pathkeys, cur_mergeclauses,
! 										  outerkeys, innerkeys, jointype, extra,
! 										  true,
! 										  grouped_outer, grouped_inner,
! 										  do_aggregate);
! 			}
! 		}
  	}
  }
  
*************** sort_inner_and_outer(PlannerInfo *root,
*** 946,951 ****
--- 1495,1508 ----
   * some sort key requirements).  So, we consider truncations of the
   * mergeclause list as well as the full list.  (Ideally we'd consider all
   * subsets of the mergeclause list, but that seems way too expensive.)
+  *
+  * grouped_outer - is outerpath grouped?
+  * grouped_inner - use grouped paths of innerrel?
+  * do_aggregate - apply (partial) aggregation to the output?
+  *
+  * TODO If subsequent calls often differ only by the 3 arguments above,
+  * consider a workspace structure to share useful info (eg merge clauses)
+  * across calls.
   */
  static void
  generate_mergejoin_paths(PlannerInfo *root,
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 957,963 ****
  						 bool useallclauses,
  						 Path *inner_cheapest_total,
  						 List *merge_pathkeys,
! 						 bool is_partial)
  {
  	List	   *mergeclauses;
  	List	   *innersortkeys;
--- 1514,1523 ----
  						 bool useallclauses,
  						 Path *inner_cheapest_total,
  						 List *merge_pathkeys,
! 						 bool is_partial,
! 						 bool grouped_outer,
! 						 bool grouped_inner,
! 						 bool do_aggregate)
  {
  	List	   *mergeclauses;
  	List	   *innersortkeys;
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1008,1024 ****
  	 * try_mergejoin_path will do the right thing if inner_cheapest_total is
  	 * already correctly sorted.)
  	 */
! 	try_mergejoin_path(root,
! 					   joinrel,
! 					   outerpath,
! 					   inner_cheapest_total,
! 					   merge_pathkeys,
! 					   mergeclauses,
! 					   NIL,
! 					   innersortkeys,
! 					   jointype,
! 					   extra,
! 					   is_partial);
  
  	/* Can't do anything else if inner path needs to be unique'd */
  	if (save_jointype == JOIN_UNIQUE_INNER)
--- 1568,1585 ----
  	 * try_mergejoin_path will do the right thing if inner_cheapest_total is
  	 * already correctly sorted.)
  	 */
! 	try_mergejoin_path_common(root,
! 							  joinrel,
! 							  outerpath,
! 							  inner_cheapest_total,
! 							  merge_pathkeys,
! 							  mergeclauses,
! 							  NIL,
! 							  innersortkeys,
! 							  jointype,
! 							  extra,
! 							  is_partial,
! 							  grouped_outer, grouped_inner, do_aggregate);
  
  	/* Can't do anything else if inner path needs to be unique'd */
  	if (save_jointype == JOIN_UNIQUE_INNER)
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1074,1089 ****
  
  	for (sortkeycnt = num_sortkeys; sortkeycnt > 0; sortkeycnt--)
  	{
  		Path	   *innerpath;
  		List	   *newclauses = NIL;
  
  		/*
  		 * Look for an inner path ordered well enough for the first
  		 * 'sortkeycnt' innersortkeys.  NB: trialsortkeys list is modified
  		 * destructively, which is why we made a copy...
  		 */
  		trialsortkeys = list_truncate(trialsortkeys, sortkeycnt);
! 		innerpath = get_cheapest_path_for_pathkeys(innerrel->pathlist,
  												   trialsortkeys,
  												   NULL,
  												   TOTAL_COST,
--- 1635,1656 ----
  
  	for (sortkeycnt = num_sortkeys; sortkeycnt > 0; sortkeycnt--)
  	{
+ 		List		*inner_pathlist = NIL;
  		Path	   *innerpath;
  		List	   *newclauses = NIL;
  
+ 		if (!grouped_inner)
+ 			inner_pathlist = innerrel->pathlist;
+ 		else if (innerrel->gpi != NULL)
+ 			inner_pathlist = innerrel->gpi->pathlist;
+ 
  		/*
  		 * Look for an inner path ordered well enough for the first
  		 * 'sortkeycnt' innersortkeys.  NB: trialsortkeys list is modified
  		 * destructively, which is why we made a copy...
  		 */
  		trialsortkeys = list_truncate(trialsortkeys, sortkeycnt);
! 		innerpath = get_cheapest_path_for_pathkeys(inner_pathlist,
  												   trialsortkeys,
  												   NULL,
  												   TOTAL_COST,
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1106,1126 ****
  			}
  			else
  				newclauses = mergeclauses;
! 			try_mergejoin_path(root,
! 							   joinrel,
! 							   outerpath,
! 							   innerpath,
! 							   merge_pathkeys,
! 							   newclauses,
! 							   NIL,
! 							   NIL,
! 							   jointype,
! 							   extra,
! 							   is_partial);
  			cheapest_total_inner = innerpath;
  		}
  		/* Same on the basis of cheapest startup cost ... */
! 		innerpath = get_cheapest_path_for_pathkeys(innerrel->pathlist,
  												   trialsortkeys,
  												   NULL,
  												   STARTUP_COST,
--- 1673,1697 ----
  			}
  			else
  				newclauses = mergeclauses;
! 
! 			try_mergejoin_path_common(root,
! 									  joinrel,
! 									  outerpath,
! 									  innerpath,
! 									  merge_pathkeys,
! 									  newclauses,
! 									  NIL,
! 									  NIL,
! 									  jointype,
! 									  extra,
! 									  is_partial,
! 									  grouped_outer, grouped_inner,
! 									  do_aggregate);
! 
  			cheapest_total_inner = innerpath;
  		}
  		/* Same on the basis of cheapest startup cost ... */
! 		innerpath = get_cheapest_path_for_pathkeys(inner_pathlist,
  												   trialsortkeys,
  												   NULL,
  												   STARTUP_COST,
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1151,1167 ****
  					else
  						newclauses = mergeclauses;
  				}
! 				try_mergejoin_path(root,
! 								   joinrel,
! 								   outerpath,
! 								   innerpath,
! 								   merge_pathkeys,
! 								   newclauses,
! 								   NIL,
! 								   NIL,
! 								   jointype,
! 								   extra,
! 								   is_partial);
  			}
  			cheapest_startup_inner = innerpath;
  		}
--- 1722,1740 ----
  					else
  						newclauses = mergeclauses;
  				}
! 				try_mergejoin_path_common(root,
! 										  joinrel,
! 										  outerpath,
! 										  innerpath,
! 										  merge_pathkeys,
! 										  newclauses,
! 										  NIL,
! 										  NIL,
! 										  jointype,
! 										  extra,
! 										  is_partial,
! 										  grouped_outer, grouped_inner,
! 										  do_aggregate);
  			}
  			cheapest_startup_inner = innerpath;
  		}
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1196,1201 ****
--- 1769,1776 ----
   * 'innerrel' is the inner join relation
   * 'jointype' is the type of join to do
   * 'extra' contains additional input values
+  * 'grouped' indicates that the at least one relation in the join has been
+  * aggregated.
   */
  static void
  match_unsorted_outer(PlannerInfo *root,
*************** match_unsorted_outer(PlannerInfo *root,
*** 1203,1209 ****
  					 RelOptInfo *outerrel,
  					 RelOptInfo *innerrel,
  					 JoinType jointype,
! 					 JoinPathExtraData *extra)
  {
  	JoinType	save_jointype = jointype;
  	bool		nestjoinOK;
--- 1778,1785 ----
  					 RelOptInfo *outerrel,
  					 RelOptInfo *innerrel,
  					 JoinType jointype,
! 					 JoinPathExtraData *extra,
! 					 bool grouped)
  {
  	JoinType	save_jointype = jointype;
  	bool		nestjoinOK;
*************** match_unsorted_outer(PlannerInfo *root,
*** 1213,1218 ****
--- 1789,1816 ----
  	ListCell   *lc1;
  
  	/*
+ 	 * If grouped join path is requested, we ignore cases where either input
+ 	 * path needs to be unique. For each side we should expect either grouped
+ 	 * or plain relation, which differ quite a bit.
+ 	 *
+ 	 * XXX Although unique-ification of grouped path might result in too
+ 	 * expensive input path (note that grouped input relation is not
+ 	 * necessarily unique, regardless the grouping keys --- one or more plain
+ 	 * relation could already have been joined to it), we might want to
+ 	 * unique-ify the input relation in the future at least in the case it's a
+ 	 * plain relation.
+ 	 *
+ 	 * (Materialization is not involved in grouped paths for similar reasons.)
+ 	 */
+ 	if (grouped &&
+ 		(jointype == JOIN_UNIQUE_OUTER || jointype == JOIN_UNIQUE_INNER))
+ 		return;
+ 
+ 	/* No grouped join w/o grouped target. */
+ 	if (grouped && joinrel->gpi == NULL)
+ 		return;
+ 
+ 	/*
  	 * Nestloop only supports inner, left, semi, and anti joins.  Also, if we
  	 * are doing a right or full mergejoin, we must use *all* the mergeclauses
  	 * as join clauses, else we will not have a valid plan.  (Although these
*************** match_unsorted_outer(PlannerInfo *root,
*** 1268,1274 ****
  			create_unique_path(root, innerrel, inner_cheapest_total, extra->sjinfo);
  		Assert(inner_cheapest_total);
  	}
! 	else if (nestjoinOK)
  	{
  		/*
  		 * Consider materializing the cheapest inner path, unless
--- 1866,1872 ----
  			create_unique_path(root, innerrel, inner_cheapest_total, extra->sjinfo);
  		Assert(inner_cheapest_total);
  	}
! 	else if (nestjoinOK && !grouped)
  	{
  		/*
  		 * Consider materializing the cheapest inner path, unless
*************** match_unsorted_outer(PlannerInfo *root,
*** 1299,1304 ****
--- 1897,1904 ----
  		 */
  		if (save_jointype == JOIN_UNIQUE_OUTER)
  		{
+ 			Assert(!grouped);
+ 
  			if (outerpath != outerrel->cheapest_total_path)
  				continue;
  			outerpath = (Path *) create_unique_path(root, outerrel,
*************** match_unsorted_outer(PlannerInfo *root,
*** 1326,1332 ****
  							  inner_cheapest_total,
  							  merge_pathkeys,
  							  jointype,
! 							  extra);
  		}
  		else if (nestjoinOK)
  		{
--- 1926,1933 ----
  							  inner_cheapest_total,
  							  merge_pathkeys,
  							  jointype,
! 							  extra,
! 							  false, false);
  		}
  		else if (nestjoinOK)
  		{
*************** match_unsorted_outer(PlannerInfo *root,
*** 1342,1365 ****
  			{
  				Path	   *innerpath = (Path *) lfirst(lc2);
  
! 				try_nestloop_path(root,
! 								  joinrel,
! 								  outerpath,
! 								  innerpath,
! 								  merge_pathkeys,
! 								  jointype,
! 								  extra);
  			}
  
! 			/* Also consider materialized form of the cheapest inner path */
! 			if (matpath != NULL)
  				try_nestloop_path(root,
  								  joinrel,
  								  outerpath,
  								  matpath,
  								  merge_pathkeys,
  								  jointype,
! 								  extra);
  		}
  
  		/* Can't do anything else if outer path needs to be unique'd */
--- 1943,1988 ----
  			{
  				Path	   *innerpath = (Path *) lfirst(lc2);
  
! 				if (!grouped)
! 					try_nestloop_path(root,
! 									  joinrel,
! 									  outerpath,
! 									  innerpath,
! 									  merge_pathkeys,
! 									  jointype,
! 									  extra, false, false);
! 				else
! 				{
! 					/*
! 					 * Since both input paths are plain, request explicit
! 					 * aggregation.
! 					 */
! 					try_grouped_nestloop_path(root,
! 											  joinrel,
! 											  outerpath,
! 											  innerpath,
! 											  merge_pathkeys,
! 											  jointype,
! 											  extra,
! 											  true,
! 											  false);
! 				}
  			}
  
! 			/*
! 			 * Also consider materialized form of the cheapest inner path.
! 			 *
! 			 * (There's no matpath for grouped join.)
! 			 */
! 			if (matpath != NULL && !grouped)
  				try_nestloop_path(root,
  								  joinrel,
  								  outerpath,
  								  matpath,
  								  merge_pathkeys,
  								  jointype,
! 								  extra,
! 								  false, false);
  		}
  
  		/* Can't do anything else if outer path needs to be unique'd */
*************** match_unsorted_outer(PlannerInfo *root,
*** 1374,1380 ****
  		generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
  								 save_jointype, extra, useallclauses,
  								 inner_cheapest_total, merge_pathkeys,
! 								 false);
  	}
  
  	/*
--- 1997,2073 ----
  		generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
  								 save_jointype, extra, useallclauses,
  								 inner_cheapest_total, merge_pathkeys,
! 								 false, false, false, grouped);
! 
! 		/* Try to join the plain outer relation to grouped inner. */
! 		if (grouped && nestjoinOK &&
! 			save_jointype != JOIN_UNIQUE_OUTER &&
! 			save_jointype != JOIN_UNIQUE_INNER &&
! 			innerrel->gpi != NULL && outerrel->gpi == NULL)
! 		{
! 			Path	*inner_cheapest_grouped = (Path *) linitial(innerrel->gpi->pathlist);
! 
! 			if (PATH_PARAM_BY_REL(inner_cheapest_grouped, outerrel))
! 				continue;
! 
! 			/* XXX Shouldn't Assert() be used here instead? */
! 			if (PATH_PARAM_BY_REL(outerpath, innerrel))
! 				continue;
! 
! 			/*
! 			 * Only outer grouped path is interesting in this case: grouped
! 			 * path on the inner side of NL join would imply repeated
! 			 * aggregation somewhere in the inner path.
! 			 */
! 			generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
! 									 save_jointype, extra, useallclauses,
! 									 inner_cheapest_grouped, merge_pathkeys,
! 									 false, false, true, false);
! 		}
! 	}
! 
! 	/*
! 	 * Combine grouped outer and plain inner paths.
! 	 */
! 	if (grouped && nestjoinOK &&
! 		save_jointype != JOIN_UNIQUE_OUTER &&
! 		save_jointype != JOIN_UNIQUE_INNER)
! 	{
! 		/*
! 		 * If the inner rel had a grouped target, its plain paths should be
! 		 * ignored. Otherwise we could create grouped paths with different
! 		 * targets.
! 		 */
! 		if (outerrel->gpi != NULL && innerrel->gpi == NULL &&
! 			inner_cheapest_total != NULL)
! 		{
! 			/* Nested loop paths. */
! 			foreach(lc1, outerrel->gpi->pathlist)
! 			{
! 				Path	   *outerpath = (Path *) lfirst(lc1);
! 				List	*merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
! 															  outerpath->pathkeys);
! 
! 				if (PATH_PARAM_BY_REL(outerpath, innerrel))
! 					continue;
! 
! 				try_grouped_nestloop_path(root,
! 										  joinrel,
! 										  outerpath,
! 										  inner_cheapest_total,
! 										  merge_pathkeys,
! 										  jointype,
! 										  extra,
! 										  false,
! 										  false);
! 
! 				/* Merge join paths. */
! 				generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
! 										 save_jointype, extra, useallclauses,
! 										 inner_cheapest_total, merge_pathkeys,
! 										 false, true, false, false);
! 			}
! 		}
  	}
  
  	/*
*************** match_unsorted_outer(PlannerInfo *root,
*** 1394,1401 ****
  		bms_is_empty(joinrel->lateral_relids))
  	{
  		if (nestjoinOK)
! 			consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! 									   save_jointype, extra);
  
  		/*
  		 * If inner_cheapest_total is NULL or non parallel-safe then find the
--- 2087,2107 ----
  		bms_is_empty(joinrel->lateral_relids))
  	{
  		if (nestjoinOK)
! 		{
! 			if (!grouped)
! 				/* Plain partial paths. */
! 				consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! 									   save_jointype, extra, false, false);
! 			else
! 			{
! 				/* Grouped partial paths with explicit aggregation. */
! 				consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! 										   save_jointype, extra, true, true);
! 				/* Grouped partial paths w/o explicit aggregation. */
! 				consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! 										   save_jointype, extra, true, false);
! 			}
! 		}
  
  		/*
  		 * If inner_cheapest_total is NULL or non parallel-safe then find the
*************** match_unsorted_outer(PlannerInfo *root,
*** 1415,1421 ****
  		if (inner_cheapest_total)
  			consider_parallel_mergejoin(root, joinrel, outerrel, innerrel,
  										save_jointype, extra,
! 										inner_cheapest_total);
  	}
  }
  
--- 2121,2127 ----
  		if (inner_cheapest_total)
  			consider_parallel_mergejoin(root, joinrel, outerrel, innerrel,
  										save_jointype, extra,
! 										inner_cheapest_total, grouped);
  	}
  }
  
*************** consider_parallel_mergejoin(PlannerInfo
*** 1438,1447 ****
  							RelOptInfo *innerrel,
  							JoinType jointype,
  							JoinPathExtraData *extra,
! 							Path *inner_cheapest_total)
  {
  	ListCell   *lc1;
  
  	/* generate merge join path for each partial outer path */
  	foreach(lc1, outerrel->partial_pathlist)
  	{
--- 2144,2162 ----
  							RelOptInfo *innerrel,
  							JoinType jointype,
  							JoinPathExtraData *extra,
! 							Path *inner_cheapest_total,
! 							bool grouped)
  {
  	ListCell   *lc1;
  
+ 	if (grouped)
+ 	{
+ 		/* TODO Consider if these types should be supported. */
+ 		if (jointype == JOIN_UNIQUE_OUTER ||
+ 			jointype == JOIN_UNIQUE_INNER)
+ 			return;
+ 	}
+ 
  	/* generate merge join path for each partial outer path */
  	foreach(lc1, outerrel->partial_pathlist)
  	{
*************** consider_parallel_mergejoin(PlannerInfo
*** 1454,1462 ****
  		merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
  											 outerpath->pathkeys);
  
! 		generate_mergejoin_paths(root, joinrel, innerrel, outerpath, jointype,
! 								 extra, false, inner_cheapest_total,
! 								 merge_pathkeys, true);
  	}
  }
  
--- 2169,2224 ----
  		merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
  											 outerpath->pathkeys);
  
! 		if (!grouped)
! 			generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
! 									 jointype, extra, false,
! 									 inner_cheapest_total, merge_pathkeys,
! 									 true,
! 									 false, false, false);
! 		else
! 		{
! 			/*
! 			 * Create grouped join by joining plain rels and aggregating the
! 			 * result.
! 			 */
! 			Assert(joinrel->gpi != NULL);
! 			generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
! 									 jointype, extra, false,
! 									 inner_cheapest_total, merge_pathkeys,
! 									 true, false, false, true);
! 
! 			/* Combine the plain outer with grouped inner one(s). */
! 			if (outerrel->gpi == NULL && innerrel->gpi != NULL)
! 			{
! 				Path	*inner_cheapest_grouped = (Path *)
! 					linitial(innerrel->gpi->pathlist);
! 
! 				if (inner_cheapest_grouped != NULL &&
! 					inner_cheapest_grouped->parallel_safe)
! 					generate_mergejoin_paths(root, joinrel, innerrel,
! 											 outerpath, jointype, extra,
! 											 false, inner_cheapest_grouped,
! 											 merge_pathkeys,
! 											 true, false, true, false);
! 			}
! 		}
! 	}
! 
! 	/* In addition, try to join grouped outer to plain inner one(s).  */
! 	if (grouped && outerrel->gpi != NULL && innerrel->gpi == NULL)
! 	{
! 		foreach(lc1, outerrel->gpi->partial_pathlist)
! 		{
! 			Path	   *outerpath = (Path *) lfirst(lc1);
! 			List	   *merge_pathkeys;
! 
! 			merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
! 												 outerpath->pathkeys);
! 			generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
! 									 jointype, extra, false,
! 									 inner_cheapest_total, merge_pathkeys,
! 									 true, true, false, false);
! 		}
  	}
  }
  
*************** consider_parallel_nestloop(PlannerInfo *
*** 1477,1491 ****
  						   RelOptInfo *outerrel,
  						   RelOptInfo *innerrel,
  						   JoinType jointype,
! 						   JoinPathExtraData *extra)
  {
  	JoinType	save_jointype = jointype;
  	ListCell   *lc1;
  
  	if (jointype == JOIN_UNIQUE_INNER)
  		jointype = JOIN_INNER;
  
! 	foreach(lc1, outerrel->partial_pathlist)
  	{
  		Path	   *outerpath = (Path *) lfirst(lc1);
  		List	   *pathkeys;
--- 2239,2283 ----
  						   RelOptInfo *outerrel,
  						   RelOptInfo *innerrel,
  						   JoinType jointype,
! 						   JoinPathExtraData *extra,
! 						   bool grouped, bool do_aggregate)
  {
  	JoinType	save_jointype = jointype;
+ 	List		*outer_pathlist;
  	ListCell   *lc1;
  
+ 	if (grouped)
+ 	{
+ 		/* TODO Consider if these types should be supported. */
+ 		if (save_jointype == JOIN_UNIQUE_OUTER ||
+ 			save_jointype == JOIN_UNIQUE_INNER)
+ 			return;
+ 	}
+ 
  	if (jointype == JOIN_UNIQUE_INNER)
  		jointype = JOIN_INNER;
  
! 	if (!grouped || do_aggregate)
! 	{
! 		/*
! 		 * If creating grouped paths by explicit aggregation, the input paths
! 		 * must be plain.
! 		 */
! 		outer_pathlist = outerrel->partial_pathlist;
! 	}
! 	else if (outerrel->gpi != NULL)
! 	{
! 		/*
! 		 * Only the outer paths are accepted as grouped when we try to combine
! 		 * grouped and plain ones. Grouped inner path implies repeated
! 		 * aggregation, which doesn't sound as a good idea.
! 		 */
! 		outer_pathlist = outerrel->gpi->partial_pathlist;
! 	}
! 	else
! 		return;
! 
! 	foreach(lc1, outer_pathlist)
  	{
  		Path	   *outerpath = (Path *) lfirst(lc1);
  		List	   *pathkeys;
*************** consider_parallel_nestloop(PlannerInfo *
*** 1516,1522 ****
  			 * inner paths, but right now create_unique_path is not on board
  			 * with that.)
  			 */
! 			if (save_jointype == JOIN_UNIQUE_INNER)
  			{
  				if (innerpath != innerrel->cheapest_total_path)
  					continue;
--- 2308,2314 ----
  			 * inner paths, but right now create_unique_path is not on board
  			 * with that.)
  			 */
! 			if (save_jointype == JOIN_UNIQUE_INNER && !grouped)
  			{
  				if (innerpath != innerrel->cheapest_total_path)
  					continue;
*************** consider_parallel_nestloop(PlannerInfo *
*** 1526,1533 ****
  				Assert(innerpath);
  			}
  
! 			try_partial_nestloop_path(root, joinrel, outerpath, innerpath,
! 									  pathkeys, jointype, extra);
  		}
  	}
  }
--- 2318,2343 ----
  				Assert(innerpath);
  			}
  
! 			if (!grouped)
! 				try_partial_nestloop_path(root, joinrel, outerpath, innerpath,
! 										  pathkeys, jointype, extra,
! 										  false, false);
! 			else if (do_aggregate)
! 			{
! 				/* Request aggregation as both input rels are plain. */
! 				try_grouped_nestloop_path(root, joinrel, outerpath, innerpath,
! 										  pathkeys, jointype, extra,
! 										  true, true);
! 			}
! 			/*
! 			 * Only combine the grouped outer path with the plain inner if the
! 			 * inner relation cannot produce grouped paths. Otherwise we could
! 			 * generate grouped paths with different targets.
! 			 */
! 			else if (innerrel->gpi == NULL)
! 				try_grouped_nestloop_path(root, joinrel, outerpath, innerpath,
! 										  pathkeys, jointype, extra,
! 										  false, true);
  		}
  	}
  }
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1549,1561 ****
  					 RelOptInfo *outerrel,
  					 RelOptInfo *innerrel,
  					 JoinType jointype,
! 					 JoinPathExtraData *extra)
  {
  	JoinType	save_jointype = jointype;
  	bool		isouterjoin = IS_OUTER_JOIN(jointype);
  	List	   *hashclauses;
  	ListCell   *l;
  
  	/*
  	 * We need to build only one hashclauses list for any given pair of outer
  	 * and inner relations; all of the hashable clauses will be used as keys.
--- 2359,2376 ----
  					 RelOptInfo *outerrel,
  					 RelOptInfo *innerrel,
  					 JoinType jointype,
! 					 JoinPathExtraData *extra,
! 					 bool grouped)
  {
  	JoinType	save_jointype = jointype;
  	bool		isouterjoin = IS_OUTER_JOIN(jointype);
  	List	   *hashclauses;
  	ListCell   *l;
  
+ 	/* No grouped join w/o grouped target. */
+ 	if (grouped && joinrel->gpi == NULL)
+ 		return;
+ 
  	/*
  	 * We need to build only one hashclauses list for any given pair of outer
  	 * and inner relations; all of the hashable clauses will be used as keys.
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1605,1610 ****
--- 2420,2428 ----
  		 * can't use a hashjoin.  (There's no use looking for alternative
  		 * input paths, since these should already be the least-parameterized
  		 * available paths.)
+ 		 *
+ 		 * (The same check should work for grouped paths, as these don't
+ 		 * differ in parameterization.)
  		 */
  		if (PATH_PARAM_BY_REL(cheapest_total_outer, innerrel) ||
  			PATH_PARAM_BY_REL(cheapest_total_inner, outerrel))
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1624,1630 ****
  							  cheapest_total_inner,
  							  hashclauses,
  							  jointype,
! 							  extra);
  			/* no possibility of cheap startup here */
  		}
  		else if (jointype == JOIN_UNIQUE_INNER)
--- 2442,2449 ----
  							  cheapest_total_inner,
  							  hashclauses,
  							  jointype,
! 							  extra,
! 							  false, false);
  			/* no possibility of cheap startup here */
  		}
  		else if (jointype == JOIN_UNIQUE_INNER)
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1640,1646 ****
  							  cheapest_total_inner,
  							  hashclauses,
  							  jointype,
! 							  extra);
  			if (cheapest_startup_outer != NULL &&
  				cheapest_startup_outer != cheapest_total_outer)
  				try_hashjoin_path(root,
--- 2459,2466 ----
  							  cheapest_total_inner,
  							  hashclauses,
  							  jointype,
! 							  extra,
! 							  false, false);
  			if (cheapest_startup_outer != NULL &&
  				cheapest_startup_outer != cheapest_total_outer)
  				try_hashjoin_path(root,
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1649,1711 ****
  								  cheapest_total_inner,
  								  hashclauses,
  								  jointype,
! 								  extra);
  		}
  		else
  		{
! 			/*
! 			 * For other jointypes, we consider the cheapest startup outer
! 			 * together with the cheapest total inner, and then consider
! 			 * pairings of cheapest-total paths including parameterized ones.
! 			 * There is no use in generating parameterized paths on the basis
! 			 * of possibly cheap startup cost, so this is sufficient.
! 			 */
! 			ListCell   *lc1;
! 			ListCell   *lc2;
! 
! 			if (cheapest_startup_outer != NULL)
! 				try_hashjoin_path(root,
! 								  joinrel,
! 								  cheapest_startup_outer,
! 								  cheapest_total_inner,
! 								  hashclauses,
! 								  jointype,
! 								  extra);
! 
! 			foreach(lc1, outerrel->cheapest_parameterized_paths)
  			{
- 				Path	   *outerpath = (Path *) lfirst(lc1);
- 
  				/*
! 				 * We cannot use an outer path that is parameterized by the
! 				 * inner rel.
  				 */
! 				if (PATH_PARAM_BY_REL(outerpath, innerrel))
! 					continue;
  
! 				foreach(lc2, innerrel->cheapest_parameterized_paths)
  				{
! 					Path	   *innerpath = (Path *) lfirst(lc2);
  
  					/*
! 					 * We cannot use an inner path that is parameterized by
! 					 * the outer rel, either.
  					 */
! 					if (PATH_PARAM_BY_REL(innerpath, outerrel))
  						continue;
  
! 					if (outerpath == cheapest_startup_outer &&
! 						innerpath == cheapest_total_inner)
! 						continue;		/* already tried it */
  
! 					try_hashjoin_path(root,
! 									  joinrel,
! 									  outerpath,
! 									  innerpath,
! 									  hashclauses,
! 									  jointype,
! 									  extra);
  				}
  			}
  		}
  
--- 2469,2622 ----
  								  cheapest_total_inner,
  								  hashclauses,
  								  jointype,
! 								  extra,
! 								  false, false);
  		}
  		else
  		{
! 			if (!grouped)
  			{
  				/*
! 				 * For other jointypes, we consider the cheapest startup outer
! 				 * together with the cheapest total inner, and then consider
! 				 * pairings of cheapest-total paths including parameterized
! 				 * ones.  There is no use in generating parameterized paths on
! 				 * the basis of possibly cheap startup cost, so this is
! 				 * sufficient.
  				 */
! 				ListCell   *lc1;
  
! 				if (cheapest_startup_outer != NULL)
! 					try_hashjoin_path(root,
! 									  joinrel,
! 									  cheapest_startup_outer,
! 									  cheapest_total_inner,
! 									  hashclauses,
! 									  jointype,
! 									  extra,
! 									  false, false);
! 
! 				foreach(lc1, outerrel->cheapest_parameterized_paths)
  				{
! 					Path	   *outerpath = (Path *) lfirst(lc1);
! 					ListCell   *lc2;
  
  					/*
! 					 * We cannot use an outer path that is parameterized by the
! 					 * inner rel.
  					 */
! 					if (PATH_PARAM_BY_REL(outerpath, innerrel))
  						continue;
  
! 					foreach(lc2, innerrel->cheapest_parameterized_paths)
! 					{
! 						Path	   *innerpath = (Path *) lfirst(lc2);
  
! 						/*
! 						 * We cannot use an inner path that is parameterized by
! 						 * the outer rel, either.
! 						 */
! 						if (PATH_PARAM_BY_REL(innerpath, outerrel))
! 							continue;
! 
! 						if (outerpath == cheapest_startup_outer &&
! 							innerpath == cheapest_total_inner)
! 							continue;		/* already tried it */
! 
! 						try_hashjoin_path(root,
! 										  joinrel,
! 										  outerpath,
! 										  innerpath,
! 										  hashclauses,
! 										  jointype,
! 										  extra,
! 										  false, false);
! 					}
! 				}
! 			}
! 			else
! 			{
! 				/* Create grouped paths if possible. */
! 				/*
! 				 * TODO
! 				 *
! 				 * Consider processing JOIN_UNIQUE_INNER and JOIN_UNIQUE_OUTER
! 				 * join types, ie perform grouping of the inner / outer rel if
! 				 * it's not unique yet and if the grouping is legal.
! 				 */
! 				if (jointype == JOIN_UNIQUE_OUTER ||
! 					jointype == JOIN_UNIQUE_INNER)
! 					return;
! 
! 				/*
! 				 * Join grouped relation to non-grouped one.
! 				 *
! 				 * Do not use plain path of the input rel whose target does
! 				 * have GroupedPahtInfo. For example (assuming that join of
! 				 * two grouped rels is not supported), the only way to
! 				 * evaluate SELECT sum(a.x), sum(b.y) ... is to join "a" and
! 				 * "b" and aggregate the result. Otherwise the path target
! 				 * wouldn't match joinrel->gpi->target. TODO Move this comment
! 				 * elsewhere as it seems common to all join kinds.
! 				 */
! 				/*
! 				 * TODO Allow outer join if the grouped rel is on the
! 				 * non-nullable side.
! 				 */
! 				if (jointype == JOIN_INNER)
! 				{
! 					Path	*grouped_path, *plain_path;
! 
! 					if (outerrel->gpi != NULL &&
! 						outerrel->gpi->pathlist != NIL &&
! 						innerrel->gpi == NULL)
! 					{
! 						grouped_path = (Path *)
! 							linitial(outerrel->gpi->pathlist);
! 						plain_path = cheapest_total_inner;
! 						try_grouped_hashjoin_path(root, joinrel,
! 												  grouped_path, plain_path,
! 												  hashclauses, jointype,
! 												  extra, false, false);
! 					}
! 					else if (innerrel->gpi != NULL &&
! 							 innerrel->gpi->pathlist != NIL &&
! 							 outerrel->gpi == NULL)
! 					{
! 						grouped_path = (Path *)
! 							linitial(innerrel->gpi->pathlist);
! 						plain_path = cheapest_total_outer;
! 						try_grouped_hashjoin_path(root, joinrel, plain_path,
! 												  grouped_path, hashclauses,
! 												  jointype, extra,
! 												  false, false);
! 
! 						if (cheapest_startup_outer != NULL &&
! 							cheapest_startup_outer != cheapest_total_outer)
! 						{
! 							plain_path = cheapest_startup_outer;
! 							try_grouped_hashjoin_path(root, joinrel,
! 													  plain_path,
! 													  grouped_path,
! 													  hashclauses,
! 													  jointype, extra,
! 													  false, false);
! 						}
! 					}
  				}
+ 
+ 				/*
+ 				 * Try to join plain relations and make a grouped rel out of
+ 				 * the join.
+ 				 *
+ 				 * Since aggregation needs the whole relation, we are only
+ 				 * interested in total costs.
+ 				 */
+ 				try_grouped_hashjoin_path(root, joinrel,
+ 										  cheapest_total_outer,
+ 										  cheapest_total_inner,
+ 										  hashclauses,
+ 										  jointype, extra, true, false);
  			}
  		}
  
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1743,1758 ****
  				cheapest_safe_inner =
  					get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
  
! 			if (cheapest_safe_inner != NULL)
! 				try_partial_hashjoin_path(root, joinrel,
! 										  cheapest_partial_outer,
! 										  cheapest_safe_inner,
! 										  hashclauses, jointype, extra);
  		}
  	}
  }
  
  /*
   * select_mergejoin_clauses
   *	  Select mergejoin clauses that are usable for a particular join.
   *	  Returns a list of RestrictInfo nodes for those clauses.
--- 2654,2880 ----
  				cheapest_safe_inner =
  					get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
  
! 			if (!grouped)
! 			{
! 				if (cheapest_safe_inner != NULL)
! 					try_partial_hashjoin_path(root, joinrel,
! 											  cheapest_partial_outer,
! 											  cheapest_safe_inner,
! 											  hashclauses, jointype, extra,
! 											  false, false);
! 			}
! 			else if (joinrel->gpi != NULL)
! 			{
! 				/*
! 				 * Grouped partial path.
! 				 *
! 				 * 1. Apply aggregation to the plain partial join path.
! 				 */
! 				if (cheapest_safe_inner != NULL)
! 					try_grouped_hashjoin_path(root, joinrel,
! 											  cheapest_partial_outer,
! 											  cheapest_safe_inner,
! 											  hashclauses,
! 											  jointype, extra, true, true);
! 
! 				/*
! 				 * 2. Join the cheapest partial grouped outer path (if one
! 				 * exists) to cheapest_safe_inner (there's no reason to look
! 				 * for another inner path than what we used for non-grouped
! 				 * partial join path).
! 				 */
! 				if (outerrel->gpi != NULL &&
! 					outerrel->gpi->partial_pathlist != NIL &&
! 					innerrel->gpi == NULL &&
! 					cheapest_safe_inner != NULL)
! 				{
! 					Path	*outer_path;
! 
! 					outer_path = (Path *)
! 						linitial(outerrel->gpi->partial_pathlist);
! 
! 					try_grouped_hashjoin_path(root, joinrel, outer_path,
! 											  cheapest_safe_inner,
! 											  hashclauses,
! 											  jointype, extra, false, true);
! 				}
! 
! 				/*
! 				 * 3. Join the cheapest_partial_outer path (again, no reason
! 				 * to use different outer path than the one we used for plain
! 				 * partial join) to the cheapest grouped inner path if the
! 				 * latter exists and is parallel-safe.
! 				 */
! 				if (innerrel->gpi != NULL &&
! 					innerrel->gpi->pathlist != NIL &&
! 					outerrel->gpi == NULL)
! 				{
! 					Path	*inner_path;
! 
! 					inner_path = (Path *) linitial(innerrel->gpi->pathlist);
! 
! 					if (inner_path->parallel_safe)
! 						try_grouped_hashjoin_path(root, joinrel,
! 												  cheapest_partial_outer,
! 												  inner_path,
! 												  hashclauses,
! 												  jointype, extra,
! 												  false, true);
! 				}
! 
! 				/*
! 				 * Other combinations seem impossible because: 1. At most 1
! 				 * input relation of the join can be grouped, 2. the inner
! 				 * path must not be partial.
! 				 */
! 			}
  		}
  	}
  }
  
  /*
+  * Do the input paths emit all the aggregates contained in the grouped target
+  * of the join?
+  *
+  * The point is that one input relation might be unable to evaluate some
+  * aggregate(s), so it'll only generate plain paths. It's wrong to combine
+  * such plain paths with grouped ones that the other input rel might be able
+  * to generate because the result would miss the aggregate(s) the first
+  * relation failed to evaluate.
+  *
+  * TODO For better efficiency, consider storing Bitmapset of
+  * GroupedVarInfo.gvid in GroupedPathInfo.
+  */
+ static bool
+ is_grouped_join_target_complete(PlannerInfo *root, PathTarget *jointarget,
+ 								Path *outer_path, Path *inner_path)
+ {
+ 	RelOptInfo	*outer_rel = outer_path->parent;
+ 	RelOptInfo	*inner_rel = inner_path->parent;
+ 	ListCell	*l1;
+ 
+ 	/*
+ 	 * Join of two grouped relations is not supported.
+ 	 *
+ 	 * This actually isn't check of target completeness --- can it be located
+ 	 * elsewhere?
+ 	 */
+ 	if (outer_rel->gpi != NULL && inner_rel->gpi != NULL)
+ 		return false;
+ 
+ 	foreach(l1, jointarget->exprs)
+ 	{
+ 		Expr	*expr = (Expr *) lfirst(l1);
+ 		GroupedVar	*gvar;
+ 		GroupedVarInfo	*gvi = NULL;
+ 		ListCell	*l2;
+ 		bool	found = false;
+ 
+ 		/* Only interested in aggregates. */
+ 		if (!IsA(expr, GroupedVar))
+ 			continue;
+ 
+ 		gvar = castNode(GroupedVar, expr);
+ 
+ 		/* Find the corresponding GroupedVarInfo. */
+ 		foreach(l2, root->grouped_var_list)
+ 		{
+ 			GroupedVarInfo	*gvi_tmp = castNode(GroupedVarInfo, lfirst(l2));
+ 
+ 			if (gvi_tmp->gvid == gvar->gvid)
+ 			{
+ 				gvi = gvi_tmp;
+ 				break;
+ 			}
+ 		}
+ 		Assert(gvi != NULL);
+ 
+ 		/*
+ 		 * If any aggregate references both input relations, something went
+ 		 * wrong during construction of one of the input targets: one input
+ 		 * rel is grouped, but no grouping target should have been created for
+ 		 * it if some aggregate required more than that input rel.
+ 		 */
+ 		Assert(gvi->gv_eval_at == NULL ||
+ 			   !(bms_overlap(gvi->gv_eval_at, outer_rel->relids) &&
+ 				 bms_overlap(gvi->gv_eval_at, inner_rel->relids)));
+ 
+ 		/*
+ 		 * If the aggregate belongs to the plain relation, it probably
+ 		 * means that non-grouping expression made aggregation of that
+ 		 * input relation impossible. Since that expression is not
+ 		 * necessarily emitted by the current join, aggregation might be
+ 		 * possible here. On the other hand, aggregation of a join which
+ 		 * already contains a grouped relation does not seem too
+ 		 * beneficial.
+ 		 *
+ 		 * XXX The condition below is also met if the query contains both
+ 		 * "star aggregate" and a normal one. Since the earlier can be
+ 		 * added to any base relation, and since we don't support join of
+ 		 * 2 grouped relations, join of arbitrary 2 relations will always
+ 		 * result in a plain relation.
+ 		 *
+ 		 * XXX If we conclude that aggregation is worth, only consider
+ 		 * this test failed if target usable for aggregation cannot be
+ 		 * created (i.e. the non-grouping expression is in the output of
+ 		 * the current join).
+ 		 */
+ 		if ((outer_rel->gpi == NULL &&
+ 			 bms_overlap(gvi->gv_eval_at, outer_rel->relids))
+ 			|| (inner_rel->gpi == NULL &&
+ 				bms_overlap(gvi->gv_eval_at, inner_rel->relids)))
+ 			return false;
+ 
+ 		/* Look for the aggregate in the input targets. */
+ 		if (outer_rel->gpi != NULL)
+ 		{
+ 			/* No more than one input path should be grouped. */
+ 			Assert(inner_rel->gpi == NULL);
+ 
+ 			foreach(l2, outer_path->pathtarget->exprs)
+ 			{
+ 				expr = (Expr *) lfirst(l2);
+ 
+ 				if (!IsA(expr, GroupedVar))
+ 					continue;
+ 
+ 				gvar = castNode(GroupedVar, expr);
+ 				if (gvar->gvid == gvi->gvid)
+ 				{
+ 					found = true;
+ 					break;
+ 				}
+ 			}
+ 		}
+ 		else if (!found && inner_rel->gpi != NULL)
+ 		{
+ 			Assert(outer_rel->gpi == NULL);
+ 
+ 			foreach(l2, inner_path->pathtarget->exprs)
+ 			{
+ 				expr = (Expr *) lfirst(l2);
+ 
+ 				if (!IsA(expr, GroupedVar))
+ 					continue;
+ 
+ 				gvar = castNode(GroupedVar, expr);
+ 				if (gvar->gvid == gvi->gvid)
+ 				{
+ 					found = true;
+ 					break;
+ 				}
+ 			}
+ 		}
+ 
+ 		/* Even a single missing aggregate causes the whole test to fail. */
+ 		if (!found)
+ 			return false;
+ 	}
+ 
+ 	return true;
+ }
+ 
+ /*
   * select_mergejoin_clauses
   *	  Select mergejoin clauses that are usable for a particular join.
   *	  Returns a list of RestrictInfo nodes for those clauses.
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
new file mode 100644
index 6a0c67b..58aea01
*** a/src/backend/optimizer/path/joinrels.c
--- b/src/backend/optimizer/path/joinrels.c
*************** mark_dummy_rel(RelOptInfo *rel)
*** 1217,1223 ****
  	rel->partial_pathlist = NIL;
  
  	/* Set up the dummy path */
! 	add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL));
  
  	/* Set or update cheapest_total_path and related fields */
  	set_cheapest(rel);
--- 1217,1223 ----
  	rel->partial_pathlist = NIL;
  
  	/* Set up the dummy path */
! 	add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL), false);
  
  	/* Set or update cheapest_total_path and related fields */
  	set_cheapest(rel);
diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c
new file mode 100644
index a2fe661..91d855c
*** a/src/backend/optimizer/path/tidpath.c
--- b/src/backend/optimizer/path/tidpath.c
*************** create_tidscan_paths(PlannerInfo *root,
*** 266,270 ****
  
  	if (tidquals)
  		add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
! 												   required_outer));
  }
--- 266,270 ----
  
  	if (tidquals)
  		add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
! 												   required_outer), false);
  }
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
new file mode 100644
index 53aefbd..f19e18c
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
***************
*** 14,19 ****
--- 14,20 ----
   */
  #include "postgres.h"
  
+ #include "access/sysattr.h"
  #include "catalog/pg_type.h"
  #include "nodes/nodeFuncs.h"
  #include "optimizer/clauses.h"
***************
*** 26,31 ****
--- 27,33 ----
  #include "optimizer/planner.h"
  #include "optimizer/prep.h"
  #include "optimizer/restrictinfo.h"
+ #include "optimizer/tlist.h"
  #include "optimizer/var.h"
  #include "parser/analyze.h"
  #include "rewrite/rewriteManip.h"
*************** typedef struct PostponedQual
*** 45,50 ****
--- 47,53 ----
  } PostponedQual;
  
  
+ static void create_grouped_var_infos(PlannerInfo *root);
  static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
  						   Index rtindex);
  static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
*************** add_vars_to_targetlist(PlannerInfo *root
*** 240,245 ****
--- 243,532 ----
  	}
  }
  
+ /*
+  * Add GroupedVarInfo to grouped_var_list for each aggregate and setup
+  * GroupedPathInfo for each base relation that can product grouped paths.
+  *
+  * XXX In the future we might want to create GroupedVarInfo for grouping
+  * expressions too, so that grouping key is not limited to plain Var if the
+  * grouping takes place below the top-level join.
+  *
+  * root->group_pathkeys must be setup before this function is called.
+  */
+ extern void
+ add_grouping_info_to_base_rels(PlannerInfo *root)
+ {
+ 	int			i;
+ 
+ 	/* No grouping in the query? */
+ 	if (!root->parse->groupClause || root->group_pathkeys == NIL)
+ 		return;
+ 
+ 	/* TODO This is just for PoC. Relax the limitation later. */
+ 	if (root->parse->havingQual)
+ 		return;
+ 
+ 	/* Create GroupedVarInfo per (distinct) aggregate. */
+ 	create_grouped_var_infos(root);
+ 
+ 	/* Is no grouping is possible below the top-level join? */
+ 	if (root->grouped_var_list == NIL)
+ 		return;
+ 
+ 	/* Process the individual base relations. */
+ 	for (i = 1; i < root->simple_rel_array_size; i++)
+ 	{
+ 		RelOptInfo	*rel = root->simple_rel_array[i];
+ 
+ 		/*
+ 		 * "other rels" will have their targets built later, by translation of
+ 		 * the target of the parent rel - see set_append_rel_size. If we
+ 		 * wanted to prepare the child rels here, we'd need another iteration
+ 		 * of simple_rel_array_size.
+ 		 */
+ 		if (rel != NULL && rel->reloptkind == RELOPT_BASEREL)
+ 			prepare_rel_for_grouping(root, rel);
+ 	}
+ }
+ 
+ /*
+  * Create GroupedVarInfo for each distinct aggregate.
+  *
+  * If any aggregate is not suitable, set root->grouped_var_list to NIL and
+  * return.
+  *
+  * TODO Include aggregates from HAVING clause.
+  */
+ static void
+ create_grouped_var_infos(PlannerInfo *root)
+ {
+ 	List	   *tlist_exprs;
+ 	ListCell	*lc;
+ 
+ 	Assert(root->grouped_var_list == NIL);
+ 
+ 	/*
+ 	 * TODO Check if processed_tlist contains the HAVING aggregates. If not,
+ 	 * get them elsewhere.
+ 	 */
+ 	tlist_exprs = pull_var_clause((Node *) root->processed_tlist,
+ 								  PVC_INCLUDE_AGGREGATES);
+ 	if (tlist_exprs == NIL)
+ 		return;
+ 
+ 	/* tlist_exprs may also contain Vars, but we only need Aggrefs. */
+ 	foreach(lc, tlist_exprs)
+ 	{
+ 		Expr	*expr = (Expr *) lfirst(lc);
+ 		Aggref	*aggref;
+ 		ListCell	*lc2;
+ 		GroupedVarInfo	*gvi;
+ 		bool	exists;
+ 
+ 		if (IsA(expr, Var))
+ 			continue;
+ 
+ 		aggref = castNode(Aggref, expr);
+ 
+ 		/* TODO Think if (some of) these can be handled. */
+ 		if (aggref->aggvariadic ||
+ 			aggref->aggdirectargs || aggref->aggorder ||
+ 			aggref->aggdistinct || aggref->aggfilter)
+ 		{
+ 			/*
+ 			 * Partial aggregation is not useful if at least one aggregate
+ 			 * cannot be evaluated below the top-level join.
+ 			 *
+ 			 * XXX Is it worth freeing the GroupedVarInfos and their subtrees?
+ 			 */
+ 			root->grouped_var_list = NIL;
+ 			break;
+ 		}
+ 
+ 		/* Does GroupedVarInfo for this aggregate already exist? */
+ 		exists = false;
+ 		foreach(lc2, root->grouped_var_list)
+ 		{
+ 			Expr	*expr = (Expr *) lfirst(lc2);
+ 
+ 			gvi = castNode(GroupedVarInfo, expr);
+ 
+ 			if (equal(expr, gvi->gvexpr))
+ 			{
+ 				exists = true;
+ 				break;
+ 			}
+ 		}
+ 
+ 		/* Construct a new GroupedVarInfo if does not exist yet. */
+ 		if (!exists)
+ 		{
+ 			Relids	relids;
+ 
+ 			/* TODO Initialize gv_width. */
+ 			gvi = makeNode(GroupedVarInfo);
+ 
+ 			gvi->gvid = list_length(root->grouped_var_list);
+ 			gvi->gvexpr = (Expr *) copyObject(aggref);
+ 			gvi->agg_partial = copyObject(aggref);
+ 			mark_partial_aggref(gvi->agg_partial, AGGSPLIT_INITIAL_SERIAL);
+ 
+ 			/* Find out where the aggregate should be evaluated. */
+ 			relids = pull_varnos((Node *) aggref);
+ 			if (!bms_is_empty(relids))
+ 				gvi->gv_eval_at = relids;
+ 			else
+ 			{
+ 				Assert(aggref->aggstar);
+ 				gvi->gv_eval_at = NULL;
+ 			}
+ 
+ 			root->grouped_var_list = lappend(root->grouped_var_list, gvi);
+ 		}
+ 	}
+ 
+ 	list_free(tlist_exprs);
+ }
+ 
+ /*
+  * Check if all the expressions of rel->reltarget can be used as grouping
+  * expressions and create target for grouped paths.
+  *
+  * If we succeed to create the grouping target, also replace rel->reltarget
+  * with a new one that has sortgrouprefs initialized -- this is necessary for
+  * create_agg_plan to match the grouping clauses against the input target
+  * expressions.
+  *
+  * rel_agg_attrs is a set attributes of the relation referenced by aggregate
+  * arguments. These can exist in the (plain) target without being grouping
+  * expressions.
+  *
+  * rel_agg_vars should be passed instead if rel is a join.
+  *
+  * TODO How about PHVs?
+  *
+  * TODO Make sure cost / width of both "result" and "plain" are correct.
+  */
+ PathTarget *
+ create_grouped_target(PlannerInfo *root, RelOptInfo *rel,
+ 					  Relids rel_agg_attrs, List *rel_agg_vars)
+ {
+ 	PathTarget	*result, *plain;
+ 	ListCell	*lc;
+ 
+ 	/* The plan to be returned. */
+ 	result = create_empty_pathtarget();
+ 	/* The one to replace rel->reltarget. */
+ 	plain = create_empty_pathtarget();
+ 
+ 	foreach(lc, rel->reltarget->exprs)
+ 	{
+ 		Expr		*texpr;
+ 		Index		sortgroupref;
+ 		bool		agg_arg_only = false;
+ 
+ 		texpr = (Expr *) lfirst(lc);
+ 
+ 		sortgroupref = get_expr_sortgroupref(root, texpr);
+ 		if (sortgroupref > 0)
+ 		{
+ 			/* It's o.k. to use the target expression for grouping. */
+ 			add_column_to_pathtarget(result, texpr, sortgroupref);
+ 
+ 			/*
+ 			 * As for the plain target, add the original expression but set
+ 			 * sortgroupref in addition.
+ 			 */
+ 			add_column_to_pathtarget(plain, texpr, sortgroupref);
+ 
+ 			/* Process the next expression. */
+ 			continue;
+ 		}
+ 
+ 		/*
+ 		 * It may still be o.k. if the expression is only contained in Aggref
+ 		 * - then it's not expected in the grouped output.
+ 		 *
+ 		 * TODO Try to handle generic expression, not only Var. That might
+ 		 * require us to create rel->reltarget of the grouping rel in
+ 		 * parallel to that of the plain rel, and adding whole expressions
+ 		 * instead of individual vars.
+ 		 */
+ 		if (IsA(texpr, Var))
+ 		{
+ 			Var	*arg_var = castNode(Var, texpr);
+ 
+ 			if (rel->relid > 0)
+ 			{
+ 				AttrNumber	varattno;
+ 
+ 				/*
+ 				 * For a single relation we only need to check attribute
+ 				 * number.
+ 				 *
+ 				 * Apply the same offset that pull_varattnos() did.
+ 				 */
+ 				varattno = arg_var->varattno - FirstLowInvalidHeapAttributeNumber;
+ 
+ 				if (bms_is_member(varattno, rel_agg_attrs))
+ 					agg_arg_only = true;
+ 			}
+ 			else
+ 			{
+ 				ListCell	*lc2;
+ 
+ 				/* Join case. */
+ 				foreach(lc2, rel_agg_vars)
+ 				{
+ 					Var	*var = castNode(Var, lfirst(lc2));
+ 
+ 					if (var->varno == arg_var->varno &&
+ 						var->varattno == arg_var->varattno)
+ 					{
+ 						agg_arg_only = true;
+ 						break;
+ 					}
+ 				}
+ 			}
+ 
+ 			if (agg_arg_only)
+ 			{
+ 				/*
+ 				 * This expression is not suitable for grouping, but the
+ 				 * aggregation input target ought to stay complete.
+ 				 */
+ 				add_column_to_pathtarget(plain, texpr, 0);
+ 			}
+ 		}
+ 
+ 		/*
+ 		 * A single mismatched expression makes the whole relation useless
+ 		 * for grouping.
+ 		 */
+ 		if (!agg_arg_only)
+ 		{
+ 			/*
+ 			 * TODO This seems possible to happen multiple times per relation,
+ 			 * so result might be worth freeing. Implement free_pathtarget()?
+ 			 * Or mark the relation as inappropriate for grouping?
+ 			 */
+ 			/* TODO Free both result and plain. */
+ 			return NULL;
+ 		}
+ 	}
+ 
+ 	if (list_length(result->exprs) == 0)
+ 	{
+ 		/* TODO free_pathtarget(result); free_pathtarget(plain) */
+ 		result = NULL;
+ 	}
+ 
+ 	/* Apply the adjusted input target as the replacement is complete now.q */
+ 	rel->reltarget = plain;
+ 
+ 	return result;
+ }
+ 
  
  /*****************************************************************************
   *
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
new file mode 100644
index 5565736..058af2c
*** a/src/backend/optimizer/plan/planagg.c
--- b/src/backend/optimizer/plan/planagg.c
*************** preprocess_minmax_aggregates(PlannerInfo
*** 223,229 ****
  			 create_minmaxagg_path(root, grouped_rel,
  								   create_pathtarget(root, tlist),
  								   aggs_list,
! 								   (List *) parse->havingQual));
  }
  
  /*
--- 223,229 ----
  			 create_minmaxagg_path(root, grouped_rel,
  								   create_pathtarget(root, tlist),
  								   aggs_list,
! 								   (List *) parse->havingQual), false);
  }
  
  /*
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
new file mode 100644
index 3c58d05..5db7dec
*** a/src/backend/optimizer/plan/planmain.c
--- b/src/backend/optimizer/plan/planmain.c
*************** query_planner(PlannerInfo *root, List *t
*** 83,89 ****
  		add_path(final_rel, (Path *)
  				 create_result_path(root, final_rel,
  									final_rel->reltarget,
! 									(List *) parse->jointree->quals));
  
  		/* Select cheapest path (pretty easy in this case...) */
  		set_cheapest(final_rel);
--- 83,89 ----
  		add_path(final_rel, (Path *)
  				 create_result_path(root, final_rel,
  									final_rel->reltarget,
! 									(List *) parse->jointree->quals), false);
  
  		/* Select cheapest path (pretty easy in this case...) */
  		set_cheapest(final_rel);
*************** query_planner(PlannerInfo *root, List *t
*** 114,119 ****
--- 114,120 ----
  	root->full_join_clauses = NIL;
  	root->join_info_list = NIL;
  	root->placeholder_list = NIL;
+ 	root->grouped_var_list = NIL;
  	root->fkey_list = NIL;
  	root->initial_rels = NIL;
  
*************** query_planner(PlannerInfo *root, List *t
*** 177,182 ****
--- 178,191 ----
  	(*qp_callback) (root, qp_extra);
  
  	/*
+ 	 * If the query result can be grouped, check if any grouping can be
+ 	 * performed below the top-level join. If so, Initialize GroupedPathInfo
+ 	 * of base relations capable to do the grouping and setup
+ 	 * root->grouped_var_list.
+ 	 */
+ 	add_grouping_info_to_base_rels(root);
+ 
+ 	/*
  	 * Examine any "placeholder" expressions generated during subquery pullup.
  	 * Make sure that the Vars they need are marked as needed at the relevant
  	 * join level.  This must be done before join removal because it might
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index f99257b..8245ce0
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** static void standard_qp_callback(Planner
*** 130,138 ****
  static double get_number_of_groups(PlannerInfo *root,
  					 double path_rows,
  					 grouping_sets_data *gd);
- static Size estimate_hashagg_tablesize(Path *path,
- 						   const AggClauseCosts *agg_costs,
- 						   double dNumGroups);
  static RelOptInfo *create_grouping_paths(PlannerInfo *root,
  					  RelOptInfo *input_rel,
  					  PathTarget *target,
--- 130,135 ----
*************** inheritance_planner(PlannerInfo *root)
*** 1420,1426 ****
  									 returningLists,
  									 rowMarks,
  									 NULL,
! 									 SS_assign_special_param(root)));
  }
  
  /*--------------------
--- 1417,1423 ----
  									 returningLists,
  									 rowMarks,
  									 NULL,
! 									 SS_assign_special_param(root)), false);
  }
  
  /*--------------------
*************** grouping_planner(PlannerInfo *root, bool
*** 2041,2047 ****
  		}
  
  		/* And shove it into final_rel */
! 		add_path(final_rel, path);
  	}
  
  	/*
--- 2038,2044 ----
  		}
  
  		/* And shove it into final_rel */
! 		add_path(final_rel, path, false);
  	}
  
  	/*
*************** get_number_of_groups(PlannerInfo *root,
*** 3445,3484 ****
  }
  
  /*
-  * estimate_hashagg_tablesize
-  *	  estimate the number of bytes that a hash aggregate hashtable will
-  *	  require based on the agg_costs, path width and dNumGroups.
-  *
-  * XXX this may be over-estimating the size now that hashagg knows to omit
-  * unneeded columns from the hashtable. Also for mixed-mode grouping sets,
-  * grouping columns not in the hashed set are counted here even though hashagg
-  * won't store them. Is this a problem?
-  */
- static Size
- estimate_hashagg_tablesize(Path *path, const AggClauseCosts *agg_costs,
- 						   double dNumGroups)
- {
- 	Size		hashentrysize;
- 
- 	/* Estimate per-hash-entry space at tuple width... */
- 	hashentrysize = MAXALIGN(path->pathtarget->width) +
- 		MAXALIGN(SizeofMinimalTupleHeader);
- 
- 	/* plus space for pass-by-ref transition values... */
- 	hashentrysize += agg_costs->transitionSpace;
- 	/* plus the per-hash-entry overhead */
- 	hashentrysize += hash_agg_entry_size(agg_costs->numAggs);
- 
- 	/*
- 	 * Note that this disregards the effect of fill-factor and growth policy
- 	 * of the hash-table. That's probably ok, given default the default
- 	 * fill-factor is relatively high. It'd be hard to meaningfully factor in
- 	 * "double-in-size" growth policies here.
- 	 */
- 	return hashentrysize * dNumGroups;
- }
- 
- /*
   * create_grouping_paths
   *
   * Build a new upperrel containing Paths for grouping and/or aggregation.
--- 3442,3447 ----
*************** create_grouping_paths(PlannerInfo *root,
*** 3599,3605 ****
  								   (List *) parse->havingQual);
  		}
  
! 		add_path(grouped_rel, path);
  
  		/* No need to consider any other alternatives. */
  		set_cheapest(grouped_rel);
--- 3562,3568 ----
  								   (List *) parse->havingQual);
  		}
  
! 		add_path(grouped_rel, path, false);
  
  		/* No need to consider any other alternatives. */
  		set_cheapest(grouped_rel);
*************** create_grouping_paths(PlannerInfo *root,
*** 3776,3782 ****
  														 parse->groupClause,
  														 NIL,
  														 &agg_partial_costs,
! 														 dNumPartialGroups));
  					else
  						add_partial_path(grouped_rel, (Path *)
  										 create_group_path(root,
--- 3739,3746 ----
  														 parse->groupClause,
  														 NIL,
  														 &agg_partial_costs,
! 														 dNumPartialGroups),
! 							false);
  					else
  						add_partial_path(grouped_rel, (Path *)
  										 create_group_path(root,
*************** create_grouping_paths(PlannerInfo *root,
*** 3785,3791 ****
  													 partial_grouping_target,
  														   parse->groupClause,
  														   NIL,
! 														 dNumPartialGroups));
  				}
  			}
  		}
--- 3749,3756 ----
  													 partial_grouping_target,
  														   parse->groupClause,
  														   NIL,
! 														   dNumPartialGroups),
! 										 false);
  				}
  			}
  		}
*************** create_grouping_paths(PlannerInfo *root,
*** 3816,3822 ****
  												 parse->groupClause,
  												 NIL,
  												 &agg_partial_costs,
! 												 dNumPartialGroups));
  			}
  		}
  	}
--- 3781,3788 ----
  												 parse->groupClause,
  												 NIL,
  												 &agg_partial_costs,
! 												 dNumPartialGroups),
! 								 false);
  			}
  		}
  	}
*************** create_grouping_paths(PlannerInfo *root,
*** 3868,3874 ****
  											 parse->groupClause,
  											 (List *) parse->havingQual,
  											 agg_costs,
! 											 dNumGroups));
  				}
  				else if (parse->groupClause)
  				{
--- 3834,3840 ----
  											 parse->groupClause,
  											 (List *) parse->havingQual,
  											 agg_costs,
! 											 dNumGroups), false);
  				}
  				else if (parse->groupClause)
  				{
*************** create_grouping_paths(PlannerInfo *root,
*** 3883,3889 ****
  											   target,
  											   parse->groupClause,
  											   (List *) parse->havingQual,
! 											   dNumGroups));
  				}
  				else
  				{
--- 3849,3855 ----
  											   target,
  											   parse->groupClause,
  											   (List *) parse->havingQual,
! 											   dNumGroups), false);
  				}
  				else
  				{
*************** create_grouping_paths(PlannerInfo *root,
*** 3932,3938 ****
  										 parse->groupClause,
  										 (List *) parse->havingQual,
  										 &agg_final_costs,
! 										 dNumGroups));
  			else
  				add_path(grouped_rel, (Path *)
  						 create_group_path(root,
--- 3898,3904 ----
  										 parse->groupClause,
  										 (List *) parse->havingQual,
  										 &agg_final_costs,
! 										 dNumGroups), false);
  			else
  				add_path(grouped_rel, (Path *)
  						 create_group_path(root,
*************** create_grouping_paths(PlannerInfo *root,
*** 3941,3947 ****
  										   target,
  										   parse->groupClause,
  										   (List *) parse->havingQual,
! 										   dNumGroups));
  
  			/*
  			 * The point of using Gather Merge rather than Gather is that it
--- 3907,3913 ----
  										   target,
  										   parse->groupClause,
  										   (List *) parse->havingQual,
! 										   dNumGroups), false);
  
  			/*
  			 * The point of using Gather Merge rather than Gather is that it
*************** create_grouping_paths(PlannerInfo *root,
*** 3994,4000 ****
  												 parse->groupClause,
  												 (List *) parse->havingQual,
  												 &agg_final_costs,
! 												 dNumGroups));
  					else
  						add_path(grouped_rel, (Path *)
  								 create_group_path(root,
--- 3960,3966 ----
  												 parse->groupClause,
  												 (List *) parse->havingQual,
  												 &agg_final_costs,
! 												 dNumGroups), false);
  					else
  						add_path(grouped_rel, (Path *)
  								 create_group_path(root,
*************** create_grouping_paths(PlannerInfo *root,
*** 4003,4009 ****
  												   target,
  												   parse->groupClause,
  												   (List *) parse->havingQual,
! 												   dNumGroups));
  				}
  			}
  		}
--- 3969,3975 ----
  												   target,
  												   parse->groupClause,
  												   (List *) parse->havingQual,
! 												   dNumGroups), false);
  				}
  			}
  		}
*************** create_grouping_paths(PlannerInfo *root,
*** 4048,4054 ****
  										 parse->groupClause,
  										 (List *) parse->havingQual,
  										 agg_costs,
! 										 dNumGroups));
  			}
  		}
  
--- 4014,4020 ----
  										 parse->groupClause,
  										 (List *) parse->havingQual,
  										 agg_costs,
! 										 dNumGroups), false);
  			}
  		}
  
*************** create_grouping_paths(PlannerInfo *root,
*** 4086,4094 ****
  										 parse->groupClause,
  										 (List *) parse->havingQual,
  										 &agg_final_costs,
! 										 dNumGroups));
  			}
  		}
  	}
  
  	/* Give a helpful error if we failed to find any implementation */
--- 4052,4128 ----
  										 parse->groupClause,
  										 (List *) parse->havingQual,
  										 &agg_final_costs,
! 										 dNumGroups), false);
  			}
  		}
+ 
+ 		/*
+ 		 * If input_rel has partially aggregated partial paths, gather them
+ 		 * and perform the final aggregation.
+ 		 *
+ 		 * TODO Allow havingQual - currently not supported at base relation
+ 		 * level.
+ 		 */
+ 		if (input_rel->gpi != NULL &&
+ 			input_rel->gpi->partial_pathlist != NIL &&
+ 			!parse->havingQual)
+ 		{
+ 			Path	   *path = (Path *) linitial(input_rel->gpi->partial_pathlist);
+ 			double		total_groups = path->rows * path->parallel_workers;
+ 
+ 			path = (Path *) create_gather_path(root,
+ 											   input_rel,
+ 											   path,
+ 											   path->pathtarget,
+ 											   NULL,
+ 											   &total_groups);
+ 
+ 			/*
+ 			 * The input path is partially aggregated and the final
+ 			 * aggregation - if the path wins - will be done below. So we're
+ 			 * done with it for now.
+ 			 *
+ 			 * The top-level grouped_rel needs to receive the path into
+ 			 * regular pathlist, as opposed grouped_rel->gpi->pathlist.
+ 			 */
+ 
+ 			add_path(input_rel, path, false);
+ 		}
+ 
+ 		/*
+ 		 * If input_rel has partially aggregated paths, perform the final
+ 		 * aggregation.
+ 		 *
+ 		 * TODO Allow havingQual - currently not supported at base relation
+ 		 * level.
+ 		 */
+ 		if (input_rel->gpi != NULL && input_rel->gpi->pathlist != NIL &&
+ 			!parse->havingQual)
+ 		{
+ 			Path *pre_agg = (Path *) linitial(input_rel->gpi->pathlist);
+ 
+ 			dNumGroups = get_number_of_groups(root, pre_agg->rows, gd);
+ 
+ 			MemSet(&agg_final_costs, 0, sizeof(AggClauseCosts));
+ 			get_agg_clause_costs(root, (Node *) target->exprs,
+ 								 AGGSPLIT_FINAL_DESERIAL,
+ 								 &agg_final_costs);
+ 			get_agg_clause_costs(root, parse->havingQual,
+ 								 AGGSPLIT_FINAL_DESERIAL,
+ 								 &agg_final_costs);
+ 
+ 			add_path(grouped_rel,
+ 					 (Path *) create_agg_path(root, grouped_rel,
+ 											  pre_agg,
+ 											  target,
+ 											  AGG_HASHED,
+ 											  AGGSPLIT_FINAL_DESERIAL,
+ 											  parse->groupClause,
+ 											  (List *) parse->havingQual,
+ 											  &agg_final_costs,
+ 											  dNumGroups),
+ 					 false);
+ 		}
  	}
  
  	/* Give a helpful error if we failed to find any implementation */
*************** consider_groupingsets_paths(PlannerInfo
*** 4288,4294 ****
  										  strat,
  										  new_rollups,
  										  agg_costs,
! 										  dNumGroups));
  		return;
  	}
  
--- 4322,4328 ----
  										  strat,
  										  new_rollups,
  										  agg_costs,
! 										  dNumGroups), false);
  		return;
  	}
  
*************** consider_groupingsets_paths(PlannerInfo
*** 4446,4452 ****
  											  AGG_MIXED,
  											  rollups,
  											  agg_costs,
! 											  dNumGroups));
  		}
  	}
  
--- 4480,4486 ----
  											  AGG_MIXED,
  											  rollups,
  											  agg_costs,
! 											  dNumGroups), false);
  		}
  	}
  
*************** consider_groupingsets_paths(PlannerInfo
*** 4463,4469 ****
  										  AGG_SORTED,
  										  gd->rollups,
  										  agg_costs,
! 										  dNumGroups));
  }
  
  /*
--- 4497,4503 ----
  										  AGG_SORTED,
  										  gd->rollups,
  										  agg_costs,
! 										  dNumGroups), false);
  }
  
  /*
*************** create_one_window_path(PlannerInfo *root
*** 4648,4654 ****
  								  window_pathkeys);
  	}
  
! 	add_path(window_rel, path);
  }
  
  /*
--- 4682,4688 ----
  								  window_pathkeys);
  	}
  
! 	add_path(window_rel, path, false);
  }
  
  /*
*************** create_distinct_paths(PlannerInfo *root,
*** 4754,4760 ****
  						 create_upper_unique_path(root, distinct_rel,
  												  path,
  										list_length(root->distinct_pathkeys),
! 												  numDistinctRows));
  			}
  		}
  
--- 4788,4794 ----
  						 create_upper_unique_path(root, distinct_rel,
  												  path,
  										list_length(root->distinct_pathkeys),
! 												  numDistinctRows), false);
  			}
  		}
  
*************** create_distinct_paths(PlannerInfo *root,
*** 4781,4787 ****
  				 create_upper_unique_path(root, distinct_rel,
  										  path,
  										list_length(root->distinct_pathkeys),
! 										  numDistinctRows));
  	}
  
  	/*
--- 4815,4821 ----
  				 create_upper_unique_path(root, distinct_rel,
  										  path,
  										list_length(root->distinct_pathkeys),
! 										  numDistinctRows), false);
  	}
  
  	/*
*************** create_distinct_paths(PlannerInfo *root,
*** 4828,4834 ****
  								 parse->distinctClause,
  								 NIL,
  								 NULL,
! 								 numDistinctRows));
  	}
  
  	/* Give a helpful error if we failed to find any implementation */
--- 4862,4868 ----
  								 parse->distinctClause,
  								 NIL,
  								 NULL,
! 								 numDistinctRows), false);
  	}
  
  	/* Give a helpful error if we failed to find any implementation */
*************** create_ordered_paths(PlannerInfo *root,
*** 4926,4932 ****
  				path = apply_projection_to_path(root, ordered_rel,
  												path, target);
  
! 			add_path(ordered_rel, path);
  		}
  	}
  
--- 4960,4966 ----
  				path = apply_projection_to_path(root, ordered_rel,
  												path, target);
  
! 			add_path(ordered_rel, path, false);
  		}
  	}
  
*************** create_ordered_paths(PlannerInfo *root,
*** 4976,4982 ****
  				path = apply_projection_to_path(root, ordered_rel,
  												path, target);
  
! 			add_path(ordered_rel, path);
  		}
  	}
  
--- 5010,5016 ----
  				path = apply_projection_to_path(root, ordered_rel,
  												path, target);
  
! 			add_path(ordered_rel, path, false);
  		}
  	}
  
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
new file mode 100644
index cdb8e95..fca3c0b
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
*************** typedef struct
*** 40,45 ****
--- 40,46 ----
  	List	   *tlist;			/* underlying target list */
  	int			num_vars;		/* number of plain Var tlist entries */
  	bool		has_ph_vars;	/* are there PlaceHolderVar entries? */
+ 	bool		has_grp_vars;	/* are there GroupedVar entries? */
  	bool		has_non_vars;	/* are there other entries? */
  	tlist_vinfo vars[FLEXIBLE_ARRAY_MEMBER];	/* has num_vars entries */
  } indexed_tlist;
*************** set_upper_references(PlannerInfo *root,
*** 1725,1733 ****
--- 1726,1777 ----
  	indexed_tlist *subplan_itlist;
  	List	   *output_targetlist;
  	ListCell   *l;
+ 	List	*sub_tlist_save = NIL;
+ 
+ 	if (root->grouped_var_list != NIL)
+ 	{
+ 		if (IsA(plan, Agg))
+ 		{
+ 			Agg	*agg = (Agg *) plan;
+ 
+ 			if (agg->aggsplit == AGGSPLIT_FINAL_DESERIAL)
+ 			{
+ 				/*
+ 				 * convert_combining_aggrefs could have replaced some vars
+ 				 * with Aggref expressions representing the partial
+ 				 * aggregation. We need to restore the same Aggrefs in the
+ 				 * subplan targetlist, but this would break the subplan if
+ 				 * it's something else than the partial aggregation (i.e. the
+ 				 * partial aggregation takes place lower in the plan tree). So
+ 				 * we'll eventually need to restore the original list.
+ 				 */
+ 				if (!IsA(subplan, Agg))
+ 					sub_tlist_save = subplan->targetlist;
+ #ifdef USE_ASSERT_CHECKING
+ 				else
+ 					Assert(((Agg *) subplan)->aggsplit == AGGSPLIT_INITIAL_SERIAL);
+ #endif	/* USE_ASSERT_CHECKING */
+ 
+ 				/*
+ 				 * Restore the aggregate expressions that we might have
+ 				 * removed when planning for aggregation at base relation
+ 				 * level.
+ 				 */
+ 				subplan->targetlist =
+ 					restore_grouping_expressions(root, subplan->targetlist);
+ 			}
+ 		}
+ 	}
  
  	subplan_itlist = build_tlist_index(subplan->targetlist);
  
+ 	/*
+ 	 * The replacement of GroupVars by Aggrefs was only needed for the index
+ 	 * build.
+ 	 */
+ 	if (sub_tlist_save != NIL)
+ 		subplan->targetlist = sub_tlist_save;
+ 
  	output_targetlist = NIL;
  	foreach(l, plan->targetlist)
  	{
*************** build_tlist_index(List *tlist)
*** 1937,1942 ****
--- 1981,1987 ----
  
  	itlist->tlist = tlist;
  	itlist->has_ph_vars = false;
+ 	itlist->has_grp_vars = false;
  	itlist->has_non_vars = false;
  
  	/* Find the Vars and fill in the index array */
*************** build_tlist_index(List *tlist)
*** 1956,1961 ****
--- 2001,2008 ----
  		}
  		else if (tle->expr && IsA(tle->expr, PlaceHolderVar))
  			itlist->has_ph_vars = true;
+ 		else if (tle->expr && IsA(tle->expr, GroupedVar))
+ 			itlist->has_grp_vars = true;
  		else
  			itlist->has_non_vars = true;
  	}
*************** fix_join_expr_mutator(Node *node, fix_jo
*** 2233,2238 ****
--- 2280,2310 ----
  		/* No referent found for Var */
  		elog(ERROR, "variable not found in subplan target lists");
  	}
+ 	if (IsA(node, GroupedVar))
+ 	{
+ 		GroupedVar *gvar = (GroupedVar *) node;
+ 
+ 		/* See if the GroupedVar has bubbled up from a lower plan node */
+ 		if (context->outer_itlist && context->outer_itlist->has_grp_vars)
+ 		{
+ 			newvar = search_indexed_tlist_for_non_var((Expr *) gvar,
+ 													  context->outer_itlist,
+ 													  OUTER_VAR);
+ 			if (newvar)
+ 				return (Node *) newvar;
+ 		}
+ 		if (context->inner_itlist && context->inner_itlist->has_grp_vars)
+ 		{
+ 			newvar = search_indexed_tlist_for_non_var((Expr *) gvar,
+ 													  context->inner_itlist,
+ 													  INNER_VAR);
+ 			if (newvar)
+ 				return (Node *) newvar;
+ 		}
+ 
+ 		/* No referent found for GroupedVar */
+ 		elog(ERROR, "grouped variable not found in subplan target lists");
+ 	}
  	if (IsA(node, PlaceHolderVar))
  	{
  		PlaceHolderVar *phv = (PlaceHolderVar *) node;
*************** fix_upper_expr_mutator(Node *node, fix_u
*** 2389,2395 ****
  		/* If no match, just fall through to process it normally */
  	}
  	/* Try matching more complex expressions too, if tlist has any */
! 	if (context->subplan_itlist->has_non_vars)
  	{
  		newvar = search_indexed_tlist_for_non_var((Expr *) node,
  												  context->subplan_itlist,
--- 2461,2468 ----
  		/* If no match, just fall through to process it normally */
  	}
  	/* Try matching more complex expressions too, if tlist has any */
! 	if (context->subplan_itlist->has_grp_vars ||
! 		context->subplan_itlist->has_non_vars)
  	{
  		newvar = search_indexed_tlist_for_non_var((Expr *) node,
  												  context->subplan_itlist,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
new file mode 100644
index e327e66..e90d72f
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
*************** plan_set_operations(PlannerInfo *root)
*** 207,213 ****
  	root->processed_tlist = top_tlist;
  
  	/* Add only the final path to the SETOP upperrel. */
! 	add_path(setop_rel, path);
  
  	/* Let extensions possibly add some more paths */
  	if (create_upper_paths_hook)
--- 207,213 ----
  	root->processed_tlist = top_tlist;
  
  	/* Add only the final path to the SETOP upperrel. */
! 	add_path(setop_rel, path, false);
  
  	/* Let extensions possibly add some more paths */
  	if (create_upper_paths_hook)
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
new file mode 100644
index 8536212..39813b8
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
***************
*** 24,29 ****
--- 24,31 ----
  #include "optimizer/paths.h"
  #include "optimizer/planmain.h"
  #include "optimizer/restrictinfo.h"
+ /* TODO Remove this if get_grouping_expressions ends up in another module. */
+ #include "optimizer/tlist.h"
  #include "optimizer/var.h"
  #include "parser/parsetree.h"
  #include "utils/lsyscache.h"
*************** set_cheapest(RelOptInfo *parent_rel)
*** 409,416 ****
   * Returns nothing, but modifies parent_rel->pathlist.
   */
  void
! add_path(RelOptInfo *parent_rel, Path *new_path)
  {
  	bool		accept_new = true;		/* unless we find a superior old path */
  	ListCell   *insert_after = NULL;	/* where to insert new item */
  	List	   *new_path_pathkeys;
--- 411,419 ----
   * Returns nothing, but modifies parent_rel->pathlist.
   */
  void
! add_path(RelOptInfo *parent_rel, Path *new_path, bool grouped)
  {
+ 	List	   *pathlist;
  	bool		accept_new = true;		/* unless we find a superior old path */
  	ListCell   *insert_after = NULL;	/* where to insert new item */
  	List	   *new_path_pathkeys;
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 427,432 ****
--- 430,443 ----
  	/* Pretend parameterized paths have no pathkeys, per comment above */
  	new_path_pathkeys = new_path->param_info ? NIL : new_path->pathkeys;
  
+ 	if (!grouped)
+ 		pathlist = parent_rel->pathlist;
+ 	else
+ 	{
+ 		Assert(parent_rel->gpi != NULL);
+ 		pathlist = parent_rel->gpi->pathlist;
+ 	}
+ 
  	/*
  	 * Loop to check proposed new path against old paths.  Note it is possible
  	 * for more than one old path to be tossed out because new_path dominates
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 436,442 ****
  	 * list cell.
  	 */
  	p1_prev = NULL;
! 	for (p1 = list_head(parent_rel->pathlist); p1 != NULL; p1 = p1_next)
  	{
  		Path	   *old_path = (Path *) lfirst(p1);
  		bool		remove_old = false; /* unless new proves superior */
--- 447,453 ----
  	 * list cell.
  	 */
  	p1_prev = NULL;
! 	for (p1 = list_head(pathlist); p1 != NULL; p1 = p1_next)
  	{
  		Path	   *old_path = (Path *) lfirst(p1);
  		bool		remove_old = false; /* unless new proves superior */
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 582,589 ****
  		 */
  		if (remove_old)
  		{
! 			parent_rel->pathlist = list_delete_cell(parent_rel->pathlist,
! 													p1, p1_prev);
  
  			/*
  			 * Delete the data pointed-to by the deleted cell, if possible
--- 593,599 ----
  		 */
  		if (remove_old)
  		{
! 			pathlist = list_delete_cell(pathlist, p1, p1_prev);
  
  			/*
  			 * Delete the data pointed-to by the deleted cell, if possible
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 614,622 ****
  	{
  		/* Accept the new path: insert it at proper place in pathlist */
  		if (insert_after)
! 			lappend_cell(parent_rel->pathlist, insert_after, new_path);
  		else
! 			parent_rel->pathlist = lcons(new_path, parent_rel->pathlist);
  	}
  	else
  	{
--- 624,637 ----
  	{
  		/* Accept the new path: insert it at proper place in pathlist */
  		if (insert_after)
! 			lappend_cell(pathlist, insert_after, new_path);
  		else
! 			pathlist = lcons(new_path, pathlist);
! 
! 		if (!grouped)
! 			parent_rel->pathlist = pathlist;
! 		else
! 			parent_rel->gpi->pathlist = pathlist;
  	}
  	else
  	{
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 646,653 ****
  bool
  add_path_precheck(RelOptInfo *parent_rel,
  				  Cost startup_cost, Cost total_cost,
! 				  List *pathkeys, Relids required_outer)
  {
  	List	   *new_path_pathkeys;
  	bool		consider_startup;
  	ListCell   *p1;
--- 661,669 ----
  bool
  add_path_precheck(RelOptInfo *parent_rel,
  				  Cost startup_cost, Cost total_cost,
! 				  List *pathkeys, Relids required_outer, bool grouped)
  {
+ 	List	   *pathlist;
  	List	   *new_path_pathkeys;
  	bool		consider_startup;
  	ListCell   *p1;
*************** add_path_precheck(RelOptInfo *parent_rel
*** 656,664 ****
  	new_path_pathkeys = required_outer ? NIL : pathkeys;
  
  	/* Decide whether new path's startup cost is interesting */
! 	consider_startup = required_outer ? parent_rel->consider_param_startup : parent_rel->consider_startup;
  
! 	foreach(p1, parent_rel->pathlist)
  	{
  		Path	   *old_path = (Path *) lfirst(p1);
  		PathKeysComparison keyscmp;
--- 672,689 ----
  	new_path_pathkeys = required_outer ? NIL : pathkeys;
  
  	/* Decide whether new path's startup cost is interesting */
! 	consider_startup = required_outer ? parent_rel->consider_param_startup :
! 		parent_rel->consider_startup;
  
! 	if (!grouped)
! 		pathlist = parent_rel->pathlist;
! 	else
! 	{
! 		Assert(parent_rel->gpi != NULL);
! 		pathlist = parent_rel->gpi->pathlist;
! 	}
! 
! 	foreach(p1, pathlist)
  	{
  		Path	   *old_path = (Path *) lfirst(p1);
  		PathKeysComparison keyscmp;
*************** add_path_precheck(RelOptInfo *parent_rel
*** 749,771 ****
   *	  referenced by partial BitmapHeapPaths.
   */
  void
! add_partial_path(RelOptInfo *parent_rel, Path *new_path)
  {
  	bool		accept_new = true;		/* unless we find a superior old path */
  	ListCell   *insert_after = NULL;	/* where to insert new item */
  	ListCell   *p1;
  	ListCell   *p1_prev;
  	ListCell   *p1_next;
  
  	/* Check for query cancel. */
  	CHECK_FOR_INTERRUPTS();
  
  	/*
  	 * As in add_path, throw out any paths which are dominated by the new
  	 * path, but throw out the new path if some existing path dominates it.
  	 */
  	p1_prev = NULL;
! 	for (p1 = list_head(parent_rel->partial_pathlist); p1 != NULL;
  		 p1 = p1_next)
  	{
  		Path	   *old_path = (Path *) lfirst(p1);
--- 774,805 ----
   *	  referenced by partial BitmapHeapPaths.
   */
  void
! add_partial_path(RelOptInfo *parent_rel, Path *new_path, bool grouped)
  {
  	bool		accept_new = true;		/* unless we find a superior old path */
  	ListCell   *insert_after = NULL;	/* where to insert new item */
  	ListCell   *p1;
  	ListCell   *p1_prev;
  	ListCell   *p1_next;
+ 	List	   *pathlist;
  
  	/* Check for query cancel. */
  	CHECK_FOR_INTERRUPTS();
  
+ 	if (!grouped)
+ 		pathlist = parent_rel->partial_pathlist;
+ 	else
+ 	{
+ 		Assert(parent_rel->gpi != NULL);
+ 		pathlist = parent_rel->gpi->partial_pathlist;
+ 	}
+ 
  	/*
  	 * As in add_path, throw out any paths which are dominated by the new
  	 * path, but throw out the new path if some existing path dominates it.
  	 */
  	p1_prev = NULL;
! 	for (p1 = list_head(pathlist); p1 != NULL;
  		 p1 = p1_next)
  	{
  		Path	   *old_path = (Path *) lfirst(p1);
*************** add_partial_path(RelOptInfo *parent_rel,
*** 819,830 ****
  		}
  
  		/*
! 		 * Remove current element from partial_pathlist if dominated by new.
  		 */
  		if (remove_old)
  		{
! 			parent_rel->partial_pathlist =
! 				list_delete_cell(parent_rel->partial_pathlist, p1, p1_prev);
  			pfree(old_path);
  			/* p1_prev does not advance */
  		}
--- 853,863 ----
  		}
  
  		/*
! 		 * Remove current element from pathlist if dominated by new.
  		 */
  		if (remove_old)
  		{
! 			pathlist = list_delete_cell(pathlist, p1, p1_prev);
  			pfree(old_path);
  			/* p1_prev does not advance */
  		}
*************** add_partial_path(RelOptInfo *parent_rel,
*** 839,845 ****
  
  		/*
  		 * If we found an old path that dominates new_path, we can quit
! 		 * scanning the partial_pathlist; we will not add new_path, and we
  		 * assume new_path cannot dominate any later path.
  		 */
  		if (!accept_new)
--- 872,878 ----
  
  		/*
  		 * If we found an old path that dominates new_path, we can quit
! 		 * scanning the pathlist; we will not add new_path, and we
  		 * assume new_path cannot dominate any later path.
  		 */
  		if (!accept_new)
*************** add_partial_path(RelOptInfo *parent_rel,
*** 850,859 ****
  	{
  		/* Accept the new path: insert it at proper place */
  		if (insert_after)
! 			lappend_cell(parent_rel->partial_pathlist, insert_after, new_path);
  		else
! 			parent_rel->partial_pathlist =
! 				lcons(new_path, parent_rel->partial_pathlist);
  	}
  	else
  	{
--- 883,896 ----
  	{
  		/* Accept the new path: insert it at proper place */
  		if (insert_after)
! 			lappend_cell(pathlist, insert_after, new_path);
  		else
! 			pathlist = lcons(new_path, pathlist);
! 
! 		if (!grouped)
! 			parent_rel->partial_pathlist = pathlist;
! 		else
! 			parent_rel->gpi->partial_pathlist = pathlist;
  	}
  	else
  	{
*************** add_partial_path(RelOptInfo *parent_rel,
*** 874,882 ****
   */
  bool
  add_partial_path_precheck(RelOptInfo *parent_rel, Cost total_cost,
! 						  List *pathkeys)
  {
  	ListCell   *p1;
  
  	/*
  	 * Our goal here is twofold.  First, we want to find out whether this path
--- 911,928 ----
   */
  bool
  add_partial_path_precheck(RelOptInfo *parent_rel, Cost total_cost,
! 						  List *pathkeys, bool grouped)
  {
  	ListCell   *p1;
+ 	List	   *pathlist;
+ 
+ 	if (!grouped)
+ 		pathlist = parent_rel->partial_pathlist;
+ 	else
+ 	{
+ 		Assert(parent_rel->gpi != NULL);
+ 		pathlist = parent_rel->gpi->partial_pathlist;
+ 	}
  
  	/*
  	 * Our goal here is twofold.  First, we want to find out whether this path
*************** add_partial_path_precheck(RelOptInfo *pa
*** 886,895 ****
  	 * final cost computations.  If so, we definitely want to consider it.
  	 *
  	 * Unlike add_path(), we always compare pathkeys here.  This is because we
! 	 * expect partial_pathlist to be very short, and getting a definitive
! 	 * answer at this stage avoids the need to call add_path_precheck.
  	 */
! 	foreach(p1, parent_rel->partial_pathlist)
  	{
  		Path	   *old_path = (Path *) lfirst(p1);
  		PathKeysComparison keyscmp;
--- 932,942 ----
  	 * final cost computations.  If so, we definitely want to consider it.
  	 *
  	 * Unlike add_path(), we always compare pathkeys here.  This is because we
! 	 * expect partial_pathlist / grouped_pathlist to be very short, and
! 	 * getting a definitive answer at this stage avoids the need to call
! 	 * add_path_precheck.
  	 */
! 	foreach(p1, pathlist)
  	{
  		Path	   *old_path = (Path *) lfirst(p1);
  		PathKeysComparison keyscmp;
*************** add_partial_path_precheck(RelOptInfo *pa
*** 918,924 ****
  	 * completion.
  	 */
  	if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! 						   NULL))
  		return false;
  
  	return true;
--- 965,971 ----
  	 * completion.
  	 */
  	if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! 						   NULL, grouped))
  		return false;
  
  	return true;
*************** calc_non_nestloop_required_outer(Path *o
*** 2056,2061 ****
--- 2103,2109 ----
   * 'restrict_clauses' are the RestrictInfo nodes to apply at the join
   * 'pathkeys' are the path keys of the new join path
   * 'required_outer' is the set of required outer rels
+  * 'target' can be passed to override that of joinrel.
   *
   * Returns the resulting path node.
   */
*************** create_nestloop_path(PlannerInfo *root,
*** 2070,2076 ****
  					 Path *inner_path,
  					 List *restrict_clauses,
  					 List *pathkeys,
! 					 Relids required_outer)
  {
  	NestPath   *pathnode = makeNode(NestPath);
  	Relids		inner_req_outer = PATH_REQ_OUTER(inner_path);
--- 2118,2125 ----
  					 Path *inner_path,
  					 List *restrict_clauses,
  					 List *pathkeys,
! 					 Relids required_outer,
! 					 PathTarget *target)
  {
  	NestPath   *pathnode = makeNode(NestPath);
  	Relids		inner_req_outer = PATH_REQ_OUTER(inner_path);
*************** create_nestloop_path(PlannerInfo *root,
*** 2103,2109 ****
  
  	pathnode->path.pathtype = T_NestLoop;
  	pathnode->path.parent = joinrel;
! 	pathnode->path.pathtarget = joinrel->reltarget;
  	pathnode->path.param_info =
  		get_joinrel_parampathinfo(root,
  								  joinrel,
--- 2152,2158 ----
  
  	pathnode->path.pathtype = T_NestLoop;
  	pathnode->path.parent = joinrel;
! 	pathnode->path.pathtarget = target == NULL ? joinrel->reltarget : target;
  	pathnode->path.param_info =
  		get_joinrel_parampathinfo(root,
  								  joinrel,
*************** create_mergejoin_path(PlannerInfo *root,
*** 2160,2172 ****
  					  Relids required_outer,
  					  List *mergeclauses,
  					  List *outersortkeys,
! 					  List *innersortkeys)
  {
  	MergePath  *pathnode = makeNode(MergePath);
  
  	pathnode->jpath.path.pathtype = T_MergeJoin;
  	pathnode->jpath.path.parent = joinrel;
! 	pathnode->jpath.path.pathtarget = joinrel->reltarget;
  	pathnode->jpath.path.param_info =
  		get_joinrel_parampathinfo(root,
  								  joinrel,
--- 2209,2223 ----
  					  Relids required_outer,
  					  List *mergeclauses,
  					  List *outersortkeys,
! 					  List *innersortkeys,
! 					  PathTarget *target)
  {
  	MergePath  *pathnode = makeNode(MergePath);
  
  	pathnode->jpath.path.pathtype = T_MergeJoin;
  	pathnode->jpath.path.parent = joinrel;
! 	pathnode->jpath.path.pathtarget = target == NULL ? joinrel->reltarget :
! 		target;
  	pathnode->jpath.path.param_info =
  		get_joinrel_parampathinfo(root,
  								  joinrel,
*************** create_mergejoin_path(PlannerInfo *root,
*** 2210,2215 ****
--- 2261,2267 ----
   * 'required_outer' is the set of required outer rels
   * 'hashclauses' are the RestrictInfo nodes to use as hash clauses
   *		(this should be a subset of the restrict_clauses list)
+  * 'target' can be passed to override that of joinrel.
   */
  HashPath *
  create_hashjoin_path(PlannerInfo *root,
*************** create_hashjoin_path(PlannerInfo *root,
*** 2222,2234 ****
  					 Path *inner_path,
  					 List *restrict_clauses,
  					 Relids required_outer,
! 					 List *hashclauses)
  {
  	HashPath   *pathnode = makeNode(HashPath);
  
  	pathnode->jpath.path.pathtype = T_HashJoin;
  	pathnode->jpath.path.parent = joinrel;
! 	pathnode->jpath.path.pathtarget = joinrel->reltarget;
  	pathnode->jpath.path.param_info =
  		get_joinrel_parampathinfo(root,
  								  joinrel,
--- 2274,2288 ----
  					 Path *inner_path,
  					 List *restrict_clauses,
  					 Relids required_outer,
! 					 List *hashclauses,
! 					 PathTarget *target)
  {
  	HashPath   *pathnode = makeNode(HashPath);
  
  	pathnode->jpath.path.pathtype = T_HashJoin;
  	pathnode->jpath.path.parent = joinrel;
! 	pathnode->jpath.path.pathtarget = target == NULL ? joinrel->reltarget :
! 		target;
  	pathnode->jpath.path.param_info =
  		get_joinrel_parampathinfo(root,
  								  joinrel,
*************** create_agg_path(PlannerInfo *root,
*** 2682,2688 ****
  	pathnode->path.pathtarget = target;
  	/* For now, assume we are above any joins, so no parameterization */
  	pathnode->path.param_info = NULL;
! 	pathnode->path.parallel_aware = false;
  	pathnode->path.parallel_safe = rel->consider_parallel &&
  		subpath->parallel_safe;
  	pathnode->path.parallel_workers = subpath->parallel_workers;
--- 2736,2742 ----
  	pathnode->path.pathtarget = target;
  	/* For now, assume we are above any joins, so no parameterization */
  	pathnode->path.param_info = NULL;
! 	pathnode->path.parallel_aware = true;
  	pathnode->path.parallel_safe = rel->consider_parallel &&
  		subpath->parallel_safe;
  	pathnode->path.parallel_workers = subpath->parallel_workers;
*************** create_agg_path(PlannerInfo *root,
*** 2713,2718 ****
--- 2767,2942 ----
  }
  
  /*
+  * Apply partial AGG_SORTED aggregation path to subpath if it's suitably
+  * sorted.
+  *
+  * first_call indicates whether the function is being called first time for
+  * given index --- since the target should not change, we can skip the check
+  * of sorting during subsequent calls.
+  *
+  * group_clauses, group_exprs and agg_exprs are pointers to lists we populate
+  * when called first time for particular index, and that user passes for
+  * subsequent calls.
+  *
+  * NULL is returned if sorting of subpath output is not suitable.
+  */
+ AggPath *
+ create_partial_agg_sorted_path(PlannerInfo *root, Path *subpath,
+ 							   bool first_call,
+ 							   List **group_clauses, List **group_exprs,
+ 							   List **agg_exprs, double input_rows)
+ {
+ 	RelOptInfo	*rel;
+ 	AggClauseCosts  agg_costs;
+ 	double	dNumGroups;
+ 	AggPath	*result = NULL;
+ 
+ 	rel = subpath->parent;
+ 	Assert(rel->gpi != NULL);
+ 
+ 	if (subpath->pathkeys == NIL)
+ 		return NULL;
+ 
+ 	if (!grouping_is_sortable(root->parse->groupClause))
+ 		return NULL;
+ 
+ 	if (first_call)
+ 	{
+ 		ListCell	*lc1;
+ 		List	*key_subset = NIL;
+ 
+ 		/*
+ 		 * Find all query pathkeys that our relation does affect.
+ 		 */
+ 		foreach(lc1, root->group_pathkeys)
+ 		{
+ 			PathKey	*gkey = castNode(PathKey, lfirst(lc1));
+ 			ListCell	*lc2;
+ 
+ 			foreach(lc2, subpath->pathkeys)
+ 			{
+ 				PathKey	*skey = castNode(PathKey, lfirst(lc2));
+ 
+ 				if (skey == gkey)
+ 				{
+ 					key_subset = lappend(key_subset, gkey);
+ 					break;
+ 				}
+ 			}
+ 		}
+ 
+ 		if (key_subset == NIL)
+ 			return NULL;
+ 
+ 		/* Check if AGG_SORTED is useful for the whole query.  */
+ 		if (!pathkeys_contained_in(key_subset, subpath->pathkeys))
+ 			return NULL;
+ 	}
+ 
+ 	if (first_call)
+ 		get_grouping_expressions(root, rel->gpi->target, group_clauses,
+ 								 group_exprs, agg_exprs);
+ 
+ 	MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
+ 	Assert(*agg_exprs != NIL);
+ 	get_agg_clause_costs(root, (Node *) *agg_exprs, AGGSPLIT_INITIAL_SERIAL,
+ 						 &agg_costs);
+ 
+ 	Assert(*group_exprs != NIL);
+ 	dNumGroups = estimate_num_groups(root, *group_exprs, input_rows, NULL);
+ 
+ 	/* TODO HAVING qual. */
+ 	Assert(*group_clauses != NIL);
+ 	result = create_agg_path(root, rel, subpath, rel->gpi->target, AGG_SORTED,
+ 							 AGGSPLIT_INITIAL_SERIAL, *group_clauses, NIL,
+ 							 &agg_costs, dNumGroups);
+ 
+ 	return result;
+ }
+ 
+ /*
+  * Appy partial AGG_HASHED aggregation to subpath.
+  *
+  * Arguments have the same meaning as those of create_agg_sorted_path.
+  *
+  */
+ AggPath *
+ create_partial_agg_hashed_path(PlannerInfo *root, Path *subpath,
+ 							   bool first_call,
+ 							   List **group_clauses, List **group_exprs,
+ 							   List **agg_exprs, double input_rows)
+ {
+ 	RelOptInfo	*rel;
+ 	bool	can_hash;
+ 	AggClauseCosts  agg_costs;
+ 	double	dNumGroups;
+ 	Size	hashaggtablesize;
+ 	Query	   *parse = root->parse;
+ 	AggPath	*result = NULL;
+ 
+ 	rel = subpath->parent;
+ 	Assert(rel->gpi != NULL);
+ 
+ 	if (first_call)
+ 	{
+ 		/*
+ 		 * Find one grouping clause per grouping column.
+ 		 *
+ 		 * All that create_agg_plan eventually needs of the clause is
+ 		 * tleSortGroupRef, so we don't have to care that the clause
+ 		 * expression might differ from texpr, in case texpr was derived from
+ 		 * EC.
+ 		 */
+ 		get_grouping_expressions(root, rel->gpi->target, group_clauses,
+ 								 group_exprs, agg_exprs);
+ 	}
+ 
+ 	MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
+ 	Assert(*agg_exprs != NIL);
+ 	get_agg_clause_costs(root, (Node *) *agg_exprs, AGGSPLIT_INITIAL_SERIAL,
+ 						 &agg_costs);
+ 
+ 	can_hash = (parse->groupClause != NIL &&
+ 				parse->groupingSets == NIL &&
+ 				agg_costs.numOrderedAggs == 0 &&
+ 				grouping_is_hashable(parse->groupClause));
+ 
+ 	if (can_hash)
+ 	{
+ 		Assert(*group_exprs != NIL);
+ 		dNumGroups = estimate_num_groups(root, *group_exprs, input_rows,
+ 										 NULL);
+ 
+ 		hashaggtablesize = estimate_hashagg_tablesize(subpath, &agg_costs,
+ 													  dNumGroups);
+ 
+ 		if (hashaggtablesize < work_mem * 1024L)
+ 		{
+ 			/*
+ 			 * Create the partial aggregation path.
+ 			 */
+ 			Assert(*group_clauses != NIL);
+ 
+ 			result = create_agg_path(root, rel, subpath,
+ 									 rel->gpi->target,
+ 									 AGG_HASHED,
+ 									 AGGSPLIT_INITIAL_SERIAL,
+ 									 *group_clauses, NIL,
+ 									 &agg_costs,
+ 									 dNumGroups);
+ 
+ 			/*
+ 			 * The agg path should require no fewer parameters than the plain
+ 			 * one.
+ 			 */
+ 			result->path.param_info = subpath->param_info;
+ 		}
+ 	}
+ 
+ 	return result;
+ }
+ 
+ /*
   * create_groupingsets_path
   *	  Creates a pathnode that represents performing GROUPING SETS aggregation
   *
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
new file mode 100644
index 7912df0..cc7f6d3
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
***************
*** 25,30 ****
--- 25,31 ----
  #include "optimizer/plancat.h"
  #include "optimizer/restrictinfo.h"
  #include "optimizer/tlist.h"
+ #include "optimizer/var.h"
  #include "utils/hsearch.h"
  
  
*************** typedef struct JoinHashEntry
*** 35,41 ****
  } JoinHashEntry;
  
  static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! 					RelOptInfo *input_rel);
  static List *build_joinrel_restrictlist(PlannerInfo *root,
  						   RelOptInfo *joinrel,
  						   RelOptInfo *outer_rel,
--- 36,42 ----
  } JoinHashEntry;
  
  static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! 								RelOptInfo *input_rel, bool grouped);
  static List *build_joinrel_restrictlist(PlannerInfo *root,
  						   RelOptInfo *joinrel,
  						   RelOptInfo *outer_rel,
*************** build_simple_rel(PlannerInfo *root, int
*** 120,125 ****
--- 121,127 ----
  	rel->cheapest_parameterized_paths = NIL;
  	rel->direct_lateral_relids = NULL;
  	rel->lateral_relids = NULL;
+ 	rel->gpi = NULL;
  	rel->relid = relid;
  	rel->rtekind = rte->rtekind;
  	/* min_attr, max_attr, attr_needed, attr_widths are set below */
*************** build_join_rel(PlannerInfo *root,
*** 478,483 ****
--- 480,486 ----
  				  inner_rel->direct_lateral_relids);
  	joinrel->lateral_relids = min_join_parameterization(root, joinrel->relids,
  														outer_rel, inner_rel);
+ 	joinrel->gpi = NULL;
  	joinrel->relid = 0;			/* indicates not a baserel */
  	joinrel->rtekind = RTE_JOIN;
  	joinrel->min_attr = 0;
*************** build_join_rel(PlannerInfo *root,
*** 516,525 ****
  	 * and inner rels we first try to build it from.  But the contents should
  	 * be the same regardless.
  	 */
! 	build_joinrel_tlist(root, joinrel, outer_rel);
! 	build_joinrel_tlist(root, joinrel, inner_rel);
  	add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel);
  
  	/*
  	 * add_placeholders_to_joinrel also took care of adding the ph_lateral
  	 * sets of any PlaceHolderVars computed here to direct_lateral_relids, so
--- 519,535 ----
  	 * and inner rels we first try to build it from.  But the contents should
  	 * be the same regardless.
  	 */
! 	build_joinrel_tlist(root, joinrel, outer_rel, false);
! 	build_joinrel_tlist(root, joinrel, inner_rel, false);
  	add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel);
  
+ 	/* Try to build grouped target. */
+ 	/*
+ 	 * TODO Consider if placeholders make sense here. If not, also make the
+ 	 * related code below conditional.
+ 	 */
+ 	prepare_rel_for_grouping(root, joinrel);
+ 
  	/*
  	 * add_placeholders_to_joinrel also took care of adding the ph_lateral
  	 * sets of any PlaceHolderVars computed here to direct_lateral_relids, so
*************** min_join_parameterization(PlannerInfo *r
*** 647,663 ****
   */
  static void
  build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! 					RelOptInfo *input_rel)
  {
  	Relids		relids = joinrel->relids;
  	ListCell   *vars;
  
! 	foreach(vars, input_rel->reltarget->exprs)
  	{
  		Var		   *var = (Var *) lfirst(vars);
  		RelOptInfo *baserel;
  		int			ndx;
  
  		/*
  		 * Ignore PlaceHolderVars in the input tlists; we'll make our own
  		 * decisions about whether to copy them.
--- 657,699 ----
   */
  static void
  build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! 					RelOptInfo *input_rel, bool grouped)
  {
  	Relids		relids = joinrel->relids;
+ 	PathTarget  *input_target, *result;
  	ListCell   *vars;
+ 	int			i = -1;
  
! 	if (!grouped)
! 	{
! 		input_target = input_rel->reltarget;
! 		result = joinrel->reltarget;
! 	}
! 	else
! 	{
! 		if (input_rel->gpi != NULL)
! 		{
! 			input_target = input_rel->gpi->target;
! 			Assert(input_target != NULL);
! 		}
! 		else
! 			input_target = input_rel->reltarget;
! 
! 		/* Caller should have initialized this. */
! 		Assert(joinrel->gpi != NULL);
! 
! 		/* Default to the plain target. */
! 		result = joinrel->gpi->target;
! 	}
! 
! 	foreach(vars, input_target->exprs)
  	{
  		Var		   *var = (Var *) lfirst(vars);
  		RelOptInfo *baserel;
  		int			ndx;
  
+ 		i++;
+ 
  		/*
  		 * Ignore PlaceHolderVars in the input tlists; we'll make our own
  		 * decisions about whether to copy them.
*************** build_joinrel_tlist(PlannerInfo *root, R
*** 681,690 ****
  		ndx = var->varattno - baserel->min_attr;
  		if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
  		{
  			/* Yup, add it to the output */
! 			joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs, var);
  			/* Vars have cost zero, so no need to adjust reltarget->cost */
! 			joinrel->reltarget->width += baserel->attr_widths[ndx];
  		}
  	}
  }
--- 717,740 ----
  		ndx = var->varattno - baserel->min_attr;
  		if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
  		{
+ 			Index sortgroupref = 0;
+ 
  			/* Yup, add it to the output */
! 			if (input_target->sortgrouprefs)
! 				sortgroupref = input_target->sortgrouprefs[i];
! 
! 			/*
! 			 * Even if not used for grouping in the input path (the input path
! 			 * is not necessarily grouped), it might be useful for grouping
! 			 * higher in the join tree.
! 			 */
! 			if (sortgroupref == 0)
! 				sortgroupref = get_expr_sortgroupref(root, (Expr *) var);
! 
! 			add_column_to_pathtarget(result, (Expr *) var, sortgroupref);
! 
  			/* Vars have cost zero, so no need to adjust reltarget->cost */
! 			result->width += baserel->attr_widths[ndx];
  		}
  	}
  }
*************** get_appendrel_parampathinfo(RelOptInfo *
*** 1360,1362 ****
--- 1410,1561 ----
  
  	return ppi;
  }
+ 
+ /*
+  * If the relation can produce grouped paths, create GroupedPathInfo for it
+  * and create target for the grouped paths.
+  */
+ void
+ prepare_rel_for_grouping(PlannerInfo *root, RelOptInfo *rel)
+ {
+ 	List	*rel_aggregates;
+ 	Relids	rel_agg_attrs = NULL;
+ 	List	*rel_agg_vars = NIL;
+ 	bool	found_higher;
+ 	ListCell	*lc;
+ 	PathTarget	*target_grouped;
+ 
+ 	if (rel->relid > 0)
+ 	{
+ 		RangeTblEntry *rte = root->simple_rte_array[rel->relid];;
+ 
+ 		/*
+ 		 * rtekind != RTE_RELATION case is not supported yet.
+ 		 */
+ 		if (rte->rtekind != RTE_RELATION)
+ 			return;
+ 	}
+ 
+ 	/* Caller should only pass base relations or joins. */
+ 	Assert(rel->reloptkind == RELOPT_BASEREL ||
+ 		   rel->reloptkind == RELOPT_JOINREL);
+ 
+ 	/*
+ 	 * If any outer join can set the attribute value to NULL, the aggregate
+ 	 * would receive different input at the base rel level.
+ 	 *
+ 	 * TODO For RELOPT_JOINREL, do not return if all the joins that can set
+ 	 * any entry of the grouped target (do we need to postpone this check
+ 	 * until the grouped target is available, and should create_grouped_target
+ 	 * take care?) of this rel to NULL are provably below rel. (It's ok if rel
+ 	 * is one of these joins.)
+ 	 */
+ 	if (bms_overlap(rel->relids, root->nullable_baserels))
+ 		return;
+ 
+ 	/*
+ 	 * Check if some aggregates can be evaluated in this relation's target,
+ 	 * and collect all vars referenced by these aggregates.
+ 	 */
+ 	rel_aggregates = NIL;
+ 	found_higher = false;
+ 	foreach(lc, root->grouped_var_list)
+ 	{
+ 		GroupedVarInfo	*gvi = castNode(GroupedVarInfo, lfirst(lc));
+ 
+ 		/*
+ 		 * The subset includes gv_eval_at uninitialized, which typically means
+ 		 * Aggref.aggstar.
+ 		 */
+ 		if (bms_is_subset(gvi->gv_eval_at, rel->relids))
+ 		{
+ 			Aggref	*aggref = castNode(Aggref, gvi->gvexpr);
+ 
+ 			/*
+ 			 * Accept the aggregate.
+ 			 *
+ 			 * GroupedVarInfo is more convenient for the next processing than
+ 			 * Aggref, see add_aggregates_to_grouped_target.
+ 			 */
+ 			rel_aggregates = lappend(rel_aggregates, gvi);
+ 
+ 			if (rel->relid > 0)
+ 			{
+ 				/*
+ 				 * Simple relation. Collect attributes referenced by the
+ 				 * aggregate arguments.
+ 				 */
+ 				pull_varattnos((Node *) aggref, rel->relid, &rel_agg_attrs);
+ 			}
+ 			else
+ 			{
+ 				List	*agg_vars;
+ 
+ 				/*
+ 				 * Join. Collect vars referenced by the aggregate
+ 				 * arguments.
+ 				 */
+ 				/*
+ 				 * TODO Can any argument contain PHVs? And if so, does it matter?
+ 				 * Consider PVC_INCLUDE_PLACEHOLDERS | PVC_RECURSE_PLACEHOLDERS.
+ 				 */
+ 				agg_vars = pull_var_clause((Node *) aggref,
+ 										   PVC_RECURSE_AGGREGATES);
+ 				rel_agg_vars = list_concat(rel_agg_vars, agg_vars);
+ 			}
+ 		}
+ 		else if (bms_overlap(gvi->gv_eval_at, rel->relids))
+ 		{
+ 			/*
+ 			 * Remember that there is at least one aggregate that needs more
+ 			 * than this rel.
+ 			 */
+ 			found_higher = true;
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Grouping makes little sense w/o aggregate function.
+ 	 */
+ 	if (rel_aggregates == NIL)
+ 	{
+ 		bms_free(rel_agg_attrs);
+ 		return;
+ 	}
+ 
+ 	if (found_higher)
+ 	{
+ 		/*
+ 		 * If some aggregate(s) need only this rel but some other need
+ 		 * multiple relations including the the current one, grouping of the
+ 		 * current rel could steal some input variables from the "higher
+ 		 * aggregate" (besides decreasing the number of input rows).
+ 		 */
+ 		list_free(rel_aggregates);
+ 		bms_free(rel_agg_attrs);
+ 		return;
+ 	}
+ 
+ 	/*
+ 	 * If rel->reltarget can be used for aggregation, mark the relation as
+ 	 * capable of grouping.
+ 	 */
+ 	Assert(rel->gpi == NULL);
+ 	target_grouped = create_grouped_target(root, rel, rel_agg_attrs,
+ 										   rel_agg_vars);
+ 	if (target_grouped != NULL)
+ 	{
+ 		GroupedPathInfo	*gpi;
+ 
+ 		gpi = makeNode(GroupedPathInfo);
+ 		gpi->target = copy_pathtarget(target_grouped);
+ 		gpi->pathlist = NIL;
+ 		gpi->partial_pathlist = NIL;
+ 		rel->gpi = gpi;
+ 
+ 		/*
+ 		 * Add aggregates (in the form of GroupedVar) to the target.
+ 		 */
+ 		add_aggregates_to_target(root, gpi->target, rel_aggregates, rel);
+ 	}
+ }
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
new file mode 100644
index 0952385..dd962b7
*** a/src/backend/optimizer/util/tlist.c
--- b/src/backend/optimizer/util/tlist.c
*************** get_sortgrouplist_exprs(List *sgClauses,
*** 408,413 ****
--- 408,487 ----
  	return result;
  }
  
+ /*
+  * get_sortgrouplist_clauses
+  *
+  *		Given a "grouped target" (i.e. target where each non-GroupedVar
+  *		element must have sortgroupref set), build a list of the referencing
+  *		SortGroupClauses, a list of the corresponding grouping expressions and
+  *		a list of aggregate expressions.
+  */
+ /* Refine the function name. */
+ void
+ get_grouping_expressions(PlannerInfo *root, PathTarget *target,
+ 						 List **grouping_clauses, List **grouping_exprs,
+ 						 List **agg_exprs)
+ {
+ 	ListCell   *l;
+ 	int		i = 0;
+ 
+ 	foreach(l, target->exprs)
+ 	{
+ 		Index	sortgroupref = 0;
+ 		SortGroupClause *cl;
+ 		Expr		*texpr;
+ 
+ 		texpr = (Expr *) lfirst(l);
+ 
+ 		/* The target should contain at least one grouping column. */
+ 		Assert(target->sortgrouprefs != NULL);
+ 
+ 		if (IsA(texpr, GroupedVar))
+ 		{
+ 			/*
+ 			 * texpr should represent the first aggregate in the targetlist.
+ 			 */
+ 			break;
+ 		}
+ 
+ 		/*
+ 		 * Find the clause by sortgroupref.
+ 		 */
+ 		sortgroupref = target->sortgrouprefs[i++];
+ 
+ 		/*
+ 		 * Besides aggregates, the target should contain no expressions w/o
+ 		 * sortgroupref. Plain relation being joined to grouped can have
+ 		 * sortgroupref equal to zero for expressions contained neither in
+ 		 * grouping expression nor in aggregate arguments, but if the target
+ 		 * contains such an expression, it shouldn't be used for aggregation
+ 		 * --- see can_aggregate field of GroupedPathInfo.
+ 		 */
+ 		Assert(sortgroupref > 0);
+ 
+ 		cl = get_sortgroupref_clause(sortgroupref, root->parse->groupClause);
+ 		*grouping_clauses = list_append_unique(*grouping_clauses, cl);
+ 
+ 		/*
+ 		 * Add only unique clauses because of joins (both sides of a join can
+ 		 * point at the same grouping clause). XXX Is it worth adding a bool
+ 		 * argument indicating that we're dealing with join right now?
+ 		 */
+ 		*grouping_exprs = list_append_unique(*grouping_exprs, texpr);
+ 	}
+ 
+ 	/* Now collect the aggregates. */
+ 	while (l != NULL)
+ 	{
+ 		GroupedVar	*gvar = castNode(GroupedVar, lfirst(l));
+ 
+ 		/* Currently, GroupedVarInfo can only represent aggregate. */
+ 		Assert(gvar->agg_partial != NULL);
+ 		*agg_exprs = lappend(*agg_exprs, gvar->agg_partial);
+ 		l = lnext(l);
+ 	}
+ }
+ 
  
  /*****************************************************************************
   *		Functions to extract data from a list of SortGroupClauses
*************** apply_pathtarget_labeling_to_tlist(List
*** 783,788 ****
--- 857,1081 ----
  }
  
  /*
+  * Replace each "grouped var" in the source targetlist with the original
+  * expression.
+  *
+  * TODO Think of more suitable name. Although "grouped var" may substitute for
+  * grouping expressions in the future, currently Aggref is the only outcome of
+  * the replacement. undo_grouped_var_substitutions?
+  */
+ List *
+ restore_grouping_expressions(PlannerInfo *root, List *src)
+ {
+ 	List	*result = NIL;
+ 	ListCell	*l;
+ 
+ 	foreach(l, src)
+ 	{
+ 		TargetEntry	*te, *te_new;
+ 		Aggref	*expr_new = NULL;
+ 
+ 		te = castNode(TargetEntry, lfirst(l));
+ 
+ 		if (IsA(te->expr, GroupedVar))
+ 		{
+ 			GroupedVar	*gvar;
+ 
+ 			gvar = castNode(GroupedVar, te->expr);
+ 			expr_new = gvar->agg_partial;
+ 		}
+ 
+ 		if (expr_new != NULL)
+ 		{
+ 			te_new = flatCopyTargetEntry(te);
+ 			te_new->expr = (Expr *) expr_new;
+ 		}
+ 		else
+ 			te_new = te;
+ 		result = lappend(result, te_new);
+ 	}
+ 
+ 	return result;
+ }
+ 
+ /*
+  * For each aggregate add GroupedVar to target if "vars" is true, or the
+  * Aggref (marked as partial) if "vars" is false.
+  *
+  * If caller passes the aggregates, he must do so in the form of
+  * GroupedVarInfos so that we don't have to look for gvid. If NULL is passed,
+  * the function retrieves the suitable aggregates itself.
+  *
+  * List of the aggregates added is returned. This is only useful if the
+  * function had to retrieve the aggregates itself (i.e. NIL was passed for
+  * aggregates) -- caller is expected to do extra checks in that case (and to
+  * also free the list).
+  */
+ List *
+ add_aggregates_to_target(PlannerInfo *root, PathTarget *target,
+ 						 List *aggregates, RelOptInfo *rel)
+ {
+ 	ListCell	*lc;
+ 	GroupedVarInfo	*gvi;
+ 
+ 	if (aggregates == NIL)
+ 	{
+ 		/* Caller should pass the aggregates for base relation. */
+ 		Assert(rel->reloptkind != RELOPT_BASEREL);
+ 
+ 		/* Collect all aggregates that this rel can evaluate. */
+ 		foreach(lc, root->grouped_var_list)
+ 		{
+ 			gvi = castNode(GroupedVarInfo, lfirst(lc));
+ 
+ 			/*
+ 			 * Overlap is not guarantee of correctness alone, but caller needs
+ 			 * to do additional checks, so we're optimistic here.
+ 			 *
+ 			 * If gv_eval_at is NULL, the underlying Aggref should have
+ 			 * aggstar set.
+ 			 */
+ 			if (bms_overlap(gvi->gv_eval_at, rel->relids) ||
+ 				gvi->gv_eval_at == NULL)
+ 				aggregates = lappend(aggregates, gvi);
+ 		}
+ 
+ 		if (aggregates == NIL)
+ 			return NIL;
+ 	}
+ 
+ 	/* Create the vars and add them to the target. */
+ 	foreach(lc, aggregates)
+ 	{
+ 		GroupedVar	*gvar;
+ 
+ 		gvi = castNode(GroupedVarInfo, lfirst(lc));
+ 		gvar = makeNode(GroupedVar);
+ 		gvar->gvid = gvi->gvid;
+ 		gvar->gvexpr = gvi->gvexpr;
+ 		gvar->agg_partial = gvi->agg_partial;
+ 		add_new_column_to_pathtarget(target, (Expr *) gvar);
+ 	}
+ 
+ 	return aggregates;
+ }
+ 
+ /*
+  * Return ressortgroupref of the target entry that is either equal to the
+  * expression or exists in the same equivalence class.
+  */
+ Index
+ get_expr_sortgroupref(PlannerInfo *root, Expr *expr)
+ {
+ 	ListCell	*lc;
+ 	Index		sortgroupref;
+ 
+ 	/*
+ 	 * First, check if the query group clause contains exactly this
+ 	 * expression.
+ 	 */
+ 	foreach(lc, root->processed_tlist)
+ 	{
+ 		TargetEntry		*te = castNode(TargetEntry, lfirst(lc));
+ 
+ 		if (equal(expr, te->expr) && te->ressortgroupref > 0)
+ 			return te->ressortgroupref;
+ 	}
+ 
+ 	/*
+ 	 * If exactly this expression is not there, check if a grouping clause
+ 	 * exists that belongs to the same equivalence class as the expression.
+ 	 */
+ 	foreach(lc, root->group_pathkeys)
+ 	{
+ 		PathKey	*pk = castNode(PathKey, lfirst(lc));
+ 		EquivalenceClass		*ec = pk->pk_eclass;
+ 		ListCell		*lm;
+ 		EquivalenceMember		*em;
+ 		Expr	*em_expr = NULL;
+ 		Query	*query = root->parse;
+ 
+ 		/*
+ 		 * Single-member EC cannot provide us with additional expression.
+ 		 */
+ 		if (list_length(ec->ec_members) < 2)
+ 			continue;
+ 
+ 		/* We need equality anywhere in the join tree. */
+ 		if (ec->ec_below_outer_join)
+ 			continue;
+ 
+ 		/*
+ 		 * TODO Reconsider this restriction. As the grouping expression is
+ 		 * only evaluated at the relation level (and only the result will be
+ 		 * propagated to the final targetlist), volatile function might be
+ 		 * o.k. Need to think what volatile EC exactly means.
+ 		 */
+ 		if (ec->ec_has_volatile)
+ 			continue;
+ 
+ 		foreach(lm, ec->ec_members)
+ 		{
+ 			em = (EquivalenceMember *) lfirst(lm);
+ 
+ 			/* The EC has !ec_below_outer_join. */
+ 			Assert(!em->em_nullable_relids);
+ 			if (equal(em->em_expr, expr))
+ 			{
+ 				em_expr = (Expr *) em->em_expr;
+ 				break;
+ 			}
+ 		}
+ 
+ 		if (em_expr == NULL)
+ 			/* Go for the next EC. */
+ 			continue;
+ 
+ 		/*
+ 		 * Find the corresponding SortGroupClause, which provides us with
+ 		 * sortgroupref. (It can belong to any EC member.)
+ 		 */
+ 		sortgroupref = 0;
+ 		foreach(lm, ec->ec_members)
+ 		{
+ 			ListCell	*lsg;
+ 
+ 			em = (EquivalenceMember *) lfirst(lm);
+ 			foreach(lsg, query->groupClause)
+ 			{
+ 				SortGroupClause	*sgc;
+ 				Expr	*expr;
+ 
+ 				sgc = (SortGroupClause *) lfirst(lsg);
+ 				expr = (Expr *) get_sortgroupclause_expr(sgc,
+ 														 query->targetList);
+ 				if (equal(em->em_expr, expr))
+ 				{
+ 					Assert(sgc->tleSortGroupRef > 0);
+ 					sortgroupref = sgc->tleSortGroupRef;
+ 					break;
+ 				}
+ 			}
+ 
+ 			if (sortgroupref > 0)
+ 				break;
+ 		}
+ 
+ 		/*
+ 		 * Since we searched in group_pathkeys, at least one EM of this EC
+ 		 * should correspond to a SortGroupClause, otherwise the EC could
+ 		 * not exist at all.
+ 		 */
+ 		Assert(sortgroupref > 0);
+ 
+ 		return sortgroupref;
+ 	}
+ 
+ 	/* No EC found in group_pathkeys. */
+ 	return 0;
+ }
+ 
+ /*
   * split_pathtarget_at_srfs
   *		Split given PathTarget into multiple levels to position SRFs safely
   *
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
new file mode 100644
index 0c1a201..f4639c4
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** get_rule_expr(Node *node, deparse_contex
*** 7499,7504 ****
--- 7499,7512 ----
  			get_agg_expr((Aggref *) node, context, (Aggref *) node);
  			break;
  
+ 		case T_GroupedVar:
+ 		{
+ 			GroupedVar *gvar = castNode(GroupedVar, node);
+ 
+ 			get_agg_expr(gvar->agg_partial, context, (Aggref *) gvar->gvexpr);
+ 			break;
+ 		}
+ 
  		case T_GroupingFunc:
  			{
  				GroupingFunc *gexpr = (GroupingFunc *) node;
*************** get_agg_combine_expr(Node *node, deparse
*** 8933,8942 ****
  	Aggref	   *aggref;
  	Aggref	   *original_aggref = private;
  
! 	if (!IsA(node, Aggref))
  		elog(ERROR, "combining Aggref does not point to an Aggref");
  
- 	aggref = (Aggref *) node;
  	get_agg_expr(aggref, context, original_aggref);
  }
  
--- 8941,8958 ----
  	Aggref	   *aggref;
  	Aggref	   *original_aggref = private;
  
! 	if (IsA(node, Aggref))
! 		aggref = (Aggref *) node;
! 	else if (IsA(node, GroupedVar))
! 	{
! 		GroupedVar *gvar = castNode(GroupedVar, node);
! 
! 		aggref = gvar->agg_partial;
! 		original_aggref = castNode(Aggref, gvar->gvexpr);
! 	}
! 	else
  		elog(ERROR, "combining Aggref does not point to an Aggref");
  
  	get_agg_expr(aggref, context, original_aggref);
  }
  
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
new file mode 100644
index 5c382a2..1dd2d73
*** a/src/backend/utils/adt/selfuncs.c
--- b/src/backend/utils/adt/selfuncs.c
***************
*** 113,118 ****
--- 113,119 ----
  #include "catalog/pg_statistic_ext.h"
  #include "catalog/pg_type.h"
  #include "executor/executor.h"
+ #include "executor/nodeAgg.h"
  #include "mb/pg_wchar.h"
  #include "nodes/makefuncs.h"
  #include "nodes/nodeFuncs.h"
*************** estimate_num_groups(PlannerInfo *root, L
*** 3473,3479 ****
  		/*
  		 * Sanity check --- don't divide by zero if empty relation.
  		 */
! 		Assert(rel->reloptkind == RELOPT_BASEREL);
  		if (rel->tuples > 0)
  		{
  			/*
--- 3474,3481 ----
  		/*
  		 * Sanity check --- don't divide by zero if empty relation.
  		 */
! 		Assert(rel->reloptkind == RELOPT_BASEREL ||
! 			   rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
  		if (rel->tuples > 0)
  		{
  			/*
*************** estimate_hash_bucketsize(PlannerInfo *ro
*** 3704,3709 ****
--- 3706,3744 ----
  	return (Selectivity) estfract;
  }
  
+ /*
+  * estimate_hashagg_tablesize
+  *	  estimate the number of bytes that a hash aggregate hashtable will
+  *	  require based on the agg_costs, path width and dNumGroups.
+  *
+  * XXX this may be over-estimating the size now that hashagg knows to omit
+  * unneeded columns from the hashtable. Also for mixed-mode grouping sets,
+  * grouping columns not in the hashed set are counted here even though hashagg
+  * won't store them. Is this a problem?
+  */
+ Size
+ estimate_hashagg_tablesize(Path *path, const AggClauseCosts *agg_costs,
+ 						   double dNumGroups)
+ {
+ 	Size		hashentrysize;
+ 
+ 	/* Estimate per-hash-entry space at tuple width... */
+ 	hashentrysize = MAXALIGN(path->pathtarget->width) +
+ 		MAXALIGN(SizeofMinimalTupleHeader);
+ 
+ 	/* plus space for pass-by-ref transition values... */
+ 	hashentrysize += agg_costs->transitionSpace;
+ 	/* plus the per-hash-entry overhead */
+ 	hashentrysize += hash_agg_entry_size(agg_costs->numAggs);
+ 
+ 	/*
+ 	 * Note that this disregards the effect of fill-factor and growth policy
+ 	 * of the hash-table. That's probably ok, given default the default
+ 	 * fill-factor is relatively high. It'd be hard to meaningfully factor in
+ 	 * "double-in-size" growth policies here.
+ 	 */
+ 	return hashentrysize * dNumGroups;
+ }
  
  /*-------------------------------------------------------------------------
   *
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
new file mode 100644
index 177853b..df98ef7
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 217,222 ****
--- 217,223 ----
  	T_IndexOptInfo,
  	T_ForeignKeyOptInfo,
  	T_ParamPathInfo,
+ 	T_GroupedPathInfo,
  	T_Path,
  	T_IndexPath,
  	T_BitmapHeapPath,
*************** typedef enum NodeTag
*** 257,266 ****
--- 258,269 ----
  	T_PathTarget,
  	T_RestrictInfo,
  	T_PlaceHolderVar,
+ 	T_GroupedVar,
  	T_SpecialJoinInfo,
  	T_AppendRelInfo,
  	T_PartitionedChildRelInfo,
  	T_PlaceHolderInfo,
+ 	T_GroupedVarInfo,
  	T_MinMaxAggInfo,
  	T_PlannerParamItem,
  	T_RollupData,
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
new file mode 100644
index ebf9480..90588d9
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct PlannerInfo
*** 256,261 ****
--- 256,263 ----
  
  	List	   *placeholder_list;		/* list of PlaceHolderInfos */
  
+ 	List		*grouped_var_list; /* List of GroupedVarInfos. */
+ 
  	List	   *fkey_list;		/* list of ForeignKeyOptInfos */
  
  	List	   *query_pathkeys; /* desired pathkeys for query_planner() */
*************** typedef struct PlannerInfo
*** 401,406 ****
--- 403,410 ----
   *		direct_lateral_relids - rels this rel has direct LATERAL references to
   *		lateral_relids - required outer rels for LATERAL, as a Relids set
   *			(includes both direct and indirect lateral references)
+  *		gpi - GroupedPathInfo if the relation can produce grouped paths, NULL
+  *		otherwise.
   *
   * If the relation is a base relation it will have these fields set:
   *
*************** typedef struct RelOptInfo
*** 518,523 ****
--- 522,530 ----
  	Relids		direct_lateral_relids;	/* rels directly laterally referenced */
  	Relids		lateral_relids; /* minimum parameterization of rel */
  
+ 	/* Information needed to produce grouped paths. */
+ 	struct GroupedPathInfo	*gpi;
+ 
  	/* information about a base rel (not set for join rels!) */
  	Index		relid;
  	Oid			reltablespace;	/* containing tablespace */
*************** typedef struct ParamPathInfo
*** 878,883 ****
--- 885,912 ----
  	List	   *ppi_clauses;	/* join clauses available from outer rels */
  } ParamPathInfo;
  
+ /*
+  * GroupedPathInfo
+  *
+  * If RelOptInfo points to this structure, grouped paths can be created for
+  * it.
+  *
+  * "target" will be used as pathtarget of grouped paths produced by this
+  * relation. Grouped path is either a result of aggregation of the relation
+  * that owns this structure or, if the owning relation is a join, a join path
+  * whose one side is a grouped path and the other is a plain (i.e. not
+  * grouped) one. (Two grouped paths cannot be joined in general because
+  * grouping of one side of the join essentially reduces occurrence of groups
+  * of the other side in the input of the final aggregation.)
+  */
+ typedef struct GroupedPathInfo
+ {
+ 	NodeTag		type;
+ 
+ 	PathTarget	*target;		/* output of grouped paths. */
+ 	List	*pathlist;			/* List of grouped paths. */
+ 	List	*partial_pathlist;	/* List of partial grouped paths. */
+ } GroupedPathInfo;
  
  /*
   * Type "Path" is used as-is for sequential-scan paths, as well as some other
*************** typedef struct PlaceHolderVar
*** 1806,1811 ****
--- 1835,1873 ----
  	Index		phlevelsup;		/* > 0 if PHV belongs to outer query */
  } PlaceHolderVar;
  
+ 
+ /*
+  * Similar to the concept of PlaceHolderVar, we treat aggregates and grouping
+  * columns as special variables if grouping is possible below the top-level
+  * join. The reason is that aggregates having start as the argument can be
+  * evaluated at various places in the join tree (i.e. cannot be assigned to
+  * target list of exactly one relation). Also this concept seems to be less
+  * invasive than adding the grouped vars to reltarget (in which case
+  * attr_needed and attr_widths arrays of RelOptInfo) would also need
+  * additional changes.
+  *
+  * gvexpr is a pointer to gvexpr field of the corresponding instance
+  * GroupedVarInfo. It's there for the sake of exprType(), exprCollation(),
+  * etc.
+  *
+  * agg_partial also points to the corresponding field of GroupedVarInfo if the
+  * GroupedVar is in the target of a parent relation (RELOPT_BASEREL). However
+  * within a child relation's (RELOPT_OTHER_MEMBER_REL) target it points to a
+  * copy which has argument expressions translated, so they no longer reference
+  * the parent.
+  *
+  * XXX Currently we only create GroupedVar for aggregates, but sometime we can
+  * do it for grouping keys as well. That would allow grouping below the
+  * top-level join by keys other than plain Var.
+  */
+ typedef struct GroupedVar
+ {
+ 	Expr		xpr;
+ 	Expr		*gvexpr;		/* the represented expression */
+ 	Aggref		*agg_partial;	/* partial aggregate if gvexpr is aggregate */
+ 	Index		gvid;		/* GroupedVarInfo */
+ } GroupedVar;
+ 
  /*
   * "Special join" info.
   *
*************** typedef struct PlaceHolderInfo
*** 2021,2026 ****
--- 2083,2104 ----
  } PlaceHolderInfo;
  
  /*
+  * Likewise, GroupedVarInfo exists for each distinct GroupedVar.
+  */
+ typedef struct GroupedVarInfo
+ {
+ 	NodeTag		type;
+ 
+ 	Index		gvid;			/* GroupedVar.gvid */
+ 	Expr		*gvexpr;		/* the represented expression. */
+ 	Aggref		*agg_partial;	/* if gvexpr is aggregate, agg_partial is
+ 								 * the corresponding partial aggregate */
+ 	Relids		gv_eval_at;		/* lowest level we can evaluate the expression
+ 								 * at or NULL if it can happen anywhere. */
+ 	int32		gv_width;		/* estimated width of the expression */
+ } GroupedVarInfo;
+ 
+ /*
   * This struct describes one potentially index-optimizable MIN/MAX aggregate
   * function.  MinMaxAggPath contains a list of these, and if we accept that
   * path, the list is stored into root->minmax_aggs for use during setrefs.c.
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
new file mode 100644
index 82d4e87..91f0a57
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern int compare_path_costs(Path *path
*** 25,37 ****
  extern int compare_fractional_path_costs(Path *path1, Path *path2,
  							  double fraction);
  extern void set_cheapest(RelOptInfo *parent_rel);
! extern void add_path(RelOptInfo *parent_rel, Path *new_path);
  extern bool add_path_precheck(RelOptInfo *parent_rel,
  				  Cost startup_cost, Cost total_cost,
! 				  List *pathkeys, Relids required_outer);
! extern void add_partial_path(RelOptInfo *parent_rel, Path *new_path);
  extern bool add_partial_path_precheck(RelOptInfo *parent_rel,
! 						  Cost total_cost, List *pathkeys);
  
  extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel,
  					Relids required_outer, int parallel_workers);
--- 25,39 ----
  extern int compare_fractional_path_costs(Path *path1, Path *path2,
  							  double fraction);
  extern void set_cheapest(RelOptInfo *parent_rel);
! extern void add_path(RelOptInfo *parent_rel, Path *new_path, bool grouped);
  extern bool add_path_precheck(RelOptInfo *parent_rel,
  				  Cost startup_cost, Cost total_cost,
! 							  List *pathkeys, Relids required_outer, bool grouped);
! extern void add_partial_path(RelOptInfo *parent_rel, Path *new_path,
! 							 bool grouped);
  extern bool add_partial_path_precheck(RelOptInfo *parent_rel,
! 									  Cost total_cost, List *pathkeys,
! 									  bool grouped);
  
  extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel,
  					Relids required_outer, int parallel_workers);
*************** extern NestPath *create_nestloop_path(Pl
*** 125,131 ****
  					 Path *inner_path,
  					 List *restrict_clauses,
  					 List *pathkeys,
! 					 Relids required_outer);
  
  extern MergePath *create_mergejoin_path(PlannerInfo *root,
  					  RelOptInfo *joinrel,
--- 127,134 ----
  					 Path *inner_path,
  					 List *restrict_clauses,
  					 List *pathkeys,
! 					 Relids required_outer,
! 					 PathTarget *target);
  
  extern MergePath *create_mergejoin_path(PlannerInfo *root,
  					  RelOptInfo *joinrel,
*************** extern MergePath *create_mergejoin_path(
*** 139,145 ****
  					  Relids required_outer,
  					  List *mergeclauses,
  					  List *outersortkeys,
! 					  List *innersortkeys);
  
  extern HashPath *create_hashjoin_path(PlannerInfo *root,
  					 RelOptInfo *joinrel,
--- 142,149 ----
  					  Relids required_outer,
  					  List *mergeclauses,
  					  List *outersortkeys,
! 					  List *innersortkeys,
! 					  PathTarget *target);
  
  extern HashPath *create_hashjoin_path(PlannerInfo *root,
  					 RelOptInfo *joinrel,
*************** extern HashPath *create_hashjoin_path(Pl
*** 151,157 ****
  					 Path *inner_path,
  					 List *restrict_clauses,
  					 Relids required_outer,
! 					 List *hashclauses);
  
  extern ProjectionPath *create_projection_path(PlannerInfo *root,
  					   RelOptInfo *rel,
--- 155,162 ----
  					 Path *inner_path,
  					 List *restrict_clauses,
  					 Relids required_outer,
! 					 List *hashclauses,
! 					 PathTarget *target);
  
  extern ProjectionPath *create_projection_path(PlannerInfo *root,
  					   RelOptInfo *rel,
*************** extern AggPath *create_agg_path(PlannerI
*** 192,197 ****
--- 197,216 ----
  				List *qual,
  				const AggClauseCosts *aggcosts,
  				double numGroups);
+ extern AggPath *create_partial_agg_sorted_path(PlannerInfo *root,
+ 											   Path *subpath,
+ 											   bool first_call,
+ 											   List **group_clauses,
+ 											   List **group_exprs,
+ 											   List **agg_exprs,
+ 											   double input_rows);
+ extern AggPath *create_partial_agg_hashed_path(PlannerInfo *root,
+ 											   Path *subpath,
+ 											   bool first_call,
+ 											   List **group_clauses,
+ 											   List **group_exprs,
+ 											   List **agg_exprs,
+ 											   double input_rows);
  extern GroupingSetsPath *create_groupingsets_path(PlannerInfo *root,
  						 RelOptInfo *rel,
  						 Path *subpath,
*************** extern ParamPathInfo *get_joinrel_paramp
*** 288,292 ****
--- 307,312 ----
  						  List **restrict_clauses);
  extern ParamPathInfo *get_appendrel_parampathinfo(RelOptInfo *appendrel,
  							Relids required_outer);
+ extern void prepare_rel_for_grouping(PlannerInfo *root, RelOptInfo *rel);
  
  #endif   /* PATHNODE_H */
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
new file mode 100644
index 25fe78c..38967da
*** a/src/include/optimizer/paths.h
--- b/src/include/optimizer/paths.h
*************** extern void set_dummy_rel_pathlist(RelOp
*** 53,59 ****
  extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,
  					 List *initial_rels);
  
! extern void generate_gather_paths(PlannerInfo *root, RelOptInfo *rel);
  extern int compute_parallel_worker(RelOptInfo *rel, double heap_pages,
  						double index_pages);
  extern void create_partial_bitmap_paths(PlannerInfo *root, RelOptInfo *rel,
--- 53,64 ----
  extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,
  					 List *initial_rels);
  
! extern void generate_gather_paths(PlannerInfo *root, RelOptInfo *rel,
! 								  bool grouped);
! 
! extern void create_grouped_path(PlannerInfo *root, RelOptInfo *rel,
! 								Path *subpath, bool precheck, bool partial,
! 								AggStrategy aggstrategy);
  extern int compute_parallel_worker(RelOptInfo *rel, double heap_pages,
  						double index_pages);
  extern void create_partial_bitmap_paths(PlannerInfo *root, RelOptInfo *rel,
*************** extern void debug_print_rel(PlannerInfo
*** 67,73 ****
   * indxpath.c
   *	  routines to generate index paths
   */
! extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel);
  extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
  							  List *restrictlist,
  							  List *exprlist, List *oprlist);
--- 72,79 ----
   * indxpath.c
   *	  routines to generate index paths
   */
! extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel,
! 							   bool grouped);
  extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
  							  List *restrictlist,
  							  List *exprlist, List *oprlist);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
new file mode 100644
index 94ef84b..159ccff
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
*************** extern int	join_collapse_limit;
*** 74,80 ****
  extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
  extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
  extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
! 					   Relids where_needed, bool create_new_ph);
  extern void find_lateral_references(PlannerInfo *root);
  extern void create_lateral_join_info(PlannerInfo *root);
  extern List *deconstruct_jointree(PlannerInfo *root);
--- 74,82 ----
  extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
  extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
  extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
! 								   Relids where_needed, bool create_new_ph);
! extern void add_grouping_info_to_base_rels(PlannerInfo *root);
! extern void add_grouped_vars_to_rels(PlannerInfo *root);
  extern void find_lateral_references(PlannerInfo *root);
  extern void create_lateral_join_info(PlannerInfo *root);
  extern List *deconstruct_jointree(PlannerInfo *root);
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
new file mode 100644
index ccb93d8..ddea03c
*** a/src/include/optimizer/tlist.h
--- b/src/include/optimizer/tlist.h
*************** extern Node *get_sortgroupclause_expr(So
*** 41,46 ****
--- 41,49 ----
  						 List *targetList);
  extern List *get_sortgrouplist_exprs(List *sgClauses,
  						List *targetList);
+ extern void get_grouping_expressions(PlannerInfo *root, PathTarget *target,
+ 									 List **grouping_clauses,
+ 									 List **grouping_exprs, List **agg_exprs);
  
  extern SortGroupClause *get_sortgroupref_clause(Index sortref,
  						List *clauses);
*************** extern void split_pathtarget_at_srfs(Pla
*** 65,70 ****
--- 68,84 ----
  						 PathTarget *target, PathTarget *input_target,
  						 List **targets, List **targets_contain_srfs);
  
+ /* TODO Find the best location (position and in some cases even file) for the
+  * following ones. */
+ extern List *restore_grouping_expressions(PlannerInfo *root, List *src);
+ extern List *add_aggregates_to_target(PlannerInfo *root, PathTarget *target,
+ 									  List *aggregates, RelOptInfo *rel);
+ extern Index get_expr_sortgroupref(PlannerInfo *root, Expr *expr);
+ /* TODO Move definition from initsplan.c to tlist.c. */
+ extern PathTarget *create_grouped_target(PlannerInfo *root, RelOptInfo *rel,
+ 										 Relids rel_agg_attrs,
+ 										 List *rel_agg_vars);
+ 
  /* Convenience macro to get a PathTarget with valid cost/width fields */
  #define create_pathtarget(root, tlist) \
  	set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
new file mode 100644
index 9f9d2dc..e05e6f6
*** a/src/include/utils/selfuncs.h
--- b/src/include/utils/selfuncs.h
*************** extern double estimate_num_groups(Planne
*** 206,211 ****
--- 206,214 ----
  
  extern Selectivity estimate_hash_bucketsize(PlannerInfo *root, Node *hashkey,
  						 double nbuckets);
+ extern Size estimate_hashagg_tablesize(Path *path,
+ 									   const AggClauseCosts *agg_costs,
+ 									   double dNumGroups);
  
  extern List *deconstruct_indexquals(IndexPath *path);
  extern void genericcostestimate(PlannerInfo *root, IndexPath *path,
