agora inbox for [email protected]  
help / color / mirror / Atom feed
From: Antonin Houska <[email protected]>
To: [email protected]
Subject: Re: [HACKERS] WIP: Aggregation push-down
Date: Fri, 22 Dec 2017 16:43:57 +0100
Message-ID: <18007.1513957437@localhost> (raw)
In-Reply-To: <CAB7nPqQgB=3ZpuvSm8hVXDcPz79kn2ZGsyyoDrmQsv-B3LDMyQ@mail.gmail.com>
References: <9666.1491295317@localhost>
	<8160.1493369169@localhost>
	<29613.1502983342@localhost>
	<14577.1509723225@localhost>
	<CAB7nPqQgB=3ZpuvSm8hVXDcPz79kn2ZGsyyoDrmQsv-B3LDMyQ@mail.gmail.com>

Michael Paquier <[email protected]> wrote:

> On Sat, Nov 4, 2017 at 12:33 AM, Antonin Houska <[email protected]> wrote:
> > I'm not about to add any other features now. Implementation of the missing
> > parts (see the TODO comments in the code) is the next step. But what I'd
> > appreciate most is a feedback on the design. Thanks.
> 
> I am getting a conflict after applying patch 5 but this did not get
> any reviews so moved to next CF with waiting on author as status.

Attached is the next version.

-- 
Antonin Houska
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt
Web: http://www.postgresql-support.de, http://www.cybertec.at



Attachments:

  [application/x-gzip] agg_pushdown_v5.tgz (118.4K, ../18007.1513957437@localhost/2-agg_pushdown_v5.tgz)
  download | 11 patch file(s) in archive:

  agg_pushdown_v5/01_new_types.diff
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index 96d1fc3..85e8ea3
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyAggref(const Aggref *from)
*** 1361,1366 ****
--- 1361,1367 ----
  	COPY_SCALAR_FIELD(aggcollid);
  	COPY_SCALAR_FIELD(inputcollid);
  	COPY_SCALAR_FIELD(aggtranstype);
+ 	COPY_SCALAR_FIELD(aggcombinefn);
  	COPY_NODE_FIELD(aggargtypes);
  	COPY_NODE_FIELD(aggdirectargs);
  	COPY_NODE_FIELD(args);
*************** _copyPlaceHolderVar(const PlaceHolderVar
*** 2213,2218 ****
--- 2214,2235 ----
  }
  
  /*
+  * _copyGroupedVar
+  */
+ static GroupedVar *
+ _copyGroupedVar(const GroupedVar *from)
+ {
+ 	GroupedVar *newnode = makeNode(GroupedVar);
+ 
+ 	COPY_NODE_FIELD(gvexpr);
+ 	COPY_NODE_FIELD(agg_partial);
+ 	COPY_SCALAR_FIELD(sortgroupref);
+ 	COPY_SCALAR_FIELD(gvid);
+ 
+ 	return newnode;
+ }
+ 
+ /*
   * _copySpecialJoinInfo
   */
  static SpecialJoinInfo *
*************** _copyPlaceHolderInfo(const PlaceHolderIn
*** 2285,2290 ****
--- 2302,2322 ----
  	return newnode;
  }
  
+ static GroupedVarInfo *
+ _copyGroupedVarInfo(const GroupedVarInfo *from)
+ {
+ 	GroupedVarInfo *newnode = makeNode(GroupedVarInfo);
+ 
+ 	COPY_SCALAR_FIELD(gvid);
+ 	COPY_NODE_FIELD(gvexpr);
+ 	COPY_NODE_FIELD(agg_partial);
+ 	COPY_SCALAR_FIELD(sortgroupref);
+ 	COPY_SCALAR_FIELD(gv_eval_at);
+ 	COPY_SCALAR_FIELD(gv_width);
+ 
+ 	return newnode;
+ }
+ 
  /* ****************************************************************
   *					parsenodes.h copy functions
   * ****************************************************************
*************** copyObjectImpl(const void *from)
*** 5020,5025 ****
--- 5052,5060 ----
  		case T_PlaceHolderVar:
  			retval = _copyPlaceHolderVar(from);
  			break;
+ 		case T_GroupedVar:
+ 			retval = _copyGroupedVar(from);
+ 			break;
  		case T_SpecialJoinInfo:
  			retval = _copySpecialJoinInfo(from);
  			break;
*************** copyObjectImpl(const void *from)
*** 5032,5037 ****
--- 5067,5075 ----
  		case T_PlaceHolderInfo:
  			retval = _copyPlaceHolderInfo(from);
  			break;
+ 		case T_GroupedVarInfo:
+ 			retval = _copyGroupedVarInfo(from);
+ 			break;
  
  			/*
  			 * VALUE NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index 2e869a9..3d735a2
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalPlaceHolderVar(const PlaceHolderVa
*** 873,878 ****
--- 873,886 ----
  }
  
  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)
*** 3171,3176 ****
--- 3179,3187 ----
  		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 c2a93b2..9a53aa7
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** exprType(const Node *expr)
*** 259,264 ****
--- 259,270 ----
  		case T_PlaceHolderVar:
  			type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
  			break;
+ 		case T_GroupedVar:
+ 			if (IsA(((const GroupedVar *) expr)->gvexpr, Aggref))
+ 				type = exprType((Node *) ((const GroupedVar *) expr)->agg_partial);
+ 			else
+ 				type = exprType((Node *) ((const GroupedVar *) expr)->gvexpr);
+ 			break;
  		default:
  			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
  			type = InvalidOid;	/* keep compiler quiet */
*************** exprTypmod(const Node *expr)
*** 492,497 ****
--- 498,508 ----
  			return ((const SetToDefault *) expr)->typeMod;
  		case T_PlaceHolderVar:
  			return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+ 		case T_GroupedVar:
+ 			if (IsA(((const GroupedVar *) expr)->gvexpr, Aggref))
+ 				return exprTypmod((Node *) ((const GroupedVar *) expr)->agg_partial);
+ 			else
+ 				return exprTypmod((Node *) ((const GroupedVar *) expr)->gvexpr);
  		default:
  			break;
  	}
*************** exprCollation(const Node *expr)
*** 903,908 ****
--- 914,925 ----
  		case T_PlaceHolderVar:
  			coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
  			break;
+ 		case T_GroupedVar:
+ 			if (IsA(((const GroupedVar *) expr)->gvexpr, Aggref))
+ 				coll = exprCollation((Node *) ((const GroupedVar *) expr)->agg_partial);
+ 			else
+ 				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,
*** 2176,2181 ****
--- 2193,2200 ----
  			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,
*** 2968,2973 ****
--- 2987,3002 ----
  				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 e468d7c..d70d210
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outAggref(StringInfo str, const Aggref
*** 1136,1141 ****
--- 1136,1142 ----
  	WRITE_OID_FIELD(aggcollid);
  	WRITE_OID_FIELD(inputcollid);
  	WRITE_OID_FIELD(aggtranstype);
+ 	WRITE_OID_FIELD(aggcombinefn);
  	WRITE_NODE_FIELD(aggargtypes);
  	WRITE_NODE_FIELD(aggdirectargs);
  	WRITE_NODE_FIELD(args);
*************** _outPlannerInfo(StringInfo str, const Pl
*** 2225,2230 ****
--- 2226,2232 ----
  	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);
*************** _outPlannerInfo(StringInfo str, const Pl
*** 2232,2237 ****
--- 2234,2240 ----
  	WRITE_NODE_FIELD(distinct_pathkeys);
  	WRITE_NODE_FIELD(sort_pathkeys);
  	WRITE_NODE_FIELD(processed_tlist);
+ 	WRITE_INT_FIELD(max_sortgroupref);
  	WRITE_NODE_FIELD(minmax_aggs);
  	WRITE_FLOAT_FIELD(total_table_pages, "%.0f");
  	WRITE_FLOAT_FIELD(tuple_fraction, "%.4f");
*************** _outRelOptInfo(StringInfo str, const Rel
*** 2271,2276 ****
--- 2274,2280 ----
  	WRITE_NODE_FIELD(cheapest_parameterized_paths);
  	WRITE_BITMAPSET_FIELD(direct_lateral_relids);
  	WRITE_BITMAPSET_FIELD(lateral_relids);
+ 	WRITE_NODE_FIELD(gpi);
  	WRITE_UINT_FIELD(relid);
  	WRITE_OID_FIELD(reltablespace);
  	WRITE_ENUM_FIELD(rtekind, RTEKind);
*************** _outParamPathInfo(StringInfo str, const
*** 2447,2452 ****
--- 2451,2468 ----
  }
  
  static void
+ _outGroupedPathInfo(StringInfo str, const GroupedPathInfo *node)
+ {
+ 	WRITE_NODE_TYPE("GROUPEDPATHINFO");
+ 
+ 	WRITE_NODE_FIELD(target);
+ 	WRITE_NODE_FIELD(group_exprs);
+ 	WRITE_NODE_FIELD(pathlist);
+ 	WRITE_NODE_FIELD(partial_pathlist);
+ 	WRITE_NODE_FIELD(sortgroupclauses);
+ }
+ 
+ static void
  _outRestrictInfo(StringInfo str, const RestrictInfo *node)
  {
  	WRITE_NODE_TYPE("RESTRICTINFO");
*************** _outPlaceHolderVar(StringInfo str, const
*** 2490,2495 ****
--- 2506,2522 ----
  }
  
  static void
+ _outGroupedVar(StringInfo str, const GroupedVar *node)
+ {
+ 	WRITE_NODE_TYPE("GROUPEDVAR");
+ 
+ 	WRITE_NODE_FIELD(gvexpr);
+ 	WRITE_NODE_FIELD(agg_partial);
+ 	WRITE_UINT_FIELD(sortgroupref);
+ 	WRITE_UINT_FIELD(gvid);
+ }
+ 
+ static void
  _outSpecialJoinInfo(StringInfo str, const SpecialJoinInfo *node)
  {
  	WRITE_NODE_TYPE("SPECIALJOININFO");
*************** _outPlaceHolderInfo(StringInfo str, cons
*** 2543,2548 ****
--- 2570,2588 ----
  }
  
  static void
+ _outGroupedVarInfo(StringInfo str, const GroupedVarInfo *node)
+ {
+ 	WRITE_NODE_TYPE("GROUPEDVARINFO");
+ 
+ 	WRITE_UINT_FIELD(gvid);
+ 	WRITE_NODE_FIELD(gvexpr);
+ 	WRITE_NODE_FIELD(agg_partial);
+ 	WRITE_UINT_FIELD(sortgroupref);
+ 	WRITE_BITMAPSET_FIELD(gv_eval_at);
+ 	WRITE_INT_FIELD(gv_width);
+ }
+ 
+ static void
  _outMinMaxAggInfo(StringInfo str, const MinMaxAggInfo *node)
  {
  	WRITE_NODE_TYPE("MINMAXAGGINFO");
*************** outNode(StringInfo str, const void *obj)
*** 4045,4056 ****
--- 4085,4102 ----
  			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;
*************** outNode(StringInfo str, const void *obj)
*** 4063,4068 ****
--- 4109,4117 ----
  			case T_PlaceHolderInfo:
  				_outPlaceHolderInfo(str, obj);
  				break;
+ 			case T_GroupedVarInfo:
+ 				_outGroupedVarInfo(str, obj);
+ 				break;
  			case T_MinMaxAggInfo:
  				_outMinMaxAggInfo(str, obj);
  				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
new file mode 100644
index 1133c70..f02a963
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readVar(void)
*** 529,534 ****
--- 529,550 ----
  }
  
  /*
+  * _readGroupedVar
+  */
+ static GroupedVar *
+ _readGroupedVar(void)
+ {
+ 	READ_LOCALS(GroupedVar);
+ 
+ 	READ_NODE_FIELD(gvexpr);
+ 	READ_NODE_FIELD(agg_partial);
+ 	READ_UINT_FIELD(sortgroupref);
+ 	READ_UINT_FIELD(gvid);
+ 
+ 	READ_DONE();
+ }
+ 
+ /*
   * _readConst
   */
  static Const *
*************** _readAggref(void)
*** 584,589 ****
--- 600,606 ----
  	READ_OID_FIELD(aggcollid);
  	READ_OID_FIELD(inputcollid);
  	READ_OID_FIELD(aggtranstype);
+ 	READ_OID_FIELD(aggcombinefn);
  	READ_NODE_FIELD(aggargtypes);
  	READ_NODE_FIELD(aggdirectargs);
  	READ_NODE_FIELD(args);
*************** parseNodeString(void)
*** 2472,2477 ****
--- 2489,2496 ----
  		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/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
new file mode 100644
index f4e0a6e..2e6a60e
*** a/src/backend/optimizer/plan/planmain.c
--- b/src/backend/optimizer/plan/planmain.c
*************** 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;
  
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 382791f..5a3efbd
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** subquery_planner(PlannerGlobal *glob, Qu
*** 544,549 ****
--- 544,550 ----
  	memset(root->upper_rels, 0, sizeof(root->upper_rels));
  	memset(root->upper_targets, 0, sizeof(root->upper_targets));
  	root->processed_tlist = NIL;
+ 	root->max_sortgroupref = 0;
  	root->grouping_map = NULL;
  	root->minmax_aggs = NIL;
  	root->qual_security_level = 0;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
new file mode 100644
index 1d7e499..710f028
*** a/src/backend/optimizer/prep/prepjointree.c
--- b/src/backend/optimizer/prep/prepjointree.c
*************** pull_up_simple_subquery(PlannerInfo *roo
*** 911,916 ****
--- 911,917 ----
  	memset(subroot->upper_rels, 0, sizeof(subroot->upper_rels));
  	memset(subroot->upper_targets, 0, sizeof(subroot->upper_targets));
  	subroot->processed_tlist = NIL;
+ 	subroot->max_sortgroupref = 0;
  	subroot->grouping_map = NULL;
  	subroot->minmax_aggs = NIL;
  	subroot->qual_security_level = 0;
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
new file mode 100644
index e6b0856..7a98753
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 98,103 ****
--- 98,104 ----
  	Oid			vatype;
  	FuncDetailCode fdresult;
  	char		aggkind = 0;
+ 	Oid			aggcombinefn = InvalidOid;
  	ParseCallbackState pcbstate;
  
  	/*
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 350,355 ****
--- 351,357 ----
  			elog(ERROR, "cache lookup failed for aggregate %u", funcid);
  		classForm = (Form_pg_aggregate) GETSTRUCT(tup);
  		aggkind = classForm->aggkind;
+ 		aggcombinefn = classForm->aggcombinefn;
  		catDirectArgs = classForm->aggnumdirectargs;
  		ReleaseSysCache(tup);
  
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 695,700 ****
--- 697,703 ----
  		aggref->aggstar = agg_star;
  		aggref->aggvariadic = func_variadic;
  		aggref->aggkind = aggkind;
+ 		aggref->aggcombinefn = aggcombinefn;
  		/* agglevelsup will be set by transformAggregateCall */
  		aggref->aggsplit = AGGSPLIT_SIMPLE; /* planner might change this */
  		aggref->location = location;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
new file mode 100644
index c5b5115..86693d1
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 218,223 ****
--- 218,224 ----
  	T_IndexOptInfo,
  	T_ForeignKeyOptInfo,
  	T_ParamPathInfo,
+ 	T_GroupedPathInfo,
  	T_Path,
  	T_IndexPath,
  	T_BitmapHeapPath,
*************** typedef enum NodeTag
*** 258,267 ****
--- 259,270 ----
  	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/primnodes.h b/src/include/nodes/primnodes.h
new file mode 100644
index 074ae0a..97fd887
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
*************** typedef struct Aggref
*** 296,301 ****
--- 296,302 ----
  	Oid			aggcollid;		/* OID of collation of result */
  	Oid			inputcollid;	/* OID of collation that function should use */
  	Oid			aggtranstype;	/* type Oid of aggregate's transition value */
+ 	Oid			aggcombinefn;	/* combine function (see pg_aggregate.h) */
  	List	   *aggargtypes;	/* type Oids of direct and aggregated args */
  	List	   *aggdirectargs;	/* direct arguments, if an ordered-set agg */
  	List	   *args;			/* aggregated arguments and sort expressions */
*************** typedef struct Aggref
*** 306,311 ****
--- 307,313 ----
  	bool		aggvariadic;	/* true if variadic arguments have been
  								 * combined into an array last argument */
  	char		aggkind;		/* aggregate kind (see pg_aggregate.h) */
+ 
  	Index		agglevelsup;	/* > 0 if agg belongs to outer query */
  	AggSplit	aggsplit;		/* expected agg-splitting mode of parent Agg */
  	int			location;		/* token location, or -1 if unknown */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
new file mode 100644
index 3b9d303..1c93688
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct PlannerInfo
*** 257,262 ****
--- 257,264 ----
  
  	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
*** 283,288 ****
--- 285,296 ----
  	 */
  	List	   *processed_tlist;
  
+ 	/*
+ 	 * The maximum ressortgroupref among target entries in processed_list.
+ 	 * Useful when adding extra grouping expressions for partial aggregation.
+ 	 */
+ 	int			max_sortgroupref;
+ 
  	/* Fields filled during create_plan() for use in setrefs.c */
  	AttrNumber *grouping_map;	/* for GroupingFunc fixup */
  	List	   *minmax_aggs;	/* List of MinMaxAggInfos */
*************** typedef struct PartitionSchemeData *Part
*** 438,443 ****
--- 446,453 ----
   *		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
*** 609,614 ****
--- 619,627 ----
  	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
*** 1003,1008 ****
--- 1016,1065 ----
  	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 either by
+  * "explicit aggregation" of the relation that owns this structure, or --- if
+  * the relation is a join --- by joining grouped path to a non-grouped
+  * one.
+  *
+  * The target contains plain-Var grouping expressions, generic grouping
+  * expressions wrapped in GroupedVar structure, or Aggrefs which are also
+  * wrapped in GroupedVar. Once GroupedVar is evaluated, its value is passed to
+  * the upper paths w/o being evaluated again. If final aggregation appears to
+  * be necessary above the final join, the contained Aggrefs are supposed to
+  * provide the final aggregation plan with input values, i.e. the aggregate
+  * transient state.
+  *
+  * Note: There's a convention that GroupedVars that contain Aggref expressions
+  * are supposed to follow the other expressions of the target. Iterations of
+  * target->exprs may rely on this arrangement.
+  *
+  * "group_exprs" contains grouping expressions which are not plain vars. These
+  * are only added to path target of a path which should generate input for
+  * partial aggregation.
+  *
+  * "sortgroupclauses" is a list of grouping clauses that the relation does
+  * have in its targetlist but the query does not.
+  *
+  * (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;			/* target of grouped paths aggregation. */
+ 	PathTarget *group_exprs;	/* non-Var grouping expressions. */
+ 	List	   *pathlist;		/* List of grouped paths. */
+ 	List	   *partial_pathlist;	/* List of partial grouped paths. */
+ 	List	   *sortgroupclauses;	/* Relation-specific grouping clauses. */
+ } GroupedPathInfo;
  
  /*
   * Type "Path" is used as-is for sequential-scan paths, as well as some other
*************** typedef struct PlaceHolderVar
*** 1950,1955 ****
--- 2007,2047 ----
  	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		sortgroupref;	/* SortGroupClause.tleSortGroupRef if gvexpr
+ 								 * is grouping expression. */
+ 	Index		gvid;			/* GroupedVarInfo */
+ } GroupedVar;
+ 
  /*
   * "Special join" info.
   *
*************** typedef struct PlaceHolderInfo
*** 2165,2170 ****
--- 2257,2281 ----
  } 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 */
+ 	Index		sortgroupref;	/* If gvexpr is a grouping expression, this is
+ 								 * the tleSortGroupRef of the corresponding
+ 								 * SortGroupClause. */
+ 	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.


  agg_pushdown_v5/02_preprocess.diff
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
new file mode 100644
index 45a6889..bd22449
*** a/src/backend/optimizer/path/equivclass.c
--- b/src/backend/optimizer/path/equivclass.c
*************** static bool reconsider_outer_join_clause
*** 65,70 ****
--- 65,83 ----
  static bool reconsider_full_join_clause(PlannerInfo *root,
  							RestrictInfo *rinfo);
  
+ typedef struct translate_expr_context
+ {
+ 	Var		  **keys;			/* Dictionary keys. */
+ 	Var		  **values;			/* Dictionary values */
+ 	int			nitems;			/* Number of dictionary items. */
+ 	Relids	   *gv_eval_at_p;	/* See GroupedVarInfo. */
+ 	Relids		relids;			/* Translate into these relids. */
+ } translate_expr_context;
+ 
+ static Node *translate_expression_to_rels_mutator(Node *node,
+ 									 translate_expr_context *context);
+ static int	var_dictionary_comparator(const void *a, const void *b);
+ 
  
  /*
   * process_equivalence
*************** is_redundant_derived_clause(RestrictInfo
*** 2510,2512 ****
--- 2523,2844 ----
  
  	return false;
  }
+ 
+ /*
+  * translate_expression_to_rels
+  *
+  *		If the appropriate equivalence classes exist, replace vars in
+  *		gvi->gvexpr with vars whose varno is contained in relids.
+  */
+ GroupedVarInfo *
+ translate_expression_to_rels(PlannerInfo *root, GroupedVarInfo *gvi,
+ 							 Relids relids)
+ {
+ 	List	   *vars;
+ 	ListCell   *l1;
+ 	int			i,
+ 				j;
+ 	int			nkeys,
+ 				nkeys_resolved;
+ 	Var		  **keys,
+ 			  **values,
+ 			  **keys_tmp;
+ 	Var		   *key,
+ 			   *key_prev;
+ 	translate_expr_context context;
+ 	GroupedVarInfo *result;
+ 
+ 	/* Can't do anything w/o equivalence classes. */
+ 	if (root->eq_classes == NIL)
+ 		return NULL;
+ 
+ 	/*
+ 	 * Before actually trying to modify the expression tree, find out if all
+ 	 * vars can be translated.
+ 	 */
+ 	vars = pull_var_clause((Node *) gvi->gvexpr, PVC_RECURSE_AGGREGATES);
+ 
+ 	/* No vars to translate? */
+ 	if (vars == NIL)
+ 		return NULL;
+ 
+ 	/*
+ 	 * Search for individual replacement vars as well as the actual expression
+ 	 * translation will be more efficient if we use a dictionary with the keys
+ 	 * (i.e. the "source vars") unique and sorted.
+ 	 */
+ 	nkeys = list_length(vars);
+ 	keys = (Var **) palloc(nkeys * sizeof(Var *));
+ 	i = 0;
+ 	foreach(l1, vars)
+ 	{
+ 		key = lfirst_node(Var, l1);
+ 		keys[i++] = key;
+ 	}
+ 
+ 	/*
+ 	 * Sort the keys by varno. varattno decides where varnos are equal.
+ 	 */
+ 	pg_qsort(keys, nkeys, sizeof(Var *), var_dictionary_comparator);
+ 
+ 	/*
+ 	 * Pick unique values and get rid of the vars that need no translation.
+ 	 */
+ 	keys_tmp = (Var **) palloc(nkeys * sizeof(Var *));
+ 	key_prev = NULL;
+ 	j = 0;
+ 	for (i = 0; i < nkeys; i++)
+ 	{
+ 		key = keys[i];
+ 
+ 		if ((key_prev == NULL || (key->varno != key_prev->varno &&
+ 								  key->varattno != key_prev->varattno)) &&
+ 			!bms_is_member(key->varno, relids))
+ 			keys_tmp[j++] = key;
+ 
+ 		key_prev = key;
+ 	}
+ 	pfree(keys);
+ 	keys = keys_tmp;
+ 	nkeys = j;
+ 
+ 	/*
+ 	 * Is there actually nothing to be translated?
+ 	 */
+ 	if (nkeys == 0)
+ 	{
+ 		pfree(keys);
+ 		return NULL;
+ 	}
+ 
+ 	nkeys_resolved = 0;
+ 
+ 	/*
+ 	 * Find the replacement vars.
+ 	 */
+ 	values = (Var **) palloc0(nkeys * sizeof(Var *));
+ 	foreach(l1, root->eq_classes)
+ 	{
+ 		EquivalenceClass *ec = lfirst_node(EquivalenceClass, l1);
+ 		Relids		ec_var_relids;
+ 		Var		  **ec_vars;
+ 		int			ec_nvars;
+ 		ListCell   *l2;
+ 
+ 		/* TODO Re-check if any other EC kind should be ignored. */
+ 		if (ec->ec_has_volatile || ec->ec_below_outer_join || ec->ec_broken)
+ 			continue;
+ 
+ 		/* Single-element EC can hardly help in translations. */
+ 		if (list_length(ec->ec_members) == 1)
+ 			continue;
+ 
+ 		/*
+ 		 * Collect all vars of this EC and their varnos.
+ 		 *
+ 		 * ec->ec_relids does not help because we're only interested in a
+ 		 * subset of EC members.
+ 		 */
+ 		ec_vars = (Var **) palloc(list_length(ec->ec_members) * sizeof(Var *));
+ 		ec_nvars = 0;
+ 		ec_var_relids = NULL;
+ 		foreach(l2, ec->ec_members)
+ 		{
+ 			EquivalenceMember *em = lfirst_node(EquivalenceMember, l2);
+ 			Var		   *ec_var;
+ 
+ 			if (!IsA(em->em_expr, Var))
+ 				continue;
+ 
+ 			ec_var = castNode(Var, em->em_expr);
+ 			ec_vars[ec_nvars++] = ec_var;
+ 			ec_var_relids = bms_add_member(ec_var_relids, ec_var->varno);
+ 		}
+ 
+ 		/*
+ 		 * At least two vars are needed so that the EC is usable for
+ 		 * translation.
+ 		 */
+ 		if (ec_nvars <= 1)
+ 		{
+ 			pfree(ec_vars);
+ 			bms_free(ec_var_relids);
+ 			continue;
+ 		}
+ 
+ 		/*
+ 		 * Now check where this EC can help.
+ 		 */
+ 		for (i = 0; i < nkeys; i++)
+ 		{
+ 			Relids		ec_rest;
+ 			bool		relids_ok,
+ 						key_found;
+ 			Var		   *key = keys[i];
+ 			Var		   *value = values[i];
+ 
+ 			/* Skip this item if it's already resolved. */
+ 			if (value != NULL)
+ 				continue;
+ 
+ 			/*
+ 			 * Can't translate if the EC does not mention key->varno.
+ 			 */
+ 			if (!bms_is_member(key->varno, ec_var_relids))
+ 				continue;
+ 
+ 			/*
+ 			 * Besides key, at least one EC member must belong to the relation
+ 			 * we're translating our expression to.
+ 			 */
+ 			ec_rest = bms_copy(ec_var_relids);
+ 			ec_rest = bms_del_member(ec_rest, key->varno);
+ 			relids_ok = bms_overlap(ec_rest, relids);
+ 			bms_free(ec_rest);
+ 			if (!relids_ok)
+ 				continue;
+ 
+ 			/*
+ 			 * The preliminary checks passed, so try to find the exact vars.
+ 			 */
+ 			key_found = false;
+ 			for (j = 0; j < ec_nvars; j++)
+ 			{
+ 				Var		   *ec_var = ec_vars[j];
+ 
+ 				if (!key_found && key->varno == ec_var->varno &&
+ 					key->varattno == ec_var->varattno)
+ 					key_found = true;
+ 
+ 				/*
+ 				 * If relids contains multiple members, it shouldn't matter to
+ 				 * which one we translate our key. Simply use the first one.
+ 				 *
+ 				 * XXX Shouldn't ec_var be copied?
+ 				 */
+ 				if (value == NULL && bms_is_member(ec_var->varno, relids))
+ 					value = ec_var;
+ 
+ 				if (key_found && value != NULL)
+ 					break;
+ 			}
+ 
+ 			if (key_found && value != NULL)
+ 			{
+ 				values[i] = value;
+ 				nkeys_resolved++;
+ 
+ 				if (nkeys_resolved == nkeys)
+ 					break;
+ 			}
+ 		}
+ 
+ 		pfree(ec_vars);
+ 		bms_free(ec_var_relids);
+ 
+ 		/* Don't need to check the remaining ECs? */
+ 		if (nkeys_resolved == nkeys)
+ 			break;
+ 	}
+ 
+ 	/* Couldn't compose usable dictionary? */
+ 	if (nkeys_resolved < nkeys)
+ 	{
+ 		pfree(keys);
+ 		pfree(values);
+ 		return NULL;
+ 	}
+ 
+ 	result = makeNode(GroupedVarInfo);
+ 	memcpy(result, gvi, sizeof(GroupedVarInfo));
+ 
+ 	/*
+ 	 * translate_expression_to_rels_mutator updates gv_eval_at.
+ 	 */
+ 	result->gv_eval_at = bms_copy(result->gv_eval_at);
+ 
+ 	/* The dictionary is ready, so perform the translation. */
+ 	context.keys = keys;
+ 	context.values = values;
+ 	context.nitems = nkeys;
+ 	context.gv_eval_at_p = &result->gv_eval_at;
+ 	context.relids = relids;
+ 	result->gvexpr = (Expr *)
+ 		translate_expression_to_rels_mutator((Node *) gvi->gvexpr, &context);
+ 
+ 	pfree(keys);
+ 	pfree(values);
+ 	return result;
+ }
+ 
+ static Node *
+ translate_expression_to_rels_mutator(Node *node,
+ 									 translate_expr_context *context)
+ {
+ 	if (node == NULL)
+ 		return NULL;
+ 
+ 	if (IsA(node, Var))
+ 	{
+ 		Var		   *var = castNode(Var, node);
+ 		Var		  **key_p;
+ 		Var		   *value;
+ 		int			index;
+ 
+ 		/*
+ 		 * Simply return the existing variable if already belongs to the
+ 		 * relation we're adjusting the expression to.
+ 		 */
+ 		if (bms_is_member(var->varno, context->relids))
+ 			return (Node *) var;
+ 
+ 		key_p = bsearch(&var, context->keys, context->nitems, sizeof(Var *),
+ 						var_dictionary_comparator);
+ 
+ 		/* We shouldn't have omitted any var from the dictionary. */
+ 		Assert(key_p != NULL);
+ 
+ 		index = key_p - context->keys;
+ 		Assert(index >= 0 && index < context->nitems);
+ 		value = context->values[index];
+ 
+ 		/* All values should be present in the dictionary. */
+ 		Assert(value != NULL);
+ 
+ 		/* Update gv_eval_at accordingly. */
+ 		bms_del_member(*context->gv_eval_at_p, var->varno);
+ 		*context->gv_eval_at_p = bms_add_member(*context->gv_eval_at_p,
+ 												value->varno);
+ 
+ 		return (Node *) value;
+ 	}
+ 
+ 	return expression_tree_mutator(node, translate_expression_to_rels_mutator,
+ 								   (void *) context);
+ }
+ 
+ static int
+ var_dictionary_comparator(const void *a, const void *b)
+ {
+ 	Var		  **var1_p,
+ 			  **var2_p;
+ 	Var		   *var1,
+ 			   *var2;
+ 
+ 	var1_p = (Var **) a;
+ 	var1 = castNode(Var, *var1_p);
+ 	var2_p = (Var **) b;
+ 	var2 = castNode(Var, *var2_p);
+ 
+ 	if (var1->varno < var2->varno)
+ 		return -1;
+ 	else if (var1->varno > var2->varno)
+ 		return 1;
+ 
+ 	if (var1->varattno < var2->varattno)
+ 		return -1;
+ 	else if (var1->varattno > var2->varattno)
+ 		return 1;
+ 
+ 	return 0;
+ }
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
new file mode 100644
index 448cb73..8de59fc
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
***************
*** 14,21 ****
--- 14,23 ----
   */
  #include "postgres.h"
  
+ #include "access/sysattr.h"
  #include "catalog/pg_type.h"
  #include "catalog/pg_class.h"
+ #include "catalog/pg_constraint_fn.h"
  #include "nodes/nodeFuncs.h"
  #include "optimizer/clauses.h"
  #include "optimizer/cost.h"
***************
*** 27,32 ****
--- 29,35 ----
  #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
*** 46,51 ****
--- 49,56 ----
  } PostponedQual;
  
  
+ static void create_aggregate_grouped_var_infos(PlannerInfo *root);
+ static void create_grouping_expr_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
*** 241,246 ****
--- 246,839 ----
  	}
  }
  
+ /*
+  * Add GroupedVarInfo to grouped_var_list for each aggregate as well as for
+  * each possible grouping expression and setup GroupedPathInfo for each base
+  * relation that can product grouped paths.
+  *
+  * root->group_pathkeys must be setup before this function is called.
+  */
+ extern void
+ add_grouping_info_to_base_rels(PlannerInfo *root)
+ {
+ 	int			i;
+ 	ListCell   *lc;
+ 
+ 	/* No grouping in the query? */
+ 	if (!root->parse->groupClause)
+ 		return;
+ 
+ 	/*
+ 	 * Grouping sets require multiple different groupings but the base
+ 	 * relation can only generate one.
+ 	 */
+ 	if (root->parse->groupingSets)
+ 		return;
+ 
+ 	/*
+ 	 * TODO Consider if this is a real limitation.
+ 	 */
+ 	if (root->parse->hasWindowFuncs)
+ 		return;
+ 
+ 	/* Create GroupedVarInfo per (distinct) aggregate. */
+ 	create_aggregate_grouped_var_infos(root);
+ 
+ 	/* Is no grouping is possible below the top-level join? */
+ 	if (root->grouped_var_list == NIL)
+ 		return;
+ 
+ 	/* Create GroupedVarInfo per grouping expression. */
+ 	create_grouping_expr_grouped_var_infos(root);
+ 
+ 	/* 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);
+ 	}
+ 
+ 	/*
+ 	 * Now that we know that grouping can be pushed down, search for the
+ 	 * maximum sortgroupref. The base relations may need it if extra grouping
+ 	 * expressions get added to them.
+ 	 */
+ 	Assert(root->max_sortgroupref == 0);
+ 	foreach(lc, root->processed_tlist)
+ 	{
+ 		TargetEntry *te = lfirst_node(TargetEntry, lc);
+ 
+ 		if (te->ressortgroupref > root->max_sortgroupref)
+ 			root->max_sortgroupref = te->ressortgroupref;
+ 	}
+ }
+ 
+ /*
+  * Create GroupedVarInfo for each distinct aggregate.
+  *
+  * If any aggregate is not suitable, set root->grouped_var_list to NIL and
+  * return.
+  */
+ static void
+ create_aggregate_grouped_var_infos(PlannerInfo *root)
+ {
+ 	List	   *tlist_exprs;
+ 	ListCell   *lc;
+ 
+ 	Assert(root->grouped_var_list == NIL);
+ 
+ 	tlist_exprs = pull_var_clause((Node *) root->processed_tlist,
+ 								  PVC_INCLUDE_AGGREGATES);
+ 
+ 	/*
+ 	 * Although GroupingFunc is related to root->parse->groupingSets, this
+ 	 * field does not necessarily reflect its presence.
+ 	 */
+ 	foreach(lc, tlist_exprs)
+ 	{
+ 		Expr	   *expr = (Expr *) lfirst(lc);
+ 
+ 		if (IsA(expr, GroupingFunc))
+ 			return;
+ 	}
+ 
+ 	/*
+ 	 * Aggregates within the HAVING clause need to be processed in the same
+ 	 * way as those in the main targetlist.
+ 	 */
+ 	if (root->parse->havingQual != NULL)
+ 	{
+ 		List	   *having_exprs;
+ 
+ 		having_exprs = pull_var_clause((Node *) root->parse->havingQual,
+ 									   PVC_INCLUDE_AGGREGATES);
+ 		if (having_exprs != NIL)
+ 			tlist_exprs = list_concat(tlist_exprs, having_exprs);
+ 	}
+ 
+ 	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;
+ 		}
+ 
+ 		/*
+ 		 * Aggregation push-down does not work w/o aggcombinefn. This field is
+ 		 * not mandatory, so check if this particular aggregate can handle
+ 		 * partial aggregation.
+ 		 */
+ 		if (!OidIsValid(aggref->aggcombinefn))
+ 		{
+ 			root->grouped_var_list = NIL;
+ 			break;
+ 		}
+ 
+ 		/* Does GroupedVarInfo for this aggregate already exist? */
+ 		exists = false;
+ 		foreach(lc2, root->grouped_var_list)
+ 		{
+ 			gvi = lfirst_node(GroupedVarInfo, lc2);
+ 
+ 			if (equal(expr, gvi->gvexpr))
+ 			{
+ 				exists = true;
+ 				break;
+ 			}
+ 		}
+ 
+ 		/* Construct a new GroupedVarInfo if does not exist yet. */
+ 		if (!exists)
+ 		{
+ 			Relids		relids;
+ 
+ 			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
+ 				gvi->gv_eval_at = NULL;
+ 
+ 			/*
+ 			 * The transient state is what appears in the target.
+ 			 */
+ 			gvi->gv_width =
+ 				get_typavgwidth(exprType((Node *) gvi->agg_partial),
+ 								exprTypmod((Node *) gvi->agg_partial));
+ 
+ 			root->grouped_var_list = lappend(root->grouped_var_list, gvi);
+ 		}
+ 	}
+ 
+ 	list_free(tlist_exprs);
+ }
+ 
+ /*
+  * Create GroupedVarInfo for each expression usable as grouping key.
+  *
+  * In addition to the expressions of the query targetlist, group_pathkeys is
+  * also considered the source of grouping expressions. That increases the
+  * chance to get the relation output grouped.
+  */
+ static void
+ create_grouping_expr_grouped_var_infos(PlannerInfo *root)
+ {
+ 	ListCell   *l1,
+ 			   *l2;
+ 	List	   *exprs = NIL;
+ 	List	   *sortgrouprefs = NIL;
+ 
+ 	/*
+ 	 * Make sure GroupedVar exists for each expression usable as grouping key.
+ 	 */
+ 	foreach(l1, root->parse->groupClause)
+ 	{
+ 		SortGroupClause *sgClause;
+ 		TargetEntry *te;
+ 		Index		sortgroupref;
+ 
+ 		sgClause = lfirst_node(SortGroupClause, l1);
+ 		te = get_sortgroupclause_tle(sgClause, root->processed_tlist);
+ 		sortgroupref = te->ressortgroupref;
+ 
+ 		if (sortgroupref == 0)
+ 			continue;
+ 
+ 		/*
+ 		 * Non-zero sortgroupref does not necessarily imply grouping
+ 		 * expression: data can also be sorted by aggregate.
+ 		 */
+ 		if (IsA(te->expr, Aggref))
+ 			continue;
+ 
+ 		exprs = lappend(exprs, te->expr);
+ 		sortgrouprefs = lappend_int(sortgrouprefs, sortgroupref);
+ 	}
+ 
+ 	/*
+ 	 * Try to derive additional grouping expressions from group_pathkeys. This
+ 	 * increases the chance that relation can be grouped even if its columns
+ 	 * are not mentioned in the grouping clause.
+ 	 *
+ 	 * It's important that we have processed the explicit grouping columns
+ 	 * first. If the grouping clause contains multiple expressions belonging
+ 	 * to the same EC, the original (i.e. not derived) one should be preferred
+ 	 * when we build grouping target for a relation. Otherwise we have a
+ 	 * problem when trying to match target entries to grouping clauses during
+ 	 * plan creation, see extract_grouping_cols.
+ 	 */
+ 	foreach(l1, root->group_pathkeys)
+ 	{
+ 		PathKey    *pk = lfirst_node(PathKey, l1);
+ 		EquivalenceClass *ec = pk->pk_eclass;
+ 		EquivalenceMember *em;
+ 		Index		sortgroupref = 0;
+ 
+ 		/* 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(l2, ec->ec_members)
+ 		{
+ 			ListCell   *l3;
+ 
+ 			em = lfirst_node(EquivalenceMember, l2);
+ 
+ 			/*
+ 			 * We search for grouping expressions now.
+ 			 */
+ 			if (IsA(em->em_expr, Aggref))
+ 				continue;
+ 
+ 			if (em->em_nullable_relids)
+ 				continue;
+ 
+ 			/*
+ 			 * If targetlist contains this expression, it should provide us
+ 			 * with the sortgroupref.
+ 			 */
+ 			foreach(l3, root->processed_tlist)
+ 			{
+ 				TargetEntry *te = lfirst_node(TargetEntry, l3);
+ 
+ 				if (equal(em->em_expr, te->expr))
+ 				{
+ 					/*
+ 					 * XXX Not sure if the same expression exist in the
+ 					 * targetlist multiple times and if some occurrences can
+ 					 * miss the ressortgroupref. Maybe we just need to
+ 					 * Assert() here that ressortgroupref is non-zero.
+ 					 */
+ 					if (te->ressortgroupref > 0)
+ 					{
+ 						sortgroupref = te->ressortgroupref;
+ 						break;
+ 					}
+ 				}
+ 			}
+ 
+ 			/*
+ 			 * If a single EC member matches, no need to check the rest of the
+ 			 * EC.
+ 			 */
+ 			if (sortgroupref > 0)
+ 				break;
+ 		}
+ 
+ 		if (sortgroupref == 0)
+ 			/* Go for the next EC. */
+ 			continue;
+ 
+ 		/*
+ 		 * The EC contains a grouping expression, so each member of that EC
+ 		 * should be usable as grouping key. Remember all the expressions of
+ 		 * the EC as well as their (supposedly common) sortgroupref for the
+ 		 * final processing.
+ 		 */
+ 		foreach(l2, ec->ec_members)
+ 		{
+ 			ListCell   *l3;
+ 
+ 			em = lfirst_node(EquivalenceMember, l2);
+ 
+ 			/*
+ 			 * Do not add the expression if it's explicitly mentioned by the
+ 			 * targetlist. We should already have it in that case.
+ 			 */
+ 			foreach(l3, exprs)
+ 			{
+ 				if (equal(lfirst(l3), em->em_expr))
+ 					break;
+ 			}
+ 			if (l3 != NULL)
+ 				continue;
+ 
+ 			exprs = lappend(exprs, em->em_expr);
+ 			sortgrouprefs = lappend_int(sortgrouprefs, sortgroupref);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Finally construct GroupedVarInfo for each expression.
+ 	 */
+ 	forboth(l1, exprs, l2, sortgrouprefs)
+ 	{
+ 		Expr	   *expr = (Expr *) lfirst(l1);
+ 		int			sortgroupref = lfirst_int(l2);
+ 		GroupedVarInfo *gvi = makeNode(GroupedVarInfo);
+ 
+ 		gvi->gvid = list_length(root->grouped_var_list);
+ 		gvi->gvexpr = (Expr *) copyObject(expr);
+ 		gvi->sortgroupref = sortgroupref;
+ 
+ 		/* Find out where the expression should be evaluated. */
+ 		gvi->gv_eval_at = pull_varnos((Node *) expr);
+ 
+ 		/*
+ 		 * As a special case, Var can be present in the non-grouped target, so
+ 		 * set_rel_widths() could take care of the width computation. However
+ 		 * the Var might not be propagated high enough in the join tree, so
+ 		 * set_rel_width() might miss it. Handle it manually.
+ 		 */
+ 		if (IsA(expr, Var))
+ 		{
+ 			Var		   *var = castNode(Var, expr);
+ 			RelOptInfo *rel = root->simple_rel_array[var->varno];
+ 			Oid			reloid = rel->relid;
+ 
+ 			if (reloid != InvalidOid && var->varattno > 0)
+ 			{
+ 				int32		width = get_attavgwidth(reloid, var->varattno);
+ 
+ 				if (width > 0)
+ 				{
+ 					gvi->gv_width += width;
+ 					continue;
+ 				}
+ 			}
+ 		}
+ 
+ 		/*
+ 		 * In general we should not expect any statistics to exist for an
+ 		 * expression just because it's a grouping expression. So use the type
+ 		 * information to get the width estimate.
+ 		 */
+ 		gvi->gv_width = get_typavgwidth(exprType((Node *) gvi->gvexpr),
+ 										exprTypmod((Node *) gvi->gvexpr));
+ 
+ 		root->grouped_var_list = lappend(root->grouped_var_list, gvi);
+ 	}
+ }
+ 
+ /*
+  * Check if rel->reltarget allows the relation to be grouped and initialize
+  * the grouping target(s).
+  *
+  * target_agg is a target that we'll eventually used to aggregate the output
+  * of relation 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. (Thus the scan / join that provides the AggPath with input
+  * does not have to care whether the source relation does have grouped target
+  * or not.)
+  *
+  * The *group_exprs_extra_p list may receive additional grouping expressions
+  * that the query does not have. These can make the aggregation of base
+  * relation / join less efficient, but can allow for join of the grouped
+  * relation that wouldn't be possible otherwise.
+  */
+ void
+ initialize_grouped_target(PlannerInfo *root, RelOptInfo *rel,
+ 						  PathTarget *target_agg,
+ 						  List **group_exprs_extra_p)
+ {
+ 	PathTarget *target_plain;
+ 	ListCell   *lc1;
+ 	List	   *vars_unresolved = NIL;
+ 
+ 	/* The target to replace rel->reltarget. */
+ 	target_plain = create_empty_pathtarget();
+ 
+ 	foreach(lc1, rel->reltarget->exprs)
+ 	{
+ 		Var		   *tvar;
+ 		GroupedVar *gvar;
+ 
+ 		/*
+ 		 * Given that PlaceHolderVar currently prevents us from doing
+ 		 * aggregation push-down, the source target cannot contain anything
+ 		 * more complex than a Var. (As for generic grouping expressions,
+ 		 * add_grouped_vars_to_target will retrieve them from the query
+ 		 * targetlist and add them to target_agg outside this function.)
+ 		 */
+ 		tvar = lfirst_node(Var, lc1);
+ 
+ 		gvar = get_grouping_expression(root, (Expr *) tvar);
+ 		if (gvar != NULL)
+ 		{
+ 			/*
+ 			 * It's o.k. to use the target expression for grouping.
+ 			 *
+ 			 * The actual Var is added to the target. If we used the
+ 			 * containing GroupedVar, references from various clauses (e.g.
+ 			 * join quals) wouldn't work.
+ 			 */
+ 			add_column_to_pathtarget(target_agg, (Expr *) gvar->gvexpr,
+ 									 gvar->sortgroupref);
+ 
+ 			/*
+ 			 * As for the plain target, add the original expression but set
+ 			 * sortgroupref in addition.
+ 			 */
+ 			add_column_to_pathtarget(target_plain, gvar->gvexpr,
+ 									 gvar->sortgroupref);
+ 
+ 			/* Process the next expression. */
+ 			continue;
+ 		}
+ 
+ 		/*
+ 		 * Further investigation involves dependency check, for which we need
+ 		 * to have all the plain-var grouping expressions gathered. So far
+ 		 * only store the var in a list.
+ 		 */
+ 		vars_unresolved = lappend(vars_unresolved, tvar);
+ 	}
+ 
+ 	/*
+ 	 * Check for other possible reasons for the var to be in the plain target.
+ 	 */
+ 	foreach(lc1, vars_unresolved)
+ 	{
+ 		Var		   *var;
+ 		RangeTblEntry *rte;
+ 		List	   *deps = NIL;
+ 		Relids		relids_subtract;
+ 		int			ndx;
+ 		RelOptInfo *baserel;
+ 
+ 		var = lfirst_node(Var, lc1);
+ 		rte = root->simple_rte_array[var->varno];
+ 
+ 		/*
+ 		 * Dependent var is almost the same as one that has sortgroupref.
+ 		 */
+ 		if (check_functional_grouping(rte->relid, var->varno,
+ 									  var->varlevelsup,
+ 									  target_agg->exprs, &deps))
+ 		{
+ 
+ 			Index		sortgroupref = 0;
+ 
+ 			add_column_to_pathtarget(target_agg, (Expr *) var, sortgroupref);
+ 
+ 			/*
+ 			 * The var shouldn't be actually used as a grouping key (instead,
+ 			 * the one this depends on will be), so sortgroupref should not be
+ 			 * important. But once we have it ...
+ 			 */
+ 			add_column_to_pathtarget(target_plain, (Expr *) var,
+ 									 sortgroupref);
+ 
+ 			/*
+ 			 * The var may or may not be present in generic grouping
+ 			 * expression(s) or aggregate arguments, but we already have it in
+ 			 * the targets, so don't care.
+ 			 */
+ 			continue;
+ 		}
+ 
+ 		/*
+ 		 * Isn't the expression needed by joins above the current rel?
+ 		 *
+ 		 * The relids we're not interested in do include 0, which is the
+ 		 * top-level targetlist. The only reason for relids to contain 0
+ 		 * should be that arg_var is referenced either by aggregate or by
+ 		 * grouping expression, but right now we're interested in the *other*
+ 		 * reasons. (As soon as GroupedVars are installed, the top level
+ 		 * aggregates / grouping expressions no longer need direct reference
+ 		 * to arg_var anyway.)
+ 		 */
+ 		relids_subtract = bms_copy(rel->relids);
+ 		bms_add_member(relids_subtract, 0);
+ 
+ 		baserel = find_base_rel(root, var->varno);
+ 		ndx = var->varattno - baserel->min_attr;
+ 		if (bms_nonempty_difference(baserel->attr_needed[ndx],
+ 									relids_subtract))
+ 		{
+ 			/*
+ 			 * The variable is needed by upper join. This includes one that is
+ 			 * referenced by a generic grouping expression but couldn't be
+ 			 * recognized as grouping expression on its own at the top of the
+ 			 * loop.
+ 			 *
+ 			 * The only way to bring this var to the aggregation output is to
+ 			 * add it to the grouping expressions too.
+ 			 *
+ 			 * Since root->parse->groupClause is not supposed to contain this
+ 			 * expression, we need to construct special SortGroupClause. Its
+ 			 * tleSortGroupRef needs to be unique within target_agg, so
+ 			 * postpone creation of the SortGroupRefs until we're done with
+ 			 * the iteration of rel->reltarget->exprs.
+ 			 */
+ 			*group_exprs_extra_p = lappend(*group_exprs_extra_p, var);
+ 		}
+ 		else
+ 		{
+ 			/*
+ 			 * As long as the query is semantically correct, arriving here
+ 			 * means that the var is referenced either by aggregate argument
+ 			 * or by generic grouping expression. The per-relation aggregation
+ 			 * target should not contain it, as it only provides input for the
+ 			 * final aggregation.
+ 			 */
+ 		}
+ 
+ 		/*
+ 		 * The var is not suitable for grouping, but the plain target ought to
+ 		 * stay complete.
+ 		 */
+ 		add_column_to_pathtarget(target_plain, (Expr *) var, 0);
+ 	}
+ 
+ 	/*
+ 	 * Apply the adjusted input target as the replacement is complete now.
+ 	 */
+ 	/* TODO Free the old one. */
+ 	rel->reltarget = target_plain;
+ }
+ 
  
  /*****************************************************************************
   *
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
new file mode 100644
index 2e6a60e..9a15e5a
*** a/src/backend/optimizer/plan/planmain.c
--- b/src/backend/optimizer/plan/planmain.c
*************** query_planner(PlannerInfo *root, List *t
*** 178,183 ****
--- 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/util/relnode.c b/src/backend/optimizer/util/relnode.c
new file mode 100644
index 674cfc6..08686f6
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
***************
*** 27,32 ****
--- 27,33 ----
  #include "optimizer/prep.h"
  #include "optimizer/restrictinfo.h"
  #include "optimizer/tlist.h"
+ #include "parser/parse_oper.h"
  #include "utils/hsearch.h"
  
  
*************** build_joinrel_partition_info(RelOptInfo
*** 1748,1750 ****
--- 1749,2083 ----
  		joinrel->nullable_partexprs[cnt] = nullable_partexpr;
  	}
  }
+ 
+ /*
+  * If the relation can produce grouped paths, create GroupedPathInfo for it
+  * and create target for aggregation of the relation output.
+  */
+ void
+ prepare_rel_for_grouping(PlannerInfo *root, RelOptInfo *rel)
+ {
+ 	List	   *gvis;
+ 	List	   *aggregates = NIL;
+ 	List	   *grp_exprs = NIL;
+ 	bool		found_higher_agg;
+ 	ListCell   *lc;
+ 	GroupedPathInfo *gpi;
+ 	PathTarget *target;
+ 	List	   *grp_exprs_extra = NIL;
+ 
+ 	/*
+ 	 * The current implementation of aggregation push-down cannot handle
+ 	 * PlaceHolderVar (PHV).
+ 	 *
+ 	 * If we knew that the PHV should be evaluated in this target (and of
+ 	 * course, if its expression matched some grouping expression or Aggref
+ 	 * argument), we'd just let initialize_grouped_targets create GroupedVar
+ 	 * for the corresponding expression (phexpr). On the other hand, if we
+ 	 * knew that the PHV is evaluated below the current rel, we'd ignore it
+ 	 * because the referencing GroupedVar would take care of propagation of
+ 	 * the value to upper joins. (PHV whose ph_eval_at is above the current
+ 	 * rel make the aggregation push-down impossible in any case because the
+ 	 * partial aggregation would receive wrong input if we ignored the
+ 	 * ph_eval_at.)
+ 	 *
+ 	 * The problem is that the same PHV can be evaluated in the target of the
+ 	 * current rel or in that of lower rel --- depending on the input paths.
+ 	 * For example, consider rel->relids = {A, B, C} and if ph_eval_at = {B,
+ 	 * C}. Path "A JOIN (B JOIN C)" implies that the PHV is evaluated by the
+ 	 * "(B JOIN C)", while path "(A JOIN B) JOIN C" evaluates the PHV itself.
+ 	 */
+ 	foreach(lc, rel->reltarget->exprs)
+ 	{
+ 		Expr	   *expr = lfirst(lc);
+ 
+ 		if (IsA(expr, PlaceHolderVar))
+ 			return;
+ 	}
+ 
+ 	/*
+ 	 * target_agg will be used to aggregate the output of the current
+ 	 * relation's paths.
+ 	 */
+ 	target = create_empty_pathtarget();
+ 
+ 	if (IS_SIMPLE_REL(rel))
+ 	{
+ 		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 Agg plan
+ 	 * would receive different input at the base rel level.
+ 	 *
+ 	 * XXX 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 initialize_grouped_targets_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;
+ 
+ 	/*
+ 	 * Use equivalence classes to generate additional grouping expressions for
+ 	 * the current rel. Without these we might not be able to apply
+ 	 * aggregation to the relation result set.
+ 	 *
+ 	 * While create_grouping_expr_grouped_var_infos generates equivalence
+ 	 * class implied plain-Var grouping expressions at once, doing so for
+ 	 * generic expressions would be complex and might result in very long list
+ 	 * to search in. So generate the EC-related grouping expressions for each
+ 	 * particular set of relids.
+ 	 */
+ 	gvis = list_copy(root->grouped_var_list);
+ 	foreach(lc, root->grouped_var_list)
+ 	{
+ 		GroupedVarInfo *gvi = lfirst_node(GroupedVarInfo, lc);
+ 
+ 		/* Only interested in grouping expressions. */
+ 		if (IsA(gvi->gvexpr, Aggref))
+ 			continue;
+ 
+ 		/*
+ 		 * Should have been processed by
+ 		 * create_grouping_expr_grouped_var_infos.
+ 		 */
+ 		if (IsA(gvi->gvexpr, Var))
+ 			continue;
+ 
+ 		gvi = translate_expression_to_rels(root, gvi, rel->relids);
+ 		if (gvi != NULL)
+ 			gvis = lappend(gvis, gvi);
+ 	}
+ 
+ 	/*
+ 	 * Check if some aggregates or grouping expressions can be evaluated in
+ 	 * this relation's target, and collect all vars referenced by these
+ 	 * aggregates / grouping expressions;
+ 	 */
+ 	found_higher_agg = false;
+ 	foreach(lc, gvis)
+ 	{
+ 		GroupedVarInfo *gvi = lfirst_node(GroupedVarInfo, lc);
+ 
+ 		/*
+ 		 * The subset includes gv_eval_at uninitialized, which includes
+ 		 * Aggref.aggstar.
+ 		 */
+ 		if (bms_is_subset(gvi->gv_eval_at, rel->relids))
+ 		{
+ 			/*
+ 			 * initialize_grouped_targets will handle plain Var grouping
+ 			 * expressions because it needs to look them up in
+ 			 * grouped_var_list anyway.
+ 			 *
+ 			 * XXX A plain Var could actually be handled w/o GroupedVar, but
+ 			 * thus initialize_grouped_targets would have to spend extra
+ 			 * effort looking for the EC-related vars, instead of relying on
+ 			 * create_grouping_expr_grouped_var_infos. (Processing of
+ 			 * particular expression would look different, so we could hardly
+ 			 * reuse the same piece of code.)
+ 			 */
+ 			if (IsA(gvi->gvexpr, Var))
+ 				continue;
+ 
+ 			/*
+ 			 * Accept the aggregate / grouping expression.
+ 			 *
+ 			 * (GroupedVarInfo is more convenient for the next processing than
+ 			 * Aggref, see add_aggregates_to_grouped_target.)
+ 			 */
+ 			if (IsA(gvi->gvexpr, Aggref))
+ 				aggregates = lappend(aggregates, gvi);
+ 			else
+ 				grp_exprs = lappend(grp_exprs, gvi);
+ 		}
+ 		else if (bms_overlap(gvi->gv_eval_at, rel->relids) &&
+ 				 IsA(gvi->gvexpr, Aggref))
+ 		{
+ 			/*
+ 			 * Remember that there is at least one aggregate / grouping
+ 			 * expression that needs more than this rel.
+ 			 */
+ 			found_higher_agg = true;
+ 		}
+ 	}
+ 	list_free(gvis);
+ 
+ 	/*
+ 	 * Give up if some other aggregate(s) need multiple relations including
+ 	 * the current one. The problem is that grouping of the current relation
+ 	 * could make some input variables unavailable for the "higher aggregate",
+ 	 * and it'd also decrease the number of input rows the "higher aggregate"
+ 	 * receives.
+ 	 *
+ 	 * In contrast, grp_exprs is only supposed to contain generic grouping
+ 	 * expression, so it can be NIL so far. If all the grouping keys are just
+ 	 * plain Vars, initialize_grouped_targets will take care of them.
+ 	 */
+ 	if (found_higher_agg)
+ 		return;
+ 
+ 	/*
+ 	 * Add plain-var grouping expressions to the target.
+ 	 */
+ 	initialize_grouped_target(root, rel, target, &grp_exprs_extra);
+ 
+ 	/*
+ 	 * Grouping makes little sense w/o aggregate function and w/o grouping
+ 	 * expressions.
+ 	 *
+ 	 * This test was postponed because initialize_grouped_targets initializes
+ 	 * sortgrouprefs of rel->reltarget.  This information is useful to avoid
+ 	 * final aggregation. In particular, even non-grouped relation (e.g.
+ 	 * unique index scan) can generate unique values of grouping keys. See
+ 	 * check_group_key_uniqueness for details.
+ 	 */
+ 	if (aggregates == NIL)
+ 		return;
+ 
+ 	/*
+ 	 * Add (non-Var) grouping expressions (in the form of GroupedVar) to
+ 	 * target_agg.
+ 	 *
+ 	 * Follow the convention that the grouping expressions should precede the
+ 	 * aggregates.
+ 	 */
+ 	add_grouped_vars_to_target(root, target, grp_exprs);
+ 
+ 	/*
+ 	 * Initialize GroupedPathInfo.
+ 	 */
+ 	Assert(rel->gpi == NULL);
+ 	gpi = makeNode(GroupedPathInfo);
+ 	gpi->pathlist = NIL;
+ 	gpi->partial_pathlist = NIL;
+ 	rel->gpi = gpi;
+ 
+ 	/*
+ 	 * Partial aggregation makes no sense w/o grouping expressions.
+ 	 */
+ 	if (list_length(target->exprs) == 0)
+ 	{
+ 		pfree(rel->gpi);
+ 		rel->gpi = NULL;
+ 		return;
+ 	}
+ 
+ 	/*
+ 	 * If the aggregation target should have extra grouping expressions, add
+ 	 * them now. This step includes assignment of tleSortGroupRef's which we
+ 	 * can generate now (the "ordinary" grouping expression are present in the
+ 	 * target by now).
+ 	 */
+ 	if (list_length(grp_exprs_extra) > 0)
+ 	{
+ 		Index		sortgroupref;
+ 
+ 		/*
+ 		 * Always start at root->max_sortgroupref. The extra grouping
+ 		 * expressions aren't used during the final aggregation, so the
+ 		 * sortgroupref values don't need to be unique across the query. Thus
+ 		 * we don't have to increase root->max_sortgroupref, which makes
+ 		 * recognition of the extra grouping expressions pretty easy.
+ 		 */
+ 		sortgroupref = root->max_sortgroupref;
+ 
+ 		/*
+ 		 * Generate the SortGroupClause's and add the expressions to the
+ 		 * target.
+ 		 */
+ 		foreach(lc, grp_exprs_extra)
+ 		{
+ 			Var		   *var = lfirst_node(Var, lc);
+ 			SortGroupClause *cl = makeNode(SortGroupClause);
+ 			PathTarget *target_plain;
+ 			int			i = 0;
+ 			ListCell   *lc2;
+ 
+ 			/*
+ 			 * TODO Verify that these fields are sufficient for this special
+ 			 * SortGroupClause.
+ 			 */
+ 			cl->tleSortGroupRef = ++sortgroupref;
+ 			get_sort_group_operators(var->vartype,
+ 									 false, true, false,
+ 									 NULL, &cl->eqop, NULL,
+ 									 &cl->hashable);
+ 			gpi->sortgroupclauses = lappend(gpi->sortgroupclauses, cl);
+ 			add_column_to_pathtarget(target, (Expr *) var,
+ 									 cl->tleSortGroupRef);
+ 
+ 			/*
+ 			 * The aggregation input target must emit this var too. It can
+ 			 * already be there, so avoid adding it again.
+ 			 */
+ 			target_plain = rel->reltarget;
+ 			foreach(lc2, target_plain->exprs)
+ 			{
+ 				Expr	   *expr = (Expr *) lfirst(lc2);
+ 
+ 				if (equal(expr, var))
+ 				{
+ 					/*
+ 					 * The fact that the var is there does not imply that it
+ 					 * has sortgroupref set. For example, the reason that it's
+ 					 * there can be that a generic grouping expression
+ 					 * references it, so grouping by the var alone hasn't been
+ 					 * considered so far.
+ 					 */
+ 					if (target_plain->sortgrouprefs == NULL)
+ 					{
+ 						target_plain->sortgrouprefs = (Index *)
+ 							palloc0(list_length(target_plain->exprs) *
+ 									sizeof(Index));
+ 					}
+ 					if (target_plain->sortgrouprefs[i] == 0)
+ 						target_plain->sortgrouprefs[i] = cl->tleSortGroupRef;
+ 
+ 					break;
+ 				}
+ 
+ 				i++;
+ 			}
+ 			if (lc2 != NULL)
+ 				continue;
+ 
+ 			/*
+ 			 * Add the var if it's not in the target yet.
+ 			 */
+ 			add_column_to_pathtarget(target_plain, (Expr *) var,
+ 									 cl->tleSortGroupRef);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Add aggregates (in the form of GroupedVar) to the target(s).
+ 	 */
+ 	add_grouped_vars_to_target(root, target, aggregates);
+ 
+ 	/* TODO Check if copy is needed here. */
+ 	gpi->target = copy_pathtarget(target);
+ 
+ 	/*
+ 	 * Add the non-Var group expressions to a separate target. If
+ 	 * rel->reltarget should be used to generate input for partial
+ 	 * aggregation, the corresponding path should include these expressions.
+ 	 */
+ 	if (list_length(grp_exprs) > 0)
+ 	{
+ 		rel->gpi->group_exprs = create_empty_pathtarget();
+ 		add_grouped_vars_to_target(root, rel->gpi->group_exprs, grp_exprs);
+ 	}
+ }
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
new file mode 100644
index 9345891..abdc5d9
*** a/src/backend/optimizer/util/tlist.c
--- b/src/backend/optimizer/util/tlist.c
*************** apply_pathtarget_labeling_to_tlist(List
*** 783,788 ****
--- 783,853 ----
  }
  
  /*
+  * For each aggregate add GroupedVar to the grouped target.
+  *
+  * Caller passes the aggregates in the form of GroupedVarInfos so that we
+  * don't have to look for gvid.
+  */
+ void
+ add_grouped_vars_to_target(PlannerInfo *root, PathTarget *target,
+ 						   List *expressions)
+ {
+ 	ListCell   *lc;
+ 
+ 	/* Create the vars and add them to the target. */
+ 	foreach(lc, expressions)
+ 	{
+ 		GroupedVarInfo *gvi;
+ 		GroupedVar *gvar;
+ 
+ 		gvi = lfirst_node(GroupedVarInfo, lc);
+ 		gvar = makeNode(GroupedVar);
+ 		gvar->gvid = gvi->gvid;
+ 		gvar->gvexpr = gvi->gvexpr;
+ 		gvar->agg_partial = gvi->agg_partial;
+ 		add_column_to_pathtarget(target, (Expr *) gvar, gvi->sortgroupref);
+ 	}
+ }
+ 
+ /*
+  * Return GroupedVar containing the passed-in expression if one exists, or
+  * NULL if the expression cannot be used as grouping key.
+  *
+  * create_grouping_expr_grouped_var_infos should have been called before this
+  * function gets called the first time.
+  */
+ GroupedVar *
+ get_grouping_expression(PlannerInfo *root, Expr *expr)
+ {
+ 	ListCell   *lc;
+ 
+ 	/* Caller should have noticed the empty list much earlier. */
+ 	Assert(root->grouped_var_list != NIL);
+ 
+ 	foreach(lc, root->grouped_var_list)
+ 	{
+ 		GroupedVarInfo *gvi = lfirst_node(GroupedVarInfo, lc);
+ 
+ 		if (IsA(gvi->gvexpr, Aggref))
+ 			continue;
+ 
+ 		if (equal(gvi->gvexpr, expr))
+ 		{
+ 			GroupedVar *result = makeNode(GroupedVar);
+ 
+ 			Assert(gvi->sortgroupref > 0);
+ 			result->gvexpr = gvi->gvexpr;
+ 			result->gvid = gvi->gvid;
+ 			result->sortgroupref = gvi->sortgroupref;
+ 			return result;
+ 		}
+ 	}
+ 
+ 	/* The expression cannot be used as grouping key. */
+ 	return NULL;
+ }
+ 
+ /*
   * split_pathtarget_at_srfs
   *		Split given PathTarget into multiple levels to position SRFs safely
   *
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
new file mode 100644
index e367221..7090d02
*** a/src/include/optimizer/clauses.h
--- b/src/include/optimizer/clauses.h
*************** extern Node *estimate_expression_value(P
*** 84,88 ****
  
  extern Query *inline_set_returning_function(PlannerInfo *root,
  							  RangeTblEntry *rte);
! 
  #endif							/* CLAUSES_H */
--- 84,90 ----
  
  extern Query *inline_set_returning_function(PlannerInfo *root,
  							  RangeTblEntry *rte);
! extern GroupedVarInfo *translate_expression_to_rels(PlannerInfo *root,
! 							 GroupedVarInfo *gvi,
! 							 Relids relids);
  #endif							/* CLAUSES_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
new file mode 100644
index 3ef12b3..08111d2
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern RelOptInfo *build_child_join_rel(
*** 300,304 ****
  					 RelOptInfo *outer_rel, RelOptInfo *inner_rel,
  					 RelOptInfo *parent_joinrel, List *restrictlist,
  					 SpecialJoinInfo *sjinfo, JoinType jointype);
! 
  #endif							/* PATHNODE_H */
--- 300,304 ----
  					 RelOptInfo *outer_rel, RelOptInfo *inner_rel,
  					 RelOptInfo *parent_joinrel, List *restrictlist,
  					 SpecialJoinInfo *sjinfo, JoinType jointype);
! extern void prepare_rel_for_grouping(PlannerInfo *root, RelOptInfo *rel);
  #endif							/* PATHNODE_H */
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
new file mode 100644
index d613322..a4c6f5d
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
*************** extern void add_base_rels_to_query(Plann
*** 76,81 ****
--- 76,83 ----
  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 0d3ec92..a725818
*** a/src/include/optimizer/tlist.h
--- b/src/include/optimizer/tlist.h
*************** extern Node *get_sortgroupclause_expr(So
*** 41,46 ****
--- 41,50 ----
  						 List *targetList);
  extern List *get_sortgrouplist_exprs(List *sgClauses,
  						List *targetList);
+ extern void get_grouping_expressions(PlannerInfo *root, PathTarget *target,
+ 						 List *target_group_clauses,
+ 						 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 ****
--- 69,82 ----
  						 PathTarget *target, PathTarget *input_target,
  						 List **targets, List **targets_contain_srfs);
  
+ extern void add_grouped_vars_to_target(PlannerInfo *root, PathTarget *target,
+ 						   List *expressions);
+ extern GroupedVar *get_grouping_expression(PlannerInfo *root, Expr *expr);
+ extern void initialize_grouped_target(PlannerInfo *root,
+ 						  RelOptInfo *rel,
+ 						  PathTarget *target_agg,
+ 						  List **group_exprs_extra_p);
+ 
  /* 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))


  agg_pushdown_v5/03_add_path.diff
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
new file mode 100644
index 370cc36..0690bd4
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
*************** fileGetForeignPaths(PlannerInfo *root,
*** 551,557 ****
  									 NIL,	/* no pathkeys */
  									 NULL,	/* no outer rel either */
  									 NULL,	/* no extra plan */
! 									 coptions));
  
  	/*
  	 * If data file was sorted, and we knew it somehow, we could insert
--- 551,557 ----
  									 NIL,	/* no pathkeys */
  									 NULL,	/* no outer rel either */
  									 NULL,	/* no extra plan */
! 									 coptions), false);
  
  	/*
  	 * If data file was sorted, and we knew it somehow, we could insert
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
new file mode 100644
index fb65e2e..459c6b9
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
*************** postgresGetForeignPaths(PlannerInfo *roo
*** 907,913 ****
  								   NULL,	/* no outer rel either */
  								   NULL,	/* no extra plan */
  								   NIL);	/* no fdw_private list */
! 	add_path(baserel, (Path *) path);
  
  	/* Add paths with pathkeys */
  	add_paths_with_pathkeys_for_rel(root, baserel, NULL);
--- 907,913 ----
  								   NULL,	/* no outer rel either */
  								   NULL,	/* no extra plan */
  								   NIL);	/* no fdw_private list */
! 	add_path(baserel, (Path *) path, false);
  
  	/* Add paths with pathkeys */
  	add_paths_with_pathkeys_for_rel(root, baserel, NULL);
*************** postgresGetForeignPaths(PlannerInfo *roo
*** 1079,1085 ****
  									   param_info->ppi_req_outer,
  									   NULL,
  									   NIL);	/* no fdw_private list */
! 		add_path(baserel, (Path *) path);
  	}
  }
  
--- 1079,1085 ----
  									   param_info->ppi_req_outer,
  									   NULL,
  									   NIL);	/* no fdw_private list */
! 		add_path(baserel, (Path *) path, false);
  	}
  }
  
*************** add_paths_with_pathkeys_for_rel(PlannerI
*** 4343,4349 ****
  										 useful_pathkeys,
  										 NULL,
  										 epq_path,
! 										 NIL));
  	}
  }
  
--- 4343,4350 ----
  										 useful_pathkeys,
  										 NULL,
  										 epq_path,
! 										 NIL),
! 				 false);
  	}
  }
  
*************** postgresGetForeignJoinPaths(PlannerInfo
*** 4574,4580 ****
  									   NIL);	/* no fdw_private */
  
  	/* Add generated path into joinrel by add_path(). */
! 	add_path(joinrel, (Path *) joinpath);
  
  	/* Consider pathkeys for the join relation */
  	add_paths_with_pathkeys_for_rel(root, joinrel, epq_path);
--- 4575,4581 ----
  									   NIL);	/* no fdw_private */
  
  	/* Add generated path into joinrel by add_path(). */
! 	add_path(joinrel, (Path *) joinpath, false);
  
  	/* Consider pathkeys for the join relation */
  	add_paths_with_pathkeys_for_rel(root, joinrel, epq_path);
*************** add_foreign_grouping_paths(PlannerInfo *
*** 4898,4904 ****
  										NIL);	/* no fdw_private */
  
  	/* Add generated path into grouped_rel by add_path(). */
! 	add_path(grouped_rel, (Path *) grouppath);
  }
  
  /*
--- 4899,4905 ----
  										NIL);	/* no fdw_private */
  
  	/* Add generated path into grouped_rel by add_path(). */
! 	add_path(grouped_rel, (Path *) grouppath, false);
  }
  
  /*
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
new file mode 100644
index 0e8463e..6872e14
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** set_plain_rel_pathlist(PlannerInfo *root
*** 698,704 ****
  	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)
--- 698,704 ----
  	required_outer = rel->lateral_relids;
  
  	/* Consider sequential scan */
! 	add_path(rel, create_seqscan_path(root, rel, required_outer, 0), false);
  
  	/* If appropriate, consider parallel sequential scan */
  	if (rel->consider_parallel && required_outer == NULL)
*************** create_plain_partial_paths(PlannerInfo *
*** 727,733 ****
  		return;
  
  	/* Add an unordered partial path based on a parallel sequential scan. */
! 	add_partial_path(rel, create_seqscan_path(root, rel, NULL, parallel_workers));
  }
  
  /*
--- 727,734 ----
  		return;
  
  	/* Add an unordered partial path based on a parallel sequential scan. */
! 	add_partial_path(rel, create_seqscan_path(root, rel, NULL, parallel_workers),
! 					 false);
  }
  
  /*
*************** set_tablesample_rel_pathlist(PlannerInfo
*** 813,819 ****
  		path = (Path *) create_material_path(rel, path);
  	}
  
! 	add_path(rel, path);
  
  	/* For the moment, at least, there are no other paths to consider */
  }
--- 814,820 ----
  		path = (Path *) create_material_path(rel, path);
  	}
  
! 	add_path(rel, path, false);
  
  	/* For the moment, at least, there are no other paths to consider */
  }
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1550,1556 ****
  	if (subpaths_valid)
  		add_path(rel, (Path *) create_append_path(rel, subpaths, NIL,
  												  NULL, 0, false,
! 												  partitioned_rels, -1));
  
  	/*
  	 * Consider an append of unordered, unparameterized partial paths.  Make
--- 1551,1558 ----
  	if (subpaths_valid)
  		add_path(rel, (Path *) create_append_path(rel, subpaths, NIL,
  												  NULL, 0, false,
! 												  partitioned_rels, -1),
! 				 false);
  
  	/*
  	 * Consider an append of unordered, unparameterized partial paths.  Make
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1602,1608 ****
  		partial_rows = appendpath->path.rows;
  
  		/* Add the path. */
! 		add_partial_path(rel, (Path *) appendpath);
  	}
  
  	/*
--- 1604,1610 ----
  		partial_rows = appendpath->path.rows;
  
  		/* Add the path. */
! 		add_partial_path(rel, (Path *) appendpath, false);
  	}
  
  	/*
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1643,1649 ****
  										pa_partial_subpaths,
  										NULL, parallel_workers, true,
  										partitioned_rels, partial_rows);
! 		add_partial_path(rel, (Path *) appendpath);
  	}
  
  	/*
--- 1645,1651 ----
  										pa_partial_subpaths,
  										NULL, parallel_workers, true,
  										partitioned_rels, partial_rows);
! 		add_partial_path(rel, (Path *) appendpath, false);
  	}
  
  	/*
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1697,1703 ****
  			add_path(rel, (Path *)
  					 create_append_path(rel, subpaths, NIL,
  										required_outer, 0, false,
! 										partitioned_rels, -1));
  	}
  }
  
--- 1699,1705 ----
  			add_path(rel, (Path *)
  					 create_append_path(rel, subpaths, NIL,
  										required_outer, 0, false,
! 										partitioned_rels, -1), false);
  	}
  }
  
*************** generate_mergeappend_paths(PlannerInfo *
*** 1793,1806 ****
  														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));
  	}
  }
  
--- 1795,1810 ----
  														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)
*** 1961,1967 ****
  	rel->partial_pathlist = NIL;
  
  	add_path(rel, (Path *) create_append_path(rel, NIL, NIL, NULL,
! 											  0, false, NIL, -1));
  
  	/*
  	 * We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
--- 1965,1972 ----
  	rel->partial_pathlist = NIL;
  
  	add_path(rel, (Path *) create_append_path(rel, NIL, NIL, NULL,
! 											  0, false, NIL, -1),
! 			 false);
  
  	/*
  	 * We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
*************** set_subquery_pathlist(PlannerInfo *root,
*** 2175,2181 ****
  		/* Generate outer path using this subpath */
  		add_path(rel, (Path *)
  				 create_subqueryscan_path(root, rel, subpath,
! 										  pathkeys, required_outer));
  	}
  }
  
--- 2180,2187 ----
  		/* 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,
*** 2244,2250 ****
  
  	/* Generate appropriate path */
  	add_path(rel, create_functionscan_path(root, rel,
! 										   pathkeys, required_outer));
  }
  
  /*
--- 2250,2256 ----
  
  	/* Generate appropriate path */
  	add_path(rel, create_functionscan_path(root, rel,
! 										   pathkeys, required_outer), false);
  }
  
  /*
*************** set_values_pathlist(PlannerInfo *root, R
*** 2264,2270 ****
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_valuesscan_path(root, rel, required_outer));
  }
  
  /*
--- 2270,2276 ----
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_valuesscan_path(root, rel, required_outer), false);
  }
  
  /*
*************** set_tablefunc_pathlist(PlannerInfo *root
*** 2285,2291 ****
  
  	/* Generate appropriate path */
  	add_path(rel, create_tablefuncscan_path(root, rel,
! 											required_outer));
  }
  
  /*
--- 2291,2297 ----
  
  	/* Generate appropriate path */
  	add_path(rel, create_tablefuncscan_path(root, rel,
! 											required_outer), false);
  }
  
  /*
*************** set_cte_pathlist(PlannerInfo *root, RelO
*** 2351,2357 ****
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_ctescan_path(root, rel, required_outer));
  }
  
  /*
--- 2357,2363 ----
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_ctescan_path(root, rel, required_outer), false);
  }
  
  /*
*************** set_namedtuplestore_pathlist(PlannerInfo
*** 2378,2384 ****
  	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);
--- 2384,2391 ----
  	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
*** 2431,2437 ****
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_worktablescan_path(root, rel, required_outer));
  }
  
  /*
--- 2438,2445 ----
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_worktablescan_path(root, rel, required_outer),
! 			 false);
  }
  
  /*
*************** generate_gather_paths(PlannerInfo *root,
*** 2463,2469 ****
  	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
--- 2471,2477 ----
  	simple_gather_path = (Path *)
  		create_gather_path(root, rel, cheapest_partial_path, rel->reltarget,
  						   NULL, NULL);
! 	add_path(rel, simple_gather_path, false);
  
  	/*
  	 * For each useful ordering, we can consider an order-preserving Gather
*************** generate_gather_paths(PlannerInfo *root,
*** 2479,2485 ****
  
  		path = create_gather_merge_path(root, rel, subpath, rel->reltarget,
  										subpath->pathkeys, NULL, NULL);
! 		add_path(rel, &path->path);
  	}
  }
  
--- 2487,2493 ----
  
  		path = create_gather_merge_path(root, rel, subpath, rel->reltarget,
  										subpath->pathkeys, NULL, NULL);
! 		add_path(rel, &path->path, false);
  	}
  }
  
*************** create_partial_bitmap_paths(PlannerInfo
*** 3304,3310 ****
  		return;
  
  	add_partial_path(rel, (Path *) create_bitmap_heap_path(root, rel,
! 														   bitmapqual, rel->lateral_relids, 1.0, parallel_workers));
  }
  
  /*
--- 3312,3319 ----
  		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/costsize.c b/src/backend/optimizer/path/costsize.c
new file mode 100644
index c3daacd..b0c7a0c
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
***************
*** 91,96 ****
--- 91,97 ----
  #include "optimizer/plancat.h"
  #include "optimizer/planmain.h"
  #include "optimizer/restrictinfo.h"
+ #include "optimizer/var.h"
  #include "parser/parsetree.h"
  #include "utils/lsyscache.h"
  #include "utils/selfuncs.h"
*************** static Selectivity get_foreign_key_join_
*** 164,170 ****
  								 List **restrictlist);
  static Cost append_nonpartial_cost(List *subpaths, int numpaths,
  					   int parallel_workers);
! static void set_rel_width(PlannerInfo *root, RelOptInfo *rel);
  static double relation_byte_size(double tuples, int width);
  static double page_size(double tuples, int width);
  static double get_parallel_divisor(Path *path);
--- 165,172 ----
  								 List **restrictlist);
  static Cost append_nonpartial_cost(List *subpaths, int numpaths,
  					   int parallel_workers);
! static void set_rel_width(PlannerInfo *root, RelOptInfo *rel,
! 			  PathTarget *reltarget, bool grouped);
  static double relation_byte_size(double tuples, int width);
  static double page_size(double tuples, int width);
  static double get_parallel_divisor(Path *path);
*************** set_baserel_size_estimates(PlannerInfo *
*** 4285,4291 ****
  
  	cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
  
! 	set_rel_width(root, rel);
  }
  
  /*
--- 4287,4300 ----
  
  	cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
  
! 	set_rel_width(root, rel, rel->reltarget, false);
! 
! 	/*
! 	 * If the relation can produce grouped path, estimate width and costs of
! 	 * the corresponding target.
! 	 */
! 	if (rel->gpi)
! 		set_rel_width(root, rel, rel->gpi->target, true);
  }
  
  /*
*************** set_foreign_size_estimates(PlannerInfo *
*** 5036,5042 ****
  
  	cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
  
! 	set_rel_width(root, rel);
  }
  
  
--- 5045,5051 ----
  
  	cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
  
! 	set_rel_width(root, rel, rel->reltarget, false);
  }
  
  
*************** set_foreign_size_estimates(PlannerInfo *
*** 5060,5068 ****
   *
   * The per-attribute width estimates are cached for possible re-use while
   * building join relations or post-scan/join pathtargets.
   */
  static void
! set_rel_width(PlannerInfo *root, RelOptInfo *rel)
  {
  	Oid			reloid = planner_rt_fetch(rel->relid, root)->relid;
  	int32		tuple_width = 0;
--- 5069,5081 ----
   *
   * The per-attribute width estimates are cached for possible re-use while
   * building join relations or post-scan/join pathtargets.
+  *
+  * If called for grouped target, it's assumed that it the function was already
+  * called for the non-grouped one.
   */
  static void
! set_rel_width(PlannerInfo *root, RelOptInfo *rel, PathTarget *reltarget,
! 			  bool grouped)
  {
  	Oid			reloid = planner_rt_fetch(rel->relid, root)->relid;
  	int32		tuple_width = 0;
*************** set_rel_width(PlannerInfo *root, RelOptI
*** 5070,5079 ****
  	ListCell   *lc;
  
  	/* Vars are assumed to have cost zero, but other exprs do not */
! 	rel->reltarget->cost.startup = 0;
! 	rel->reltarget->cost.per_tuple = 0;
  
! 	foreach(lc, rel->reltarget->exprs)
  	{
  		Node	   *node = (Node *) lfirst(lc);
  
--- 5083,5092 ----
  	ListCell   *lc;
  
  	/* Vars are assumed to have cost zero, but other exprs do not */
! 	reltarget->cost.startup = 0;
! 	reltarget->cost.per_tuple = 0;
  
! 	foreach(lc, reltarget->exprs)
  	{
  		Node	   *node = (Node *) lfirst(lc);
  
*************** set_rel_width(PlannerInfo *root, RelOptI
*** 5115,5120 ****
--- 5128,5140 ----
  				continue;
  			}
  
+ 			/*
+ 			 * The non-grouped target of this rel should already have been
+ 			 * processed, so do not waste any effort to get zero width again.
+ 			 */
+ 			if (grouped && rel->attr_widths[ndx] == 0)
+ 				continue;
+ 
  			/* Try to get column width from statistics */
  			if (reloid != InvalidOid && var->varattno > 0)
  			{
*************** set_rel_width(PlannerInfo *root, RelOptI
*** 5148,5155 ****
  
  			tuple_width += phinfo->ph_width;
  			cost_qual_eval_node(&cost, (Node *) phv->phexpr, root);
! 			rel->reltarget->cost.startup += cost.startup;
! 			rel->reltarget->cost.per_tuple += cost.per_tuple;
  		}
  		else
  		{
--- 5168,5195 ----
  
  			tuple_width += phinfo->ph_width;
  			cost_qual_eval_node(&cost, (Node *) phv->phexpr, root);
! 			reltarget->cost.startup += cost.startup;
! 			reltarget->cost.per_tuple += cost.per_tuple;
! 		}
! 		else if (IsA(node, GroupedVar))
! 		{
! 			GroupedVar *gvar = (GroupedVar *) node;
! 			GroupedVarInfo *gvinfo;
! 
! 			/*
! 			 * GroupedVar should not appear in the "ordinary" target.
! 			 */
! 			Assert(grouped);
! 
! 			gvinfo = find_grouped_var_info(root, gvar);
! 			tuple_width += gvinfo->gv_width;
! 
! 			/*
! 			 * Only AggPath can evaluate GroupedVar, whether it's an aggregate
! 			 * or generic grouping expression. In the other cases the
! 			 * GroupedVar we see here only bubbled up from a lower AggPath, so
! 			 * it does not add any cost to the path that owns this target.
! 			 */
  		}
  		else
  		{
*************** set_rel_width(PlannerInfo *root, RelOptI
*** 5166,5173 ****
  			tuple_width += item_width;
  			/* Not entirely clear if we need to account for cost, but do so */
  			cost_qual_eval_node(&cost, node, root);
! 			rel->reltarget->cost.startup += cost.startup;
! 			rel->reltarget->cost.per_tuple += cost.per_tuple;
  		}
  	}
  
--- 5206,5213 ----
  			tuple_width += item_width;
  			/* Not entirely clear if we need to account for cost, but do so */
  			cost_qual_eval_node(&cost, node, root);
! 			reltarget->cost.startup += cost.startup;
! 			reltarget->cost.per_tuple += cost.per_tuple;
  		}
  	}
  
*************** set_rel_width(PlannerInfo *root, RelOptI
*** 5204,5210 ****
  	}
  
  	Assert(tuple_width >= 0);
! 	rel->reltarget->width = tuple_width;
  }
  
  /*
--- 5244,5250 ----
  	}
  
  	Assert(tuple_width >= 0);
! 	reltarget->width = tuple_width;
  }
  
  /*
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
new file mode 100644
index 18f6baf..c8bd820
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
*************** 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)
--- 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, 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);
  		}
  	}
  }
--- 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, false);
  		}
  	}
  }
*************** get_index_paths(PlannerInfo *root, RelOp
*** 789,795 ****
  		IndexPath  *ipath = (IndexPath *) lfirst(lc);
  
  		if (index->amhasgettuple)
! 			add_path(rel, (Path *) ipath);
  
  		if (index->amhasgetbitmap &&
  			(ipath->path.pathkeys == NIL ||
--- 789,795 ----
  		IndexPath  *ipath = (IndexPath *) lfirst(lc);
  
  		if (index->amhasgettuple)
! 			add_path(rel, (Path *) ipath, false);
  
  		if (index->amhasgetbitmap &&
  			(ipath->path.pathkeys == NIL ||
*************** build_index_paths(PlannerInfo *root, Rel
*** 1077,1083 ****
  			 * parallel workers, just free it.
  			 */
  			if (ipath->path.parallel_workers > 0)
! 				add_partial_path(rel, (Path *) ipath);
  			else
  				pfree(ipath);
  		}
--- 1077,1083 ----
  			 * parallel workers, just free it.
  			 */
  			if (ipath->path.parallel_workers > 0)
! 				add_partial_path(rel, (Path *) ipath, false);
  			else
  				pfree(ipath);
  		}
*************** 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);
  			}
--- 1129,1135 ----
  				 * using parallel workers, just free it.
  				 */
  				if (ipath->path.parallel_workers > 0)
! 					add_partial_path(rel, (Path *) ipath, false);
  				else
  					pfree(ipath);
  			}
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
new file mode 100644
index e774130..2a931af
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
*************** try_nestloop_path(PlannerInfo *root,
*** 422,428 ****
  
  	if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  pathkeys, required_outer))
  	{
  		/*
  		 * If the inner path is parameterized, it is parameterized by the
--- 422,428 ----
  
  	if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  pathkeys, required_outer, false))
  	{
  		/*
  		 * If the inner path is parameterized, it is parameterized by the
*************** try_nestloop_path(PlannerInfo *root,
*** 455,461 ****
  									  inner_path,
  									  extra->restrictlist,
  									  pathkeys,
! 									  required_outer));
  	}
  	else
  	{
--- 455,461 ----
  									  inner_path,
  									  extra->restrictlist,
  									  pathkeys,
! 									  required_outer), false);
  	}
  	else
  	{
*************** try_partial_nestloop_path(PlannerInfo *r
*** 513,519 ****
  	 */
  	initial_cost_nestloop(root, &workspace, jointype,
  						  outer_path, inner_path, extra);
! 	if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys))
  		return;
  
  	/*
--- 513,520 ----
  	 */
  	initial_cost_nestloop(root, &workspace, jointype,
  						  outer_path, inner_path, extra);
! 	if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys,
! 								   false))
  		return;
  
  	/*
*************** try_partial_nestloop_path(PlannerInfo *r
*** 543,549 ****
  										  inner_path,
  										  extra->restrictlist,
  										  pathkeys,
! 										  NULL));
  }
  
  /*
--- 544,550 ----
  										  inner_path,
  										  extra->restrictlist,
  										  pathkeys,
! 										  NULL), false);
  }
  
  /*
*************** try_mergejoin_path(PlannerInfo *root,
*** 617,623 ****
  
  	if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  pathkeys, required_outer))
  	{
  		add_path(joinrel, (Path *)
  				 create_mergejoin_path(root,
--- 618,624 ----
  
  	if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  pathkeys, required_outer, false))
  	{
  		add_path(joinrel, (Path *)
  				 create_mergejoin_path(root,
*************** try_mergejoin_path(PlannerInfo *root,
*** 632,638 ****
  									   required_outer,
  									   mergeclauses,
  									   outersortkeys,
! 									   innersortkeys));
  	}
  	else
  	{
--- 633,639 ----
  									   required_outer,
  									   mergeclauses,
  									   outersortkeys,
! 									   innersortkeys), false);
  	}
  	else
  	{
*************** try_partial_mergejoin_path(PlannerInfo *
*** 691,697 ****
  						   outersortkeys, innersortkeys,
  						   extra);
  
! 	if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys))
  		return;
  
  	/* Might be good enough to be worth trying, so let's try it. */
--- 692,699 ----
  						   outersortkeys, innersortkeys,
  						   extra);
  
! 	if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys,
! 								   false))
  		return;
  
  	/* Might be good enough to be worth trying, so let's try it. */
*************** try_partial_mergejoin_path(PlannerInfo *
*** 708,714 ****
  										   NULL,
  										   mergeclauses,
  										   outersortkeys,
! 										   innersortkeys));
  }
  
  /*
--- 710,716 ----
  										   NULL,
  										   mergeclauses,
  										   outersortkeys,
! 										   innersortkeys), false);
  }
  
  /*
*************** try_hashjoin_path(PlannerInfo *root,
*** 751,757 ****
  
  	if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  NIL, required_outer))
  	{
  		add_path(joinrel, (Path *)
  				 create_hashjoin_path(root,
--- 753,759 ----
  
  	if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  NIL, required_outer, false))
  	{
  		add_path(joinrel, (Path *)
  				 create_hashjoin_path(root,
*************** try_hashjoin_path(PlannerInfo *root,
*** 764,770 ****
  									  false,	/* parallel_hash */
  									  extra->restrictlist,
  									  required_outer,
! 									  hashclauses));
  	}
  	else
  	{
--- 766,772 ----
  									  false,	/* parallel_hash */
  									  extra->restrictlist,
  									  required_outer,
! 									  hashclauses), false);
  	}
  	else
  	{
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 815,821 ****
  	 */
  	initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
  						  outer_path, inner_path, extra, true);
! 	if (!add_partial_path_precheck(joinrel, workspace.total_cost, NIL))
  		return;
  
  	/* Might be good enough to be worth trying, so let's try it. */
--- 817,823 ----
  	 */
  	initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
  						  outer_path, inner_path, extra, true);
! 	if (!add_partial_path_precheck(joinrel, workspace.total_cost, NIL, false))
  		return;
  
  	/* Might be good enough to be worth trying, so let's try it. */
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 830,836 ****
  										  parallel_hash,
  										  extra->restrictlist,
  										  NULL,
! 										  hashclauses));
  }
  
  /*
--- 832,838 ----
  										  parallel_hash,
  										  extra->restrictlist,
  										  NULL,
! 										  hashclauses), false);
  }
  
  /*
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
new file mode 100644
index 5e03f8b..285f269
*** a/src/backend/optimizer/path/joinrels.c
--- b/src/backend/optimizer/path/joinrels.c
*************** mark_dummy_rel(RelOptInfo *rel)
*** 1233,1239 ****
  
  	/* Set up the dummy path */
  	add_path(rel, (Path *) create_append_path(rel, NIL, NIL, NULL,
! 											  0, false, NIL, -1));
  
  	/* Set or update cheapest_total_path and related fields */
  	set_cheapest(rel);
--- 1233,1240 ----
  
  	/* Set up the dummy path */
  	add_path(rel, (Path *) create_append_path(rel, NIL, NIL, NULL,
! 											  0, false, NIL, -1),
! 			 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 8de59fc..faa654b
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
*************** create_grouping_expr_grouped_var_infos(P
*** 653,658 ****
--- 653,659 ----
  	}
  }
  
+ 
  /*
   * Check if rel->reltarget allows the relation to be grouped and initialize
   * the grouping target(s).
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
new file mode 100644
index 889e8af..351fb93
*** 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,230 ----
  			 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 9a15e5a..c1939a1
*** 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,90 ----
  		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);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 5a3efbd..8c524ac
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** inheritance_planner(PlannerInfo *root)
*** 1520,1526 ****
  									 returningLists,
  									 rowMarks,
  									 NULL,
! 									 SS_assign_special_param(root)));
  }
  
  /*--------------------
--- 1520,1527 ----
  									 returningLists,
  									 rowMarks,
  									 NULL,
! 									 SS_assign_special_param(root)),
! 			 false);
  }
  
  /*--------------------
*************** grouping_planner(PlannerInfo *root, bool
*** 2135,2141 ****
  		}
  
  		/* And shove it into final_rel */
! 		add_path(final_rel, path);
  	}
  
  	/*
--- 2136,2142 ----
  		}
  
  		/* And shove it into final_rel */
! 		add_path(final_rel, path, false);
  	}
  
  	/*
*************** create_grouping_paths(PlannerInfo *root,
*** 3698,3704 ****
  								   (List *) parse->havingQual);
  		}
  
! 		add_path(grouped_rel, path);
  
  		/* No need to consider any other alternatives. */
  		set_cheapest(grouped_rel);
--- 3699,3705 ----
  								   (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,
*** 3875,3881 ****
  														 parse->groupClause,
  														 NIL,
  														 &agg_partial_costs,
! 														 dNumPartialGroups));
  					else
  						add_partial_path(grouped_rel, (Path *)
  										 create_group_path(root,
--- 3876,3883 ----
  														 parse->groupClause,
  														 NIL,
  														 &agg_partial_costs,
! 														 dNumPartialGroups),
! 										 false);
  					else
  						add_partial_path(grouped_rel, (Path *)
  										 create_group_path(root,
*************** create_grouping_paths(PlannerInfo *root,
*** 3884,3890 ****
  														   partial_grouping_target,
  														   parse->groupClause,
  														   NIL,
! 														   dNumPartialGroups));
  				}
  			}
  		}
--- 3886,3893 ----
  														   partial_grouping_target,
  														   parse->groupClause,
  														   NIL,
! 														   dNumPartialGroups),
! 										 false);
  				}
  			}
  		}
*************** create_grouping_paths(PlannerInfo *root,
*** 3915,3921 ****
  												 parse->groupClause,
  												 NIL,
  												 &agg_partial_costs,
! 												 dNumPartialGroups));
  			}
  		}
  	}
--- 3918,3924 ----
  												 parse->groupClause,
  												 NIL,
  												 &agg_partial_costs,
! 												 dNumPartialGroups), false);
  			}
  		}
  	}
*************** create_grouping_paths(PlannerInfo *root,
*** 3967,3973 ****
  											 parse->groupClause,
  											 (List *) parse->havingQual,
  											 agg_costs,
! 											 dNumGroups));
  				}
  				else if (parse->groupClause)
  				{
--- 3970,3976 ----
  											 parse->groupClause,
  											 (List *) parse->havingQual,
  											 agg_costs,
! 											 dNumGroups), false);
  				}
  				else if (parse->groupClause)
  				{
*************** create_grouping_paths(PlannerInfo *root,
*** 3982,3988 ****
  											   target,
  											   parse->groupClause,
  											   (List *) parse->havingQual,
! 											   dNumGroups));
  				}
  				else
  				{
--- 3985,3991 ----
  											   target,
  											   parse->groupClause,
  											   (List *) parse->havingQual,
! 											   dNumGroups), false);
  				}
  				else
  				{
*************** create_grouping_paths(PlannerInfo *root,
*** 4031,4037 ****
  										 parse->groupClause,
  										 (List *) parse->havingQual,
  										 &agg_final_costs,
! 										 dNumGroups));
  			else
  				add_path(grouped_rel, (Path *)
  						 create_group_path(root,
--- 4034,4040 ----
  										 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,
*** 4040,4046 ****
  										   target,
  										   parse->groupClause,
  										   (List *) parse->havingQual,
! 										   dNumGroups));
  
  			/*
  			 * The point of using Gather Merge rather than Gather is that it
--- 4043,4049 ----
  										   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,
*** 4093,4099 ****
  												 parse->groupClause,
  												 (List *) parse->havingQual,
  												 &agg_final_costs,
! 												 dNumGroups));
  					else
  						add_path(grouped_rel, (Path *)
  								 create_group_path(root,
--- 4096,4102 ----
  												 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,
*** 4102,4108 ****
  												   target,
  												   parse->groupClause,
  												   (List *) parse->havingQual,
! 												   dNumGroups));
  				}
  			}
  		}
--- 4105,4111 ----
  												   target,
  												   parse->groupClause,
  												   (List *) parse->havingQual,
! 												   dNumGroups), false);
  				}
  			}
  		}
*************** create_grouping_paths(PlannerInfo *root,
*** 4147,4153 ****
  										 parse->groupClause,
  										 (List *) parse->havingQual,
  										 agg_costs,
! 										 dNumGroups));
  			}
  		}
  
--- 4150,4156 ----
  										 parse->groupClause,
  										 (List *) parse->havingQual,
  										 agg_costs,
! 										 dNumGroups), false);
  			}
  		}
  
*************** create_grouping_paths(PlannerInfo *root,
*** 4185,4191 ****
  										 parse->groupClause,
  										 (List *) parse->havingQual,
  										 &agg_final_costs,
! 										 dNumGroups));
  			}
  		}
  	}
--- 4188,4194 ----
  										 parse->groupClause,
  										 (List *) parse->havingQual,
  										 &agg_final_costs,
! 										 dNumGroups), false);
  			}
  		}
  	}
*************** consider_groupingsets_paths(PlannerInfo
*** 4387,4393 ****
  										  strat,
  										  new_rollups,
  										  agg_costs,
! 										  dNumGroups));
  		return;
  	}
  
--- 4390,4396 ----
  										  strat,
  										  new_rollups,
  										  agg_costs,
! 										  dNumGroups), false);
  		return;
  	}
  
*************** consider_groupingsets_paths(PlannerInfo
*** 4545,4551 ****
  											  AGG_MIXED,
  											  rollups,
  											  agg_costs,
! 											  dNumGroups));
  		}
  	}
  
--- 4548,4554 ----
  											  AGG_MIXED,
  											  rollups,
  											  agg_costs,
! 											  dNumGroups), false);
  		}
  	}
  
*************** consider_groupingsets_paths(PlannerInfo
*** 4562,4568 ****
  										  AGG_SORTED,
  										  gd->rollups,
  										  agg_costs,
! 										  dNumGroups));
  }
  
  /*
--- 4565,4571 ----
  										  AGG_SORTED,
  										  gd->rollups,
  										  agg_costs,
! 										  dNumGroups), false);
  }
  
  /*
*************** create_one_window_path(PlannerInfo *root
*** 4747,4753 ****
  								  window_pathkeys);
  	}
  
! 	add_path(window_rel, path);
  }
  
  /*
--- 4750,4756 ----
  								  window_pathkeys);
  	}
  
! 	add_path(window_rel, path, false);
  }
  
  /*
*************** create_distinct_paths(PlannerInfo *root,
*** 4853,4859 ****
  						 create_upper_unique_path(root, distinct_rel,
  												  path,
  												  list_length(root->distinct_pathkeys),
! 												  numDistinctRows));
  			}
  		}
  
--- 4856,4862 ----
  						 create_upper_unique_path(root, distinct_rel,
  												  path,
  												  list_length(root->distinct_pathkeys),
! 												  numDistinctRows), false);
  			}
  		}
  
*************** create_distinct_paths(PlannerInfo *root,
*** 4880,4886 ****
  				 create_upper_unique_path(root, distinct_rel,
  										  path,
  										  list_length(root->distinct_pathkeys),
! 										  numDistinctRows));
  	}
  
  	/*
--- 4883,4889 ----
  				 create_upper_unique_path(root, distinct_rel,
  										  path,
  										  list_length(root->distinct_pathkeys),
! 										  numDistinctRows), false);
  	}
  
  	/*
*************** create_distinct_paths(PlannerInfo *root,
*** 4927,4933 ****
  								 parse->distinctClause,
  								 NIL,
  								 NULL,
! 								 numDistinctRows));
  	}
  
  	/* Give a helpful error if we failed to find any implementation */
--- 4930,4936 ----
  								 parse->distinctClause,
  								 NIL,
  								 NULL,
! 								 numDistinctRows), false);
  	}
  
  	/* Give a helpful error if we failed to find any implementation */
*************** create_ordered_paths(PlannerInfo *root,
*** 5025,5031 ****
  				path = apply_projection_to_path(root, ordered_rel,
  												path, target);
  
! 			add_path(ordered_rel, path);
  		}
  	}
  
--- 5028,5034 ----
  				path = apply_projection_to_path(root, ordered_rel,
  												path, target);
  
! 			add_path(ordered_rel, path, false);
  		}
  	}
  
*************** create_ordered_paths(PlannerInfo *root,
*** 5076,5082 ****
  				path = apply_projection_to_path(root, ordered_rel,
  												path, target);
  
! 			add_path(ordered_rel, path);
  		}
  	}
  
--- 5079,5085 ----
  				path = apply_projection_to_path(root, ordered_rel,
  												path, target);
  
! 			add_path(ordered_rel, path, false);
  		}
  	}
  
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
new file mode 100644
index a24e8ac..eb8050f
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
*************** plan_set_operations(PlannerInfo *root)
*** 221,227 ****
  	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)
--- 221,227 ----
  	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 2aee156..a1bea53
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
*************** set_cheapest(RelOptInfo *parent_rel)
*** 419,426 ****
   * 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;
--- 419,427 ----
   * 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
*** 437,442 ****
--- 438,451 ----
  	/* 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
*** 446,452 ****
  	 * 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 */
--- 455,461 ----
  	 * 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
*** 592,599 ****
  		 */
  		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
--- 601,607 ----
  		 */
  		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
*** 624,632 ****
  	{
  		/* 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
  	{
--- 632,645 ----
  	{
  		/* 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
*** 656,663 ****
  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;
--- 669,677 ----
  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
*** 666,674 ****
  	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;
--- 680,697 ----
  	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
*** 759,781 ****
   *	  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);
--- 782,813 ----
   *	  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,
*** 829,840 ****
  		}
  
  		/*
! 		 * 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 */
  		}
--- 861,871 ----
  		}
  
  		/*
! 		 * 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,
*** 849,856 ****
  
  		/*
  		 * 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)
  			break;
--- 880,887 ----
  
  		/*
  		 * 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)
  			break;
*************** add_partial_path(RelOptInfo *parent_rel,
*** 860,869 ****
  	{
  		/* 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
  	{
--- 891,904 ----
  	{
  		/* 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,
*** 884,892 ****
   */
  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
--- 919,936 ----
   */
  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
*** 896,905 ****
  	 * 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;
--- 940,950 ----
  	 * 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
*** 928,934 ****
  	 * completion.
  	 */
  	if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! 						   NULL))
  		return false;
  
  	return true;
--- 973,979 ----
  	 * completion.
  	 */
  	if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! 						   NULL, false))
  		return false;
  
  	return true;
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
new file mode 100644
index 81c60dc..8ecb21d
*** a/src/backend/optimizer/util/var.c
--- b/src/backend/optimizer/util/var.c
*************** alias_relid_set(PlannerInfo *root, Relid
*** 840,842 ****
--- 840,864 ----
  	}
  	return result;
  }
+ 
+ /*
+  * Return GroupedVarInfo for given GroupedVar.
+  *
+  * XXX Consider better location of this routine.
+  */
+ GroupedVarInfo *
+ find_grouped_var_info(PlannerInfo *root, GroupedVar *gvar)
+ {
+ 	ListCell   *l;
+ 
+ 	foreach(l, root->grouped_var_list)
+ 	{
+ 		GroupedVarInfo *gvi = lfirst_node(GroupedVarInfo, l);
+ 
+ 		if (gvi->gvid == gvar->gvid)
+ 			return gvi;
+ 	}
+ 
+ 	elog(ERROR, "GroupedVarInfo not found");
+ 	return NULL;				/* keep compiler quiet */
+ }
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
new file mode 100644
index 08111d2..9c05492
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern int compare_path_costs(Path *path
*** 26,38 ****
  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);
--- 26,40 ----
  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);
diff --git a/src/include/optimizer/var.h b/src/include/optimizer/var.h
new file mode 100644
index 6186152..22816db
*** a/src/include/optimizer/var.h
--- b/src/include/optimizer/var.h
*************** extern bool contain_vars_of_level(Node *
*** 36,40 ****
--- 36,42 ----
  extern int	locate_var_of_level(Node *node, int levelsup);
  extern List *pull_var_clause(Node *node, int flags);
  extern Node *flatten_join_alias_vars(PlannerInfo *root, Node *node);
+ extern GroupedVarInfo *find_grouped_var_info(PlannerInfo *root,
+ 					  GroupedVar *gvar);
  
  #endif							/* VAR_H */


  agg_pushdown_v5/04_planner_prepare.diff
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 8c524ac..6371c34
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** static void standard_qp_callback(Planner
*** 131,139 ****
  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,
--- 131,136 ----
*************** static void consider_groupingsets_paths(
*** 148,153 ****
--- 145,162 ----
  							grouping_sets_data *gd,
  							const AggClauseCosts *agg_costs,
  							double dNumGroups);
+ static void sort_agg_partial_grouped_paths(PlannerInfo *root, List *pathlist,
+ 							   AggClauseCosts *agg_final_costs,
+ 							   double dNumGroups,
+ 							   RelOptInfo *grouped_rel,
+ 							   PathTarget *gather_target,
+ 							   PathTarget *group_target);
+ static void hash_agg_partial_grouped_path(PlannerInfo *root, Path *path,
+ 							  AggClauseCosts *agg_final_costs,
+ 							  double dNumGroups,
+ 							  RelOptInfo *grouped_rel,
+ 							  PathTarget *gather_target,
+ 							  PathTarget *group_target);
  static RelOptInfo *create_window_paths(PlannerInfo *root,
  					RelOptInfo *input_rel,
  					PathTarget *input_target,
*************** get_number_of_groups(PlannerInfo *root,
*** 3542,3581 ****
  }
  
  /*
-  * 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.
--- 3551,3556 ----
*************** create_grouping_paths(PlannerInfo *root,
*** 4000,4114 ****
  		 * path.  We can do this using either Gather or Gather Merge.
  		 */
  		if (grouped_rel->partial_pathlist)
! 		{
! 			Path	   *path = (Path *) linitial(grouped_rel->partial_pathlist);
! 			double		total_groups = path->rows * path->parallel_workers;
! 
! 			path = (Path *) create_gather_path(root,
! 											   grouped_rel,
! 											   path,
! 											   partial_grouping_target,
! 											   NULL,
! 											   &total_groups);
! 
! 			/*
! 			 * Since Gather's output is always unsorted, we'll need to sort,
! 			 * unless there's no GROUP BY clause or a degenerate (constant)
! 			 * one, in which case there will only be a single group.
! 			 */
! 			if (root->group_pathkeys)
! 				path = (Path *) create_sort_path(root,
! 												 grouped_rel,
! 												 path,
! 												 root->group_pathkeys,
! 												 -1.0);
! 
! 			if (parse->hasAggs)
! 				add_path(grouped_rel, (Path *)
! 						 create_agg_path(root,
! 										 grouped_rel,
! 										 path,
! 										 target,
! 										 parse->groupClause ? AGG_SORTED : AGG_PLAIN,
! 										 AGGSPLIT_FINAL_DESERIAL,
! 										 parse->groupClause,
! 										 (List *) parse->havingQual,
! 										 &agg_final_costs,
! 										 dNumGroups), false);
! 			else
! 				add_path(grouped_rel, (Path *)
! 						 create_group_path(root,
! 										   grouped_rel,
! 										   path,
! 										   target,
! 										   parse->groupClause,
! 										   (List *) parse->havingQual,
! 										   dNumGroups), false);
! 
! 			/*
! 			 * The point of using Gather Merge rather than Gather is that it
! 			 * can preserve the ordering of the input path, so there's no
! 			 * reason to try it unless (1) it's possible to produce more than
! 			 * one output row and (2) we want the output path to be ordered.
! 			 */
! 			if (parse->groupClause != NIL && root->group_pathkeys != NIL)
! 			{
! 				foreach(lc, grouped_rel->partial_pathlist)
! 				{
! 					Path	   *subpath = (Path *) lfirst(lc);
! 					Path	   *gmpath;
! 					double		total_groups;
! 
! 					/*
! 					 * It's useful to consider paths that are already properly
! 					 * ordered for Gather Merge, because those don't need a
! 					 * sort.  It's also useful to consider the cheapest path,
! 					 * because sorting it in parallel and then doing Gather
! 					 * Merge may be better than doing an unordered Gather
! 					 * followed by a sort.  But there's no point in
! 					 * considering non-cheapest paths that aren't already
! 					 * sorted correctly.
! 					 */
! 					if (path != subpath &&
! 						!pathkeys_contained_in(root->group_pathkeys,
! 											   subpath->pathkeys))
! 						continue;
! 
! 					total_groups = subpath->rows * subpath->parallel_workers;
! 
! 					gmpath = (Path *)
! 						create_gather_merge_path(root,
! 												 grouped_rel,
! 												 subpath,
! 												 partial_grouping_target,
! 												 root->group_pathkeys,
! 												 NULL,
! 												 &total_groups);
! 
! 					if (parse->hasAggs)
! 						add_path(grouped_rel, (Path *)
! 								 create_agg_path(root,
! 												 grouped_rel,
! 												 gmpath,
! 												 target,
! 												 parse->groupClause ? AGG_SORTED : AGG_PLAIN,
! 												 AGGSPLIT_FINAL_DESERIAL,
! 												 parse->groupClause,
! 												 (List *) parse->havingQual,
! 												 &agg_final_costs,
! 												 dNumGroups), false);
! 					else
! 						add_path(grouped_rel, (Path *)
! 								 create_group_path(root,
! 												   grouped_rel,
! 												   gmpath,
! 												   target,
! 												   parse->groupClause,
! 												   (List *) parse->havingQual,
! 												   dNumGroups), false);
! 				}
! 			}
! 		}
  	}
  
  	if (can_hash)
--- 3975,3985 ----
  		 * path.  We can do this using either Gather or Gather Merge.
  		 */
  		if (grouped_rel->partial_pathlist)
! 			sort_agg_partial_grouped_paths(root,
! 										   grouped_rel->partial_pathlist,
! 										   &agg_final_costs,
! 										   dNumGroups, grouped_rel,
! 										   partial_grouping_target, target);
  	}
  
  	if (can_hash)
*************** create_grouping_paths(PlannerInfo *root,
*** 4163,4195 ****
  		{
  			Path	   *path = (Path *) linitial(grouped_rel->partial_pathlist);
  
! 			hashaggtablesize = estimate_hashagg_tablesize(path,
! 														  &agg_final_costs,
! 														  dNumGroups);
! 
! 			if (hashaggtablesize < work_mem * 1024L)
! 			{
! 				double		total_groups = path->rows * path->parallel_workers;
! 
! 				path = (Path *) create_gather_path(root,
! 												   grouped_rel,
! 												   path,
! 												   partial_grouping_target,
! 												   NULL,
! 												   &total_groups);
! 
! 				add_path(grouped_rel, (Path *)
! 						 create_agg_path(root,
! 										 grouped_rel,
! 										 path,
! 										 target,
! 										 AGG_HASHED,
! 										 AGGSPLIT_FINAL_DESERIAL,
! 										 parse->groupClause,
! 										 (List *) parse->havingQual,
! 										 &agg_final_costs,
! 										 dNumGroups), false);
! 			}
  		}
  	}
  
--- 4034,4042 ----
  		{
  			Path	   *path = (Path *) linitial(grouped_rel->partial_pathlist);
  
! 			hash_agg_partial_grouped_path(root, path, &agg_final_costs,
! 										  dNumGroups, grouped_rel,
! 										  partial_grouping_target, target);
  		}
  	}
  
*************** consider_groupingsets_paths(PlannerInfo
*** 4569,4574 ****
--- 4416,4588 ----
  }
  
  /*
+  * Subroutine of create_grouping_paths() to apply GatherPath or
+  * GatherMergePath and AGG_SORTED AggPath to cases which only differ in the
+  * GatherPath / GatherMergePath target.
+  */
+ static void
+ sort_agg_partial_grouped_paths(PlannerInfo *root, List *pathlist,
+ 							   AggClauseCosts *agg_final_costs,
+ 							   double dNumGroups, RelOptInfo *grouped_rel,
+ 							   PathTarget *gather_target,
+ 							   PathTarget *group_target)
+ {
+ 	Path	   *path = (Path *) linitial(pathlist);
+ 	double		total_groups = path->rows * path->parallel_workers;
+ 	Query	   *parse = root->parse;
+ 	ListCell   *lc;
+ 
+ 	/*
+ 	 * Generate a complete GroupAgg Path atop of the cheapest partial path. We
+ 	 * can do this using either Gather or Gather Merge.
+ 	 */
+ 	path = (Path *) create_gather_path(root,
+ 									   grouped_rel,
+ 									   path,
+ 									   gather_target,
+ 									   NULL,
+ 									   &total_groups);
+ 
+ 	/*
+ 	 * Since Gather's output is always unsorted, we'll need to sort, unless
+ 	 * there's no GROUP BY clause or a degenerate (constant) one, in which
+ 	 * case there will only be a single group.
+ 	 */
+ 	if (root->group_pathkeys)
+ 		path = (Path *) create_sort_path(root,
+ 										 grouped_rel,
+ 										 path,
+ 										 root->group_pathkeys,
+ 										 -1.0);
+ 
+ 	if (parse->hasAggs)
+ 		add_path(grouped_rel, (Path *)
+ 				 create_agg_path(root,
+ 								 grouped_rel,
+ 								 path,
+ 								 group_target,
+ 								 parse->groupClause ? AGG_SORTED : AGG_PLAIN,
+ 								 AGGSPLIT_FINAL_DESERIAL,
+ 								 parse->groupClause,
+ 								 (List *) parse->havingQual,
+ 								 agg_final_costs,
+ 								 dNumGroups), false);
+ 	else
+ 		add_path(grouped_rel, (Path *)
+ 				 create_group_path(root,
+ 								   grouped_rel,
+ 								   path,
+ 								   group_target,
+ 								   parse->groupClause,
+ 								   (List *) parse->havingQual,
+ 								   dNumGroups), false);
+ 
+ 	/*
+ 	 * The point of using Gather Merge rather than Gather is that it can
+ 	 * preserve the ordering of the input path, so there's no reason to try it
+ 	 * unless (1) it's possible to produce more than one output row and (2) we
+ 	 * want the output path to be ordered.
+ 	 */
+ 	if (parse->groupClause != NIL && root->group_pathkeys != NIL)
+ 	{
+ 		foreach(lc, pathlist)
+ 		{
+ 			Path	   *subpath = (Path *) lfirst(lc);
+ 			Path	   *gmpath;
+ 			double		total_groups;
+ 
+ 			/*
+ 			 * It's useful to consider paths that are already properly ordered
+ 			 * for Gather Merge, because those don't need a sort.  It's also
+ 			 * useful to consider the cheapest path, because sorting it in
+ 			 * parallel and then doing Gather Merge may be better than doing
+ 			 * an unordered Gather followed by a sort.  But there's no point
+ 			 * in considering non-cheapest paths that aren't already sorted
+ 			 * correctly.
+ 			 */
+ 			if (path != subpath &&
+ 				!pathkeys_contained_in(root->group_pathkeys,
+ 									   subpath->pathkeys))
+ 				continue;
+ 
+ 			total_groups = subpath->rows * subpath->parallel_workers;
+ 
+ 			gmpath = (Path *)
+ 				create_gather_merge_path(root,
+ 										 grouped_rel,
+ 										 subpath,
+ 										 gather_target,
+ 										 root->group_pathkeys,
+ 										 NULL,
+ 										 &total_groups);
+ 
+ 			if (parse->hasAggs)
+ 				add_path(grouped_rel, (Path *)
+ 						 create_agg_path(root,
+ 										 grouped_rel,
+ 										 gmpath,
+ 										 group_target,
+ 										 parse->groupClause ? AGG_SORTED : AGG_PLAIN,
+ 										 AGGSPLIT_FINAL_DESERIAL,
+ 										 parse->groupClause,
+ 										 (List *) parse->havingQual,
+ 										 agg_final_costs,
+ 										 dNumGroups), false);
+ 			else
+ 				add_path(grouped_rel, (Path *)
+ 						 create_group_path(root,
+ 										   grouped_rel,
+ 										   gmpath,
+ 										   group_target,
+ 										   parse->groupClause,
+ 										   (List *) parse->havingQual,
+ 										   dNumGroups), false);
+ 		}
+ 	}
+ }
+ 
+ /*
+  * Subroutine of create_grouping_paths() to apply GatherPath and AGG_HASHED
+  * AggPath to cases which only differ in the GatherPath target.
+  */
+ static void
+ hash_agg_partial_grouped_path(PlannerInfo *root, Path *path,
+ 							  AggClauseCosts *agg_final_costs,
+ 							  double dNumGroups, RelOptInfo *grouped_rel,
+ 							  PathTarget *gather_target,
+ 							  PathTarget *group_target)
+ {
+ 	Size		hashaggtablesize = estimate_hashagg_tablesize(path, agg_final_costs,
+ 															  dNumGroups);
+ 	Query	   *parse = root->parse;
+ 	double		total_groups;
+ 
+ 	if (hashaggtablesize >= work_mem * 1024L)
+ 		return;
+ 
+ 	total_groups = path->rows * path->parallel_workers;
+ 
+ 	path = (Path *) create_gather_path(root,
+ 									   grouped_rel,
+ 									   path,
+ 									   gather_target,
+ 									   NULL,
+ 									   &total_groups);
+ 
+ 	add_path(grouped_rel, (Path *)
+ 			 create_agg_path(root,
+ 							 grouped_rel,
+ 							 path,
+ 							 group_target,
+ 							 AGG_HASHED,
+ 							 AGGSPLIT_FINAL_DESERIAL,
+ 							 parse->groupClause,
+ 							 (List *) parse->havingQual,
+ 							 agg_final_costs,
+ 							 dNumGroups), false);
+ }
+ 
+ /*
   * create_window_paths
   *
   * Build a new upperrel containing Paths for window-function evaluation.
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
new file mode 100644
index ea95b80..87dec17
*** a/src/backend/utils/adt/selfuncs.c
--- b/src/backend/utils/adt/selfuncs.c
***************
*** 114,119 ****
--- 114,120 ----
  #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 "miscadmin.h"
  #include "nodes/makefuncs.h"
*************** estimate_hash_bucket_stats(PlannerInfo *
*** 3863,3868 ****
--- 3864,3902 ----
  	ReleaseVariableStats(vardata);
  }
  
+ /*
+  * 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/utils/selfuncs.h b/src/include/utils/selfuncs.h
new file mode 100644
index 199a631..e2435b6
*** a/src/include/utils/selfuncs.h
--- b/src/include/utils/selfuncs.h
*************** extern void estimate_hash_bucket_stats(P
*** 210,215 ****
--- 210,219 ----
  						   Node *hashkey, double nbuckets,
  						   Selectivity *mcv_freq,
  						   Selectivity *bucketsize_frac);
+ 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,


  agg_pushdown_v5/05_base_rel.diff
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
new file mode 100644
index 55bb925..f09647e
*** a/src/backend/executor/execExpr.c
--- b/src/backend/executor/execExpr.c
*************** ExecInitExprRec(Expr *node, ExprState *s
*** 789,794 ****
--- 789,831 ----
  				break;
  			}
  
+ 		case T_GroupedVar:
+ 
+ 			/*
+ 			 * If GroupedVar appears in targetlist of Agg node, it can
+ 			 * represent either Aggref or grouping expression.
+ 			 */
+ 			if (state->parent && (IsA(state->parent, AggState)))
+ 			{
+ 				GroupedVar *gvar = (GroupedVar *) node;
+ 
+ 				/*
+ 				 * The only reason to execute GroupedVar is to generate either
+ 				 * aggregate transient state or grouping expression value. So
+ 				 * any contained Aggref must be partial.
+ 				 *
+ 				 * (The purpose of propagating GroupedVars to upper plan nodes
+ 				 * is just to transfer the value, no execution takes place
+ 				 * there.)
+ 				 */
+ 				if (IsA(gvar->gvexpr, Aggref))
+ 
+ 					ExecInitExprRec((Expr *) gvar->agg_partial, state,
+ 									resv, resnull);
+ 				else
+ 					ExecInitExprRec((Expr *) gvar->gvexpr, 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 da6ef1a..2dabe4c
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** find_unaggregated_cols_walker(Node *node
*** 1875,1880 ****
--- 1875,1887 ----
  		/* do not descend into aggregate exprs */
  		return false;
  	}
+ 	if (IsA(node, GroupedVar))
+ 	{
+ 		GroupedVar *gvar = (GroupedVar *) node;
+ 
+ 		if (IsA(gvar->gvexpr, Aggref))
+ 			return false;
+ 	}
  	return expression_tree_walker(node, find_unaggregated_cols_walker,
  								  (void *) colnos);
  }
diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c
new file mode 100644
index 3cf268c..e0dc8d9
*** a/src/backend/optimizer/geqo/geqo_eval.c
--- b/src/backend/optimizer/geqo/geqo_eval.c
*************** merge_clump(PlannerInfo *root, List *clu
*** 268,274 ****
  				generate_partition_wise_join_paths(root, 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);
--- 268,274 ----
  				generate_partition_wise_join_paths(root, 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 6872e14..1ccb834
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** set_rel_pathlist(PlannerInfo *root, RelO
*** 488,494 ****
  	 * 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
--- 488,497 ----
  	 * 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
*** 689,694 ****
--- 692,699 ----
  set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
  {
  	Relids		required_outer;
+ 	Path	   *seq_path;
+ 	PartialAggInfo *agg_info = NULL;
  
  	/*
  	 * We don't support pushing join clauses into the quals of a seqscan, but
*************** set_plain_rel_pathlist(PlannerInfo *root
*** 697,710 ****
  	 */
  	required_outer = rel->lateral_relids;
  
! 	/* Consider sequential scan */
! 	add_path(rel, create_seqscan_path(root, rel, required_outer, 0), false);
  
! 	/* 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 */
--- 702,742 ----
  	 */
  	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 && rel->gpi->target != NULL &&
! 		required_outer == NULL)
! 	{
! 		/*
! 		 * Retrieve the necessary info for the grouping expressions and
! 		 * aggregates.
! 		 *
! 		 * 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.
! 		 */
! 		agg_info = get_partial_agg_info(root, rel->gpi->target,
! 										rel->gpi->sortgroupclauses);
! 
! 		/*
! 		 * Only AGG_HASHED is suitable here as it does not expect the input +
! 		 * set to be sorted.
! 		 */
! 		create_grouped_path(root, rel, seq_path, false, false, AGG_HASHED,
! 							agg_info);
! 	}
! 
! 	/* 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);
  
  	/* Consider TID scans */
*************** static void
*** 719,724 ****
--- 751,757 ----
  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 *
*** 727,734 ****
  		return;
  
  	/* Add an unordered partial path based on a parallel sequential scan. */
! 	add_partial_path(rel, create_seqscan_path(root, rel, NULL, parallel_workers),
! 					 false);
  }
  
  /*
--- 760,887 ----
  		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. Only AGG_HASHED is suitable here as it does not expect
! 	 * the input set to be sorted.
! 	 */
! 	if (rel->gpi != NULL && rel->gpi->target != NULL)
! 	{
! 		PartialAggInfo *agg_info = get_partial_agg_info(root,
! 														rel->gpi->target,
! 														rel->gpi->sortgroupclauses);
! 
! 		create_grouped_path(root, rel, path, false, true, AGG_HASHED,
! 							agg_info);
! 	}
! }
! 
! /*
!  * 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 seems to be an exception ---
!  * consider implementing parameterized AGG_SORTED unless the IndexPath is
!  * partial.
!  */
! void
! create_grouped_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
! 					bool precheck, bool partial, AggStrategy aggstrategy,
! 					PartialAggInfo *agg_info)
! {
! 	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 || aggstrategy != AGG_HASHED);
! 
! 	/*
! 	 * Non-var grouping expressions will eventually require projection using
! 	 * Result plan, but that does not work with SRFs.
! 	 */
! 	Assert(rel->gpi != NULL);
! 	if (rel->gpi->group_exprs != NULL)
! 	{
! 		ListCell   *lc;
! 
! 		foreach(lc, rel->gpi->group_exprs->exprs)
! 		{
! 			GroupedVar *gvar = lfirst_node(GroupedVar, lc);
! 
! 			if (IsA(gvar->gvexpr, FuncExpr))
! 			{
! 				FuncExpr   *fexpr = (FuncExpr *) gvar->gvexpr;
! 
! 				if (fexpr->funcretset)
! 					return;
! 			}
! 		}
! 	}
! 
! 	/*
! 	 * 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,
! 														   agg_info,
! 														   subpath->rows);
! 	else if (aggstrategy == AGG_SORTED)
! 		agg_path = (Path *) create_partial_agg_sorted_path(root, subpath,
! 														   true,
! 														   agg_info,
! 														   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_append_rel_size(PlannerInfo *root, R
*** 1138,1143 ****
--- 1291,1327 ----
  			adjust_appendrel_attrs(root,
  								   (Node *) rel->joininfo,
  								   1, &appinfo);
+ 		childrel->reltarget->exprs = (List *)
+ 			adjust_appendrel_attrs(root,
+ 								   (Node *) rel->reltarget->exprs,
+ 								   1, &appinfo);
+ 
+ 		/*
+ 		 * Setup GroupedPathInfo for the child relation if the parent has
+ 		 * some.
+ 		 */
+ 		if (rel->gpi != NULL)
+ 			build_child_rel_gpi(root, childrel, rel, 1, &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
+ 		 * inner-indexscan joins on the individual children) or if the parent
+ 		 * has useful pathkeys (because we should try to build MergeAppend
+ 		 * paths that produce those sort orderings).
+ 		 */
+ 		if (rel->has_eclass_joins || has_useful_pathkeys(root, rel))
+ 			add_child_rel_equivalences(root, appinfo, rel, childrel);
+ 		childrel->has_eclass_joins = rel->has_eclass_joins;
+ 
+ 		/*
+ 		 * Note: we could compute appropriate attr_needed data for the child's
+ 		 * variables, by transforming the parent's attr_needed through the
+ 		 * translated_vars mapping.  However, currently there's no need
+ 		 * because attr_needed is only examined for base relations not
+ 		 * otherrels.  So we just leave the child's attr_needed empty.
+ 		 */
  
  		/*
  		 * If parallelism is allowable for this query in general, see whether
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1336,1341 ****
--- 1520,1527 ----
  	List	   *pa_partial_subpaths = NIL;
  	List	   *pa_nonpartial_subpaths = NIL;
  	bool		partial_subpaths_valid = true;
+ 	List	   *grouped_subpaths = NIL;
+ 	bool		grouped_subpaths_valid = true;
  	bool		pa_subpaths_valid = enable_parallel_append;
  	List	   *all_child_pathkeys = NIL;
  	List	   *all_child_outers = NIL;
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1479,1484 ****
--- 1665,1691 ----
  		}
  
  		/*
+ 		 * 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)
+ 				accumulate_append_subpath(path, &grouped_subpaths, NULL);
+ 			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
*** 1648,1653 ****
--- 1855,1871 ----
  		add_partial_path(rel, (Path *) appendpath, false);
  	}
  
+ 	if (grouped_subpaths_valid)
+ 	{
+ 		Path	   *path;
+ 
+ 		path = (Path *) create_append_path(rel, grouped_subpaths, NIL, NULL, 0,
+ 										   false, partitioned_rels, -1);
+ 		/* pathtarget will produce the grouped relation.. */
+ 		path->pathtarget = rel->gpi->target;
+ 		add_path(rel, path, true);
+ 	}
+ 
  	/*
  	 * Also build unparameterized MergeAppend paths based on the collected
  	 * list of child pathkeys.
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1699,1705 ****
  			add_path(rel, (Path *)
  					 create_append_path(rel, subpaths, NIL,
  										required_outer, 0, false,
! 										partitioned_rels, -1), false);
  	}
  }
  
--- 1917,1924 ----
  			add_path(rel, (Path *)
  					 create_append_path(rel, subpaths, NIL,
  										required_outer, 0, false,
! 										partitioned_rels, -1),
! 					 false);
  	}
  }
  
*************** set_subquery_pathlist(PlannerInfo *root,
*** 2180,2187 ****
  		/* Generate outer path using this subpath */
  		add_path(rel, (Path *)
  				 create_subqueryscan_path(root, rel, subpath,
! 										  pathkeys, required_outer),
! 				 false);
  	}
  }
  
--- 2399,2405 ----
  		/* Generate outer path using this subpath */
  		add_path(rel, (Path *)
  				 create_subqueryscan_path(root, rel, subpath,
! 										  pathkeys, required_outer), false);
  	}
  }
  
*************** set_worktable_pathlist(PlannerInfo *root
*** 2452,2465 ****
   * 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;
  
  	/*
--- 2670,2690 ----
   * 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,
*** 2467,2483 ****
  	 * 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, false);
  
  	/*
  	 * For each useful ordering, we can consider an order-preserving Gather
  	 * Merge.
  	 */
! 	foreach(lc, rel->partial_pathlist)
  	{
  		Path	   *subpath = (Path *) lfirst(lc);
  		GatherMergePath *path;
--- 2692,2714 ----
  	 * 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 && rel->gpi->target != 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,
*** 2485,2493 ****
  		if (subpath->pathkeys == NIL)
  			continue;
  
! 		path = create_gather_merge_path(root, rel, subpath, rel->reltarget,
  										subpath->pathkeys, NULL, NULL);
! 		add_path(rel, &path->path, false);
  	}
  }
  
--- 2716,2724 ----
  		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,
*** 2659,2665 ****
  			generate_partition_wise_join_paths(root, rel);
  
  			/* 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);
--- 2890,2897 ----
  			generate_partition_wise_join_paths(root, rel);
  
  			/* 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);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
new file mode 100644
index b0c7a0c..9f489c3
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
*************** bool		enable_gathermerge = true;
*** 131,136 ****
--- 131,137 ----
  bool		enable_partition_wise_join = false;
  bool		enable_parallel_append = true;
  bool		enable_parallel_hash = true;
+ bool		enable_agg_pushdown = false;
  
  typedef struct
  {
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
new file mode 100644
index faa654b..79de6cf
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
*************** add_grouping_info_to_base_rels(PlannerIn
*** 259,264 ****
--- 259,270 ----
  	int			i;
  	ListCell   *lc;
  
+ 	/*
+ 	 * Isn't user interested in the aggregate push-down feature?
+ 	 */
+ 	if (!enable_agg_pushdown)
+ 		return;
+ 
  	/* No grouping in the query? */
  	if (!root->parse->groupClause)
  		return;
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
new file mode 100644
index 351fb93..73bccee
*** a/src/backend/optimizer/plan/planagg.c
--- b/src/backend/optimizer/plan/planagg.c
*************** preprocess_minmax_aggregates(PlannerInfo
*** 223,230 ****
  			 create_minmaxagg_path(root, grouped_rel,
  								   create_pathtarget(root, tlist),
  								   aggs_list,
! 								   (List *) parse->havingQual),
! 			 false);
  }
  
  /*
--- 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 c1939a1..2bf4c8b
*** a/src/backend/optimizer/plan/planmain.c
--- b/src/backend/optimizer/plan/planmain.c
*************** query_planner(PlannerInfo *root, List *t
*** 83,90 ****
  		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);
--- 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);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 6371c34..94b1f8e
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** inheritance_planner(PlannerInfo *root)
*** 1529,1536 ****
  									 returningLists,
  									 rowMarks,
  									 NULL,
! 									 SS_assign_special_param(root)),
! 			 false);
  }
  
  /*--------------------
--- 1529,1535 ----
  									 returningLists,
  									 rowMarks,
  									 NULL,
! 									 SS_assign_special_param(root)), false);
  }
  
  /*--------------------
*************** grouping_planner(PlannerInfo *root, bool
*** 1933,1939 ****
  				newpath = (Path *) create_projection_path(root,
  														  current_rel,
  														  subpath,
! 														  scanjoin_target);
  				lfirst(lc) = newpath;
  			}
  		}
--- 1932,1939 ----
  				newpath = (Path *) create_projection_path(root,
  														  current_rel,
  														  subpath,
! 														  scanjoin_target,
! 														  false);
  				lfirst(lc) = newpath;
  			}
  		}
*************** create_grouping_paths(PlannerInfo *root,
*** 3581,3588 ****
  	Path	   *cheapest_path = input_rel->cheapest_total_path;
  	RelOptInfo *grouped_rel;
  	PathTarget *partial_grouping_target = NULL;
! 	AggClauseCosts agg_partial_costs;	/* parallel only */
! 	AggClauseCosts agg_final_costs; /* parallel only */
  	Size		hashaggtablesize;
  	double		dNumGroups;
  	double		dNumPartialGroups = 0;
--- 3581,3588 ----
  	Path	   *cheapest_path = input_rel->cheapest_total_path;
  	RelOptInfo *grouped_rel;
  	PathTarget *partial_grouping_target = NULL;
! 	AggClauseCosts agg_final_costs;
! 	bool		agg_final_costs_known = false;
  	Size		hashaggtablesize;
  	double		dNumGroups;
  	double		dNumPartialGroups = 0;
*************** create_grouping_paths(PlannerInfo *root,
*** 3767,3772 ****
--- 3767,3779 ----
  	}
  
  	/*
+ 	 * Collect statistics about aggregates for estimating costs of performing
+ 	 * aggregation in parallel.
+ 	 */
+ 	MemSet(&agg_final_costs, 0, sizeof(AggClauseCosts));
+ 	agg_final_costs_known = false;
+ 
+ 	/*
  	 * Before generating paths for grouped_rel, we first generate any possible
  	 * partial paths; that way, later code can easily consider both parallel
  	 * and non-parallel approaches to grouping.  Note that the partial paths
*************** create_grouping_paths(PlannerInfo *root,
*** 3777,3782 ****
--- 3784,3792 ----
  	if (try_parallel_aggregation)
  	{
  		Path	   *cheapest_partial_path = linitial(input_rel->partial_pathlist);
+ 		AggClauseCosts agg_partial_costs;
+ 
+ 		MemSet(&agg_partial_costs, 0, sizeof(AggClauseCosts));
  
  		/*
  		 * Build target list for partial aggregate paths.  These paths cannot
*************** create_grouping_paths(PlannerInfo *root,
*** 3792,3817 ****
  												 cheapest_partial_path->rows,
  												 gd);
  
- 		/*
- 		 * Collect statistics about aggregates for estimating costs of
- 		 * performing aggregation in parallel.
- 		 */
- 		MemSet(&agg_partial_costs, 0, sizeof(AggClauseCosts));
- 		MemSet(&agg_final_costs, 0, sizeof(AggClauseCosts));
  		if (parse->hasAggs)
  		{
  			/* partial phase */
  			get_agg_clause_costs(root, (Node *) partial_grouping_target->exprs,
  								 AGGSPLIT_INITIAL_SERIAL,
  								 &agg_partial_costs);
- 
- 			/* final phase */
- 			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);
  		}
  
  		if (can_sort)
--- 3802,3813 ----
*************** create_grouping_paths(PlannerInfo *root,
*** 3893,3899 ****
  												 parse->groupClause,
  												 NIL,
  												 &agg_partial_costs,
! 												 dNumPartialGroups), false);
  			}
  		}
  	}
--- 3889,3896 ----
  												 parse->groupClause,
  												 NIL,
  												 &agg_partial_costs,
! 												 dNumPartialGroups),
! 								 false);
  			}
  		}
  	}
*************** create_grouping_paths(PlannerInfo *root,
*** 3901,3917 ****
  	/* Build final grouping paths */
  	if (can_sort)
  	{
  		/*
  		 * Use any available suitably-sorted path as input, and also consider
  		 * sorting the cheapest-total path.
  		 */
! 		foreach(lc, input_rel->pathlist)
  		{
  			Path	   *path = (Path *) lfirst(lc);
  			bool		is_sorted;
  
  			is_sorted = pathkeys_contained_in(root->group_pathkeys,
  											  path->pathkeys);
  			if (path == cheapest_path || is_sorted)
  			{
  				/* Sort the cheapest-total path if it isn't already sorted */
--- 3898,3933 ----
  	/* Build final grouping paths */
  	if (can_sort)
  	{
+ 		List	   *pathlist = input_rel->pathlist;
+ 		int			nplain = list_length(input_rel->pathlist);
+ 		int			i;
+ 
+ 		/*
+ 		 * The grouped paths created out of grouped base relations or joins
+ 		 * can be treated almost identically here (the major difference is
+ 		 * that their targetlists contain GroupedVars instead aggregate input
+ 		 * vars, but this is handled by using the appropriate value of
+ 		 * AggSplit), so process them in the same loop.
+ 		 */
+ 		if (input_rel->gpi != NULL && input_rel->gpi->pathlist != NIL)
+ 			pathlist = list_concat(list_copy(pathlist),
+ 								   input_rel->gpi->pathlist);
+ 
  		/*
  		 * Use any available suitably-sorted path as input, and also consider
  		 * sorting the cheapest-total path.
  		 */
! 		i = 0;
! 		foreach(lc, pathlist)
  		{
  			Path	   *path = (Path *) lfirst(lc);
  			bool		is_sorted;
+ 			bool		is_grouped;
  
  			is_sorted = pathkeys_contained_in(root->group_pathkeys,
  											  path->pathkeys);
+ 			is_grouped = i >= nplain;
+ 
  			if (path == cheapest_path || is_sorted)
  			{
  				/* Sort the cheapest-total path if it isn't already sorted */
*************** create_grouping_paths(PlannerInfo *root,
*** 3925,3936 ****
--- 3941,3979 ----
  				/* Now decide what to stick atop it */
  				if (parse->groupingSets)
  				{
+ 					/*
+ 					 * No grouping should have taken place at base relation /
+ 					 * join level, i.e. pathlist should be equal to
+ 					 * input_rel->pathlist.
+ 					 */
+ 					Assert(!is_grouped);
+ 
  					consider_groupingsets_paths(root, grouped_rel,
  												path, true, can_hash, target,
  												gd, agg_costs, dNumGroups);
  				}
  				else if (parse->hasAggs)
  				{
+ 					AggStrategy aggstrategy;
+ 					AggSplit	aggsplit;
+ 
+ 					if (!is_grouped)
+ 					{
+ 						aggstrategy = parse->groupClause ? AGG_SORTED : AGG_PLAIN;
+ 						aggsplit = AGGSPLIT_SIMPLE;
+ 					}
+ 					else
+ 					{
+ 						/*
+ 						 * Like above, no grouping of base relation is not
+ 						 * possible w/o this.
+ 						 */
+ 						Assert(parse->groupClause);
+ 
+ 						aggstrategy = AGG_SORTED;
+ 						aggsplit = AGGSPLIT_FINAL_DESERIAL;
+ 					}
+ 
  					/*
  					 * We have aggregation, possibly with plain GROUP BY. Make
  					 * an AggPath.
*************** create_grouping_paths(PlannerInfo *root,
*** 3940,3947 ****
  											 grouped_rel,
  											 path,
  											 target,
! 											 parse->groupClause ? AGG_SORTED : AGG_PLAIN,
! 											 AGGSPLIT_SIMPLE,
  											 parse->groupClause,
  											 (List *) parse->havingQual,
  											 agg_costs,
--- 3983,3990 ----
  											 grouped_rel,
  											 path,
  											 target,
! 											 aggstrategy,
! 											 aggsplit,
  											 parse->groupClause,
  											 (List *) parse->havingQual,
  											 agg_costs,
*************** create_grouping_paths(PlannerInfo *root,
*** 3968,3978 ****
  					Assert(false);
  				}
  			}
  		}
  
  		/*
! 		 * Now generate a complete GroupAgg Path atop of the cheapest partial
! 		 * path.  We can do this using either Gather or Gather Merge.
  		 */
  		if (grouped_rel->partial_pathlist)
  			sort_agg_partial_grouped_paths(root,
--- 4011,4040 ----
  					Assert(false);
  				}
  			}
+ 
+ 			i++;
  		}
  
  		/*
! 		 * Compute agg_final_costs iff the aggregation should take place in 2
! 		 * steps.
! 		 */
! 		if (parse->hasAggs &&
! 			(grouped_rel->partial_pathlist ||
! 			 (input_rel->gpi != NULL && input_rel->gpi->partial_pathlist)))
! 		{
! 			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);
! 
! 			agg_final_costs_known = true;
! 		}
! 
! 		/*
! 		 * Gather grouped partial paths and apply AGG_SORTED to them.
  		 */
  		if (grouped_rel->partial_pathlist)
  			sort_agg_partial_grouped_paths(root,
*************** create_grouping_paths(PlannerInfo *root,
*** 3980,3985 ****
--- 4042,4060 ----
  										   &agg_final_costs,
  										   dNumGroups, grouped_rel,
  										   partial_grouping_target, target);
+ 
+ 		/*
+ 		 * The same for the grouped partial paths involving base relation /
+ 		 * join grouping.
+ 		 */
+ 		if (input_rel->gpi != NULL && input_rel->gpi->target != NULL &&
+ 			input_rel->gpi->partial_pathlist)
+ 			sort_agg_partial_grouped_paths(root,
+ 										   input_rel->gpi->partial_pathlist,
+ 										   &agg_final_costs, dNumGroups,
+ 										   grouped_rel,
+ 										   input_rel->gpi->target,
+ 										   target);
  	}
  
  	if (can_hash)
*************** create_grouping_paths(PlannerInfo *root,
*** 4026,4031 ****
--- 4101,4122 ----
  		}
  
  		/*
+ 		 * Compute agg_final_costs if needed below and if not done above.
+ 		 */
+ 		if (parse->hasAggs && !agg_final_costs_known &&
+ 			(grouped_rel->partial_pathlist ||
+ 			 (input_rel->gpi != NULL &&
+ 			  (input_rel->gpi->pathlist || input_rel->gpi->partial_pathlist))))
+ 		{
+ 			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);
+ 		}
+ 
+ 		/*
  		 * Generate a HashAgg Path atop of the cheapest partial path. Once
  		 * again, we'll only do this if it looks as though the hash table
  		 * won't exceed work_mem.
*************** create_grouping_paths(PlannerInfo *root,
*** 4038,4043 ****
--- 4129,4181 ----
  										  dNumGroups, grouped_rel,
  										  partial_grouping_target, target);
  		}
+ 
+ 		/*
+ 		 * If input_rel has partially aggregated paths, perform the final
+ 		 * aggregation.
+ 		 */
+ 		if (input_rel->gpi != NULL && input_rel->gpi->pathlist != NIL)
+ 		{
+ 			Path	   *path = (Path *) linitial(input_rel->gpi->pathlist);
+ 
+ 			hashaggtablesize = estimate_hashagg_tablesize(path,
+ 														  &agg_final_costs,
+ 														  dNumGroups);
+ 
+ 			if (hashaggtablesize < work_mem * 1024L)
+ 			{
+ 				/*
+ 				 * The top-level grouped_rel needs to receive the path into
+ 				 * regular pathlist, as opposed grouped_rel->gpi->pathlist. So
+ 				 * pass FALSE for grouped.
+ 				 */
+ 				add_path(grouped_rel,
+ 						 (Path *) create_agg_path(root, grouped_rel,
+ 												  path,
+ 												  target,
+ 												  AGG_HASHED,
+ 												  AGGSPLIT_FINAL_DESERIAL,
+ 												  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.
+ 		 */
+ 		if (input_rel->gpi != NULL && input_rel->gpi->target != NULL &&
+ 			input_rel->gpi->partial_pathlist != NIL)
+ 		{
+ 			Path	   *path = (Path *) linitial(input_rel->gpi->partial_pathlist);
+ 
+ 			hash_agg_partial_grouped_path(root, path, &agg_final_costs, dNumGroups,
+ 										  grouped_rel, input_rel->gpi->target,
+ 										  target);
+ 		}
  	}
  
  	/* Give a helpful error if we failed to find any implementation */
*************** create_distinct_paths(PlannerInfo *root,
*** 4870,4876 ****
  						 create_upper_unique_path(root, distinct_rel,
  												  path,
  												  list_length(root->distinct_pathkeys),
! 												  numDistinctRows), false);
  			}
  		}
  
--- 5008,5015 ----
  						 create_upper_unique_path(root, distinct_rel,
  												  path,
  												  list_length(root->distinct_pathkeys),
! 												  numDistinctRows),
! 						 false);
  			}
  		}
  
*************** create_distinct_paths(PlannerInfo *root,
*** 4897,4903 ****
  				 create_upper_unique_path(root, distinct_rel,
  										  path,
  										  list_length(root->distinct_pathkeys),
! 										  numDistinctRows), false);
  	}
  
  	/*
--- 5036,5043 ----
  				 create_upper_unique_path(root, distinct_rel,
  										  path,
  										  list_length(root->distinct_pathkeys),
! 										  numDistinctRows),
! 				 false);
  	}
  
  	/*
*************** adjust_paths_for_srfs(PlannerInfo *root,
*** 6011,6017 ****
  				newpath = (Path *) create_projection_path(root,
  														  rel,
  														  newpath,
! 														  thistarget);
  			}
  		}
  		lfirst(lc) = newpath;
--- 6151,6158 ----
  				newpath = (Path *) create_projection_path(root,
  														  rel,
  														  newpath,
! 														  thistarget,
! 														  false);
  			}
  		}
  		lfirst(lc) = newpath;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
new file mode 100644
index b5c4124..b5be81c
*** 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? */
  	bool		has_conv_whole_rows;	/* are there ConvertRowtypeExpr
  										 * entries encapsulating a whole-row
*************** set_upper_references(PlannerInfo *root,
*** 1739,1747 ****
--- 1740,1802 ----
  	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 current
+ 				 * subplan->targetlist.
+ 				 */
+ 				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 =
+ 					replace_grouped_vars_with_aggrefs(root, subplan->targetlist);
+ 			}
+ 			else if (agg->aggsplit == AGGSPLIT_INITIAL_SERIAL)
+ 			{
+ 				/*
+ 				 * Partial aggregation node can have GroupedVar's on the input
+ 				 * if those represent generic (non-Var) grouping expressions.
+ 				 * Unlike above, the restored expressions should stay there.
+ 				 */
+ 				subplan->targetlist =
+ 					replace_grouped_vars_with_aggrefs(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)
*** 1996,2001 ****
--- 2051,2057 ----
  
  	itlist->tlist = tlist;
  	itlist->has_ph_vars = false;
+ 	itlist->has_grp_vars = false;
  	itlist->has_non_vars = false;
  	itlist->has_conv_whole_rows = false;
  
*************** build_tlist_index(List *tlist)
*** 2016,2021 ****
--- 2072,2079 ----
  		}
  		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 if (is_converted_whole_row_reference((Node *) tle->expr))
  			itlist->has_conv_whole_rows = true;
  		else
*************** fix_join_expr_mutator(Node *node, fix_jo
*** 2299,2304 ****
--- 2357,2387 ----
  		/* 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
*** 2461,2467 ****
  		/* 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 ||
  		(context->subplan_itlist->has_conv_whole_rows &&
  		 is_converted_whole_row_reference(node)))
  	{
--- 2544,2551 ----
  		/* 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 ||
  		(context->subplan_itlist->has_conv_whole_rows &&
  		 is_converted_whole_row_reference(node)))
  	{
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
new file mode 100644
index a1bea53..0aaac38
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
***************
*** 26,31 ****
--- 26,32 ----
  #include "optimizer/planmain.h"
  #include "optimizer/prep.h"
  #include "optimizer/restrictinfo.h"
+ /* TODO Remove this if create_grouped_path ends up in another module. */
  #include "optimizer/tlist.h"
  #include "optimizer/var.h"
  #include "parser/parsetree.h"
*************** static int	append_startup_cost_compare(c
*** 56,62 ****
  static List *reparameterize_pathlist_by_child(PlannerInfo *root,
  								 List *pathlist,
  								 RelOptInfo *child_rel);
! 
  
  /*****************************************************************************
   *		MISC. PATH UTILITIES
--- 57,65 ----
  static List *reparameterize_pathlist_by_child(PlannerInfo *root,
  								 List *pathlist,
  								 RelOptInfo *child_rel);
! static Path *add_grouping_expressions_to_subpath(PlannerInfo *root,
! 									Path *subpath,
! 									PathTarget *group_exprs);
  
  /*****************************************************************************
   *		MISC. PATH UTILITIES
*************** add_partial_path_precheck(RelOptInfo *pa
*** 973,979 ****
  	 * completion.
  	 */
  	if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! 						   NULL, false))
  		return false;
  
  	return true;
--- 976,982 ----
  	 * completion.
  	 */
  	if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! 						   NULL, grouped))
  		return false;
  
  	return true;
*************** create_unique_path(PlannerInfo *root, Re
*** 1543,1550 ****
  	MemoryContext oldcontext;
  	int			numCols;
  
! 	/* Caller made a mistake if subpath isn't cheapest_total ... */
! 	Assert(subpath == rel->cheapest_total_path);
  	Assert(subpath->parent == rel);
  	/* ... or if SpecialJoinInfo is the wrong one */
  	Assert(sjinfo->jointype == JOIN_SEMI);
--- 1546,1557 ----
  	MemoryContext oldcontext;
  	int			numCols;
  
! 	/*
! 	 * Caller made a mistake if subpath isn't cheapest_total (or the cheapest
! 	 * grouped).
! 	 */
! 	Assert(subpath == rel->cheapest_total_path ||
! 		   (rel->gpi != NULL && subpath == linitial(rel->gpi->pathlist)));
  	Assert(subpath->parent == rel);
  	/* ... or if SpecialJoinInfo is the wrong one */
  	Assert(sjinfo->jointype == JOIN_SEMI);
*************** create_hashjoin_path(PlannerInfo *root,
*** 2394,2405 ****
   * 'rel' is the parent relation associated with the result
   * 'subpath' is the path representing the source of data
   * 'target' is the PathTarget to be computed
   */
  ProjectionPath *
  create_projection_path(PlannerInfo *root,
  					   RelOptInfo *rel,
  					   Path *subpath,
! 					   PathTarget *target)
  {
  	ProjectionPath *pathnode = makeNode(ProjectionPath);
  	PathTarget *oldtarget = subpath->pathtarget;
--- 2401,2414 ----
   * 'rel' is the parent relation associated with the result
   * 'subpath' is the path representing the source of data
   * 'target' is the PathTarget to be computed
+  * 'force_result' enforces implementation using Result plan.
   */
  ProjectionPath *
  create_projection_path(PlannerInfo *root,
  					   RelOptInfo *rel,
  					   Path *subpath,
! 					   PathTarget *target,
! 					   bool force_result)
  {
  	ProjectionPath *pathnode = makeNode(ProjectionPath);
  	PathTarget *oldtarget = subpath->pathtarget;
*************** create_projection_path(PlannerInfo *root
*** 2418,2423 ****
--- 2427,2433 ----
  	pathnode->path.pathkeys = subpath->pathkeys;
  
  	pathnode->subpath = subpath;
+ 	pathnode->force_result = force_result;
  
  	/*
  	 * We might not need a separate Result node.  If the input plan node type
*************** create_projection_path(PlannerInfo *root
*** 2428,2435 ****
  	 * Note: in the latter case, create_projection_plan has to recheck our
  	 * conclusion; see comments therein.
  	 */
! 	if (is_projection_capable_path(subpath) ||
! 		equal(oldtarget->exprs, target->exprs))
  	{
  		/* No separate Result node needed */
  		pathnode->dummypp = true;
--- 2438,2446 ----
  	 * Note: in the latter case, create_projection_plan has to recheck our
  	 * conclusion; see comments therein.
  	 */
! 	if (!force_result &&
! 		(is_projection_capable_path(subpath) ||
! 		 equal(oldtarget->exprs, target->exprs)))
  	{
  		/* No separate Result node needed */
  		pathnode->dummypp = true;
*************** apply_projection_to_path(PlannerInfo *ro
*** 2499,2505 ****
  	 * separate ProjectionPath.
  	 */
  	if (!is_projection_capable_path(path))
! 		return (Path *) create_projection_path(root, rel, path, target);
  
  	/*
  	 * We can just jam the desired tlist into the existing path, being sure to
--- 2510,2517 ----
  	 * separate ProjectionPath.
  	 */
  	if (!is_projection_capable_path(path))
! 		return (Path *) create_projection_path(root, rel, path, target,
! 											   false);
  
  	/*
  	 * We can just jam the desired tlist into the existing path, being sure to
*************** apply_projection_to_path(PlannerInfo *ro
*** 2539,2545 ****
  				create_projection_path(root,
  									   gpath->subpath->parent,
  									   gpath->subpath,
! 									   target);
  		}
  		else
  		{
--- 2551,2558 ----
  				create_projection_path(root,
  									   gpath->subpath->parent,
  									   gpath->subpath,
! 									   target,
! 									   false);
  		}
  		else
  		{
*************** apply_projection_to_path(PlannerInfo *ro
*** 2549,2555 ****
  				create_projection_path(root,
  									   gmpath->subpath->parent,
  									   gmpath->subpath,
! 									   target);
  		}
  	}
  	else if (path->parallel_safe &&
--- 2562,2569 ----
  				create_projection_path(root,
  									   gmpath->subpath->parent,
  									   gmpath->subpath,
! 									   target,
! 									   false);
  		}
  	}
  	else if (path->parallel_safe &&
*************** create_agg_path(PlannerInfo *root,
*** 2848,2853 ****
--- 2862,3123 ----
  }
  
  /*
+  * get_partial_agg_data
+  *
+  *		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.
+  *
+  *      target_group_clauses can contain a list of target-specific
+  *	    SortGroupClause's, which correspond to grouping expressions possibly
+  *	    added to the target by initialize_grouped_target. These are not
+  *	    necessarily present in root->query->groupClause.
+  */
+ PartialAggInfo *
+ get_partial_agg_info(PlannerInfo *root, PathTarget *target,
+ 					 List *target_group_clauses)
+ {
+ 	ListCell   *l;
+ 	int			i = 0;
+ 	PartialAggInfo *result;
+ 
+ 	/* The target should contain at least one grouping column. */
+ 	Assert(target->sortgrouprefs != NULL);
+ 
+ 	result = palloc0(sizeof(PartialAggInfo));
+ 
+ 	foreach(l, target->exprs)
+ 	{
+ 		Index		sortgroupref = 0;
+ 		SortGroupClause *cl;
+ 		Expr	   *texpr;
+ 
+ 		texpr = (Expr *) lfirst(l);
+ 
+ 		if (IsA(texpr, GroupedVar) &&
+ 			IsA(((GroupedVar *) texpr)->gvexpr, Aggref))
+ 		{
+ 			/*
+ 			 * texpr should represent the first aggregate in the targetlist.
+ 			 */
+ 			break;
+ 		}
+ 
+ 		/*
+ 		 * Find the clause by sortgroupref.
+ 		 */
+ 		sortgroupref = target->sortgrouprefs[i++];
+ 
+ 		/*
+ 		 * Besides being an aggregate, the target expression should have no
+ 		 * other reason then being a column of a relation functionally
+ 		 * dependent on the GROUP BY clause. So it's not actually a grouping
+ 		 * column.
+ 		 */
+ 		if (sortgroupref == 0)
+ 			continue;
+ 
+ 		cl = get_sortgroupref_clause_noerr(sortgroupref, root->parse->groupClause);
+ 
+ 		/*
+ 		 * If query does not have this clause, it must be target-specific.
+ 		 */
+ 		if (cl == NULL)
+ 			cl = get_sortgroupref_clause(sortgroupref, target_group_clauses);
+ 
+ 		result->group_clauses = list_append_unique(result->group_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?
+ 		 */
+ 		result->group_exprs = list_append_unique(result->group_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);
+ 		result->agg_exprs = lappend(result->agg_exprs, gvar->agg_partial);
+ 		l = lnext(l);
+ 	}
+ 
+ 	return result;
+ }
+ 
+ /*
+  * Apply partial AGG_SORTED aggregation path to subpath if it's suitably
+  * sorted.
+  *
+  * check_pathkeys can be passed FALSE if the function was already called for
+  * given index --- since the target should not change, we can skip the check
+  * of sorting during subsequent calls.
+  *
+  * agg_info contains both aggregate and grouping expressions.
+  *
+  * NULL is returned if sorting of subpath output is not suitable.
+  */
+ AggPath *
+ create_partial_agg_sorted_path(PlannerInfo *root, Path *subpath,
+ 							   bool check_pathkeys,
+ 							   PartialAggInfo *agg_info,
+ 							   double input_rows)
+ {
+ 	RelOptInfo *rel;
+ 	AggClauseCosts agg_costs;
+ 	double		dNumGroups;
+ 	AggPath    *result = NULL;
+ 
+ 	rel = subpath->parent;
+ 	Assert(rel->gpi != NULL);
+ 	Assert(rel->gpi->target != NULL);
+ 
+ 	if (subpath->pathkeys == NIL)
+ 		return NULL;
+ 
+ 	if (!grouping_is_sortable(root->parse->groupClause))
+ 		return NULL;
+ 
+ 	/*
+ 	 * Add generic grouping expressions to the subpath if there are some, and
+ 	 * adjust the costs accordingly.
+ 	 */
+ 	if (rel->gpi->group_exprs != NULL)
+ 		subpath = add_grouping_expressions_to_subpath(root, subpath,
+ 													  rel->gpi->group_exprs);
+ 
+ 	if (check_pathkeys)
+ 	{
+ 		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;
+ 	}
+ 
+ 	MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
+ 	Assert(agg_info->agg_exprs != NIL);
+ 	get_agg_clause_costs(root, (Node *) agg_info->agg_exprs,
+ 						 AGGSPLIT_INITIAL_SERIAL, &agg_costs);
+ 
+ 	Assert(agg_info->group_exprs != NIL);
+ 	dNumGroups = estimate_num_groups(root, agg_info->group_exprs,
+ 									 input_rows, NULL);
+ 
+ 	Assert(agg_info->group_clauses != NIL);
+ 	result = create_agg_path(root, rel, subpath, rel->gpi->target,
+ 							 AGG_SORTED, AGGSPLIT_INITIAL_SERIAL,
+ 							 agg_info->group_clauses, NIL, &agg_costs, dNumGroups);
+ 
+ 	return result;
+ }
+ 
+ /*
+  * Apply 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,
+ 							   PartialAggInfo *agg_info, 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);
+ 	Assert(rel->gpi->target != NULL);
+ 
+ 	/*
+ 	 * Add generic grouping expressions to the subpath if there are some, and
+ 	 * adjust the costs accordingly.
+ 	 */
+ 	if (rel->gpi->group_exprs != NULL)
+ 		subpath = add_grouping_expressions_to_subpath(root, subpath,
+ 													  rel->gpi->group_exprs);
+ 
+ 	MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
+ 	Assert(agg_info->agg_exprs != NIL);
+ 	get_agg_clause_costs(root, (Node *) agg_info->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(agg_info->group_exprs != NIL);
+ 		dNumGroups = estimate_num_groups(root, agg_info->group_exprs,
+ 										 input_rows, NULL);
+ 
+ 		hashaggtablesize = estimate_hashagg_tablesize(subpath, &agg_costs,
+ 													  dNumGroups);
+ 
+ 		if (hashaggtablesize < work_mem * 1024L)
+ 		{
+ 			/*
+ 			 * Create the partial aggregation path.
+ 			 */
+ 			Assert(agg_info->group_clauses != NIL);
+ 
+ 			result = create_agg_path(root, rel, subpath,
+ 									 rel->gpi->target,
+ 									 AGG_HASHED,
+ 									 AGGSPLIT_INITIAL_SERIAL,
+ 									 agg_info->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
   *
*************** reparameterize_pathlist_by_child(Planner
*** 3936,3938 ****
--- 4206,4261 ----
  
  	return result;
  }
+ 
+ /*
+  * Add (non-Var) grouping expressions contained in group_exprs target to a
+  * subpath. The passed in subpath should not be touched so that it can still
+  * be used as a subpath for non-grouped paths. Therefore we wrap it into
+  * ResultPath, to which we actually add those expressions.
+  *
+  * Costs of the grouping expression evaluation is added to the ResultPath
+  * because the evaluation will take place here, rather than at the AggPath
+  * we're preparing data for.
+  */
+ static Path *
+ add_grouping_expressions_to_subpath(PlannerInfo *root, Path *subpath,
+ 									PathTarget *group_exprs)
+ {
+ 	PathTarget *target;
+ 	ListCell   *lc;
+ 	int			i;
+ 
+ 	/*
+ 	 * The subpath must stay intact because, besides producing input data for
+ 	 * aggregation, it can participate in a plan that does not use aggregation
+ 	 * push-down. For the same reason we pass force_result=true to
+ 	 * create_projection_path below.
+ 	 */
+ 	target = copy_pathtarget(subpath->pathtarget);
+ 
+ 	Assert(group_exprs->sortgrouprefs != NULL);
+ 	i = 0;
+ 	foreach(lc, group_exprs->exprs)
+ 	{
+ 		Index		sortgroupref;
+ 		GroupedVar *gvar;
+ 		QualCost	cost;
+ 
+ 		sortgroupref = group_exprs->sortgrouprefs[i++];
+ 		gvar = lfirst_node(GroupedVar, lc);
+ 		add_column_to_pathtarget(target, (Expr *) gvar, sortgroupref);
+ 
+ 		/*
+ 		 * Account for the cost to evaluate the grouping expressions.
+ 		 */
+ 		cost_qual_eval_node(&cost, (Node *) gvar->gvexpr, root);
+ 		target->cost.startup += cost.startup;
+ 		target->cost.per_tuple += cost.per_tuple;
+ 	}
+ 
+ 	return (Path *) create_projection_path(root,
+ 										   subpath->parent,
+ 										   subpath,
+ 										   target,
+ 										   true);
+ }
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
new file mode 100644
index 08686f6..e4c2313
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
***************
*** 27,32 ****
--- 27,33 ----
  #include "optimizer/prep.h"
  #include "optimizer/restrictinfo.h"
  #include "optimizer/tlist.h"
+ #include "optimizer/var.h"
  #include "parser/parse_oper.h"
  #include "utils/hsearch.h"
  
*************** build_simple_rel(PlannerInfo *root, int
*** 126,131 ****
--- 127,133 ----
  	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_child_join_rel(PlannerInfo *root,
*** 797,802 ****
--- 799,857 ----
  }
  
  /*
+  * Initialize GroupedPathInfo of a child relation according to that of the
+  * parent.
+  */
+ void
+ build_child_rel_gpi(PlannerInfo *root, RelOptInfo *child, RelOptInfo *parent,
+ 					int nappinfos, AppendRelInfo **appinfos)
+ {
+ 	GroupedPathInfo *gpi,
+ 			   *gpi_parent;
+ 
+ 	Assert(child->gpi == NULL);
+ 
+ 	gpi_parent = parent->gpi;
+ 	child->gpi = gpi = makeNode(GroupedPathInfo);
+ 
+ 	/*
+ 	 * Create grouping target translated to the child varnos.
+ 	 *
+ 	 * We need a copy of the target so that the translation does not affect
+ 	 * the parent.
+ 	 */
+ 	gpi->target = copy_pathtarget(gpi_parent->target);
+ 	gpi->target->exprs = (List *)
+ 		adjust_appendrel_attrs(root,
+ 							   (Node *) gpi->target->exprs,
+ 							   nappinfos, appinfos);
+ 
+ 	/*
+ 	 * Non-var grouping expressions need to be translated as well.
+ 	 */
+ 	if (gpi_parent->group_exprs != NULL)
+ 	{
+ 		gpi->group_exprs = copy_pathtarget(gpi_parent->group_exprs);
+ 		gpi->group_exprs->exprs = (List *)
+ 			adjust_appendrel_attrs(root,
+ 								   (Node *) gpi->group_exprs->exprs,
+ 								   nappinfos, appinfos);
+ 	}
+ 
+ 	/*
+ 	 * sortgroupclauses need no kind of translation, so just copy the pointer.
+ 	 */
+ 	gpi->sortgroupclauses = gpi_parent->sortgroupclauses;
+ 
+ 	/*
+ 	 * If the output of the child rel's paths should be aggregated,
+ 	 * sortgrouprefs are also needed. (initialize_grouped_targets should have
+ 	 * initialized sortgrouprefs of the parent rel.)
+ 	 */
+ 	child->reltarget->sortgrouprefs = parent->reltarget->sortgrouprefs;
+ }
+ 
+ /*
   * min_join_parameterization
   *
   * Determine the minimum possible parameterization of a joinrel, that is, the
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
new file mode 100644
index abdc5d9..b9c2d13
*** a/src/backend/optimizer/util/tlist.c
--- b/src/backend/optimizer/util/tlist.c
*************** get_sortgrouplist_exprs(List *sgClauses,
*** 408,414 ****
  	return result;
  }
  
- 
  /*****************************************************************************
   *		Functions to extract data from a list of SortGroupClauses
   *
--- 408,413 ----
*************** apply_pathtarget_labeling_to_tlist(List
*** 783,788 ****
--- 782,841 ----
  }
  
  /*
+  * Replace each GroupedVar in the source targetlist with the original
+  * expression --- either Aggref or a non-Var grouping expression.
+  *
+  * Even if the query targetlist has the Aggref wrapped in a generic
+  * expression, any subplan should emit the corresponding GroupedVar
+  * alone. (Aggregate finalization is needed before the aggregate result can be
+  * used for any purposes and that happens at the top level of the query.)
+  * Therefore we do not have to recurse into the target expressions here.
+  */
+ List *
+ replace_grouped_vars_with_aggrefs(PlannerInfo *root, List *src)
+ {
+ 	List	   *result = NIL;
+ 	ListCell   *l;
+ 
+ 	foreach(l, src)
+ 	{
+ 		TargetEntry *te,
+ 				   *te_new;
+ 		Expr	   *expr_new = NULL;
+ 
+ 		te = lfirst_node(TargetEntry, l);
+ 
+ 		if (IsA(te->expr, GroupedVar))
+ 		{
+ 			GroupedVar *gvar;
+ 
+ 			gvar = castNode(GroupedVar, te->expr);
+ 			if (IsA(gvar->gvexpr, Aggref))
+ 			{
+ 				/*
+ 				 * Partial aggregate should appear in the targetlist so that
+ 				 * it looks as if convert_combining_aggrefs arranged it.
+ 				 */
+ 				expr_new = (Expr *) gvar->agg_partial;
+ 			}
+ 			else
+ 				expr_new = gvar->gvexpr;
+ 		}
+ 
+ 		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 the grouped target.
   *
   * Caller passes the aggregates in the form of GroupedVarInfos so that we
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
new file mode 100644
index 8514c21..ad8a0b9
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** get_rule_expr(Node *node, deparse_contex
*** 7647,7652 ****
--- 7647,7669 ----
  			get_agg_expr((Aggref *) node, context, (Aggref *) node);
  			break;
  
+ 		case T_GroupedVar:
+ 			{
+ 				GroupedVar *gvar = castNode(GroupedVar, node);
+ 				Expr	   *expr = gvar->gvexpr;
+ 
+ 				if (IsA(expr, Aggref))
+ 					get_agg_expr(gvar->agg_partial, context, (Aggref *) gvar->gvexpr);
+ 				else if (IsA(expr, Var))
+ 					(void) get_variable((Var *) expr, 0, false, context);
+ 				else
+ 				{
+ 					Assert(IsA(gvar->gvexpr, OpExpr));
+ 					get_oper_expr((OpExpr *) expr, context);
+ 				}
+ 				break;
+ 			}
+ 
  		case T_GroupingFunc:
  			{
  				GroupingFunc *gexpr = (GroupingFunc *) node;
*************** get_agg_combine_expr(Node *node, deparse
*** 9132,9141 ****
  	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);
  }
  
--- 9149,9166 ----
  	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/misc/guc.c b/src/backend/utils/misc/guc.c
new file mode 100644
index e32901d..90703c6
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
*************** static struct config_bool ConfigureNames
*** 921,926 ****
--- 921,935 ----
  		NULL, NULL, NULL
  	},
  	{
+ 		{"enable_agg_pushdown", PGC_USERSET, QUERY_TUNING_METHOD,
+ 			gettext_noop("Enables aggregation push-down."),
+ 			NULL
+ 		},
+ 		&enable_agg_pushdown,
+ 		false,
+ 		NULL, NULL, NULL
+ 	},
+ 	{
  		{"enable_parallel_append", PGC_USERSET, QUERY_TUNING_METHOD,
  			gettext_noop("Enables the planner's use of parallel append plans."),
  			NULL
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
new file mode 100644
index 1c93688..00e73ff
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct HashPath
*** 1537,1548 ****
--- 1537,1552 ----
   * ProjectionPath node, which is marked dummy to indicate that we intend to
   * assign the work to the input plan node.  The estimated cost for the
   * ProjectionPath node will account for whether a Result will be used or not.
+  *
+  * force_result field tells that the Result node must be used for some reason
+  * even though the subpath could normally handle the projection.
   */
  typedef struct ProjectionPath
  {
  	Path		path;
  	Path	   *subpath;		/* path representing input source */
  	bool		dummypp;		/* true if no separate Result is needed */
+ 	bool		force_result;	/* Is Result node required? */
  } ProjectionPath;
  
  /*
*************** typedef struct AggPath
*** 1618,1623 ****
--- 1622,1638 ----
  } AggPath;
  
  /*
+  * Information needed to create grouped paths out of base relation scan or
+  * join.
+  */
+ typedef struct PartialAggInfo
+ {
+ 	List	   *group_clauses;
+ 	List	   *group_exprs;
+ 	List	   *agg_exprs;
+ } PartialAggInfo;
+ 
+ /*
   * Various annotations used for grouping sets in the planner.
   */
  
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
new file mode 100644
index 27afc2e..12d8134
*** a/src/include/optimizer/cost.h
--- b/src/include/optimizer/cost.h
*************** extern bool enable_mergejoin;
*** 68,73 ****
--- 68,74 ----
  extern bool enable_hashjoin;
  extern bool enable_gathermerge;
  extern bool enable_partition_wise_join;
+ extern bool enable_agg_pushdown;
  extern bool enable_parallel_append;
  extern bool enable_parallel_hash;
  extern int	constraint_exclusion;
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
new file mode 100644
index 9c05492..b11bd47
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern HashPath *create_hashjoin_path(Pl
*** 163,169 ****
  extern ProjectionPath *create_projection_path(PlannerInfo *root,
  					   RelOptInfo *rel,
  					   Path *subpath,
! 					   PathTarget *target);
  extern Path *apply_projection_to_path(PlannerInfo *root,
  						 RelOptInfo *rel,
  						 Path *path,
--- 163,170 ----
  extern ProjectionPath *create_projection_path(PlannerInfo *root,
  					   RelOptInfo *rel,
  					   Path *subpath,
! 					   PathTarget *target,
! 					   bool force_result);
  extern Path *apply_projection_to_path(PlannerInfo *root,
  						 RelOptInfo *rel,
  						 Path *path,
*************** extern AggPath *create_agg_path(PlannerI
*** 199,204 ****
--- 200,217 ----
  				List *qual,
  				const AggClauseCosts *aggcosts,
  				double numGroups);
+ extern PartialAggInfo *get_partial_agg_info(PlannerInfo *root,
+ 					 PathTarget *target,
+ 					 List *target_group_clauses);
+ extern AggPath *create_partial_agg_sorted_path(PlannerInfo *root,
+ 							   Path *subpath,
+ 							   bool check_pathkeys,
+ 							   PartialAggInfo *agg_info,
+ 							   double input_rows);
+ extern AggPath *create_partial_agg_hashed_path(PlannerInfo *root,
+ 							   Path *subpath,
+ 							   PartialAggInfo *agg_info,
+ 							   double input_rows);
  extern GroupingSetsPath *create_groupingsets_path(PlannerInfo *root,
  						 RelOptInfo *rel,
  						 Path *subpath,
*************** extern RelOptInfo *build_child_join_rel(
*** 303,306 ****
--- 316,322 ----
  					 RelOptInfo *parent_joinrel, List *restrictlist,
  					 SpecialJoinInfo *sjinfo, JoinType jointype);
  extern void prepare_rel_for_grouping(PlannerInfo *root, RelOptInfo *rel);
+ extern void build_child_rel_gpi(PlannerInfo *root, RelOptInfo *child,
+ 					RelOptInfo *parent, int nappinfos,
+ 					AppendRelInfo **appinfos);
  #endif							/* PATHNODE_H */
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
new file mode 100644
index ea886b6..7815138
*** 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,65 ----
  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,
! 					PartialAggInfo *agg_info);
  extern int compute_parallel_worker(RelOptInfo *rel, double heap_pages,
  						double index_pages);
  extern void create_partial_bitmap_paths(PlannerInfo *root, RelOptInfo *rel,
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
new file mode 100644
index a725818..dfc9352
*** a/src/include/optimizer/tlist.h
--- b/src/include/optimizer/tlist.h
***************
*** 16,22 ****
  
  #include "nodes/relation.h"
  
- 
  extern TargetEntry *tlist_member(Expr *node, List *targetlist);
  extern TargetEntry *tlist_member_ignore_relabel(Expr *node, List *targetlist);
  
--- 16,21 ----
*************** extern Node *get_sortgroupclause_expr(So
*** 41,51 ****
  						 List *targetList);
  extern List *get_sortgrouplist_exprs(List *sgClauses,
  						List *targetList);
- extern void get_grouping_expressions(PlannerInfo *root, PathTarget *target,
- 						 List *target_group_clauses,
- 						 List **grouping_clauses,
- 						 List **grouping_exprs, List **agg_exprs);
- 
  extern SortGroupClause *get_sortgroupref_clause(Index sortref,
  						List *clauses);
  extern SortGroupClause *get_sortgroupref_clause_noerr(Index sortref,
--- 40,45 ----
*************** extern void split_pathtarget_at_srfs(Pla
*** 69,74 ****
--- 63,71 ----
  						 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 *replace_grouped_vars_with_aggrefs(PlannerInfo *root, List *src);
  extern void add_grouped_vars_to_target(PlannerInfo *root, PathTarget *target,
  						   List *expressions);
  extern GroupedVar *get_grouping_expression(PlannerInfo *root, Expr *expr);
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
new file mode 100644
index c9c8f51..e8ebafe
*** a/src/test/regress/expected/sysviews.out
--- b/src/test/regress/expected/sysviews.out
*************** select count(*) >= 0 as ok from pg_prepa
*** 72,77 ****
--- 72,78 ----
  select name, setting from pg_settings where name like 'enable%';
              name            | setting 
  ----------------------------+---------
+  enable_agg_pushdown        | off
   enable_bitmapscan          | on
   enable_gathermerge         | on
   enable_hashagg             | on
*************** select name, setting from pg_settings wh
*** 87,93 ****
   enable_seqscan             | on
   enable_sort                | on
   enable_tidscan             | on
! (15 rows)
  
  -- Test that the pg_timezone_names and pg_timezone_abbrevs views are
  -- more-or-less working.  We can't test their contents in any great detail
--- 88,94 ----
   enable_seqscan             | on
   enable_sort                | on
   enable_tidscan             | on
! (16 rows)
  
  -- Test that the pg_timezone_names and pg_timezone_abbrevs views are
  -- more-or-less working.  We can't test their contents in any great detail


  agg_pushdown_v5/06_join_prepare.diff
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
new file mode 100644
index 2a931af..ab739db
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
*************** set_join_pathlist_hook_type set_join_pat
*** 39,57 ****
  #define PATH_PARAM_BY_REL(path, rel)	\
  	(PATH_PARAM_BY_REL_SELF(path, rel) || PATH_PARAM_BY_PARENT(path, rel))
  
  static void try_partial_mergejoin_path(PlannerInfo *root,
  						   RelOptInfo *joinrel,
  						   Path *outer_path,
  						   Path *inner_path,
! 						   List *pathkeys,
! 						   List *mergeclauses,
! 						   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);
--- 39,72 ----
  #define PATH_PARAM_BY_REL(path, rel)	\
  	(PATH_PARAM_BY_REL_SELF(path, rel) || PATH_PARAM_BY_PARENT(path, rel))
  
+ /*
+  * The sort-specific input for try_mergejoin_... functions.
+  */
+ typedef struct MergeJoinSortInfo
+ {
+ 	List	   *outerkeys;
+ 	List	   *innerkeys;
+ 	List	   *merge_pathkeys;
+ 	List	   *merge_clauses;
+ } MergeJoinSortInfo;
+ 
  static void try_partial_mergejoin_path(PlannerInfo *root,
  						   RelOptInfo *joinrel,
  						   Path *outer_path,
  						   Path *inner_path,
! 						   MergeJoinSortInfo *si,
  						   JoinType jointype,
  						   JoinPathExtraData *extra);
  static void sort_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
  					 RelOptInfo *outerrel, RelOptInfo *innerrel,
  					 JoinType jointype, JoinPathExtraData *extra);
+ static void sort_inner_and_outer_common(PlannerInfo *root,
+ 							RelOptInfo *joinrel,
+ 							RelOptInfo *outerrel,
+ 							RelOptInfo *innerrel,
+ 							JoinType jointype,
+ 							JoinPathExtraData *extra,
+ 							List *sort_infos);
  static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel,
  					 RelOptInfo *outerrel, RelOptInfo *innerrel,
  					 JoinType jointype, JoinPathExtraData *extra);
*************** try_partial_nestloop_path(PlannerInfo *r
*** 547,552 ****
--- 562,680 ----
  										  NULL), false);
  }
  
+ static void
+ try_nestloop_path_common(PlannerInfo *root,
+ 						 RelOptInfo *joinrel,
+ 						 Path *outer_path,
+ 						 Path *inner_path,
+ 						 List *pathkeys,
+ 						 JoinType jointype,
+ 						 JoinPathExtraData *extra,
+ 						 bool partial)
+ {
+ 
+ 	/* Only join plain paths. */
+ 	if (!partial)
+ 		try_nestloop_path(root,
+ 						  joinrel,
+ 						  outer_path,
+ 						  inner_path,
+ 						  pathkeys,
+ 						  jointype,
+ 						  extra);
+ 	else
+ 		try_partial_nestloop_path(root,
+ 								  joinrel,
+ 								  outer_path,
+ 								  inner_path,
+ 								  pathkeys,
+ 								  jointype,
+ 								  extra);
+ }
+ 
+ 
+ /*
+  * Create one MergeJoinSortInfo for each possible use of merge clauses.
+  */
+ static List *
+ get_merge_join_sort_info(PlannerInfo *root, JoinPathExtraData *extra,
+ 						 RelOptInfo *joinrel, JoinType jointype)
+ {
+ 	List	   *all_pathkeys;
+ 	ListCell   *l;
+ 	List	   *result = NIL;
+ 
+ 	/*
+ 	 * Each possible ordering of the available mergejoin clauses will generate
+ 	 * a differently-sorted result path at essentially the same cost.  We have
+ 	 * no basis for choosing one over another at this level of joining, but
+ 	 * some sort orders may be more useful than others for higher-level
+ 	 * mergejoins, so it's worth considering multiple orderings.
+ 	 *
+ 	 * Actually, it's not quite true that every mergeclause ordering will
+ 	 * generate a different path order, because some of the clauses may be
+ 	 * partially redundant (refer to the same EquivalenceClasses).  Therefore,
+ 	 * what we do is convert the mergeclause list to a list of canonical
+ 	 * pathkeys, and then consider different orderings of the pathkeys.
+ 	 *
+ 	 * Generating a path for *every* permutation of the pathkeys doesn't seem
+ 	 * like a winning strategy; the cost in planning time is too high. For
+ 	 * now, we generate one path for each pathkey, listing that pathkey first
+ 	 * and the rest in random order.  This should allow at least a one-clause
+ 	 * mergejoin without re-sorting against any other possible mergejoin
+ 	 * partner path.  But if we've not guessed the right ordering of secondary
+ 	 * keys, we may end up evaluating clauses as qpquals when they could have
+ 	 * been done as mergeclauses.  (In practice, it's rare that there's more
+ 	 * than two or three mergeclauses, so expending a huge amount of thought
+ 	 * on that is probably not worth it.)
+ 	 *
+ 	 * The pathkey order returned by select_outer_pathkeys_for_merge() has
+ 	 * some heuristics behind it (see that function), so be sure to try it
+ 	 * exactly as-is as well as making variants.
+ 	 */
+ 	all_pathkeys = select_outer_pathkeys_for_merge(root,
+ 												   extra->mergeclause_list,
+ 												   joinrel);
+ 
+ 	foreach(l, all_pathkeys)
+ 	{
+ 		List	   *front_pathkey = (List *) lfirst(l);
+ 		MergeJoinSortInfo *si;
+ 
+ 		si = (MergeJoinSortInfo *) palloc(sizeof(MergeJoinSortInfo));
+ 
+ 		/* Make a pathkey list with this guy first */
+ 		if (l != list_head(all_pathkeys))
+ 			si->outerkeys = lcons(front_pathkey,
+ 								  list_delete_ptr(list_copy(all_pathkeys),
+ 												  front_pathkey));
+ 		else
+ 			si->outerkeys = all_pathkeys;	/* no work at first one... */
+ 
+ 		/* Sort the mergeclauses into the corresponding ordering */
+ 		si->merge_clauses = find_mergeclauses_for_pathkeys(root,
+ 														   si->outerkeys,
+ 														   true,
+ 														   extra->mergeclause_list);
+ 
+ 		/* Should have used them all... */
+ 		Assert(list_length(si->merge_clauses) == list_length(extra->mergeclause_list));
+ 
+ 		/* Build sort pathkeys for the inner side */
+ 		si->innerkeys = make_inner_pathkeys_for_merge(root,
+ 													  si->merge_clauses,
+ 													  si->outerkeys);
+ 
+ 		/* Build pathkeys representing output sort order */
+ 		si->merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+ 												 si->outerkeys);
+ 
+ 		result = lappend(result, si);
+ 	}
+ 
+ 	return result;
+ }
+ 
  /*
   * try_mergejoin_path
   *	  Consider a merge join path; if it appears useful, push it into
*************** try_mergejoin_path(PlannerInfo *root,
*** 557,587 ****
  				   RelOptInfo *joinrel,
  				   Path *outer_path,
  				   Path *inner_path,
! 				   List *pathkeys,
! 				   List *mergeclauses,
! 				   List *outersortkeys,
! 				   List *innersortkeys,
  				   JoinType jointype,
! 				   JoinPathExtraData *extra,
! 				   bool is_partial)
  {
  	Relids		required_outer;
  	JoinCostWorkspace workspace;
! 
! 	if (is_partial)
! 	{
! 		try_partial_mergejoin_path(root,
! 								   joinrel,
! 								   outer_path,
! 								   inner_path,
! 								   pathkeys,
! 								   mergeclauses,
! 								   outersortkeys,
! 								   innersortkeys,
! 								   jointype,
! 								   extra);
! 		return;
! 	}
  
  	/*
  	 * Check to see if proposed path is still parameterized, and reject if the
--- 685,698 ----
  				   RelOptInfo *joinrel,
  				   Path *outer_path,
  				   Path *inner_path,
! 				   MergeJoinSortInfo *si,
  				   JoinType jointype,
! 				   JoinPathExtraData *extra)
  {
  	Relids		required_outer;
  	JoinCostWorkspace workspace;
! 	List	   *outersortkeys = si->outerkeys;
! 	List	   *innersortkeys = si->innerkeys;
  
  	/*
  	 * Check to see if proposed path is still parameterized, and reject if the
*************** try_mergejoin_path(PlannerInfo *root,
*** 611,624 ****
  	/*
  	 * See comments in try_nestloop_path().
  	 */
! 	initial_cost_mergejoin(root, &workspace, jointype, mergeclauses,
  						   outer_path, inner_path,
! 						   outersortkeys, innersortkeys,
! 						   extra);
  
  	if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  pathkeys, required_outer, false))
  	{
  		add_path(joinrel, (Path *)
  				 create_mergejoin_path(root,
--- 722,734 ----
  	/*
  	 * See comments in try_nestloop_path().
  	 */
! 	initial_cost_mergejoin(root, &workspace, jointype, si->merge_clauses,
  						   outer_path, inner_path,
! 						   outersortkeys, innersortkeys, extra);
  
  	if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  si->merge_pathkeys, required_outer, false))
  	{
  		add_path(joinrel, (Path *)
  				 create_mergejoin_path(root,
*************** try_mergejoin_path(PlannerInfo *root,
*** 629,637 ****
  									   outer_path,
  									   inner_path,
  									   extra->restrictlist,
! 									   pathkeys,
  									   required_outer,
! 									   mergeclauses,
  									   outersortkeys,
  									   innersortkeys), false);
  	}
--- 739,747 ----
  									   outer_path,
  									   inner_path,
  									   extra->restrictlist,
! 									   si->merge_pathkeys,
  									   required_outer,
! 									   si->merge_clauses,
  									   outersortkeys,
  									   innersortkeys), false);
  	}
*************** try_partial_mergejoin_path(PlannerInfo *
*** 652,665 ****
  						   RelOptInfo *joinrel,
  						   Path *outer_path,
  						   Path *inner_path,
! 						   List *pathkeys,
! 						   List *mergeclauses,
! 						   List *outersortkeys,
! 						   List *innersortkeys,
  						   JoinType jointype,
  						   JoinPathExtraData *extra)
  {
  	JoinCostWorkspace workspace;
  
  	/*
  	 * See comments in try_partial_hashjoin_path().
--- 762,774 ----
  						   RelOptInfo *joinrel,
  						   Path *outer_path,
  						   Path *inner_path,
! 						   MergeJoinSortInfo *si,
  						   JoinType jointype,
  						   JoinPathExtraData *extra)
  {
  	JoinCostWorkspace workspace;
+ 	List	   *outersortkeys = si->outerkeys;
+ 	List	   *innersortkeys = si->innerkeys;
  
  	/*
  	 * See comments in try_partial_hashjoin_path().
*************** try_partial_mergejoin_path(PlannerInfo *
*** 687,698 ****
  	/*
  	 * See comments in try_partial_nestloop_path().
  	 */
! 	initial_cost_mergejoin(root, &workspace, jointype, mergeclauses,
  						   outer_path, inner_path,
! 						   outersortkeys, innersortkeys,
! 						   extra);
  
! 	if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys,
  								   false))
  		return;
  
--- 796,807 ----
  	/*
  	 * See comments in try_partial_nestloop_path().
  	 */
! 	initial_cost_mergejoin(root, &workspace, jointype, si->merge_clauses,
  						   outer_path, inner_path,
! 						   outersortkeys, innersortkeys, extra);
  
! 	if (!add_partial_path_precheck(joinrel, workspace.total_cost,
! 								   si->merge_pathkeys,
  								   false))
  		return;
  
*************** try_partial_mergejoin_path(PlannerInfo *
*** 706,719 ****
  										   outer_path,
  										   inner_path,
  										   extra->restrictlist,
! 										   pathkeys,
  										   NULL,
! 										   mergeclauses,
  										   outersortkeys,
  										   innersortkeys), false);
  }
  
  /*
   * try_hashjoin_path
   *	  Consider a hash join path; if it appears useful, push it into
   *	  the joinrel's pathlist via add_path().
--- 815,861 ----
  										   outer_path,
  										   inner_path,
  										   extra->restrictlist,
! 										   si->merge_pathkeys,
  										   NULL,
! 										   si->merge_clauses,
  										   outersortkeys,
  										   innersortkeys), false);
  }
  
  /*
+  * Generic front-end to create combinations of full/partial and grouped/plain
+  * merge joins.
+  */
+ static void
+ try_mergejoin_path_common(PlannerInfo *root,
+ 						  RelOptInfo *joinrel,
+ 						  Path *outer_path,
+ 						  Path *inner_path,
+ 						  MergeJoinSortInfo *si,
+ 						  JoinType jointype,
+ 						  JoinPathExtraData *extra,
+ 						  bool partial)
+ {
+ 	/* Only join plain paths. */
+ 	if (!partial)
+ 		try_mergejoin_path(root,
+ 						   joinrel,
+ 						   outer_path,
+ 						   inner_path,
+ 						   si,
+ 						   jointype,
+ 						   extra);
+ 	else
+ 		try_partial_mergejoin_path(root,
+ 								   joinrel,
+ 								   outer_path,
+ 								   inner_path,
+ 								   si,
+ 								   jointype,
+ 								   extra);
+ }
+ 
+ /*
   * try_hashjoin_path
   *	  Consider a hash join path; if it appears useful, push it into
   *	  the joinrel's pathlist via add_path().
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 836,841 ****
--- 978,1021 ----
  }
  
  /*
+  * Generic front-end to create combinations of full/partial and grouped/plain
+  * hash joins.
+  */
+ static void
+ try_hashjoin_path_common(PlannerInfo *root,
+ 						 RelOptInfo *joinrel,
+ 						 Path *outer_path,
+ 						 Path *inner_path,
+ 						 List *hashclauses,
+ 						 JoinType jointype,
+ 						 JoinPathExtraData *extra,
+ 						 bool partial,
+ 						 bool parallel_hash)
+ {
+ 	/* parallel_hash can only be set if partial is too. */
+ 	Assert(!parallel_hash || partial);
+ 
+ 	/* Only join plain paths. */
+ 	if (!partial)
+ 		try_hashjoin_path(root,
+ 						  joinrel,
+ 						  outer_path,
+ 						  inner_path,
+ 						  hashclauses,
+ 						  jointype,
+ 						  extra);
+ 	else
+ 		try_partial_hashjoin_path(root,
+ 								  joinrel,
+ 								  outer_path,
+ 								  inner_path,
+ 								  hashclauses,
+ 								  jointype,
+ 								  extra,
+ 								  parallel_hash);
+ }
+ 
+ /*
   * clause_sides_match_join
   *	  Determine whether a join clause is of the right form to use in this join.
   *
*************** sort_inner_and_outer(PlannerInfo *root,
*** 885,896 ****
  					 JoinType jointype,
  					 JoinPathExtraData *extra)
  {
  	JoinType	save_jointype = jointype;
  	Path	   *outer_path;
  	Path	   *inner_path;
  	Path	   *cheapest_partial_outer = NULL;
  	Path	   *cheapest_safe_inner = NULL;
- 	List	   *all_pathkeys;
  	ListCell   *l;
  
  	/*
--- 1065,1098 ----
  					 JoinType jointype,
  					 JoinPathExtraData *extra)
  {
+ 	List	   *sort_infos = get_merge_join_sort_info(root, extra, joinrel,
+ 													  jointype);
+ 
+ 
+ 	sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
+ 								jointype, extra, sort_infos);
+ }
+ 
+ /*
+  * The workhorse of sort_inner_and_outer().
+  *
+  * sort_infos contains one MergeJoinSortInfo per possible sorting of the input
+  * paths that we should consider.
+  */
+ static void
+ sort_inner_and_outer_common(PlannerInfo *root,
+ 							RelOptInfo *joinrel,
+ 							RelOptInfo *outerrel,
+ 							RelOptInfo *innerrel,
+ 							JoinType jointype,
+ 							JoinPathExtraData *extra,
+ 							List *sort_infos)
+ {
  	JoinType	save_jointype = jointype;
  	Path	   *outer_path;
  	Path	   *inner_path;
  	Path	   *cheapest_partial_outer = NULL;
  	Path	   *cheapest_safe_inner = NULL;
  	ListCell   *l;
  
  	/*
*************** sort_inner_and_outer(PlannerInfo *root,
*** 962,1032 ****
  				get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
  	}
  
! 	/*
! 	 * Each possible ordering of the available mergejoin clauses will generate
! 	 * a differently-sorted result path at essentially the same cost.  We have
! 	 * no basis for choosing one over another at this level of joining, but
! 	 * some sort orders may be more useful than others for higher-level
! 	 * mergejoins, so it's worth considering multiple orderings.
! 	 *
! 	 * Actually, it's not quite true that every mergeclause ordering will
! 	 * generate a different path order, because some of the clauses may be
! 	 * partially redundant (refer to the same EquivalenceClasses).  Therefore,
! 	 * what we do is convert the mergeclause list to a list of canonical
! 	 * pathkeys, and then consider different orderings of the pathkeys.
! 	 *
! 	 * Generating a path for *every* permutation of the pathkeys doesn't seem
! 	 * like a winning strategy; the cost in planning time is too high. For
! 	 * now, we generate one path for each pathkey, listing that pathkey first
! 	 * and the rest in random order.  This should allow at least a one-clause
! 	 * mergejoin without re-sorting against any other possible mergejoin
! 	 * partner path.  But if we've not guessed the right ordering of secondary
! 	 * keys, we may end up evaluating clauses as qpquals when they could have
! 	 * been done as mergeclauses.  (In practice, it's rare that there's more
! 	 * than two or three mergeclauses, so expending a huge amount of thought
! 	 * on that is probably not worth it.)
! 	 *
! 	 * The pathkey order returned by select_outer_pathkeys_for_merge() has
! 	 * some heuristics behind it (see that function), so be sure to try it
! 	 * exactly as-is as well as making variants.
! 	 */
! 	all_pathkeys = select_outer_pathkeys_for_merge(root,
! 												   extra->mergeclause_list,
! 												   joinrel);
! 
! 	foreach(l, all_pathkeys)
  	{
! 		List	   *front_pathkey = (List *) lfirst(l);
! 		List	   *cur_mergeclauses;
! 		List	   *outerkeys;
! 		List	   *innerkeys;
! 		List	   *merge_pathkeys;
! 
! 		/* Make a pathkey list with this guy first */
! 		if (l != list_head(all_pathkeys))
! 			outerkeys = lcons(front_pathkey,
! 							  list_delete_ptr(list_copy(all_pathkeys),
! 											  front_pathkey));
! 		else
! 			outerkeys = all_pathkeys;	/* no work at first one... */
! 
! 		/* Sort the mergeclauses into the corresponding ordering */
! 		cur_mergeclauses = find_mergeclauses_for_pathkeys(root,
! 														  outerkeys,
! 														  true,
! 														  extra->mergeclause_list);
! 
! 		/* Should have used them all... */
! 		Assert(list_length(cur_mergeclauses) == list_length(extra->mergeclause_list));
! 
! 		/* Build sort pathkeys for the inner side */
! 		innerkeys = make_inner_pathkeys_for_merge(root,
! 												  cur_mergeclauses,
! 												  outerkeys);
! 
! 		/* Build pathkeys representing output sort order */
! 		merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
! 											 outerkeys);
  
  		/*
  		 * And now we can make the path.
--- 1164,1172 ----
  				get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
  	}
  
! 	foreach(l, sort_infos)
  	{
! 		MergeJoinSortInfo *si = (MergeJoinSortInfo *) lfirst(l);
  
  		/*
  		 * And now we can make the path.
*************** sort_inner_and_outer(PlannerInfo *root,
*** 1035,1067 ****
  		 * 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);
  	}
  }
  
--- 1175,1193 ----
  		 * properly.  try_mergejoin_path will detect that case and suppress an
  		 * explicit sort step, so we needn't do so here.
  		 */
! 		try_mergejoin_path_common(root, joinrel, outer_path, inner_path,
! 								  si, 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_mergejoin_path_common(root, joinrel,
! 									  cheapest_partial_outer,
! 									  cheapest_safe_inner,
! 									  si, jointype, extra,
! 									  true);
  	}
  }
  
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1091,1113 ****
  						 List *merge_pathkeys,
  						 bool is_partial)
  {
- 	List	   *mergeclauses;
- 	List	   *innersortkeys;
  	List	   *trialsortkeys;
  	Path	   *cheapest_startup_inner;
  	Path	   *cheapest_total_inner;
  	JoinType	save_jointype = jointype;
  	int			num_sortkeys;
  	int			sortkeycnt;
  
  	if (jointype == JOIN_UNIQUE_OUTER || jointype == JOIN_UNIQUE_INNER)
  		jointype = JOIN_INNER;
  
  	/* Look for useful mergeclauses (if any) */
! 	mergeclauses = find_mergeclauses_for_pathkeys(root,
! 												  outerpath->pathkeys,
! 												  true,
! 												  extra->mergeclause_list);
  
  	/*
  	 * Done with this outer path if no chance for a mergejoin.
--- 1217,1240 ----
  						 List *merge_pathkeys,
  						 bool is_partial)
  {
  	List	   *trialsortkeys;
  	Path	   *cheapest_startup_inner;
  	Path	   *cheapest_total_inner;
  	JoinType	save_jointype = jointype;
  	int			num_sortkeys;
  	int			sortkeycnt;
+ 	MergeJoinSortInfo si;
  
  	if (jointype == JOIN_UNIQUE_OUTER || jointype == JOIN_UNIQUE_INNER)
  		jointype = JOIN_INNER;
  
+ 	si.merge_pathkeys = merge_pathkeys;
  	/* Look for useful mergeclauses (if any) */
! 	si.merge_clauses = find_mergeclauses_for_pathkeys(root,
! 													  outerpath->pathkeys,
! 													  true,
! 													  extra->mergeclause_list);
! 	si.outerkeys = NIL;
  
  	/*
  	 * Done with this outer path if no chance for a mergejoin.
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1118,1124 ****
  	 * without any join clauses, it's necessary to generate a clauseless
  	 * mergejoin path instead.
  	 */
! 	if (mergeclauses == NIL)
  	{
  		if (jointype == JOIN_FULL)
  			 /* okay to try for mergejoin */ ;
--- 1245,1251 ----
  	 * without any join clauses, it's necessary to generate a clauseless
  	 * mergejoin path instead.
  	 */
! 	if (si.merge_clauses == NIL)
  	{
  		if (jointype == JOIN_FULL)
  			 /* okay to try for mergejoin */ ;
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1126,1138 ****
  			return;
  	}
  	if (useallclauses &&
! 		list_length(mergeclauses) != list_length(extra->mergeclause_list))
  		return;
  
  	/* Compute the required ordering of the inner path */
! 	innersortkeys = make_inner_pathkeys_for_merge(root,
! 												  mergeclauses,
! 												  outerpath->pathkeys);
  
  	/*
  	 * Generate a mergejoin on the basis of sorting the cheapest inner. Since
--- 1253,1265 ----
  			return;
  	}
  	if (useallclauses &&
! 		list_length(si.merge_clauses) != list_length(extra->mergeclause_list))
  		return;
  
  	/* Compute the required ordering of the inner path */
! 	si.innerkeys = make_inner_pathkeys_for_merge(root,
! 												 si.merge_clauses,
! 												 outerpath->pathkeys);
  
  	/*
  	 * Generate a mergejoin on the basis of sorting the cheapest inner. Since
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1140,1156 ****
  	 * 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)
--- 1267,1280 ----
  	 * 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,
! 							  &si,
! 							  jointype,
! 							  extra,
! 							  is_partial);
  
  	/* Can't do anything else if inner path needs to be unique'd */
  	if (save_jointype == JOIN_UNIQUE_INNER)
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1185,1191 ****
  	 * sorting inner_cheapest_total, whereas we did sort it above, so the
  	 * plans being considered are different.
  	 */
! 	if (pathkeys_contained_in(innersortkeys,
  							  inner_cheapest_total->pathkeys))
  	{
  		/* inner_cheapest_total didn't require a sort */
--- 1309,1315 ----
  	 * sorting inner_cheapest_total, whereas we did sort it above, so the
  	 * plans being considered are different.
  	 */
! 	if (pathkeys_contained_in(si.innerkeys,
  							  inner_cheapest_total->pathkeys))
  	{
  		/* inner_cheapest_total didn't require a sort */
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1198,1213 ****
  		cheapest_startup_inner = NULL;
  		cheapest_total_inner = NULL;
  	}
! 	num_sortkeys = list_length(innersortkeys);
  	if (num_sortkeys > 1 && !useallclauses)
! 		trialsortkeys = list_copy(innersortkeys);	/* need modifiable copy */
  	else
! 		trialsortkeys = innersortkeys;	/* won't really truncate */
  
  	for (sortkeycnt = num_sortkeys; sortkeycnt > 0; sortkeycnt--)
  	{
  		Path	   *innerpath;
! 		List	   *newclauses = NIL;
  
  		/*
  		 * Look for an inner path ordered well enough for the first
--- 1322,1342 ----
  		cheapest_startup_inner = NULL;
  		cheapest_total_inner = NULL;
  	}
! 	num_sortkeys = list_length(si.innerkeys);
  	if (num_sortkeys > 1 && !useallclauses)
! 		trialsortkeys = list_copy(si.innerkeys);	/* need modifiable copy */
  	else
! 		trialsortkeys = si.innerkeys;	/* won't really truncate */
  
  	for (sortkeycnt = num_sortkeys; sortkeycnt > 0; sortkeycnt--)
  	{
  		Path	   *innerpath;
! 		MergeJoinSortInfo si_trial;
! 
! 		si_trial.outerkeys = NIL;
! 		si_trial.innerkeys = NIL;
! 		si_trial.merge_pathkeys = si.merge_pathkeys;
! 		si_trial.merge_clauses = NIL;
  
  		/*
  		 * Look for an inner path ordered well enough for the first
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1229,1254 ****
  			/* Select the right mergeclauses, if we didn't already */
  			if (sortkeycnt < num_sortkeys)
  			{
! 				newclauses =
  					find_mergeclauses_for_pathkeys(root,
  												   trialsortkeys,
  												   false,
! 												   mergeclauses);
! 				Assert(newclauses != NIL);
  			}
  			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 ... */
--- 1358,1381 ----
  			/* Select the right mergeclauses, if we didn't already */
  			if (sortkeycnt < num_sortkeys)
  			{
! 				si_trial.merge_clauses =
  					find_mergeclauses_for_pathkeys(root,
  												   trialsortkeys,
  												   false,
! 												   si.merge_clauses);
! 				Assert(si_trial.merge_clauses != NIL);
  			}
  			else
! 				si_trial.merge_clauses = si.merge_clauses;
! 
! 			try_mergejoin_path_common(root,
! 									  joinrel,
! 									  outerpath,
! 									  innerpath,
! 									  &si,
! 									  jointype,
! 									  extra,
! 									  is_partial);
  			cheapest_total_inner = innerpath;
  		}
  		/* Same on the basis of cheapest startup cost ... */
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1269,1299 ****
  				 * Avoid rebuilding clause list if we already made one; saves
  				 * memory in big join trees...
  				 */
! 				if (newclauses == NIL)
  				{
  					if (sortkeycnt < num_sortkeys)
  					{
! 						newclauses =
  							find_mergeclauses_for_pathkeys(root,
  														   trialsortkeys,
  														   false,
! 														   mergeclauses);
! 						Assert(newclauses != NIL);
  					}
  					else
! 						newclauses = mergeclauses;
  				}
! 				try_mergejoin_path(root,
! 								   joinrel,
! 								   outerpath,
! 								   innerpath,
! 								   merge_pathkeys,
! 								   newclauses,
! 								   NIL,
! 								   NIL,
! 								   jointype,
! 								   extra,
! 								   is_partial);
  			}
  			cheapest_startup_inner = innerpath;
  		}
--- 1396,1423 ----
  				 * Avoid rebuilding clause list if we already made one; saves
  				 * memory in big join trees...
  				 */
! 				if (si_trial.merge_clauses == NIL)
  				{
  					if (sortkeycnt < num_sortkeys)
  					{
! 						si_trial.merge_clauses =
  							find_mergeclauses_for_pathkeys(root,
  														   trialsortkeys,
  														   false,
! 														   si.merge_clauses);
! 						Assert(si_trial.merge_clauses != NIL);
  					}
  					else
! 						si_trial.merge_clauses = si.merge_clauses;
  				}
! 				try_mergejoin_path_common(root,
! 										  joinrel,
! 										  outerpath,
! 										  innerpath,
! 										  &si,
! 										  jointype,
! 										  extra,
! 										  is_partial);
  			}
  			cheapest_startup_inner = innerpath;
  		}
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1306,1341 ****
  	}
  }
  
- /*
-  * match_unsorted_outer
-  *	  Creates possible join paths for processing a single join relation
-  *	  'joinrel' by employing either iterative substitution or
-  *	  mergejoining on each of its possible outer paths (considering
-  *	  only outer paths that are already ordered well enough for merging).
-  *
-  * We always generate a nestloop path for each available outer path.
-  * In fact we may generate as many as five: one on the cheapest-total-cost
-  * inner path, one on the same with materialization, one on the
-  * cheapest-startup-cost inner path (if different), one on the
-  * cheapest-total inner-indexscan path (if any), and one on the
-  * cheapest-startup inner-indexscan path (if different).
-  *
-  * We also consider mergejoins if mergejoin clauses are available.  See
-  * detailed comments in generate_mergejoin_paths.
-  *
-  * 'joinrel' is the join relation
-  * 'outerrel' is the outer join relation
-  * 'innerrel' is the inner join relation
-  * 'jointype' is the type of join to do
-  * 'extra' contains additional input values
-  */
  static void
! match_unsorted_outer(PlannerInfo *root,
! 					 RelOptInfo *joinrel,
! 					 RelOptInfo *outerrel,
! 					 RelOptInfo *innerrel,
! 					 JoinType jointype,
! 					 JoinPathExtraData *extra)
  {
  	JoinType	save_jointype = jointype;
  	bool		nestjoinOK;
--- 1430,1442 ----
  	}
  }
  
  static void
! match_unsorted_outer_common(PlannerInfo *root,
! 							RelOptInfo *joinrel,
! 							RelOptInfo *outerrel,
! 							RelOptInfo *innerrel,
! 							JoinType jointype,
! 							JoinPathExtraData *extra)
  {
  	JoinType	save_jointype = jointype;
  	bool		nestjoinOK;
*************** match_unsorted_outer(PlannerInfo *root,
*** 1396,1401 ****
--- 1497,1503 ----
  		/* No way to do this with an inner path parameterized by outer rel */
  		if (inner_cheapest_total == NULL)
  			return;
+ 
  		inner_cheapest_total = (Path *)
  			create_unique_path(root, innerrel, inner_cheapest_total, extra->sjinfo);
  		Assert(inner_cheapest_total);
*************** match_unsorted_outer(PlannerInfo *root,
*** 1433,1438 ****
--- 1535,1541 ----
  		{
  			if (outerpath != outerrel->cheapest_total_path)
  				continue;
+ 
  			outerpath = (Path *) create_unique_path(root, outerrel,
  													outerpath, extra->sjinfo);
  			Assert(outerpath);
*************** match_unsorted_outer(PlannerInfo *root,
*** 1452,1464 ****
  			 * Consider nestloop join, but only with the unique-ified cheapest
  			 * inner path
  			 */
! 			try_nestloop_path(root,
! 							  joinrel,
! 							  outerpath,
! 							  inner_cheapest_total,
! 							  merge_pathkeys,
! 							  jointype,
! 							  extra);
  		}
  		else if (nestjoinOK)
  		{
--- 1555,1568 ----
  			 * Consider nestloop join, but only with the unique-ified cheapest
  			 * inner path
  			 */
! 			try_nestloop_path_common(root,
! 									 joinrel,
! 									 outerpath,
! 									 inner_cheapest_total,
! 									 merge_pathkeys,
! 									 jointype,
! 									 extra,
! 									 false);
  		}
  		else if (nestjoinOK)
  		{
*************** match_unsorted_outer(PlannerInfo *root,
*** 1474,1497 ****
  			{
  				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 */
--- 1578,1603 ----
  			{
  				Path	   *innerpath = (Path *) lfirst(lc2);
  
! 				try_nestloop_path_common(root,
! 										 joinrel,
! 										 outerpath,
! 										 innerpath,
! 										 merge_pathkeys,
! 										 jointype,
! 										 extra,
! 										 false);
  			}
  
! 			/* Also consider materialized form of the cheapest inner path. */
  			if (matpath != NULL)
! 				try_nestloop_path_common(root,
! 										 joinrel,
! 										 outerpath,
! 										 matpath,
! 										 merge_pathkeys,
! 										 jointype,
! 										 extra,
! 										 false);
  		}
  
  		/* Can't do anything else if outer path needs to be unique'd */
*************** match_unsorted_outer(PlannerInfo *root,
*** 1552,1557 ****
--- 1658,1698 ----
  }
  
  /*
+  * match_unsorted_outer
+  *	  Creates possible join paths for processing a single join relation
+  *	  'joinrel' by employing either iterative substitution or
+  *	  mergejoining on each of its possible outer paths (considering
+  *	  only outer paths that are already ordered well enough for merging).
+  *
+  * We always generate a nestloop path for each available outer path.
+  * In fact we may generate as many as five: one on the cheapest-total-cost
+  * inner path, one on the same with materialization, one on the
+  * cheapest-startup-cost inner path (if different), one on the
+  * cheapest-total inner-indexscan path (if any), and one on the
+  * cheapest-startup inner-indexscan path (if different).
+  *
+  * We also consider mergejoins if mergejoin clauses are available.  See
+  * detailed comments in generate_mergejoin_paths.
+  *
+  * 'joinrel' is the join relation
+  * 'outerrel' is the outer join relation
+  * 'innerrel' is the inner join relation
+  * 'jointype' is the type of join to do
+  * 'extra' contains additional input values
+  */
+ static void
+ match_unsorted_outer(PlannerInfo *root,
+ 					 RelOptInfo *joinrel,
+ 					 RelOptInfo *outerrel,
+ 					 RelOptInfo *innerrel,
+ 					 JoinType jointype,
+ 					 JoinPathExtraData *extra)
+ {
+ 	match_unsorted_outer_common(root, joinrel, outerrel, innerrel,
+ 								jointype, extra);
+ }
+ 
+ /*
   * consider_parallel_mergejoin
   *	  Try to build partial paths for a joinrel by joining a partial path
   *	  for the outer relation to a complete path for the inner relation.
*************** consider_parallel_nestloop(PlannerInfo *
*** 1658,1667 ****
  				Assert(innerpath);
  			}
  
! 			try_partial_nestloop_path(root, joinrel, outerpath, innerpath,
! 									  pathkeys, jointype, extra);
  		}
  	}
  }
  
  /*
--- 1799,2029 ----
  				Assert(innerpath);
  			}
  
! 			try_nestloop_path_common(root, joinrel, outerpath, innerpath,
! 									 pathkeys, jointype, extra, true);
! 		}
! 	}
! }
! 
! /*
!  * The workhorse of hash_inner_and_outer(). The hashclauses list is passed by
!  * caller because it does not differ between calls.
!  */
! static void
! hash_inner_and_outer_common(PlannerInfo *root,
! 							RelOptInfo *joinrel,
! 							RelOptInfo *outerrel,
! 							RelOptInfo *innerrel,
! 							JoinType jointype,
! 							JoinPathExtraData *extra,
! 							List *hashclauses)
! {
! 	JoinType	save_jointype = jointype;
! 
! 	/*
! 	 * We consider both the cheapest-total-cost and cheapest-startup-cost
! 	 * outer paths.  There's no need to consider any but the
! 	 * cheapest-total-cost inner path, however.
! 	 */
! 	Path	   *cheapest_startup_outer,
! 			   *cheapest_total_outer,
! 			   *cheapest_total_inner;
! 
! 	/* Shouldn't be called w/o hashclauses. */
! 	Assert(hashclauses != NIL);
! 
! 	cheapest_startup_outer = outerrel->cheapest_startup_path;
! 	cheapest_total_outer = outerrel->cheapest_total_path;
! 	cheapest_total_inner = innerrel->cheapest_total_path;
! 
! 	/*
! 	 * If either cheapest-total path is parameterized by the other rel, we
! 	 * can't use a hashjoin.  (There's no use looking for alternative input
! 	 * paths, since these should already be the least-parameterized available
! 	 * paths.)
! 	 */
! 	if (PATH_PARAM_BY_REL(cheapest_total_outer, innerrel) ||
! 		PATH_PARAM_BY_REL(cheapest_total_inner, outerrel))
! 		return;
! 
! 	/* Unique-ify if need be; we ignore parameterized possibilities */
! 	if (jointype == JOIN_UNIQUE_OUTER)
! 	{
! 		cheapest_total_outer = (Path *)
! 			create_unique_path(root, outerrel,
! 							   cheapest_total_outer, extra->sjinfo);
! 		Assert(cheapest_total_outer);
! 		jointype = JOIN_INNER;
! 		try_hashjoin_path_common(root,
! 								 joinrel,
! 								 cheapest_total_outer,
! 								 cheapest_total_inner,
! 								 hashclauses,
! 								 jointype,
! 								 extra,
! 								 false,
! 								 false);
! 		/* no possibility of cheap startup here */
! 	}
! 	else if (jointype == JOIN_UNIQUE_INNER)
! 	{
! 		cheapest_total_inner = (Path *)
! 			create_unique_path(root, innerrel,
! 							   cheapest_total_inner, extra->sjinfo);
! 		Assert(cheapest_total_inner);
! 		jointype = JOIN_INNER;
! 		try_hashjoin_path_common(root,
! 								 joinrel,
! 								 cheapest_total_outer,
! 								 cheapest_total_inner,
! 								 hashclauses,
! 								 jointype,
! 								 extra,
! 								 false,
! 								 false);
! 
! 		if (cheapest_startup_outer != NULL &&
! 			cheapest_startup_outer != cheapest_total_outer)
! 			try_hashjoin_path_common(root,
! 									 joinrel,
! 									 cheapest_startup_outer,
! 									 cheapest_total_inner,
! 									 hashclauses,
! 									 jointype,
! 									 extra,
! 									 false,
! 									 false);
! 	}
! 	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.
! 		 *
! 		 * For if either side of the join is grouped, we simply use
! 		 * rel->gpi->pathlist. (cheapest_startup_outer should be NULL if
! 		 * grouped_outer.)
! 		 */
! 		ListCell   *lc1;
! 		ListCell   *lc2;
! 
! 		if (cheapest_startup_outer != NULL)
! 			try_hashjoin_path_common(root,
! 									 joinrel,
! 									 cheapest_startup_outer,
! 									 cheapest_total_inner,
! 									 hashclauses,
! 									 jointype,
! 									 extra,
! 									 false,
! 									 false);
! 
! 		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_common(root,
! 										 joinrel,
! 										 outerpath,
! 										 innerpath,
! 										 hashclauses,
! 										 jointype,
! 										 extra,
! 										 false,
! 										 false);
! 			}
  		}
  	}
+ 
+ 	/*
+ 	 * If the joinrel is parallel-safe, we may be able to consider a partial
+ 	 * hash join.  However, we can't handle JOIN_UNIQUE_OUTER, because the
+ 	 * outer path will be partial, and therefore we won't be able to properly
+ 	 * guarantee uniqueness.  Similarly, we can't handle JOIN_FULL and
+ 	 * JOIN_RIGHT, because they can produce false null extended rows.  Also,
+ 	 * the resulting path must not be parameterized.  We would be able to
+ 	 * support JOIN_FULL and JOIN_RIGHT for Parallel Hash, since in that case
+ 	 * we're back to a single hash table with a single set of match bits for
+ 	 * each batch, but that will require figuring out a deadlock-free way to
+ 	 * wait for the probe to finish.
+ 	 */
+ 	if (joinrel->consider_parallel &&
+ 		save_jointype != JOIN_UNIQUE_OUTER &&
+ 		save_jointype != JOIN_FULL &&
+ 		save_jointype != JOIN_RIGHT &&
+ 		outerrel->partial_pathlist != NIL &&
+ 		bms_is_empty(joinrel->lateral_relids))
+ 	{
+ 		Path	   *cheapest_partial_outer;
+ 		Path	   *cheapest_partial_inner = NULL;
+ 		Path	   *cheapest_safe_inner = NULL;
+ 
+ 		cheapest_partial_outer =
+ 			(Path *) linitial(outerrel->partial_pathlist);
+ 
+ 		/*
+ 		 * Can we use a partial inner plan too, so that we can build a shared
+ 		 * hash table in parallel?
+ 		 */
+ 		if (innerrel->partial_pathlist != NIL && enable_parallel_hash)
+ 		{
+ 			cheapest_partial_inner =
+ 				(Path *) linitial(innerrel->partial_pathlist);
+ 			try_hashjoin_path_common(root, joinrel,
+ 									 cheapest_partial_outer,
+ 									 cheapest_partial_inner,
+ 									 hashclauses, jointype, extra,
+ 									 true,
+ 									 true /* parallel_hash */ );
+ 		}
+ 
+ 
+ 		/*
+ 		 * Normally, given that the joinrel is parallel-safe, the cheapest
+ 		 * total inner path will also be parallel-safe, but if not, we'll have
+ 		 * to search cheapest_parameterized_paths for the cheapest safe,
+ 		 * unparameterized inner path.  If doing JOIN_UNIQUE_INNER, we can't
+ 		 * use any alternative inner path.
+ 		 */
+ 		if (cheapest_total_inner->parallel_safe)
+ 			cheapest_safe_inner = cheapest_total_inner;
+ 		else if (save_jointype != JOIN_UNIQUE_INNER)
+ 			cheapest_safe_inner =
+ 				get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
+ 
+ 		if (cheapest_safe_inner != NULL)
+ 			try_hashjoin_path_common(root, joinrel,
+ 									 cheapest_partial_outer,
+ 									 cheapest_safe_inner,
+ 									 hashclauses, jointype, extra,
+ 									 true,
+ 									 false);	/* parallel_hash */
+ 	}
  }
  
  /*
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1683,1691 ****
  					 JoinType jointype,
  					 JoinPathExtraData *extra)
  {
- 	JoinType	save_jointype = jointype;
  	bool		isouterjoin = IS_OUTER_JOIN(jointype);
! 	List	   *hashclauses;
  	ListCell   *l;
  
  	/*
--- 2045,2052 ----
  					 JoinType jointype,
  					 JoinPathExtraData *extra)
  {
  	bool		isouterjoin = IS_OUTER_JOIN(jointype);
! 	List	   *hashclauses = NIL;
  	ListCell   *l;
  
  	/*
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1695,1701 ****
  	 * Scan the join's restrictinfo list to find hashjoinable clauses that are
  	 * usable with this pair of sub-relations.
  	 */
- 	hashclauses = NIL;
  	foreach(l, extra->restrictlist)
  	{
  		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);
--- 2056,2061 ----
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1720,1908 ****
  		hashclauses = lappend(hashclauses, restrictinfo);
  	}
  
! 	/* If we found any usable hashclauses, make paths */
! 	if (hashclauses)
! 	{
! 		/*
! 		 * We consider both the cheapest-total-cost and cheapest-startup-cost
! 		 * outer paths.  There's no need to consider any but the
! 		 * cheapest-total-cost inner path, however.
! 		 */
! 		Path	   *cheapest_startup_outer = outerrel->cheapest_startup_path;
! 		Path	   *cheapest_total_outer = outerrel->cheapest_total_path;
! 		Path	   *cheapest_total_inner = innerrel->cheapest_total_path;
! 
! 		/*
! 		 * If either cheapest-total path is parameterized by the other rel, we
! 		 * can't use a hashjoin.  (There's no use looking for alternative
! 		 * input paths, since these should already be the least-parameterized
! 		 * available paths.)
! 		 */
! 		if (PATH_PARAM_BY_REL(cheapest_total_outer, innerrel) ||
! 			PATH_PARAM_BY_REL(cheapest_total_inner, outerrel))
! 			return;
! 
! 		/* Unique-ify if need be; we ignore parameterized possibilities */
! 		if (jointype == JOIN_UNIQUE_OUTER)
! 		{
! 			cheapest_total_outer = (Path *)
! 				create_unique_path(root, outerrel,
! 								   cheapest_total_outer, extra->sjinfo);
! 			Assert(cheapest_total_outer);
! 			jointype = JOIN_INNER;
! 			try_hashjoin_path(root,
! 							  joinrel,
! 							  cheapest_total_outer,
! 							  cheapest_total_inner,
! 							  hashclauses,
! 							  jointype,
! 							  extra);
! 			/* no possibility of cheap startup here */
! 		}
! 		else if (jointype == JOIN_UNIQUE_INNER)
! 		{
! 			cheapest_total_inner = (Path *)
! 				create_unique_path(root, innerrel,
! 								   cheapest_total_inner, extra->sjinfo);
! 			Assert(cheapest_total_inner);
! 			jointype = JOIN_INNER;
! 			try_hashjoin_path(root,
! 							  joinrel,
! 							  cheapest_total_outer,
! 							  cheapest_total_inner,
! 							  hashclauses,
! 							  jointype,
! 							  extra);
! 			if (cheapest_startup_outer != NULL &&
! 				cheapest_startup_outer != cheapest_total_outer)
! 				try_hashjoin_path(root,
! 								  joinrel,
! 								  cheapest_startup_outer,
! 								  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);
! 				}
! 			}
! 		}
! 
! 		/*
! 		 * If the joinrel is parallel-safe, we may be able to consider a
! 		 * partial hash join.  However, we can't handle JOIN_UNIQUE_OUTER,
! 		 * because the outer path will be partial, and therefore we won't be
! 		 * able to properly guarantee uniqueness.  Similarly, we can't handle
! 		 * JOIN_FULL and JOIN_RIGHT, because they can produce false null
! 		 * extended rows.  Also, the resulting path must not be parameterized.
! 		 * We would be able to support JOIN_FULL and JOIN_RIGHT for Parallel
! 		 * Hash, since in that case we're back to a single hash table with a
! 		 * single set of match bits for each batch, but that will require
! 		 * figuring out a deadlock-free way to wait for the probe to finish.
! 		 */
! 		if (joinrel->consider_parallel &&
! 			save_jointype != JOIN_UNIQUE_OUTER &&
! 			save_jointype != JOIN_FULL &&
! 			save_jointype != JOIN_RIGHT &&
! 			outerrel->partial_pathlist != NIL &&
! 			bms_is_empty(joinrel->lateral_relids))
! 		{
! 			Path	   *cheapest_partial_outer;
! 			Path	   *cheapest_partial_inner = NULL;
! 			Path	   *cheapest_safe_inner = NULL;
! 
! 			cheapest_partial_outer =
! 				(Path *) linitial(outerrel->partial_pathlist);
! 
! 			/*
! 			 * Can we use a partial inner plan too, so that we can build a
! 			 * shared hash table in parallel?
! 			 */
! 			if (innerrel->partial_pathlist != NIL && enable_parallel_hash)
! 			{
! 				cheapest_partial_inner =
! 					(Path *) linitial(innerrel->partial_pathlist);
! 				try_partial_hashjoin_path(root, joinrel,
! 										  cheapest_partial_outer,
! 										  cheapest_partial_inner,
! 										  hashclauses, jointype, extra,
! 										  true /* parallel_hash */ );
! 			}
! 
! 			/*
! 			 * Normally, given that the joinrel is parallel-safe, the cheapest
! 			 * total inner path will also be parallel-safe, but if not, we'll
! 			 * have to search for the cheapest safe, unparameterized inner
! 			 * path.  If doing JOIN_UNIQUE_INNER, we can't use any alternative
! 			 * inner path.
! 			 */
! 			if (cheapest_total_inner->parallel_safe)
! 				cheapest_safe_inner = cheapest_total_inner;
! 			else if (save_jointype != JOIN_UNIQUE_INNER)
! 				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,
! 										  false /* parallel_hash */ );
! 		}
! 	}
  }
  
  /*
--- 2080,2090 ----
  		hashclauses = lappend(hashclauses, restrictinfo);
  	}
  
! 	if (hashclauses == NIL)
! 		return;
  
! 	hash_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! 								jointype, extra, hashclauses);
  }
  
  /*


  agg_pushdown_v5/07_join.diff
diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c
new file mode 100644
index e0dc8d9..30ac459
*** a/src/backend/optimizer/geqo/geqo_eval.c
--- b/src/backend/optimizer/geqo/geqo_eval.c
*************** merge_clump(PlannerInfo *root, List *clu
*** 269,274 ****
--- 269,275 ----
  
  				/* Create GatherPaths for any useful partial paths for rel */
  				generate_gather_paths(root, joinrel, false);
+ 				generate_gather_paths(root, joinrel, true);
  
  				/* 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 1ccb834..36e038e
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** static void set_append_rel_pathlist(Plan
*** 97,103 ****
  static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
  						   List *live_childrels,
  						   List *all_child_pathkeys,
! 						   List *partitioned_rels);
  static Path *get_cheapest_parameterized_child_path(PlannerInfo *root,
  									  RelOptInfo *rel,
  									  Relids required_outer);
--- 97,104 ----
  static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
  						   List *live_childrels,
  						   List *all_child_pathkeys,
! 						   List *partitioned_rels,
! 						   bool grouped);
  static Path *get_cheapest_parameterized_child_path(PlannerInfo *root,
  									  RelOptInfo *rel,
  									  Relids required_outer);
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1873,1879 ****
  	if (subpaths_valid)
  		generate_mergeappend_paths(root, rel, live_childrels,
  								   all_child_pathkeys,
! 								   partitioned_rels);
  
  	/*
  	 * Build Append paths for each parameterization seen among the child rels.
--- 1874,1884 ----
  	if (subpaths_valid)
  		generate_mergeappend_paths(root, rel, live_childrels,
  								   all_child_pathkeys,
! 								   partitioned_rels, false);
! 	if (grouped_subpaths_valid)
! 		generate_mergeappend_paths(root, rel, live_childrels,
! 								   all_child_pathkeys,
! 								   partitioned_rels, true);
  
  	/*
  	 * Build Append paths for each parameterization seen among the child rels.
*************** static void
*** 1949,1957 ****
  generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
  						   List *live_childrels,
  						   List *all_child_pathkeys,
! 						   List *partitioned_rels)
  {
  	ListCell   *lcp;
  
  	foreach(lcp, all_child_pathkeys)
  	{
--- 1954,1964 ----
  generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
  						   List *live_childrels,
  						   List *all_child_pathkeys,
! 						   List *partitioned_rels,
! 						   bool grouped)
  {
  	ListCell   *lcp;
+ 	PathTarget *target = NULL;
  
  	foreach(lcp, all_child_pathkeys)
  	{
*************** generate_mergeappend_paths(PlannerInfo *
*** 1960,1982 ****
  		List	   *total_subpaths = NIL;
  		bool		startup_neq_total = false;
  		ListCell   *lcr;
  
  		/* Select the child paths for this ordering... */
  		foreach(lcr, live_childrels)
  		{
  			RelOptInfo *childrel = (RelOptInfo *) lfirst(lcr);
  			Path	   *cheapest_startup,
  					   *cheapest_total;
  
  			/* Locate the right paths, if they are available. */
  			cheapest_startup =
! 				get_cheapest_path_for_pathkeys(childrel->pathlist,
  											   pathkeys,
  											   NULL,
  											   STARTUP_COST,
  											   false);
  			cheapest_total =
! 				get_cheapest_path_for_pathkeys(childrel->pathlist,
  											   pathkeys,
  											   NULL,
  											   TOTAL_COST,
--- 1967,1998 ----
  		List	   *total_subpaths = NIL;
  		bool		startup_neq_total = false;
  		ListCell   *lcr;
+ 		Path	   *path;
  
  		/* Select the child paths for this ordering... */
  		foreach(lcr, live_childrels)
  		{
  			RelOptInfo *childrel = (RelOptInfo *) lfirst(lcr);
+ 			List	   *pathlist;
  			Path	   *cheapest_startup,
  					   *cheapest_total;
  
+ 			if (grouped &&
+ 				(childrel->gpi == NULL || childrel->gpi->pathlist == NIL))
+ 				return;
+ 
+ 			pathlist = !grouped ? childrel->pathlist :
+ 				childrel->gpi->pathlist;
+ 
  			/* Locate the right paths, if they are available. */
  			cheapest_startup =
! 				get_cheapest_path_for_pathkeys(pathlist,
  											   pathkeys,
  											   NULL,
  											   STARTUP_COST,
  											   false);
  			cheapest_total =
! 				get_cheapest_path_for_pathkeys(pathlist,
  											   pathkeys,
  											   NULL,
  											   TOTAL_COST,
*************** generate_mergeappend_paths(PlannerInfo *
*** 2008,2029 ****
  									  &total_subpaths, NULL);
  		}
  
  		/* ... and build the MergeAppend paths */
! 		add_path(rel, (Path *) create_merge_append_path(root,
! 														rel,
! 														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);
  	}
  }
  
--- 2024,2070 ----
  									  &total_subpaths, NULL);
  		}
  
+ 		/*
+ 		 * Special target is needed if the path output should be grouped.
+ 		 */
+ 		if (grouped)
+ 		{
+ 			Assert(rel->gpi != NULL);
+ 			target = rel->gpi->target;
+ 		}
+ 
  		/* ... and build the MergeAppend paths */
! 		path = (Path *) create_merge_append_path(root,
! 												 rel,
! 												 target,
! 												 startup_subpaths,
! 												 pathkeys,
! 												 NULL,
! 												 partitioned_rels);
! 
! 		/* pathtarget will produce the grouped relation.. */
! 		if (grouped)
! 		{
! 			Assert(rel->gpi != NULL && rel->gpi->target != NULL);
! 			path->pathtarget = rel->gpi->target;
! 		}
! 
! 		add_path(rel, path, grouped);
! 
  		if (startup_neq_total)
! 		{
! 			path = (Path *) create_merge_append_path(root,
! 													 rel,
! 													 target,
! 													 total_subpaths,
! 													 pathkeys,
! 													 NULL,
! 													 partitioned_rels);
! 			if (grouped)
! 				path->pathtarget = rel->gpi->target;
! 			add_path(rel, path, grouped);
! 		}
! 
  	}
  }
  
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
new file mode 100644
index ab739db..7422fc4
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
***************
*** 22,27 ****
--- 22,28 ----
  #include "optimizer/pathnode.h"
  #include "optimizer/paths.h"
  #include "optimizer/planmain.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;
*************** set_join_pathlist_hook_type set_join_pat
*** 39,44 ****
--- 40,49 ----
  #define PATH_PARAM_BY_REL(path, rel)	\
  	(PATH_PARAM_BY_REL_SELF(path, rel) || PATH_PARAM_BY_PARENT(path, rel))
  
+ #define REL_HAS_GROUPED_PATHS(rel) ((rel)->gpi && (rel)->gpi->pathlist)
+ #define REL_HAS_PARTIAL_GROUPED_PATHS(rel) \
+ 	((rel)->gpi && (rel)->gpi->partial_pathlist)
+ 
  /*
   * The sort-specific input for try_mergejoin_... functions.
   */
*************** static void try_partial_mergejoin_path(P
*** 56,62 ****
  						   Path *inner_path,
  						   MergeJoinSortInfo *si,
  						   JoinType jointype,
! 						   JoinPathExtraData *extra);
  static void sort_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
  					 RelOptInfo *outerrel, RelOptInfo *innerrel,
  					 JoinType jointype, JoinPathExtraData *extra);
--- 61,69 ----
  						   Path *inner_path,
  						   MergeJoinSortInfo *si,
  						   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);
*************** static void sort_inner_and_outer_common(
*** 66,72 ****
  							RelOptInfo *innerrel,
  							JoinType jointype,
  							JoinPathExtraData *extra,
! 							List *sort_infos);
  static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel,
  					 RelOptInfo *outerrel, RelOptInfo *innerrel,
  					 JoinType jointype, JoinPathExtraData *extra);
--- 73,82 ----
  							RelOptInfo *innerrel,
  							JoinType jointype,
  							JoinPathExtraData *extra,
! 							List *sort_infos,
! 							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);
*************** static void consider_parallel_nestloop(P
*** 75,88 ****
  						   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);
--- 85,101 ----
  						   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_outer,
! 							bool do_aggregate);
  static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
  					 RelOptInfo *outerrel, RelOptInfo *innerrel,
  					 JoinType jointype, JoinPathExtraData *extra);
*************** static void generate_mergejoin_paths(Pla
*** 102,108 ****
  						 bool useallclauses,
  						 Path *inner_cheapest_total,
  						 List *merge_pathkeys,
! 						 bool is_partial);
  
  
  /*
--- 115,124 ----
  						 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,
*** 317,324 ****
  	 * 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
--- 333,340 ----
  	 * 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
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 351,357 ****
   * across joins unless there's a join-order-constraint-based reason to do so.
   * So we ignore the param_source_rels restriction when this case applies.
   *
!  * allow_star_schema_join() returns true if the param_source_rels restriction
   * should be overridden, ie, it's okay to perform this join.
   */
  static inline bool
--- 367,373 ----
   * across joins unless there's a join-order-constraint-based reason to do so.
   * So we ignore the param_source_rels restriction when this case applies.
   *
!  * allow_star_schema_join() returns TRUE if the param_source_rels restriction
   * should be overridden, ie, it's okay to perform this join.
   */
  static inline bool
*************** try_nestloop_path(PlannerInfo *root,
*** 379,385 ****
  				  Path *inner_path,
  				  List *pathkeys,
  				  JoinType jointype,
! 				  JoinPathExtraData *extra)
  {
  	Relids		required_outer;
  	JoinCostWorkspace workspace;
--- 395,403 ----
  				  Path *inner_path,
  				  List *pathkeys,
  				  JoinType jointype,
! 				  JoinPathExtraData *extra,
! 				  bool grouped,
! 				  bool do_aggregate)
  {
  	Relids		required_outer;
  	JoinCostWorkspace workspace;
*************** try_nestloop_path(PlannerInfo *root,
*** 389,394 ****
--- 407,426 ----
  	Relids		outerrelids;
  	Relids		inner_paramrels = PATH_REQ_OUTER(inner_path);
  	Relids		outer_paramrels = PATH_REQ_OUTER(outer_path);
+ 	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 || !grouped);
+ 
+ 	/*
+ 	 * Aggregation target is necessary to produce grouped join output.
+ 	 */
+ 	Assert((joinrel->gpi && joinrel->gpi->target) || !grouped ||
+ 		   !do_aggregate);
  
  	/*
  	 * Paths are parameterized by top-level parents, so run parameterization
*************** try_nestloop_path(PlannerInfo *root,
*** 409,420 ****
  	 * parameterization wouldn't be sensible --- unless allow_star_schema_join
  	 * 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(outerrelids, outer_paramrels,
  												  innerrelids, inner_paramrels);
  	if (required_outer &&
! 		((!bms_overlap(required_outer, extra->param_source_rels) &&
  		  !allow_star_schema_join(root, outerrelids, inner_paramrels)) ||
  		 have_dangerous_phv(root, outerrelids, inner_paramrels)))
  	{
--- 441,453 ----
  	 * parameterization wouldn't be sensible --- unless allow_star_schema_join
  	 * 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. Finally reject parameterized aggregation.
  	 */
  	required_outer = calc_nestloop_required_outer(outerrelids, outer_paramrels,
  												  innerrelids, inner_paramrels);
  	if (required_outer &&
! 		(do_aggregate ||
! 		 (!bms_overlap(required_outer, extra->param_source_rels) &&
  		  !allow_star_schema_join(root, outerrelids, inner_paramrels)) ||
  		 have_dangerous_phv(root, outerrelids, inner_paramrels)))
  	{
*************** try_nestloop_path(PlannerInfo *root,
*** 435,476 ****
  	initial_cost_nestloop(root, &workspace, jointype,
  						  outer_path, inner_path, extra);
  
! 	if (add_path_precheck(joinrel,
! 						  workspace.startup_cost, workspace.total_cost,
! 						  pathkeys, required_outer, false))
  	{
  		/*
! 		 * If the inner path is parameterized, it is parameterized by the
! 		 * topmost parent of the outer rel, not the outer rel itself.  Fix
! 		 * that.
  		 */
! 		if (PATH_PARAM_BY_PARENT(inner_path, outer_path->parent))
  		{
! 			inner_path = reparameterize_path_by_child(root, inner_path,
! 													  outer_path->parent);
! 
! 			/*
! 			 * If we could not translate the path, we can't create nest loop
! 			 * path.
! 			 */
! 			if (!inner_path)
! 			{
! 				bms_free(required_outer);
! 				return;
! 			}
  		}
  
! 		add_path(joinrel, (Path *)
! 				 create_nestloop_path(root,
! 									  joinrel,
! 									  jointype,
! 									  &workspace,
! 									  extra,
! 									  outer_path,
! 									  inner_path,
! 									  extra->restrictlist,
! 									  pathkeys,
! 									  required_outer), false);
  	}
  	else
  	{
--- 468,526 ----
  	initial_cost_nestloop(root, &workspace, jointype,
  						  outer_path, inner_path, extra);
  
! 	/*
! 	 * 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;
! 
! 	/*
! 	 * If the inner path is parameterized, it is parameterized by the topmost
! 	 * parent of the outer rel, not the outer rel itself.  Fix that.
! 	 */
! 	if (PATH_PARAM_BY_PARENT(inner_path, outer_path->parent))
  	{
+ 		inner_path = reparameterize_path_by_child(root, inner_path,
+ 												  outer_path->parent);
+ 
  		/*
! 		 * If we could not translate the path, we can't create nest loop path.
  		 */
! 		if (!inner_path)
  		{
! 			bms_free(required_outer);
! 			return;
  		}
+ 	}
  
! 	join_path = (Path *) create_nestloop_path(root, joinrel, jointype,
! 											  &workspace, extra,
! 											  outer_path, inner_path,
! 											  extra->restrictlist, pathkeys,
! 											  required_outer, join_target);
! 
! 	/* Do partial aggregation if needed. */
! 	if (do_aggregate && required_outer == NULL)
! 	{
! 		PartialAggInfo *agg_info = get_partial_agg_info(root,
! 														joinrel->gpi->target,
! 														joinrel->gpi->sortgroupclauses);
! 
! 		create_grouped_path(root, joinrel, join_path, true, false,
! 							AGG_HASHED, agg_info);
! 		create_grouped_path(root, joinrel, join_path, true, false,
! 							AGG_SORTED, agg_info);
! 	}
! 	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
*** 491,499 ****
  						  Path *inner_path,
  						  List *pathkeys,
  						  JoinType jointype,
! 						  JoinPathExtraData *extra)
  {
  	JoinCostWorkspace workspace;
  
  	/*
  	 * If the inner path is parameterized, the parameterization must be fully
--- 541,559 ----
  						  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 || !grouped);
+ 	Assert((joinrel->gpi && joinrel->gpi->target) || !grouped ||
+ 		   !do_aggregate);
  
  	/*
  	 * If the inner path is parameterized, the parameterization must be fully
*************** try_partial_nestloop_path(PlannerInfo *r
*** 528,535 ****
  	 */
  	initial_cost_nestloop(root, &workspace, jointype,
  						  outer_path, inner_path, extra);
! 	if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys,
! 								   false))
  		return;
  
  	/*
--- 588,655 ----
  	 */
  	initial_cost_nestloop(root, &workspace, jointype,
  						  outer_path, inner_path, extra);
! 
! 	/*
! 	 * 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,
! 											  outer_path, inner_path,
! 											  extra->restrictlist, pathkeys,
! 											  NULL, join_target);
! 
! 	if (do_aggregate)
! 	{
! 		PartialAggInfo *agg_info = get_partial_agg_info(root,
! 														joinrel->gpi->target,
! 														joinrel->gpi->sortgroupclauses);
! 
! 		create_grouped_path(root, joinrel, join_path, true, true, AGG_HASHED,
! 							agg_info);
! 		create_grouped_path(root, joinrel, join_path, true, true, AGG_SORTED,
! 							agg_info);
! 	}
! 	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 partial,
! 						  bool do_aggregate)
! {
! 	/*
! 	 * Missing GroupedPathInfo indicates that we should not try to create a
! 	 * grouped join.
! 	 */
! 	if (joinrel->gpi == NULL)
! 		return;
! 
! 	/*
! 	 * Can't preform explicit aggregation w/o the appropriate target.
! 	 */
! 	if (do_aggregate && joinrel->gpi->target == NULL)
  		return;
  
  	/*
*************** try_partial_nestloop_path(PlannerInfo *r
*** 548,565 ****
  			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,
! 										  outer_path,
! 										  inner_path,
! 										  extra->restrictlist,
! 										  pathkeys,
! 										  NULL), false);
  }
  
  static void
--- 668,680 ----
  			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);
  }
  
  static void
*************** try_nestloop_path_common(PlannerInfo *ro
*** 570,598 ****
  						 List *pathkeys,
  						 JoinType jointype,
  						 JoinPathExtraData *extra,
! 						 bool partial)
  {
  
! 	/* Only join plain paths. */
! 	if (!partial)
! 		try_nestloop_path(root,
! 						  joinrel,
! 						  outer_path,
! 						  inner_path,
! 						  pathkeys,
! 						  jointype,
! 						  extra);
  	else
! 		try_partial_nestloop_path(root,
  								  joinrel,
  								  outer_path,
  								  inner_path,
  								  pathkeys,
  								  jointype,
! 								  extra);
  }
  
- 
  /*
   * Create one MergeJoinSortInfo for each possible use of merge clauses.
   */
--- 685,744 ----
  						 List *pathkeys,
  						 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. */
! 		if (!partial)
! 			try_nestloop_path(root,
! 							  joinrel,
! 							  outer_path,
! 							  inner_path,
! 							  pathkeys,
! 							  jointype,
! 							  extra,
! 							  grouped_join,
! 							  do_aggregate);
! 		else
! 			try_partial_nestloop_path(root,
! 									  joinrel,
! 									  outer_path,
! 									  inner_path,
! 									  pathkeys,
! 									  jointype,
! 									  extra,
! 									  grouped_join,
! 									  do_aggregate);
! 	}
  	else
! 	{
! 		/*
! 		 * Either exactly one of the input paths should be grouped, or no one
! 		 * is grouped and do_aggregate is true.
! 		 */
! 		try_grouped_nestloop_path(root,
  								  joinrel,
  								  outer_path,
  								  inner_path,
  								  pathkeys,
  								  jointype,
! 								  extra,
! 								  partial,
! 								  do_aggregate);
! 	}
  }
  
  /*
   * Create one MergeJoinSortInfo for each possible use of merge clauses.
   */
*************** try_mergejoin_path(PlannerInfo *root,
*** 687,711 ****
  				   Path *inner_path,
  				   MergeJoinSortInfo *si,
  				   JoinType jointype,
! 				   JoinPathExtraData *extra)
  {
  	Relids		required_outer;
  	JoinCostWorkspace workspace;
  	List	   *outersortkeys = si->outerkeys;
  	List	   *innersortkeys = si->innerkeys;
  
  	/*
  	 * 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;
  	}
  
  	/*
--- 833,876 ----
  				   Path *inner_path,
  				   MergeJoinSortInfo *si,
  				   JoinType jointype,
! 				   JoinPathExtraData *extra,
! 				   bool grouped,
! 				   bool do_aggregate)
  {
  	Relids		required_outer;
  	JoinCostWorkspace workspace;
  	List	   *outersortkeys = si->outerkeys;
  	List	   *innersortkeys = si->innerkeys;
+ 	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 || !grouped);
+ 
+ 	/*
+ 	 * Aggregation target is necessary to produce grouped join output.
+ 	 */
+ 	Assert((joinrel->gpi && joinrel->gpi->target) || !grouped ||
+ 		   !do_aggregate);
  
  	/*
  	 * Check to see if proposed path is still parameterized, and reject if the
! 	 * parameterization wouldn't be sensible. Also reject parameterized
! 	 * aggregation.
  	 */
! 	required_outer = calc_non_nestloop_required_outer(outer_path, inner_path);
! 	if (required_outer)
  	{
! 		if (!bms_overlap(required_outer, extra->param_source_rels) ||
! 			do_aggregate)
! 		{
! 			/* Waste no memory when we reject a path here */
! 			bms_free(required_outer);
! 			return;
! 		}
  	}
  
  	/*
*************** try_mergejoin_path(PlannerInfo *root,
*** 726,749 ****
  						   outer_path, inner_path,
  						   outersortkeys, innersortkeys, extra);
  
! 	if (add_path_precheck(joinrel,
! 						  workspace.startup_cost, workspace.total_cost,
! 						  si->merge_pathkeys, required_outer, false))
  	{
! 		add_path(joinrel, (Path *)
! 				 create_mergejoin_path(root,
! 									   joinrel,
! 									   jointype,
! 									   &workspace,
! 									   extra,
! 									   outer_path,
! 									   inner_path,
! 									   extra->restrictlist,
! 									   si->merge_pathkeys,
! 									   required_outer,
! 									   si->merge_clauses,
! 									   outersortkeys,
! 									   innersortkeys), false);
  	}
  	else
  	{
--- 891,939 ----
  						   outer_path, inner_path,
  						   outersortkeys, innersortkeys, extra);
  
! 	/*
! 	 * 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,
! 											   outer_path,
! 											   inner_path,
! 											   extra->restrictlist,
! 											   si->merge_pathkeys,
! 											   required_outer,
! 											   si->merge_clauses,
! 											   outersortkeys,
! 											   innersortkeys,
! 											   join_target);
! 
! 	/* Do partial aggregation if needed. */
! 	if (do_aggregate)
  	{
! 		PartialAggInfo *agg_info = get_partial_agg_info(root,
! 														joinrel->gpi->target,
! 														joinrel->gpi->sortgroupclauses);
! 
! 		create_grouped_path(root, joinrel, join_path, true, false, AGG_HASHED,
! 							agg_info);
! 		create_grouped_path(root, joinrel, join_path, true, false, AGG_SORTED,
! 							agg_info);
! 	}
! 	else if (add_path_precheck(joinrel,
! 							   workspace.startup_cost, workspace.total_cost,
! 							   si->merge_pathkeys, required_outer, grouped))
! 	{
! 		add_path(joinrel, (Path *) join_path, grouped);
  	}
  	else
  	{
*************** try_partial_mergejoin_path(PlannerInfo *
*** 764,774 ****
  						   Path *inner_path,
  						   MergeJoinSortInfo *si,
  						   JoinType jointype,
! 						   JoinPathExtraData *extra)
  {
  	JoinCostWorkspace workspace;
  	List	   *outersortkeys = si->outerkeys;
  	List	   *innersortkeys = si->innerkeys;
  
  	/*
  	 * See comments in try_partial_hashjoin_path().
--- 954,974 ----
  						   Path *inner_path,
  						   MergeJoinSortInfo *si,
  						   JoinType jointype,
! 						   JoinPathExtraData *extra,
! 						   bool grouped,
! 						   bool do_aggregate)
  {
  	JoinCostWorkspace workspace;
  	List	   *outersortkeys = si->outerkeys;
  	List	   *innersortkeys = si->innerkeys;
+ 	Path	   *join_path;
+ 	PathTarget *join_target;
+ 
+ 	/* The same checks we do in try_mergejoin_path. */
+ 	Assert(!do_aggregate || grouped);
+ 	Assert(joinrel->gpi || !grouped);
+ 	Assert((joinrel->gpi && joinrel->gpi->target) || !grouped ||
+ 		   !do_aggregate);
  
  	/*
  	 * See comments in try_partial_hashjoin_path().
*************** try_partial_mergejoin_path(PlannerInfo *
*** 800,825 ****
  						   outer_path, inner_path,
  						   outersortkeys, innersortkeys, extra);
  
! 	if (!add_partial_path_precheck(joinrel, workspace.total_cost,
! 								   si->merge_pathkeys,
! 								   false))
  		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,
! 										   outer_path,
! 										   inner_path,
! 										   extra->restrictlist,
! 										   si->merge_pathkeys,
! 										   NULL,
! 										   si->merge_clauses,
! 										   outersortkeys,
! 										   innersortkeys), false);
  }
  
  /*
--- 1000,1083 ----
  						   outer_path, inner_path,
  						   outersortkeys, innersortkeys, extra);
  
! 	/*
! 	 * 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,
! 											   outer_path,
! 											   inner_path,
! 											   extra->restrictlist,
! 											   si->merge_pathkeys,
! 											   NULL,
! 											   si->merge_clauses,
! 											   outersortkeys,
! 											   innersortkeys,
! 											   join_target);
! 
! 	if (do_aggregate)
! 	{
! 		PartialAggInfo *agg_info = get_partial_agg_info(root,
! 														joinrel->gpi->target,
! 														joinrel->gpi->sortgroupclauses);
! 
! 		create_grouped_path(root, joinrel, join_path, true, true, AGG_HASHED,
! 							agg_info);
! 		create_grouped_path(root, joinrel, join_path, true, true, AGG_SORTED,
! 							agg_info);
! 	}
! 	else if (add_partial_path_precheck(joinrel, workspace.total_cost,
! 									   si->merge_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,
! 						   MergeJoinSortInfo *si,
! 						   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;
  
! 	/*
! 	 * Can't preform explicit aggregation w/o the appropriate target.
! 	 */
! 	if (do_aggregate && joinrel->gpi->target == NULL)
! 		return;
! 
! 	if (!partial)
! 		try_mergejoin_path(root, joinrel, outer_path, inner_path, si,
! 						   jointype, extra, true, do_aggregate);
! 	else
! 		try_partial_mergejoin_path(root, joinrel, outer_path, inner_path, si,
! 								   jointype, extra, true, do_aggregate);
  }
  
  /*
*************** try_mergejoin_path_common(PlannerInfo *r
*** 834,858 ****
  						  MergeJoinSortInfo *si,
  						  JoinType jointype,
  						  JoinPathExtraData *extra,
! 						  bool partial)
  {
! 	/* Only join plain paths. */
! 	if (!partial)
! 		try_mergejoin_path(root,
! 						   joinrel,
! 						   outer_path,
! 						   inner_path,
! 						   si,
! 						   jointype,
! 						   extra);
  	else
! 		try_partial_mergejoin_path(root,
  								   joinrel,
  								   outer_path,
  								   inner_path,
  								   si,
  								   jointype,
! 								   extra);
  }
  
  /*
--- 1092,1147 ----
  						  MergeJoinSortInfo *si,
  						  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. */
! 		if (!partial)
! 			try_mergejoin_path(root,
! 							   joinrel,
! 							   outer_path,
! 							   inner_path,
! 							   si,
! 							   jointype,
! 							   extra,
! 							   false, false);
! 		else
! 			try_partial_mergejoin_path(root,
! 									   joinrel,
! 									   outer_path,
! 									   inner_path,
! 									   si,
! 									   jointype,
! 									   extra,
! 									   false, false);
! 	}
  	else
! 	{
! 		/*
! 		 * Either exactly one of the input paths should be grouped, or no one
! 		 * is grouped and do_aggregate is true.
! 		 */
! 		try_grouped_mergejoin_path(root,
  								   joinrel,
  								   outer_path,
  								   inner_path,
  								   si,
  								   jointype,
! 								   extra,
! 								   partial,
! 								   do_aggregate);
! 	}
  }
  
  /*
*************** try_hashjoin_path(PlannerInfo *root,
*** 867,889 ****
  				  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;
  	}
  
  	/*
--- 1156,1197 ----
  				  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 || !grouped);
+ 
+ 	/*
+ 	 * Aggregation target is necessary to produce grouped join output.
+ 	 */
+ 	Assert((joinrel->gpi && joinrel->gpi->target) || !grouped ||
+ 		   !do_aggregate);
  
  	/*
  	 * Check to see if proposed path is still parameterized, and reject if the
! 	 * parameterization wouldn't be sensible. Also reject parameterized
! 	 * aggregation.
  	 */
! 	required_outer = calc_non_nestloop_required_outer(outer_path, inner_path);
! 	if (required_outer)
  	{
! 		if (!bms_overlap(required_outer, extra->param_source_rels) ||
! 			do_aggregate)
! 		{
! 			/* Waste no memory when we reject a path here */
! 			bms_free(required_outer);
! 			return;
! 		}
  	}
  
  	/*
*************** try_hashjoin_path(PlannerInfo *root,
*** 893,914 ****
  	initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
  						  outer_path, inner_path, extra, false);
  
! 	if (add_path_precheck(joinrel,
! 						  workspace.startup_cost, workspace.total_cost,
! 						  NIL, required_outer, false))
  	{
! 		add_path(joinrel, (Path *)
! 				 create_hashjoin_path(root,
! 									  joinrel,
! 									  jointype,
! 									  &workspace,
! 									  extra,
! 									  outer_path,
! 									  inner_path,
! 									  false,	/* parallel_hash */
! 									  extra->restrictlist,
! 									  required_outer,
! 									  hashclauses), false);
  	}
  	else
  	{
--- 1201,1245 ----
  	initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
  						  outer_path, inner_path, extra, false);
  
! 	/*
! 	 * 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,
! 											  outer_path, inner_path,
! 											  false,	/* parallel_hash */
! 											  extra->restrictlist,
! 											  required_outer, hashclauses,
! 											  join_target);
! 
! 	/* Do partial aggregation if needed. */
! 	if (do_aggregate)
  	{
! 		PartialAggInfo *agg_info = get_partial_agg_info(root,
! 														joinrel->gpi->target,
! 														joinrel->gpi->sortgroupclauses);
! 
! 		/*
! 		 * Only AGG_HASHED is suitable here as it does not expect the input
! 		 * set to be sorted.
! 		 */
! 		create_grouped_path(root, joinrel, join_path, true, false, AGG_HASHED,
! 							agg_info);
! 	}
! 	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
*** 934,942 ****
  						  List *hashclauses,
  						  JoinType jointype,
  						  JoinPathExtraData *extra,
! 						  bool parallel_hash)
  {
  	JoinCostWorkspace workspace;
  
  	/*
  	 * If the inner path is parameterized, the parameterization must be fully
--- 1265,1283 ----
  						  List *hashclauses,
  						  JoinType jointype,
  						  JoinPathExtraData *extra,
! 						  bool parallel_hash,
! 						  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 || !grouped);
+ 	Assert((joinrel->gpi && joinrel->gpi->target) || !grouped ||
+ 		   !do_aggregate);
  
  	/*
  	 * If the inner path is parameterized, the parameterization must be fully
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 958,980 ****
  	 * cost.  Bail out right away if it looks terrible.
  	 */
  	initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
! 						  outer_path, inner_path, extra, true);
! 	if (!add_partial_path_precheck(joinrel, workspace.total_cost, NIL, false))
  		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,
! 										  outer_path,
! 										  inner_path,
! 										  parallel_hash,
! 										  extra->restrictlist,
! 										  NULL,
! 										  hashclauses), false);
  }
  
  /*
--- 1299,1393 ----
  	 * cost.  Bail out right away if it looks terrible.
  	 */
  	initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
! 						  outer_path, inner_path, extra, parallel_hash);
! 
! 	/*
! 	 * 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,
! 											  outer_path, inner_path,
! 											  parallel_hash,
! 											  extra->restrictlist, NULL,
! 											  hashclauses, join_target);
! 
! 	/* Do partial aggregation if needed. */
! 	if (do_aggregate)
! 	{
! 		PartialAggInfo *agg_info = get_partial_agg_info(root,
! 														joinrel->gpi->target,
! 														joinrel->gpi->sortgroupclauses);
! 
! 		create_grouped_path(root, joinrel, join_path, true, true, AGG_HASHED,
! 							agg_info);
! 	}
! 	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 partial,
! 						  bool parallel_hash,
! 						  bool do_aggregate
! )
! {
! 	/*
! 	 * Missing GroupedPathInfo indicates that we should not try to create a
! 	 * grouped join.
! 	 */
! 	if (joinrel->gpi == NULL)
  		return;
  
! 	/*
! 	 * Can't preform explicit aggregation w/o the appropriate target.
! 	 */
! 	if (do_aggregate && joinrel->gpi->target == 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, parallel_hash,
! 								  true, do_aggregate);
  }
  
  /*
*************** try_hashjoin_path_common(PlannerInfo *ro
*** 990,1020 ****
  						 JoinType jointype,
  						 JoinPathExtraData *extra,
  						 bool partial,
! 						 bool parallel_hash)
  {
! 	/* parallel_hash can only be set if partial is too. */
! 	Assert(!parallel_hash || partial);
  
! 	/* Only join plain paths. */
! 	if (!partial)
! 		try_hashjoin_path(root,
! 						  joinrel,
! 						  outer_path,
! 						  inner_path,
! 						  hashclauses,
! 						  jointype,
! 						  extra);
  	else
! 		try_partial_hashjoin_path(root,
  								  joinrel,
  								  outer_path,
  								  inner_path,
  								  hashclauses,
  								  jointype,
  								  extra,
! 								  parallel_hash);
  }
  
  /*
   * clause_sides_match_join
   *	  Determine whether a join clause is of the right form to use in this join.
--- 1403,1463 ----
  						 JoinType jointype,
  						 JoinPathExtraData *extra,
  						 bool partial,
! 						 bool parallel_hash,
! 						 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. */
! 		if (!partial)
! 			try_hashjoin_path(root,
! 							  joinrel,
! 							  outer_path,
! 							  inner_path,
! 							  hashclauses,
! 							  jointype,
! 							  extra,
! 							  false, false);
! 		else
! 			try_partial_hashjoin_path(root,
! 									  joinrel,
! 									  outer_path,
! 									  inner_path,
! 									  hashclauses,
! 									  jointype,
! 									  extra,
! 									  parallel_hash,
! 									  false, false);
! 	}
  	else
! 	{
! 		/*
! 		 * Either exactly one of the input paths should be grouped, or no one
! 		 * is grouped and do_aggregate is true.
! 		 */
! 		try_grouped_hashjoin_path(root,
  								  joinrel,
  								  outer_path,
  								  inner_path,
  								  hashclauses,
  								  jointype,
  								  extra,
! 								  partial,
! 								  parallel_hash,
! 								  do_aggregate);
! 	}
  }
  
+ 
  /*
   * clause_sides_match_join
   *	  Determine whether a join clause is of the right form to use in this join.
*************** sort_inner_and_outer(PlannerInfo *root,
*** 1068,1076 ****
  	List	   *sort_infos = get_merge_join_sort_info(root, extra, joinrel,
  													  jointype);
  
  
  	sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! 								jointype, extra, sort_infos);
  }
  
  /*
--- 1511,1533 ----
  	List	   *sort_infos = get_merge_join_sort_info(root, extra, joinrel,
  													  jointype);
  
+ 	/* Plain (non-grouped) join. */
+ 	sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
+ 								jointype, extra, sort_infos, false, false,
+ 								false);
  
+ 	/* Use all the supported strategies to generate grouped join. */
  	sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! 								jointype, extra, sort_infos, true, false,
! 								false);
! 	sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! 								jointype, extra, sort_infos, false, true,
! 								false);
! 	sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! 								jointype, extra, sort_infos, false, false,
! 								true);
! 
! 	list_free_deep(sort_infos);
  }
  
  /*
*************** sort_inner_and_outer_common(PlannerInfo
*** 1086,1092 ****
  							RelOptInfo *innerrel,
  							JoinType jointype,
  							JoinPathExtraData *extra,
! 							List *sort_infos)
  {
  	JoinType	save_jointype = jointype;
  	Path	   *outer_path;
--- 1543,1552 ----
  							RelOptInfo *innerrel,
  							JoinType jointype,
  							JoinPathExtraData *extra,
! 							List *sort_infos,
! 							bool grouped_outer,
! 							bool grouped_inner,
! 							bool do_aggregate)
  {
  	JoinType	save_jointype = jointype;
  	Path	   *outer_path;
*************** sort_inner_and_outer_common(PlannerInfo
*** 1108,1115 ****
  	 * 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
--- 1568,1592 ----
  	 * against mergejoins with parameterized inputs; see comments in
  	 * src/backend/optimizer/README.
  	 */
! 	if (grouped_outer)
! 	{
! 		if (REL_HAS_GROUPED_PATHS(outerrel))
! 			outer_path = linitial(outerrel->gpi->pathlist);
! 		else
! 			return;
! 	}
! 	else
! 		outer_path = outerrel->cheapest_total_path;
! 
! 	if (grouped_inner)
! 	{
! 		if (REL_HAS_GROUPED_PATHS(innerrel))
! 			inner_path = linitial(innerrel->gpi->pathlist);
! 		else
! 			return;
! 	}
! 	else
! 		inner_path = innerrel->cheapest_total_path;
  
  	/*
  	 * If either cheapest-total path is parameterized by the other rel, we
*************** sort_inner_and_outer_common(PlannerInfo
*** 1127,1132 ****
--- 1604,1619 ----
  	 */
  	if (jointype == JOIN_UNIQUE_OUTER)
  	{
+ 		/*
+ 		 * TODO This is just a temporary limitation. Before lifting it, make
+ 		 * sure that the UniquePath does emit GroupedVars. Also try to avoid
+ 		 * the unique-ification if outer_path comes directly from AggPath
+ 		 * (i.e. it's not grouped path combined with plain one) and the
+ 		 * grouping keys guaranteed the uniqueness.
+ 		 */
+ 		if (grouped_outer)
+ 			return;
+ 
  		outer_path = (Path *) create_unique_path(root, outerrel,
  												 outer_path, extra->sjinfo);
  		Assert(outer_path);
*************** sort_inner_and_outer_common(PlannerInfo
*** 1134,1139 ****
--- 1621,1630 ----
  	}
  	else if (jointype == JOIN_UNIQUE_INNER)
  	{
+ 		/* TODO Temporary limitation, like above. */
+ 		if (grouped_inner)
+ 			return;
+ 
  		inner_path = (Path *) create_unique_path(root, innerrel,
  												 inner_path, extra->sjinfo);
  		Assert(inner_path);
*************** sort_inner_and_outer_common(PlannerInfo
*** 1155,1167 ****
  		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);
  	}
  
  	foreach(l, sort_infos)
--- 1646,1695 ----
  		outerrel->partial_pathlist != NIL &&
  		bms_is_empty(joinrel->lateral_relids))
  	{
! 		if (grouped_outer)
! 		{
! 			if (REL_HAS_PARTIAL_GROUPED_PATHS(outerrel))
! 				cheapest_partial_outer = (Path *)
! 					linitial(outerrel->gpi->partial_pathlist);
! 			else
! 				return;
! 		}
! 		else
! 			cheapest_partial_outer = (Path *)
! 				linitial(outerrel->partial_pathlist);
! 
! 		if (grouped_inner)
! 		{
! 			if (REL_HAS_GROUPED_PATHS(innerrel))
! 				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);
! 		}
  	}
  
  	foreach(l, sort_infos)
*************** sort_inner_and_outer_common(PlannerInfo
*** 1176,1182 ****
  		 * explicit sort step, so we needn't do so here.
  		 */
  		try_mergejoin_path_common(root, joinrel, outer_path, inner_path,
! 								  si, jointype, extra, false);
  
  		/*
  		 * If we have partial outer and parallel safe inner path then try
--- 1704,1712 ----
  		 * explicit sort step, so we needn't do so here.
  		 */
  		try_mergejoin_path_common(root, joinrel, outer_path, inner_path,
! 								  si, jointype, extra,
! 								  false, grouped_outer, grouped_inner,
! 								  do_aggregate);
  
  		/*
  		 * If we have partial outer and parallel safe inner path then try
*************** sort_inner_and_outer_common(PlannerInfo
*** 1187,1193 ****
  									  cheapest_partial_outer,
  									  cheapest_safe_inner,
  									  si, jointype, extra,
! 									  true);
  	}
  }
  
--- 1717,1724 ----
  									  cheapest_partial_outer,
  									  cheapest_safe_inner,
  									  si, jointype, extra,
! 									  true, grouped_outer, grouped_inner,
! 									  do_aggregate);
  	}
  }
  
*************** sort_inner_and_outer_common(PlannerInfo
*** 1204,1209 ****
--- 1735,1744 ----
   * 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?
   */
  static void
  generate_mergejoin_paths(PlannerInfo *root,
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1215,1221 ****
  						 bool useallclauses,
  						 Path *inner_cheapest_total,
  						 List *merge_pathkeys,
! 						 bool is_partial)
  {
  	List	   *trialsortkeys;
  	Path	   *cheapest_startup_inner;
--- 1750,1759 ----
  						 bool useallclauses,
  						 Path *inner_cheapest_total,
  						 List *merge_pathkeys,
! 						 bool is_partial,
! 						 bool grouped_outer,
! 						 bool grouped_inner,
! 						 bool do_aggregate)
  {
  	List	   *trialsortkeys;
  	Path	   *cheapest_startup_inner;
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1274,1280 ****
  							  &si,
  							  jointype,
  							  extra,
! 							  is_partial);
  
  	/* Can't do anything else if inner path needs to be unique'd */
  	if (save_jointype == JOIN_UNIQUE_INNER)
--- 1812,1819 ----
  							  &si,
  							  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
*** 1330,1335 ****
--- 1869,1875 ----
  
  	for (sortkeycnt = num_sortkeys; sortkeycnt > 0; sortkeycnt--)
  	{
+ 		List	   *inner_pathlist = NIL;
  		Path	   *innerpath;
  		MergeJoinSortInfo si_trial;
  
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1338,1350 ****
  		si_trial.merge_pathkeys = si.merge_pathkeys;
  		si_trial.merge_clauses = 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,
--- 1878,1895 ----
  		si_trial.merge_pathkeys = si.merge_pathkeys;
  		si_trial.merge_clauses = 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' si.innerkeys.  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
*** 1372,1385 ****
  									  joinrel,
  									  outerpath,
  									  innerpath,
! 									  &si,
  									  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,
--- 1917,1933 ----
  									  joinrel,
  									  outerpath,
  									  innerpath,
! 									  &si_trial,
  									  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
*** 1414,1423 ****
  										  joinrel,
  										  outerpath,
  										  innerpath,
! 										  &si,
  										  jointype,
  										  extra,
! 										  is_partial);
  			}
  			cheapest_startup_inner = innerpath;
  		}
--- 1962,1973 ----
  										  joinrel,
  										  outerpath,
  										  innerpath,
! 										  &si_trial,
  										  jointype,
  										  extra,
! 										  is_partial,
! 										  grouped_outer, grouped_inner,
! 										  do_aggregate);
  			}
  			cheapest_startup_inner = innerpath;
  		}
*************** match_unsorted_outer_common(PlannerInfo
*** 1436,1449 ****
  							RelOptInfo *outerrel,
  							RelOptInfo *innerrel,
  							JoinType jointype,
! 							JoinPathExtraData *extra)
  {
  	JoinType	save_jointype = jointype;
  	bool		nestjoinOK;
  	bool		useallclauses;
! 	Path	   *inner_cheapest_total = innerrel->cheapest_total_path;
  	Path	   *matpath = NULL;
  	ListCell   *lc1;
  
  	/*
  	 * Nestloop only supports inner, left, semi, and anti joins.  Also, if we
--- 1986,2006 ----
  							RelOptInfo *outerrel,
  							RelOptInfo *innerrel,
  							JoinType jointype,
! 							JoinPathExtraData *extra,
! 							bool grouped_outer,
! 							bool grouped_inner,
! 							bool do_aggregate)
  {
  	JoinType	save_jointype = jointype;
  	bool		nestjoinOK;
  	bool		useallclauses;
! 	Path	   *inner_cheapest_total;
  	Path	   *matpath = NULL;
  	ListCell   *lc1;
+ 	List	   *outer_pathlist;
+ 
+ 	/* At most one input path may be grouped. */
+ 	Assert(!(grouped_outer && grouped_inner));
  
  	/*
  	 * Nestloop only supports inner, left, semi, and anti joins.  Also, if we
*************** match_unsorted_outer_common(PlannerInfo
*** 1480,1492 ****
--- 2037,2074 ----
  			break;
  	}
  
+ 	if (grouped_outer)
+ 	{
+ 		if (REL_HAS_GROUPED_PATHS(outerrel))
+ 			outer_pathlist = outerrel->gpi->pathlist;
+ 		else
+ 			return;
+ 	}
+ 	else
+ 		outer_pathlist = outerrel->pathlist;
+ 
+ 	if (grouped_inner)
+ 	{
+ 		if (REL_HAS_GROUPED_PATHS(innerrel))
+ 			inner_cheapest_total = linitial(innerrel->gpi->pathlist);
+ 		else
+ 			return;
+ 	}
+ 	else
+ 		inner_cheapest_total = innerrel->cheapest_total_path;
+ 
  	/*
  	 * If inner_cheapest_total is parameterized by the outer rel, ignore it;
  	 * we will consider it below as a member of cheapest_parameterized_paths,
  	 * but the other possibilities considered in this routine aren't usable.
  	 */
  	if (PATH_PARAM_BY_REL(inner_cheapest_total, outerrel))
+ 	{
+ 		/* Grouped path should not be parameterized at all. */
+ 		Assert(!grouped_inner);
+ 
  		inner_cheapest_total = NULL;
+ 	}
  
  	/*
  	 * If we need to unique-ify the inner path, we will consider only the
*************** match_unsorted_outer_common(PlannerInfo
*** 1498,1513 ****
  		if (inner_cheapest_total == NULL)
  			return;
  
  		inner_cheapest_total = (Path *)
  			create_unique_path(root, innerrel, inner_cheapest_total, extra->sjinfo);
  		Assert(inner_cheapest_total);
  	}
! 	else if (nestjoinOK)
  	{
  		/*
  		 * Consider materializing the cheapest inner path, unless
  		 * enable_material is off or the path in question materializes its
  		 * output anyway.
  		 */
  		if (enable_material && inner_cheapest_total != NULL &&
  			!ExecMaterializesOutput(inner_cheapest_total->pathtype))
--- 2080,2109 ----
  		if (inner_cheapest_total == NULL)
  			return;
  
+ 		/*
+ 		 * TODO This is just a temporary limitation. Before lifting it, make
+ 		 * sure that the UniquePath does emit GroupedVars. Also try to avoid
+ 		 * the unique-ification if outer_path comes directly from AggPath
+ 		 * (i.e. it's not grouped path combined with plain one) and the
+ 		 * grouping keys guaranteed the uniqueness.
+ 		 */
+ 		if (grouped_inner)
+ 			return;
+ 
  		inner_cheapest_total = (Path *)
  			create_unique_path(root, innerrel, inner_cheapest_total, extra->sjinfo);
  		Assert(inner_cheapest_total);
  	}
! 	else if (nestjoinOK && !grouped_inner)
  	{
  		/*
  		 * Consider materializing the cheapest inner path, unless
  		 * enable_material is off or the path in question materializes its
  		 * output anyway.
+ 		 *
+ 		 * TODO Verify that this is ok even if grouped_inner is true (at least
+ 		 * make sure that mathpath does contain GroupedVars) and remove
+ 		 * grouped_inner from the condition above.
  		 */
  		if (enable_material && inner_cheapest_total != NULL &&
  			!ExecMaterializesOutput(inner_cheapest_total->pathtype))
*************** match_unsorted_outer_common(PlannerInfo
*** 1515,1521 ****
  				create_material_path(innerrel, inner_cheapest_total);
  	}
  
! 	foreach(lc1, outerrel->pathlist)
  	{
  		Path	   *outerpath = (Path *) lfirst(lc1);
  		List	   *merge_pathkeys;
--- 2111,2117 ----
  				create_material_path(innerrel, inner_cheapest_total);
  	}
  
! 	foreach(lc1, outer_pathlist)
  	{
  		Path	   *outerpath = (Path *) lfirst(lc1);
  		List	   *merge_pathkeys;
*************** match_unsorted_outer_common(PlannerInfo
*** 1536,1541 ****
--- 2132,2141 ----
  			if (outerpath != outerrel->cheapest_total_path)
  				continue;
  
+ 			/* TODO Temporary restriction, see above. */
+ 			if (grouped_outer)
+ 				continue;
+ 
  			outerpath = (Path *) create_unique_path(root, outerrel,
  													outerpath, extra->sjinfo);
  			Assert(outerpath);
*************** match_unsorted_outer_common(PlannerInfo
*** 1551,1556 ****
--- 2151,2160 ----
  
  		if (save_jointype == JOIN_UNIQUE_INNER)
  		{
+ 			/* TODO Temporary restriction, see above. */
+ 			if (grouped_inner)
+ 				continue;
+ 
  			/*
  			 * Consider nestloop join, but only with the unique-ified cheapest
  			 * inner path
*************** match_unsorted_outer_common(PlannerInfo
*** 1562,1568 ****
  									 merge_pathkeys,
  									 jointype,
  									 extra,
! 									 false);
  		}
  		else if (nestjoinOK)
  		{
--- 2166,2173 ----
  									 merge_pathkeys,
  									 jointype,
  									 extra,
! 									 false, grouped_outer, grouped_inner,
! 									 do_aggregate);
  		}
  		else if (nestjoinOK)
  		{
*************** match_unsorted_outer_common(PlannerInfo
*** 1574,1594 ****
  			 */
  			ListCell   *lc2;
  
! 			foreach(lc2, innerrel->cheapest_parameterized_paths)
  			{
! 				Path	   *innerpath = (Path *) lfirst(lc2);
  
! 				try_nestloop_path_common(root,
! 										 joinrel,
! 										 outerpath,
! 										 innerpath,
! 										 merge_pathkeys,
! 										 jointype,
! 										 extra,
! 										 false);
  			}
  
! 			/* Also consider materialized form of the cheapest inner path. */
  			if (matpath != NULL)
  				try_nestloop_path_common(root,
  										 joinrel,
--- 2179,2208 ----
  			 */
  			ListCell   *lc2;
  
! 			/*
! 			 * There are no grouped paths in cheapest_parameterized_paths.
! 			 */
! 			if (!grouped_inner)
  			{
! 				foreach(lc2, innerrel->cheapest_parameterized_paths)
! 				{
! 					Path	   *innerpath = (Path *) lfirst(lc2);
  
! 					try_nestloop_path_common(root,
! 											 joinrel,
! 											 outerpath,
! 											 innerpath,
! 											 merge_pathkeys,
! 											 jointype,
! 											 extra,
! 											 false, grouped_outer, grouped_inner,
! 											 do_aggregate);
! 				}
  			}
  
! 			/*
! 			 * Also consider materialized form of the cheapest inner path.
! 			 */
  			if (matpath != NULL)
  				try_nestloop_path_common(root,
  										 joinrel,
*************** match_unsorted_outer_common(PlannerInfo
*** 1597,1603 ****
  										 merge_pathkeys,
  										 jointype,
  										 extra,
! 										 false);
  		}
  
  		/* Can't do anything else if outer path needs to be unique'd */
--- 2211,2218 ----
  										 merge_pathkeys,
  										 jointype,
  										 extra,
! 										 false, grouped_outer, grouped_inner,
! 										 do_aggregate);
  		}
  
  		/* Can't do anything else if outer path needs to be unique'd */
*************** match_unsorted_outer_common(PlannerInfo
*** 1612,1618 ****
  		generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
  								 save_jointype, extra, useallclauses,
  								 inner_cheapest_total, merge_pathkeys,
! 								 false);
  	}
  
  	/*
--- 2227,2234 ----
  		generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
  								 save_jointype, extra, useallclauses,
  								 inner_cheapest_total, merge_pathkeys,
! 								 false, grouped_outer, grouped_inner,
! 								 do_aggregate);
  	}
  
  	/*
*************** match_unsorted_outer_common(PlannerInfo
*** 1623,1639 ****
  	 * we handle extra_lateral_rels, since partial paths must not be
  	 * parameterized. Similarly, we can't handle JOIN_FULL and JOIN_RIGHT,
  	 * because they can produce false null extended rows.
  	 */
  	if (joinrel->consider_parallel &&
  		save_jointype != JOIN_UNIQUE_OUTER &&
  		save_jointype != JOIN_FULL &&
  		save_jointype != JOIN_RIGHT &&
  		outerrel->partial_pathlist != NIL &&
! 		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
--- 2239,2261 ----
  	 * we handle extra_lateral_rels, since partial paths must not be
  	 * parameterized. Similarly, we can't handle JOIN_FULL and JOIN_RIGHT,
  	 * because they can produce false null extended rows.
+ 	 *
+ 	 * grouped_inner must be false because we're not interested in nest loop
+ 	 * joins with the grouped path on the inner side (i.e. repeated
+ 	 * aggregation).
  	 */
  	if (joinrel->consider_parallel &&
  		save_jointype != JOIN_UNIQUE_OUTER &&
  		save_jointype != JOIN_FULL &&
  		save_jointype != JOIN_RIGHT &&
  		outerrel->partial_pathlist != NIL &&
! 		bms_is_empty(joinrel->lateral_relids) &&
! 		!grouped_inner)
  	{
  		if (nestjoinOK)
  			consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! 									   save_jointype, extra,
! 									   grouped_outer, do_aggregate);
  
  		/*
  		 * If inner_cheapest_total is NULL or non parallel-safe then find the
*************** match_unsorted_outer_common(PlannerInfo
*** 1653,1659 ****
  		if (inner_cheapest_total)
  			consider_parallel_mergejoin(root, joinrel, outerrel, innerrel,
  										save_jointype, extra,
! 										inner_cheapest_total);
  	}
  }
  
--- 2275,2282 ----
  		if (inner_cheapest_total)
  			consider_parallel_mergejoin(root, joinrel, outerrel, innerrel,
  										save_jointype, extra,
! 										inner_cheapest_total,
! 										grouped_outer, do_aggregate);
  	}
  }
  
*************** match_unsorted_outer_common(PlannerInfo
*** 1679,1684 ****
--- 2302,2309 ----
   * '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,
*** 1688,1695 ****
  					 JoinType jointype,
  					 JoinPathExtraData *extra)
  {
  	match_unsorted_outer_common(root, joinrel, outerrel, innerrel,
! 								jointype, extra);
  }
  
  /*
--- 2313,2329 ----
  					 JoinType jointype,
  					 JoinPathExtraData *extra)
  {
+ 	/* Plain (non-grouped) join. */
  	match_unsorted_outer_common(root, joinrel, outerrel, innerrel,
! 								jointype, extra, false, false, false);
! 
! 	/* Use all the supported strategies to generate grouped join. */
! 	match_unsorted_outer_common(root, joinrel, outerrel, innerrel,
! 								jointype, extra, true, false, false);
! 	match_unsorted_outer_common(root, joinrel, outerrel, innerrel,
! 								jointype, extra, false, true, false);
! 	match_unsorted_outer_common(root, joinrel, outerrel, innerrel,
! 								jointype, extra, false, false, true);
  }
  
  /*
*************** consider_parallel_mergejoin(PlannerInfo
*** 1711,1722 ****
  							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)
  	{
  		Path	   *outerpath = (Path *) lfirst(lc1);
  		List	   *merge_pathkeys;
--- 2345,2380 ----
  							RelOptInfo *innerrel,
  							JoinType jointype,
  							JoinPathExtraData *extra,
! 							Path *inner_cheapest_total,
! 							bool grouped_outer,
! 							bool do_aggregate)
  {
  	ListCell   *lc1;
+ 	List	   *outer_pathlist;
+ 	bool		grouped = grouped_outer || do_aggregate;
+ 
+ 	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;
  
  	/* generate merge join path for each partial outer path */
! 	foreach(lc1, outer_pathlist)
  	{
  		Path	   *outerpath = (Path *) lfirst(lc1);
  		List	   *merge_pathkeys;
*************** consider_parallel_mergejoin(PlannerInfo
*** 1727,1735 ****
  		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);
  	}
  }
  
--- 2385,2394 ----
  		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, grouped_outer, false, do_aggregate);
  	}
  }
  
*************** consider_parallel_nestloop(PlannerInfo *
*** 1750,1764 ****
  						   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;
--- 2409,2446 ----
  						   RelOptInfo *outerrel,
  						   RelOptInfo *innerrel,
  						   JoinType jointype,
! 						   JoinPathExtraData *extra,
! 						   bool grouped_outer, bool do_aggregate)
  {
  	JoinType	save_jointype = jointype;
+ 	List	   *outer_pathlist;
  	ListCell   *lc1;
+ 	bool		grouped = grouped_outer || do_aggregate;
  
  	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 *
*** 1789,1795 ****
  			 * 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;
--- 2471,2477 ----
  			 * 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 *
*** 1800,1806 ****
  			}
  
  			try_nestloop_path_common(root, joinrel, outerpath, innerpath,
! 									 pathkeys, jointype, extra, true);
  		}
  	}
  }
--- 2482,2490 ----
  			}
  
  			try_nestloop_path_common(root, joinrel, outerpath, innerpath,
! 									 pathkeys, jointype, extra,
! 									 true, grouped_outer, false,
! 									 do_aggregate);
  		}
  	}
  }
*************** hash_inner_and_outer_common(PlannerInfo
*** 1816,1822 ****
  							RelOptInfo *innerrel,
  							JoinType jointype,
  							JoinPathExtraData *extra,
! 							List *hashclauses)
  {
  	JoinType	save_jointype = jointype;
  
--- 2500,2509 ----
  							RelOptInfo *innerrel,
  							JoinType jointype,
  							JoinPathExtraData *extra,
! 							List *hashclauses,
! 							bool grouped_outer,
! 							bool grouped_inner,
! 							bool do_aggregate)
  {
  	JoinType	save_jointype = jointype;
  
*************** hash_inner_and_outer_common(PlannerInfo
*** 1832,1840 ****
  	/* Shouldn't be called w/o hashclauses. */
  	Assert(hashclauses != NIL);
  
! 	cheapest_startup_outer = outerrel->cheapest_startup_path;
! 	cheapest_total_outer = outerrel->cheapest_total_path;
! 	cheapest_total_inner = innerrel->cheapest_total_path;
  
  	/*
  	 * If either cheapest-total path is parameterized by the other rel, we
--- 2519,2550 ----
  	/* Shouldn't be called w/o hashclauses. */
  	Assert(hashclauses != NIL);
  
! 	if (grouped_outer)
! 	{
! 		if (REL_HAS_GROUPED_PATHS(outerrel))
! 		{
! 			cheapest_total_outer = linitial(outerrel->gpi->pathlist);
! 			/* No parameterized grouped paths. */
! 			cheapest_startup_outer = NULL;
! 		}
! 		else
! 			return;
! 	}
! 	else
! 	{
! 		cheapest_total_outer = outerrel->cheapest_total_path;
! 		cheapest_startup_outer = outerrel->cheapest_startup_path;
! 	}
! 
! 	if (grouped_inner)
! 	{
! 		if (REL_HAS_GROUPED_PATHS(innerrel))
! 			cheapest_total_inner = linitial(innerrel->gpi->pathlist);
! 		else
! 			return;
! 	}
! 	else
! 		cheapest_total_inner = innerrel->cheapest_total_path;
  
  	/*
  	 * If either cheapest-total path is parameterized by the other rel, we
*************** hash_inner_and_outer_common(PlannerInfo
*** 1849,1854 ****
--- 2559,2574 ----
  	/* Unique-ify if need be; we ignore parameterized possibilities */
  	if (jointype == JOIN_UNIQUE_OUTER)
  	{
+ 		/*
+ 		 * TODO This is just a temporary limitation. Before lifting it, make
+ 		 * sure that the UniquePath does emit GroupedVars. Also try to avoid
+ 		 * the unique-ification if the outer path comes directly from AggPath
+ 		 * (i.e. it's not grouped path combined with plain one) and the
+ 		 * grouping keys guaranteed the uniqueness.
+ 		 */
+ 		if (grouped_outer)
+ 			return;
+ 
  		cheapest_total_outer = (Path *)
  			create_unique_path(root, outerrel,
  							   cheapest_total_outer, extra->sjinfo);
*************** hash_inner_and_outer_common(PlannerInfo
*** 1862,1872 ****
  								 jointype,
  								 extra,
  								 false,
! 								 false);
  		/* no possibility of cheap startup here */
  	}
  	else if (jointype == JOIN_UNIQUE_INNER)
  	{
  		cheapest_total_inner = (Path *)
  			create_unique_path(root, innerrel,
  							   cheapest_total_inner, extra->sjinfo);
--- 2582,2598 ----
  								 jointype,
  								 extra,
  								 false,
! 								 false,
! 								 grouped_outer, grouped_inner,
! 								 do_aggregate);
  		/* no possibility of cheap startup here */
  	}
  	else if (jointype == JOIN_UNIQUE_INNER)
  	{
+ 		/* TODO Temporary restriction, see above. */
+ 		if (grouped_inner)
+ 			return;
+ 
  		cheapest_total_inner = (Path *)
  			create_unique_path(root, innerrel,
  							   cheapest_total_inner, extra->sjinfo);
*************** hash_inner_and_outer_common(PlannerInfo
*** 1880,1886 ****
  								 jointype,
  								 extra,
  								 false,
! 								 false);
  
  		if (cheapest_startup_outer != NULL &&
  			cheapest_startup_outer != cheapest_total_outer)
--- 2606,2614 ----
  								 jointype,
  								 extra,
  								 false,
! 								 false,
! 								 grouped_outer, grouped_inner,
! 								 do_aggregate);
  
  		if (cheapest_startup_outer != NULL &&
  			cheapest_startup_outer != cheapest_total_outer)
*************** hash_inner_and_outer_common(PlannerInfo
*** 1892,1898 ****
  									 jointype,
  									 extra,
  									 false,
! 									 false);
  	}
  	else
  	{
--- 2620,2628 ----
  									 jointype,
  									 extra,
  									 false,
! 									 false,
! 									 grouped_outer, grouped_inner,
! 									 do_aggregate);
  	}
  	else
  	{
*************** hash_inner_and_outer_common(PlannerInfo
*** 1909,1914 ****
--- 2639,2645 ----
  		 */
  		ListCell   *lc1;
  		ListCell   *lc2;
+ 		List	   *outer_pathlist;
  
  		if (cheapest_startup_outer != NULL)
  			try_hashjoin_path_common(root,
*************** hash_inner_and_outer_common(PlannerInfo
*** 1919,1929 ****
  									 jointype,
  									 extra,
  									 false,
! 									 false);
  
! 		foreach(lc1, outerrel->cheapest_parameterized_paths)
  		{
  			Path	   *outerpath = (Path *) lfirst(lc1);
  
  			/*
  			 * We cannot use an outer path that is parameterized by the inner
--- 2650,2667 ----
  									 jointype,
  									 extra,
  									 false,
! 									 false,
! 									 grouped_outer, grouped_inner,
! 									 do_aggregate);
  
! 		outer_pathlist = !grouped_outer ?
! 			outerrel->cheapest_parameterized_paths :
! 			outerrel->gpi->pathlist;
! 
! 		foreach(lc1, outer_pathlist)
  		{
  			Path	   *outerpath = (Path *) lfirst(lc1);
+ 			List	   *inner_pathlist;
  
  			/*
  			 * We cannot use an outer path that is parameterized by the inner
*************** hash_inner_and_outer_common(PlannerInfo
*** 1932,1938 ****
  			if (PATH_PARAM_BY_REL(outerpath, innerrel))
  				continue;
  
! 			foreach(lc2, innerrel->cheapest_parameterized_paths)
  			{
  				Path	   *innerpath = (Path *) lfirst(lc2);
  
--- 2670,2680 ----
  			if (PATH_PARAM_BY_REL(outerpath, innerrel))
  				continue;
  
! 			inner_pathlist = !grouped_inner ?
! 				innerrel->cheapest_parameterized_paths :
! 				innerrel->gpi->pathlist;
! 
! 			foreach(lc2, inner_pathlist)
  			{
  				Path	   *innerpath = (Path *) lfirst(lc2);
  
*************** hash_inner_and_outer_common(PlannerInfo
*** 1955,1961 ****
  										 jointype,
  										 extra,
  										 false,
! 										 false);
  			}
  		}
  	}
--- 2697,2705 ----
  										 jointype,
  										 extra,
  										 false,
! 										 false,
! 										 grouped_outer, grouped_inner,
! 										 do_aggregate);
  			}
  		}
  	}
*************** hash_inner_and_outer_common(PlannerInfo
*** 1966,1976 ****
  	 * outer path will be partial, and therefore we won't be able to properly
  	 * guarantee uniqueness.  Similarly, we can't handle JOIN_FULL and
  	 * JOIN_RIGHT, because they can produce false null extended rows.  Also,
! 	 * the resulting path must not be parameterized.  We would be able to
! 	 * support JOIN_FULL and JOIN_RIGHT for Parallel Hash, since in that case
! 	 * we're back to a single hash table with a single set of match bits for
! 	 * each batch, but that will require figuring out a deadlock-free way to
! 	 * wait for the probe to finish.
  	 */
  	if (joinrel->consider_parallel &&
  		save_jointype != JOIN_UNIQUE_OUTER &&
--- 2710,2716 ----
  	 * outer path will be partial, and therefore we won't be able to properly
  	 * guarantee uniqueness.  Similarly, we can't handle JOIN_FULL and
  	 * JOIN_RIGHT, because they can produce false null extended rows.  Also,
! 	 * the resulting path must not be parameterized.
  	 */
  	if (joinrel->consider_parallel &&
  		save_jointype != JOIN_UNIQUE_OUTER &&
*************** hash_inner_and_outer_common(PlannerInfo
*** 1983,1990 ****
  		Path	   *cheapest_partial_inner = NULL;
  		Path	   *cheapest_safe_inner = NULL;
  
! 		cheapest_partial_outer =
! 			(Path *) linitial(outerrel->partial_pathlist);
  
  		/*
  		 * Can we use a partial inner plan too, so that we can build a shared
--- 2723,2741 ----
  		Path	   *cheapest_partial_inner = NULL;
  		Path	   *cheapest_safe_inner = NULL;
  
! 		if (grouped_outer)
! 		{
! 			if (REL_HAS_PARTIAL_GROUPED_PATHS(outerrel))
! 			{
! 				cheapest_partial_outer =
! 					(Path *) linitial(outerrel->gpi->partial_pathlist);
! 			}
! 			else
! 				return;
! 		}
! 		else
! 			cheapest_partial_outer =
! 				(Path *) linitial(outerrel->partial_pathlist);
  
  		/*
  		 * Can we use a partial inner plan too, so that we can build a shared
*************** hash_inner_and_outer_common(PlannerInfo
*** 1992,2008 ****
  		 */
  		if (innerrel->partial_pathlist != NIL && enable_parallel_hash)
  		{
! 			cheapest_partial_inner =
! 				(Path *) linitial(innerrel->partial_pathlist);
  			try_hashjoin_path_common(root, joinrel,
  									 cheapest_partial_outer,
  									 cheapest_partial_inner,
  									 hashclauses, jointype, extra,
  									 true,
! 									 true /* parallel_hash */ );
  		}
  
- 
  		/*
  		 * Normally, given that the joinrel is parallel-safe, the cheapest
  		 * total inner path will also be parallel-safe, but if not, we'll have
--- 2743,2772 ----
  		 */
  		if (innerrel->partial_pathlist != NIL && enable_parallel_hash)
  		{
! 			if (grouped_inner)
! 			{
! 				if (REL_HAS_PARTIAL_GROUPED_PATHS(innerrel))
! 				{
! 					cheapest_partial_inner =
! 						(Path *) linitial(innerrel->gpi->partial_pathlist);
! 				}
! 				else
! 					return;
! 			}
! 			else
! 				cheapest_partial_inner =
! 					(Path *) linitial(innerrel->partial_pathlist);
! 
  			try_hashjoin_path_common(root, joinrel,
  									 cheapest_partial_outer,
  									 cheapest_partial_inner,
  									 hashclauses, jointype, extra,
  									 true,
! 									 true /* parallel_hash */ ,
! 									 grouped_outer, grouped_inner,
! 									 do_aggregate);
  		}
  
  		/*
  		 * Normally, given that the joinrel is parallel-safe, the cheapest
  		 * total inner path will also be parallel-safe, but if not, we'll have
*************** hash_inner_and_outer_common(PlannerInfo
*** 2013,2020 ****
  		if (cheapest_total_inner->parallel_safe)
  			cheapest_safe_inner = cheapest_total_inner;
  		else if (save_jointype != JOIN_UNIQUE_INNER)
  			cheapest_safe_inner =
! 				get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
  
  		if (cheapest_safe_inner != NULL)
  			try_hashjoin_path_common(root, joinrel,
--- 2777,2792 ----
  		if (cheapest_total_inner->parallel_safe)
  			cheapest_safe_inner = cheapest_total_inner;
  		else if (save_jointype != JOIN_UNIQUE_INNER)
+ 		{
+ 			List	   *inner_pathlist;
+ 
+ 			inner_pathlist = !grouped_inner ?
+ 				innerrel->cheapest_parameterized_paths :
+ 				innerrel->gpi->pathlist;
+ 
  			cheapest_safe_inner =
! 				get_cheapest_parallel_safe_total_inner(inner_pathlist);
! 		}
  
  		if (cheapest_safe_inner != NULL)
  			try_hashjoin_path_common(root, joinrel,
*************** hash_inner_and_outer_common(PlannerInfo
*** 2022,2028 ****
  									 cheapest_safe_inner,
  									 hashclauses, jointype, extra,
  									 true,
! 									 false);	/* parallel_hash */
  	}
  }
  
--- 2794,2802 ----
  									 cheapest_safe_inner,
  									 hashclauses, jointype, extra,
  									 true,
! 									 false /* parallel_hash */ ,
! 									 grouped_outer, grouped_inner,
! 									 do_aggregate);
  	}
  }
  
*************** hash_inner_and_outer(PlannerInfo *root,
*** 2045,2052 ****
  					 JoinType jointype,
  					 JoinPathExtraData *extra)
  {
- 	bool		isouterjoin = IS_OUTER_JOIN(jointype);
  	List	   *hashclauses = NIL;
  	ListCell   *l;
  
  	/*
--- 2819,2826 ----
  					 JoinType jointype,
  					 JoinPathExtraData *extra)
  {
  	List	   *hashclauses = NIL;
+ 	bool		isouterjoin = IS_OUTER_JOIN(jointype);
  	ListCell   *l;
  
  	/*
*************** hash_inner_and_outer(PlannerInfo *root,
*** 2083,2090 ****
  	if (hashclauses == NIL)
  		return;
  
  	hash_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! 								jointype, extra, hashclauses);
  }
  
  /*
--- 2857,2877 ----
  	if (hashclauses == NIL)
  		return;
  
+ 	/* Plain (non-grouped) join. */
  	hash_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! 								jointype, extra, hashclauses,
! 								false, false, false);
! 
! 	/* Use all the supported strategies to generate grouped join. */
! 	hash_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! 								jointype, extra, hashclauses,
! 								true, false, false);
! 	hash_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! 								jointype, extra, hashclauses,
! 								false, true, false);
! 	hash_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! 								jointype, extra, hashclauses,
! 								false, false, true);
  }
  
  /*
*************** hash_inner_and_outer(PlannerInfo *root,
*** 2092,2098 ****
   *	  Select mergejoin clauses that are usable for a particular join.
   *	  Returns a list of RestrictInfo nodes for those clauses.
   *
!  * *mergejoin_allowed is normally set to true, but it is set to false if
   * this is a right/full join and there are nonmergejoinable join clauses.
   * The executor's mergejoin machinery cannot handle such cases, so we have
   * to avoid generating a mergejoin plan.  (Note that this flag does NOT
--- 2879,2885 ----
   *	  Select mergejoin clauses that are usable for a particular join.
   *	  Returns a list of RestrictInfo nodes for those clauses.
   *
!  * *mergejoin_allowed is normally set to TRUE, but it is set to FALSE if
   * this is a right/full join and there are nonmergejoinable join clauses.
   * The executor's mergejoin machinery cannot handle such cases, so we have
   * to avoid generating a mergejoin plan.  (Note that this flag does NOT
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
new file mode 100644
index 285f269..1823ee3
*** a/src/backend/optimizer/path/joinrels.c
--- b/src/backend/optimizer/path/joinrels.c
*************** try_partition_wise_join(PlannerInfo *roo
*** 1405,1411 ****
  			(List *) adjust_appendrel_attrs(root,
  											(Node *) parent_restrictlist,
  											nappinfos, appinfos);
- 		pfree(appinfos);
  
  		child_joinrel = joinrel->part_rels[cnt_parts];
  		if (!child_joinrel)
--- 1405,1410 ----
*************** try_partition_wise_join(PlannerInfo *roo
*** 1415,1421 ****
--- 1414,1428 ----
  												 child_sjinfo,
  												 child_sjinfo->jointype);
  			joinrel->part_rels[cnt_parts] = child_joinrel;
+ 
+ 			/*
+ 			 * If the parent join can be grouped, so can be its children.
+ 			 */
+ 			if (joinrel->gpi != NULL)
+ 				build_child_rel_gpi(root, child_joinrel, joinrel, nappinfos,
+ 									appinfos);
  		}
+ 		pfree(appinfos);
  
  		Assert(bms_equal(child_joinrel->relids, child_joinrelids));
  
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
new file mode 100644
index 1a0d3a8..b11afd9
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
*************** use_physical_tlist(PlannerInfo *root, Pa
*** 809,814 ****
--- 809,820 ----
  		return false;
  
  	/*
+ 	 * Grouped relation's target list contains GroupedVars.
+ 	 */
+ 	if (rel->gpi != NULL)
+ 		return false;
+ 
+ 	/*
  	 * If a bitmap scan's tlist is empty, keep it as-is.  This may allow the
  	 * executor to skip heap page fetches, and in any case, the benefit of
  	 * using a physical tlist instead would be minimal.
*************** create_projection_plan(PlannerInfo *root
*** 1587,1594 ****
  	 * creation, but that would add expense to creating Paths we might end up
  	 * not using.)
  	 */
! 	if (is_projection_capable_path(best_path->subpath) ||
! 		tlist_same_exprs(tlist, subplan->targetlist))
  	{
  		/* Don't need a separate Result, just assign tlist to subplan */
  		plan = subplan;
--- 1593,1601 ----
  	 * creation, but that would add expense to creating Paths we might end up
  	 * not using.)
  	 */
! 	if (!best_path->force_result &&
! 		(is_projection_capable_path(best_path->subpath) ||
! 		 tlist_same_exprs(tlist, subplan->targetlist)))
  	{
  		/* Don't need a separate Result, just assign tlist to subplan */
  		plan = subplan;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
new file mode 100644
index 0aaac38..c3528fc
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
*************** append_startup_cost_compare(const void *
*** 1359,1369 ****
  /*
   * create_merge_append_path
   *	  Creates a path corresponding to a MergeAppend plan, returning the
!  *	  pathnode.
   */
  MergeAppendPath *
  create_merge_append_path(PlannerInfo *root,
  						 RelOptInfo *rel,
  						 List *subpaths,
  						 List *pathkeys,
  						 Relids required_outer,
--- 1359,1371 ----
  /*
   * create_merge_append_path
   *	  Creates a path corresponding to a MergeAppend plan, returning the
!  *	  pathnode. target can be supplied by caller. If NULL is passed, the field
!  *	  is set to rel->reltarget.
   */
  MergeAppendPath *
  create_merge_append_path(PlannerInfo *root,
  						 RelOptInfo *rel,
+ 						 PathTarget *target,
  						 List *subpaths,
  						 List *pathkeys,
  						 Relids required_outer,
*************** create_merge_append_path(PlannerInfo *ro
*** 1376,1382 ****
  
  	pathnode->path.pathtype = T_MergeAppend;
  	pathnode->path.parent = rel;
! 	pathnode->path.pathtarget = rel->reltarget;
  	pathnode->path.param_info = get_appendrel_parampathinfo(rel,
  															required_outer);
  	pathnode->path.parallel_aware = false;
--- 1378,1384 ----
  
  	pathnode->path.pathtype = T_MergeAppend;
  	pathnode->path.parent = rel;
! 	pathnode->path.pathtarget = target ? target : rel->reltarget;
  	pathnode->path.param_info = get_appendrel_parampathinfo(rel,
  															required_outer);
  	pathnode->path.parallel_aware = false;
*************** calc_non_nestloop_required_outer(Path *o
*** 2179,2184 ****
--- 2181,2187 ----
   * '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,
*** 2192,2198 ****
  					 Path *inner_path,
  					 List *restrict_clauses,
  					 List *pathkeys,
! 					 Relids required_outer)
  {
  	NestPath   *pathnode = makeNode(NestPath);
  	Relids		inner_req_outer = PATH_REQ_OUTER(inner_path);
--- 2195,2202 ----
  					 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,
*** 2225,2231 ****
  
  	pathnode->path.pathtype = T_NestLoop;
  	pathnode->path.parent = joinrel;
! 	pathnode->path.pathtarget = joinrel->reltarget;
  	pathnode->path.param_info =
  		get_joinrel_parampathinfo(root,
  								  joinrel,
--- 2229,2235 ----
  
  	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,
*** 2283,2295 ****
  					  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,
--- 2287,2301 ----
  					  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,
*** 2335,2340 ****
--- 2341,2347 ----
   * '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,
*** 2347,2359 ****
  					 bool parallel_hash,
  					 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,
--- 2354,2368 ----
  					 bool parallel_hash,
  					 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,
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
new file mode 100644
index e4c2313..3fa9261
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
*************** build_join_rel(PlannerInfo *root,
*** 537,542 ****
--- 537,543 ----
  				  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,
*** 590,595 ****
--- 591,655 ----
  	add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel);
  
  	/*
+ 	 * If grouping is applicable at relation level, try to build grouped
+ 	 * target.
+ 	 */
+ 	if (root->grouped_var_list != NIL)
+ 	{
+ 		/*
+ 		 * Try to create a grouping target.
+ 		 */
+ 		prepare_rel_for_grouping(root, joinrel);
+ 
+ 		/*
+ 		 * If the relation appears to be eligible for grouping, joinrel->gpi
+ 		 * has been initialized. Compute width of the grouping target.
+ 		 */
+ 		if (joinrel->gpi != NULL)
+ 		{
+ 			ListCell   *lc;
+ 			int32		width = 0;
+ 
+ 			Assert(joinrel->gpi->target != NULL);
+ 			foreach(lc, joinrel->gpi->target->exprs)
+ 			{
+ 				Expr	   *expr = lfirst(lc);
+ 
+ 				if (IsA(expr, Var))
+ 				{
+ 					Var		   *var = castNode(Var, expr);
+ 					RelOptInfo *rel;
+ 					int			ndx;
+ 
+ 					/*
+ 					 * If set_rel_width() managed to set the width, it should
+ 					 * be cached in the underlying base relation. Otherwise we
+ 					 * can hardly find out more w/o inadequate effort.
+ 					 */
+ 					rel = find_base_rel(root, var->varno);
+ 					ndx = var->varattno - rel->min_attr;
+ 					width += rel->attr_widths[ndx];
+ 				}
+ 				else if (IsA(expr, GroupedVar))
+ 				{
+ 					GroupedVar *gvar = castNode(GroupedVar, expr);
+ 					GroupedVarInfo *gvinfo = find_grouped_var_info(root,
+ 																   gvar);
+ 
+ 					width += gvinfo->gv_width;
+ 				}
+ 				else
+ 
+ 					/*
+ 					 * No other kind of expression should appear in a grouped
+ 					 * target.
+ 					 */
+ 					Assert(false);
+ 			}
+ 		}
+ 	}
+ 
+ 	/*
  	 * add_placeholders_to_joinrel also took care of adding the ph_lateral
  	 * sets of any PlaceHolderVars computed here to direct_lateral_relids, so
  	 * now we can finish computing that.  This is much like the computation of
*************** build_child_join_rel(PlannerInfo *root,
*** 711,716 ****
--- 771,777 ----
  	joinrel->cheapest_parameterized_paths = NIL;
  	joinrel->direct_lateral_relids = NULL;
  	joinrel->lateral_relids = NULL;
+ 	joinrel->gpi = NULL;
  	joinrel->relid = 0;			/* indicates not a baserel */
  	joinrel->rtekind = RTE_JOIN;
  	joinrel->min_attr = 0;
*************** build_child_join_rel(PlannerInfo *root,
*** 759,765 ****
  														(Node *) parent_joinrel->joininfo,
  														nappinfos,
  														appinfos);
- 	pfree(appinfos);
  
  	/*
  	 * Lateral relids referred in child join will be same as that referred in
--- 820,825 ----
*************** build_child_join_rel(PlannerInfo *root,
*** 795,800 ****
--- 855,862 ----
  	/* Add the relation to the PlannerInfo. */
  	add_join_rel(root, joinrel);
  
+ 	pfree(appinfos);
+ 
  	return joinrel;
  }
  
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
new file mode 100644
index b11bd47..bbcfc7f
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern AppendPath *create_append_path(Re
*** 73,78 ****
--- 73,79 ----
  				   List *partitioned_rels, double rows);
  extern MergeAppendPath *create_merge_append_path(PlannerInfo *root,
  						 RelOptInfo *rel,
+ 						 PathTarget *target,
  						 List *subpaths,
  						 List *pathkeys,
  						 Relids required_outer,
*************** extern NestPath *create_nestloop_path(Pl
*** 132,138 ****
  					 Path *inner_path,
  					 List *restrict_clauses,
  					 List *pathkeys,
! 					 Relids required_outer);
  
  extern MergePath *create_mergejoin_path(PlannerInfo *root,
  					  RelOptInfo *joinrel,
--- 133,140 ----
  					 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(
*** 146,152 ****
  					  Relids required_outer,
  					  List *mergeclauses,
  					  List *outersortkeys,
! 					  List *innersortkeys);
  
  extern HashPath *create_hashjoin_path(PlannerInfo *root,
  					 RelOptInfo *joinrel,
--- 148,155 ----
  					  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
*** 158,164 ****
  					 bool parallel_hash,
  					 List *restrict_clauses,
  					 Relids required_outer,
! 					 List *hashclauses);
  
  extern ProjectionPath *create_projection_path(PlannerInfo *root,
  					   RelOptInfo *rel,
--- 161,168 ----
  					 bool parallel_hash,
  					 List *restrict_clauses,
  					 Relids required_outer,
! 					 List *hashclauses,
! 					 PathTarget *target);
  
  extern ProjectionPath *create_projection_path(PlannerInfo *root,
  					   RelOptInfo *rel,
*************** extern RelOptInfo *build_child_join_rel(
*** 315,320 ****
--- 319,327 ----
  					 RelOptInfo *outer_rel, RelOptInfo *inner_rel,
  					 RelOptInfo *parent_joinrel, List *restrictlist,
  					 SpecialJoinInfo *sjinfo, JoinType jointype);
+ extern void build_child_rel_gpi(PlannerInfo *root, RelOptInfo *child,
+ 					RelOptInfo *parent, int nappinfos,
+ 					AppendRelInfo **appinfos);
  extern void prepare_rel_for_grouping(PlannerInfo *root, RelOptInfo *rel);
  extern void build_child_rel_gpi(PlannerInfo *root, RelOptInfo *child,
  					RelOptInfo *parent, int nappinfos,
diff --git a/src/test/regress/expected/agg_pushdown.out b/src/test/regress/expected/agg_pushdown.out
new file mode 100644
index ...e3071de
*** a/src/test/regress/expected/agg_pushdown.out
--- b/src/test/regress/expected/agg_pushdown.out
***************
*** 0 ****
--- 1,259 ----
+ BEGIN;
+ CREATE TABLE agg_pushdown_parent (
+ 	i int primary key);
+ CREATE TABLE agg_pushdown_child1 (
+ 	j int primary key,
+ 	parent int references agg_pushdown_parent,
+ 	v double precision);
+ CREATE INDEX ON agg_pushdown_child1(parent);
+ CREATE TABLE agg_pushdown_child2 (
+ 	k int primary key,
+ 	parent int references agg_pushdown_parent,
+ 	v double precision);
+ INSERT INTO agg_pushdown_parent(i)
+ SELECT n
+ FROM generate_series(0, 7) AS s(n);
+ INSERT INTO agg_pushdown_child1(j, parent, v)
+ SELECT 64 * i + n, i, random()
+ FROM generate_series(0, 63) AS s(n), agg_pushdown_parent;
+ INSERT INTO agg_pushdown_child2(k, parent, v)
+ SELECT 64 * i + n, i, random()
+ FROM generate_series(0, 63) AS s(n), agg_pushdown_parent;
+ ANALYZE;
+ SET enable_agg_pushdown TO on;
+ -- The supposed advantage of pushing aggregation down below joins is that
+ -- joins will then receive less input tuples. Let's stress this difference.
+ SET cpu_tuple_cost TO 0.1;
+ -- Perform scan of a table and partially aggregate the result.
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
+ AS c1 ON c1.parent = p.i GROUP BY p.i;
+                                      QUERY PLAN                                      
+ -------------------------------------------------------------------------------------
+  Finalize HashAggregate
+    Group Key: p.i
+    ->  Nested Loop
+          ->  Partial HashAggregate
+                Group Key: c1.parent
+                ->  Seq Scan on agg_pushdown_child1 c1
+          ->  Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+                Index Cond: (i = c1.parent)
+ (8 rows)
+ 
+ -- Perform nestloop join between agg_pushdown_child1 and agg_pushdown_child2
+ -- and partially aggregate the result.
+ SET enable_nestloop TO on;
+ SET enable_hashjoin TO off;
+ SET enable_mergejoin TO off;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+                                          QUERY PLAN                                          
+ ---------------------------------------------------------------------------------------------
+  Finalize HashAggregate
+    Group Key: p.i
+    ->  Nested Loop
+          ->  Partial HashAggregate
+                Group Key: c1.parent
+                ->  Nested Loop
+                      ->  Seq Scan on agg_pushdown_child1 c1
+                      ->  Index Scan using agg_pushdown_child2_pkey on agg_pushdown_child2 c2
+                            Index Cond: (k = c1.j)
+                            Filter: (c1.parent = parent)
+          ->  Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+                Index Cond: (i = c1.parent)
+ (12 rows)
+ 
+ -- The same for hash join.
+ SET enable_nestloop TO off;
+ SET enable_hashjoin TO on;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+                                  QUERY PLAN                                 
+ ----------------------------------------------------------------------------
+  Finalize HashAggregate
+    Group Key: p.i
+    ->  Hash Join
+          Hash Cond: (c1.parent = p.i)
+          ->  Partial HashAggregate
+                Group Key: c1.parent
+                ->  Hash Join
+                      Hash Cond: ((c1.parent = c2.parent) AND (c1.j = c2.k))
+                      ->  Seq Scan on agg_pushdown_child1 c1
+                      ->  Hash
+                            ->  Seq Scan on agg_pushdown_child2 c2
+          ->  Hash
+                ->  Seq Scan on agg_pushdown_parent p
+ (13 rows)
+ 
+ -- The same for merge join.
+ SET enable_hashjoin TO off;
+ SET enable_mergejoin TO on;
+ SET enable_seqscan TO off;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+                                             QUERY PLAN                                             
+ ---------------------------------------------------------------------------------------------------
+  Finalize GroupAggregate
+    Group Key: p.i
+    ->  Merge Join
+          Merge Cond: (c1.parent = p.i)
+          ->  Sort
+                Sort Key: c1.parent
+                ->  Partial HashAggregate
+                      Group Key: c1.parent
+                      ->  Merge Join
+                            Merge Cond: (c1.j = c2.k)
+                            Join Filter: (c1.parent = c2.parent)
+                            ->  Index Scan using agg_pushdown_child1_pkey on agg_pushdown_child1 c1
+                            ->  Index Scan using agg_pushdown_child2_pkey on agg_pushdown_child2 c2
+          ->  Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+ (14 rows)
+ 
+ -- Generic grouping expression.
+ -- Make any processing of the grouping expression expensive. If the final
+ -- relation output needs to be sorted before GroupAggregate is applied, the
+ -- (p.i / 2) expression is also evaluated by the Sort node. Thus the plan that
+ -- uses the partial aggregation is preferrable because it emits the value of
+ -- the exprssion and no node above has to evaluate it again.
+ SET cpu_operator_cost TO 0.1;
+ EXPLAIN (COSTS off)
+ SELECT p.i / 2, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i / 2;
+                                                QUERY PLAN                                                
+ ---------------------------------------------------------------------------------------------------------
+  Finalize HashAggregate
+    Group Key: (((c1.parent / 2)))
+    ->  Merge Join
+          Merge Cond: (c1.parent = p.i)
+          ->  Sort
+                Sort Key: c1.parent
+                ->  Partial HashAggregate
+                      Group Key: (c1.parent / 2), c1.parent, c2.parent
+                      ->  Result
+                            ->  Merge Join
+                                  Merge Cond: (c1.j = c2.k)
+                                  Join Filter: (c1.parent = c2.parent)
+                                  ->  Index Scan using agg_pushdown_child1_pkey on agg_pushdown_child1 c1
+                                  ->  Index Scan using agg_pushdown_child2_pkey on agg_pushdown_child2 c2
+          ->  Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+ (15 rows)
+ 
+ -- The same tests for parallel plans.
+ RESET ALL;
+ SET parallel_setup_cost TO 0;
+ SET parallel_tuple_cost TO 0;
+ SET min_parallel_table_scan_size TO 0;
+ SET min_parallel_index_scan_size TO 0;
+ SET max_parallel_workers_per_gather TO 4;
+ SET enable_agg_pushdown TO on;
+ SET cpu_tuple_cost TO 0.1;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
+ AS c1 ON c1.parent = p.i GROUP BY p.i;
+                                         QUERY PLAN                                         
+ -------------------------------------------------------------------------------------------
+  Finalize HashAggregate
+    Group Key: p.i
+    ->  Gather
+          Workers Planned: 2
+          ->  Nested Loop
+                ->  Partial HashAggregate
+                      Group Key: c1.parent
+                      ->  Parallel Seq Scan on agg_pushdown_child1 c1
+                ->  Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+                      Index Cond: (i = c1.parent)
+ (10 rows)
+ 
+ SET enable_nestloop TO on;
+ SET enable_hashjoin TO off;
+ SET enable_mergejoin TO off;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+                                             QUERY PLAN                                             
+ ---------------------------------------------------------------------------------------------------
+  Finalize HashAggregate
+    Group Key: p.i
+    ->  Gather
+          Workers Planned: 2
+          ->  Nested Loop
+                ->  Partial HashAggregate
+                      Group Key: c1.parent
+                      ->  Nested Loop
+                            ->  Parallel Seq Scan on agg_pushdown_child1 c1
+                            ->  Index Scan using agg_pushdown_child2_pkey on agg_pushdown_child2 c2
+                                  Index Cond: (k = c1.j)
+                                  Filter: (c1.parent = parent)
+                ->  Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+                      Index Cond: (i = c1.parent)
+ (14 rows)
+ 
+ SET enable_nestloop TO off;
+ SET enable_hashjoin TO on;
+ -- TODO Force the Hash Join node to stay below Gather.
+ -- EXPLAIN (COSTS off)
+ -- SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ -- agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ -- c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+ SET enable_hashjoin TO off;
+ SET enable_mergejoin TO on;
+ SET enable_seqscan TO off;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+                                                     QUERY PLAN                                                    
+ ------------------------------------------------------------------------------------------------------------------
+  Finalize GroupAggregate
+    Group Key: p.i
+    ->  Gather Merge
+          Workers Planned: 2
+          ->  Merge Join
+                Merge Cond: (c1.parent = p.i)
+                ->  Sort
+                      Sort Key: c1.parent
+                      ->  Partial HashAggregate
+                            Group Key: c1.parent
+                            ->  Merge Join
+                                  Merge Cond: (c1.j = c2.k)
+                                  Join Filter: (c1.parent = c2.parent)
+                                  ->  Parallel Index Scan using agg_pushdown_child1_pkey on agg_pushdown_child1 c1
+                                  ->  Index Scan using agg_pushdown_child2_pkey on agg_pushdown_child2 c2
+                ->  Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+ (16 rows)
+ 
+ SET cpu_operator_cost TO 0.1;
+ EXPLAIN (COSTS off)
+ SELECT p.i / 2, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i / 2;
+                                                        QUERY PLAN                                                       
+ ------------------------------------------------------------------------------------------------------------------------
+  Finalize HashAggregate
+    Group Key: (((c1.parent / 2)))
+    ->  Gather
+          Workers Planned: 2
+          ->  Merge Join
+                Merge Cond: (c1.parent = p.i)
+                ->  Sort
+                      Sort Key: c1.parent
+                      ->  Partial HashAggregate
+                            Group Key: (c1.parent / 2), c1.parent, c2.parent
+                            ->  Result
+                                  ->  Merge Join
+                                        Merge Cond: (c1.j = c2.k)
+                                        Join Filter: (c1.parent = c2.parent)
+                                        ->  Parallel Index Scan using agg_pushdown_child1_pkey on agg_pushdown_child1 c1
+                                        ->  Index Scan using agg_pushdown_child2_pkey on agg_pushdown_child2 c2
+                ->  Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+ (17 rows)
+ 
+ ROLLBACK;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
new file mode 100644
index e224977..cf52728
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
*************** test: rules psql_crosstab amutils
*** 98,103 ****
--- 98,106 ----
  test: select_parallel
  test: write_parallel
  
+ # this one runs parallel workers too
+ test: agg_pushdown
+ 
  # no relation related tests can be put in this group
  test: publication subscription
  
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
new file mode 100644
index 9fc5f1a..ca0bf76
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
*************** test: stats_ext
*** 136,141 ****
--- 136,142 ----
  test: rules
  test: psql_crosstab
  test: select_parallel
+ test: agg_pushdown
  test: write_parallel
  test: publication
  test: subscription
diff --git a/src/test/regress/sql/agg_pushdown.sql b/src/test/regress/sql/agg_pushdown.sql
new file mode 100644
index ...e6e40cb
*** a/src/test/regress/sql/agg_pushdown.sql
--- b/src/test/regress/sql/agg_pushdown.sql
***************
*** 0 ****
--- 1,138 ----
+ BEGIN;
+ 
+ CREATE TABLE agg_pushdown_parent (
+ 	i int primary key);
+ 
+ CREATE TABLE agg_pushdown_child1 (
+ 	j int primary key,
+ 	parent int references agg_pushdown_parent,
+ 	v double precision);
+ 
+ CREATE INDEX ON agg_pushdown_child1(parent);
+ 
+ CREATE TABLE agg_pushdown_child2 (
+ 	k int primary key,
+ 	parent int references agg_pushdown_parent,
+ 	v double precision);
+ 
+ INSERT INTO agg_pushdown_parent(i)
+ SELECT n
+ FROM generate_series(0, 7) AS s(n);
+ 
+ INSERT INTO agg_pushdown_child1(j, parent, v)
+ SELECT 64 * i + n, i, random()
+ FROM generate_series(0, 63) AS s(n), agg_pushdown_parent;
+ 
+ INSERT INTO agg_pushdown_child2(k, parent, v)
+ SELECT 64 * i + n, i, random()
+ FROM generate_series(0, 63) AS s(n), agg_pushdown_parent;
+ 
+ ANALYZE;
+ 
+ SET enable_agg_pushdown TO on;
+ 
+ -- The supposed advantage of pushing aggregation down below joins is that
+ -- joins will then receive less input tuples. Let's stress this difference.
+ SET cpu_tuple_cost TO 0.1;
+ 
+ -- Perform scan of a table and partially aggregate the result.
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
+ AS c1 ON c1.parent = p.i GROUP BY p.i;
+ 
+ -- Perform nestloop join between agg_pushdown_child1 and agg_pushdown_child2
+ -- and partially aggregate the result.
+ SET enable_nestloop TO on;
+ SET enable_hashjoin TO off;
+ SET enable_mergejoin TO off;
+ 
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+ 
+ -- The same for hash join.
+ SET enable_nestloop TO off;
+ SET enable_hashjoin TO on;
+ 
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+ 
+ -- The same for merge join.
+ SET enable_hashjoin TO off;
+ SET enable_mergejoin TO on;
+ SET enable_seqscan TO off;
+ 
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+ 
+ -- Generic grouping expression.
+ 
+ -- Make any processing of the grouping expression expensive. If the final
+ -- relation output needs to be sorted before GroupAggregate is applied, the
+ -- (p.i / 2) expression is also evaluated by the Sort node. Thus the plan that
+ -- uses the partial aggregation is preferrable because it emits the value of
+ -- the exprssion and no node above has to evaluate it again.
+ SET cpu_operator_cost TO 0.1;
+ 
+ EXPLAIN (COSTS off)
+ SELECT p.i / 2, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i / 2;
+ 
+ -- The same tests for parallel plans.
+ RESET ALL;
+ 
+ SET parallel_setup_cost TO 0;
+ SET parallel_tuple_cost TO 0;
+ SET min_parallel_table_scan_size TO 0;
+ SET min_parallel_index_scan_size TO 0;
+ SET max_parallel_workers_per_gather TO 4;
+ 
+ SET enable_agg_pushdown TO on;
+ SET cpu_tuple_cost TO 0.1;
+ 
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
+ AS c1 ON c1.parent = p.i GROUP BY p.i;
+ 
+ SET enable_nestloop TO on;
+ SET enable_hashjoin TO off;
+ SET enable_mergejoin TO off;
+ 
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+ 
+ SET enable_nestloop TO off;
+ SET enable_hashjoin TO on;
+ 
+ -- TODO Force the Hash Join node to stay below Gather.
+ -- EXPLAIN (COSTS off)
+ -- SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ -- agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ -- c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+ 
+ SET enable_hashjoin TO off;
+ SET enable_mergejoin TO on;
+ SET enable_seqscan TO off;
+ 
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+ 
+ SET cpu_operator_cost TO 0.1;
+ 
+ EXPLAIN (COSTS off)
+ SELECT p.i / 2, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i / 2;
+ 
+ ROLLBACK;
+ 


  agg_pushdown_v5/08_index.diff
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
new file mode 100644
index 36e038e..045dd21
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** set_plain_rel_pathlist(PlannerInfo *root
*** 738,744 ****
  	/*
  	 * Consider index scans.
  	 */
! 	create_index_paths(root, rel);
  
  	/* Consider TID scans */
  	create_tidscan_paths(root, rel);
--- 738,744 ----
  	/*
  	 * Consider index scans.
  	 */
! 	create_index_paths(root, rel, agg_info);
  
  	/* Consider TID scans */
  	create_tidscan_paths(root, rel);
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
new file mode 100644
index c8bd820..5c39ef6
*** 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"
*************** typedef struct
*** 78,83 ****
--- 79,93 ----
  	int			indexcol;		/* index column we want to match to */
  } ec_member_matches_arg;
  
+ /*
+  * A set of lists containing index paths to which specific kind of aggregation
+  * was applied.
+  */
+ typedef struct GroupedIndexPaths
+ {
+ 	List	   *paths;
+ 	List	   *paths_partial;
+ } GroupedIndexPaths;
  
  static void consider_index_join_clauses(PlannerInfo *root, RelOptInfo *rel,
  							IndexOptInfo *index,
*************** static bool eclass_already_used(Equivale
*** 107,123 ****
  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,
! 						 List *clauses, List *other_clauses);
  static Path *choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel,
  				  List *paths);
  static int	path_usage_comparator(const void *a, const void *b);
--- 117,139 ----
  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, PartialAggInfo *agg_info);
  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,
! 				  PartialAggInfo *agg_info,
! 				  GroupedIndexPaths *agg_sorted,
! 				  GroupedIndexPaths *agg_hashed);
  static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
! 				   List *clauses, List *other_clauses,
! 				   PartialAggInfo *agg_info,
! 				   GroupedIndexPaths *agg_hashed);
  static List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
! 						 List *clauses, List *other_clauses,
! 						 PartialAggInfo *agg_info);
  static Path *choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel,
  				  List *paths);
  static int	path_usage_comparator(const void *a, const void *b);
*************** static Const *string_to_const(const char
*** 227,235 ****
   * index quals ... but for now, it doesn't seem worth troubling over.
   * In particular, comments below about "unparameterized" paths should be read
   * as meaning "unparameterized so far as the indexquals are concerned".
   */
  void
! create_index_paths(PlannerInfo *root, RelOptInfo *rel)
  {
  	List	   *indexpaths;
  	List	   *bitindexpaths;
--- 243,254 ----
   * index quals ... but for now, it doesn't seem worth troubling over.
   * In particular, comments below about "unparameterized" paths should be read
   * as meaning "unparameterized so far as the indexquals are concerned".
+  *
+  * If agg_info is passed, grouped paths are generated too.
   */
  void
! create_index_paths(PlannerInfo *root, RelOptInfo *rel,
! 				   PartialAggInfo *agg_info)
  {
  	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
--- 293,300 ----
  		 * 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,
! 						agg_info);
  
  		/*
  		 * Identify the join clauses that can match the index.  For the moment
*************** create_index_paths(PlannerInfo *root, Re
*** 310,326 ****
  	/*
  	 * Generate BitmapOrPaths for any suitable OR-clauses present in the
  	 * restriction list.  Add these to bitindexpaths.
  	 */
  	indexpaths = generate_bitmap_or_paths(root, rel,
! 										  rel->baserestrictinfo, NIL);
  	bitindexpaths = list_concat(bitindexpaths, indexpaths);
  
  	/*
  	 * Likewise, generate BitmapOrPaths for any suitable OR-clauses present in
  	 * the joinclause list.  Add these to bitjoinpaths.
  	 */
  	indexpaths = generate_bitmap_or_paths(root, rel,
! 										  joinorclauses, rel->baserestrictinfo);
  	bitjoinpaths = list_concat(bitjoinpaths, indexpaths);
  
  	/*
--- 329,351 ----
  	/*
  	 * Generate BitmapOrPaths for any suitable OR-clauses present in the
  	 * restriction list.  Add these to bitindexpaths.
+ 	 *
+ 	 * Include the grouped path.
  	 */
  	indexpaths = generate_bitmap_or_paths(root, rel,
! 										  rel->baserestrictinfo, NIL,
! 										  agg_info);
  	bitindexpaths = list_concat(bitindexpaths, indexpaths);
  
  	/*
  	 * Likewise, generate BitmapOrPaths for any suitable OR-clauses present in
  	 * the joinclause list.  Add these to bitjoinpaths.
+ 	 *
+ 	 * Do not include the grouped paths, as these cab be parameterized.
  	 */
  	indexpaths = generate_bitmap_or_paths(root, rel,
! 										  joinorclauses, rel->baserestrictinfo,
! 										  NULL);
  	bitjoinpaths = list_concat(bitjoinpaths, indexpaths);
  
  	/*
*************** 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
--- 692,698 ----
  	Assert(clauseset.nonempty);
  
  	/* Build index path(s) using the collected set of clauses */
! 	get_index_paths(root, rel, index, &clauseset, bitindexpaths, NULL);
  
  	/*
  	 * Remember we considered paths for this set of relids.  We use lcons not
*************** bms_equal_any(Relids relids, List *relid
*** 717,723 ****
  	return false;
  }
  
- 
  /*
   * get_index_paths
   *	  Given an index and a set of index clauses for it, construct IndexPaths.
--- 742,747 ----
*************** bms_equal_any(Relids relids, List *relid
*** 736,765 ****
  static void
  get_index_paths(PlannerInfo *root, RelOptInfo *rel,
  				IndexOptInfo *index, IndexClauseSet *clauses,
! 				List **bitindexpaths)
  {
  	List	   *indexpaths;
  	bool		skip_nonnative_saop = false;
  	bool		skip_lower_saop = false;
  	ListCell   *lc;
  
  	/*
  	 * Build simple index paths using the clauses.  Allow ScalarArrayOpExpr
  	 * clauses only if the index AM supports them natively, and skip any such
  	 * clauses for index columns after the first (so that we produce ordered
  	 * paths if possible).
  	 */
  	indexpaths = build_index_paths(root, rel,
  								   index, clauses,
  								   index->predOK,
  								   ST_ANYSCAN,
  								   &skip_nonnative_saop,
! 								   &skip_lower_saop);
  
  	/*
  	 * If we skipped any lower-order ScalarArrayOpExprs on an index with an AM
  	 * that supports them, then try again including those clauses.  This will
  	 * produce paths with more selectivity but no ordering.
  	 */
  	if (skip_lower_saop)
  	{
--- 760,826 ----
  static void
  get_index_paths(PlannerInfo *root, RelOptInfo *rel,
  				IndexOptInfo *index, IndexClauseSet *clauses,
! 				List **bitindexpaths, PartialAggInfo *agg_info)
  {
  	List	   *indexpaths;
+ 	GroupedIndexPaths agg_sorted;
+ 	GroupedIndexPaths *agg_sorted_p = NULL;
+ 	GroupedIndexPaths agg_hashed;
+ 	GroupedIndexPaths *agg_hashed_p = NULL;
  	bool		skip_nonnative_saop = false;
  	bool		skip_lower_saop = false;
  	ListCell   *lc;
  
  	/*
+ 	 * Set all lists of grouped paths to NIL.
+ 	 */
+ 	MemSet(&agg_sorted, 0, sizeof(GroupedIndexPaths));
+ 	MemSet(&agg_hashed, 0, sizeof(GroupedIndexPaths));
+ 
+ 	/*
+ 	 * If grouping at relation level might be useful for the current query,
+ 	 * prepare for collection of the grouped paths.
+ 	 */
+ 	if (agg_info != NULL)
+ 	{
+ 		/*
+ 		 * AGG_HASHED is always possible.
+ 		 */
+ 		agg_hashed_p = &agg_hashed;
+ 
+ 		/*
+ 		 * Can this index provide input for AGG_SORTED aggregation?
+ 		 */
+ 		if (index->amhasgettuple)
+ 			agg_sorted_p = &agg_sorted;
+ 	}
+ 
+ 	/*
  	 * Build simple index paths using the clauses.  Allow ScalarArrayOpExpr
  	 * clauses only if the index AM supports them natively, and skip any such
  	 * clauses for index columns after the first (so that we produce ordered
  	 * paths if possible).
+ 	 *
+ 	 * These paths are good candidates for AGG_SORTED, so pass the output
+ 	 * lists for this strategy. AGG_HASHED should be applied to paths with no
+ 	 * pathkeys.
  	 */
  	indexpaths = build_index_paths(root, rel,
  								   index, clauses,
  								   index->predOK,
  								   ST_ANYSCAN,
  								   &skip_nonnative_saop,
! 								   &skip_lower_saop,
! 								   agg_info,
! 								   agg_sorted_p, agg_hashed_p);
  
  	/*
  	 * If we skipped any lower-order ScalarArrayOpExprs on an index with an AM
  	 * that supports them, then try again including those clauses.  This will
  	 * produce paths with more selectivity but no ordering.
+ 	 *
+ 	 * As for the grouping paths, only AGG_HASHED is considered due to the
+ 	 * missing ordering.
  	 */
  	if (skip_lower_saop)
  	{
*************** get_index_paths(PlannerInfo *root, RelOp
*** 769,775 ****
  												   index->predOK,
  												   ST_ANYSCAN,
  												   &skip_nonnative_saop,
! 												   NULL));
  	}
  
  	/*
--- 830,838 ----
  												   index->predOK,
  												   ST_ANYSCAN,
  												   &skip_nonnative_saop,
! 												   NULL,
! 												   agg_info,
! 												   NULL, agg_hashed_p));
  	}
  
  	/*
*************** get_index_paths(PlannerInfo *root, RelOp
*** 801,806 ****
--- 864,872 ----
  	 * If there were ScalarArrayOpExpr clauses that the index can't handle
  	 * natively, generate bitmap scan paths relying on executor-managed
  	 * ScalarArrayOpExpr.
+ 	 *
+ 	 * As for grouping, only AGG_HASHED is possible here. Again, because
+ 	 * there's no ordering.
  	 */
  	if (skip_nonnative_saop)
  	{
*************** get_index_paths(PlannerInfo *root, RelOp
*** 809,817 ****
  									   false,
  									   ST_BITMAPSCAN,
  									   NULL,
! 									   NULL);
  		*bitindexpaths = list_concat(*bitindexpaths, indexpaths);
  	}
  }
  
  /*
--- 875,909 ----
  									   false,
  									   ST_BITMAPSCAN,
  									   NULL,
! 									   NULL,
! 									   agg_info,
! 									   NULL, agg_hashed_p);
  		*bitindexpaths = list_concat(*bitindexpaths, indexpaths);
  	}
+ 
+ 	/*
+ 	 * If we collected the grouped path, process them now.
+ 	 */
+ 	if (agg_info != NULL)
+ 	{
+ 		List	   *paths;
+ 		AggPath    *agg_path;
+ 
+ 		paths = list_concat(agg_sorted.paths, agg_hashed.paths);
+ 		foreach(lc, paths)
+ 		{
+ 			agg_path = lfirst_node(AggPath, lc);
+ 			add_path(rel, (Path *) agg_path, true);
+ 		}
+ 
+ 		paths = list_concat(agg_sorted.paths_partial,
+ 							agg_hashed.paths_partial);
+ 		foreach(lc, paths)
+ 		{
+ 			agg_path = lfirst_node(AggPath, lc);
+ 			add_partial_path(rel, (Path *) agg_path, true);
+ 		}
+ 	}
  }
  
  /*
*************** get_index_paths(PlannerInfo *root, RelOp
*** 847,859 ****
   * NULL, we do not ignore non-first ScalarArrayOpExpr clauses, but they will
   * result in considering the scan's output to be unordered.
   *
   * 'rel' is the index's heap relation
   * 'index' is the index for which we want to generate paths
   * 'clauses' is the collection of indexable clauses (RestrictInfo nodes)
   * 'useful_predicate' indicates whether the index has a useful predicate
   * 'scantype' indicates whether we need plain or bitmap scan support
   * 'skip_nonnative_saop' indicates whether to accept SAOP if index AM doesn't
!  * 'skip_lower_saop' indicates whether to accept non-first-column SAOP
   */
  static List *
  build_index_paths(PlannerInfo *root, RelOptInfo *rel,
--- 939,959 ----
   * NULL, we do not ignore non-first ScalarArrayOpExpr clauses, but they will
   * result in considering the scan's output to be unordered.
   *
+  * If 'agg_info' is passed, 'agg_sorted' and / or 'agg_hashed' must be passed
+  * too. In that case AGG_SORTED and / or AGG_HASHED aggregation is applied to
+  * the index path (as long as the index path is appropriate) and the resulting
+  * grouped path is stored here.
+  *
   * 'rel' is the index's heap relation
   * 'index' is the index for which we want to generate paths
   * 'clauses' is the collection of indexable clauses (RestrictInfo nodes)
   * 'useful_predicate' indicates whether the index has a useful predicate
   * 'scantype' indicates whether we need plain or bitmap scan support
   * 'skip_nonnative_saop' indicates whether to accept SAOP if index AM doesn't
!  * 'skip_lower_saop' indicates whether to accept non-first-column SAOP.
!  * 'agg_info' is information needed to aggregate the join scan.
!  * 'agg_sorted' is an output lists for AGG_SORTED paths.
!  * 'agg_hashed' is an output lists for AGG_HASHED paths.
   */
  static List *
  build_index_paths(PlannerInfo *root, RelOptInfo *rel,
*************** 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;
--- 961,970 ----
  				  bool useful_predicate,
  				  ScanTypeControl scantype,
  				  bool *skip_nonnative_saop,
! 				  bool *skip_lower_saop,
! 				  PartialAggInfo *agg_info,
! 				  GroupedIndexPaths *agg_sorted,
! 				  GroupedIndexPaths *agg_hashed)
  {
  	List	   *result = NIL;
  	IndexPath  *ipath;
*************** build_index_paths(PlannerInfo *root, Rel
*** 878,883 ****
--- 981,999 ----
  	bool		index_is_ordered;
  	bool		index_only_scan;
  	int			indexcol;
+ 	bool		include_grouped;
+ 	bool		can_agg_sorted,
+ 				can_agg_hashed;
+ 	AggPath    *agg_path;
+ 
+ 	/*
+ 	 * Make some of the following tests simpler.
+ 	 */
+ 	include_grouped = agg_sorted != NULL || agg_hashed != NULL;
+ 
+ 	/* Check that the agg_* arguments are consistent. */
+ 	Assert((agg_info == NULL && !include_grouped) ||
+ 		   (agg_info != NULL && include_grouped));
  
  	/*
  	 * Check that index supports the desired scan type(s)
*************** build_index_paths(PlannerInfo *root, Rel
*** 1031,1037 ****
--- 1147,1158 ----
  	 * in the current clauses, OR the index ordering is potentially useful for
  	 * later merging or final output ordering, OR the index has a useful
  	 * predicate, OR an index-only scan is possible.
+ 	 *
+ 	 * This is where grouped path start to be considered.
  	 */
+ 	can_agg_sorted = true;
+ 	can_agg_hashed = true;
+ 
  	if (index_clauses != NIL || useful_pathkeys != NIL || useful_predicate ||
  		index_only_scan)
  	{
*************** build_index_paths(PlannerInfo *root, Rel
*** 1048,1055 ****
--- 1169,1212 ----
  								  outer_relids,
  								  loop_count,
  								  false);
+ 
  		result = lappend(result, ipath);
  
+ 		if (include_grouped && outer_relids == NULL &&
+ 			rel->gpi != NULL && rel->gpi->target != NULL)
+ 		{
+ 			/*
+ 			 * Try to create the grouped paths if caller is interested in
+ 			 * them.
+ 			 */
+ 			if (agg_sorted != NULL && useful_pathkeys != NIL)
+ 			{
+ 				agg_path = create_partial_agg_sorted_path(root,
+ 														  (Path *) ipath,
+ 														  true,
+ 														  agg_info,
+ 														  ipath->path.rows);
+ 
+ 				if (agg_path != NULL)
+ 					agg_sorted->paths = lappend(agg_sorted->paths, agg_path);
+ 				else
+ 					can_agg_sorted = false;
+ 			}
+ 
+ 			if (agg_hashed != NULL)
+ 			{
+ 				agg_path = create_partial_agg_hashed_path(root,
+ 														  (Path *) ipath,
+ 														  agg_info,
+ 														  ipath->path.rows);
+ 
+ 				if (agg_path != NULL)
+ 					agg_hashed->paths = lappend(agg_hashed->paths, agg_path);
+ 				else
+ 					can_agg_hashed = false;
+ 			}
+ 		}
+ 
  		/*
  		 * If appropriate, consider parallel index scan.  We don't allow
  		 * parallel index scan for bitmap index scans.
*************** build_index_paths(PlannerInfo *root, Rel
*** 1077,1083 ****
--- 1234,1285 ----
  			 * parallel workers, just free it.
  			 */
  			if (ipath->path.parallel_workers > 0)
+ 			{
  				add_partial_path(rel, (Path *) ipath, false);
+ 
+ 				if (include_grouped && outer_relids == NULL &&
+ 					rel->gpi != NULL && rel->gpi->target != NULL)
+ 				{
+ 					if (agg_sorted != NULL && useful_pathkeys != NIL &&
+ 						can_agg_sorted)
+ 					{
+ 						/*
+ 						 * No need to check the pathkeys again.
+ 						 */
+ 						agg_path = create_partial_agg_sorted_path(root,
+ 																  (Path *) ipath,
+ 																  false,
+ 																  agg_info,
+ 																  ipath->path.rows);
+ 
+ 						/*
+ 						 * If create_agg_sorted_path succeeded once, it should
+ 						 * always do.
+ 						 */
+ 						Assert(agg_path != NULL);
+ 
+ 						agg_sorted->paths_partial =
+ 							lappend(agg_sorted->paths_partial, agg_path);
+ 					}
+ 
+ 					if (agg_hashed != NULL && can_agg_hashed)
+ 					{
+ 						agg_path = create_partial_agg_hashed_path(root,
+ 																  (Path *) ipath,
+ 																  agg_info,
+ 																  ipath->path.rows);
+ 
+ 						/*
+ 						 * If create_agg_hashed_path succeeded once, it should
+ 						 * always do.
+ 						 */
+ 						Assert(agg_path != NULL);
+ 
+ 						agg_hashed->paths_partial =
+ 							lappend(agg_hashed->paths_partial, agg_path);
+ 					}
+ 				}
+ 			}
  			else
  				pfree(ipath);
  		}
*************** build_index_paths(PlannerInfo *root, Rel
*** 1105,1112 ****
--- 1307,1345 ----
  									  outer_relids,
  									  loop_count,
  									  false);
+ 
  			result = lappend(result, ipath);
  
+ 			if (include_grouped && outer_relids == NULL &&
+ 				rel->gpi != NULL && rel->gpi->target != NULL)
+ 			{
+ 				/*
+ 				 * As the input set ordering does not matter to AGG_HASHED,
+ 				 * only AGG_SORTED makes sense here. (The AGG_HASHED path we'd
+ 				 * create here should already exist.)
+ 				 *
+ 				 * The existing value of can_agg_sorted is not up-to-date for
+ 				 * the new pathkeys.
+ 				 */
+ 				can_agg_sorted = true;
+ 
+ 				if (agg_sorted != NULL)
+ 				{
+ 					/* pathkeys are new, so check them. */
+ 					agg_path = create_partial_agg_sorted_path(root,
+ 															  (Path *) ipath,
+ 															  true,
+ 															  agg_info,
+ 															  ipath->path.rows);
+ 
+ 					if (agg_path != NULL)
+ 						agg_sorted->paths = lappend(agg_sorted->paths,
+ 													agg_path);
+ 					else
+ 						can_agg_sorted = false;
+ 				}
+ 			}
+ 
  			/* If appropriate, consider parallel index scan */
  			if (index->amcanparallel &&
  				rel->consider_parallel && outer_relids == NULL &&
*************** build_index_paths(PlannerInfo *root, Rel
*** 1129,1135 ****
--- 1362,1390 ----
  				 * using parallel workers, just free it.
  				 */
  				if (ipath->path.parallel_workers > 0)
+ 				{
  					add_partial_path(rel, (Path *) ipath, false);
+ 
+ 					if (include_grouped && outer_relids == NULL &&
+ 						rel->gpi != NULL && rel->gpi->target != NULL)
+ 					{
+ 						if (agg_sorted != NULL && can_agg_sorted)
+ 						{
+ 							/*
+ 							 * The non-partial path above should have been
+ 							 * created, so no need to check pathkeys.
+ 							 */
+ 							agg_path = create_partial_agg_sorted_path(root,
+ 																	  (Path *) ipath,
+ 																	  false,
+ 																	  agg_info,
+ 																	  ipath->path.rows);
+ 							Assert(agg_path != NULL);
+ 							agg_sorted->paths_partial =
+ 								lappend(agg_sorted->paths_partial, agg_path);
+ 						}
+ 					}
+ 				}
  				else
  					pfree(ipath);
  			}
*************** build_index_paths(PlannerInfo *root, Rel
*** 1164,1173 ****
   * 'rel' is the relation for which we want to generate index paths
   * 'clauses' is the current list of clauses (RestrictInfo nodes)
   * 'other_clauses' is the list of additional upper-level clauses
   */
  static List *
  build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
! 				   List *clauses, List *other_clauses)
  {
  	List	   *result = NIL;
  	List	   *all_clauses = NIL;	/* not computed till needed */
--- 1419,1430 ----
   * 'rel' is the relation for which we want to generate index paths
   * 'clauses' is the current list of clauses (RestrictInfo nodes)
   * 'other_clauses' is the list of additional upper-level clauses
+  * 'agg_info' indicates that grouped paths should be added to 'agg_hashed'.
   */
  static List *
  build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
! 				   List *clauses, List *other_clauses,
! 				   PartialAggInfo *agg_info, GroupedIndexPaths *agg_hashed)
  {
  	List	   *result = NIL;
  	List	   *all_clauses = NIL;	/* not computed till needed */
*************** build_paths_for_OR(PlannerInfo *root, Re
*** 1244,1250 ****
  									   useful_predicate,
  									   ST_BITMAPSCAN,
  									   NULL,
! 									   NULL);
  		result = list_concat(result, indexpaths);
  	}
  
--- 1501,1508 ----
  									   useful_predicate,
  									   ST_BITMAPSCAN,
  									   NULL,
! 									   NULL,
! 									   agg_info, NULL, agg_hashed);
  		result = list_concat(result, indexpaths);
  	}
  
*************** build_paths_for_OR(PlannerInfo *root, Re
*** 1260,1273 ****
   * other_clauses is a list of additional clauses that can be assumed true
   * for the purpose of generating indexquals, but are not to be searched for
   * ORs.  (See build_paths_for_OR() for motivation.)
   */
  static List *
  generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
! 						 List *clauses, List *other_clauses)
  {
  	List	   *result = NIL;
  	List	   *all_clauses;
  	ListCell   *lc;
  
  	/*
  	 * We can use both the current and other clauses as context for
--- 1518,1545 ----
   * other_clauses is a list of additional clauses that can be assumed true
   * for the purpose of generating indexquals, but are not to be searched for
   * ORs.  (See build_paths_for_OR() for motivation.)
+  *
+  * If agg_info is not NULL, consider grouped paths in addition.
   */
  static List *
  generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
! 						 List *clauses, List *other_clauses,
! 						 PartialAggInfo *agg_info)
  {
  	List	   *result = NIL;
  	List	   *all_clauses;
  	ListCell   *lc;
+ 	GroupedIndexPaths agg_hashed;
+ 	GroupedIndexPaths *agg_hashed_p = NULL;
+ 
+ 	/*
+ 	 * Prepare the output for grouped paths.
+ 	 *
+ 	 * Bitmap scan is not supposed to deliver the tuples sorted, so only
+ 	 * consider AGG_HASHED strategy.
+ 	 */
+ 	if (agg_info != NULL)
+ 		agg_hashed_p = &agg_hashed;
  
  	/*
  	 * We can use both the current and other clauses as context for
*************** generate_bitmap_or_paths(PlannerInfo *ro
*** 1287,1292 ****
--- 1559,1572 ----
  			continue;
  
  		/*
+ 		 * Free what the previous iteration might have allocated.
+ 		 */
+ 		list_free(agg_hashed.paths);
+ 		agg_hashed.paths = NIL;
+ 		list_free(agg_hashed.paths_partial);
+ 		agg_hashed.paths_partial = NIL;
+ 
+ 		/*
  		 * We must be able to match at least one index to each of the arms of
  		 * the OR, else we can't use it.
  		 */
*************** generate_bitmap_or_paths(PlannerInfo *ro
*** 1303,1315 ****
  
  				indlist = build_paths_for_OR(root, rel,
  											 andargs,
! 											 all_clauses);
  
  				/* Recurse in case there are sub-ORs */
  				indlist = list_concat(indlist,
  									  generate_bitmap_or_paths(root, rel,
  															   andargs,
! 															   all_clauses));
  			}
  			else
  			{
--- 1583,1597 ----
  
  				indlist = build_paths_for_OR(root, rel,
  											 andargs,
! 											 all_clauses,
! 											 agg_info, agg_hashed_p);
  
  				/* Recurse in case there are sub-ORs */
  				indlist = list_concat(indlist,
  									  generate_bitmap_or_paths(root, rel,
  															   andargs,
! 															   all_clauses,
! 															   agg_info));
  			}
  			else
  			{
*************** generate_bitmap_or_paths(PlannerInfo *ro
*** 1321,1327 ****
  
  				indlist = build_paths_for_OR(root, rel,
  											 orargs,
! 											 all_clauses);
  			}
  
  			/*
--- 1603,1610 ----
  
  				indlist = build_paths_for_OR(root, rel,
  											 orargs,
! 											 all_clauses,
! 											 agg_info, agg_hashed_p);
  			}
  
  			/*
*************** generate_bitmap_or_paths(PlannerInfo *ro
*** 1351,1356 ****
--- 1634,1659 ----
  			bitmapqual = (Path *) create_bitmap_or_path(root, rel, pathlist);
  			result = lappend(result, bitmapqual);
  		}
+ 
+ 		/*
+ 		 * Process the grouped paths.
+ 		 */
+ 		if (pathlist != NIL && agg_info != NULL)
+ 		{
+ 			AggPath    *agg_path;
+ 
+ 			foreach(lc, agg_hashed_p->paths)
+ 			{
+ 				agg_path = lfirst_node(AggPath, lc);
+ 				add_path(rel, (Path *) agg_path, true);
+ 			}
+ 
+ 			foreach(lc, agg_hashed_p->paths_partial)
+ 			{
+ 				agg_path = lfirst_node(AggPath, lc);
+ 				add_partial_path(rel, (Path *) agg_path, true);
+ 			}
+ 		}
  	}
  
  	return result;
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
new file mode 100644
index 7815138..b44b8a8
*** a/src/include/optimizer/paths.h
--- b/src/include/optimizer/paths.h
*************** extern void debug_print_rel(PlannerInfo
*** 75,81 ****
   * 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);
--- 75,82 ----
   * indxpath.c
   *	  routines to generate index paths
   */
! extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel,
! 				   PartialAggInfo *agg_info);
  extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
  							  List *restrictlist,
  							  List *exprlist, List *oprlist);
diff --git a/src/test/regress/expected/agg_pushdown.out b/src/test/regress/expected/agg_pushdown.out
new file mode 100644
index e3071de..cac70a0
*** a/src/test/regress/expected/agg_pushdown.out
--- b/src/test/regress/expected/agg_pushdown.out
*************** AS c1 ON c1.parent = p.i GROUP BY p.i;
*** 40,45 ****
--- 40,64 ----
                 Index Cond: (i = c1.parent)
  (8 rows)
  
+ -- Scan index on agg_pushdown_child1(parent) column and partially aggregate
+ -- the result using AGG_SORTED strategy.
+ SET enable_seqscan TO off;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
+ AS c1 ON c1.parent = p.i GROUP BY p.i;
+                                          QUERY PLAN                                          
+ ---------------------------------------------------------------------------------------------
+  Finalize GroupAggregate
+    Group Key: p.i
+    ->  Nested Loop
+          ->  Partial GroupAggregate
+                Group Key: c1.parent
+                ->  Index Scan using agg_pushdown_child1_parent_idx on agg_pushdown_child1 c1
+          ->  Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+                Index Cond: (i = c1.parent)
+ (8 rows)
+ 
+ SET enable_seqscan TO on;
  -- Perform nestloop join between agg_pushdown_child1 and agg_pushdown_child2
  -- and partially aggregate the result.
  SET enable_nestloop TO on;
*************** AS c1 ON c1.parent = p.i GROUP BY p.i;
*** 171,176 ****
--- 190,214 ----
                       Index Cond: (i = c1.parent)
  (10 rows)
  
+ SET enable_seqscan TO off;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
+ AS c1 ON c1.parent = p.i GROUP BY p.i;
+                                                  QUERY PLAN                                                 
+ ------------------------------------------------------------------------------------------------------------
+  Finalize HashAggregate
+    Group Key: p.i
+    ->  Gather
+          Workers Planned: 2
+          ->  Nested Loop
+                ->  Partial GroupAggregate
+                      Group Key: c1.parent
+                      ->  Parallel Index Scan using agg_pushdown_child1_parent_idx on agg_pushdown_child1 c1
+                ->  Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+                      Index Cond: (i = c1.parent)
+ (10 rows)
+ 
+ SET enable_seqscan TO on;
  SET enable_nestloop TO on;
  SET enable_hashjoin TO off;
  SET enable_mergejoin TO off;
*************** c2.parent = p.i WHERE c1.j = c2.k GROUP
*** 198,204 ****
  
  SET enable_nestloop TO off;
  SET enable_hashjoin TO on;
! -- TODO Force the Hash Join node to stay below Gather.
  -- EXPLAIN (COSTS off)
  -- SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
  -- agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
--- 236,244 ----
  
  SET enable_nestloop TO off;
  SET enable_hashjoin TO on;
! -- TODO Force the Hash Join node to stay below Gather. Does each parallel
! -- worker still use a separate hash table? Try again once this limitation has
! -- been fixed.
  -- EXPLAIN (COSTS off)
  -- SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
  -- agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
diff --git a/src/test/regress/sql/agg_pushdown.sql b/src/test/regress/sql/agg_pushdown.sql
new file mode 100644
index e6e40cb..f5b9b24
*** a/src/test/regress/sql/agg_pushdown.sql
--- b/src/test/regress/sql/agg_pushdown.sql
*************** EXPLAIN (COSTS off)
*** 40,45 ****
--- 40,53 ----
  SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
  AS c1 ON c1.parent = p.i GROUP BY p.i;
  
+ -- Scan index on agg_pushdown_child1(parent) column and partially aggregate
+ -- the result using AGG_SORTED strategy.
+ SET enable_seqscan TO off;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
+ AS c1 ON c1.parent = p.i GROUP BY p.i;
+ SET enable_seqscan TO on;
+ 
  -- Perform nestloop join between agg_pushdown_child1 and agg_pushdown_child2
  -- and partially aggregate the result.
  SET enable_nestloop TO on;
*************** EXPLAIN (COSTS off)
*** 100,105 ****
--- 108,119 ----
  SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
  AS c1 ON c1.parent = p.i GROUP BY p.i;
  
+ SET enable_seqscan TO off;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
+ AS c1 ON c1.parent = p.i GROUP BY p.i;
+ SET enable_seqscan TO on;
+ 
  SET enable_nestloop TO on;
  SET enable_hashjoin TO off;
  SET enable_mergejoin TO off;
*************** c2.parent = p.i WHERE c1.j = c2.k GROUP
*** 112,118 ****
  SET enable_nestloop TO off;
  SET enable_hashjoin TO on;
  
! -- TODO Force the Hash Join node to stay below Gather.
  -- EXPLAIN (COSTS off)
  -- SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
  -- agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
--- 126,135 ----
  SET enable_nestloop TO off;
  SET enable_hashjoin TO on;
  
! -- TODO Force the Hash Join node to stay below Gather. Does each parallel
! -- worker still use a separate hash table? Try again once this limitation has
! -- been fixed.
! 
  -- EXPLAIN (COSTS off)
  -- SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
  -- agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON


  agg_pushdown_v5/09_avoid_agg_finalization.diff
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index 85e8ea3..b1dd21e
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyAggref(const Aggref *from)
*** 1362,1367 ****
--- 1362,1368 ----
  	COPY_SCALAR_FIELD(inputcollid);
  	COPY_SCALAR_FIELD(aggtranstype);
  	COPY_SCALAR_FIELD(aggcombinefn);
+ 	COPY_SCALAR_FIELD(aggfinalfn);
  	COPY_NODE_FIELD(aggargtypes);
  	COPY_NODE_FIELD(aggdirectargs);
  	COPY_NODE_FIELD(args);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index d70d210..1ccaee7
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outAggref(StringInfo str, const Aggref
*** 1137,1142 ****
--- 1137,1143 ----
  	WRITE_OID_FIELD(inputcollid);
  	WRITE_OID_FIELD(aggtranstype);
  	WRITE_OID_FIELD(aggcombinefn);
+ 	WRITE_OID_FIELD(aggfinalfn);
  	WRITE_NODE_FIELD(aggargtypes);
  	WRITE_NODE_FIELD(aggdirectargs);
  	WRITE_NODE_FIELD(args);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
new file mode 100644
index f02a963..ed5686a
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readAggref(void)
*** 601,606 ****
--- 601,607 ----
  	READ_OID_FIELD(inputcollid);
  	READ_OID_FIELD(aggtranstype);
  	READ_OID_FIELD(aggcombinefn);
+ 	READ_OID_FIELD(aggfinalfn);
  	READ_NODE_FIELD(aggargtypes);
  	READ_NODE_FIELD(aggdirectargs);
  	READ_NODE_FIELD(args);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
new file mode 100644
index 045dd21..f95c913
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** set_plain_rel_pathlist(PlannerInfo *root
*** 706,711 ****
--- 706,714 ----
  	/* Consider sequential scan, both plain and grouped. */
  	seq_path = create_seqscan_path(root, rel, required_outer, 0);
  
+ 	/* Try to compute unique keys. */
+ 	make_uniquekeys(root, seq_path);
+ 
  	add_path(rel, seq_path, false);
  
  	if (rel->gpi != NULL && rel->gpi->target != NULL &&
*************** set_plain_rel_pathlist(PlannerInfo *root
*** 736,742 ****
  		create_plain_partial_paths(root, rel);
  
  	/*
! 	 * Consider index scans.
  	 */
  	create_index_paths(root, rel, agg_info);
  
--- 739,746 ----
  		create_plain_partial_paths(root, rel);
  
  	/*
! 	 * Consider index scans, possibly including the grouped and grouped
! 	 * partial paths.
  	 */
  	create_index_paths(root, rel, agg_info);
  
*************** create_grouped_path(PlannerInfo *root, R
*** 847,858 ****
  	if (aggstrategy == AGG_HASHED)
  		agg_path = (Path *) create_partial_agg_hashed_path(root, subpath,
  														   agg_info,
! 														   subpath->rows);
  	else if (aggstrategy == AGG_SORTED)
  		agg_path = (Path *) create_partial_agg_sorted_path(root, subpath,
  														   true,
  														   agg_info,
! 														   subpath->rows);
  	else
  		elog(ERROR, "unexpected strategy %d", aggstrategy);
  
--- 851,864 ----
  	if (aggstrategy == AGG_HASHED)
  		agg_path = (Path *) create_partial_agg_hashed_path(root, subpath,
  														   agg_info,
! 														   subpath->rows,
! 														   partial);
  	else if (aggstrategy == AGG_SORTED)
  		agg_path = (Path *) create_partial_agg_sorted_path(root, subpath,
  														   true,
  														   agg_info,
! 														   subpath->rows,
! 														   partial);
  	else
  		elog(ERROR, "unexpected strategy %d", aggstrategy);
  
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1864,1869 ****
--- 1870,1879 ----
  										   false, partitioned_rels, -1);
  		/* pathtarget will produce the grouped relation.. */
  		path->pathtarget = rel->gpi->target;
+ 
+ 		/* Try to compute unique keys. */
+ 		make_uniquekeys(root, path);
+ 
  		add_path(rel, path, true);
  	}
  
*************** generate_mergeappend_paths(PlannerInfo *
*** 2042,2047 ****
--- 2052,2060 ----
  												 NULL,
  												 partitioned_rels);
  
+ 		/* Try to compute unique keys. */
+ 		make_uniquekeys(root, path);
+ 
  		/* pathtarget will produce the grouped relation.. */
  		if (grouped)
  		{
*************** generate_mergeappend_paths(PlannerInfo *
*** 2060,2065 ****
--- 2073,2079 ----
  													 pathkeys,
  													 NULL,
  													 partitioned_rels);
+ 			make_uniquekeys(root, path);
  			if (grouped)
  				path->pathtarget = rel->gpi->target;
  			add_path(rel, path, grouped);
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
new file mode 100644
index 5c39ef6..65db91c
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
***************
*** 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"
--- 32,37 ----
*************** get_index_paths(PlannerInfo *root, RelOp
*** 852,858 ****
--- 851,866 ----
  		IndexPath  *ipath = (IndexPath *) lfirst(lc);
  
  		if (index->amhasgettuple)
+ 		{
+ 			/*
+ 			 * In case grouping may be pushed down, try to identify unique
+ 			 * keys to possibly avoid final aggregation.
+ 			 */
+ 			if (root->grouped_var_list != NIL)
+ 				make_uniquekeys(root, (Path *) ipath);
+ 
  			add_path(rel, (Path *) ipath, false);
+ 		}
  
  		if (index->amhasgetbitmap &&
  			(ipath->path.pathkeys == NIL ||
*************** build_index_paths(PlannerInfo *root, Rel
*** 1185,1191 ****
  														  (Path *) ipath,
  														  true,
  														  agg_info,
! 														  ipath->path.rows);
  
  				if (agg_path != NULL)
  					agg_sorted->paths = lappend(agg_sorted->paths, agg_path);
--- 1193,1200 ----
  														  (Path *) ipath,
  														  true,
  														  agg_info,
! 														  ipath->path.rows,
! 														  false);
  
  				if (agg_path != NULL)
  					agg_sorted->paths = lappend(agg_sorted->paths, agg_path);
*************** build_index_paths(PlannerInfo *root, Rel
*** 1198,1204 ****
  				agg_path = create_partial_agg_hashed_path(root,
  														  (Path *) ipath,
  														  agg_info,
! 														  ipath->path.rows);
  
  				if (agg_path != NULL)
  					agg_hashed->paths = lappend(agg_hashed->paths, agg_path);
--- 1207,1214 ----
  				agg_path = create_partial_agg_hashed_path(root,
  														  (Path *) ipath,
  														  agg_info,
! 														  ipath->path.rows,
! 														  false);
  
  				if (agg_path != NULL)
  					agg_hashed->paths = lappend(agg_hashed->paths, agg_path);
*************** build_index_paths(PlannerInfo *root, Rel
*** 1250,1256 ****
  																  (Path *) ipath,
  																  false,
  																  agg_info,
! 																  ipath->path.rows);
  
  						/*
  						 * If create_agg_sorted_path succeeded once, it should
--- 1260,1267 ----
  																  (Path *) ipath,
  																  false,
  																  agg_info,
! 																  ipath->path.rows,
! 																  true);
  
  						/*
  						 * If create_agg_sorted_path succeeded once, it should
*************** build_index_paths(PlannerInfo *root, Rel
*** 1267,1273 ****
  						agg_path = create_partial_agg_hashed_path(root,
  																  (Path *) ipath,
  																  agg_info,
! 																  ipath->path.rows);
  
  						/*
  						 * If create_agg_hashed_path succeeded once, it should
--- 1278,1285 ----
  						agg_path = create_partial_agg_hashed_path(root,
  																  (Path *) ipath,
  																  agg_info,
! 																  ipath->path.rows,
! 																  true);
  
  						/*
  						 * If create_agg_hashed_path succeeded once, it should
*************** build_index_paths(PlannerInfo *root, Rel
*** 1330,1336 ****
  															  (Path *) ipath,
  															  true,
  															  agg_info,
! 															  ipath->path.rows);
  
  					if (agg_path != NULL)
  						agg_sorted->paths = lappend(agg_sorted->paths,
--- 1342,1349 ----
  															  (Path *) ipath,
  															  true,
  															  agg_info,
! 															  ipath->path.rows,
! 															  false);
  
  					if (agg_path != NULL)
  						agg_sorted->paths = lappend(agg_sorted->paths,
*************** build_index_paths(PlannerInfo *root, Rel
*** 1378,1384 ****
  																	  (Path *) ipath,
  																	  false,
  																	  agg_info,
! 																	  ipath->path.rows);
  							Assert(agg_path != NULL);
  							agg_sorted->paths_partial =
  								lappend(agg_sorted->paths_partial, agg_path);
--- 1391,1398 ----
  																	  (Path *) ipath,
  																	  false,
  																	  agg_info,
! 																	  ipath->path.rows,
! 																	  true);
  							Assert(agg_path != NULL);
  							agg_sorted->paths_partial =
  								lappend(agg_sorted->paths_partial, agg_path);
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
new file mode 100644
index 7422fc4..883d8a1
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
*************** try_nestloop_path(PlannerInfo *root,
*** 520,525 ****
--- 520,532 ----
  							   workspace.startup_cost, workspace.total_cost,
  							   pathkeys, required_outer, grouped))
  	{
+ 		/*
+ 		 * For a grouped path try to identify unique keys, to possibly avoid
+ 		 * final aggregation.
+ 		 */
+ 		if (grouped)
+ 			make_uniquekeys(root, (Path *) join_path);
+ 
  		add_path(joinrel, join_path, grouped);
  	}
  	else
*************** try_mergejoin_path(PlannerInfo *root,
*** 933,938 ****
--- 940,952 ----
  							   workspace.startup_cost, workspace.total_cost,
  							   si->merge_pathkeys, required_outer, grouped))
  	{
+ 		/*
+ 		 * For a grouped path try to identify unique keys, to possibly avoid
+ 		 * final aggregation.
+ 		 */
+ 		if (grouped)
+ 			make_uniquekeys(root, (Path *) join_path);
+ 
  		add_path(joinrel, (Path *) join_path, grouped);
  	}
  	else
*************** try_hashjoin_path(PlannerInfo *root,
*** 1239,1244 ****
--- 1253,1265 ----
  							   workspace.startup_cost, workspace.total_cost,
  							   NIL, required_outer, grouped))
  	{
+ 		/*
+ 		 * For a grouped path try to identify unique keys, to possibly avoid
+ 		 * final aggregation.
+ 		 */
+ 		if (grouped)
+ 			make_uniquekeys(root, (Path *) join_path);
+ 
  		add_path(joinrel, (Path *) join_path, grouped);
  	}
  	else
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
new file mode 100644
index c6870d3..bee0157
*** a/src/backend/optimizer/path/pathkeys.c
--- b/src/backend/optimizer/path/pathkeys.c
*************** has_useful_pathkeys(PlannerInfo *root, R
*** 1563,1565 ****
--- 1563,1719 ----
  		return true;			/* might be able to use them for ordering */
  	return false;				/* definitely useless */
  }
+ 
+ /*
+  * Add a new set of unique keys to path's list of unique key sets. If an
+  * identical set is already there --- free new_set instead of adding it.
+  */
+ void
+ add_uniquekeys_to_path(Path *path, Bitmapset *new_set)
+ {
+ 	ListCell   *lc;
+ 
+ 	foreach(lc, path->uniquekeys)
+ 	{
+ 		Bitmapset  *set = (Bitmapset *) lfirst(lc);
+ 
+ 		if (bms_equal(new_set, set))
+ 			break;
+ 	}
+ 	if (lc == NULL)
+ 		path->uniquekeys = lappend(path->uniquekeys, new_set);
+ 	else
+ 		bms_free(new_set);
+ }
+ 
+ /*
+  * Return true if path output is grouped in a way described by
+  * root->group_pathkeys. AGGSPLIT_FINAL_DESERIAL can be skipped in such a
+  * case.
+  */
+ bool
+ match_path_to_group_pathkeys(PlannerInfo *root, Path *path)
+ {
+ 	Bitmapset  *uniquekeys_all = NULL;
+ 	ListCell   *l1;
+ 	int			i;
+ 	bool	   *is_group_expr;
+ 
+ 	/*
+ 	 * group_pathkeys are essential for this function.
+ 	 */
+ 	if (root->group_pathkeys == NIL)
+ 		return false;
+ 
+ 	/*
+ 	 * The path is not aware of being unique.
+ 	 */
+ 	if (path->uniquekeys == NIL)
+ 		return false;
+ 
+ 	/*
+ 	 * There can be multiple known unique key sets. Gather pathkeys of all the
+ 	 * unique expressions the sets may reference.
+ 	 */
+ 	foreach(l1, path->uniquekeys)
+ 	{
+ 		Bitmapset  *set = (Bitmapset *) lfirst(l1);
+ 
+ 		uniquekeys_all = bms_union(uniquekeys_all, set);
+ 	}
+ 
+ 	/*
+ 	 * Find pathkeys for the expressions.
+ 	 */
+ 	is_group_expr = (bool *)
+ 		palloc0(list_length(path->pathtarget->exprs) * sizeof(bool));
+ 
+ 	i = 0;
+ 	foreach(l1, path->pathtarget->exprs)
+ 	{
+ 		Expr	   *expr = (Expr *) lfirst(l1);
+ 
+ 		if (bms_is_member(i, uniquekeys_all))
+ 		{
+ 			ListCell   *l2;
+ 			bool		found = false;
+ 
+ 			/*
+ 			 * This is an unique expression, so find its pathkey.
+ 			 */
+ 			foreach(l2, root->group_pathkeys)
+ 			{
+ 				PathKey    *pk = lfirst_node(PathKey, l2);
+ 				EquivalenceClass *ec = pk->pk_eclass;
+ 				ListCell   *l3;
+ 				EquivalenceMember *em = NULL;
+ 
+ 				if (ec->ec_below_outer_join)
+ 					continue;
+ 				if (ec->ec_has_volatile)
+ 					continue;
+ 
+ 				foreach(l3, ec->ec_members)
+ 				{
+ 					em = lfirst_node(EquivalenceMember, l3);
+ 
+ 					if (em->em_nullable_relids)
+ 						continue;
+ 
+ 					if (equal(em->em_expr, expr))
+ 					{
+ 						found = true;
+ 						break;
+ 					}
+ 				}
+ 				if (found)
+ 					break;
+ 
+ 			}
+ 			is_group_expr[i] = found;
+ 		}
+ 
+ 		i++;
+ 	}
+ 
+ 	/*
+ 	 * Now check the unique key sets and see if any one has all its
+ 	 * expressions among group_pathkeys.
+ 	 */
+ 	foreach(l1, path->uniquekeys)
+ 	{
+ 		Bitmapset  *set = (Bitmapset *) lfirst(l1);
+ 		bool		found = false;
+ 
+ 		/*
+ 		 * Collect PKs associated with this set.
+ 		 */
+ 		for (i = 0; i < list_length(path->pathtarget->exprs); i++)
+ 		{
+ 			if (bms_is_member(i, set))
+ 			{
+ 				/*
+ 				 * If the set misses a single grouping path key, at least one
+ 				 * expression of the unique key is outside the grouping
+ 				 * expressions, and thus the grouping expressions are not
+ 				 * guaranteed to be unique. Thus the avoidance of final
+ 				 * aggregation is not justified.
+ 				 */
+ 				if (!is_group_expr[i])
+ 				{
+ 					found = true;
+ 					break;
+ 				}
+ 			}
+ 		}
+ 
+ 		/*
+ 		 * No problem with this set. No need to check the other ones.
+ 		 */
+ 		if (!found)
+ 			return true;
+ 	}
+ 
+ 	/* No match found. */
+ 	return false;
+ }
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 94b1f8e..6ad1218
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** static PathTarget *make_sort_input_targe
*** 194,199 ****
--- 194,201 ----
  					   bool *have_postponed_srfs);
  static void adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
  					  List *targets, List *targets_contain_srfs);
+ static Path *adjust_path_for_unique_group_keys(PlannerInfo *root, Path *path,
+ 								  PathTarget *target);
  
  
  /*****************************************************************************
*************** grouping_planner(PlannerInfo *root, bool
*** 1977,1982 ****
--- 1979,1985 ----
  												grouping_target,
  												&agg_costs,
  												gset_data);
+ 
  			/* Fix things up if grouping_target contains SRFs */
  			if (parse->hasTargetSRFs)
  				adjust_paths_for_srfs(root, current_rel,
*************** create_grouping_paths(PlannerInfo *root,
*** 4178,4183 ****
--- 4181,4212 ----
  		}
  	}
  
+ 	/*
+ 	 * If input_rel has partially aggregated paths which emit an unique set of
+ 	 * grouping keys, we only need to call aggfinalfn on each aggregate state
+ 	 * instead of doing the final aggregation
+ 	 */
+ 	if (input_rel->gpi != NULL && !parse->groupingSets)
+ 	{
+ 		foreach(lc, input_rel->gpi->pathlist)
+ 		{
+ 			Path	   *path = (Path *) lfirst(lc);
+ 
+ 			/*
+ 			 * If aggregation could have been pushed down and if the path
+ 			 * generates unique grouping keys, replace aggregates with the
+ 			 * aggregate final functions alone.
+ 			 */
+ 			if ((parse->groupClause || parse->hasAggs || root->hasHavingQual)
+ 				&& root->grouped_var_list != NIL &&
+ 				match_path_to_group_pathkeys(root, path))
+ 				add_path(grouped_rel,
+ 						 adjust_path_for_unique_group_keys(root, path,
+ 														   target),
+ 						 false);
+ 		}
+ 	}
+ 
  	/* Give a helpful error if we failed to find any implementation */
  	if (grouped_rel->pathlist == NIL)
  		ereport(ERROR,
*************** adjust_paths_for_srfs(PlannerInfo *root,
*** 6160,6165 ****
--- 6189,6228 ----
  }
  
  /*
+  * Project a path that generates unique grouping keys to a new one whose
+  * target, instead of the (partial) aggregates, contains the aggregates' final
+  * functions, each referencing the corresponding partial aggregate.
+  */
+ static Path *
+ adjust_path_for_unique_group_keys(PlannerInfo *root, Path *path,
+ 								  PathTarget *target)
+ {
+ 	Path	   *result;
+ 
+ 	/*
+ 	 * While path->pathtarget contains GroupedVars, "target contains Aggrefs.
+ 	 * setrefs.c will recognize and handle this special case.
+ 	 *
+ 	 * Pass true for "force_result" so that the input path's target does not
+ 	 * receive extra entries (such as junk TLEs) from upper nodes (e.g. Sort)
+ 	 * that it might not be able to evaluate (e.g. aggregates)
+ 	 */
+ 	result = (Path *) create_projection_path(root, path->parent, path,
+ 											 target, true);
+ 
+ 	/*
+ 	 * TODO Account for the evaluation of the aggfinalfn function. BTW, this
+ 	 * might be a case for a new kind of Path instead of projection. Otherwise
+ 	 * it's hard to detect this special kind of input and output targetlist,
+ 	 * without checking for presence of Aggrefs and GroupedVars respectively.
+ 	 */
+ 	result->startup_cost = path->startup_cost;
+ 	result->total_cost = path->total_cost;
+ 	result->rows = path->rows;
+ 	return result;
+ }
+ 
+ /*
   * expression_planner
   *		Perform planner's transformations on a standalone expression.
   *
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
new file mode 100644
index b5be81c..502c725
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
*************** static Node *fix_scan_expr_mutator(Node
*** 108,113 ****
--- 108,115 ----
  static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
  static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
  static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+ static bool contains_groupedvar(Node *node);
+ static bool contains_groupedvar_walker(Node *node, void *context);
  static void set_param_references(PlannerInfo *root, Plan *plan);
  static Node *convert_combining_aggrefs(Node *node, void *context);
  static void set_dummy_tlist_references(Plan *plan, int rtoffset);
*************** set_upper_references(PlannerInfo *root,
*** 1786,1791 ****
--- 1788,1808 ----
  					replace_grouped_vars_with_aggrefs(root, subplan->targetlist);
  			}
  		}
+ 		else if (IsA(plan, Result))
+ 		{
+ 			/*
+ 			 * Check if this is the projection created by
+ 			 * adjust_path_for_unique_group_keys.
+ 			 */
+ 			if (root->grouped_var_list != NIL &&
+ 				contains_aggref_simple((Node *) plan->targetlist) &&
+ 				contains_groupedvar((Node *) subplan->targetlist))
+ 			{
+ 				plan->targetlist = (List *)
+ 					replace_aggref_with_groupedvar((Node *) plan->targetlist,
+ 												   root->grouped_var_list);
+ 			}
+ 		}
  	}
  
  	subplan_itlist = build_tlist_index(subplan->targetlist);
*************** set_upper_references(PlannerInfo *root,
*** 1841,1846 ****
--- 1858,1882 ----
  }
  
  /*
+  * Find out if the node contains at least one GroupedVar.
+  */
+ static bool
+ contains_groupedvar(Node *node)
+ {
+ 	return contains_groupedvar_walker(node, NULL);
+ }
+ 
+ static bool
+ contains_groupedvar_walker(Node *node, void *context)
+ {
+ 	if (node == NULL)
+ 		return false;
+ 	if (IsA(node, GroupedVar))
+ 		return true;			/* abort the tree traversal and return true */
+ 	return expression_tree_walker(node, contains_groupedvar_walker, context);
+ }
+ 
+ /*
   * set_param_references
   *	  Initialize the initParam list in Gather or Gather merge node such that
   *	  it contains reference of all the params that needs to be evaluated
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
new file mode 100644
index c3528fc..1d01457
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
*************** static List *reparameterize_pathlist_by_
*** 60,65 ****
--- 60,68 ----
  static Path *add_grouping_expressions_to_subpath(PlannerInfo *root,
  									Path *subpath,
  									PathTarget *group_exprs);
+ static void make_uniquekeys_for_unique_index(PathTarget *reltarget,
+ 								 IndexOptInfo *index, Path *path);
+ static void make_uniquekeys_for_append_path(PlannerInfo *root, Path *path);
  
  /*****************************************************************************
   *		MISC. PATH UTILITIES
*************** create_material_path(RelOptInfo *rel, Pa
*** 1515,1520 ****
--- 1518,1524 ----
  		subpath->parallel_safe;
  	pathnode->path.parallel_workers = subpath->parallel_workers;
  	pathnode->path.pathkeys = subpath->pathkeys;
+ 	pathnode->path.uniquekeys = subpath->uniquekeys;
  
  	pathnode->subpath = subpath;
  
*************** create_projection_path(PlannerInfo *root
*** 2434,2439 ****
--- 2438,2444 ----
  	pathnode->path.parallel_workers = subpath->parallel_workers;
  	/* Projection does not change the sort order */
  	pathnode->path.pathkeys = subpath->pathkeys;
+ 	pathnode->path.uniquekeys = subpath->uniquekeys;
  
  	pathnode->subpath = subpath;
  	pathnode->force_result = force_result;
*************** create_sort_path(PlannerInfo *root,
*** 2685,2690 ****
--- 2690,2696 ----
  		subpath->parallel_safe;
  	pathnode->path.parallel_workers = subpath->parallel_workers;
  	pathnode->path.pathkeys = pathkeys;
+ 	pathnode->path.uniquekeys = subpath->uniquekeys;
  
  	pathnode->subpath = subpath;
  
*************** get_partial_agg_info(PlannerInfo *root,
*** 2963,2969 ****
  
  /*
   * Apply partial AGG_SORTED aggregation path to subpath if it's suitably
!  * sorted.
   *
   * check_pathkeys can be passed FALSE if the function was already called for
   * given index --- since the target should not change, we can skip the check
--- 2969,2976 ----
  
  /*
   * Apply partial AGG_SORTED aggregation path to subpath if it's suitably
!  * sorted. ("partial" in the function name refers to AGGSPLIT_INITIAL_SERIAL
!  * strategy, as opposed to parallel processing.)
   *
   * check_pathkeys can be passed FALSE if the function was already called for
   * given index --- since the target should not change, we can skip the check
*************** AggPath *
*** 2977,2983 ****
  create_partial_agg_sorted_path(PlannerInfo *root, Path *subpath,
  							   bool check_pathkeys,
  							   PartialAggInfo *agg_info,
! 							   double input_rows)
  {
  	RelOptInfo *rel;
  	AggClauseCosts agg_costs;
--- 2984,2991 ----
  create_partial_agg_sorted_path(PlannerInfo *root, Path *subpath,
  							   bool check_pathkeys,
  							   PartialAggInfo *agg_info,
! 							   double input_rows,
! 							   bool parallel)
  {
  	RelOptInfo *rel;
  	AggClauseCosts agg_costs;
*************** create_partial_agg_sorted_path(PlannerIn
*** 3047,3065 ****
  	Assert(agg_info->group_clauses != NIL);
  	result = create_agg_path(root, rel, subpath, rel->gpi->target,
  							 AGG_SORTED, AGGSPLIT_INITIAL_SERIAL,
! 							 agg_info->group_clauses, NIL, &agg_costs, dNumGroups);
  
  	return result;
  }
  
  /*
!  * Apply 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,
! 							   PartialAggInfo *agg_info, double input_rows)
  {
  	RelOptInfo *rel;
  	bool		can_hash;
--- 3055,3083 ----
  	Assert(agg_info->group_clauses != NIL);
  	result = create_agg_path(root, rel, subpath, rel->gpi->target,
  							 AGG_SORTED, AGGSPLIT_INITIAL_SERIAL,
! 							 agg_info->group_clauses, NIL, &agg_costs,
! 							 dNumGroups);
! 
! 	/*
! 	 * Check if there's a chance to avoid final aggregation.
! 	 */
! 	if (!parallel)
! 		make_uniquekeys(root, (Path *) result);
  
  	return result;
  }
  
  /*
!  * Apply partial AGG_HASHED aggregation to subpath. ("partial" in the function
!  * name refers to AGGSPLIT_INITIAL_SERIAL strategy, as opposed to parallel
!  * processing.)
   *
   * Arguments have the same meaning as those of create_agg_sorted_path.
   */
  AggPath *
  create_partial_agg_hashed_path(PlannerInfo *root, Path *subpath,
! 							   PartialAggInfo *agg_info, double input_rows,
! 							   bool parallel)
  {
  	RelOptInfo *rel;
  	bool		can_hash;
*************** create_partial_agg_hashed_path(PlannerIn
*** 3123,3128 ****
--- 3141,3152 ----
  		}
  	}
  
+ 	/*
+ 	 * Check if there's a chance to avoid final aggregation.
+ 	 */
+ 	if (result != NULL && !parallel)
+ 		make_uniquekeys(root, (Path *) result);
+ 
  	return result;
  }
  
*************** add_grouping_expressions_to_subpath(Plan
*** 4268,4270 ****
--- 4292,4881 ----
  										   target,
  										   true);
  }
+ 
+ /*
+  * Find out if path produces an unique set of expressions and set uniquekeys
+  * accordingly.
+  *
+  * TODO Check if any expression of any unique key isn't nullable, whether in
+  * the table / index or by an outer join.
+  */
+ void
+ make_uniquekeys(PlannerInfo *root, Path *path)
+ {
+ 	RelOptInfo *rel;
+ 
+ 	/*
+ 	 * In general, uniquekeys make no sense if the query has HAVING clause
+ 	 * because it requires the aggregate finalization anyway.
+ 	 *
+ 	 * XXX If all aggregates, including those in the HAVING clause had no
+ 	 * aggfinalfn, we could add the corresponding GroupedVars to the grouped
+ 	 * target and construct the uniquekeys. Maybe even evaluate the HAVING
+ 	 * quals at relation / join level?
+ 	 */
+ 	if (root->parse->havingQual != NULL)
+ 		return;
+ 
+ 	/*
+ 	 * The unique keys are not interesting if there's no chance to push
+ 	 * aggregation down to base relations / joins.
+ 	 */
+ 	if (root->grouped_var_list == NIL)
+ 		return;
+ 
+ 	/*
+ 	 * Do not accept repeated calls of the function on the same path.
+ 	 */
+ 	if (path->uniquekeys != NIL)
+ 		return;
+ 
+ 	rel = path->parent;
+ 
+ 	/*
+ 	 * Base relations.
+ 	 */
+ 	if (IsA(path, IndexPath) ||
+ 		(IsA(path, Path) &&path->pathtype == T_SeqScan) ||
+ 		IsA(path, AppendPath) ||IsA(path, MergeAppendPath))
+ 	{
+ 		ListCell   *lc;
+ 
+ 		/*
+ 		 * Derive grouping keys from unique indexes.
+ 		 */
+ 		if (IsA(path, IndexPath) ||IsA(path, Path))
+ 		{
+ 			foreach(lc, rel->indexlist)
+ 			{
+ 				IndexOptInfo *index = lfirst_node(IndexOptInfo, lc);
+ 
+ 				make_uniquekeys_for_unique_index(rel->reltarget, index, path);
+ 			}
+ 		}
+ 		else if (IsA(path, AppendPath) ||IsA(path, MergeAppendPath))
+ 			make_uniquekeys_for_append_path(root, path);
+ #ifdef USE_ASSERT_CHECKING
+ 		else
+ 			Assert(false);
+ #endif
+ 		return;
+ 	}
+ 
+ 	if (IsA(path, AggPath))
+ 	{
+ 		/*
+ 		 * The immediate output of aggregation essentially produces an unique
+ 		 * set of grouping keys.
+ 		 */
+ 		make_uniquekeys_for_agg_path(root, path);
+ 	}
+ 	else if (IS_JOIN_REL(path->parent))
+ 	{
+ 		JoinPath   *jpath = (JoinPath *) path;
+ 		Path	   *outerpath = jpath->outerjoinpath;
+ 		Path	   *innerpath = jpath->innerjoinpath;
+ 		ListCell   *l1;
+ 
+ 		/*
+ 		 * Find out if the join produces unique keys for various combinations
+ 		 * of input sets of unique keys.
+ 		 *
+ 		 * TODO Implement heuristic that picks a few most useful sets on each
+ 		 * side, to avoid exponential growth of the uniquekeys list as we
+ 		 * proceed from lower to higher joins. Maybe also discard the
+ 		 * resulting sets containing unique expressions which are not grouping
+ 		 * expressions (and of course which are not aggregates) of this join's
+ 		 * target.
+ 		 */
+ 		foreach(l1, outerpath->uniquekeys)
+ 		{
+ 			Bitmapset  *outerset = (Bitmapset *) lfirst(l1);
+ 			ListCell   *l2;
+ 
+ 			foreach(l2, innerpath->uniquekeys)
+ 			{
+ 				Bitmapset  *innerset = (Bitmapset *) lfirst(l2);
+ 				Bitmapset  *joinset;
+ 
+ 				/*
+ 				 * Given that unique keys of each input relation are contained
+ 				 * in that relation's target, the union of the key sets should
+ 				 * be contained in the join target.
+ 				 */
+ 				joinset = bms_union(outerset, innerset);
+ 
+ 				/* Add the set to the path. */
+ 				add_uniquekeys_to_path((Path *) jpath, joinset);
+ 			}
+ 		}
+ 	}
+ #ifdef USE_ASSERT_CHECKING
+ 
+ 	/*
+ 	 * TODO Consider other ones, e.g. UniquePath.
+ 	 */
+ 	else
+ 		Assert(false);
+ #endif
+ }
+ 
+ /*
+  * Create a set of positions of expressions in reltarget if the index is
+  * unique and if reltarget contains all the index columns. Add the set to
+  * uniquekeys if identical one is not already there.
+  */
+ static void
+ make_uniquekeys_for_unique_index(PathTarget *reltarget, IndexOptInfo *index,
+ 								 Path *path)
+ {
+ 	int			i;
+ 	Bitmapset  *new_set = NULL;
+ 
+ 	/*
+ 	 * Give up if the index does not guarantee uniqueness.
+ 	 */
+ 	if (!index->unique || !index->immediate ||
+ 		(index->indpred != NIL && !index->predOK))
+ 		return;
+ 
+ 	/*
+ 	 * For the index path to be acceptable, reltarget must contain all the
+ 	 * index columns.
+ 	 *
+ 	 * reltarget is not supposed to contain non-var expressions, so the index
+ 	 * should neither.
+ 	 */
+ 	if (index->indexprs != NULL)
+ 		return;
+ 
+ 	for (i = 0; i < index->ncolumns; i++)
+ 	{
+ 		int			indkey = index->indexkeys[i];
+ 		ListCell   *lc;
+ 		bool		found = false;
+ 		int			j = 0;
+ 
+ 		foreach(lc, reltarget->exprs)
+ 		{
+ 			Var		   *var = lfirst_node(Var, lc);
+ 
+ 			if (var->varno == index->rel->relid && var->varattno == indkey)
+ 			{
+ 				new_set = bms_add_member(new_set, j);
+ 				found = true;
+ 				break;
+ 			}
+ 
+ 			j++;
+ 		}
+ 
+ 		/*
+ 		 * If rel needs less than the whole index key then the values of the
+ 		 * columns matched so far can be duplicate.
+ 		 */
+ 		if (!found)
+ 		{
+ 			bms_free(new_set);
+ 			return;
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Add the set to the path, unless it's already there.
+ 	 */
+ 	add_uniquekeys_to_path(path, new_set);
+ }
+ 
+ /*
+  * Create uniquekeys for a path that has Aggrefs in its target.
+  * set.
+  *
+  * Besides AggPath, ForeignPath is a known use case for this function.
+  */
+ void
+ make_uniquekeys_for_agg_path(PlannerInfo *root, Path *path)
+ {
+ 	PathTarget *target;
+ 	ListCell   *lc;
+ 	Bitmapset  *keyset = NULL;
+ 	int			i = 0;
+ 
+ 	target = path->pathtarget;
+ 	Assert(target->sortgrouprefs != NULL);
+ 
+ 	foreach(lc, target->exprs)
+ 	{
+ 		Expr	   *expr = (Expr *) lfirst(lc);
+ 
+ 		if (IsA(expr, GroupedVar))
+ 		{
+ 			GroupedVar *gvar = castNode(GroupedVar, expr);
+ 
+ 			if (!IsA(gvar->gvexpr, Aggref))
+ 			{
+ 				/*
+ 				 * Generic grouping expression.
+ 				 */
+ 				keyset = bms_add_member(keyset, i);
+ 			}
+ 		}
+ 		else
+ 		{
+ 			Index		sortgroupref = target->sortgrouprefs[i];
+ 
+ 			Assert(IsA(expr, Var));
+ 
+ 			/*
+ 			 * Ignore "extra grouping expressions" that might have been added
+ 			 * by prepare_rel_for_grouping() --- these exist merely for join
+ 			 * purposes and thus should not appear in the final targetlist.
+ 			 */
+ 			if (sortgroupref > 0 && sortgroupref <= root->max_sortgroupref)
+ 			{
+ 				/*
+ 				 * Plain Var grouping expression.
+ 				 */
+ 				keyset = bms_add_member(keyset, i);
+ 			}
+ 			else
+ 			{
+ 				/*
+ 				 * A column functionally dependent on the GROUP BY clause?
+ 				 */
+ 			}
+ 		}
+ 
+ 		i++;
+ 	}
+ 
+ 	add_uniquekeys_to_path((Path *) path, keyset);
+ }
+ 
+ /*
+  * Create uniquekeys for a AppendPath or MergeAppendPath.
+  *
+  * Besides AggPath, ForeignPath is a known use case for this function.
+  */
+ static void
+ make_uniquekeys_for_append_path(PlannerInfo *root, Path *path)
+ {
+ 	RelOptInfo *rel = path->parent;
+ 	List	   *subpaths;
+ 	Path	   *subpath;
+ 	ListCell   *l1;
+ 	bool		first = true;
+ 	List	   *uniquekeys_common = NIL;
+ 
+ 	/*
+ 	 * In addition to the requirement that all subpaths must have uniquekeys
+ 	 * set, the table needs to be partitioned in a specific way. Reject
+ 	 * non-partitioned tables immediately, the other check to follow.
+ 	 */
+ 	if (!IS_PARTITIONED_REL(rel))
+ 		return;
+ 
+ 	if (IsA(path, AppendPath))
+ 		subpaths = ((AppendPath *) path)->subpaths;
+ 	else if (IsA(path, MergeAppendPath))
+ 		subpaths = ((MergeAppendPath *) path)->subpaths;
+ #ifdef USE_ASSERT_CHECKING
+ 	else
+ 		Assert(false);
+ #endif
+ 
+ 	/*
+ 	 * Check if each subpath has uniquekeys.
+ 	 */
+ 	foreach(l1, subpaths)
+ 	{
+ 		subpath = (Path *) lfirst(l1);
+ 
+ 		/*
+ 		 * If any subpath does not have uniquekeys, the whole append path can
+ 		 * have them neither.
+ 		 */
+ 		if (subpath->uniquekeys == NIL)
+ 			return;
+ 	}
+ 
+ 	/*
+ 	 * Choose groupkey sets that each subpath has.
+ 	 */
+ 	foreach(l1, subpaths)
+ 	{
+ 		List	   *common_new = NIL;
+ 		ListCell   *l2;
+ 
+ 		subpath = (Path *) lfirst(l1);
+ 
+ 		if (uniquekeys_common == NIL)
+ 		{
+ 			/* Get the initial list from the first subpath. */
+ 			uniquekeys_common = subpath->uniquekeys;
+ 			continue;
+ 		}
+ 
+ 		/*
+ 		 * Remove items missing in the current subpath from uniquekeys_common.
+ 		 */
+ 		foreach(l2, uniquekeys_common)
+ 		{
+ 			Bitmapset  *set1 = (Bitmapset *) lfirst(l2);
+ 			ListCell   *l3;
+ 
+ 			/*
+ 			 * Does the current subpath contain this set?
+ 			 */
+ 			foreach(l3, subpath->uniquekeys)
+ 			{
+ 				Bitmapset  *set2 = (Bitmapset *) lfirst(l3);
+ 
+ 				if (bms_equal(set1, set2))
+ 				{
+ 					common_new = lappend(common_new, set1);
+ 					break;
+ 				}
+ 			}
+ 		}
+ 
+ 		/*
+ 		 * If there are no sets in common, the next subpaths cannot help us.
+ 		 */
+ 		if (common_new == NIL)
+ 			return;
+ 
+ 		/*
+ 		 * Adopt the new, possibly reduced list.
+ 		 */
+ 		uniquekeys_common = common_new;
+ 	}
+ 
+ 	foreach(l1, subpaths)
+ 	{
+ 		subpath = (Path *) lfirst(l1);
+ 
+ 		/*
+ 		 * The following tests should ideally be performed either on
+ 		 * rel->reltarget or rel->gpi->target outside the iteration of
+ 		 * subpaths. However there's no easy way to find out if path is
+ 		 * grouped or not. So we check the target of the first subpath and
+ 		 * assume that the other ones would pass or fail in the same way:
+ 		 * another subpath target should be the same path target whose
+ 		 * attributes are translated to another child relations.
+ 		 */
+ 		if (first)
+ 		{
+ 			List	   *partexprs = NIL;
+ 			List	   *partexprs_all = NIL;
+ 			ListCell   *l2,
+ 					   *l3;
+ 			AppendRelInfo **appinfos;
+ 			int			i,
+ 						nappinfos;
+ 			bool		found;
+ 			List	   *texprs = NIL;
+ 
+ 			/*
+ 			 * Put all partition expressions into two lists --- one for
+ 			 * non-nullable expressions, one for nullable.
+ 			 */
+ 			for (i = 0; i < rel->part_scheme->partnatts; i++)
+ 			{
+ 				List	   *sublist;
+ 
+ 				sublist = rel->partexprs[i];
+ 				if (sublist != NIL)
+ 				{
+ 					partexprs = list_union(partexprs, sublist);
+ 					partexprs_all = list_union(partexprs_all,
+ 											   sublist);
+ 				}
+ 
+ 				/*
+ 				 * The nullable expressions should only appear in
+ 				 * partexprs_all.
+ 				 */
+ 				sublist = rel->nullable_partexprs[i];
+ 				if (sublist != NIL)
+ 					partexprs_all = list_union(partexprs_all,
+ 											   sublist);
+ 			}
+ 			Assert(partexprs != NIL);
+ 
+ 			/*
+ 			 * Translate the partitioning expressions so that they can match
+ 			 * the subpath target.
+ 			 */
+ 			appinfos =
+ 				find_appinfos_by_relids(root, subpath->parent->relids,
+ 										&nappinfos);
+ 			partexprs = (List *)
+ 				adjust_appendrel_attrs(root, (Node *) partexprs,
+ 									   nappinfos, appinfos);
+ 			partexprs_all = (List *)
+ 				adjust_appendrel_attrs(root, (Node *) partexprs_all,
+ 									   nappinfos, appinfos);
+ 			pfree(appinfos);
+ 
+ 			/*
+ 			 * Since equivalence classes can be used to derive target
+ 			 * expressions, the following checks have to consider ECs too. The
+ 			 * targetlist we construct here contains the original expressions
+ 			 * plus those generated from the appropriate ECs.
+ 			 */
+ 			foreach(l2, subpath->pathtarget->exprs)
+ 			{
+ 				Expr	   *texpr = (Expr *) lfirst(l2);
+ 
+ 				if (IsA(texpr, GroupedVar))
+ 				{
+ 					GroupedVar *gvar = castNode(GroupedVar, texpr);
+ 
+ 					if (IsA(gvar->gvexpr, Aggref))
+ 					{
+ 						/*
+ 						 * Aggregate should not appear in any EC.
+ 						 */
+ 						texprs = lappend(texprs, texpr);
+ 						continue;
+ 					}
+ 
+ 					/*
+ 					 * The contained expression (i.e. generic grouping
+ 					 * expression) is what we'll search for in the ECs.
+ 					 */
+ 					texpr = gvar->gvexpr;
+ 				}
+ 
+ 				/*
+ 				 * Search for matching EC.
+ 				 */
+ 				foreach(l3, root->group_pathkeys)
+ 				{
+ 					PathKey    *pk = lfirst_node(PathKey, l3);
+ 					EquivalenceClass *ec = pk->pk_eclass;
+ 					ListCell   *l4;
+ 					EquivalenceMember *em;
+ 
+ 					if (ec->ec_below_outer_join)
+ 						continue;
+ 
+ 					if (ec->ec_has_volatile)
+ 						continue;
+ 
+ 					foreach(l4, ec->ec_members)
+ 					{
+ 						em = lfirst_node(EquivalenceMember, l4);
+ 
+ 						if (em->em_nullable_relids)
+ 							continue;
+ 
+ 						if (!bms_is_subset(em->em_relids,
+ 										   subpath->parent->relids))
+ 							continue;
+ 
+ 						if (equal(em->em_expr, texpr))
+ 							break;
+ 					}
+ 
+ 					/*
+ 					 * texpr not found in the current EC, try the next one.
+ 					 */
+ 					if (l4 == NULL)
+ 						continue;
+ 
+ 					/*
+ 					 * The EC does match, so add all its members to texprs.
+ 					 */
+ 					foreach(l4, ec->ec_members)
+ 					{
+ 						em = lfirst_node(EquivalenceMember, l4);
+ 						texprs = lappend(texprs, em->em_expr);
+ 					}
+ 
+ 					/*
+ 					 * No expression is supposed to appear in multiple EC.
+ 					 */
+ 					continue;
+ 				}
+ 			}
+ 
+ 			/*
+ 			 * To ensure that no output row can be produced by multiple
+ 			 * partitions, check that:
+ 			 *
+ 			 * (a) at least one output column is a partitioning expression.
+ 			 * Thus no output row of this path can appear in multiple
+ 			 * partitions.
+ 			 *
+ 			 * Only search in the non-nullable partitioning expressions
+ 			 * because NULL value can be generated by any partition.
+ 			 */
+ 			found = false;
+ 			foreach(l2, texprs)
+ 			{
+ 				Expr	   *texpr = (Expr *) lfirst(l2);
+ 
+ 				/*
+ 				 * Try to find the matching partitioning expression.
+ 				 */
+ 				foreach(l3, partexprs)
+ 				{
+ 					Expr	   *pexpr = lfirst(l3);
+ 
+ 					if (equal(texpr, pexpr))
+ 					{
+ 						found = true;
+ 						break;
+ 					}
+ 				}
+ 
+ 				if (found)
+ 					break;
+ 			}
+ 			if (!found)
+ 			{
+ 				list_free(uniquekeys_common);
+ 				return;
+ 			}
+ 
+ 			/*
+ 			 * (b) there's no partitioning expression not contained in the
+ 			 * target. If there was some, then the partitioning expression
+ 			 * found above could appear in multiple partitions.
+ 			 *
+ 			 * Even nullable partition key makes harm here, so consider all
+ 			 * partition keys this time.
+ 			 */
+ 			foreach(l2, partexprs_all)
+ 			{
+ 				Expr	   *pexpr = lfirst(l2);
+ 
+ 				found = false;
+ 				foreach(l3, texprs)
+ 				{
+ 					Expr	   *texpr = (Expr *) lfirst(l3);
+ 
+ 					if (equal(pexpr, texpr))
+ 					{
+ 						found = true;
+ 						break;
+ 					}
+ 				}
+ 				if (!found)
+ 				{
+ 					list_free(uniquekeys_common);
+ 					return;
+ 				}
+ 			}
+ 			list_free(texprs);
+ 
+ 			/* Do not repeat the checks for the other subpaths. */
+ 			first = false;
+ 		}
+ 	}
+ 
+ 	/* All the checks passed. */
+ 	path->uniquekeys = uniquekeys_common;
+ }
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
new file mode 100644
index b9c2d13..3f25949
*** a/src/backend/optimizer/util/tlist.c
--- b/src/backend/optimizer/util/tlist.c
*************** replace_grouped_vars_with_aggrefs(Planne
*** 835,840 ****
--- 835,970 ----
  	return result;
  }
  
+ static Node *
+ replace_aggref_with_groupedvar_mutator(Node *node, List *grouped_vars)
+ {
+ 	if (node == NULL)
+ 		return NULL;
+ 
+ 	/*
+ 	 * No node should contain both AGGSPLIT_SIMPLE Aggref and GroupedVar.
+ 	 * Caller should know when the replacement should be performed, otherwise
+ 	 * he must wastes memory and time.
+ 	 */
+ 	Assert(!IsA(node, GroupedVar));
+ 
+ 	if (IsA(node, Aggref))
+ 	{
+ 		Aggref	   *aggref = castNode(Aggref, node);
+ 		ListCell   *l;
+ 
+ 		/*
+ 		 * There's currently no usecase for other aggsplit. If there should be
+ 		 * some, extra effort is needed to make the aggref match gvi->gvexpr.
+ 		 */
+ 		Assert(aggref->aggsplit == AGGSPLIT_SIMPLE);
+ 
+ 		foreach(l, grouped_vars)
+ 		{
+ 			GroupedVarInfo *gvi = lfirst_node(GroupedVarInfo, l);
+ 
+ 			if (equal(gvi->gvexpr, aggref))
+ 			{
+ 				GroupedVar *gvar = makeNode(GroupedVar);
+ 
+ 				gvar = makeNode(GroupedVar);
+ 				gvar->gvexpr = gvi->gvexpr;
+ 				gvar->gvid = gvi->gvid;
+ 				gvar->sortgroupref = gvi->sortgroupref;
+ 
+ 				/*
+ 				 * Arrange for the call of aggfinalfn or let the transient
+ 				 * state appear on the output unprocessed.
+ 				 */
+ 				if (OidIsValid(aggref->aggfinalfn))
+ 				{
+ 					FuncExpr   *fexpr = makeNode(FuncExpr);
+ 
+ 					fexpr = makeNode(FuncExpr);
+ 					fexpr->funcid = aggref->aggfinalfn;
+ 					fexpr->funcresulttype = aggref->aggtype;
+ 					fexpr->funcretset = false;
+ 					fexpr->funcvariadic = aggref->aggvariadic;
+ 					fexpr->funcformat = COERCE_EXPLICIT_CALL;
+ 					fexpr->funccollid = aggref->aggcollid;
+ 					fexpr->inputcollid = aggref->inputcollid;
+ 					fexpr->args = list_make1(gvar);
+ 					fexpr->location = -1;
+ 					return (Node *) fexpr;
+ 				}
+ 				else
+ 					return (Node *) gvar;
+ 			}
+ 		}
+ 		/* Match should have been found. */
+ 		Assert(l == NULL);
+ 	}
+ 
+ 	return expression_tree_mutator(node,
+ 								   replace_aggref_with_groupedvar_mutator,
+ 								   (void *) grouped_vars);
+ }
+ 
+ /*
+  * Replace each Aggref (only AGGSPLIT_SIMPLE expected) in the expression with
+  * the matching GroupedVar.
+  *
+  * Do not use if node may contain GroupedVars!
+  */
+ Node *
+ replace_aggref_with_groupedvar(Node *node, List *grouped_vars)
+ {
+ 	return expression_tree_mutator(node,
+ 								   replace_aggref_with_groupedvar_mutator,
+ 								   grouped_vars);
+ }
+ 
+ static bool
+ contains_aggref_simple_walker(Node *node, void *context)
+ {
+ 	bool	   *result_p = (bool *) context;
+ 
+ 	if (node == NULL)
+ 		return false;
+ 
+ 	/*
+ 	 * Not interested in Aggrefs wrapped in GroupedVar.
+ 	 */
+ 	if (IsA(node, GroupedVar))
+ 		return false;
+ 
+ 	if (IsA(node, Aggref))
+ 	{
+ 		Aggref	   *aggref = castNode(Aggref, node);
+ 
+ 		*result_p = aggref->aggsplit == AGGSPLIT_SIMPLE;
+ 
+ 		/*
+ 		 * Abort the tree traversal regardless *result_p. We assume that
+ 		 * targetlist should never contain both AGGSPLIT_SIMPLE and any other
+ 		 * value of aggsplit. Thus the search for AGGSPLIT_SIMPLE makes no
+ 		 * more sense even if this aggregate has a different value.
+ 		 */
+ 		return true;
+ 	}
+ 	return expression_tree_walker(node, contains_aggref_simple_walker,
+ 								  context);
+ }
+ 
+ /*
+  * Find out if the node contains at least one Aggref having aggsplit equal to
+  * AGGSPLIT_SIMPLE.
+  */
+ bool
+ contains_aggref_simple(Node *node)
+ {
+ 	bool		result = false;
+ 
+ 	contains_aggref_simple_walker(node, (void *) &result);
+ 
+ 	return result;
+ }
+ 
  /*
   * For each aggregate add GroupedVar to the grouped target.
   *
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
new file mode 100644
index 7a98753..b5baa2c
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 99,104 ****
--- 99,105 ----
  	FuncDetailCode fdresult;
  	char		aggkind = 0;
  	Oid			aggcombinefn = InvalidOid;
+ 	Oid			aggfinalfn = InvalidOid;
  	ParseCallbackState pcbstate;
  
  	/*
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 352,357 ****
--- 353,359 ----
  		classForm = (Form_pg_aggregate) GETSTRUCT(tup);
  		aggkind = classForm->aggkind;
  		aggcombinefn = classForm->aggcombinefn;
+ 		aggfinalfn = classForm->aggfinalfn;
  		catDirectArgs = classForm->aggnumdirectargs;
  		ReleaseSysCache(tup);
  
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 698,703 ****
--- 700,706 ----
  		aggref->aggvariadic = func_variadic;
  		aggref->aggkind = aggkind;
  		aggref->aggcombinefn = aggcombinefn;
+ 		aggref->aggfinalfn = aggfinalfn;
  		/* agglevelsup will be set by transformAggregateCall */
  		aggref->aggsplit = AGGSPLIT_SIMPLE; /* planner might change this */
  		aggref->location = location;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
new file mode 100644
index 97fd887..edc9ab1
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
*************** typedef struct Aggref
*** 297,302 ****
--- 297,303 ----
  	Oid			inputcollid;	/* OID of collation that function should use */
  	Oid			aggtranstype;	/* type Oid of aggregate's transition value */
  	Oid			aggcombinefn;	/* combine function (see pg_aggregate.h) */
+ 	Oid			aggfinalfn;		/* final function (see pg_aggregate.h) */
  	List	   *aggargtypes;	/* type Oids of direct and aggregated args */
  	List	   *aggdirectargs;	/* direct arguments, if an ordered-set agg */
  	List	   *args;			/* aggregated arguments and sort expressions */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
new file mode 100644
index 00e73ff..5093809
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct GroupedPathInfo
*** 1089,1094 ****
--- 1089,1098 ----
   *
   * "pathkeys" is a List of PathKey nodes (see above), describing the sort
   * ordering of the path's output rows.
+  *
+  * "uniquekeys" is a List of Bitmapset objects, each pointing at a set of
+  * expressions of "pathtarget" whose values within the path output are
+  * distinct.
   */
  typedef struct Path
  {
*************** typedef struct Path
*** 1112,1117 ****
--- 1116,1125 ----
  
  	List	   *pathkeys;		/* sort ordering of path's output */
  	/* pathkeys is a List of PathKey nodes; see above */
+ 
+ 	List	   *uniquekeys;		/* list of bitmapsets where each set contains
+ 								 * positions of unique expressions within
+ 								 * pathtarget. */
  } Path;
  
  /* Macro for extracting a path's parameterization relids; beware double eval */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
new file mode 100644
index bbcfc7f..6f3b78d
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern AggPath *create_partial_agg_sorte
*** 211,221 ****
  							   Path *subpath,
  							   bool check_pathkeys,
  							   PartialAggInfo *agg_info,
! 							   double input_rows);
  extern AggPath *create_partial_agg_hashed_path(PlannerInfo *root,
  							   Path *subpath,
  							   PartialAggInfo *agg_info,
! 							   double input_rows);
  extern GroupingSetsPath *create_groupingsets_path(PlannerInfo *root,
  						 RelOptInfo *rel,
  						 Path *subpath,
--- 211,223 ----
  							   Path *subpath,
  							   bool check_pathkeys,
  							   PartialAggInfo *agg_info,
! 							   double input_rows,
! 							   bool parallel);
  extern AggPath *create_partial_agg_hashed_path(PlannerInfo *root,
  							   Path *subpath,
  							   PartialAggInfo *agg_info,
! 							   double input_rows,
! 							   bool parallel);
  extern GroupingSetsPath *create_groupingsets_path(PlannerInfo *root,
  						 RelOptInfo *rel,
  						 Path *subpath,
*************** extern Path *reparameterize_path(Planner
*** 276,281 ****
--- 278,285 ----
  					double loop_count);
  extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path,
  							 RelOptInfo *child_rel);
+ extern void make_uniquekeys(PlannerInfo *root, Path *path);
+ extern void make_uniquekeys_for_agg_path(PlannerInfo *root, Path *path);
  
  /*
   * prototypes for relnode.c
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
new file mode 100644
index b44b8a8..61f3c69
*** a/src/include/optimizer/paths.h
--- b/src/include/optimizer/paths.h
*************** extern bool has_useful_pathkeys(PlannerI
*** 240,244 ****
  extern PathKey *make_canonical_pathkey(PlannerInfo *root,
  					   EquivalenceClass *eclass, Oid opfamily,
  					   int strategy, bool nulls_first);
! 
  #endif							/* PATHS_H */
--- 240,245 ----
  extern PathKey *make_canonical_pathkey(PlannerInfo *root,
  					   EquivalenceClass *eclass, Oid opfamily,
  					   int strategy, bool nulls_first);
! extern void add_uniquekeys_to_path(Path *path, Bitmapset *new_set);
! extern bool match_path_to_group_pathkeys(PlannerInfo *root, Path *path);
  #endif							/* PATHS_H */
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
new file mode 100644
index dfc9352..d7d9ce4
*** a/src/include/optimizer/tlist.h
--- b/src/include/optimizer/tlist.h
*************** extern void split_pathtarget_at_srfs(Pla
*** 66,71 ****
--- 66,73 ----
  /* TODO Find the best location (position and in some cases even file) for the
   * following ones. */
  extern List *replace_grouped_vars_with_aggrefs(PlannerInfo *root, List *src);
+ extern Node *replace_aggref_with_groupedvar(Node *node, List *grouped_vars);
+ extern bool contains_aggref_simple(Node *node);
  extern void add_grouped_vars_to_target(PlannerInfo *root, PathTarget *target,
  						   List *expressions);
  extern GroupedVar *get_grouping_expression(PlannerInfo *root, Expr *expr);
diff --git a/src/test/regress/expected/agg_pushdown.out b/src/test/regress/expected/agg_pushdown.out
new file mode 100644
index cac70a0..f146994
*** a/src/test/regress/expected/agg_pushdown.out
--- b/src/test/regress/expected/agg_pushdown.out
*************** SELECT p.i, avg(c1.v) FROM agg_pushdown_
*** 30,44 ****
  AS c1 ON c1.parent = p.i GROUP BY p.i;
                                       QUERY PLAN                                      
  -------------------------------------------------------------------------------------
!  Finalize HashAggregate
!    Group Key: p.i
     ->  Nested Loop
           ->  Partial HashAggregate
                 Group Key: c1.parent
                 ->  Seq Scan on agg_pushdown_child1 c1
           ->  Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
                 Index Cond: (i = c1.parent)
! (8 rows)
  
  -- Scan index on agg_pushdown_child1(parent) column and partially aggregate
  -- the result using AGG_SORTED strategy.
--- 30,43 ----
  AS c1 ON c1.parent = p.i GROUP BY p.i;
                                       QUERY PLAN                                      
  -------------------------------------------------------------------------------------
!  Result
     ->  Nested Loop
           ->  Partial HashAggregate
                 Group Key: c1.parent
                 ->  Seq Scan on agg_pushdown_child1 c1
           ->  Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
                 Index Cond: (i = c1.parent)
! (7 rows)
  
  -- Scan index on agg_pushdown_child1(parent) column and partially aggregate
  -- the result using AGG_SORTED strategy.
*************** SELECT p.i, avg(c1.v) FROM agg_pushdown_
*** 48,62 ****
  AS c1 ON c1.parent = p.i GROUP BY p.i;
                                           QUERY PLAN                                          
  ---------------------------------------------------------------------------------------------
!  Finalize GroupAggregate
!    Group Key: p.i
     ->  Nested Loop
           ->  Partial GroupAggregate
                 Group Key: c1.parent
                 ->  Index Scan using agg_pushdown_child1_parent_idx on agg_pushdown_child1 c1
           ->  Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
                 Index Cond: (i = c1.parent)
! (8 rows)
  
  SET enable_seqscan TO on;
  -- Perform nestloop join between agg_pushdown_child1 and agg_pushdown_child2
--- 47,60 ----
  AS c1 ON c1.parent = p.i GROUP BY p.i;
                                           QUERY PLAN                                          
  ---------------------------------------------------------------------------------------------
!  Result
     ->  Nested Loop
           ->  Partial GroupAggregate
                 Group Key: c1.parent
                 ->  Index Scan using agg_pushdown_child1_parent_idx on agg_pushdown_child1 c1
           ->  Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
                 Index Cond: (i = c1.parent)
! (7 rows)
  
  SET enable_seqscan TO on;
  -- Perform nestloop join between agg_pushdown_child1 and agg_pushdown_child2


  agg_pushdown_v5/10_fdw.diff
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
new file mode 100644
index 0690bd4..1e855d6
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
*************** PG_FUNCTION_INFO_V1(file_fdw_validator);
*** 117,126 ****
   */
  static void fileGetForeignRelSize(PlannerInfo *root,
  					  RelOptInfo *baserel,
! 					  Oid foreigntableid);
  static void fileGetForeignPaths(PlannerInfo *root,
  					RelOptInfo *baserel,
! 					Oid foreigntableid);
  static ForeignScan *fileGetForeignPlan(PlannerInfo *root,
  				   RelOptInfo *baserel,
  				   Oid foreigntableid,
--- 117,128 ----
   */
  static void fileGetForeignRelSize(PlannerInfo *root,
  					  RelOptInfo *baserel,
! 					  Oid foreigntableid,
! 					  bool grouped);
  static void fileGetForeignPaths(PlannerInfo *root,
  					RelOptInfo *baserel,
! 					Oid foreigntableid,
! 					bool grouped);
  static ForeignScan *fileGetForeignPlan(PlannerInfo *root,
  				   RelOptInfo *baserel,
  				   Oid foreigntableid,
*************** get_file_fdw_attribute_options(Oid relid
*** 487,497 ****
  static void
  fileGetForeignRelSize(PlannerInfo *root,
  					  RelOptInfo *baserel,
! 					  Oid foreigntableid)
  {
  	FileFdwPlanState *fdw_private;
  
  	/*
  	 * Fetch options.  We only need filename (or program) at this point, but
  	 * we might as well get everything and not need to re-fetch it later in
  	 * planning.
--- 489,507 ----
  static void
  fileGetForeignRelSize(PlannerInfo *root,
  					  RelOptInfo *baserel,
! 					  Oid foreigntableid,
! 					  bool grouped)
  {
  	FileFdwPlanState *fdw_private;
  
  	/*
+ 	 * XXX Grouping at relation level is possible but this FDW does not
+ 	 * implement it yet.
+ 	 */
+ 	if (grouped)
+ 		return;
+ 
+ 	/*
  	 * Fetch options.  We only need filename (or program) at this point, but
  	 * we might as well get everything and not need to re-fetch it later in
  	 * planning.
*************** fileGetForeignRelSize(PlannerInfo *root,
*** 518,524 ****
  static void
  fileGetForeignPaths(PlannerInfo *root,
  					RelOptInfo *baserel,
! 					Oid foreigntableid)
  {
  	FileFdwPlanState *fdw_private = (FileFdwPlanState *) baserel->fdw_private;
  	Cost		startup_cost;
--- 528,535 ----
  static void
  fileGetForeignPaths(PlannerInfo *root,
  					RelOptInfo *baserel,
! 					Oid foreigntableid,
! 					bool grouped)
  {
  	FileFdwPlanState *fdw_private = (FileFdwPlanState *) baserel->fdw_private;
  	Cost		startup_cost;
*************** fileGetForeignPaths(PlannerInfo *root,
*** 526,531 ****
--- 537,549 ----
  	List	   *columns;
  	List	   *coptions = NIL;
  
+ 	/*
+ 	 * XXX Grouping at relation level is possible but this FDW does not
+ 	 * implement it yet.
+ 	 */
+ 	if (grouped)
+ 		return;
+ 
  	/* Decide whether to selectively perform binary conversion */
  	if (check_selective_binary_conversion(baserel,
  										  foreigntableid,
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
new file mode 100644
index 0876589..22cd89d
*** a/contrib/postgres_fdw/deparse.c
--- b/contrib/postgres_fdw/deparse.c
*************** typedef struct deparse_expr_cxt
*** 103,108 ****
--- 103,109 ----
  								 * a base relation. */
  	StringInfo	buf;			/* output buffer to append to */
  	List	  **params_list;	/* exprs that will become remote Params */
+ 	List		*tlist;
  } deparse_expr_cxt;
  
  #define REL_ALIAS_PREFIX	"r"
*************** static void deparseTargetList(StringInfo
*** 133,139 ****
  				  bool qualify_col,
  				  List **retrieved_attrs);
  static void deparseExplicitTargetList(List *tlist, List **retrieved_attrs,
! 						  deparse_expr_cxt *context);
  static void deparseSubqueryTargetList(deparse_expr_cxt *context);
  static void deparseReturningList(StringInfo buf, PlannerInfo *root,
  					 Index rtindex, Relation rel,
--- 134,140 ----
  				  bool qualify_col,
  				  List **retrieved_attrs);
  static void deparseExplicitTargetList(List *tlist, List **retrieved_attrs,
! 									  deparse_expr_cxt *context);
  static void deparseSubqueryTargetList(deparse_expr_cxt *context);
  static void deparseReturningList(StringInfo buf, PlannerInfo *root,
  					 Index rtindex, Relation rel,
*************** static void printRemoteParam(int paramin
*** 163,171 ****
  static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod,
  					   deparse_expr_cxt *context);
  static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs,
! 				 deparse_expr_cxt *context);
  static void deparseLockingClause(deparse_expr_cxt *context);
! static void appendOrderByClause(List *pathkeys, deparse_expr_cxt *context);
  static void appendConditions(List *exprs, deparse_expr_cxt *context);
  static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root,
  					  RelOptInfo *joinrel, bool use_alias, List **params_list);
--- 164,173 ----
  static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod,
  					   deparse_expr_cxt *context);
  static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs,
! 				 deparse_expr_cxt *context, bool grouped);
  static void deparseLockingClause(deparse_expr_cxt *context);
! static void appendOrderByClause(deparse_expr_cxt *context, List *expressions,
! 								List *pathkeys);
  static void appendConditions(List *exprs, deparse_expr_cxt *context);
  static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root,
  					  RelOptInfo *joinrel, bool use_alias, List **params_list);
*************** static void deparseAggref(Aggref *node,
*** 177,185 ****
  static void appendGroupByClause(List *tlist, deparse_expr_cxt *context);
  static void appendAggOrderBy(List *orderList, List *targetList,
  				 deparse_expr_cxt *context);
! static void appendFunctionName(Oid funcid, deparse_expr_cxt *context);
  static Node *deparseSortGroupClause(Index ref, List *tlist,
  					   deparse_expr_cxt *context);
  
  /*
   * Helper functions
--- 179,188 ----
  static void appendGroupByClause(List *tlist, deparse_expr_cxt *context);
  static void appendAggOrderBy(List *orderList, List *targetList,
  				 deparse_expr_cxt *context);
! static void appendFunctionName(Oid funcid, StringInfo buf, char *schemaname);
  static Node *deparseSortGroupClause(Index ref, List *tlist,
  					   deparse_expr_cxt *context);
+ static void deparseSortGroupExpr(Expr *expr, deparse_expr_cxt *context);
  
  /*
   * Helper functions
*************** foreign_expr_walker(Node *node,
*** 365,370 ****
--- 368,381 ----
  				}
  			}
  			break;
+ 		case T_GroupedVar:
+ 			{
+ 				GroupedVar *gvar = (GroupedVar *) node;
+ 
+ 				Assert(!IsA(gvar->gvexpr, Aggref));
+ 				return foreign_expr_walker((Node *) gvar->gvexpr, glob_cxt,
+ 										   outer_cxt);
+ 			}
  		case T_Const:
  			{
  				Const	   *c = (Const *) node;
*************** foreign_expr_walker(Node *node,
*** 676,687 ****
  				Aggref	   *agg = (Aggref *) node;
  				ListCell   *lc;
  
! 				/* Not safe to pushdown when not in grouping context */
! 				if (!IS_UPPER_REL(glob_cxt->foreignrel))
! 					return false;
! 
! 				/* Only non-split aggregates are pushable. */
! 				if (agg->aggsplit != AGGSPLIT_SIMPLE)
  					return false;
  
  				/* As usual, it must be shippable. */
--- 687,701 ----
  				Aggref	   *agg = (Aggref *) node;
  				ListCell   *lc;
  
! 				/*
! 				 * Only non-split aggregates are pushable.
! 				 *
! 				 * XXX Like above, AGGSPLIT_SIMPLE shouldn't appear here if
! 				 * the aggregation of the whole upper relation gets abandoned.
! 				 * (Check all occurrences of AGGSPLIT_SIMPLE.)
! 				 */
! 				if (agg->aggsplit != AGGSPLIT_SIMPLE &&
! 					agg->aggsplit != AGGSPLIT_INITIAL_SERIAL)
  					return false;
  
  				/* As usual, it must be shippable. */
*************** build_tlist_to_deparse(RelOptInfo *forei
*** 923,931 ****
   */
  extern void
  deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel,
! 						List *tlist, List *remote_conds, List *pathkeys,
! 						bool is_subquery, List **retrieved_attrs,
! 						List **params_list)
  {
  	deparse_expr_cxt context;
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
--- 937,945 ----
   */
  extern void
  deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel,
! 						List *tlist, List *remote_conds, List *order_by_exprs,
! 						List *pathkeys, bool is_subquery, List **retrieved_attrs,
! 						List **params_list, bool grouped)
  {
  	deparse_expr_cxt context;
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
*************** deparseSelectStmtForRel(StringInfo buf,
*** 943,951 ****
  	context.foreignrel = rel;
  	context.scanrel = IS_UPPER_REL(rel) ? fpinfo->outerrel : rel;
  	context.params_list = params_list;
  
  	/* Construct SELECT clause */
! 	deparseSelectSql(tlist, is_subquery, retrieved_attrs, &context);
  
  	/*
  	 * For upper relations, the WHERE clause is built from the remote
--- 957,966 ----
  	context.foreignrel = rel;
  	context.scanrel = IS_UPPER_REL(rel) ? fpinfo->outerrel : rel;
  	context.params_list = params_list;
+ 	context.tlist = tlist;
  
  	/* Construct SELECT clause */
! 	deparseSelectSql(tlist, is_subquery, retrieved_attrs, &context, grouped);
  
  	/*
  	 * For upper relations, the WHERE clause is built from the remote
*************** deparseSelectStmtForRel(StringInfo buf,
*** 965,977 ****
  	/* Construct FROM and WHERE clauses */
  	deparseFromExpr(quals, &context);
  
! 	if (IS_UPPER_REL(rel))
  	{
  		/* Append GROUP BY clause */
  		appendGroupByClause(tlist, &context);
  
! 		/* Append HAVING clause */
! 		if (remote_conds)
  		{
  			appendStringInfoString(buf, " HAVING ");
  			appendConditions(remote_conds, &context);
--- 980,1008 ----
  	/* Construct FROM and WHERE clauses */
  	deparseFromExpr(quals, &context);
  
! 	if (IS_UPPER_REL(rel) || grouped)
  	{
  		/* Append GROUP BY clause */
  		appendGroupByClause(tlist, &context);
  
! 		/*
! 		 * Append HAVING clause.
! 		 *
! 		 * XXX As the "partition-wise join" patch will probably be committed
! 		 * to PG core earlier than the "aggregate push-down" patch, we don't
! 		 * add HAVING clause to query that represents remote simple relation
! 		 * or join. The point is that the HAVING clause can't be evaluated
! 		 * below the final aggregation node, and the final aggregation is
! 		 * essentially local.
! 		 *
! 		 * To surpass this limitation, the FDW would have to know whether the
! 		 * relation / join is a partition or the whole table. In case it's
! 		 * partition, HAVING is only legal if the query does not use any other
! 		 * partition. If we eventually can add the HAVING clause to scan /
! 		 * join remote queries, we also have to change the API so that WHERE /
! 		 * ON conditions are passed separate from the HAVING expression.
! 		 */
! 		if (IS_UPPER_REL(rel) && remote_conds)
  		{
  			appendStringInfoString(buf, " HAVING ");
  			appendConditions(remote_conds, &context);
*************** deparseSelectStmtForRel(StringInfo buf,
*** 979,986 ****
  	}
  
  	/* Add ORDER BY clause if we found any useful pathkeys */
! 	if (pathkeys)
! 		appendOrderByClause(pathkeys, &context);
  
  	/* Add any necessary FOR UPDATE/SHARE. */
  	deparseLockingClause(&context);
--- 1010,1020 ----
  	}
  
  	/* Add ORDER BY clause if we found any useful pathkeys */
! 	if (order_by_exprs)
! 	{
! 		Assert(pathkeys);
! 		appendOrderByClause(&context, order_by_exprs, pathkeys);
! 	}
  
  	/* Add any necessary FOR UPDATE/SHARE. */
  	deparseLockingClause(&context);
*************** deparseSelectStmtForRel(StringInfo buf,
*** 1001,1007 ****
   */
  static void
  deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs,
! 				 deparse_expr_cxt *context)
  {
  	StringInfo	buf = context->buf;
  	RelOptInfo *foreignrel = context->foreignrel;
--- 1035,1041 ----
   */
  static void
  deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs,
! 				 deparse_expr_cxt *context, bool grouped)
  {
  	StringInfo	buf = context->buf;
  	RelOptInfo *foreignrel = context->foreignrel;
*************** deparseSelectSql(List *tlist, bool is_su
*** 1030,1035 ****
--- 1064,1078 ----
  		 */
  		deparseExplicitTargetList(tlist, retrieved_attrs, context);
  	}
+ 	else if (grouped && IS_SIMPLE_REL(foreignrel) && foreignrel->gpi != NULL)
+ 	{
+ 		/*
+ 		 * An explicit targetlist is also passed if aggregation should take
+ 		 * place in the remote database.
+ 		 */
+ 		deparseExplicitTargetList(fpinfo->grouped_tlist, retrieved_attrs,
+ 								  context);
+ 	}
  	else
  	{
  		/*
*************** deparseRangeTblRef(StringInfo buf, Plann
*** 1504,1511 ****
  		/* Deparse the subquery representing the relation. */
  		appendStringInfoChar(buf, '(');
  		deparseSelectStmtForRel(buf, root, foreignrel, NIL,
! 								fpinfo->remote_conds, NIL, true,
! 								&retrieved_attrs, params_list);
  		appendStringInfoChar(buf, ')');
  
  		/* Append the relation alias. */
--- 1547,1554 ----
  		/* Deparse the subquery representing the relation. */
  		appendStringInfoChar(buf, '(');
  		deparseSelectStmtForRel(buf, root, foreignrel, NIL,
! 								fpinfo->remote_conds, NIL, NIL, true,
! 								&retrieved_attrs, params_list, false);
  		appendStringInfoChar(buf, ')');
  
  		/* Append the relation alias. */
*************** deparseExpr(Expr *node, deparse_expr_cxt
*** 2126,2131 ****
--- 2169,2177 ----
  		case T_Var:
  			deparseVar((Var *) node, context);
  			break;
+ 		case T_GroupedVar:
+ 			deparseExpr((Expr *) ((GroupedVar *) node)->gvexpr, context);
+ 			break;
  		case T_Const:
  			deparseConst((Const *) node, context, 0);
  			break;
*************** deparseFuncExpr(FuncExpr *node, deparse_
*** 2469,2475 ****
  	/*
  	 * Normal function: display as proname(args).
  	 */
! 	appendFunctionName(node->funcid, context);
  	appendStringInfoChar(buf, '(');
  
  	/* ... and all the arguments */
--- 2515,2521 ----
  	/*
  	 * Normal function: display as proname(args).
  	 */
! 	appendFunctionName(node->funcid, context->buf, NULL);
  	appendStringInfoChar(buf, '(');
  
  	/* ... and all the arguments */
*************** deparseAggref(Aggref *node, deparse_expr
*** 2747,2761 ****
  {
  	StringInfo	buf = context->buf;
  	bool		use_variadic;
  
  	/* Only basic, non-split aggregation accepted. */
! 	Assert(node->aggsplit == AGGSPLIT_SIMPLE);
  
  	/* Check if need to print VARIADIC (cf. ruleutils.c) */
  	use_variadic = node->aggvariadic;
  
  	/* Find aggregate name from aggfnoid which is a pg_proc entry */
! 	appendFunctionName(node->aggfnoid, context);
  	appendStringInfoChar(buf, '(');
  
  	/* Add DISTINCT */
--- 2793,2876 ----
  {
  	StringInfo	buf = context->buf;
  	bool		use_variadic;
+ 	ListCell   *lc;
+ 	bool		use_core_aggregate = true;
+ 	char	   *schemaname = NULL;
  
  	/* Only basic, non-split aggregation accepted. */
! 	Assert(node->aggsplit == AGGSPLIT_SIMPLE ||
! 		   node->aggsplit == AGGSPLIT_INITIAL_SERIAL);
  
  	/* Check if need to print VARIADIC (cf. ruleutils.c) */
  	use_variadic = node->aggvariadic;
  
+ 	if (context->root->grouped_var_list != NIL)
+ 	{
+ 		/*
+ 		 * The core aggregates (i.e. those in postgres catalog) do or do not
+ 		 * perform finalization on both remote and local side. But sometimes
+ 		 * we need the same aggregate to run w/o finalization on the remote
+ 		 * node and with the finalization locally.
+ 		 *
+ 		 * Since the current Aggref is partial, it essentially returns the
+ 		 * transient type. We need to find the original aggregate.
+ 		 */
+ 		foreach(lc, context->root->grouped_var_list)
+ 		{
+ 			GroupedVarInfo *gvi;
+ 			Aggref	   *orig;
+ 
+ 			gvi = lfirst_node(GroupedVarInfo, lc);
+ 			/* GroupedVar currently represents only aggregates. */
+ 			orig = castNode(Aggref, gvi->gvexpr);
+ 
+ 			if (orig->aggfnoid == node->aggfnoid)
+ 			{
+ 				/*
+ 				 * The core aggregate can only be useful if it does not
+ 				 * perform finalization.
+ 				 */
+ 				if (!OidIsValid(orig->aggfinalfn))
+ 				{
+ 					use_core_aggregate =  true;
+ 
+ 					/*
+ 					 * If there's no finalization, the aggregate should return
+ 					 * value of the internal type.
+ 					 */
+ 					Assert(orig->aggtype == orig->aggtranstype);
+ 				}
+ 				else
+ 					use_core_aggregate =  false;
+ 
+ 				/*
+ 				 * If there are multiple aggregates of the same aggfnoid in
+ 				 * the query, they should all have the same values of the
+ 				 * fields we're going to check. So just use the first one we
+ 				 * find.
+ 				 */
+ 				break;
+ 			}
+ 		}
+ 
+ 		/* Must have found the original aggregate in the list. */
+ 		Assert(lc != NULL);
+ 	}
+ 
+ 	/*
+ 	 * TODO
+ 	 *
+ 	 * 1. Make the schema name configurable as FDW option.
+ 	 *
+ 	 * 2. Implement the partial aggregates. These don't fit into postgres_fdw
+ 	 * because there's no reason to install postgres_fdw on the remote node.
+ 	 * Separate extension (contrib module?) is probably needed.
+ 	 */
+ 	if (!use_core_aggregate)
+ 		schemaname = "partial";
+ 
  	/* Find aggregate name from aggfnoid which is a pg_proc entry */
! 	appendFunctionName(node->aggfnoid, context->buf, schemaname);
  	appendStringInfoChar(buf, '(');
  
  	/* Add DISTINCT */
*************** deparseAggref(Aggref *node, deparse_expr
*** 2829,2834 ****
--- 2944,2965 ----
  	}
  
  	appendStringInfoChar(buf, ')');
+ 
+ 	/*
+ 	 * If special (non-core) aggregate is needed on the remote node, it can
+ 	 * return data type for which postgres does not have textual format. cast
+ 	 * to bytea seems to be an universal solution in such a case.
+ 	 *
+ 	 * TODO
+ 	 *
+ 	 * Consider checking if aggserialfn exists --- aggtranstype is used now
+ 	 * because the info is easier to access. Also consider a requirement that
+ 	 * the "remote aggregates" must return bytea, unless they return a
+ 	 * primitive type. Thus we wouldn't have to deal with the cast here at
+ 	 * all.
+ 	 */
+ 	if (schemaname != NULL && node->aggtranstype == INTERNALOID)
+ 		appendStringInfoString(buf, "::bytea");
  }
  
  /*
*************** appendGroupByClause(List *tlist, deparse
*** 2952,2997 ****
  	 */
  	Assert(!query->groupingSets);
  
! 	foreach(lc, query->groupClause)
  	{
! 		SortGroupClause *grp = (SortGroupClause *) lfirst(lc);
  
! 		if (!first)
! 			appendStringInfoString(buf, ", ");
! 		first = false;
  
! 		deparseSortGroupClause(grp->tleSortGroupRef, tlist, context);
  	}
  }
  
  /*
!  * Deparse ORDER BY clause according to the given pathkeys for given base
!  * relation. From given pathkeys expressions belonging entirely to the given
!  * base relation are obtained and deparsed.
   */
  static void
! appendOrderByClause(List *pathkeys, deparse_expr_cxt *context)
  {
! 	ListCell   *lcell;
  	int			nestlevel;
  	char	   *delim = " ";
- 	RelOptInfo *baserel = context->scanrel;
  	StringInfo	buf = context->buf;
  
  	/* Make sure any constants in the exprs are printed portably */
  	nestlevel = set_transmission_modes();
  
  	appendStringInfoString(buf, " ORDER BY");
! 	foreach(lcell, pathkeys)
  	{
! 		PathKey    *pathkey = lfirst(lcell);
! 		Expr	   *em_expr;
! 
! 		em_expr = find_em_expr_for_rel(pathkey->pk_eclass, baserel);
! 		Assert(em_expr != NULL);
  
  		appendStringInfoString(buf, delim);
! 		deparseExpr(em_expr, context);
  		if (pathkey->pk_strategy == BTLessStrategyNumber)
  			appendStringInfoString(buf, " ASC");
  		else
--- 3083,3142 ----
  	 */
  	Assert(!query->groupingSets);
  
! 	/*
! 	 * Due to aggregation push-down (which includes join aggregation on the
! 	 * remote server) we should expect multiple TLEs containing different
! 	 * expressions but having the same sortgroupref. The typical case is that
! 	 * an equality join clause receives the input value (var) from each side
! 	 * of a join, and each of these vars is needed above the join for a
! 	 * different reason. That's why we don't use query->groupClause here.
! 	 *
! 	 * TODO Consider using get_grouping_expressions().
! 	 */
! 	foreach(lc, tlist)
  	{
! 		TargetEntry *te = lfirst_node(TargetEntry, lc);
! 		Index	sgref = te->ressortgroupref;
  
! 		/* Check whether this expression is part of GROUP BY clause */
! 		if (sgref &&
! 			get_sortgroupref_clause_noerr(sgref, query->groupClause))
! 		{
! 			if (!first)
! 				appendStringInfoString(buf, ", ");
! 			first = false;
  
! 			deparseSortGroupExpr(te->expr, context);
! 		}
  	}
  }
  
  /*
!  * Deparse ORDER BY clause.
!  *
!  * "expressions" and "strategies" are lists of the expressions and the
!  * corresponding pathkeys respectively.
   */
  static void
! appendOrderByClause(deparse_expr_cxt *context, List *expressions, List *pathkeys)
  {
! 	ListCell   *l1, *l2;
  	int			nestlevel;
  	char	   *delim = " ";
  	StringInfo	buf = context->buf;
  
  	/* Make sure any constants in the exprs are printed portably */
  	nestlevel = set_transmission_modes();
  
  	appendStringInfoString(buf, " ORDER BY");
! 	Assert(list_length(expressions) == list_length(pathkeys));
! 	forboth(l1, expressions,  l2, pathkeys)
  	{
! 		Expr	   *expr = (Expr *) lfirst(l1);
! 		PathKey		*pathkey = lfirst_node(PathKey, l2);
  
  		appendStringInfoString(buf, delim);
! 		deparseExpr(expr, context);
  		if (pathkey->pk_strategy == BTLessStrategyNumber)
  			appendStringInfoString(buf, " ASC");
  		else
*************** appendOrderByClause(List *pathkeys, depa
*** 3012,3020 ****
   *		Deparses function name from given function oid.
   */
  static void
! appendFunctionName(Oid funcid, deparse_expr_cxt *context)
  {
- 	StringInfo	buf = context->buf;
  	HeapTuple	proctup;
  	Form_pg_proc procform;
  	const char *proname;
--- 3157,3164 ----
   *		Deparses function name from given function oid.
   */
  static void
! appendFunctionName(Oid funcid, StringInfo buf, char *schemaname)
  {
  	HeapTuple	proctup;
  	Form_pg_proc procform;
  	const char *proname;
*************** appendFunctionName(Oid funcid, deparse_e
*** 3025,3035 ****
  	procform = (Form_pg_proc) GETSTRUCT(proctup);
  
  	/* Print schema name only if it's not pg_catalog */
! 	if (procform->pronamespace != PG_CATALOG_NAMESPACE)
  	{
! 		const char *schemaname;
! 
! 		schemaname = get_namespace_name(procform->pronamespace);
  		appendStringInfo(buf, "%s.", quote_identifier(schemaname));
  	}
  
--- 3169,3179 ----
  	procform = (Form_pg_proc) GETSTRUCT(proctup);
  
  	/* Print schema name only if it's not pg_catalog */
! 	if (procform->pronamespace != PG_CATALOG_NAMESPACE ||
! 		schemaname != NULL)
  	{
! 		if (schemaname == NULL)
! 			schemaname = get_namespace_name(procform->pronamespace);
  		appendStringInfo(buf, "%s.", quote_identifier(schemaname));
  	}
  
*************** appendFunctionName(Oid funcid, deparse_e
*** 3049,3061 ****
  static Node *
  deparseSortGroupClause(Index ref, List *tlist, deparse_expr_cxt *context)
  {
- 	StringInfo	buf = context->buf;
  	TargetEntry *tle;
  	Expr	   *expr;
  
  	tle = get_sortgroupref_tle(ref, tlist);
  	expr = tle->expr;
  
  	if (expr && IsA(expr, Const))
  	{
  		/*
--- 3193,3214 ----
  static Node *
  deparseSortGroupClause(Index ref, List *tlist, deparse_expr_cxt *context)
  {
  	TargetEntry *tle;
  	Expr	   *expr;
  
  	tle = get_sortgroupref_tle(ref, tlist);
  	expr = tle->expr;
  
+ 	deparseSortGroupExpr(expr, context);
+ 
+ 	return (Node *) expr;
+ }
+ 
+ static void
+ deparseSortGroupExpr(Expr *expr, deparse_expr_cxt *context)
+ {
+ 	StringInfo	buf = context->buf;
+ 
  	if (expr && IsA(expr, Const))
  	{
  		/*
*************** deparseSortGroupClause(Index ref, List *
*** 3074,3081 ****
  		deparseExpr(expr, context);
  		appendStringInfoChar(buf, ')');
  	}
- 
- 	return (Node *) expr;
  }
  
  
--- 3227,3232 ----
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
new file mode 100644
index 683d641..5549b07
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
*************** SELECT t1.a,t1.b FROM fprt1 t1, LATERAL
*** 7525,7527 ****
--- 7525,7650 ----
  (4 rows)
  
  RESET enable_partition_wise_join;
+ -- ===================================================================
+ -- test aggregation of base relation or join
+ -- ===================================================================
+ CREATE TABLE customers(id int primary key, name varchar);
+ INSERT INTO customers VALUES (0, 'me'), (1, 'you'), (2, 'he'), (3, 'she');
+ CREATE TABLE products(id int, price float4) PARTITION BY RANGE (id);
+ CREATE TABLE products_0 (LIKE products);
+ INSERT INTO products_0(id, price) SELECT g.i, trunc(100 * random()) /
+ 10 + 1 FROM generate_series(1, 1000) AS g(i);
+ CREATE TABLE products_1 (LIKE products);
+ INSERT INTO products_1(id, price) SELECT g.i, trunc(100 * random()) /
+ 10 + 1 FROM generate_series(1001, 2000) AS g(i);
+ CREATE TABLE orders(customer_id int, product_id int) PARTITION BY RANGE
+ (product_id);
+ CREATE TABLE orders_0 (LIKE orders);
+ INSERT INTO orders_0(customer_id, product_id) SELECT trunc(2 * random()),
+ trunc(1000 * random()) FROM generate_series(1, 5000);
+ CREATE TABLE orders_1 (LIKE orders);
+ INSERT INTO orders_1(customer_id, product_id)
+ SELECT trunc(2 * random()) + 2, trunc(1000 * random()) + 1001 FROM
+ generate_series(1, 5000);
+ CREATE FOREIGN TABLE f_orders_0 PARTITION OF orders FOR VALUES FROM (1) TO
+ (1001) SERVER loopback OPTIONS (table_name 'orders_0');
+ CREATE FOREIGN TABLE f_orders_1 PARTITION OF orders FOR VALUES FROM (1001) TO
+ (2001) SERVER loopback OPTIONS (table_name 'orders_1');
+ CREATE FOREIGN TABLE f_products_0 PARTITION OF products FOR VALUES FROM (1) TO
+ (1001) SERVER loopback OPTIONS (table_name 'products_0');
+ CREATE FOREIGN TABLE f_products_1 PARTITION OF products FOR VALUES FROM (1001)
+ TO (2001) SERVER loopback OPTIONS (table_name 'products_1');
+ ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'true');
+ ALTER SERVER loopback2 OPTIONS (ADD use_remote_estimate 'true');
+ ANALYZE;
+ SET enable_agg_pushdown TO on;
+ SET enable_partition_wise_join TO on;
+ -- Perform the aggregation on partitions of "orders" , join the result to the
+ -- "customers" local table and finalize the aggregation.
+ EXPLAIN (VERBOSE on, COSTS off)
+ SELECT          c.name, count(*)
+ FROM            customers AS c, orders AS o
+ WHERE           c.id = o.customer_id
+ GROUP BY        c.id;
+                                                                  QUERY PLAN                                                                 
+ --------------------------------------------------------------------------------------------------------------------------------------------
+  Finalize GroupAggregate
+    Output: c.name, count(*), c.id
+    Group Key: c.id
+    ->  Nested Loop
+          Output: c.id, c.name, (PARTIAL count(*))
+          Inner Unique: true
+          ->  Merge Append
+                Sort Key: o.customer_id
+                ->  Foreign Scan
+                      Output: o.customer_id, (PARTIAL count(*))
+                      Remote SQL: SELECT customer_id, count(*) FROM public.orders_0 GROUP BY customer_id ORDER BY customer_id ASC NULLS LAST
+                ->  Foreign Scan
+                      Output: o_1.customer_id, (PARTIAL count(*))
+                      Remote SQL: SELECT customer_id, count(*) FROM public.orders_1 GROUP BY customer_id ORDER BY customer_id ASC NULLS LAST
+          ->  Index Scan using customers_pkey on public.customers c
+                Output: c.id, c.name
+                Index Cond: (c.id = o.customer_id)
+ (17 rows)
+ 
+ -- Perform partition-wise join of "orders" and "products", aggregate the
+ -- result at partition level, append the outputs of partitions and join it to
+ -- the "customers" local table.
+ EXPLAIN (VERBOSE on, COSTS off)
+ SELECT          p.id, c.name, sum(p.price)
+ FROM            customers AS c, orders AS o, products AS p
+ WHERE           c.id = o.customer_id AND p.id = o.product_id
+ GROUP BY        p.id, c.id
+ ORDER BY	3;
+                                                                                                 QUERY PLAN                                                                                                 
+ -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+  Sort
+    Output: p.id, c.name, (PARTIAL sum(p.price)), c.id
+    Sort Key: (PARTIAL sum(p.price))
+    ->  Result
+          Output: p.id, c.name, (PARTIAL sum(p.price)), c.id
+          ->  Hash Join
+                Output: c.id, p.id, c.name, (PARTIAL sum(p.price))
+                Inner Unique: true
+                Hash Cond: (o.customer_id = c.id)
+                ->  Append
+                      ->  Foreign Scan
+                            Output: o.customer_id, p.id, (PARTIAL sum(p.price))
+                            Relations: (public.f_orders_0 o) INNER JOIN (public.f_products_0 p)
+                            Remote SQL: SELECT r5.customer_id, r8.id, sum(r8.price) FROM (public.orders_0 r5 INNER JOIN public.products_0 r8 ON (((r5.product_id = r8.id)))) GROUP BY r5.customer_id, r8.id
+                      ->  Foreign Scan
+                            Output: o_1.customer_id, p_1.id, (PARTIAL sum(p_1.price))
+                            Relations: (public.f_orders_1 o) INNER JOIN (public.f_products_1 p)
+                            Remote SQL: SELECT r6.customer_id, r9.id, sum(r9.price) FROM (public.orders_1 r6 INNER JOIN public.products_1 r9 ON (((r6.product_id = r9.id)))) GROUP BY r6.customer_id, r9.id
+                ->  Hash
+                      Output: c.id, c.name
+                      ->  Seq Scan on public.customers c
+                            Output: c.id, c.name
+ (22 rows)
+ 
+ -- The final aggregation is not necessary if the aggregated output of
+ -- partitions contains distinct set of grouping keys. Moreover, skip sort of
+ -- the result if the output of per-partition queries has the desired ordering
+ -- and so the sets can be merge-appended.
+ EXPLAIN (VERBOSE on, COSTS off)
+ SELECT          p.id, sum(p.price)
+ FROM            orders AS o, products AS p
+ WHERE           p.id = o.product_id
+ GROUP BY        p.id
+ ORDER BY	2 DESC;
+                                                                                               QUERY PLAN                                                                                               
+ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+  Result
+    Output: p.id, (PARTIAL sum(p.price))
+    ->  Merge Append
+          Sort Key: (PARTIAL sum(p.price)) DESC
+          ->  Foreign Scan
+                Output: p.id, (PARTIAL sum(p.price))
+                Relations: (public.f_orders_0 o) INNER JOIN (public.f_products_0 p)
+                Remote SQL: SELECT r7.id, sum(r7.price) FROM (public.orders_0 r4 INNER JOIN public.products_0 r7 ON (((r4.product_id = r7.id)))) GROUP BY r7.id ORDER BY sum(r7.price) DESC NULLS FIRST
+          ->  Foreign Scan
+                Output: p_1.id, (PARTIAL sum(p_1.price))
+                Relations: (public.f_orders_1 o) INNER JOIN (public.f_products_1 p)
+                Remote SQL: SELECT r8.id, sum(r8.price) FROM (public.orders_1 r5 INNER JOIN public.products_1 r8 ON (((r5.product_id = r8.id)))) GROUP BY r8.id ORDER BY sum(r8.price) DESC NULLS FIRST
+ (12 rows)
+ 
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
new file mode 100644
index 459c6b9..6085879
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
*************** PG_FUNCTION_INFO_V1(postgres_fdw_handler
*** 273,282 ****
   */
  static void postgresGetForeignRelSize(PlannerInfo *root,
  						  RelOptInfo *baserel,
! 						  Oid foreigntableid);
  static void postgresGetForeignPaths(PlannerInfo *root,
  						RelOptInfo *baserel,
! 						Oid foreigntableid);
  static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
  					   RelOptInfo *foreignrel,
  					   Oid foreigntableid,
--- 273,283 ----
   */
  static void postgresGetForeignRelSize(PlannerInfo *root,
  						  RelOptInfo *baserel,
! 						  Oid foreigntableid, bool grouped);
  static void postgresGetForeignPaths(PlannerInfo *root,
  						RelOptInfo *baserel,
! 						Oid foreigntableid,
! 						bool grouped);
  static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
  					   RelOptInfo *foreignrel,
  					   Oid foreigntableid,
*************** static void postgresGetForeignJoinPaths(
*** 341,347 ****
  							RelOptInfo *outerrel,
  							RelOptInfo *innerrel,
  							JoinType jointype,
! 							JoinPathExtraData *extra);
  static bool postgresRecheckForeignScan(ForeignScanState *node,
  						   TupleTableSlot *slot);
  static void postgresGetForeignUpperPaths(PlannerInfo *root,
--- 342,349 ----
  							RelOptInfo *outerrel,
  							RelOptInfo *innerrel,
  							JoinType jointype,
! 							JoinPathExtraData *extra,
! 							bool grouped);
  static bool postgresRecheckForeignScan(ForeignScanState *node,
  						   TupleTableSlot *slot);
  static void postgresGetForeignUpperPaths(PlannerInfo *root,
*************** static void postgresGetForeignUpperPaths
*** 352,363 ****
  /*
   * Helper functions
   */
  static void estimate_path_cost_size(PlannerInfo *root,
  						RelOptInfo *baserel,
  						List *join_conds,
  						List *pathkeys,
! 						double *p_rows, int *p_width,
! 						Cost *p_startup_cost, Cost *p_total_cost);
  static void get_remote_estimate(const char *sql,
  					PGconn *conn,
  					double *rows,
--- 354,368 ----
  /*
   * Helper functions
   */
+ static void init_pgfdw_baserel_info(PlannerInfo *root, RelOptInfo *rel,
+ 						Oid foreigntableid, bool grouped);
  static void estimate_path_cost_size(PlannerInfo *root,
  						RelOptInfo *baserel,
  						List *join_conds,
+ 						List *order_by_exprs,
  						List *pathkeys,
! 						EstimateInfo * estimates,
! 						bool grouped);
  static void get_remote_estimate(const char *sql,
  					PGconn *conn,
  					double *rows,
*************** static HeapTuple make_tuple_from_result_
*** 404,416 ****
  static void conversion_error_callback(void *arg);
  static bool foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel,
  				JoinType jointype, RelOptInfo *outerrel, RelOptInfo *innerrel,
! 				JoinPathExtraData *extra);
  static bool foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel);
  static List *get_useful_pathkeys_for_relation(PlannerInfo *root,
! 								 RelOptInfo *rel);
  static List *get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel);
  static void add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
! 								Path *epq_path);
  static void add_foreign_grouping_paths(PlannerInfo *root,
  						   RelOptInfo *input_rel,
  						   RelOptInfo *grouped_rel);
--- 409,426 ----
  static void conversion_error_callback(void *arg);
  static bool foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel,
  				JoinType jointype, RelOptInfo *outerrel, RelOptInfo *innerrel,
! 				JoinPathExtraData *extra, bool grouped);
  static bool foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel);
+ static List *create_remote_agg_tlist(PlannerInfo *root,
+ 						RelOptInfo *grouped_rel,
+ 						PathTarget *grouping_target);
  static List *get_useful_pathkeys_for_relation(PlannerInfo *root,
! 											  RelOptInfo *rel, bool grouped);
  static List *get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel);
  static void add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
! 								Path *epq_path, bool grouped);
! static List *get_order_by_expressions(Relids relids, List *pathkeys,
! 									  List *fdw_scan_tlist);
  static void add_foreign_grouping_paths(PlannerInfo *root,
  						   RelOptInfo *input_rel,
  						   RelOptInfo *grouped_rel);
*************** postgres_fdw_handler(PG_FUNCTION_ARGS)
*** 485,567 ****
  static void
  postgresGetForeignRelSize(PlannerInfo *root,
  						  RelOptInfo *baserel,
! 						  Oid foreigntableid)
  {
  	PgFdwRelationInfo *fpinfo;
! 	ListCell   *lc;
! 	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
! 	const char *namespace;
! 	const char *relname;
! 	const char *refname;
  
  	/*
  	 * We use PgFdwRelationInfo to pass various information to subsequent
  	 * functions.
  	 */
! 	fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
! 	baserel->fdw_private = (void *) fpinfo;
! 
! 	/* Base foreign tables need to be pushed down always. */
! 	fpinfo->pushdown_safe = true;
! 
! 	/* Look up foreign-table catalog info. */
! 	fpinfo->table = GetForeignTable(foreigntableid);
! 	fpinfo->server = GetForeignServer(fpinfo->table->serverid);
! 
! 	/*
! 	 * Extract user-settable option values.  Note that per-table setting of
! 	 * use_remote_estimate overrides per-server setting.
! 	 */
! 	fpinfo->use_remote_estimate = false;
! 	fpinfo->fdw_startup_cost = DEFAULT_FDW_STARTUP_COST;
! 	fpinfo->fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST;
! 	fpinfo->shippable_extensions = NIL;
! 	fpinfo->fetch_size = 100;
! 
! 	apply_server_options(fpinfo);
! 	apply_table_options(fpinfo);
! 
! 	/*
! 	 * If the table or the server is configured to use remote estimates,
! 	 * identify which user to do remote access as during planning.  This
! 	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
! 	 * permissions, the query would have failed at runtime anyway.
! 	 */
! 	if (fpinfo->use_remote_estimate)
  	{
! 		Oid			userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
! 
! 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
  	}
- 	else
- 		fpinfo->user = NULL;
  
! 	/*
! 	 * Identify which baserestrictinfo clauses can be sent to the remote
! 	 * server and which can't.
! 	 */
! 	classifyConditions(root, baserel, baserel->baserestrictinfo,
! 					   &fpinfo->remote_conds, &fpinfo->local_conds);
  
! 	/*
! 	 * Identify which attributes will need to be retrieved from the remote
! 	 * server.  These include all attrs needed for joins or final output, plus
! 	 * all attrs used in the local_conds.  (Note: if we end up using a
! 	 * parameterized scan, it's possible that some of the join clauses will be
! 	 * sent to the remote and thus we wouldn't really need to retrieve the
! 	 * columns used in them.  Doesn't seem worth detecting that case though.)
! 	 */
! 	fpinfo->attrs_used = NULL;
! 	pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid,
! 				   &fpinfo->attrs_used);
! 	foreach(lc, fpinfo->local_conds)
  	{
! 		RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
  
! 		pull_varattnos((Node *) rinfo->clause, baserel->relid,
! 					   &fpinfo->attrs_used);
  	}
  
  	/*
  	 * Compute the selectivity and cost of the local_conds, so we don't have
  	 * to do it over again for each path.  The best we can do for these
--- 495,545 ----
  static void
  postgresGetForeignRelSize(PlannerInfo *root,
  						  RelOptInfo *baserel,
! 						  Oid foreigntableid,
! 						  bool grouped)
  {
  	PgFdwRelationInfo *fpinfo;
! 	EstimateInfo *estimates;
  
  	/*
  	 * We use PgFdwRelationInfo to pass various information to subsequent
  	 * functions.
+ 	 *
+ 	 * This function can be called twice: first time for plain scan, second
+ 	 * time for the grouped one. fpinfo should only be initialized once.
  	 */
! 	if (baserel->fdw_private == NULL)
  	{
! 		fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
! 		baserel->fdw_private = fpinfo;
! 		init_pgfdw_baserel_info(root, baserel, foreigntableid, grouped);
  	}
  
! 	fpinfo = (PgFdwRelationInfo *) baserel->fdw_private;
  
! 	if (grouped)
  	{
! 		/*
! 		 * baserestrictinfo must be evaluated below the aggregation, so all
! 		 * its expressions must be remote.
! 		 */
! 		if (fpinfo->local_conds != NIL)
! 			return;
  
! 		/*
! 		 * Given that there are no local_conds and remote_conds are evaluated
! 		 * on the foreign server, baserel->gpi->target is the only thing left
! 		 * to check.
! 		 *
! 		 * XXX Do we need to verify that the target contains no
! 		 * PlaceHolderVars?
! 		 */
! 		if (!foreign_grouping_ok(root, baserel))
! 			return;
  	}
  
+ 	estimates = !grouped ? &fpinfo->est_plain : &fpinfo->est_grouped;
+ 
  	/*
  	 * Compute the selectivity and cost of the local_conds, so we don't have
  	 * to do it over again for each path.  The best we can do for these
*************** postgresGetForeignRelSize(PlannerInfo *r
*** 580,587 ****
  	 * when they are set to some sensible costs during one (usually the first)
  	 * of the calls to estimate_path_cost_size().
  	 */
! 	fpinfo->rel_startup_cost = -1;
! 	fpinfo->rel_total_cost = -1;
  
  	/*
  	 * If the table or the server is configured to use remote estimates,
--- 558,565 ----
  	 * when they are set to some sensible costs during one (usually the first)
  	 * of the calls to estimate_path_cost_size().
  	 */
! 	estimates->rel_startup_cost = -1;
! 	estimates->rel_total_cost = -1;
  
  	/*
  	 * If the table or the server is configured to use remote estimates,
*************** postgresGetForeignRelSize(PlannerInfo *r
*** 597,609 ****
  		 * values in fpinfo so we don't need to do it again to generate the
  		 * basic foreign path.
  		 */
! 		estimate_path_cost_size(root, baserel, NIL, NIL,
! 								&fpinfo->rows, &fpinfo->width,
! 								&fpinfo->startup_cost, &fpinfo->total_cost);
  
  		/* Report estimated baserel size to planner. */
! 		baserel->rows = fpinfo->rows;
! 		baserel->reltarget->width = fpinfo->width;
  	}
  	else
  	{
--- 575,591 ----
  		 * values in fpinfo so we don't need to do it again to generate the
  		 * basic foreign path.
  		 */
! 		estimate_path_cost_size(root, baserel, NIL, NIL, NIL, estimates,
! 								grouped);
  
  		/* Report estimated baserel size to planner. */
! 
! 		/*
! 		 * TODO If grouped, make sure baserel->gpi exists and store the values
! 		 * there.
! 		 */
! 		baserel->rows = estimates->rows;
! 		baserel->reltarget->width = estimates->width;
  	}
  	else
  	{
*************** postgresGetForeignRelSize(PlannerInfo *r
*** 628,661 ****
  		set_baserel_size_estimates(root, baserel);
  
  		/* Fill in basically-bogus cost estimates for use later. */
! 		estimate_path_cost_size(root, baserel, NIL, NIL,
! 								&fpinfo->rows, &fpinfo->width,
! 								&fpinfo->startup_cost, &fpinfo->total_cost);
  	}
- 
- 	/*
- 	 * Set the name of relation in fpinfo, while we are constructing it here.
- 	 * It will be used to build the string describing the join relation in
- 	 * EXPLAIN output. We can't know whether VERBOSE option is specified or
- 	 * not, so always schema-qualify the foreign table name.
- 	 */
- 	fpinfo->relation_name = makeStringInfo();
- 	namespace = get_namespace_name(get_rel_namespace(foreigntableid));
- 	relname = get_rel_name(foreigntableid);
- 	refname = rte->eref->aliasname;
- 	appendStringInfo(fpinfo->relation_name, "%s.%s",
- 					 quote_identifier(namespace),
- 					 quote_identifier(relname));
- 	if (*refname && strcmp(refname, relname) != 0)
- 		appendStringInfo(fpinfo->relation_name, " %s",
- 						 quote_identifier(rte->eref->aliasname));
- 
- 	/* No outer and inner relations. */
- 	fpinfo->make_outerrel_subquery = false;
- 	fpinfo->make_innerrel_subquery = false;
- 	fpinfo->lower_subquery_rels = NULL;
- 	/* Set the relation index. */
- 	fpinfo->relation_index = baserel->relid;
  }
  
  /*
--- 610,618 ----
  		set_baserel_size_estimates(root, baserel);
  
  		/* Fill in basically-bogus cost estimates for use later. */
! 		estimate_path_cost_size(root, baserel, NIL, NIL, NIL, estimates,
! 								grouped);
  	}
  }
  
  /*
*************** get_useful_ecs_for_relation(PlannerInfo
*** 775,781 ****
   * to figure out which pathkeys to consider.
   */
  static List *
! get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel)
  {
  	List	   *useful_pathkeys_list = NIL;
  	List	   *useful_eclass_list;
--- 732,739 ----
   * to figure out which pathkeys to consider.
   */
  static List *
! get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel,
! 								 bool grouped)
  {
  	List	   *useful_pathkeys_list = NIL;
  	List	   *useful_eclass_list;
*************** get_useful_pathkeys_for_relation(Planner
*** 790,801 ****
  	if (root->query_pathkeys)
  	{
  		bool		query_pathkeys_ok = true;
  
  		foreach(lc, root->query_pathkeys)
  		{
! 			PathKey    *pathkey = (PathKey *) lfirst(lc);
! 			EquivalenceClass *pathkey_ec = pathkey->pk_eclass;
! 			Expr	   *em_expr;
  
  			/*
  			 * The planner and executor don't have any clever strategy for
--- 748,762 ----
  	if (root->query_pathkeys)
  	{
  		bool		query_pathkeys_ok = true;
+ 		bool		sort_pathkeys_ok = true;
+ 		PathKey    *pathkey;
+ 		EquivalenceClass *pathkey_ec;
+ 		Expr	   *em_expr;
  
  		foreach(lc, root->query_pathkeys)
  		{
! 			pathkey = lfirst_node(PathKey, lc);
! 			pathkey_ec = pathkey->pk_eclass;
  
  			/*
  			 * The planner and executor don't have any clever strategy for
*************** get_useful_pathkeys_for_relation(Planner
*** 815,823 ****
  				break;
  			}
  		}
- 
  		if (query_pathkeys_ok)
  			useful_pathkeys_list = list_make1(list_copy(root->query_pathkeys));
  	}
  
  	/*
--- 776,807 ----
  				break;
  			}
  		}
  		if (query_pathkeys_ok)
  			useful_pathkeys_list = list_make1(list_copy(root->query_pathkeys));
+ 
+ 		/*
+ 		 * If the path is grouped, then it can be sorted by aggregates. These
+ 		 * are only present in sort_pathkeys.
+ 		 */
+ 		if (grouped && root->sort_pathkeys != root->query_pathkeys)
+ 		{
+ 			foreach(lc, root->sort_pathkeys)
+ 			{
+ 				pathkey = lfirst_node(PathKey, lc);
+ 				pathkey_ec = pathkey->pk_eclass;
+ 
+ 				if (pathkey_ec->ec_has_volatile ||
+ 					!(em_expr = find_em_expr_for_rel(pathkey_ec, rel)) ||
+ 					!is_foreign_expr(root, rel, em_expr))
+ 				{
+ 					sort_pathkeys_ok = false;
+ 					break;
+ 				}
+ 			}
+ 			if (sort_pathkeys_ok)
+ 				useful_pathkeys_list = lappend(useful_pathkeys_list,
+ 											   list_copy(root->sort_pathkeys));
+ 		}
  	}
  
  	/*
*************** get_useful_pathkeys_for_relation(Planner
*** 884,895 ****
  static void
  postgresGetForeignPaths(PlannerInfo *root,
  						RelOptInfo *baserel,
! 						Oid foreigntableid)
  {
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) baserel->fdw_private;
  	ForeignPath *path;
  	List	   *ppi_list;
  	ListCell   *lc;
  
  	/*
  	 * Create simplest ForeignScan path node and add it to baserel.  This path
--- 868,966 ----
  static void
  postgresGetForeignPaths(PlannerInfo *root,
  						RelOptInfo *baserel,
! 						Oid foreigntableid,
! 						bool grouped)
  {
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) baserel->fdw_private;
  	ForeignPath *path;
+ 	PathTarget *target = NULL;
  	List	   *ppi_list;
  	ListCell   *lc;
+ 	List	   *fdw_private;
+ 	EstimateInfo *estimates;
+ 
+ 	if (grouped)
+ 	{
+ 		/*
+ 		 * The target must contain aggregates.
+ 		 */
+ 		Assert(baserel->gpi != NULL);
+ 		target = baserel->gpi->target;
+ 
+ 		/*
+ 		 * See postgresGetForeignRelSize().
+ 		 */
+ 		if (fpinfo->local_conds != NIL)
+ 			return;
+ 
+ 		/*
+ 		 * remote_conds will become fdw_recheck_quals for base relation, see
+ 		 * postgresGetForeignPlan(). Check if the grouping target contains all
+ 		 * vars necessary to evaluate the recheck quals.
+ 		 */
+ 		if (fpinfo->remote_conds != NIL && IS_SIMPLE_REL(baserel))
+ 		{
+ 			foreach(lc, fpinfo->remote_conds)
+ 			{
+ 				RestrictInfo *ri = lfirst_node(RestrictInfo, lc);
+ 				Expr	*clause = ri->clause;
+ 				List	*vars = pull_var_clause((Node *) clause, 0);
+ 				ListCell	*lc2;
+ 
+ 				/*
+ 				 * Each var must exist in the grouping target.
+ 				 */
+ 				foreach(lc2, vars)
+ 				{
+ 					ListCell	*lc3;
+ 					bool	found = false;
+ 					Var		*var = lfirst_node(Var, lc2);
+ 
+ 					foreach(lc3, target->exprs)
+ 					{
+ 						Expr	*gexpr = (Expr *) lfirst(lc3);
+ 
+ 						if (IsA(gexpr, Var))
+ 						{
+ 							Var	*gvar = castNode(Var, gexpr);
+ 
+ 							if (gvar->varno == var->varno &&
+ 								gvar->varattno == var->varattno &&
+ 								gvar->varlevelsup == var->varlevelsup)
+ 							{
+ 								found = true;
+ 								break;
+ 							}
+ 						}
+ 					}
+ 					if (!found)
+ 						break;
+ 				}
+ 				list_free(vars);
+ 
+ 				/*
+ 				 * Haven't checked all vars with success?
+ 				 */
+ 				if (lc2 != NULL)
+ 					return;
+ 			}
+ 			return;
+ 		}
+ 
+ 		/*
+ 		 * Wasn't it possible to construct remote query?
+ 		 */
+ 		if (fpinfo->grouped_tlist == NIL)
+ 			return;
+ 	}
+ 
+ 	estimates = !grouped ? &fpinfo->est_plain : &fpinfo->est_grouped;
+ 
+ 	/*
+ 	 * Add information to the paths whether they are grouped or not. The 2nd
+ 	 * element tells there's no ORDER BY clause.
+ 	 */
+ 	fdw_private = list_make2(makeInteger(grouped ? 1 : 0), NIL);
  
  	/*
  	 * Create simplest ForeignScan path node and add it to baserel.  This path
*************** postgresGetForeignPaths(PlannerInfo *roo
*** 899,916 ****
  	 * to estimate cost and size of this path.
  	 */
  	path = create_foreignscan_path(root, baserel,
! 								   NULL,	/* default pathtarget */
! 								   fpinfo->rows,
! 								   fpinfo->startup_cost,
! 								   fpinfo->total_cost,
  								   NIL, /* no pathkeys */
  								   NULL,	/* no outer rel either */
  								   NULL,	/* no extra plan */
! 								   NIL);	/* no fdw_private list */
! 	add_path(baserel, (Path *) path, false);
  
  	/* Add paths with pathkeys */
! 	add_paths_with_pathkeys_for_rel(root, baserel, NULL);
  
  	/*
  	 * If we're not using remote estimates, stop here.  We have no way to
--- 970,995 ----
  	 * to estimate cost and size of this path.
  	 */
  	path = create_foreignscan_path(root, baserel,
! 								   target,
! 								   estimates->rows,
! 								   estimates->startup_cost,
! 								   estimates->total_cost,
  								   NIL, /* no pathkeys */
  								   NULL,	/* no outer rel either */
  								   NULL,	/* no extra plan */
! 								   fdw_private);
! 
! 	/*
! 	 * Grouped path essentially produces an unique set of grouping
! 	 * keys.
! 	 */
! 	if (grouped)
! 		make_uniquekeys_for_agg_path(root, (Path *) path);
! 
! 	add_path(baserel, (Path *) path, grouped);
  
  	/* Add paths with pathkeys */
! 	add_paths_with_pathkeys_for_rel(root, baserel, NULL, grouped);
  
  	/*
  	 * If we're not using remote estimates, stop here.  We have no way to
*************** postgresGetForeignPaths(PlannerInfo *roo
*** 921,926 ****
--- 1000,1013 ----
  		return;
  
  	/*
+ 	 * Grouped parameterized path would lead to repeated aggregation, which is
+ 	 * not too interesting. Since the rest deals only with parameterized
+ 	 * paths, return now.
+ 	 */
+ 	if (grouped)
+ 		return;
+ 
+ 	/*
  	 * Thumb through all join clauses for the rel to identify which outer
  	 * relations could supply one or more safe-to-send-to-remote join clauses.
  	 * We'll build a parameterized path for each such outer relation.
*************** postgresGetForeignPaths(PlannerInfo *roo
*** 1052,1085 ****
  	foreach(lc, ppi_list)
  	{
  		ParamPathInfo *param_info = (ParamPathInfo *) lfirst(lc);
! 		double		rows;
! 		int			width;
! 		Cost		startup_cost;
! 		Cost		total_cost;
  
  		/* Get a cost estimate from the remote */
  		estimate_path_cost_size(root, baserel,
! 								param_info->ppi_clauses, NIL,
! 								&rows, &width,
! 								&startup_cost, &total_cost);
  
  		/*
  		 * ppi_rows currently won't get looked at by anything, but still we
  		 * may as well ensure that it matches our idea of the rowcount.
  		 */
! 		param_info->ppi_rows = rows;
  
  		/* Make the path */
  		path = create_foreignscan_path(root, baserel,
  									   NULL,	/* default pathtarget */
! 									   rows,
! 									   startup_cost,
! 									   total_cost,
  									   NIL, /* no pathkeys */
  									   param_info->ppi_req_outer,
  									   NULL,
! 									   NIL);	/* no fdw_private list */
! 		add_path(baserel, (Path *) path, false);
  	}
  }
  
--- 1139,1170 ----
  	foreach(lc, ppi_list)
  	{
  		ParamPathInfo *param_info = (ParamPathInfo *) lfirst(lc);
! 		EstimateInfo est_param;
  
  		/* Get a cost estimate from the remote */
+ 		memset(&est_param, 0, sizeof(EstimateInfo));
  		estimate_path_cost_size(root, baserel,
! 								param_info->ppi_clauses, NIL, NIL,
! 								&est_param, grouped);
  
  		/*
  		 * ppi_rows currently won't get looked at by anything, but still we
  		 * may as well ensure that it matches our idea of the rowcount.
  		 */
! 		param_info->ppi_rows = est_param.rows;
  
  		/* Make the path */
  		path = create_foreignscan_path(root, baserel,
  									   NULL,	/* default pathtarget */
! 									   est_param.rows,
! 									   est_param.startup_cost,
! 									   est_param.total_cost,
  									   NIL, /* no pathkeys */
  									   param_info->ppi_req_outer,
  									   NULL,
! 									   fdw_private);
! 
! 		add_path(baserel, (Path *) path, grouped);
  	}
  }
  
*************** postgresGetForeignPlan(PlannerInfo *root
*** 1107,1119 ****
  	List	   *retrieved_attrs;
  	StringInfoData sql;
  	ListCell   *lc;
  
  	if (IS_SIMPLE_REL(foreignrel))
  	{
! 		/*
! 		 * For base relations, set scan_relid as the relid of the relation.
! 		 */
! 		scan_relid = foreignrel->relid;
  
  		/*
  		 * In a base-relation scan, we must apply the given scan_clauses.
--- 1192,1235 ----
  	List	   *retrieved_attrs;
  	StringInfoData sql;
  	ListCell   *lc;
+ 	Value	   *grouped_val;
+ 	List		*order_by_exprs;
+ 	bool		grouped;
+ 
+ 	/*
+ 	 * Retrieve the useful information from fdw_private.
+ 	 */
+ 	Assert(list_length(best_path->fdw_private) == 2);
+ 	grouped_val = (Value *) linitial(best_path->fdw_private);
+ 	Assert(IsA(grouped_val, Integer));
+ 	grouped = intVal(grouped_val) == 1;
+ 	order_by_exprs = (List *) lsecond(best_path->fdw_private);
  
  	if (IS_SIMPLE_REL(foreignrel))
  	{
! 		if (!grouped)
! 		{
! 			/*
! 			 * For base relations, set scan_relid as the relid of the
! 			 * relation.
! 			 */
! 			scan_relid = foreignrel->relid;
! 		}
! 		else
! 		{
! 			/*
! 			 * XXX Something seems to be inconsistent here: deparseSelectSql()
! 			 * would create the targetlist from foreignrel->gpi->target, but
! 			 * not appendGroupByClause(). Try to refactor.
! 			 */
! 			fdw_scan_tlist = fpinfo->grouped_tlist;
! 
! 			/*
! 			 * The scan tuple descriptor should be constructed from
! 			 * fdw_scan_tlist rather than from that of the remote relation.
! 			 */
! 			scan_relid = 0;
! 		}
  
  		/*
  		 * In a base-relation scan, we must apply the given scan_clauses.
*************** postgresGetForeignPlan(PlannerInfo *root
*** 1160,1165 ****
--- 1276,1283 ----
  	}
  	else
  	{
+ 		List	*remote_src, *local_src;
+ 
  		/*
  		 * Join relation or upper relation - set scan_relid to 0.
  		 */
*************** postgresGetForeignPlan(PlannerInfo *root
*** 1176,1183 ****
  		 * Instead we get the conditions to apply from the fdw_private
  		 * structure.
  		 */
! 		remote_exprs = extract_actual_clauses(fpinfo->remote_conds, false);
! 		local_exprs = extract_actual_clauses(fpinfo->local_conds, false);
  
  		/*
  		 * We leave fdw_recheck_quals empty in this case, since we never need
--- 1294,1313 ----
  		 * Instead we get the conditions to apply from the fdw_private
  		 * structure.
  		 */
! 		if (IS_UPPER_REL(foreignrel) &&
! 			(fpinfo->remote_conds_upper || fpinfo->local_conds_upper))
! 		{
! 			remote_src = fpinfo->remote_conds_upper;
! 			local_src = fpinfo->local_conds_upper;
! 		}
! 		else
! 		{
! 			remote_src = fpinfo->remote_conds;
! 			local_src = fpinfo->local_conds;
! 		}
! 
! 		remote_exprs = extract_actual_clauses(remote_src, false);
! 		local_exprs = extract_actual_clauses(local_src, false);
  
  		/*
  		 * We leave fdw_recheck_quals empty in this case, since we never need
*************** postgresGetForeignPlan(PlannerInfo *root
*** 1191,1197 ****
  		 */
  
  		/* Build the list of columns to be fetched from the foreign server. */
! 		fdw_scan_tlist = build_tlist_to_deparse(foreignrel);
  
  		/*
  		 * Ensure that the outer plan produces a tuple whose descriptor
--- 1321,1330 ----
  		 */
  
  		/* Build the list of columns to be fetched from the foreign server. */
! 		if (!grouped)
! 			fdw_scan_tlist = build_tlist_to_deparse(foreignrel);
! 		else
! 			fdw_scan_tlist = fpinfo->grouped_tlist;
  
  		/*
  		 * Ensure that the outer plan produces a tuple whose descriptor
*************** postgresGetForeignPlan(PlannerInfo *root
*** 1238,1245 ****
  	 */
  	initStringInfo(&sql);
  	deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist,
! 							remote_exprs, best_path->path.pathkeys,
! 							false, &retrieved_attrs, &params_list);
  
  	/* Remember remote_exprs for possible use by postgresPlanDirectModify */
  	fpinfo->final_remote_exprs = remote_exprs;
--- 1371,1380 ----
  	 */
  	initStringInfo(&sql);
  	deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist,
! 							remote_exprs,
! 							order_by_exprs, best_path->path.pathkeys,
! 							false, &retrieved_attrs, &params_list,
! 							grouped);
  
  	/* Remember remote_exprs for possible use by postgresPlanDirectModify */
  	fpinfo->final_remote_exprs = remote_exprs;
*************** postgresExplainDirectModify(ForeignScanS
*** 2481,2486 ****
--- 2616,2739 ----
  	}
  }
  
+ /*
+  * init_pgfdw_rel_info
+  *      Initialize PgFdwRelationInfo and assign it to the base relation. That
+  *      includes estimates, as well as separation of local expressions from
+  *      remote ones.
+  */
+ static void
+ init_pgfdw_baserel_info(PlannerInfo *root, RelOptInfo *rel,
+ 						Oid foreigntableid, bool grouped)
+ 
+ {
+ 	const char *namespace;
+ 	const char *relname;
+ 	const char *refname;
+ 	PgFdwRelationInfo *fpinfo;
+ 	ListCell   *lc;
+ 	RangeTblEntry *rte;
+ 
+ 	rte = planner_rt_fetch(rel->relid, root);
+ 	fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
+ 
+ 	/* Base foreign tables need to be pushed down always. */
+ 	fpinfo->pushdown_safe = true;
+ 
+ 	/* Look up foreign-table catalog info. */
+ 	fpinfo->table = GetForeignTable(foreigntableid);
+ 	fpinfo->server = GetForeignServer(fpinfo->table->serverid);
+ 
+ 	/*
+ 	 * Extract user-settable option values.  Note that per-table setting of
+ 	 * use_remote_estimate overrides per-server setting.
+ 	 */
+ 	fpinfo->use_remote_estimate = false;
+ 	fpinfo->fdw_startup_cost = DEFAULT_FDW_STARTUP_COST;
+ 	fpinfo->fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST;
+ 	fpinfo->shippable_extensions = NIL;
+ 	fpinfo->fetch_size = 100;
+ 
+ 	apply_server_options(fpinfo);
+ 	apply_table_options(fpinfo);
+ 
+ 	/*
+ 	 * If the table or the server is configured to use remote estimates,
+ 	 * identify which user to do remote access as during planning.  This
+ 	 * should match what ExecCheckRTEPerms() does.  If we fail due to lack of
+ 	 * permissions, the query would have failed at runtime anyway.
+ 	 */
+ 	if (fpinfo->use_remote_estimate)
+ 	{
+ 		Oid			userid = rte->checkAsUser ? rte->checkAsUser :
+ 		GetUserId();
+ 
+ 		fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
+ 	}
+ 	else
+ 		fpinfo->user = NULL;
+ 
+ 	/*
+ 	 * Identify which baserestrictinfo clauses can be sent to the remote
+ 	 * server and which can't.
+ 	 */
+ 	classifyConditions(root, rel, rel->baserestrictinfo,
+ 					   &fpinfo->remote_conds, &fpinfo->local_conds);
+ 
+ 	/*
+ 	 * Identify which attributes will need to be retrieved from the remote
+ 	 * server.  These include all attrs needed for joins or final output, plus
+ 	 * all attrs used in the local_conds.  (Note: if we end up using a
+ 	 * parameterized scan, it's possible that some of the join clauses will be
+ 	 * sent to the remote and thus we wouldn't really need to retrieve the
+ 	 * columns used in them.  Doesn't seem worth detecting that case though.)
+ 	 */
+ 	fpinfo->attrs_used = NULL;
+ 	if (!grouped)
+ 	{
+ 		pull_varattnos((Node *) rel->reltarget->exprs, rel->relid,
+ 					   &fpinfo->attrs_used);
+ 		foreach(lc, fpinfo->local_conds)
+ 		{
+ 			RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
+ 
+ 			pull_varattnos((Node *) rinfo->clause, rel->relid,
+ 						   &fpinfo->attrs_used);
+ 		}
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * deparseExplicitTargetList() is used for the grouped relation, thus
+ 		 * no need to retrieve attrs_used.
+ 		 */
+ 	}
+ 
+ 	/*
+ 	 * Set the name of relation in fpinfo, while we are constructing it here.
+ 	 * It will be used to build the string describing the join relation in
+ 	 * EXPLAIN output. We can't know whether VERBOSE option is specified or
+ 	 * not, so always schema-qualify the foreign table name.
+ 	 */
+ 	fpinfo->relation_name = makeStringInfo();
+ 	namespace = get_namespace_name(get_rel_namespace(foreigntableid));
+ 	relname = get_rel_name(foreigntableid);
+ 	refname = rte->eref->aliasname;
+ 	appendStringInfo(fpinfo->relation_name, "%s.%s",
+ 					 quote_identifier(namespace),
+ 					 quote_identifier(relname));
+ 	if (*refname && strcmp(refname, relname) != 0)
+ 		appendStringInfo(fpinfo->relation_name, " %s",
+ 						 quote_identifier(rte->eref->aliasname));
+ 
+ 	/* No outer and inner relations. */
+ 	fpinfo->make_outerrel_subquery = false;
+ 	fpinfo->make_innerrel_subquery = false;
+ 	fpinfo->lower_subquery_rels = NULL;
+ 	/* Set the relation index. */
+ 	fpinfo->relation_index = rel->relid;
+ }
+ 
  
  /*
   * estimate_path_cost_size
*************** static void
*** 2498,2506 ****
  estimate_path_cost_size(PlannerInfo *root,
  						RelOptInfo *foreignrel,
  						List *param_join_conds,
  						List *pathkeys,
! 						double *p_rows, int *p_width,
! 						Cost *p_startup_cost, Cost *p_total_cost)
  {
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
  	double		rows;
--- 2751,2760 ----
  estimate_path_cost_size(PlannerInfo *root,
  						RelOptInfo *foreignrel,
  						List *param_join_conds,
+ 						List *order_by_exprs,
  						List *pathkeys,
! 						EstimateInfo * estimates,
! 						bool grouped)
  {
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
  	double		rows;
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2539,2546 ****
  						   &remote_param_join_conds, &local_param_join_conds);
  
  		/* Build the list of columns to be fetched from the foreign server. */
! 		if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel))
  			fdw_scan_tlist = build_tlist_to_deparse(foreignrel);
  		else
  			fdw_scan_tlist = NIL;
  
--- 2793,2805 ----
  						   &remote_param_join_conds, &local_param_join_conds);
  
  		/* Build the list of columns to be fetched from the foreign server. */
! 		if ((IS_JOIN_REL(foreignrel) && !grouped) || IS_UPPER_REL(foreignrel))
  			fdw_scan_tlist = build_tlist_to_deparse(foreignrel);
+ 		else if (grouped)
+ 		{
+ 			Assert(fpinfo->grouped_tlist != NIL);
+ 			fdw_scan_tlist = fpinfo->grouped_tlist;
+ 		}
  		else
  			fdw_scan_tlist = NIL;
  
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2560,2567 ****
  		initStringInfo(&sql);
  		appendStringInfoString(&sql, "EXPLAIN ");
  		deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist,
! 								remote_conds, pathkeys, false,
! 								&retrieved_attrs, NULL);
  
  		/* Get the remote estimate */
  		conn = GetConnection(fpinfo->user, false);
--- 2819,2826 ----
  		initStringInfo(&sql);
  		appendStringInfoString(&sql, "EXPLAIN ");
  		deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist,
! 								remote_conds, order_by_exprs, pathkeys, false,
! 								&retrieved_attrs, NULL, grouped);
  
  		/* Get the remote estimate */
  		conn = GetConnection(fpinfo->user, false);
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2615,2629 ****
  		 * bare scan each time. Instead, use the costs if we have cached them
  		 * already.
  		 */
! 		if (fpinfo->rel_startup_cost > 0 && fpinfo->rel_total_cost > 0)
  		{
! 			startup_cost = fpinfo->rel_startup_cost;
! 			run_cost = fpinfo->rel_total_cost - fpinfo->rel_startup_cost;
  		}
  		else if (IS_JOIN_REL(foreignrel))
  		{
! 			PgFdwRelationInfo *fpinfo_i;
! 			PgFdwRelationInfo *fpinfo_o;
  			QualCost	join_cost;
  			QualCost	remote_conds_cost;
  			double		nrows;
--- 2874,2890 ----
  		 * bare scan each time. Instead, use the costs if we have cached them
  		 * already.
  		 */
! 		if (estimates->rel_startup_cost > 0 && estimates->rel_total_cost > 0)
  		{
! 			startup_cost = estimates->rel_startup_cost;
! 			run_cost = estimates->rel_total_cost - estimates->rel_startup_cost;
  		}
  		else if (IS_JOIN_REL(foreignrel))
  		{
! 			PgFdwRelationInfo *fpinfo_i,
! 					   *fpinfo_o;
! 			EstimateInfo *est_i,
! 					   *est_o;
  			QualCost	join_cost;
  			QualCost	remote_conds_cost;
  			double		nrows;
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2632,2641 ****
  			Assert(fpinfo->innerrel && fpinfo->outerrel);
  
  			fpinfo_i = (PgFdwRelationInfo *) fpinfo->innerrel->fdw_private;
  			fpinfo_o = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private;
  
  			/* Estimate of number of rows in cross product */
! 			nrows = fpinfo_i->rows * fpinfo_o->rows;
  			/* Clamp retrieved rows estimate to at most size of cross product */
  			retrieved_rows = Min(retrieved_rows, nrows);
  
--- 2893,2904 ----
  			Assert(fpinfo->innerrel && fpinfo->outerrel);
  
  			fpinfo_i = (PgFdwRelationInfo *) fpinfo->innerrel->fdw_private;
+ 			est_i = !grouped ? &fpinfo_i->est_plain : &fpinfo_i->est_grouped;
  			fpinfo_o = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private;
+ 			est_o = !grouped ? &fpinfo_o->est_plain : &fpinfo_o->est_grouped;
  
  			/* Estimate of number of rows in cross product */
! 			nrows = est_i->rows * est_o->rows;
  			/* Clamp retrieved rows estimate to at most size of cross product */
  			retrieved_rows = Min(retrieved_rows, nrows);
  
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2659,2665 ****
  			 * tables) since we do not know what strategy the foreign server
  			 * is going to use.
  			 */
! 			startup_cost = fpinfo_i->rel_startup_cost + fpinfo_o->rel_startup_cost;
  			startup_cost += join_cost.startup;
  			startup_cost += remote_conds_cost.startup;
  			startup_cost += fpinfo->local_conds_cost.startup;
--- 2922,2928 ----
  			 * tables) since we do not know what strategy the foreign server
  			 * is going to use.
  			 */
! 			startup_cost = est_i->rel_startup_cost + est_o->rel_startup_cost;
  			startup_cost += join_cost.startup;
  			startup_cost += remote_conds_cost.startup;
  			startup_cost += fpinfo->local_conds_cost.startup;
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2679,2695 ****
  			 * 4. Run time cost of applying nonpushable other clauses locally
  			 * on the result fetched from the foreign server.
  			 */
! 			run_cost = fpinfo_i->rel_total_cost - fpinfo_i->rel_startup_cost;
! 			run_cost += fpinfo_o->rel_total_cost - fpinfo_o->rel_startup_cost;
  			run_cost += nrows * join_cost.per_tuple;
  			nrows = clamp_row_est(nrows * fpinfo->joinclause_sel);
  			run_cost += nrows * remote_conds_cost.per_tuple;
  			run_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows;
  		}
! 		else if (IS_UPPER_REL(foreignrel))
  		{
  			PgFdwRelationInfo *ofpinfo;
! 			PathTarget *ptarget = root->upper_targets[UPPERREL_GROUP_AGG];
  			AggClauseCosts aggcosts;
  			double		input_rows;
  			int			numGroupCols;
--- 2942,2970 ----
  			 * 4. Run time cost of applying nonpushable other clauses locally
  			 * on the result fetched from the foreign server.
  			 */
! 			run_cost = est_i->rel_total_cost - est_i->rel_startup_cost;
! 			run_cost += est_o->rel_total_cost - est_o->rel_startup_cost;
  			run_cost += nrows * join_cost.per_tuple;
  			nrows = clamp_row_est(nrows * fpinfo->joinclause_sel);
  			run_cost += nrows * remote_conds_cost.per_tuple;
  			run_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows;
  		}
! 
! 		/*
! 		 * TODO Consider a separate branch for
! 		 *
! 		 * (IS_SIMPLE_REL(foreignrel) && * grouped)
! 		 *
! 		 * and use foreignrel->gpi->target in it instead of
! 		 * fpinfo->grouped_tlist.
! 		 */
! 		else if (IS_UPPER_REL(foreignrel) ||
! 				 (IS_SIMPLE_REL(foreignrel) && grouped)
! 			)
  		{
  			PgFdwRelationInfo *ofpinfo;
! 			EstimateInfo *est;
! 			PathTarget *ptarget;
  			AggClauseCosts aggcosts;
  			double		input_rows;
  			int			numGroupCols;
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2707,2717 ****
  			 * considering remote and local conditions for costing.
  			 */
  
! 			ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private;
  
  			/* Get rows and width from input rel */
! 			input_rows = ofpinfo->rows;
! 			width = ofpinfo->width;
  
  			/* Collect statistics about aggregates for estimating costs. */
  			MemSet(&aggcosts, 0, sizeof(AggClauseCosts));
--- 2982,3001 ----
  			 * considering remote and local conditions for costing.
  			 */
  
! 			if (IS_UPPER_REL(foreignrel))
! 			{
! 				ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private;
! 				est = &ofpinfo->est_plain;
! 			}
! 			else
! 			{
! 				ofpinfo = fpinfo;
! 				est = &ofpinfo->est_grouped;
! 			}
  
  			/* Get rows and width from input rel */
! 			input_rows = est->rows;
! 			width = est->width;
  
  			/* Collect statistics about aggregates for estimating costs. */
  			MemSet(&aggcosts, 0, sizeof(AggClauseCosts));
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2736,2741 ****
--- 3020,3033 ----
  			 */
  			rows = retrieved_rows = numGroups;
  
+ 			if (IS_UPPER_REL(foreignrel))
+ 				ptarget = root->upper_targets[UPPERREL_GROUP_AGG];
+ 			else
+ 			{
+ 				Assert(foreignrel->gpi != NULL);
+ 				ptarget = foreignrel->gpi->target;
+ 			}
+ 
  			/*-----
  			 * Startup cost includes:
  			 *	  1. Startup cost for underneath input * relation
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2743,2749 ****
  			 *	  3. Startup cost for PathTarget eval
  			 *-----
  			 */
! 			startup_cost = ofpinfo->rel_startup_cost;
  			startup_cost += aggcosts.transCost.startup;
  			startup_cost += aggcosts.transCost.per_tuple * input_rows;
  			startup_cost += (cpu_operator_cost * numGroupCols) * input_rows;
--- 3035,3041 ----
  			 *	  3. Startup cost for PathTarget eval
  			 *-----
  			 */
! 			startup_cost = est->rel_startup_cost;
  			startup_cost += aggcosts.transCost.startup;
  			startup_cost += aggcosts.transCost.per_tuple * input_rows;
  			startup_cost += (cpu_operator_cost * numGroupCols) * input_rows;
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2756,2762 ****
  			 *	  3. PathTarget eval cost for each output row
  			 *-----
  			 */
! 			run_cost = ofpinfo->rel_total_cost - ofpinfo->rel_startup_cost;
  			run_cost += aggcosts.finalCost * numGroups;
  			run_cost += cpu_tuple_cost * numGroups;
  			run_cost += ptarget->cost.per_tuple * numGroups;
--- 3048,3054 ----
  			 *	  3. PathTarget eval cost for each output row
  			 *-----
  			 */
! 			run_cost = est->rel_total_cost - est->rel_startup_cost;
  			run_cost += aggcosts.finalCost * numGroups;
  			run_cost += cpu_tuple_cost * numGroups;
  			run_cost += ptarget->cost.per_tuple * numGroups;
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2809,2816 ****
  	 */
  	if (pathkeys == NIL && param_join_conds == NIL)
  	{
! 		fpinfo->rel_startup_cost = startup_cost;
! 		fpinfo->rel_total_cost = total_cost;
  	}
  
  	/*
--- 3101,3108 ----
  	 */
  	if (pathkeys == NIL && param_join_conds == NIL)
  	{
! 		estimates->rel_startup_cost = startup_cost;
! 		estimates->rel_total_cost = total_cost;
  	}
  
  	/*
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2825,2834 ****
  	total_cost += cpu_tuple_cost * retrieved_rows;
  
  	/* Return results. */
! 	*p_rows = rows;
! 	*p_width = width;
! 	*p_startup_cost = startup_cost;
! 	*p_total_cost = total_cost;
  }
  
  /*
--- 3117,3126 ----
  	total_cost += cpu_tuple_cost * retrieved_rows;
  
  	/* Return results. */
! 	estimates->rows = rows;
! 	estimates->width = width;
! 	estimates->startup_cost = startup_cost;
! 	estimates->total_cost = total_cost;
  }
  
  /*
*************** postgresImportForeignSchema(ImportForeig
*** 4057,4075 ****
   * Assess whether the join between inner and outer relations can be pushed down
   * to the foreign server. As a side effect, save information we obtain in this
   * function to PgFdwRelationInfo passed in.
   */
  static bool
  foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
  				RelOptInfo *outerrel, RelOptInfo *innerrel,
! 				JoinPathExtraData *extra)
  {
! 	PgFdwRelationInfo *fpinfo;
! 	PgFdwRelationInfo *fpinfo_o;
! 	PgFdwRelationInfo *fpinfo_i;
  	ListCell   *lc;
  	List	   *joinclauses;
  
  	/*
  	 * We support pushing down INNER, LEFT, RIGHT and FULL OUTER joins.
  	 * Constructing queries representing SEMI and ANTI joins is hard, hence
  	 * not considered right now.
--- 4349,4378 ----
   * Assess whether the join between inner and outer relations can be pushed down
   * to the foreign server. As a side effect, save information we obtain in this
   * function to PgFdwRelationInfo passed in.
+  *
+  * Caller expects that the "grouped" argument does not affect the result, so
+  * it's enough to call the function only once per relation. XXX As that
+  * argument is only used in Assert() statement, shouldn't it be removed?
   */
  static bool
  foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
  				RelOptInfo *outerrel, RelOptInfo *innerrel,
! 				JoinPathExtraData *extra, bool grouped)
  {
! 	PgFdwRelationInfo *fpinfo,
! 			   *fpinfo_o,
! 			   *fpinfo_i;
  	ListCell   *lc;
  	List	   *joinclauses;
  
  	/*
+ 	 * TODO
+ 	 *
+ 	 * Put the (join-relevant) initial checks of foreign_grouping_ok into a
+ 	 * function and call it here.
+ 	 */
+ 
+ 	/*
  	 * We support pushing down INNER, LEFT, RIGHT and FULL OUTER joins.
  	 * Constructing queries representing SEMI and ANTI joins is hard, hence
  	 * not considered right now.
*************** foreign_join_ok(PlannerInfo *root, RelOp
*** 4139,4145 ****
--- 4442,4458 ----
  			if (is_remote_clause)
  				fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo);
  			else
+ 			{
+ 				/*
+ 				 * We could return in the grouped case because all clauses
+ 				 * must be evaluated below the grouping plan (Thus if the
+ 				 * grouping is remote, those clauses must be remote too.)
+ 				 * However the same PgFdwRelationInfo is used for both grouped
+ 				 * and non-grouped case and we don't want to force caller to
+ 				 * process the non-grouped case first.
+ 				 */
  				fpinfo->local_conds = lappend(fpinfo->local_conds, rinfo);
+ 			}
  		}
  	}
  
*************** foreign_join_ok(PlannerInfo *root, RelOp
*** 4158,4166 ****
  		PlaceHolderInfo *phinfo = lfirst(lc);
  		Relids		relids = joinrel->relids;
  
! 		if (bms_is_subset(phinfo->ph_eval_at, relids) &&
! 			bms_nonempty_difference(relids, phinfo->ph_eval_at))
! 			return false;
  	}
  
  	/* Save the join clauses, for later use. */
--- 4471,4496 ----
  		PlaceHolderInfo *phinfo = lfirst(lc);
  		Relids		relids = joinrel->relids;
  
! 		if (bms_is_subset(phinfo->ph_eval_at, relids))
! 		{
! 			if (bms_nonempty_difference(relids, phinfo->ph_eval_at))
! 			{
! #ifdef USE_ASSERT_CHECKING
! 				if (grouped)
! 				{
! 					/*
! 					 * Grouped join shouldn't have PlaceHolderVar even in its
! 					 * own targetlist. It'd mean that the output of partial
! 					 * aggregation can be set to NULL before it gets to the
! 					 * input of the final aggregation.
! 					 */
! 					Assert(false);
! 				}
! #endif							/* USE_ASSERT_CHECKING */
! 
! 				return false;
! 			}
! 		}
  	}
  
  	/* Save the join clauses, for later use. */
*************** foreign_join_ok(PlannerInfo *root, RelOp
*** 4283,4296 ****
  		fpinfo->user = NULL;
  
  	/*
- 	 * Set cached relation costs to some negative value, so that we can detect
- 	 * when they are set to some sensible costs, during one (usually the
- 	 * first) of the calls to estimate_path_cost_size().
- 	 */
- 	fpinfo->rel_startup_cost = -1;
- 	fpinfo->rel_total_cost = -1;
- 
- 	/*
  	 * Set the string describing this join relation to be used in EXPLAIN
  	 * output of corresponding ForeignScan.
  	 */
--- 4613,4618 ----
*************** foreign_join_ok(PlannerInfo *root, RelOp
*** 4315,4354 ****
  
  static void
  add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
! 								Path *epq_path)
  {
  	List	   *useful_pathkeys_list = NIL; /* List of all pathkeys */
! 	ListCell   *lc;
  
! 	useful_pathkeys_list = get_useful_pathkeys_for_relation(root, rel);
  
  	/* Create one path for each set of pathkeys we found above. */
! 	foreach(lc, useful_pathkeys_list)
  	{
! 		double		rows;
! 		int			width;
! 		Cost		startup_cost;
! 		Cost		total_cost;
! 		List	   *useful_pathkeys = lfirst(lc);
  
! 		estimate_path_cost_size(root, rel, NIL, useful_pathkeys,
! 								&rows, &width, &startup_cost, &total_cost);
  
! 		add_path(rel, (Path *)
! 				 create_foreignscan_path(root, rel,
! 										 NULL,
! 										 rows,
! 										 startup_cost,
! 										 total_cost,
! 										 useful_pathkeys,
! 										 NULL,
! 										 epq_path,
! 										 NIL),
! 				 false);
  	}
  }
  
  /*
   * Parse options from foreign server and apply them to fpinfo.
   *
   * New options might also require tweaking merge_fdw_options().
--- 4637,4858 ----
  
  static void
  add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
! 								Path *epq_path, bool grouped)
  {
  	List	   *useful_pathkeys_list = NIL; /* List of all pathkeys */
! 	ListCell   *l;
! 	PathTarget *target = NULL;
! 	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
! 	List	*fdw_scan_tlist = NIL;
  
! 	useful_pathkeys_list = get_useful_pathkeys_for_relation(root, rel,
! 															grouped);
! 
! 	/*
! 	 * If the path should be grouped, not all the ECs are necessarily present
! 	 * in the target: the EC member applicable to the parent relation can in
! 	 * fact be an aggregate argument which does not otherwise appear in the
! 	 * query output.
! 	 */
! 	if (grouped)
! 		fdw_scan_tlist = fpinfo->grouped_tlist;
! 
! 	/*
! 	 * Grouped foreign scan should emit tuples according to the grouped
! 	 * target, i.e. the one containing GroupedVars.
! 	 */
! 	if (grouped)
! 	{
! 		Assert(rel->gpi != NULL);
! 		target = rel->gpi->target;
! 	}
  
  	/* Create one path for each set of pathkeys we found above. */
! 	foreach(l, useful_pathkeys_list)
  	{
! 		List	   *useful_pathkeys = lfirst(l);
! 		EstimateInfo estimates;
! 		List	*order_by_exprs = NIL;
! 		List	   *fdw_private;
! 		Path	*path;
  
! 		/*
! 		 * Perform preprocessing specific to grouped paths or reject the
! 		 * pathkeys if they cannot be used on the remote database.
! 		 */
! 		order_by_exprs = get_order_by_expressions(rel->relids,
! 												  useful_pathkeys,
! 												  fdw_scan_tlist);
  
! 		/*
! 		 * Skip these pathkeys if the remote database cannot perform the
! 		 * appropriate sort.
! 		 */
! 		if (order_by_exprs == NIL)
! 			continue;
! 
! 		/*
! 		 * The cached values are not useful for join path.
! 		 */
! 		estimates.rel_startup_cost = -1;
! 		estimates.rel_total_cost = -1;
! 
! 		estimate_path_cost_size(root, rel, NIL, order_by_exprs,
! 								useful_pathkeys, &estimates, grouped);
! 
! 		/*
! 		 * Add information to the paths whether they are grouped or not. And if it
! 		 * is, pass it the expressions for the ORDER BY clause.
! 		 */
! 		fdw_private = list_make2(makeInteger(grouped ? 1 : 0),
! 								 order_by_exprs);
! 
! 		path = (Path *) create_foreignscan_path(root, rel,
! 												target,
! 												estimates.rows,
! 												estimates.startup_cost,
! 												estimates.total_cost,
! 												useful_pathkeys,
! 												NULL,
! 												epq_path,
! 												fdw_private);
! 
! 		/*
! 		 * Grouped path essentially produces an unique set of grouping
! 		 * keys.
! 		 */
! 		if (grouped)
! 			make_uniquekeys_for_agg_path(root, path);
! 
! 		add_path(rel, path, grouped);
  	}
  }
  
  /*
+  * Create a list of expressions to form ORDER BY clause on the remote
+  * database. Pass fdw_scan_tlist if the pathkeys possibly contain expressions
+  * not present in the targetlist. Such expressions will be ignored.
+  */
+ static List *
+ get_order_by_expressions(Relids relids, List *pathkeys, List *fdw_scan_tlist)
+ {
+ 	ListCell	*l1;
+ 	List	*result = NIL;
+ 
+ 	foreach(l1, pathkeys)
+ 	{
+ 		ListCell	*l2;
+ 		PathKey		*pathkey = lfirst_node(PathKey, l1);
+ 		Expr	*em_expr = NULL;
+ 		Aggref	*aggref_copy = NULL;
+ 
+ 		/*
+ 		 * Since the targetlist can contain EC-derived expressions, extra
+ 		 * effort is needed to ensure that ORDER BY clause references
+ 		 * expression actually contained in the targetlist.
+ 		 */
+ 		foreach(l2, pathkey->pk_eclass->ec_members)
+ 		{
+ 			EquivalenceMember *em = lfirst_node(EquivalenceMember, l2);
+ 
+ 			if (bms_is_subset(em->em_relids, relids))
+ 			{
+ 				ListCell	*l3;
+ 
+ 				if (fdw_scan_tlist)
+ 				{
+ 					/*
+ 					 * The EM can be derived from an expression present in the
+ 					 * targetlist, but is not necessarily in the list
+ 					 * itself. However the ORDER BY clause used on the remote
+ 					 * server can only contain the "real" targetlist
+ 					 * expression.
+ 					 */
+ 					foreach(l3, fdw_scan_tlist)
+ 					{
+ 						TargetEntry	*te = lfirst_node(TargetEntry, l3);
+ 						Expr	*expr = te->expr;
+ 
+ 						/*
+ 						 * Even if the query has Aggref nested in a generic
+ 						 * expression, fdw_scan_tlist should only contain
+ 						 * separate entry for the Aggref itself. Thus we don't
+ 						 * need to check the expressions recursively.
+ 						 */
+ 						if (IsA(expr, Aggref) && IsA(em->em_expr, Aggref))
+ 						{
+ 							Aggref	*aggref = castNode(Aggref, expr);
+ 							Aggref	*aggref_em;
+ 
+ 							/*
+ 							 * It makes no sense to sort by an aggregate if it
+ 							 * requires finalization. First, the finalized
+ 							 * values might end up sorted in a different
+ 							 * order, second, the transient state might not be
+ 							 * sortable at all.
+ 							 */
+ 							if (aggref->aggfinalfn != InvalidOid)
+ 							{
+ 								list_free(result);
+ 								return NIL;
+ 							}
+ 
+ 							aggref_em = (Aggref *) em->em_expr;
+ 
+ 							/*
+ 							 * The EC members are simple aggregates, so adjust
+ 							 * the target expression to make match
+ 							 * possible. Flat copy is sufficient for this
+ 							 * purpose.
+ 							 */
+ 							Assert(aggref_em->aggsplit == AGGSPLIT_SIMPLE);
+ 							Assert(aggref->aggsplit == AGGSPLIT_INITIAL_SERIAL);
+ 							aggref_copy = makeNode(Aggref);
+ 							memcpy(aggref_copy, aggref, sizeof(Aggref));
+ 							aggref_copy->aggsplit = AGGSPLIT_SIMPLE;
+ 							aggref_copy->aggtype = aggref_em->aggtype;
+ 							expr = (Expr *) aggref_copy;
+ 						}
+ 
+ 						if (equal(expr, em->em_expr))
+ 						{
+ 							em_expr = em->em_expr;
+ 							break;
+ 						}
+ 					}
+ 				}
+ 				else
+ 					em_expr = em->em_expr;
+ 			}
+ 
+ 			/*
+ 			 * Target expression found for the current pathkey?
+ 			 */
+ 			if (em_expr != NULL)
+ 				break;
+ 		}
+ 
+ 		/*
+ 		 * A single missing expression makes the pathkey useless. This is
+ 		 * specific to grouped relation because it can only emit grouping
+ 		 * expressions and aggregates.
+ 		 */
+ 		if (em_expr == NULL)
+ 		{
+ 			list_free(result);
+ 			return NIL;
+ 		}
+ 
+ 		result = lappend(result, em_expr);
+ 
+ 		if (aggref_copy)
+ 			pfree(aggref_copy);
+ 	}
+ 
+ 	return result;
+ }
+ 
+ /*
   * Parse options from foreign server and apply them to fpinfo.
   *
   * New options might also require tweaking merge_fdw_options().
*************** postgresGetForeignJoinPaths(PlannerInfo
*** 4462,4496 ****
  							RelOptInfo *outerrel,
  							RelOptInfo *innerrel,
  							JoinType jointype,
! 							JoinPathExtraData *extra)
  {
  	PgFdwRelationInfo *fpinfo;
  	ForeignPath *joinpath;
- 	double		rows;
- 	int			width;
- 	Cost		startup_cost;
- 	Cost		total_cost;
  	Path	   *epq_path;		/* Path to create plan to be executed when
  								 * EvalPlanQual gets triggered. */
  
! 	/*
! 	 * Skip if this join combination has been considered already.
! 	 */
! 	if (joinrel->fdw_private)
! 		return;
  
! 	/*
! 	 * Create unfinished PgFdwRelationInfo entry which is used to indicate
! 	 * that the join relation is already considered, so that we won't waste
! 	 * time in judging safety of join pushdown and adding the same paths again
! 	 * if found safe. Once we know that this join can be pushed down, we fill
! 	 * the entry.
! 	 */
! 	fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
! 	fpinfo->pushdown_safe = false;
! 	joinrel->fdw_private = fpinfo;
! 	/* attrs_used is only for base relations. */
! 	fpinfo->attrs_used = NULL;
  
  	/*
  	 * If there is a possibility that EvalPlanQual will be executed, we need
--- 4966,5016 ----
  							RelOptInfo *outerrel,
  							RelOptInfo *innerrel,
  							JoinType jointype,
! 							JoinPathExtraData *extra,
! 							bool grouped)
  {
  	PgFdwRelationInfo *fpinfo;
+ 	EstimateInfo *estimates;
  	ForeignPath *joinpath;
  	Path	   *epq_path;		/* Path to create plan to be executed when
  								 * EvalPlanQual gets triggered. */
+ 	bool		first_time;
+ 	PathTarget *target = NULL;
+ 	List	   *fdw_private;
  
! 	if (joinrel->fdw_private == NULL)
! 	{
! 		/*
! 		 * First time through.
! 		 *
! 		 * Create unfinished PgFdwRelationInfo entry which is used to indicate
! 		 * that the join relation is already considered, so that we won't
! 		 * waste time in judging safety of join pushdown and adding the same
! 		 * paths again if found safe. Once we know that this join can be
! 		 * pushed down, we fill the entry.
! 		 */
! 		fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
! 		fpinfo->pushdown_safe = false;
! 		joinrel->fdw_private = fpinfo;
! 		/* attrs_used is only for base relations. */
! 		fpinfo->attrs_used = NULL;
  
! 		first_time = true;
! 	}
! 	else
! 	{
! 		fpinfo = (PgFdwRelationInfo *) joinrel->fdw_private;
! 		first_time = false;
! 
! 		if (!fpinfo->pushdown_safe)
! 		{
! 			/*
! 			 * The function was already called but some test failed. No reason
! 			 * to see that failure again.
! 			 */
! 			return;
! 		}
! 	}
  
  	/*
  	 * If there is a possibility that EvalPlanQual will be executed, we need
*************** postgresGetForeignJoinPaths(PlannerInfo
*** 4501,4527 ****
  	 * dominate the only suitable local path available.  We also do it before
  	 * calling foreign_join_ok(), since that function updates fpinfo and marks
  	 * it as pushable if the join is found to be pushable.
  	 */
! 	if (root->parse->commandType == CMD_DELETE ||
! 		root->parse->commandType == CMD_UPDATE ||
! 		root->rowMarks)
  	{
! 		epq_path = GetExistingLocalJoinPath(joinrel);
! 		if (!epq_path)
  		{
! 			elog(DEBUG3, "could not push down foreign join because a local path suitable for EPQ checks was not found");
  			return;
  		}
  	}
- 	else
- 		epq_path = NULL;
  
! 	if (!foreign_join_ok(root, joinrel, jointype, outerrel, innerrel, extra))
! 	{
! 		/* Free path required for EPQ if we copied one; we don't need it now */
! 		if (epq_path)
! 			pfree(epq_path);
  		return;
  	}
  
  	/*
--- 5021,5098 ----
  	 * dominate the only suitable local path available.  We also do it before
  	 * calling foreign_join_ok(), since that function updates fpinfo and marks
  	 * it as pushable if the join is found to be pushable.
+ 	 *
+ 	 * The following tests should only be performed once --- repeated, the are
+ 	 * supposed to yield the same result. (If anything failed initially,
+ 	 * pushdown_safe should have caused return above.)
  	 */
! 	if (first_time)
  	{
! 		if (root->parse->commandType == CMD_DELETE ||
! 			root->parse->commandType == CMD_UPDATE ||
! 			root->rowMarks)
  		{
! 			epq_path = GetExistingLocalJoinPath(joinrel);
! 			if (!epq_path)
! 			{
! 				elog(DEBUG3, "could not push down foreign join because a local path suitable for EPQ checks was not found");
! 				return;
! 			}
! 		}
! 		else
! 			epq_path = NULL;
! 
! 		if (!foreign_join_ok(root, joinrel, jointype, outerrel, innerrel, extra,
! 							 grouped))
! 		{
! 			/*
! 			 * Free path required for EPQ if we copied one; we don't need it
! 			 * now
! 			 */
! 			if (epq_path)
! 				pfree(epq_path);
  			return;
  		}
  	}
  
! 	/*
! 	 * Local expressions would be evaluated above the grouping plan, which
! 	 * will be remote.
! 	 */
! 	if (grouped && fpinfo->local_conds != NIL)
  		return;
+ 
+ 	/*
+ 	 * This function should not be called repeatedly wit the same value of
+ 	 * "grouped", but if it happened, make sure the "upper conditions" do not
+ 	 * get duplicated or used at all if !grouped.
+ 	 */
+ 	if (fpinfo->remote_conds_upper)
+ 	{
+ 		list_free(fpinfo->remote_conds_upper);
+ 		fpinfo->remote_conds_upper = NIL;
+ 	}
+ 	if (fpinfo->local_conds_upper)
+ 	{
+ 		list_free(fpinfo->local_conds_upper);
+ 		fpinfo->local_conds_upper = NIL;
+ 	}
+ 
+ 	if (grouped)
+ 	{
+ 		/*
+ 		 * Create targelist to be used for the remote grouping.
+ 		 */
+ 		Assert(joinrel->gpi != NULL);
+ 		fpinfo->grouped_tlist = create_remote_agg_tlist(root, joinrel,
+ 														joinrel->gpi->target);
+ 
+ 		/*
+ 		 * Couldn't push any GROUP BY expression or aggregate to the remote
+ 		 * server?
+ 		 */
+ 		if (fpinfo->grouped_tlist == NIL)
+ 			return;
  	}
  
  	/*
*************** postgresGetForeignJoinPaths(PlannerInfo
*** 4548,4563 ****
  														0, fpinfo->jointype,
  														extra->sjinfo);
  
  	/* Estimate costs for bare join relation */
! 	estimate_path_cost_size(root, joinrel, NIL, NIL, &rows,
! 							&width, &startup_cost, &total_cost);
  	/* Now update this information in the joinrel */
! 	joinrel->rows = rows;
! 	joinrel->reltarget->width = width;
! 	fpinfo->rows = rows;
! 	fpinfo->width = width;
! 	fpinfo->startup_cost = startup_cost;
! 	fpinfo->total_cost = total_cost;
  
  	/*
  	 * Create a new join path and add it to the joinrel which represents a
--- 5119,5163 ----
  														0, fpinfo->jointype,
  														extra->sjinfo);
  
+ 	/*
+ 	 * Set cached relation costs to some negative value, so that we can detect
+ 	 * when they are set to some sensible costs, during one (usually the
+ 	 * first) of the calls to estimate_path_cost_size().
+ 	 */
+ 	estimates = !grouped ? &fpinfo->est_plain : &fpinfo->est_grouped;
+ 	estimates->rel_startup_cost = -1;
+ 	estimates->rel_total_cost = -1;
+ 
  	/* Estimate costs for bare join relation */
! 
! 	/*
! 	 * TODO Include remote_conds_upper and local_conds_upper into the
! 	 * estimates.
! 	 */
! 	estimate_path_cost_size(root, joinrel, NIL, NIL, NIL, estimates, grouped);
  	/* Now update this information in the joinrel */
! 
! 	/*
! 	 * TODO If grouped, make sure baserel->gpi exists and store the values
! 	 * there.
! 	 */
! 	joinrel->rows = estimates->rows;
! 	joinrel->reltarget->width = estimates->width;
! 
! 	if (grouped)
! 	{
! 		/*
! 		 * The target must contain aggregates.
! 		 */
! 		Assert(joinrel->gpi != NULL);
! 		target = joinrel->gpi->target;
! 	}
! 
! 	/*
! 	 * Add information to the paths whether they are grouped or not. Pass NIL
! 	 * for ordering expressions.
! 	 */
! 	fdw_private = list_make2(makeInteger(grouped ? 1 : 0), NIL);
  
  	/*
  	 * Create a new join path and add it to the joinrel which represents a
*************** postgresGetForeignJoinPaths(PlannerInfo
*** 4565,4584 ****
  	 */
  	joinpath = create_foreignscan_path(root,
  									   joinrel,
! 									   NULL,	/* default pathtarget */
! 									   rows,
! 									   startup_cost,
! 									   total_cost,
  									   NIL, /* no pathkeys */
  									   NULL,	/* no required_outer */
  									   epq_path,
! 									   NIL);	/* no fdw_private */
  
  	/* Add generated path into joinrel by add_path(). */
! 	add_path(joinrel, (Path *) joinpath, false);
  
  	/* Consider pathkeys for the join relation */
! 	add_paths_with_pathkeys_for_rel(root, joinrel, epq_path);
  
  	/* XXX Consider parameterized paths for the join relation */
  }
--- 5165,5191 ----
  	 */
  	joinpath = create_foreignscan_path(root,
  									   joinrel,
! 									   target,	/* default pathtarget */
! 									   estimates->rows,
! 									   estimates->startup_cost,
! 									   estimates->total_cost,
  									   NIL, /* no pathkeys */
  									   NULL,	/* no required_outer */
  									   epq_path,
! 									   fdw_private);	/* no fdw_private */
! 
! 	/*
! 	 * Grouped path essentially produces an unique set of grouping
! 	 * keys.
! 	 */
! 	if (grouped)
! 		make_uniquekeys_for_agg_path(root, (Path *) joinpath);
  
  	/* Add generated path into joinrel by add_path(). */
! 	add_path(joinrel, (Path *) joinpath, grouped);
  
  	/* Consider pathkeys for the join relation */
! 	add_paths_with_pathkeys_for_rel(root, joinrel, epq_path, grouped);
  
  	/* XXX Consider parameterized paths for the join relation */
  }
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4594,4618 ****
  	Query	   *query = root->parse;
  	PathTarget *grouping_target;
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) grouped_rel->fdw_private;
! 	PgFdwRelationInfo *ofpinfo;
! 	List	   *aggvars;
! 	ListCell   *lc;
! 	int			i;
! 	List	   *tlist = NIL;
  
  	/* Grouping Sets are not pushable */
  	if (query->groupingSets)
  		return false;
  
  	/* Get the fpinfo of the underlying scan relation. */
! 	ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private;
  
  	/*
  	 * If underneath input relation has any local conditions, those conditions
  	 * are required to be applied before performing aggregation.  Hence the
  	 * aggregate cannot be pushed down.
  	 */
! 	if (ofpinfo->local_conds)
  		return false;
  
  	/*
--- 5201,5223 ----
  	Query	   *query = root->parse;
  	PathTarget *grouping_target;
  	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) grouped_rel->fdw_private;
! 	PgFdwRelationInfo *ofpinfo = NULL;
  
  	/* Grouping Sets are not pushable */
  	if (query->groupingSets)
  		return false;
  
  	/* Get the fpinfo of the underlying scan relation. */
! 	if (IS_UPPER_REL(grouped_rel))
! 		ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private;
  
  	/*
  	 * If underneath input relation has any local conditions, those conditions
  	 * are required to be applied before performing aggregation.  Hence the
  	 * aggregate cannot be pushed down.
  	 */
! 	if ((IS_UPPER_REL(grouped_rel) && ofpinfo->local_conds) ||
! 		fpinfo->local_conds)
  		return false;
  
  	/*
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4623,4637 ****
  	 * different from those in the plan's targetlist. Use a copy of path
  	 * target to record the new sortgrouprefs.
  	 */
! 	grouping_target = copy_pathtarget(root->upper_targets[UPPERREL_GROUP_AGG]);
  
  	/*
! 	 * Evaluate grouping targets and check whether they are safe to push down
! 	 * to the foreign side.  All GROUP BY expressions will be part of the
! 	 * grouping target and thus there is no need to evaluate it separately.
! 	 * While doing so, add required expressions into target list which can
! 	 * then be used to pass to foreign server.
  	 */
  	i = 0;
  	foreach(lc, grouping_target->exprs)
  	{
--- 5228,5296 ----
  	 * different from those in the plan's targetlist. Use a copy of path
  	 * target to record the new sortgrouprefs.
  	 */
! 	if (IS_UPPER_REL(grouped_rel))
! 		grouping_target = root->upper_targets[UPPERREL_GROUP_AGG];
! 	else
! 	{
! 		/*
! 		 * FDW should not be asked to generate grouped paths if the simple or
! 		 * join relation has no relevant target.
! 		 */
! 		Assert(grouped_rel->gpi != NULL);
! 		Assert(grouped_rel->gpi->target != NULL);
! 
! 		grouping_target = grouped_rel->gpi->target;
! 	}
  
  	/*
! 	 * Create targetlist to be sent to the remote server.
  	 */
+ 	fpinfo->grouped_tlist = create_remote_agg_tlist(root, grouped_rel,
+ 													grouping_target);
+ 	if (fpinfo->grouped_tlist == NIL)
+ 		return false;
+ 
+ 	/* Safe to pushdown */
+ 	fpinfo->pushdown_safe = true;
+ 
+ 	/*
+ 	 * Set the string describing this grouped relation to be used in EXPLAIN
+ 	 * output of corresponding ForeignScan.
+ 	 */
+ 	if (ofpinfo != NULL)
+ 	{
+ 		fpinfo->relation_name = makeStringInfo();
+ 		appendStringInfo(fpinfo->relation_name, "Aggregate on (%s)",
+ 						 ofpinfo->relation_name->data);
+ 	}
+ 
+ 	return true;
+ }
+ 
+ /*
+  * Evaluate grouping targets and check whether they are safe to push down to
+  * the foreign side.  All GROUP BY expressions will be part of the grouping
+  * target and thus there is no need to evaluate it separately.  While doing
+  * so, add required expressions into target list which can then be used to
+  * pass to foreign server.
+  *
+  * NIL is returned if the remote aggregation appears to be impossible.
+  *
+  * Note that the function can add items to both remote_conds_upper and
+  * local_conds_upper lists of PgFdwRelationInfo.
+  */
+ static List *
+ create_remote_agg_tlist(PlannerInfo *root, RelOptInfo *grouped_rel,
+ 						PathTarget *grouping_target)
+ {
+ 	List	   *tlist = NIL;
+ 	ListCell   *lc;
+ 	int			i;
+ 	Query	   *query = root->parse;
+ 	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) grouped_rel->fdw_private;
+ 
+ 	grouping_target = copy_pathtarget(grouping_target);
+ 
  	i = 0;
  	foreach(lc, grouping_target->exprs)
  	{
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4647,4659 ****
  			 * push down aggregation to the foreign server.
  			 */
  			if (!is_foreign_expr(root, grouped_rel, expr))
! 				return false;
  
  			/* Pushable, add to tlist */
  			tlist = add_to_flat_tlist(tlist, list_make1(expr));
  		}
  		else
  		{
  			/* Check entire expression whether it is pushable or not */
  			if (is_foreign_expr(root, grouped_rel, expr))
  			{
--- 5306,5339 ----
  			 * push down aggregation to the foreign server.
  			 */
  			if (!is_foreign_expr(root, grouped_rel, expr))
! 				return NIL;
  
  			/* Pushable, add to tlist */
  			tlist = add_to_flat_tlist(tlist, list_make1(expr));
  		}
  		else
  		{
+ 			/*
+ 			 * Aggregate can sometimes be wrapped by GroupedVar.
+ 			 *
+ 			 */
+ 			if ((IS_SIMPLE_REL(grouped_rel) || IS_JOIN_REL(grouped_rel)) &&
+ 				IsA(expr, GroupedVar))
+ 			{
+ 				GroupedVar *gvar = castNode(GroupedVar, expr);
+ 
+ 				/*
+ 				 * GroupedVar is currently used only to handle Aggrefs.
+ 				 *
+ 				 * XXX These may include aggregates that appear in the HAVING
+ 				 * clause. Although the HAVING clause is currently not sent to
+ 				 * the remote server (see deparseSelectStmtForRel), we still
+ 				 * need to send the aggregates the HAVING clause contains.
+ 				 */
+ 				Assert(IsA(gvar->agg_partial, Aggref));
+ 				expr = (Expr *) gvar->agg_partial;
+ 			}
+ 
  			/* Check entire expression whether it is pushable or not */
  			if (is_foreign_expr(root, grouped_rel, expr))
  			{
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4662,4667 ****
--- 5342,5349 ----
  			}
  			else
  			{
+ 				List	   *aggvars;
+ 
  				/*
  				 * If we have sortgroupref set, then it means that we have an
  				 * ORDER BY entry pointing to this expression.  Since we are
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4670,4681 ****
  				if (sgref)
  					grouping_target->sortgrouprefs[i] = 0;
  
! 				/* Not matched exactly, pull the var with aggregates then */
  				aggvars = pull_var_clause((Node *) expr,
  										  PVC_INCLUDE_AGGREGATES);
  
  				if (!is_foreign_expr(root, grouped_rel, (Expr *) aggvars))
! 					return false;
  
  				/*
  				 * Add aggregates, if any, into the targetlist.  Plain var
--- 5352,5365 ----
  				if (sgref)
  					grouping_target->sortgrouprefs[i] = 0;
  
! 				/*
! 				 * Not matched exactly, pull the var with aggregates then
! 				 */
  				aggvars = pull_var_clause((Node *) expr,
  										  PVC_INCLUDE_AGGREGATES);
  
  				if (!is_foreign_expr(root, grouped_rel, (Expr *) aggvars))
! 					return NIL;
  
  				/*
  				 * Add aggregates, if any, into the targetlist.  Plain var
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4703,4714 ****
  
  	/*
  	 * Classify the pushable and non-pushable having clauses and save them in
! 	 * remote_conds and local_conds of the grouped rel's fpinfo.
  	 */
  	if (root->hasHavingQual && query->havingQual)
  	{
- 		ListCell   *lc;
- 
  		foreach(lc, (List *) query->havingQual)
  		{
  			Expr	   *expr = (Expr *) lfirst(lc);
--- 5387,5403 ----
  
  	/*
  	 * Classify the pushable and non-pushable having clauses and save them in
! 	 * remote_conds_upper and local_conds_upper of the grouped rel's fpinfo.
! 	 */
! 
! 	/*
! 	 * TODO For non-upper grouped_rel, only pick those expressions for which
! 	 * grouped_rel provides all input vars. (And Assert() that grouped_rel
! 	 * does not emit just some of them --- in such a case grouped_rel
! 	 * shouldn't be subject to partial aggregation at all.)
  	 */
  	if (root->hasHavingQual && query->havingQual)
  	{
  		foreach(lc, (List *) query->havingQual)
  		{
  			Expr	   *expr = (Expr *) lfirst(lc);
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4728,4736 ****
  									  NULL,
  									  NULL);
  			if (is_foreign_expr(root, grouped_rel, expr))
! 				fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo);
  			else
! 				fpinfo->local_conds = lappend(fpinfo->local_conds, rinfo);
  		}
  	}
  
--- 5417,5427 ----
  									  NULL,
  									  NULL);
  			if (is_foreign_expr(root, grouped_rel, expr))
! 				fpinfo->remote_conds_upper =
! 					lappend(fpinfo->remote_conds_upper, rinfo);
  			else
! 				fpinfo->local_conds_upper =
! 					lappend(fpinfo->local_conds_upper, rinfo);
  		}
  	}
  
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4738,4749 ****
  	 * If there are any local conditions, pull Vars and aggregates from it and
  	 * check whether they are safe to pushdown or not.
  	 */
! 	if (fpinfo->local_conds)
  	{
  		List	   *aggvars = NIL;
- 		ListCell   *lc;
  
! 		foreach(lc, fpinfo->local_conds)
  		{
  			RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
  
--- 5429,5439 ----
  	 * If there are any local conditions, pull Vars and aggregates from it and
  	 * check whether they are safe to pushdown or not.
  	 */
! 	if (fpinfo->local_conds_upper)
  	{
  		List	   *aggvars = NIL;
  
! 		foreach(lc, fpinfo->local_conds_upper)
  		{
  			RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
  
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4765,4771 ****
  			if (IsA(expr, Aggref))
  			{
  				if (!is_foreign_expr(root, grouped_rel, expr))
! 					return false;
  
  				tlist = add_to_flat_tlist(tlist, list_make1(expr));
  			}
--- 5455,5461 ----
  			if (IsA(expr, Aggref))
  			{
  				if (!is_foreign_expr(root, grouped_rel, expr))
! 					return NIL;
  
  				tlist = add_to_flat_tlist(tlist, list_make1(expr));
  			}
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4775,4803 ****
  	/* Transfer any sortgroupref data to the replacement tlist */
  	apply_pathtarget_labeling_to_tlist(tlist, grouping_target);
  
! 	/* Store generated targetlist */
! 	fpinfo->grouped_tlist = tlist;
! 
! 	/* Safe to pushdown */
! 	fpinfo->pushdown_safe = true;
! 
! 	/*
! 	 * Set cached relation costs to some negative value, so that we can detect
! 	 * when they are set to some sensible costs, during one (usually the
! 	 * first) of the calls to estimate_path_cost_size().
! 	 */
! 	fpinfo->rel_startup_cost = -1;
! 	fpinfo->rel_total_cost = -1;
! 
! 	/*
! 	 * Set the string describing this grouped relation to be used in EXPLAIN
! 	 * output of corresponding ForeignScan.
! 	 */
! 	fpinfo->relation_name = makeStringInfo();
! 	appendStringInfo(fpinfo->relation_name, "Aggregate on (%s)",
! 					 ofpinfo->relation_name->data);
! 
! 	return true;
  }
  
  /*
--- 5465,5471 ----
  	/* Transfer any sortgroupref data to the replacement tlist */
  	apply_pathtarget_labeling_to_tlist(tlist, grouping_target);
  
! 	return tlist;
  }
  
  /*
*************** add_foreign_grouping_paths(PlannerInfo *
*** 4846,4857 ****
  	Query	   *parse = root->parse;
  	PgFdwRelationInfo *ifpinfo = input_rel->fdw_private;
  	PgFdwRelationInfo *fpinfo = grouped_rel->fdw_private;
  	ForeignPath *grouppath;
  	PathTarget *grouping_target;
! 	double		rows;
! 	int			width;
! 	Cost		startup_cost;
! 	Cost		total_cost;
  
  	/* Nothing to be done, if there is no grouping or aggregation required. */
  	if (!parse->groupClause && !parse->groupingSets && !parse->hasAggs &&
--- 5514,5523 ----
  	Query	   *parse = root->parse;
  	PgFdwRelationInfo *ifpinfo = input_rel->fdw_private;
  	PgFdwRelationInfo *fpinfo = grouped_rel->fdw_private;
+ 	EstimateInfo *estimates;
  	ForeignPath *grouppath;
  	PathTarget *grouping_target;
! 	List	*fdw_private;
  
  	/* Nothing to be done, if there is no grouping or aggregation required. */
  	if (!parse->groupClause && !parse->groupingSets && !parse->hasAggs &&
*************** add_foreign_grouping_paths(PlannerInfo *
*** 4877,4902 ****
  		return;
  
  	/* Estimate the cost of push down */
! 	estimate_path_cost_size(root, grouped_rel, NIL, NIL, &rows,
! 							&width, &startup_cost, &total_cost);
  
! 	/* Now update this information in the fpinfo */
! 	fpinfo->rows = rows;
! 	fpinfo->width = width;
! 	fpinfo->startup_cost = startup_cost;
! 	fpinfo->total_cost = total_cost;
  
  	/* Create and add foreign path to the grouping relation. */
  	grouppath = create_foreignscan_path(root,
  										grouped_rel,
  										grouping_target,
! 										rows,
! 										startup_cost,
! 										total_cost,
  										NIL,	/* no pathkeys */
  										NULL,	/* no required_outer */
  										NULL,
! 										NIL);	/* no fdw_private */
  
  	/* Add generated path into grouped_rel by add_path(). */
  	add_path(grouped_rel, (Path *) grouppath, false);
--- 5543,5570 ----
  		return;
  
  	/* Estimate the cost of push down */
! 	estimates = &fpinfo->est_grouped;
! 	estimate_path_cost_size(root, grouped_rel, NIL, NIL, NIL, estimates,
! 							true);
  
! 	/*
! 	 * The path is considered grouped when it comes to plan creation and
! 	 * deparsing. The 2nd element (ORDER BY expressions) is NIL because
! 	 * UPPERREL_GROUP_AGG does not care about ordering.
! 	 */
! 	fdw_private = list_make2(makeInteger(1), NIL);
  
  	/* Create and add foreign path to the grouping relation. */
  	grouppath = create_foreignscan_path(root,
  										grouped_rel,
  										grouping_target,
! 										estimates->rows,
! 										estimates->startup_cost,
! 										estimates->total_cost,
  										NIL,	/* no pathkeys */
  										NULL,	/* no required_outer */
  										NULL,
! 										fdw_private);
  
  	/* Add generated path into grouped_rel by add_path(). */
  	add_path(grouped_rel, (Path *) grouppath, false);
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
new file mode 100644
index 788b003..e9a63a8
*** a/contrib/postgres_fdw/postgres_fdw.h
--- b/contrib/postgres_fdw/postgres_fdw.h
***************
*** 20,25 ****
--- 20,37 ----
  
  #include "libpq-fe.h"
  
+ typedef struct EstimateInfo
+ {
+ 	/* Estimated size and cost for a scan or join. */
+ 	double		rows;
+ 	int			width;
+ 	Cost		startup_cost;
+ 	Cost		total_cost;
+ 	/* Costs excluding costs for transferring data from the foreign server */
+ 	Cost		rel_startup_cost;
+ 	Cost		rel_total_cost;
+ }			EstimateInfo;
+ 
  /*
   * FDW-specific planner information kept in RelOptInfo.fdw_private for a
   * postgres_fdw foreign table.  For a baserel, this struct is created by
*************** typedef struct PgFdwRelationInfo
*** 43,48 ****
--- 55,74 ----
  	List	   *remote_conds;
  	List	   *local_conds;
  
+ 	/*
+ 	 * HAVING clauses.
+ 	 *
+ 	 * As for postgresGetForeignUpperPaths(), remote_conds / local_conds can
+ 	 * be used for the HAVING clause because the remote_conds / local_conds of
+ 	 * the input relation can be used for the WHERE clause. However, separate
+ 	 * fields are needed for postgresGetForeignJoinPaths() because remote join
+ 	 * can evaluate both WHERE and HAVING clause if it's grouped, and it does
+ 	 * not use the concept of upper path. To be consistent, we always store
+ 	 * the HAVING expressions into the following fields.
+ 	 */
+ 	List	   *remote_conds_upper;
+ 	List	   *local_conds_upper;
+ 
  	/* Actual remote restriction clauses for scan (sans RestrictInfos) */
  	List	   *final_remote_exprs;
  
*************** typedef struct PgFdwRelationInfo
*** 56,69 ****
  	/* Selectivity of join conditions */
  	Selectivity joinclause_sel;
  
! 	/* Estimated size and cost for a scan or join. */
! 	double		rows;
! 	int			width;
! 	Cost		startup_cost;
! 	Cost		total_cost;
! 	/* Costs excluding costs for transferring data from the foreign server */
! 	Cost		rel_startup_cost;
! 	Cost		rel_total_cost;
  
  	/* Options extracted from catalogs. */
  	bool		use_remote_estimate;
--- 82,90 ----
  	/* Selectivity of join conditions */
  	Selectivity joinclause_sel;
  
! 	/* Estimates for both plain and grouped relation. */
! 	EstimateInfo est_plain;
! 	EstimateInfo est_grouped;
  
  	/* Options extracted from catalogs. */
  	bool		use_remote_estimate;
*************** typedef struct PgFdwRelationInfo
*** 92,98 ****
  	/* joinclauses contains only JOIN/ON conditions for an outer join */
  	List	   *joinclauses;	/* List of RestrictInfo */
  
! 	/* Grouping information */
  	List	   *grouped_tlist;
  
  	/* Subquery information */
--- 113,121 ----
  	/* joinclauses contains only JOIN/ON conditions for an outer join */
  	List	   *joinclauses;	/* List of RestrictInfo */
  
! 	/*
! 	 * The targetlist used to construct the remote query.
! 	 */
  	List	   *grouped_tlist;
  
  	/* Subquery information */
*************** extern unsigned int GetCursorNumber(PGco
*** 121,126 ****
--- 144,150 ----
  extern unsigned int GetPrepStmtNumber(PGconn *conn);
  extern PGresult *pgfdw_get_result(PGconn *conn, const char *query);
  extern PGresult *pgfdw_exec_query(PGconn *conn, const char *query);
+ extern void pgfdw_send_query(PGconn *conn, const char *query);
  extern void pgfdw_report_error(int elevel, PGresult *res, PGconn *conn,
  				   bool clear, const char *sql);
  
*************** extern Expr *find_em_expr_for_rel(Equiva
*** 174,181 ****
  extern List *build_tlist_to_deparse(RelOptInfo *foreignrel);
  extern void deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root,
  						RelOptInfo *foreignrel, List *tlist,
! 						List *remote_conds, List *pathkeys, bool is_subquery,
! 						List **retrieved_attrs, List **params_list);
  extern const char *get_jointype_name(JoinType jointype);
  
  /* in shippable.c */
--- 198,207 ----
  extern List *build_tlist_to_deparse(RelOptInfo *foreignrel);
  extern void deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root,
  						RelOptInfo *foreignrel, List *tlist,
! 						List *remote_conds, List *order_by_exprs,
! 						List *pathkeys, bool is_subquery,
! 						List **retrieved_attrs, List **params_list,
! 						bool grouping);
  extern const char *get_jointype_name(JoinType jointype);
  
  /* in shippable.c */
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
new file mode 100644
index 3c3c5c7..3fc974f
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
*************** SELECT t1.a,t1.b FROM fprt1 t1, LATERAL
*** 1835,1837 ****
--- 1835,1902 ----
  SELECT t1.a,t1.b FROM fprt1 t1, LATERAL (SELECT t2.a, t2.b FROM fprt2 t2 WHERE t1.a = t2.b AND t1.b = t2.a) q WHERE t1.a%25 = 0 ORDER BY 1,2;
  
  RESET enable_partition_wise_join;
+ 
+ -- ===================================================================
+ -- test aggregation of base relation or join
+ -- ===================================================================
+ CREATE TABLE customers(id int primary key, name varchar);
+ INSERT INTO customers VALUES (0, 'me'), (1, 'you'), (2, 'he'), (3, 'she');
+ CREATE TABLE products(id int, price float4) PARTITION BY RANGE (id);
+ CREATE TABLE products_0 (LIKE products);
+ INSERT INTO products_0(id, price) SELECT g.i, trunc(100 * random()) /
+ 10 + 1 FROM generate_series(1, 1000) AS g(i);
+ CREATE TABLE products_1 (LIKE products);
+ INSERT INTO products_1(id, price) SELECT g.i, trunc(100 * random()) /
+ 10 + 1 FROM generate_series(1001, 2000) AS g(i);
+ CREATE TABLE orders(customer_id int, product_id int) PARTITION BY RANGE
+ (product_id);
+ CREATE TABLE orders_0 (LIKE orders);
+ INSERT INTO orders_0(customer_id, product_id) SELECT trunc(2 * random()),
+ trunc(1000 * random()) FROM generate_series(1, 5000);
+ CREATE TABLE orders_1 (LIKE orders);
+ INSERT INTO orders_1(customer_id, product_id)
+ SELECT trunc(2 * random()) + 2, trunc(1000 * random()) + 1001 FROM
+ generate_series(1, 5000);
+ CREATE FOREIGN TABLE f_orders_0 PARTITION OF orders FOR VALUES FROM (1) TO
+ (1001) SERVER loopback OPTIONS (table_name 'orders_0');
+ CREATE FOREIGN TABLE f_orders_1 PARTITION OF orders FOR VALUES FROM (1001) TO
+ (2001) SERVER loopback OPTIONS (table_name 'orders_1');
+ CREATE FOREIGN TABLE f_products_0 PARTITION OF products FOR VALUES FROM (1) TO
+ (1001) SERVER loopback OPTIONS (table_name 'products_0');
+ CREATE FOREIGN TABLE f_products_1 PARTITION OF products FOR VALUES FROM (1001)
+ TO (2001) SERVER loopback OPTIONS (table_name 'products_1');
+ ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'true');
+ ALTER SERVER loopback2 OPTIONS (ADD use_remote_estimate 'true');
+ ANALYZE;
+ 
+ SET enable_agg_pushdown TO on;
+ SET enable_partition_wise_join TO on;
+ 
+ -- Perform the aggregation on partitions of "orders" , join the result to the
+ -- "customers" local table and finalize the aggregation.
+ EXPLAIN (VERBOSE on, COSTS off)
+ SELECT          c.name, count(*)
+ FROM            customers AS c, orders AS o
+ WHERE           c.id = o.customer_id
+ GROUP BY        c.id;
+ 
+ -- Perform partition-wise join of "orders" and "products", aggregate the
+ -- result at partition level, append the outputs of partitions and join it to
+ -- the "customers" local table.
+ EXPLAIN (VERBOSE on, COSTS off)
+ SELECT          p.id, c.name, sum(p.price)
+ FROM            customers AS c, orders AS o, products AS p
+ WHERE           c.id = o.customer_id AND p.id = o.product_id
+ GROUP BY        p.id, c.id
+ ORDER BY	3;
+ 
+ -- The final aggregation is not necessary if the aggregated output of
+ -- partitions contains distinct set of grouping keys. Moreover, skip sort of
+ -- the result if the output of per-partition queries has the desired ordering
+ -- and so the sets can be merge-appended.
+ EXPLAIN (VERBOSE on, COSTS off)
+ SELECT          p.id, sum(p.price)
+ FROM            orders AS o, products AS p
+ WHERE           p.id = o.product_id
+ GROUP BY        p.id
+ ORDER BY	2 DESC;
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
new file mode 100644
index f09647e..734940a
*** a/src/backend/executor/execExpr.c
--- b/src/backend/executor/execExpr.c
*************** ExecInitExprRec(Expr *node, ExprState *s
*** 809,815 ****
  				 * there.)
  				 */
  				if (IsA(gvar->gvexpr, Aggref))
- 
  					ExecInitExprRec((Expr *) gvar->agg_partial, state,
  									resv, resnull);
  				else
--- 809,814 ----
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
new file mode 100644
index f95c913..3b709a3
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** set_tablesample_rel_pathlist(PlannerInfo
*** 986,996 ****
  static void
  set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
  {
  	/* Mark rel with estimated output rows, width, etc */
! 	set_foreign_size_estimates(root, rel);
  
! 	/* Let FDW adjust the size estimates, if it can */
! 	rel->fdwroutine->GetForeignRelSize(root, rel, rte->relid);
  
  	/* ... but do not let it set the rows estimate to zero */
  	rel->rows = clamp_row_est(rel->rows);
--- 986,1014 ----
  static void
  set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
  {
+ 	Relids		required_outer = rel->lateral_relids;
+ 
  	/* Mark rel with estimated output rows, width, etc */
! 	set_foreign_size_estimates(root, rel, false);
  
! 	/*
! 	 * Let FDW adjust the size estimates, if it can.
! 	 *
! 	 * For the grouped relation it only makes sense if set_foreign_pathlist is
! 	 * supposed to handle it, see below.
! 	 */
! 	rel->fdwroutine->GetForeignRelSize(root, rel, rte->relid, false);
! 	if (rel->gpi != NULL && rel->gpi->target != NULL &&
! 		required_outer == NULL)
! 	{
! 		rel->fdwroutine->GetForeignRelSize(root, rel, rte->relid, true);
! 
! 		/*
! 		 * XXX Should cost_qual_eval be called separate so we don't repeat it
! 		 * here for essentially identical baserestrictinfo?
! 		 */
! 		set_foreign_size_estimates(root, rel, true);
! 	}
  
  	/* ... but do not let it set the rows estimate to zero */
  	rel->rows = clamp_row_est(rel->rows);
*************** set_foreign_size(PlannerInfo *root, RelO
*** 1003,1010 ****
  static void
  set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
  {
  	/* Call the FDW's GetForeignPaths function to generate path(s) */
! 	rel->fdwroutine->GetForeignPaths(root, rel, rte->relid);
  }
  
  /*
--- 1021,1038 ----
  static void
  set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
  {
+ 	Relids		required_outer = rel->lateral_relids;
+ 
  	/* Call the FDW's GetForeignPaths function to generate path(s) */
! 	rel->fdwroutine->GetForeignPaths(root, rel, rte->relid, false);
! 
! 	/*
! 	 * Create grouped paths if grouped target exists. Do nothing if outer
! 	 * relations are needed --- that could cause parameterized (and therefore
! 	 * repeated) grouping.
! 	 */
! 	if (rel->gpi != NULL && rel->gpi->target && required_outer == NULL)
! 		rel->fdwroutine->GetForeignPaths(root, rel, rte->relid, true);
  }
  
  /*
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1586,1591 ****
--- 1614,1620 ----
  	foreach(l, live_childrels)
  	{
  		RelOptInfo *childrel = lfirst(l);
+ 		List	   *childpaths;
  		ListCell   *lcp;
  		Path	   *cheapest_partial_path = NULL;
  
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1698,1704 ****
  		 * heuristic to indicate which sort orderings and parameterizations we
  		 * should build Append and MergeAppend paths for.
  		 */
! 		foreach(lcp, childrel->pathlist)
  		{
  			Path	   *childpath = (Path *) lfirst(lcp);
  			List	   *childkeys = childpath->pathkeys;
--- 1727,1743 ----
  		 * heuristic to indicate which sort orderings and parameterizations we
  		 * should build Append and MergeAppend paths for.
  		 */
! 		childpaths = childrel->pathlist;
! 
! 		/*
! 		 * Extra orderings may be available for grouped paths, i.e. ordered by
! 		 * aggregate. (At least ForeignPath can generate these.)
! 		 */
! 		if (childrel->gpi != NULL && childrel->gpi->pathlist != NIL)
! 			childpaths = list_concat(list_copy(childpaths),
! 									 childrel->gpi->pathlist);
! 
! 		foreach(lcp, childpaths)
  		{
  			Path	   *childpath = (Path *) lfirst(lcp);
  			List	   *childkeys = childpath->pathkeys;
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
new file mode 100644
index 9f489c3..f28f5fc
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
*************** set_namedtuplestore_size_estimates(Plann
*** 5037,5043 ****
   * already.
   */
  void
! set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel)
  {
  	/* Should only be applied to base relations */
  	Assert(rel->relid > 0);
--- 5037,5043 ----
   * already.
   */
  void
! set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel, bool grouped)
  {
  	/* Should only be applied to base relations */
  	Assert(rel->relid > 0);
*************** set_foreign_size_estimates(PlannerInfo *
*** 5046,5052 ****
  
  	cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
  
! 	set_rel_width(root, rel, rel->reltarget, false);
  }
  
  
--- 5046,5064 ----
  
  	cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
  
! 	if (!grouped)
! 		set_rel_width(root, rel, rel->reltarget, false);
! 	else
! 	{
! 		Assert(rel->gpi != NULL);
! 
! 		/*
! 		 * If the relation can produce grouped path, estimate width and costs
! 		 * of the corresponding target.
! 		 */
! 		if (rel->gpi->target)
! 			set_rel_width(root, rel, rel->gpi->target, true);
! 	}
  }
  
  
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
new file mode 100644
index 883d8a1..a678bc2
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 343,351 ****
  	 */
  	if (joinrel->fdwroutine &&
  		joinrel->fdwroutine->GetForeignJoinPaths)
  		joinrel->fdwroutine->GetForeignJoinPaths(root, joinrel,
  												 outerrel, innerrel,
! 												 jointype, &extra);
  
  	/*
  	 * 6. Finally, give extensions a chance to manipulate the path list.
--- 343,357 ----
  	 */
  	if (joinrel->fdwroutine &&
  		joinrel->fdwroutine->GetForeignJoinPaths)
+ 	{
  		joinrel->fdwroutine->GetForeignJoinPaths(root, joinrel,
  												 outerrel, innerrel,
! 												 jointype, &extra, false);
! 		if (joinrel->gpi != NULL && joinrel->gpi->target != NULL)
! 			joinrel->fdwroutine->GetForeignJoinPaths(root, joinrel,
! 													 outerrel, innerrel,
! 													 jointype, &extra, true);
! 	}
  
  	/*
  	 * 6. Finally, give extensions a chance to manipulate the path list.
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
new file mode 100644
index b11afd9..ba63830
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
*************** static Plan *prepare_sort_from_pathkeys(
*** 251,256 ****
--- 251,257 ----
  static EquivalenceMember *find_ec_member_for_tle(EquivalenceClass *ec,
  					   TargetEntry *tle,
  					   Relids relids);
+ static TargetEntry *get_aggref_from_tle(TargetEntry *tle);
  static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
  						Relids relids);
  static Sort *make_sort_from_groupcols(List *groupcls,
*************** prepare_sort_from_pathkeys(Plan *lefttre
*** 5655,5660 ****
--- 5656,5664 ----
  			tle = get_tle_by_resno(tlist, reqColIdx[numsortkeys]);
  			if (tle)
  			{
+ 				/* Handle special cases of sorting by aggregate. */
+ 				tle = get_aggref_from_tle(tle);
+ 
  				em = find_ec_member_for_tle(ec, tle, relids);
  				if (em)
  				{
*************** prepare_sort_from_pathkeys(Plan *lefttre
*** 5686,5691 ****
--- 5690,5698 ----
  			foreach(j, tlist)
  			{
  				tle = (TargetEntry *) lfirst(j);
+ 
+ 				/* Handle special cases of sorting by aggregate. */
+ 				tle = get_aggref_from_tle(tle);
  				em = find_ec_member_for_tle(ec, tle, relids);
  				if (em)
  				{
*************** find_ec_member_for_tle(EquivalenceClass
*** 5859,5864 ****
--- 5866,5893 ----
  }
  
  /*
+  * Get Aggref from target entry if it's wrapped in GroupedVar.
+  */
+ static TargetEntry *
+ get_aggref_from_tle(TargetEntry *tle)
+ {
+ 	if (IsA(tle->expr, GroupedVar))
+ 	{
+ 		GroupedVar *gvar = castNode(GroupedVar, tle->expr);
+ 
+ 		if (IsA(gvar->gvexpr, Aggref))
+ 		{
+ 			Aggref	   *aggref = castNode(Aggref, gvar->gvexpr);
+ 
+ 			tle = flatCopyTargetEntry(tle);
+ 			tle->expr = (Expr *) aggref;
+ 		}
+ 	}
+ 
+ 	return tle;
+ }
+ 
+ /*
   * make_sort_from_pathkeys
   *	  Create sort plan to sort according to given pathkeys
   *
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
new file mode 100644
index 502c725..02c6c61
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
*************** set_foreignscan_references(PlannerInfo *
*** 1199,1228 ****
  
  	if (fscan->fdw_scan_tlist != NIL || fscan->scan.scanrelid == 0)
  	{
  		/*
  		 * Adjust tlist, qual, fdw_exprs, fdw_recheck_quals to reference
! 		 * foreign scan tuple
  		 */
! 		indexed_tlist *itlist = build_tlist_index(fscan->fdw_scan_tlist);
! 
  		fscan->scan.plan.targetlist = (List *)
  			fix_upper_expr(root,
! 						   (Node *) fscan->scan.plan.targetlist,
  						   itlist,
  						   INDEX_VAR,
  						   rtoffset);
  		fscan->scan.plan.qual = (List *)
  			fix_upper_expr(root,
  						   (Node *) fscan->scan.plan.qual,
  						   itlist,
  						   INDEX_VAR,
  						   rtoffset);
  		fscan->fdw_exprs = (List *)
  			fix_upper_expr(root,
  						   (Node *) fscan->fdw_exprs,
  						   itlist,
  						   INDEX_VAR,
  						   rtoffset);
  		fscan->fdw_recheck_quals = (List *)
  			fix_upper_expr(root,
  						   (Node *) fscan->fdw_recheck_quals,
--- 1199,1242 ----
  
  	if (fscan->fdw_scan_tlist != NIL || fscan->scan.scanrelid == 0)
  	{
+ 		List	   *tlist_tmp;
+ 		indexed_tlist *itlist = build_tlist_index(fscan->fdw_scan_tlist);
+ 
  		/*
  		 * Adjust tlist, qual, fdw_exprs, fdw_recheck_quals to reference
! 		 * foreign scan tuple.
! 		 *
! 		 * Since fdw_scan_tlist contains Aggrefs instead of GroupVars (this is
! 		 * convenient for deparsing), the Aggrefs also need to be restored in
! 		 * the referencing lists.
! 		 *
! 		 * XXX Consider thoroughly where tlist_tmp really needs to be used,
! 		 * i.e. which of the lists can / cannot contain aggregates.
  		 */
! 		tlist_tmp =
! 			replace_grouped_vars_with_aggrefs(root,
! 											  fscan->scan.plan.targetlist);
  		fscan->scan.plan.targetlist = (List *)
  			fix_upper_expr(root,
! 						   (Node *) tlist_tmp,
  						   itlist,
  						   INDEX_VAR,
  						   rtoffset);
+ 
  		fscan->scan.plan.qual = (List *)
  			fix_upper_expr(root,
  						   (Node *) fscan->scan.plan.qual,
  						   itlist,
  						   INDEX_VAR,
  						   rtoffset);
+ 
  		fscan->fdw_exprs = (List *)
  			fix_upper_expr(root,
  						   (Node *) fscan->fdw_exprs,
  						   itlist,
  						   INDEX_VAR,
  						   rtoffset);
+ 
  		fscan->fdw_recheck_quals = (List *)
  			fix_upper_expr(root,
  						   (Node *) fscan->fdw_recheck_quals,
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
new file mode 100644
index 04e43cc..16d6082
*** a/src/include/foreign/fdwapi.h
--- b/src/include/foreign/fdwapi.h
*************** struct ExplainState;
*** 26,36 ****
  
  typedef void (*GetForeignRelSize_function) (PlannerInfo *root,
  											RelOptInfo *baserel,
! 											Oid foreigntableid);
  
  typedef void (*GetForeignPaths_function) (PlannerInfo *root,
  										  RelOptInfo *baserel,
! 										  Oid foreigntableid);
  
  typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
  												 RelOptInfo *baserel,
--- 26,38 ----
  
  typedef void (*GetForeignRelSize_function) (PlannerInfo *root,
  											RelOptInfo *baserel,
! 											Oid foreigntableid,
! 											bool grouped);
  
  typedef void (*GetForeignPaths_function) (PlannerInfo *root,
  										  RelOptInfo *baserel,
! 										  Oid foreigntableid,
! 										  bool grouped);
  
  typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
  												 RelOptInfo *baserel,
*************** typedef void (*GetForeignJoinPaths_funct
*** 57,63 ****
  											  RelOptInfo *outerrel,
  											  RelOptInfo *innerrel,
  											  JoinType jointype,
! 											  JoinPathExtraData *extra);
  
  typedef void (*GetForeignUpperPaths_function) (PlannerInfo *root,
  											   UpperRelationKind stage,
--- 59,66 ----
  											  RelOptInfo *outerrel,
  											  RelOptInfo *innerrel,
  											  JoinType jointype,
! 											  JoinPathExtraData *extra,
! 											  bool grouped);
  
  typedef void (*GetForeignUpperPaths_function) (PlannerInfo *root,
  											   UpperRelationKind stage,
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
new file mode 100644
index 12d8134..9956e0f
*** a/src/include/optimizer/cost.h
--- b/src/include/optimizer/cost.h
*************** extern void set_cte_size_estimates(Plann
*** 194,200 ****
  					   double cte_rows);
  extern void set_tablefunc_size_estimates(PlannerInfo *root, RelOptInfo *rel);
  extern void set_namedtuplestore_size_estimates(PlannerInfo *root, RelOptInfo *rel);
! extern void set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel);
  extern PathTarget *set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target);
  extern double compute_bitmap_pages(PlannerInfo *root, RelOptInfo *baserel,
  					 Path *bitmapqual, int loop_count, Cost *cost, double *tuple);
--- 194,201 ----
  					   double cte_rows);
  extern void set_tablefunc_size_estimates(PlannerInfo *root, RelOptInfo *rel);
  extern void set_namedtuplestore_size_estimates(PlannerInfo *root, RelOptInfo *rel);
! extern void set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel,
! 						   bool grouped);
  extern PathTarget *set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target);
  extern double compute_bitmap_pages(PlannerInfo *root, RelOptInfo *baserel,
  					 Path *bitmapqual, int loop_count, Cost *cost, double *tuple);


  agg_pushdown_v5/11_readme.diff
diff --git a/src/backend/optimizer/README b/src/backend/optimizer/README
new file mode 100644
index 1e4084d..3994394
*** a/src/backend/optimizer/README
--- b/src/backend/optimizer/README
*************** plan as possible.  Expanding the range o
*** 1076,1081 ****
--- 1076,1116 ----
  pushed below the Gather (and costing them accurately) is likely to keep us
  busy for a long time to come.
  
+ Grouped Paths
+ -------------
+ 
+ If both query semantics and path type allow, an existing path can be subject
+ to partial aggregation (ie aggregation without calling the aggfinalfn
+ function) and stored as a "grouped path" in a separate pathlist. Both base
+ relation and join paths can be turned into a grouped path this way. If the
+ source path was partial (in terms of parallel processing, see above), then the
+ grouped path is called "partial grouped path".
+ 
+ If a grouped path appears at the top of the join tree, the transient state
+ values are aggregated and the aggfinalfn function is called for each
+ aggregate. Likewise, if the top-level join produces a partial grouped path, we
+ first apply Gather plan on it and then we finalize the aggregation in the same
+ way.
+ 
+ Besides being a result of aggregation, (partial) grouped path can be created
+ by joining an existing grouped path to an ordinary (non-grouped) path. This
+ technique can result in duplication of grouping keys, which are essentially
+ unique in the output of aggregation. The final aggregation takes these
+ duplicities into account.
+ 
+ In contrast, join of two grouped paths is not supported as there doesn't seem
+ to be an interesting use case for it. If we want to support this kind of join
+ in the future, additional attention needs to be paid to the duplication of
+ grouping keys mentioned above. For example, if scan of table A is joined to a
+ grouped scan of table B, then the cardinality of grouping keys of table B
+ present in (non-grouped) table A determines how many times does each
+ aggregation transient state value of B appear on the input of the final
+ aggregation path. However if A is grouped too, the number of those key values
+ on the A side can get lower and thus affect the input of the final aggregation
+ path. When compensating for this effect, we'd have to count input values of
+ each group during the aggregation of A and "multiply" the corresponding
+ transient state values of B accordingly.
+ 
  Partition-wise joins
  --------------------
  A join between two similarly partitioned tables can be broken down into joins


view thread (59+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected]
  Subject: Re: [HACKERS] WIP: Aggregation push-down
  In-Reply-To: <18007.1513957437@localhost>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox