diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index 930f2f1..59836ba
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyAggref(const Aggref *from)
*** 1245,1250 ****
--- 1245,1251 ----
  	COPY_SCALAR_FIELD(aggstar);
  	COPY_SCALAR_FIELD(aggvariadic);
  	COPY_SCALAR_FIELD(aggkind);
+ 	COPY_SCALAR_FIELD(aggtransmultifn);
  	COPY_SCALAR_FIELD(agglevelsup);
  	COPY_SCALAR_FIELD(aggsplit);
  	COPY_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index 806d0a9..8e789fe
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outPlannerInfo(StringInfo str, const Pl
*** 2046,2051 ****
--- 2046,2053 ----
  	WRITE_NODE_FIELD(append_rel_list);
  	WRITE_NODE_FIELD(rowMarks);
  	WRITE_NODE_FIELD(placeholder_list);
+ 	WRITE_NODE_FIELD(grouped_var_list);
+ 	WRITE_BOOL_FIELD(all_baserels_grouped);
  	WRITE_NODE_FIELD(fkey_list);
  	WRITE_NODE_FIELD(query_pathkeys);
  	WRITE_NODE_FIELD(group_pathkeys);
diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c
new file mode 100644
index b5cab0c..f89406d
*** a/src/backend/optimizer/geqo/geqo_eval.c
--- b/src/backend/optimizer/geqo/geqo_eval.c
*************** merge_clump(PlannerInfo *root, List *clu
*** 265,271 ****
  			if (joinrel)
  			{
  				/* Create GatherPaths for any useful partial paths for rel */
! 				generate_gather_paths(root, joinrel);
  
  				/* Find and save the cheapest paths for this joinrel */
  				set_cheapest(joinrel);
--- 265,271 ----
  			if (joinrel)
  			{
  				/* Create GatherPaths for any useful partial paths for rel */
! 				generate_gather_paths(root, joinrel, false);
  
  				/* Find and save the cheapest paths for this joinrel */
  				set_cheapest(joinrel);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
new file mode 100644
index 46d7d06..0ae0a1b
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
***************
*** 24,29 ****
--- 24,30 ----
  #include "catalog/pg_operator.h"
  #include "catalog/pg_proc.h"
  #include "foreign/fdwapi.h"
+ #include "miscadmin.h"
  #include "nodes/makefuncs.h"
  #include "nodes/nodeFuncs.h"
  #ifdef OPTIMIZER_DEBUG
***************
*** 44,49 ****
--- 45,51 ----
  #include "parser/parsetree.h"
  #include "rewrite/rewriteManip.h"
  #include "utils/lsyscache.h"
+ #include "utils/selfuncs.h"
  
  
  /* results of subquery_is_pushdown_safe */
*************** static void set_rel_pathlist(PlannerInfo
*** 76,81 ****
--- 78,85 ----
  static void set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel,
  				   RangeTblEntry *rte);
  static void create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel);
+ static void create_plain_grouped_path(PlannerInfo *root, RelOptInfo *rel,
+ 									  Path *subpath);
  static void set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
  						  RangeTblEntry *rte);
  static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
*************** set_rel_pathlist(PlannerInfo *root, RelO
*** 457,463 ****
  	 * 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
--- 461,470 ----
  	 * 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
*************** set_plain_rel_pathlist(PlannerInfo *root
*** 656,662 ****
  	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)
--- 663,669 ----
  	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)
*************** static void
*** 677,682 ****
--- 684,690 ----
  create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel)
  {
  	int			parallel_workers;
+ 	Path	   *path;
  
  	/*
  	 * If the user has set the parallel_workers reloption, use that; otherwise
*************** 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));
  }
  
  /*
--- 735,888 ----
  		return;
  
  	/* Add an unordered partial path based on a parallel sequential scan. */
! 	path = create_seqscan_path(root, rel, NULL, parallel_workers);
! 	add_partial_path(rel, path, false);
! 
! 	/*
! 	 * Do partial aggregation at base relation level if the relation is
! 	 * eligible for it.
! 	 */
! 	if (rel->reltarget_grouped)
! 		create_plain_grouped_path(root, rel, path);
! }
! 
! /*
!  * Apply (partial) aggregation to partial subpath.
!  *
!  * As we modify the subpath here, copy is taken before we adjust it.
!  */
! static void
! create_plain_grouped_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath)
! {
! 	Query	   *parse = root->parse;
! 	ListCell	*lc;
! 	Expr		*texpr;
! 	AggClauseCosts  agg_costs;
! 	AggPath		*agg_path;
! 	List    *group_clause = NIL;
! 	List	*group_exprs = NIL;
! 	List	*agg_exprs = NIL;
! 	bool	can_hash;
! 	Size	hashaggtablesize;
! 	int		i;
! 	double	dNumGroups;
! 	Path	*subpath_tmp;
! 	PathTarget	*target = rel->reltarget_grouped;
! 
! 	/* Copy the subpath before doing changes. */
! 	subpath_tmp = makeNode(Path);
! 	memcpy(subpath_tmp, subpath, sizeof(Path));
! 	subpath = subpath_tmp;
! 
! 	/* Apply the specific target. */
! 	subpath->pathtarget = target;
! 
! 	/*
! 	 * Find one grouping clause per grouping column.
! 	 *
! 	 * The "grouped target" should contain grouping expressions, and these
! 	 * should have non-zero sortgroupref.
! 	 */
! 	Assert(target->sortgrouprefs != NULL);
! 
! 	i = 0;
! 	foreach(lc, target->exprs)
! 	{
! 		Index	sortgroupref;
! 		SortGroupClause *cl;
! 
! 		texpr = (Expr *) lfirst(lc);
! 		sortgroupref = target->sortgrouprefs[i++];
! 
! 		if (sortgroupref == 0)
! 		{
! 			/*
! 			 * Looks like "grouped var" representing Aggref - these should
! 			 * appear at the end of the list.
! 			 */
! 			break;
! 		}
! 
! 		/*
! 		 * Find the clause by sortgroupref.
! 		 *
! 		 * 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.
! 		 */
! 		cl = get_sortgroupref_clause(sortgroupref, root->parse->groupClause);
! 		group_clause = lappend(group_clause, cl);
! 		group_exprs = lappend(group_exprs, texpr);
! 	}
! 
! 	/* Now collect the aggregates. */
! 	while (lc != NULL)
! 	{
! 		texpr = (Expr *) lfirst(lc);
! 
! 		/*
! 		 * texpr still contains the replacement var, so restore the actual
! 		 * Aggref expression.
! 		 */
! 		Assert(IsA(texpr, Var));
! 		agg_exprs = lappend(agg_exprs, find_grouped_var_expr(root,
! 															 (Var *) texpr));
! 		lc = lnext(lc);
! 	}
! 
! 	Assert(agg_exprs != NIL);
! 	MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
! 	get_agg_clause_costs(root, (Node *) agg_exprs, AGGSPLIT_INITIAL_SERIAL,
! 						 &agg_costs);
! 
! 	can_hash = (parse->groupClause != NIL &&
! 				parse->groupingSets == NIL &&
! 				agg_costs.numOrderedAggs == 0 &&
! 				grouping_is_hashable(parse->groupClause));
! 
! 	/* TODO Consider other kinds of aggregation. */
! 	if (!can_hash)
! 		return;
! 
! 	Assert(group_exprs != NIL);
! 	dNumGroups = estimate_num_groups(root, group_exprs, subpath->rows, NULL);
! 
! 	hashaggtablesize = estimate_hashagg_tablesize(subpath, &agg_costs,
! 												  dNumGroups);
! 
! 	if (hashaggtablesize < work_mem * 1024L)
! 	{
! 		/*
! 		 * Create the partial aggregation path.
! 		 *
! 		 * Note that target contains "grouped vars" (see GroupedVarInfo)
! 		 * instead of Aggref expressions so far. Thus set_upper_references can
! 		 * easily link TLEs of the upper node (which is not necessarily the
! 		 * final Agg node) to the output of the patial Agg plan. Once
! 		 * descended to this partial Agg node, set_upper_references will
! 		 * install the Aggref expressions.
! 		 */
! 		Assert(group_clause != NIL);
! 		agg_path = create_agg_path(root,
! 								   rel,
! 								   subpath,
! 								   target,
! 								   AGG_HASHED,
! 								   AGGSPLIT_INITIAL_SERIAL,
! 								   group_clause,
! 								   NIL,
! 								   &agg_costs,
! 								   dNumGroups);
! 
! 		/*
! 		 * The agg path should require no fewer parameters than the plain one.
! 		 */
! 		agg_path->path.param_info = subpath->param_info;
! 
! 		/* Finally add the grouped path to the list of grouped base paths. */
! 		add_partial_path(rel, (Path *) agg_path, true);
! 	}
  }
  
  /*
*************** 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 */
  }
--- 968,974 ----
  		path = (Path *) create_material_path(rel, path);
  	}
  
! 	add_path(rel, path, false);
  
  	/* For the moment, at least, there are no other paths to consider */
  }
*************** set_append_rel_pathlist(PlannerInfo *roo
*** 1267,1273 ****
  	 * if we have zero or one live subpath due to constraint exclusion.)
  	 */
  	if (subpaths_valid)
! 		add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0));
  
  	/*
  	 * Consider an append of partial unordered, unparameterized partial paths.
--- 1422,1429 ----
  	 * if we have zero or one live subpath due to constraint exclusion.)
  	 */
  	if (subpaths_valid)
! 		add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0),
! 				 false);
  
  	/*
  	 * Consider an append of partial unordered, unparameterized partial paths.
*************** set_append_rel_pathlist(PlannerInfo *roo
*** 1295,1301 ****
  		/* Generate a partial append path. */
  		appendpath = create_append_path(rel, partial_subpaths, NULL,
  										parallel_workers);
! 		add_partial_path(rel, (Path *) appendpath);
  	}
  
  	/*
--- 1451,1457 ----
  		/* Generate a partial append path. */
  		appendpath = create_append_path(rel, partial_subpaths, NULL,
  										parallel_workers);
! 		add_partial_path(rel, (Path *) appendpath, false);
  	}
  
  	/*
*************** set_append_rel_pathlist(PlannerInfo *roo
*** 1346,1352 ****
  
  		if (subpaths_valid)
  			add_path(rel, (Path *)
! 					 create_append_path(rel, subpaths, required_outer, 0));
  	}
  }
  
--- 1502,1509 ----
  
  		if (subpaths_valid)
  			add_path(rel, (Path *)
! 					 create_append_path(rel, subpaths, required_outer, 0),
! 					 false);
  	}
  }
  
*************** generate_mergeappend_paths(PlannerInfo *
*** 1438,1450 ****
  														rel,
  														startup_subpaths,
  														pathkeys,
! 														NULL));
  		if (startup_neq_total)
  			add_path(rel, (Path *) create_merge_append_path(root,
  															rel,
  															total_subpaths,
  															pathkeys,
! 															NULL));
  	}
  }
  
--- 1595,1607 ----
  														rel,
  														startup_subpaths,
  														pathkeys,
! 														NULL), false);
  		if (startup_neq_total)
  			add_path(rel, (Path *) create_merge_append_path(root,
  															rel,
  															total_subpaths,
  															pathkeys,
! 															NULL), false);
  	}
  }
  
*************** set_dummy_rel_pathlist(RelOptInfo *rel)
*** 1576,1582 ****
  	rel->pathlist = NIL;
  	rel->partial_pathlist = NIL;
  
! 	add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0));
  
  	/*
  	 * We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
--- 1733,1739 ----
  	rel->pathlist = NIL;
  	rel->partial_pathlist = NIL;
  
! 	add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0), false);
  
  	/*
  	 * We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
*************** set_subquery_pathlist(PlannerInfo *root,
*** 1789,1795 ****
  		/* Generate outer path using this subpath */
  		add_path(rel, (Path *)
  				 create_subqueryscan_path(root, rel, subpath,
! 										  pathkeys, required_outer));
  	}
  }
  
--- 1946,1952 ----
  		/* 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,
*** 1858,1864 ****
  
  	/* Generate appropriate path */
  	add_path(rel, create_functionscan_path(root, rel,
! 										   pathkeys, required_outer));
  }
  
  /*
--- 2015,2021 ----
  
  	/* Generate appropriate path */
  	add_path(rel, create_functionscan_path(root, rel,
! 										   pathkeys, required_outer), false);
  }
  
  /*
*************** set_values_pathlist(PlannerInfo *root, R
*** 1878,1884 ****
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_valuesscan_path(root, rel, required_outer));
  }
  
  /*
--- 2035,2041 ----
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_valuesscan_path(root, rel, required_outer), false);
  }
  
  /*
*************** set_cte_pathlist(PlannerInfo *root, RelO
*** 1944,1950 ****
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_ctescan_path(root, rel, required_outer));
  }
  
  /*
--- 2101,2107 ----
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_ctescan_path(root, rel, required_outer), false);
  }
  
  /*
*************** set_worktable_pathlist(PlannerInfo *root
*** 1994,2000 ****
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_worktablescan_path(root, rel, required_outer));
  }
  
  /*
--- 2151,2158 ----
  	required_outer = rel->lateral_relids;
  
  	/* Generate appropriate path */
! 	add_path(rel, create_worktablescan_path(root, rel, required_outer),
! 			 false);
  }
  
  /*
*************** set_worktable_pathlist(PlannerInfo *root
*** 2007,2019 ****
   * path that some GatherPath has a reference to.)
   */
  void
! generate_gather_paths(PlannerInfo *root, RelOptInfo *rel)
  {
  	Path	   *cheapest_partial_path;
  	Path	   *simple_gather_path;
  
  	/* If there are no partial paths, there's nothing to do here. */
! 	if (rel->partial_pathlist == NIL)
  		return;
  
  	/*
--- 2165,2182 ----
   * path that some GatherPath has a reference to.)
   */
  void
! generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool grouped)
  {
  	Path	   *cheapest_partial_path;
  	Path	   *simple_gather_path;
+ 	List	   *pathlist;
+ 	PathTarget *partial_target;
+ 
+ 	pathlist = !grouped ? rel->partial_pathlist :
+ 		rel->partial_grouped_pathlist;
  
  	/* If there are no partial paths, there's nothing to do here. */
! 	if (pathlist == NIL)
  		return;
  
  	/*
*************** generate_gather_paths(PlannerInfo *root,
*** 2027,2037 ****
  	 * could usefully generate such a path from each partial path that has
  	 * non-NIL pathkeys.
  	 */
! 	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);
  }
  
  /*
--- 2190,2203 ----
  	 * could usefully generate such a path from each partial path that has
  	 * non-NIL pathkeys.
  	 */
! 	cheapest_partial_path = linitial(pathlist);
! 
! 	partial_target = !grouped ? rel->reltarget : rel->reltarget_grouped;
! 
  	simple_gather_path = (Path *)
! 		create_gather_path(root, rel, cheapest_partial_path, partial_target,
  						   NULL, NULL);
! 	add_path(rel, simple_gather_path, grouped);
  }
  
  /*
*************** standard_join_search(PlannerInfo *root,
*** 2196,2202 ****
  			rel = (RelOptInfo *) lfirst(lc);
  
  			/* Create GatherPaths for any useful partial paths for rel */
! 			generate_gather_paths(root, rel);
  
  			/* Find and save the cheapest paths for this rel */
  			set_cheapest(rel);
--- 2362,2368 ----
  			rel = (RelOptInfo *) lfirst(lc);
  
  			/* Create GatherPaths for any useful partial paths for rel */
! 			generate_gather_paths(root, rel, false);
  
  			/* Find and save the cheapest paths for this rel */
  			set_cheapest(rel);
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
new file mode 100644
index 7b43c4a..7c44938
*** 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);
! 		add_path(rel, (Path *) bpath);
  	}
  
  	/*
--- 338,344 ----
  		bitmapqual = choose_bitmap_and(root, rel, bitindexpaths);
  		bpath = create_bitmap_heap_path(root, rel, bitmapqual,
  										rel->lateral_relids, 1.0);
! 		add_path(rel, (Path *) bpath, false);
  	}
  
  	/*
*************** create_index_paths(PlannerInfo *root, Re
*** 411,417 ****
  			loop_count = get_loop_count(root, rel->relid, required_outer);
  			bpath = create_bitmap_heap_path(root, rel, bitmapqual,
  											required_outer, loop_count);
! 			add_path(rel, (Path *) bpath);
  		}
  	}
  }
--- 411,417 ----
  			loop_count = get_loop_count(root, rel->relid, required_outer);
  			bpath = create_bitmap_heap_path(root, rel, bitmapqual,
  											required_outer, loop_count);
! 			add_path(rel, (Path *) bpath, false);
  		}
  	}
  }
*************** get_index_paths(PlannerInfo *root, RelOp
*** 785,791 ****
  		IndexPath  *ipath = (IndexPath *) lfirst(lc);
  
  		if (index->amhasgettuple)
! 			add_path(rel, (Path *) ipath);
  
  		if (index->amhasgetbitmap &&
  			(ipath->path.pathkeys == NIL ||
--- 785,791 ----
  		IndexPath  *ipath = (IndexPath *) lfirst(lc);
  
  		if (index->amhasgettuple)
! 			add_path(rel, (Path *) ipath, false);
  
  		if (index->amhasgetbitmap &&
  			(ipath->path.pathkeys == NIL ||
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
new file mode 100644
index 7c30ec6..45318c8
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
*************** static void consider_parallel_nestloop(P
*** 39,48 ****
  						   RelOptInfo *outerrel,
  						   RelOptInfo *innerrel,
  						   JoinType jointype,
! 						   JoinPathExtraData *extra);
  static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
  					 RelOptInfo *outerrel, RelOptInfo *innerrel,
! 					 JoinType jointype, JoinPathExtraData *extra);
  static List *select_mergejoin_clauses(PlannerInfo *root,
  						 RelOptInfo *joinrel,
  						 RelOptInfo *outerrel,
--- 39,49 ----
  						   RelOptInfo *outerrel,
  						   RelOptInfo *innerrel,
  						   JoinType jointype,
! 						   JoinPathExtraData *extra,
! 						   bool grouped);
  static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
  					 RelOptInfo *outerrel, RelOptInfo *innerrel,
! 								 JoinType jointype, JoinPathExtraData *extra, bool grouped);
  static List *select_mergejoin_clauses(PlannerInfo *root,
  						 RelOptInfo *joinrel,
  						 RelOptInfo *outerrel,
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 217,224 ****
  	 * 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
--- 218,229 ----
  	 * joins, because there may be no other alternative.
  	 */
  	if (enable_hashjoin || jointype == JOIN_FULL)
+ 	{
  		hash_inner_and_outer(root, joinrel, outerrel, innerrel,
! 							 jointype, &extra, false);
! 		hash_inner_and_outer(root, joinrel, outerrel, innerrel,
! 							 jointype, &extra, true);
! 	}
  
  	/*
  	 * 5. If inner and outer relations are foreign tables (or joins) belonging
*************** try_nestloop_path(PlannerInfo *root,
*** 323,329 ****
  
  	if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  pathkeys, required_outer))
  	{
  		add_path(joinrel, (Path *)
  				 create_nestloop_path(root,
--- 328,334 ----
  
  	if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  pathkeys, required_outer, false))
  	{
  		add_path(joinrel, (Path *)
  				 create_nestloop_path(root,
*************** try_nestloop_path(PlannerInfo *root,
*** 336,342 ****
  									  inner_path,
  									  extra->restrictlist,
  									  pathkeys,
! 									  required_outer));
  	}
  	else
  	{
--- 341,348 ----
  									  inner_path,
  									  extra->restrictlist,
  									  pathkeys,
! 									  required_outer),
! 				 false);
  	}
  	else
  	{
*************** try_partial_nestloop_path(PlannerInfo *r
*** 357,363 ****
  						  Path *inner_path,
  						  List *pathkeys,
  						  JoinType jointype,
! 						  JoinPathExtraData *extra)
  {
  	JoinCostWorkspace workspace;
  
--- 363,370 ----
  						  Path *inner_path,
  						  List *pathkeys,
  						  JoinType jointype,
! 						  JoinPathExtraData *extra,
! 						  bool grouped)
  {
  	JoinCostWorkspace workspace;
  
*************** try_partial_nestloop_path(PlannerInfo *r
*** 383,389 ****
  	initial_cost_nestloop(root, &workspace, jointype,
  						  outer_path, inner_path,
  						  extra->sjinfo, &extra->semifactors);
! 	if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys))
  		return;
  
  	/* Might be good enough to be worth trying, so let's try it. */
--- 390,397 ----
  	initial_cost_nestloop(root, &workspace, jointype,
  						  outer_path, inner_path,
  						  extra->sjinfo, &extra->semifactors);
! 	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_nestloop_path(PlannerInfo *r
*** 398,404 ****
  										  inner_path,
  										  extra->restrictlist,
  										  pathkeys,
! 										  NULL));
  }
  
  /*
--- 406,413 ----
  										  inner_path,
  										  extra->restrictlist,
  										  pathkeys,
! 										  NULL),
! 					 grouped);
  }
  
  /*
*************** try_mergejoin_path(PlannerInfo *root,
*** 456,462 ****
  
  	if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  pathkeys, required_outer))
  	{
  		add_path(joinrel, (Path *)
  				 create_mergejoin_path(root,
--- 465,471 ----
  
  	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,
*** 471,477 ****
  									   required_outer,
  									   mergeclauses,
  									   outersortkeys,
! 									   innersortkeys));
  	}
  	else
  	{
--- 480,486 ----
  									   required_outer,
  									   mergeclauses,
  									   outersortkeys,
! 									   innersortkeys), false);
  	}
  	else
  	{
*************** try_hashjoin_path(PlannerInfo *root,
*** 492,498 ****
  				  Path *inner_path,
  				  List *hashclauses,
  				  JoinType jointype,
! 				  JoinPathExtraData *extra)
  {
  	Relids		required_outer;
  	JoinCostWorkspace workspace;
--- 501,508 ----
  				  Path *inner_path,
  				  List *hashclauses,
  				  JoinType jointype,
! 				  JoinPathExtraData *extra,
! 				  bool grouped)
  {
  	Relids		required_outer;
  	JoinCostWorkspace workspace;
*************** try_hashjoin_path(PlannerInfo *root,
*** 521,527 ****
  
  	if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  NIL, required_outer))
  	{
  		add_path(joinrel, (Path *)
  				 create_hashjoin_path(root,
--- 531,537 ----
  
  	if (add_path_precheck(joinrel,
  						  workspace.startup_cost, workspace.total_cost,
! 						  NIL, required_outer, grouped))
  	{
  		add_path(joinrel, (Path *)
  				 create_hashjoin_path(root,
*************** try_hashjoin_path(PlannerInfo *root,
*** 534,540 ****
  									  inner_path,
  									  extra->restrictlist,
  									  required_outer,
! 									  hashclauses));
  	}
  	else
  	{
--- 544,551 ----
  									  inner_path,
  									  extra->restrictlist,
  									  required_outer,
! 									  hashclauses,
! 									  grouped), grouped);
  	}
  	else
  	{
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 555,561 ****
  						  Path *inner_path,
  						  List *hashclauses,
  						  JoinType jointype,
! 						  JoinPathExtraData *extra)
  {
  	JoinCostWorkspace workspace;
  
--- 566,573 ----
  						  Path *inner_path,
  						  List *hashclauses,
  						  JoinType jointype,
! 						  JoinPathExtraData *extra,
! 						  bool grouped)
  {
  	JoinCostWorkspace workspace;
  
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 581,587 ****
  	initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
  						  outer_path, inner_path,
  						  extra->sjinfo, &extra->semifactors);
! 	if (!add_partial_path_precheck(joinrel, workspace.total_cost, NIL))
  		return;
  
  	/* Might be good enough to be worth trying, so let's try it. */
--- 593,599 ----
  	initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
  						  outer_path, inner_path,
  						  extra->sjinfo, &extra->semifactors);
! 	if (!add_partial_path_precheck(joinrel, workspace.total_cost, NIL, false))
  		return;
  
  	/* Might be good enough to be worth trying, so let's try it. */
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 596,602 ****
  										  inner_path,
  										  extra->restrictlist,
  										  NULL,
! 										  hashclauses));
  }
  
  /*
--- 608,616 ----
  										  inner_path,
  										  extra->restrictlist,
  										  NULL,
! 										  hashclauses,
! 										  grouped),
! 					 grouped);
  }
  
  /*
*************** match_unsorted_outer(PlannerInfo *root,
*** 1233,1240 ****
  	if (joinrel->consider_parallel && nestjoinOK &&
  		save_jointype != JOIN_UNIQUE_OUTER &&
  		bms_is_empty(joinrel->lateral_relids))
  		consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! 								   save_jointype, extra);
  }
  
  /*
--- 1247,1258 ----
  	if (joinrel->consider_parallel && nestjoinOK &&
  		save_jointype != JOIN_UNIQUE_OUTER &&
  		bms_is_empty(joinrel->lateral_relids))
+ 	{
  		consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! 								   save_jointype, extra, false);
! 		consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! 								   save_jointype, extra, true);
! 	}
  }
  
  /*
*************** consider_parallel_nestloop(PlannerInfo *
*** 1254,1268 ****
  						   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;
--- 1272,1291 ----
  						   RelOptInfo *outerrel,
  						   RelOptInfo *innerrel,
  						   JoinType jointype,
! 						   JoinPathExtraData *extra,
! 						   bool grouped)
  {
  	JoinType	save_jointype = jointype;
+ 	List	   *outerrel_pathlist;
  	ListCell   *lc1;
  
  	if (jointype == JOIN_UNIQUE_INNER)
  		jointype = JOIN_INNER;
  
! 	outerrel_pathlist = !grouped ? outerrel->partial_pathlist :
! 		outerrel->partial_grouped_pathlist;
! 
! 	foreach(lc1, outerrel_pathlist)
  	{
  		Path	   *outerpath = (Path *) lfirst(lc1);
  		List	   *pathkeys;
*************** consider_parallel_nestloop(PlannerInfo *
*** 1304,1310 ****
  			}
  
  			try_partial_nestloop_path(root, joinrel, outerpath, innerpath,
! 									  pathkeys, jointype, extra);
  		}
  	}
  }
--- 1327,1333 ----
  			}
  
  			try_partial_nestloop_path(root, joinrel, outerpath, innerpath,
! 									  pathkeys, jointype, extra, grouped);
  		}
  	}
  }
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1326,1332 ****
  					 RelOptInfo *outerrel,
  					 RelOptInfo *innerrel,
  					 JoinType jointype,
! 					 JoinPathExtraData *extra)
  {
  	JoinType	save_jointype = jointype;
  	bool		isouterjoin = IS_OUTER_JOIN(jointype);
--- 1349,1356 ----
  					 RelOptInfo *outerrel,
  					 RelOptInfo *innerrel,
  					 JoinType jointype,
! 					 JoinPathExtraData *extra,
! 					 bool grouped)
  {
  	JoinType	save_jointype = jointype;
  	bool		isouterjoin = IS_OUTER_JOIN(jointype);
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1382,1394 ****
  		 * 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,
--- 1406,1445 ----
  		 * can't use a hashjoin.  (There's no use looking for alternative
  		 * input paths, since these should already be the least-parameterized
  		 * available paths.)
+ 		 *
+ 		 * (The same check should work for grouped paths, as these don't
+ 		 * differ in parameterization.)
  		 */
  		if (PATH_PARAM_BY_REL(cheapest_total_outer, innerrel) ||
  			PATH_PARAM_BY_REL(cheapest_total_inner, outerrel))
  			return;
  
+ 		if (grouped)
+ 		{
+ 			/*
+ 			 * The result of grouped path should be quite different from that
+ 			 * of the non-grouped one as such, so don't bother combining
+ 			 * multiple kinds of grouped paths.
+ 			 *
+ 			 * TODO As for JOIN_UNIQUE_OUTER and JOIN_UNIQUE_INNER, consider
+ 			 * if the unique-ificiation is worth the effort.
+ 			 */
+ 			if (jointype != JOIN_UNIQUE_OUTER &&
+ 				jointype != JOIN_UNIQUE_INNER &&
+ 				outerrel->grouped_pathlist && innerrel->grouped_pathlist)
+ 			{
+ 				try_hashjoin_path(root,
+ 								  joinrel,
+ 								  (Path *) linitial(outerrel->grouped_pathlist),
+ 								  (Path *) linitial(innerrel->grouped_pathlist),
+ 								  hashclauses,
+ 								  jointype,
+ 								  extra,
+ 								  true);
+ 			}
+ 		}
  		/* Unique-ify if need be; we ignore parameterized possibilities */
! 		else if (jointype == JOIN_UNIQUE_OUTER)
  		{
  			cheapest_total_outer = (Path *)
  				create_unique_path(root, outerrel,
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1401,1407 ****
  							  cheapest_total_inner,
  							  hashclauses,
  							  jointype,
! 							  extra);
  			/* no possibility of cheap startup here */
  		}
  		else if (jointype == JOIN_UNIQUE_INNER)
--- 1452,1459 ----
  							  cheapest_total_inner,
  							  hashclauses,
  							  jointype,
! 							  extra,
! 							  false);
  			/* no possibility of cheap startup here */
  		}
  		else if (jointype == JOIN_UNIQUE_INNER)
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1417,1423 ****
  							  cheapest_total_inner,
  							  hashclauses,
  							  jointype,
! 							  extra);
  			if (cheapest_startup_outer != NULL &&
  				cheapest_startup_outer != cheapest_total_outer)
  				try_hashjoin_path(root,
--- 1469,1476 ----
  							  cheapest_total_inner,
  							  hashclauses,
  							  jointype,
! 							  extra,
! 							  false);
  			if (cheapest_startup_outer != NULL &&
  				cheapest_startup_outer != cheapest_total_outer)
  				try_hashjoin_path(root,
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1426,1432 ****
  								  cheapest_total_inner,
  								  hashclauses,
  								  jointype,
! 								  extra);
  		}
  		else
  		{
--- 1479,1486 ----
  								  cheapest_total_inner,
  								  hashclauses,
  								  jointype,
! 								  extra,
! 								  false);
  		}
  		else
  		{
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1447,1453 ****
  								  cheapest_total_inner,
  								  hashclauses,
  								  jointype,
! 								  extra);
  
  			foreach(lc1, outerrel->cheapest_parameterized_paths)
  			{
--- 1501,1508 ----
  								  cheapest_total_inner,
  								  hashclauses,
  								  jointype,
! 								  extra,
! 								  false);
  
  			foreach(lc1, outerrel->cheapest_parameterized_paths)
  			{
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1481,1487 ****
  									  innerpath,
  									  hashclauses,
  									  jointype,
! 									  extra);
  				}
  			}
  		}
--- 1536,1543 ----
  									  innerpath,
  									  hashclauses,
  									  jointype,
! 									  extra,
! 									  false);
  				}
  			}
  		}
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1537,1543 ****
  				try_partial_hashjoin_path(root, joinrel,
  										  cheapest_partial_outer,
  										  cheapest_safe_inner,
! 										  hashclauses, jointype, extra);
  		}
  	}
  }
--- 1593,1613 ----
  				try_partial_hashjoin_path(root, joinrel,
  										  cheapest_partial_outer,
  										  cheapest_safe_inner,
! 										  hashclauses, jointype, extra,
! 										  false);
! 
! 			/*
! 			 * If partial grouped path exists for either side, join the
! 			 * cheapest ones into a new grouped path.
! 			 */
! 			if (outerrel->partial_grouped_pathlist &&
! 				innerrel->partial_grouped_pathlist)
! 				try_partial_hashjoin_path(root,
! 										  joinrel,
! 										  (Path *) linitial(outerrel->partial_grouped_pathlist),
! 										  (Path *) linitial(innerrel->partial_grouped_pathlist),
! 										  hashclauses, jointype, extra,
! 										  true);
  		}
  	}
  }
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
new file mode 100644
index 6f3c20b..51e01d7
*** a/src/backend/optimizer/path/joinrels.c
--- b/src/backend/optimizer/path/joinrels.c
*************** mark_dummy_rel(RelOptInfo *rel)
*** 1197,1203 ****
  	rel->partial_pathlist = NIL;
  
  	/* Set up the dummy path */
! 	add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0));
  
  	/* Set or update cheapest_total_path and related fields */
  	set_cheapest(rel);
--- 1197,1203 ----
  	rel->partial_pathlist = NIL;
  
  	/* Set up the dummy path */
! 	add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0), 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 240ade6..6c3a77a
*** a/src/backend/optimizer/path/tidpath.c
--- b/src/backend/optimizer/path/tidpath.c
*************** create_tidscan_paths(PlannerInfo *root,
*** 263,267 ****
  
  	if (tidquals)
  		add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
! 												   required_outer));
  }
--- 263,267 ----
  
  	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 6ceb801..6712f8f
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
***************
*** 14,20 ****
--- 14,25 ----
   */
  #include "postgres.h"
  
+ #include "access/htup_details.h"
+ #include "access/sysattr.h"
+ #include "catalog/pg_aggregate.h"
+ #include "catalog/pg_operator.h"
  #include "catalog/pg_type.h"
+ #include "nodes/makefuncs.h"
  #include "nodes/nodeFuncs.h"
  #include "optimizer/clauses.h"
  #include "optimizer/cost.h"
***************
*** 26,35 ****
--- 31,42 ----
  #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"
  #include "utils/lsyscache.h"
+ #include "utils/syscache.h"
  
  
  /* These parameters are set by GUC */
*************** static List *deconstruct_recurse(Planner
*** 51,56 ****
--- 58,67 ----
  					bool below_outer_join,
  					Relids *qualscope, Relids *inner_join_rels,
  					List **postponed_qual_list);
+ static void build_base_rel_tlist_grouped(PlannerInfo *root, RelOptInfo *rel,
+ 										 List *aggregates, Aggref  *countagg,
+ 										 List **countagg_vars);
+ static void finalize_grouped_vars(PlannerInfo *root, List *countagg_vars);
  static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root,
  				   Relids left_rels, Relids right_rels,
  				   Relids inner_join_rels,
*************** add_vars_to_targetlist(PlannerInfo *root
*** 236,241 ****
--- 247,861 ----
  	}
  }
  
+ /*
+  * Initialize rel->reltarget_grouped where possible.
+  *
+  * root->group_pathkeys must be setup before this function is called.
+  */
+ extern void
+ build_base_rel_tlists_grouped(PlannerInfo *root)
+ {
+ 	List	   *tlist_vars;
+ 	List		*aggregates = NIL;
+ 	ListCell	*lc;
+ 	bool		aggs_acceptable = true;
+ 	Aggref		*countagg = NULL;
+ 	List		*countagg_vars = NIL;
+ 	int			nbaserels = 0;
+ 	int			i;
+ 
+ 	/* No grouping in the query? */
+ 	if (!root->parse->groupClause || root->group_pathkeys == NIL)
+ 		return;
+ 
+ 	/* TODO This is just for PoC. Relax the limitation later. */
+ 	if (root->parse->havingQual)
+ 		return;
+ 
+ 	/*
+ 	 * If no join is expected, aggregation at base relation level makes no
+ 	 * sense. XXX Is there simpler way to find out? (We're not interested in
+ 	 * RELOPT_OTHER_MEMBER_REL, so simple_rel_array_size does not help.)
+ 	 */
+ 	for (i = 1; i < root->simple_rel_array_size; i++)
+ 	{
+ 		RelOptInfo *rel;
+ 
+ 		rel = find_base_rel(root, i);
+ 		if (rel->reloptkind == RELOPT_BASEREL)
+ 		{
+ 			nbaserels++;
+ 			/*
+ 			 * We only want to know whether the number of relations is greater
+ 			 * than one.
+ 			 */
+ 			if (nbaserels > 1)
+ 				break;
+ 		}
+ 	}
+ 	if (nbaserels <= 1)
+ 		return;
+ 
+ 	tlist_vars = pull_var_clause((Node *) root->processed_tlist,
+ 								 PVC_INCLUDE_AGGREGATES);
+ 	if (tlist_vars == NIL)
+ 		return;
+ 
+ 	/* tlist_vars may also contain Vars, but we only need Aggrefs. */
+ 	foreach(lc, tlist_vars)
+ 	{
+ 		Expr *expr = (Expr *) lfirst(lc);
+ 
+ 		if (!IsA(expr, Var))
+ 		{
+ 			Aggref	*aggref = (Aggref *) expr;
+ 
+ 			Assert(IsA(aggref, Aggref));
+ 
+ 			/* TODO Think if (some of) these can be handled. */
+ 			if (aggref->aggstar || aggref->aggvariadic || aggref->aggdirectargs ||
+ 				aggref->aggorder || aggref->aggdistinct || aggref->aggfilter)
+ 			{
+ 				aggs_acceptable = false;
+ 				break;
+ 			}
+ 
+ 			aggregates = lappend(aggregates, expr);
+ 		}
+ 	}
+ 
+ 	if (aggregates != NIL && aggs_acceptable)
+ 	{
+ 		int	i;
+ 
+ 		if (countagg == NULL)
+ 		{
+ 			/* count(*) aggregate for base relation grouping. */
+ 			countagg = makeNode(Aggref);
+ 			countagg->aggfnoid = COUNTFNOID;
+ 			countagg->aggkind = AGGKIND_NORMAL;
+ 			countagg->aggtype = INT8OID;
+ 			countagg->aggtranstype = INT8OID;
+ 			countagg->aggstar = true;
+ 		}
+ 		/* Process the individual base relations. */
+ 		root->all_baserels_grouped = true;
+ 		for (i = 1; i < root->simple_rel_array_size; i++)
+ 		{
+ 			RelOptInfo	*rel;
+ 
+ 			rel = root->simple_rel_array[i];
+ 			if (rel != NULL)
+ 				build_base_rel_tlist_grouped(root, rel, aggregates, countagg,
+ 											 &countagg_vars);
+ 
+ 			/*
+ 			 * Don't bother processing the rest if the current rel prevents us
+ 			 * from forming the final "grouped join".
+ 			 */
+ 			if (!root->all_baserels_grouped)
+ 				break;
+ 		}
+ 
+ 		if (root->all_baserels_grouped)
+ 			finalize_grouped_vars(root, countagg_vars);
+ 	}
+ 	else
+ 		root->all_baserels_grouped = false;
+ 
+ 	if (aggregates != NIL)
+ 		list_free(aggregates);
+ 	list_free(tlist_vars);
+ }
+ 
+ /*
+  * Construct target of grouped relation - that can only contain grouping
+  * expressions and aggregates. Such a target can't be created if anything else
+  * is required on the output.
+  *
+  * countagg represents count(*) aggregate that each grouped rel must have to
+  * collect information of per-group row count. This information is used to
+  * adjust the transient state in the output of the final join.
+  *
+  * Variable representing the result of countagg is added to *countagg_vars
+  * list (besides being added to root->grouped_var_list as well, so that it the
+  * actual count(*) expression be restored by set_upper_references.)
+  */
+ static void
+ build_base_rel_tlist_grouped(PlannerInfo *root, RelOptInfo *rel,
+ 							 List *aggregates, Aggref  *countagg,
+ 							 List **countagg_vars)
+ {
+ 	RangeTblEntry *rte;
+ 	List		*rel_aggregates;
+ 	ListCell	*lc;
+ 	Relids	agg_arg_rels_all, agg_arg_attrs_all;
+ 	PathTarget	*reltarget_grouped;
+ 	bool		first;
+ 
+ 	rte = root->simple_rte_array[rel->relid];
+ 
+ 	/*
+ 	 * XXX Append relation, as well as rtekind != RTE_RELATION seem to deserve
+ 	 * separate patches.
+ 	 */
+ 	if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL ||
+ 		rte->rtekind != RTE_RELATION)
+ 	{
+ 		root->all_baserels_grouped = false;
+ 		return;
+ 	}
+ 
+ 	/* Caller should only pass elements of root->simple_rel_array. */
+ 	Assert(rel->reloptkind == RELOPT_BASEREL);
+ 	Assert(bms_membership(rel->relids) == BMS_SINGLETON);
+ 
+ 	/*
+ 	 * If any outer join can set the attribute value to NULL, the aggregate
+ 	 * would receive different input at the base rel level.
+ 	 *
+ 	 * TODO This as well as some other limitations below should only apply to
+ 	 * the PoC version of the patch. In the future we should handle
+ 	 * non-grouped relations by joining them to the grouped ones and applying
+ 	 * additional partial aggregation to the final join.
+ 	 */
+ 	if (bms_overlap(rel->relids, root->nullable_baserels))
+ 	{
+ 		root->all_baserels_grouped = false;
+ 		return;
+ 	}
+ 
+ 	/* Collect aggregates applicable to the current relation. */
+ 	rel_aggregates = NIL;
+ 	agg_arg_rels_all = NULL;
+ 	agg_arg_attrs_all = NULL;
+ 	foreach(lc, aggregates)
+ 	{
+ 		Aggref	*aggref = (Aggref *) lfirst(lc);
+ 		Relids	agg_arg_rels, agg_arg_attrs = NULL;
+ 
+ 		/* TODO Does it matter if any argument contains PHV ? */
+ 		agg_arg_rels = pull_varnos((Node *) aggref->args);
+ 
+ 		/* Skip aggregates which don't reference rel at all. */
+ 		if (!bms_overlap(rel->relids, agg_arg_rels))
+ 			continue;
+ 
+ 		/*
+ 		 * If the aggregate references multiple rels, it must be processed
+ 		 * higher in the join tree.
+ 		 */
+ 		if (bms_membership(agg_arg_rels) != BMS_SINGLETON)
+ 		{
+ 			/*
+ 			 * TODO Consider relaxing this limitation by adding one extra
+ 			 * partial aggregation below the final one.
+ 			 */
+ 			root->all_baserels_grouped = false;
+ 
+ 			return;
+ 		}
+ 
+ 		/* Accept this aggregate. */
+ 		rel_aggregates = lappend(rel_aggregates, aggref);
+ 		agg_arg_rels_all = bms_union(agg_arg_rels_all, agg_arg_rels);
+ 
+ 		/* Collect attnos while being here. */
+ 		pull_varattnos((Node *) aggref->args, rel->relid, &agg_arg_attrs);
+ 		agg_arg_attrs_all = bms_union(agg_arg_attrs_all, agg_arg_attrs);
+ 	}
+ 
+ 	Assert(rel->reltarget_grouped == NULL);
+ 
+ 	/*
+ 	 * Create a separate target which represents the actual output of grouped
+ 	 * relation. This target will have aggregates replaced with special vars
+ 	 * which will ensure propagation of the result of the partial aggregation
+ 	 * of the base relation to the final Agg node.
+ 	 *
+ 	 * This target won't contain expressions that the grouped relation cannot
+ 	 * emit. Another difference from rel->reltarget is that sortgroupref is
+ 	 * set for each grouping expression. This is because the same target will
+ 	 * be used for the partial aggregation.
+ 	 *
+ 	 * (Do not set rel->reltarget_grouped yet, as we might return
+ 	 * prematurely.)
+ 	 */
+ 	reltarget_grouped = create_empty_pathtarget();
+ 
+ 	/*
+ 	 * Check if all the output columns can be used as grouping expressions.
+ 	 *
+ 	 * TODO
+ 	 *
+ 	 * 1. How about PHVs?
+ 	 *
+ 	 * 2. Consider additional grouping expressions, just to be able to emit
+ 	 * the columns that the query group clause doesn't mention. (These
+ 	 * additional expressions wouldn't be used during the final aggregation.)
+ 	 * Does this also mean that we shouldn't check existence of
+ 	 * parse->groupClause earlier in this function?
+ 	 */
+ 	foreach(lc, rel->reltarget->exprs)
+ 	{
+ 		ListCell	*lc2;
+ 		Expr		*texpr = (Expr *) lfirst(lc);
+ 		bool		is_grouping = false;
+ 		bool		ec_found = false;
+ 		bool		agg_arg_only = false;
+ 
+ 		/*
+ 		 * First, check if the query group clause contains exactly this
+ 		 * expression.
+ 		 */
+ 		foreach(lc2, root->processed_tlist)
+ 		{
+ 			TargetEntry		*te = (TargetEntry *) lfirst(lc2);
+ 
+ 			Assert(IsA(te, TargetEntry));
+ 
+ 			if (equal(texpr, te->expr) && te->ressortgroupref > 0)
+ 			{
+ 				add_column_to_pathtarget(reltarget_grouped, texpr,
+ 										 te->ressortgroupref);
+ 				is_grouping = true;
+ 				break;
+ 			}
+ 		}
+ 
+ 		/* Go for the next expression if matched the current one. */
+ 		if (is_grouping)
+ 			continue;
+ 
+ 		/*
+ 		 * If exactly this expression is not there, check if a grouping clause
+ 		 * exists that belongs to the same equivalence class as the
+ 		 * expression.
+ 		 */
+ 		foreach(lc2, root->group_pathkeys)
+ 		{
+ 			PathKey	*pk = (PathKey *) lfirst(lc2);
+ 			EquivalenceClass		*ec = pk->pk_eclass;
+ 			ListCell		*lm;
+ 			EquivalenceMember		*em;
+ 			Expr	*em_expr = NULL;
+ 			Query	*query = root->parse;
+ 			Index	sortgroupref;
+ 
+ 			/*
+ 			 * Single-member EC cannot provide us with additional
+ 			 * expression.
+ 			 */
+ 			if (list_length(ec->ec_members) < 2)
+ 				continue;
+ 
+ 			/* We need equality anywhere in the join tree. */
+ 			if (ec->ec_below_outer_join)
+ 				continue;
+ 
+ 			/*
+ 			 * TODO Reconsider this restriction. As the grouping expression is
+ 			 * only evaluated at the relation level (and only the result will
+ 			 * be propagated to the final targetlist), volatile function might
+ 			 * be o.k. Need to think what volatile EC exactly means.
+ 			 */
+ 			if (ec->ec_has_volatile)
+ 				continue;
+ 
+ 			foreach(lm, ec->ec_members)
+ 			{
+ 				em = (EquivalenceMember *) lfirst(lm);
+ 
+ 				/* The EC has !ec_below_outer_join. */
+ 				Assert(!em->em_nullable_relids);
+ 				if (equal(em->em_expr, texpr))
+ 				{
+ 					em_expr = (Expr *) em->em_expr;
+ 					break;
+ 				}
+ 			}
+ 
+ 			if (em_expr == NULL)
+ 				/* Go for the next EC. */
+ 				continue;
+ 
+ 			/*
+ 			 * Find the corresponding SortGroupClause, which provides us
+ 			 * with sortgroupref. (It can belong to any EC member.)
+ 			 */
+ 			sortgroupref = 0;
+ 			foreach(lm, ec->ec_members)
+ 			{
+ 				ListCell	*lsg;
+ 
+ 				em = (EquivalenceMember *) lfirst(lm);
+ 				foreach(lsg, query->groupClause)
+ 				{
+ 					SortGroupClause	*sgc;
+ 					Expr	*texpr;
+ 
+ 					sgc = (SortGroupClause *) lfirst(lsg);
+ 					texpr = (Expr *) get_sortgroupclause_expr(sgc,
+ 															  query->targetList);
+ 					if (equal(em->em_expr, texpr))
+ 					{
+ 						Assert(sgc->tleSortGroupRef > 0);
+ 						sortgroupref = sgc->tleSortGroupRef;
+ 						break;
+ 					}
+ 				}
+ 
+ 				if (sortgroupref > 0)
+ 					break;
+ 			}
+ 
+ 			/*
+ 			 * At least one EM of this EC should have correspond to a
+ 			 * SortGroupClause, otherwise the EC could hardly exist.
+ 			 */
+ 			if (sortgroupref == 0)
+ 				elog(ERROR, "Grouping EC does not match any grouping clause.");
+ 
+ 			/* It's o.k. to use the target expression for grouping. */
+ 			add_column_to_pathtarget(reltarget_grouped, texpr, sortgroupref);
+ 			ec_found = true;
+ 			break;
+ 		}
+ 
+ 		/*
+ 		 * It may still be o.k. if the expression is only contained in Aggref
+ 		 * - then it's not expected in the grouped output.
+ 		 *
+ 		 * TODO Try to handle generic expression, not only Var. That might
+ 		 * require us to create rel->reltarget of the grouping rel in
+ 		 * parallel to that of the plain rel, and adding whole expressions
+ 		 * instead of individual vars.
+ 		 */
+ 		if (IsA(texpr, Var) &&
+ 			bms_is_member(((Var *) texpr)->varattno -
+ 						  FirstLowInvalidHeapAttributeNumber, agg_arg_attrs_all))
+ 			agg_arg_only = true;
+ 
+ 		/*
+ 		 * A single mismatched expression makes the whole relation useless
+ 		 * for grouping at base level.
+ 		 */
+ 		if (!ec_found && !agg_arg_only)
+ 			return;
+ 	}
+ 
+ 	/*
+ 	 * Add the count(*) aggregate, as its processing is nearly identical to
+ 	 * that of other aggregates. It should be located in front of the other
+ 	 * vars so they can reference it.
+ 	 *
+ 	 * TODO Avoid this if no aggregate in the query has aggtransmultifn.
+ 	 */
+ 	rel_aggregates = lcons(countagg, rel_aggregates);
+ 
+ 	/*
+ 	 * Add each aggregate to reltarget_grouped in the form of special
+ 	 * variable, for which GroupedVarInfo will also be added to
+ 	 * root->grouped_var_list.
+ 	 *
+ 	 * It doesn't matter that the relation contains no such variables - they
+ 	 * will be replaced with the partially-aggregated Aggref before execution
+ 	 * starts - see restore_grouped_var_expr.
+ 	 */
+ 	first = true;
+ 	foreach(lc, rel_aggregates)
+ 	{
+ 		Aggref	*aggref = (Aggref *) lfirst(lc);
+ 		AttrNumber	varattno;
+ 		Var		*var;
+ 		GroupedVarInfo	*gvi;
+ 
+ 		rel->max_attr++;
+ 		varattno = rel->max_attr;
+ 
+ 		var = makeVar(rel->relid, varattno, InvalidOid, 0, InvalidOid, 0);
+ 		add_new_column_to_pathtarget(reltarget_grouped, (Expr *) var);
+ 
+ 		/*
+ 		 * Create writable copy so that it can be marked as partial, and
+ 		 * eventually be associated with the final aggregate - see
+ 		 * set_upper_references. (XXX Is copy necessary for the count(*)
+ 		 * aggregate as well?)
+ 		 */
+ 		aggref = copyObject(aggref);
+ 
+ 		if (first)
+ 		{
+ 			/* count(*) should be the first item of the list. */
+ 			Assert(aggref->aggfnoid == COUNTFNOID && aggref->aggstar);
+ 
+ 			/*
+ 			 * The variable representing the auxiliary count(*) is not subject
+ 			 * to the final aggregation (in fact the result is used as
+ 			 * argument of aggtransmultifn, which prepares input value for the
+ 			 * final aggregationq), so there's never reason to restore the
+ 			 * count(*) expression. Moreover, restoration of the count(*)
+ 			 * expression outside Aggref (e.g. in the targetlist of the final
+ 			 * join) would cause ERROR during plan initialization.
+ 			 *
+ 			 * So instead of creating GroupedVarInfo we only add the var to a
+ 			 * list of vars that GroupedVarInfo.expr_intermediate can
+ 			 * reference.
+ 			 */
+ 			*countagg_vars = lappend(*countagg_vars, var);
+ 
+ 			first = false;
+ 		}
+ 
+ 		/*
+ 		 * Associate the expression with the corresponding variable, so that
+ 		 * set_upper_references can do the changes explained above.
+ 		 */
+ 		gvi = (GroupedVarInfo *) palloc0(sizeof(GroupedVarInfo));
+ 		gvi->var = var;
+ 
+ 		/* The variable represents result of the partial aggregation. */
+ 		mark_partial_aggref(aggref, AGGSPLIT_INITIAL_SERIAL);
+ 		/* The variable type info should reflect the transient type. */
+ 		var->vartype = exprType((Node *) aggref);
+ 		var->vartypmod = exprTypmod((Node *) aggref);
+ 		var->varcollid = exprCollation((Node *) aggref);
+ 
+ 		gvi->expr = (Expr *) aggref;
+ 		gvi->sortgroupref = 0;
+ 		root->grouped_var_list = lappend(root->grouped_var_list, gvi);
+ 
+ 		/*
+ 		 * Add the variable to rel->attr_needed to ensure propagation to the
+ 		 * top-level target list. (Non-grouped path are not aware of the
+ 		 * variable, and they should already be generated by now.)
+ 		 */
+ 		rel->attr_needed = (Relids *)
+ 			repalloc(rel->attr_needed, (rel->max_attr - rel->min_attr + 1) *
+ 					 sizeof(Relids));
+ 		rel->attr_needed[varattno - rel->min_attr] = bms_make_singleton(0);
+ 
+ 		/*
+ 		 * As set_rel_width should already have completed, it makes no sense
+ 		 * to add the variable to attr_widths. Instead we only adjust
+ 		 * rel->reltarget->width.
+ 		 */
+ 		/* TODO Adjust rel->reltarget->width */
+ 	}
+ 
+ 	rel->reltarget_grouped = reltarget_grouped;
+ }
+ 
+ /*
+  *  Initialize expr_intermediate of each GroupedVarInfo that requires it.
+  *  This expression will adjust partial state of an aggregate prior to final
+  *  aggregation, so it reflects existence of joins.
+  *
+  *  If a single relation is aggregated, only the table determines how many
+  *  times each value of aggregate argument appears in the relevant group. But
+  *  if relation is joined to another one, the aggregate executed on the final
+  *  join (i.e. w/o the relation-level aggregation) can actually receive the
+  *  whole input set multiple times: some values of the grouping key present in
+  *  the relation can match multiple values in the other table(s).
+  *
+  *  sum() is an example of an aggregate where join matters, avg() is one where
+  *  it does not.
+  *
+  *  We simulate the effect of joins by applying aggtransmultifn (see
+  *  pg_aggregate) to the result of relation-level aggregation.
+  */
+ static void
+ finalize_grouped_vars(PlannerInfo *root, List *countagg_vars)
+ {
+ 	ListCell	*lc;
+ 	Oid			op_int8mul = InvalidOid;
+ 
+ 	foreach(lc, root->grouped_var_list)
+ 	{
+ 		GroupedVarInfo	*gvi = (GroupedVarInfo *) lfirst(lc);
+ 		Aggref	*aggref = (Aggref *) gvi->expr;
+ 		ListCell	*l;
+ 		Expr	*factor = NULL;
+ 
+ 		Assert(gvi->expr_intermediate == NULL);
+ 		Assert(IsA(aggref, Aggref));
+ 
+ 		/* Is transient state multiplication needed for this aggregate? */
+ 		if (aggref->aggtransmultifn == InvalidOid)
+ 			continue;
+ 
+ 		/*
+ 		 * If there was no relation-level aggregation, each table joined to
+ 		 * the one gvi->varno points at would increase the frequency of a
+ 		 * value within a group by the factor which equals to the frequency of
+ 		 * the corresponding grouping key in the table joined.
+ 		 *
+ 		 * To prepare the correct input for the final aggregation, the
+ 		 * relation-level per-group state must be "multiplied" by the number
+ 		 * of grouping key values that join of the *other* tables (i.e. all
+ 		 * but the one referenced by the current aggregate) generates.
+ 		 *
+ 		 * This is why count(*) was added to each base relation.
+ 		 */
+ 		foreach(l, countagg_vars)
+ 		{
+ 			Var *var = (Var *) lfirst(l);
+ 
+ 			/* Only the other tables do matter. */
+ 			if (var->varno == gvi->var->varno)
+ 				continue;
+ 
+ 			if (factor == NULL)
+ 				/* The first value. */
+ 				factor = (Expr *) var;
+ 			else
+ 			{
+ 				OpExpr	*op = makeNode(OpExpr);
+ 
+ 				/*
+ 				 * Multiply the intermediate result by the result of the next
+ 				 * count(*) aggregate.
+ 				 */
+ 				op->opno = OPERATOR_INT8MUL;
+ 				if (op_int8mul == InvalidOid)
+ 				{
+ 					set_opfuncid(op);
+ 					op_int8mul = op->opfuncid;
+ 				}
+ 				else
+ 					op->opfuncid = op_int8mul;
+ 
+ 				op->opresulttype = INT8OID;
+ 				op->opretset = false;
+ 				op->opcollid = InvalidOid;
+ 				op->inputcollid = InvalidOid;
+ 				op->args = list_make2(factor, var);
+ 				op->location = -1;
+ 
+ 				factor = (Expr *) op;
+ 			}
+ 		}
+ 
+ 		/* Construct the function call if needed. */
+ 		if (factor != NULL)
+ 		{
+ 			FuncExpr	*func = makeNode(FuncExpr);
+ 
+ 			func->funcid = aggref->aggtransmultifn;
+ 			func->funcresulttype = aggref->aggtranstype;
+ 			func->funcretset = false;
+ 			func->funcvariadic = false;
+ 			func->funccollid = InvalidOid;
+ 			func->inputcollid = InvalidOid;
+ 			Assert(gvi->var != NULL);
+ 			func->args = list_make2(gvi->var, factor);
+ 			func->location = -1;
+ 
+ 			gvi->expr_intermediate = (Expr *) func;
+ 		}
+ 	}
+ }
+ 
  
  /*****************************************************************************
   *
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
new file mode 100644
index c3fbf3c..5b3bc71
*** a/src/backend/optimizer/plan/planagg.c
--- b/src/backend/optimizer/plan/planagg.c
*************** preprocess_minmax_aggregates(PlannerInfo
*** 223,229 ****
  			 create_minmaxagg_path(root, grouped_rel,
  								   create_pathtarget(root, tlist),
  								   aggs_list,
! 								   (List *) parse->havingQual));
  }
  
  /*
--- 223,229 ----
  			 create_minmaxagg_path(root, grouped_rel,
  								   create_pathtarget(root, tlist),
  								   aggs_list,
! 								   (List *) parse->havingQual), false);
  }
  
  /*
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
new file mode 100644
index e880759..c7d98ca
*** a/src/backend/optimizer/plan/planmain.c
--- b/src/backend/optimizer/plan/planmain.c
*************** query_planner(PlannerInfo *root, List *t
*** 83,89 ****
  		add_path(final_rel, (Path *)
  				 create_result_path(root, final_rel,
  									final_rel->reltarget,
! 									(List *) parse->jointree->quals));
  
  		/* Select cheapest path (pretty easy in this case...) */
  		set_cheapest(final_rel);
--- 83,89 ----
  		add_path(final_rel, (Path *)
  				 create_result_path(root, final_rel,
  									final_rel->reltarget,
! 									(List *) parse->jointree->quals), false);
  
  		/* Select cheapest path (pretty easy in this case...) */
  		set_cheapest(final_rel);
*************** query_planner(PlannerInfo *root, List *t
*** 176,181 ****
--- 176,183 ----
  	 */
  	(*qp_callback) (root, qp_extra);
  
+ 	build_base_rel_tlists_grouped(root);
+ 
  	/*
  	 * Examine any "placeholder" expressions generated during subquery pullup.
  	 * Make sure that the Vars they need are marked as needed at the relevant
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 207290f..eeac4ac
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** static double get_number_of_groups(Plann
*** 109,117 ****
  					 double path_rows,
  					 List *rollup_lists,
  					 List *rollup_groupclauses);
- 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,
--- 109,114 ----
*************** inheritance_planner(PlannerInfo *root)
*** 1396,1402 ****
  									 returningLists,
  									 rowMarks,
  									 NULL,
! 									 SS_assign_special_param(root)));
  }
  
  /*--------------------
--- 1393,1399 ----
  									 returningLists,
  									 rowMarks,
  									 NULL,
! 									 SS_assign_special_param(root)), false);
  }
  
  /*--------------------
*************** grouping_planner(PlannerInfo *root, bool
*** 2061,2067 ****
  		}
  
  		/* And shove it into final_rel */
! 		add_path(final_rel, path);
  	}
  
  	/*
--- 2058,2064 ----
  		}
  
  		/* And shove it into final_rel */
! 		add_path(final_rel, path, false);
  	}
  
  	/*
*************** get_number_of_groups(PlannerInfo *root,
*** 3270,3304 ****
  }
  
  /*
-  * estimate_hashagg_tablesize
-  *	  estimate the number of bytes that a hash aggregate hashtable will
-  *	  require based on the agg_costs, path width and dNumGroups.
-  */
- 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.
--- 3267,3272 ----
*************** create_grouping_paths(PlannerInfo *root,
*** 3419,3425 ****
  								   (List *) parse->havingQual);
  		}
  
! 		add_path(grouped_rel, path);
  
  		/* No need to consider any other alternatives. */
  		set_cheapest(grouped_rel);
--- 3387,3393 ----
  								   (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,
*** 3592,3598 ****
  														 parse->groupClause,
  														 NIL,
  														 &agg_partial_costs,
! 														 dNumPartialGroups));
  					else
  						add_partial_path(grouped_rel, (Path *)
  										 create_group_path(root,
--- 3560,3567 ----
  														 parse->groupClause,
  														 NIL,
  														 &agg_partial_costs,
! 														 dNumPartialGroups),
! 							false);
  					else
  						add_partial_path(grouped_rel, (Path *)
  										 create_group_path(root,
*************** create_grouping_paths(PlannerInfo *root,
*** 3601,3607 ****
  													 partial_grouping_target,
  														   parse->groupClause,
  														   NIL,
! 														 dNumPartialGroups));
  				}
  			}
  		}
--- 3570,3577 ----
  													 partial_grouping_target,
  														   parse->groupClause,
  														   NIL,
! 														   dNumPartialGroups),
! 										 false);
  				}
  			}
  		}
*************** create_grouping_paths(PlannerInfo *root,
*** 3632,3638 ****
  												 parse->groupClause,
  												 NIL,
  												 &agg_partial_costs,
! 												 dNumPartialGroups));
  			}
  		}
  	}
--- 3602,3609 ----
  												 parse->groupClause,
  												 NIL,
  												 &agg_partial_costs,
! 												 dNumPartialGroups),
! 								 false);
  			}
  		}
  	}
*************** create_grouping_paths(PlannerInfo *root,
*** 3677,3683 ****
  													  rollup_lists,
  													  rollup_groupclauses,
  													  agg_costs,
! 													  dNumGroups));
  				}
  				else if (parse->hasAggs)
  				{
--- 3648,3654 ----
  													  rollup_lists,
  													  rollup_groupclauses,
  													  agg_costs,
! 													  dNumGroups), false);
  				}
  				else if (parse->hasAggs)
  				{
*************** create_grouping_paths(PlannerInfo *root,
*** 3695,3701 ****
  											 parse->groupClause,
  											 (List *) parse->havingQual,
  											 agg_costs,
! 											 dNumGroups));
  				}
  				else if (parse->groupClause)
  				{
--- 3666,3672 ----
  											 parse->groupClause,
  											 (List *) parse->havingQual,
  											 agg_costs,
! 											 dNumGroups), false);
  				}
  				else if (parse->groupClause)
  				{
*************** create_grouping_paths(PlannerInfo *root,
*** 3710,3716 ****
  											   target,
  											   parse->groupClause,
  											   (List *) parse->havingQual,
! 											   dNumGroups));
  				}
  				else
  				{
--- 3681,3687 ----
  											   target,
  											   parse->groupClause,
  											   (List *) parse->havingQual,
! 											   dNumGroups), false);
  				}
  				else
  				{
*************** create_grouping_paths(PlannerInfo *root,
*** 3760,3766 ****
  										 parse->groupClause,
  										 (List *) parse->havingQual,
  										 &agg_final_costs,
! 										 dNumGroups));
  			else
  				add_path(grouped_rel, (Path *)
  						 create_group_path(root,
--- 3731,3737 ----
  										 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,
*** 3769,3775 ****
  										   target,
  										   parse->groupClause,
  										   (List *) parse->havingQual,
! 										   dNumGroups));
  		}
  	}
  
--- 3740,3746 ----
  										   target,
  										   parse->groupClause,
  										   (List *) parse->havingQual,
! 										   dNumGroups), false);
  		}
  	}
  
*************** create_grouping_paths(PlannerInfo *root,
*** 3801,3807 ****
  									 parse->groupClause,
  									 (List *) parse->havingQual,
  									 agg_costs,
! 									 dNumGroups));
  		}
  
  		/*
--- 3772,3778 ----
  									 parse->groupClause,
  									 (List *) parse->havingQual,
  									 agg_costs,
! 									 dNumGroups), false);
  		}
  
  		/*
*************** create_grouping_paths(PlannerInfo *root,
*** 3838,3846 ****
  										 parse->groupClause,
  										 (List *) parse->havingQual,
  										 &agg_final_costs,
! 										 dNumGroups));
  			}
  		}
  	}
  
  	/* Give a helpful error if we failed to find any implementation */
--- 3809,3895 ----
  										 parse->groupClause,
  										 (List *) parse->havingQual,
  										 &agg_final_costs,
! 										 dNumGroups), false);
  			}
  		}
+ 
+ 		/*
+ 		 * If input_rel has partially aggregated partial paths, perform the
+ 		 * final aggregation.
+ 		 *
+ 		 * TODO Allow havingQual - currently not supported at base relation
+ 		 * level.
+ 		 */
+ 		if (input_rel->partial_grouped_pathlist != NIL &&
+ 			!parse->havingQual)
+ 		{
+ 			Path	   *path = (Path *) linitial(input_rel->partial_grouped_pathlist);
+ 			double		total_groups = path->rows * path->parallel_workers;
+ 
+ 			path = (Path *) create_gather_path(root,
+ 											   input_rel,
+ 											   path,
+ 											   path->pathtarget,
+ 											   NULL,
+ 											   &total_groups);
+ 
+ 			/*
+ 			 * The input path is partially aggregated and the final
+ 			 * aggregation - if the path wins - will be done below. So we're
+ 			 * done with it for now.
+ 			 */
+ 			add_path(input_rel, path, true);
+ 		}
+ 
+ 		/*
+ 		 * If input_rel has partially aggregated paths, perform the final
+ 		 * aggregation.
+ 		 *
+ 		 * TODO Allow havingQual - currently not supported at base relation
+ 		 * level.
+ 		 */
+ 		if (input_rel->grouped_pathlist != NIL &&
+ 			!parse->havingQual)
+ 		{
+ 			Path *pre_agg = (Path *) linitial(input_rel->grouped_pathlist);
+ 			PathTarget	*proj_target;
+ 
+ 			/*
+ 			 * For each grouped variable in pre_agg->pathtarget ensure
+ 			 * evaluation of expr_intermediate (see GroupedVarInfo).
+ 			 */
+ 			proj_target =
+ 				create_intermediate_grouping_target(root,
+ 													pre_agg->pathtarget);
+ 			pre_agg = (Path *) create_projection_path(root, input_rel,
+ 													  pre_agg,
+ 													  proj_target);
+ 
+ 			dNumGroups = get_number_of_groups(root,
+ 											  pre_agg->rows,
+ 											  rollup_lists,
+ 											  rollup_groupclauses);
+ 
+ 			MemSet(&agg_final_costs, 0, sizeof(AggClauseCosts));
+ 			get_agg_clause_costs(root, (Node *) target->exprs,
+ 								 AGGSPLIT_FINAL_DESERIAL,
+ 								 &agg_final_costs);
+ 			/* get_agg_clause_costs(root, parse->havingQual, */
+ 			/* 					 AGGSPLIT_FINAL_DESERIAL, */
+ 			/* 					 &agg_final_costs); */
+ 
+ 			add_path(grouped_rel,
+ 					 (Path *) create_agg_path(root, grouped_rel,
+ 											  pre_agg,
+ 											  target,
+ 											  AGG_HASHED,
+ 											  AGGSPLIT_FINAL_DESERIAL,
+ 											  parse->groupClause,
+ 											  (List *) parse->havingQual,
+ 											  &agg_final_costs,
+ 											  dNumGroups),
+ 					 false);
+ 		}
  	}
  
  	/* Give a helpful error if we failed to find any implementation */
*************** create_one_window_path(PlannerInfo *root
*** 4053,4059 ****
  								  window_pathkeys);
  	}
  
! 	add_path(window_rel, path);
  }
  
  /*
--- 4102,4108 ----
  								  window_pathkeys);
  	}
  
! 	add_path(window_rel, path, false);
  }
  
  /*
*************** create_distinct_paths(PlannerInfo *root,
*** 4159,4165 ****
  						 create_upper_unique_path(root, distinct_rel,
  												  path,
  										list_length(root->distinct_pathkeys),
! 												  numDistinctRows));
  			}
  		}
  
--- 4208,4214 ----
  						 create_upper_unique_path(root, distinct_rel,
  												  path,
  										list_length(root->distinct_pathkeys),
! 												  numDistinctRows), false);
  			}
  		}
  
*************** create_distinct_paths(PlannerInfo *root,
*** 4186,4192 ****
  				 create_upper_unique_path(root, distinct_rel,
  										  path,
  										list_length(root->distinct_pathkeys),
! 										  numDistinctRows));
  	}
  
  	/*
--- 4235,4241 ----
  				 create_upper_unique_path(root, distinct_rel,
  										  path,
  										list_length(root->distinct_pathkeys),
! 										  numDistinctRows), false);
  	}
  
  	/*
*************** create_distinct_paths(PlannerInfo *root,
*** 4233,4239 ****
  								 parse->distinctClause,
  								 NIL,
  								 NULL,
! 								 numDistinctRows));
  	}
  
  	/* Give a helpful error if we failed to find any implementation */
--- 4282,4288 ----
  								 parse->distinctClause,
  								 NIL,
  								 NULL,
! 								 numDistinctRows), false);
  	}
  
  	/* Give a helpful error if we failed to find any implementation */
*************** create_ordered_paths(PlannerInfo *root,
*** 4331,4337 ****
  				path = apply_projection_to_path(root, ordered_rel,
  												path, target);
  
! 			add_path(ordered_rel, path);
  		}
  	}
  
--- 4380,4386 ----
  				path = apply_projection_to_path(root, ordered_rel,
  												path, target);
  
! 			add_path(ordered_rel, path, false);
  		}
  	}
  
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
new file mode 100644
index 413a0d9..632f4bb
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
*************** set_upper_references(PlannerInfo *root,
*** 1677,1686 ****
  	indexed_tlist *subplan_itlist;
  	List	   *output_targetlist;
  	ListCell   *l;
  
! 	subplan_itlist = build_tlist_index(subplan->targetlist);
  
  	output_targetlist = NIL;
  	foreach(l, plan->targetlist)
  	{
  		TargetEntry *tle = (TargetEntry *) lfirst(l);
--- 1677,1744 ----
  	indexed_tlist *subplan_itlist;
  	List	   *output_targetlist;
  	ListCell   *l;
+ 	List	*sub_tlist_save = NIL;
+ 	bool	install_partial_aggrefs = false;
  
! 	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 need restore the original list when done with the
! 				 * references.
! 				 */
! 				if (!IsA(subplan, Agg))
! 					sub_tlist_save = copyObject(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.
! 				 *
! 				 * TODO Optimize restore_grouping_expressions using a new
! 				 * parameter indicating whether the targetlist can contain
! 				 * "intermediate expression". Only Result (or also Gather if
! 				 * we use its target list to evaluate "intermediate
! 				 * expressions"?) node should contain those.
! 				 */
! 				subplan->targetlist =
! 					restore_grouping_expressions(root, subplan->targetlist);
! 			}
! 			else if (agg->aggsplit == AGGSPLIT_INITIAL_SERIAL)
! 				install_partial_aggrefs = true;
! 		}
! 	}
  
+ 	/*
+ 	 *  AGGSPLIT_INITIAL_SERIAL might be there just to implement grouped base
+ 	 *  relation. Since the parent node does not necessarily call
+ 	 *  set_upper_references() (note that there might be various nodes between
+ 	 *  the initial and the final aggregation), special effort is needed to
+ 	 *  ensure that "grouped vars" are replaced with the corresponding
+ 	 *  expressions (see GroupedVarInfo).
+ 	 */
+ 	if (install_partial_aggrefs)
+ 		plan->targetlist = restore_grouping_expressions(root,
+ 														plan->targetlist);
+ 
+ 	subplan_itlist = build_tlist_index(subplan->targetlist);
  	output_targetlist = NIL;
+ 
  	foreach(l, plan->targetlist)
  	{
  		TargetEntry *tle = (TargetEntry *) lfirst(l);
*************** set_upper_references(PlannerInfo *root,
*** 1720,1725 ****
--- 1778,1787 ----
  					   OUTER_VAR,
  					   rtoffset);
  
+ 	/* Restore the original list if appropriate. */
+ 	if (sub_tlist_save != NIL)
+ 		subplan->targetlist = sub_tlist_save;
+ 
  	pfree(subplan_itlist);
  }
  
*************** convert_combining_aggrefs(Node *node, vo
*** 1796,1801 ****
--- 1858,1864 ----
  								   (void *) context);
  }
  
+ 
  /*
   * set_dummy_tlist_references
   *	  Replace the targetlist of an upper-level plan node with a simple
*************** fix_join_expr_mutator(Node *node, fix_jo
*** 2235,2240 ****
--- 2298,2304 ----
  								   (void *) context);
  }
  
+ 
  /*
   * fix_upper_expr
   *		Modifies an expression tree so that all Var nodes reference outputs
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
new file mode 100644
index 1bbbc29..e0b31c3
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
*************** plan_set_operations(PlannerInfo *root)
*** 208,214 ****
  	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)
--- 208,214 ----
  	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 3b7c56d..aefb79e
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
*************** set_cheapest(RelOptInfo *parent_rel)
*** 409,416 ****
   * Returns nothing, but modifies parent_rel->pathlist.
   */
  void
! add_path(RelOptInfo *parent_rel, Path *new_path)
  {
  	bool		accept_new = true;		/* unless we find a superior old path */
  	ListCell   *insert_after = NULL;	/* where to insert new item */
  	List	   *new_path_pathkeys;
--- 409,417 ----
   * Returns nothing, but modifies parent_rel->pathlist.
   */
  void
! add_path(RelOptInfo *parent_rel, Path *new_path, bool grouped)
  {
+ 	List	   *pathlist;
  	bool		accept_new = true;		/* unless we find a superior old path */
  	ListCell   *insert_after = NULL;	/* where to insert new item */
  	List	   *new_path_pathkeys;
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 427,432 ****
--- 428,435 ----
  	/* Pretend parameterized paths have no pathkeys, per comment above */
  	new_path_pathkeys = new_path->param_info ? NIL : new_path->pathkeys;
  
+ 	pathlist = !grouped ? parent_rel->pathlist : parent_rel->grouped_pathlist;
+ 
  	/*
  	 * Loop to check proposed new path against old paths.  Note it is possible
  	 * for more than one old path to be tossed out because new_path dominates
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 436,442 ****
  	 * list cell.
  	 */
  	p1_prev = NULL;
! 	for (p1 = list_head(parent_rel->pathlist); p1 != NULL; p1 = p1_next)
  	{
  		Path	   *old_path = (Path *) lfirst(p1);
  		bool		remove_old = false; /* unless new proves superior */
--- 439,445 ----
  	 * list cell.
  	 */
  	p1_prev = NULL;
! 	for (p1 = list_head(pathlist); p1 != NULL; p1 = p1_next)
  	{
  		Path	   *old_path = (Path *) lfirst(p1);
  		bool		remove_old = false; /* unless new proves superior */
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 582,589 ****
  		 */
  		if (remove_old)
  		{
! 			parent_rel->pathlist = list_delete_cell(parent_rel->pathlist,
! 													p1, p1_prev);
  
  			/*
  			 * Delete the data pointed-to by the deleted cell, if possible
--- 585,591 ----
  		 */
  		if (remove_old)
  		{
! 			pathlist = list_delete_cell(pathlist, p1, p1_prev);
  
  			/*
  			 * Delete the data pointed-to by the deleted cell, if possible
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 614,622 ****
  	{
  		/* Accept the new path: insert it at proper place in pathlist */
  		if (insert_after)
! 			lappend_cell(parent_rel->pathlist, insert_after, new_path);
  		else
! 			parent_rel->pathlist = lcons(new_path, parent_rel->pathlist);
  	}
  	else
  	{
--- 616,624 ----
  	{
  		/* 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);
  	}
  	else
  	{
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 624,629 ****
--- 626,636 ----
  		if (!IsA(new_path, IndexPath))
  			pfree(new_path);
  	}
+ 
+ 	if (!grouped)
+ 		parent_rel->pathlist = pathlist;
+ 	else
+ 		parent_rel->grouped_pathlist = pathlist;
  }
  
  /*
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 646,653 ****
  bool
  add_path_precheck(RelOptInfo *parent_rel,
  				  Cost startup_cost, Cost total_cost,
! 				  List *pathkeys, Relids required_outer)
  {
  	List	   *new_path_pathkeys;
  	bool		consider_startup;
  	ListCell   *p1;
--- 653,661 ----
  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
*** 658,664 ****
  	/* 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;
--- 666,674 ----
  	/* Decide whether new path's startup cost is interesting */
  	consider_startup = required_outer ? parent_rel->consider_param_startup : parent_rel->consider_startup;
  
! 	pathlist = !grouped ? parent_rel->pathlist : parent_rel->grouped_pathlist;
! 
! 	foreach(p1, pathlist)
  	{
  		Path	   *old_path = (Path *) lfirst(p1);
  		PathKeysComparison keyscmp;
*************** add_path_precheck(RelOptInfo *parent_rel
*** 750,772 ****
   *	  isn't an IndexPath.
   */
  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);
--- 760,786 ----
   *	  isn't an IndexPath.
   */
  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();
  
+ 	pathlist = !grouped ? parent_rel->partial_pathlist :
+ 		parent_rel->partial_grouped_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,
*** 820,831 ****
  		}
  
  		/*
! 		 * 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);
  			/* we should not see IndexPaths here, so always safe to delete */
  			Assert(!IsA(old_path, IndexPath));
  			pfree(old_path);
--- 834,844 ----
  		}
  
  		/*
! 		 * Remove current element from pathlist if dominated by new.
  		 */
  		if (remove_old)
  		{
! 			pathlist = list_delete_cell(pathlist, p1, p1_prev);
  			/* we should not see IndexPaths here, so always safe to delete */
  			Assert(!IsA(old_path, IndexPath));
  			pfree(old_path);
*************** add_partial_path(RelOptInfo *parent_rel,
*** 842,848 ****
  
  		/*
  		 * 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)
--- 855,861 ----
  
  		/*
  		 * If we found an old path that dominates new_path, we can quit
! 		 * scanning the pathlist; we will not add new_path, and we
  		 * assume new_path cannot dominate any later path.
  		 */
  		if (!accept_new)
*************** add_partial_path(RelOptInfo *parent_rel,
*** 853,862 ****
  	{
  		/* 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
  	{
--- 866,874 ----
  	{
  		/* 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);
  	}
  	else
  	{
*************** add_partial_path(RelOptInfo *parent_rel,
*** 865,870 ****
--- 877,887 ----
  		/* Reject and recycle the new path */
  		pfree(new_path);
  	}
+ 
+ 	if (!grouped)
+ 		parent_rel->partial_pathlist = pathlist;
+ 	else
+ 		parent_rel->partial_grouped_pathlist = pathlist;
  }
  
  /*
*************** add_partial_path(RelOptInfo *parent_rel,
*** 879,887 ****
   */
  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
--- 896,906 ----
   */
  bool
  add_partial_path_precheck(RelOptInfo *parent_rel, Cost total_cost,
! 						  List *pathkeys, bool grouped)
  {
  	ListCell   *p1;
+ 	List	   *pathlist = !grouped ? parent_rel->partial_pathlist :
+ 		parent_rel->partial_grouped_pathlist;
  
  	/*
  	 * Our goal here is twofold.  First, we want to find out whether this path
*************** add_partial_path_precheck(RelOptInfo *pa
*** 891,900 ****
  	 * 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;
--- 910,920 ----
  	 * 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
*** 923,929 ****
  	 * completion.
  	 */
  	if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! 						   NULL))
  		return false;
  
  	return true;
--- 943,949 ----
  	 * completion.
  	 */
  	if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! 						   NULL, false))
  		return false;
  
  	return true;
*************** create_hashjoin_path(PlannerInfo *root,
*** 2108,2120 ****
  					 Path *inner_path,
  					 List *restrict_clauses,
  					 Relids required_outer,
! 					 List *hashclauses)
  {
  	HashPath   *pathnode = makeNode(HashPath);
  
  	pathnode->jpath.path.pathtype = T_HashJoin;
  	pathnode->jpath.path.parent = joinrel;
! 	pathnode->jpath.path.pathtarget = joinrel->reltarget;
  	pathnode->jpath.path.param_info =
  		get_joinrel_parampathinfo(root,
  								  joinrel,
--- 2128,2142 ----
  					 Path *inner_path,
  					 List *restrict_clauses,
  					 Relids required_outer,
! 					 List *hashclauses,
! 					 bool grouped)
  {
  	HashPath   *pathnode = makeNode(HashPath);
  
  	pathnode->jpath.path.pathtype = T_HashJoin;
  	pathnode->jpath.path.parent = joinrel;
! 	pathnode->jpath.path.pathtarget = !grouped ?
! 		joinrel->reltarget : joinrel->reltarget_grouped;
  	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 7a8674d..126120f
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
*************** typedef struct JoinHashEntry
*** 33,39 ****
  } JoinHashEntry;
  
  static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! 					RelOptInfo *input_rel);
  static List *build_joinrel_restrictlist(PlannerInfo *root,
  						   RelOptInfo *joinrel,
  						   RelOptInfo *outer_rel,
--- 33,39 ----
  } JoinHashEntry;
  
  static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! 								RelOptInfo *input_rel, bool grouped);
  static List *build_joinrel_restrictlist(PlannerInfo *root,
  						   RelOptInfo *joinrel,
  						   RelOptInfo *outer_rel,
*************** build_simple_rel(PlannerInfo *root, int
*** 106,114 ****
--- 106,117 ----
  	rel->consider_param_startup = false;		/* might get changed later */
  	rel->consider_parallel = false;		/* might get changed later */
  	rel->reltarget = create_empty_pathtarget();
+ 	/* Not all relations have this target. */
+ 	rel->reltarget_grouped = NULL;
  	rel->pathlist = NIL;
  	rel->ppilist = NIL;
  	rel->partial_pathlist = NIL;
+ 	rel->partial_grouped_pathlist = NIL;
  	rel->cheapest_startup_path = NULL;
  	rel->cheapest_total_path = NULL;
  	rel->cheapest_unique_path = NULL;
*************** build_join_rel(PlannerInfo *root,
*** 371,379 ****
--- 374,385 ----
  	joinrel->consider_param_startup = false;
  	joinrel->consider_parallel = false;
  	joinrel->reltarget = create_empty_pathtarget();
+ 	/* Not all joins have this target. */
+ 	joinrel->reltarget_grouped = NULL;
  	joinrel->pathlist = NIL;
  	joinrel->ppilist = NIL;
  	joinrel->partial_pathlist = NIL;
+ 	joinrel->partial_grouped_pathlist = NIL;
  	joinrel->cheapest_startup_path = NULL;
  	joinrel->cheapest_total_path = NULL;
  	joinrel->cheapest_unique_path = NULL;
*************** build_join_rel(PlannerInfo *root,
*** 459,468 ****
  	 * and inner rels we first try to build it from.  But the contents should
  	 * be the same regardless.
  	 */
! 	build_joinrel_tlist(root, joinrel, outer_rel);
! 	build_joinrel_tlist(root, joinrel, inner_rel);
  	add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel);
  
  	/*
  	 * add_placeholders_to_joinrel also took care of adding the ph_lateral
  	 * sets of any PlaceHolderVars computed here to direct_lateral_relids, so
--- 465,488 ----
  	 * and inner rels we first try to build it from.  But the contents should
  	 * be the same regardless.
  	 */
! 	build_joinrel_tlist(root, joinrel, outer_rel, false);
! 	build_joinrel_tlist(root, joinrel, inner_rel, false);
  	add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel);
  
+ 	/* Build reltarget_grouped if appropriate. */
+ 	if (outer_rel->reltarget_grouped && inner_rel->reltarget_grouped &&
+ 		root->all_baserels_grouped)
+ 	{
+ 		build_joinrel_tlist(root, joinrel, outer_rel, true);
+ 		build_joinrel_tlist(root, joinrel, inner_rel, true);
+ 
+ 		/*
+ 		 * No need to add PHVs - if there were some, it'd mean that the join
+ 		 * is below nullable side of outer join, in which case no
+ 		 * pre-aggregation should take place.
+ 		 */
+ 	}
+ 
  	/*
  	 * add_placeholders_to_joinrel also took care of adding the ph_lateral
  	 * sets of any PlaceHolderVars computed here to direct_lateral_relids, so
*************** min_join_parameterization(PlannerInfo *r
*** 607,622 ****
   */
  static void
  build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! 					RelOptInfo *input_rel)
  {
  	Relids		relids = joinrel->relids;
  	ListCell   *vars;
  
! 	foreach(vars, input_rel->reltarget->exprs)
  	{
  		Var		   *var = (Var *) lfirst(vars);
  		RelOptInfo *baserel;
  		int			ndx;
  
  		/*
  		 * Ignore PlaceHolderVars in the input tlists; we'll make our own
--- 627,665 ----
   */
  static void
  build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! 					RelOptInfo *input_rel, bool grouped)
  {
  	Relids		relids = joinrel->relids;
+ 	PathTarget *reltarget_input, *reltarget_result;
  	ListCell   *vars;
+ 	int			i = -1;
  
! 	if (!grouped)
! 	{
! 		reltarget_input = input_rel->reltarget;
! 		reltarget_result = joinrel->reltarget;
! 	}
! 	else
! 	{
! 		reltarget_input = input_rel->reltarget_grouped;
! 		/* Shouldn't be called otherwise. */
! 		Assert(reltarget_input != NULL);
! 
! 		/* Called first time for this joinrel? */
! 		if (joinrel->reltarget_grouped == NULL)
! 			joinrel->reltarget_grouped = create_empty_pathtarget();
! 
! 		reltarget_result = joinrel->reltarget_grouped;
! 	}
! 
! 	foreach(vars, reltarget_input->exprs)
  	{
  		Var		   *var = (Var *) lfirst(vars);
  		RelOptInfo *baserel;
  		int			ndx;
+ 		Index		sortgroupref = 0;
+ 
+ 		i++;
  
  		/*
  		 * Ignore PlaceHolderVars in the input tlists; we'll make our own
*************** build_joinrel_tlist(PlannerInfo *root, R
*** 642,650 ****
  		if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
  		{
  			/* Yup, add it to the output */
! 			joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs, var);
  			/* Vars have cost zero, so no need to adjust reltarget->cost */
! 			joinrel->reltarget->width += baserel->attr_widths[ndx];
  		}
  	}
  }
--- 685,697 ----
  		if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
  		{
  			/* Yup, add it to the output */
! 			if (reltarget_input->sortgrouprefs)
! 				sortgroupref = reltarget_input->sortgrouprefs[i];
! 			add_column_to_pathtarget(reltarget_result, (Expr *) var,
! 									 sortgroupref);
! 
  			/* Vars have cost zero, so no need to adjust reltarget->cost */
! 			reltarget_result->width += baserel->attr_widths[ndx];
  		}
  	}
  }
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
new file mode 100644
index 45205a8..423a39e
*** a/src/backend/optimizer/util/tlist.c
--- b/src/backend/optimizer/util/tlist.c
*************** apply_pathtarget_labeling_to_tlist(List
*** 759,761 ****
--- 759,896 ----
  		i++;
  	}
  }
+ 
+ /*
+  * Replace each "grouped var" in the source target with its "intermediate
+  * expression" if one exists, i.e. apply the effect of joins to the result of
+  * partial aggregation emitted by base relation.
+  */
+ PathTarget *
+ create_intermediate_grouping_target(PlannerInfo *root, PathTarget *src)
+ {
+ 	PathTarget	*result = create_empty_pathtarget();
+ 	int		i = 0;
+ 	ListCell	*l;
+ 
+ 	foreach(l, src->exprs)
+ 	{
+ 		Expr	*expr, *expr_new;
+ 		Index	sortgroupref = 0;
+ 
+ 		if (src->sortgrouprefs)
+ 			sortgroupref = src->sortgrouprefs[i];
+ 
+ 		expr_new = expr = (Expr *) lfirst(l);
+ 		if (IsA(expr, Var))
+ 		{
+ 			Var	*var = (Var *) expr;
+ 			ListCell	*lc;
+ 
+ 			foreach(lc, root->grouped_var_list)
+ 			{
+ 				GroupedVarInfo *gvi = (GroupedVarInfo *) lfirst(lc);
+ 
+ 				if (gvi->expr_intermediate &&
+ 					gvi->var->varno == var->varno &&
+ 					gvi->var->varattno == var->varattno)
+ 				{
+ 					/* XXX Need a copy? */
+ 					expr_new = gvi->expr_intermediate;
+ 					break;
+ 				}
+ 			}
+ 		}
+ 		add_column_to_pathtarget(result, expr_new, sortgroupref);
+ 
+ 		i++;
+ 	}
+ 
+ 	return result;
+ }
+ 
+ /*
+  * Replace each "grouped var" in the source targetlist with the original
+  * expression. This includes the items already replaced by "intermediate
+  * expression", see create_intermediate_grouping_target.
+  *
+  * TODO Think of more suitable name. Although "grouped var" may substitute for
+  * grouping expressions in the future, currently Aggref is the only outcome of
+  * the replacement. undo_grouped_var_substitutions?
+  */
+ List *
+ restore_grouping_expressions(PlannerInfo *root, List *src)
+ {
+ 	List	*result = NIL;
+ 	ListCell	*l;
+ 
+ 	foreach(l, src)
+ 	{
+ 		TargetEntry	*te, *te_new;
+ 		Expr	*expr_new = NULL;
+ 
+ 		te = (TargetEntry *) lfirst(l);
+ 
+ 		if (IsA(te->expr, Var))
+ 		{
+ 			Var	*var = (Var *) te->expr;
+ 
+ 			expr_new = find_grouped_var_expr(root, var);
+ 		}
+ 		else
+ 		{
+ 			/* Is this the "intermediate expression"? */
+ 			ListCell	*lc;
+ 
+ 			foreach(lc, root->grouped_var_list)
+ 			{
+ 				GroupedVarInfo *gvi = (GroupedVarInfo *) lfirst(lc);
+ 
+ 				/*
+ 				 * If expr_intermediate was valid, the var should already have
+ 				 * been replaced by the "intermediate expression" (which
+ 				 * should be non-Var).
+ 				 */
+ 				if (gvi->expr_intermediate != NULL &&
+ 					equal(te->expr, gvi->expr_intermediate))
+ 				{
+ 					/* XXX Need a copy? */
+ 					expr_new = gvi->expr;
+ 					break;
+ 				}
+ 			}
+ 		}
+ 		if (expr_new != NULL)
+ 		{
+ 			te_new = flatCopyTargetEntry(te);
+ 			te_new->expr = expr_new;
+ 		}
+ 		else
+ 			te_new = te;
+ 		result = lappend(result, te_new);
+ 	}
+ 
+ 	return result;
+ }
+ 
+ /*
+  * Find the expression that we've replaced with grouped var earlier.
+  */
+ Expr *
+ find_grouped_var_expr(PlannerInfo *root, Var *var)
+ {
+ 	ListCell	*lc;
+ 
+ 	foreach(lc, root->grouped_var_list)
+ 	{
+ 		GroupedVarInfo *gvi = (GroupedVarInfo *) lfirst(lc);
+ 
+ 		if (gvi->var->varno == var->varno &&
+ 			gvi->var->varattno == var->varattno)
+ 		{
+ 			/* XXX Need a copy? */
+ 			return gvi->expr;
+ 		}
+ 	}
+ 
+ 	return NULL;
+ }
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
new file mode 100644
index 1297960..ed521bb
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 95,100 ****
--- 95,101 ----
  	FuncDetailCode fdresult;
  	char		aggkind = 0;
  	ParseCallbackState pcbstate;
+ 	Oid			aggtransmultifn = InvalidOid;
  
  	/*
  	 * If there's an aggregate filter, transform it using transformWhereClause
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 318,323 ****
--- 319,325 ----
  		classForm = (Form_pg_aggregate) GETSTRUCT(tup);
  		aggkind = classForm->aggkind;
  		catDirectArgs = classForm->aggnumdirectargs;
+ 		aggtransmultifn = classForm->aggtransmultifn;
  		ReleaseSysCache(tup);
  
  		/* Now check various disallowed cases. */
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 662,667 ****
--- 664,670 ----
  		aggref->aggstar = agg_star;
  		aggref->aggvariadic = func_variadic;
  		aggref->aggkind = aggkind;
+ 		aggref->aggtransmultifn = aggtransmultifn;
  		/* agglevelsup will be set by transformAggregateCall */
  		aggref->aggsplit = AGGSPLIT_SIMPLE;		/* planner might change this */
  		aggref->location = location;
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
new file mode 100644
index 86b46de..be7ef22
*** a/src/backend/utils/adt/float.c
--- b/src/backend/utils/adt/float.c
*************** float4_accum(PG_FUNCTION_ARGS)
*** 2630,2635 ****
--- 2630,2663 ----
  	}
  }
  
+ /*
+  * State multiplication function for sum(float4) aggregate.
+  */
+ Datum
+ float4_sum_mul(PG_FUNCTION_ARGS)
+ {
+ 	float4	state = PG_GETARG_FLOAT4(0);
+ 	int64	factor;
+ 
+ #ifndef USE_FLOAT8_BYVAL	/* controls int8 too */
+ 	{
+ 		int64	*factor_p = (int64 *) PG_GETARG_POINTER(1);
+ 
+ 		factor = *factor_p;
+ 	}
+ #else
+ 	factor = PG_GETARG_INT64(1);
+ #endif
+ 
+ 	/*
+ 	 * Sum of a single group is added each time the same input set is
+ 	 * aggregated again.
+ 	 */
+ 	state *= (float8) factor;
+ 
+ 	PG_RETURN_FLOAT4(state);
+ }
+ 
  Datum
  float8_avg(PG_FUNCTION_ARGS)
  {
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
new file mode 100644
index 301dffa..063adbb
*** a/src/backend/utils/adt/selfuncs.c
--- b/src/backend/utils/adt/selfuncs.c
***************
*** 111,116 ****
--- 111,117 ----
  #include "catalog/pg_statistic.h"
  #include "catalog/pg_type.h"
  #include "executor/executor.h"
+ #include "executor/nodeAgg.h"
  #include "mb/pg_wchar.h"
  #include "nodes/makefuncs.h"
  #include "nodes/nodeFuncs.h"
*************** estimate_hash_bucketsize(PlannerInfo *ro
*** 3657,3662 ****
--- 3658,3691 ----
  	return (Selectivity) estfract;
  }
  
+ /*
+  * estimate_hashagg_tablesize
+  *	  estimate the number of bytes that a hash aggregate hashtable will
+  *	  require based on the agg_costs, path width and dNumGroups.
+  */
+ 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/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
new file mode 100644
index 1ffde6c..f0b5498
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
***************
*** 32,37 ****
--- 32,38 ----
   *	aggkind				aggregate kind, see AGGKIND_ categories below
   *	aggnumdirectargs	number of arguments that are "direct" arguments
   *	aggtransfn			transition function
+  *	aggtransmultifn		transition state multiplication function
   *	aggfinalfn			final function (0 if none)
   *	aggcombinefn		combine function (0 if none)
   *	aggserialfn			function to convert transtype to bytea (0 if none)
*************** CATALOG(pg_aggregate,2600) BKI_WITHOUT_O
*** 58,63 ****
--- 59,65 ----
  	char		aggkind;
  	int16		aggnumdirectargs;
  	regproc		aggtransfn;
+ 	regproc		aggtransmultifn;
  	regproc		aggfinalfn;
  	regproc		aggcombinefn;
  	regproc		aggserialfn;
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 91,117 ****
   * ----------------
   */
  
! #define Natts_pg_aggregate					20
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_aggfinalfn		5
! #define Anum_pg_aggregate_aggcombinefn		6
! #define Anum_pg_aggregate_aggserialfn		7
! #define Anum_pg_aggregate_aggdeserialfn		8
! #define Anum_pg_aggregate_aggmtransfn		9
! #define Anum_pg_aggregate_aggminvtransfn	10
! #define Anum_pg_aggregate_aggmfinalfn		11
! #define Anum_pg_aggregate_aggfinalextra		12
! #define Anum_pg_aggregate_aggmfinalextra	13
! #define Anum_pg_aggregate_aggsortop			14
! #define Anum_pg_aggregate_aggtranstype		15
! #define Anum_pg_aggregate_aggtransspace		16
! #define Anum_pg_aggregate_aggmtranstype		17
! #define Anum_pg_aggregate_aggmtransspace	18
! #define Anum_pg_aggregate_agginitval		19
! #define Anum_pg_aggregate_aggminitval		20
  
  /*
   * Symbolic values for aggkind column.  We distinguish normal aggregates
--- 93,120 ----
   * ----------------
   */
  
! #define Natts_pg_aggregate					21
  #define Anum_pg_aggregate_aggfnoid			1
  #define Anum_pg_aggregate_aggkind			2
  #define Anum_pg_aggregate_aggnumdirectargs	3
  #define Anum_pg_aggregate_aggtransfn		4
! #define Anum_pg_aggregate_aggtransmultifn	5
! #define Anum_pg_aggregate_aggfinalfn		6
! #define Anum_pg_aggregate_aggcombinefn		7
! #define Anum_pg_aggregate_aggserialfn		8
! #define Anum_pg_aggregate_aggdeserialfn		9
! #define Anum_pg_aggregate_aggmtransfn		10
! #define Anum_pg_aggregate_aggminvtransfn	11
! #define Anum_pg_aggregate_aggmfinalfn		12
! #define Anum_pg_aggregate_aggfinalextra		13
! #define Anum_pg_aggregate_aggmfinalextra	14
! #define Anum_pg_aggregate_aggsortop			15
! #define Anum_pg_aggregate_aggtranstype		16
! #define Anum_pg_aggregate_aggtransspace		17
! #define Anum_pg_aggregate_aggmtranstype		18
! #define Anum_pg_aggregate_aggmtransspace	19
! #define Anum_pg_aggregate_agginitval		20
! #define Anum_pg_aggregate_aggminitval		21
  
  /*
   * Symbolic values for aggkind column.  We distinguish normal aggregates
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 135,318 ****
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum		numeric_poly_avg	int8_avg_combine	int8_avg_serialize		int8_avg_deserialize	int8_avg_accum	int8_avg_accum_inv	numeric_poly_avg	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum		int8_avg			int4_avg_combine	-						-						int4_avg_accum	int4_avg_accum_inv	int8_avg			f f 0	1016	0	1016	0	"{0,0}" "{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum		int8_avg			int4_avg_combine	-						-						int2_avg_accum	int2_avg_accum_inv	int8_avg			f f 0	1016	0	1016	0	"{0,0}" "{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	numeric_avg			numeric_avg_combine numeric_avg_serialize	numeric_avg_deserialize numeric_avg_accum numeric_accum_inv numeric_avg			f f 0	2281	128 2281	128 _null_ _null_ ));
! DATA(insert ( 2104	n 0 float4_accum		float8_avg			float8_combine		-						-						-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2105	n 0 float8_accum		float8_avg			float8_combine		-						-						-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2106	n 0 interval_accum		interval_avg		interval_combine	-						-						interval_accum	interval_accum_inv	interval_avg		f f 0	1187	0	1187	0	"{0 second,0 second}" "{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum		numeric_poly_sum	int8_avg_combine	int8_avg_serialize		int8_avg_deserialize	int8_avg_accum	int8_avg_accum_inv	numeric_poly_sum	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2108	n 0 int4_sum			-					int8pl				-						-						int4_avg_accum	int4_avg_accum_inv	int2int4_sum		f f 0	20		0	1016	0	_null_ "{0,0}" ));
! DATA(insert ( 2109	n 0 int2_sum			-					int8pl				-						-						int2_avg_accum	int2_avg_accum_inv	int2int4_sum		f f 0	20		0	1016	0	_null_ "{0,0}" ));
! DATA(insert ( 2110	n 0 float4pl			-					float4pl			-						-						-				-					-					f f 0	700		0	0		0	_null_ _null_ ));
! DATA(insert ( 2111	n 0 float8pl			-					float8pl			-						-						-				-					-					f f 0	701		0	0		0	_null_ _null_ ));
! DATA(insert ( 2112	n 0 cash_pl				-					cash_pl				-						-						cash_pl			cash_mi				-					f f 0	790		0	790		0	_null_ _null_ ));
! DATA(insert ( 2113	n 0 interval_pl			-					interval_pl			-						-						interval_pl		interval_mi			-					f f 0	1186	0	1186	0	_null_ _null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	numeric_sum			numeric_avg_combine numeric_avg_serialize	numeric_avg_deserialize numeric_avg_accum numeric_accum_inv numeric_sum			f f 0	2281	128 2281	128 _null_ _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		-				int8larger			-	-	-				-				-				f f 413		20		0	0		0	_null_ _null_ ));
! DATA(insert ( 2116	n 0 int4larger		-				int4larger			-	-	-				-				-				f f 521		23		0	0		0	_null_ _null_ ));
! DATA(insert ( 2117	n 0 int2larger		-				int2larger			-	-	-				-				-				f f 520		21		0	0		0	_null_ _null_ ));
! DATA(insert ( 2118	n 0 oidlarger		-				oidlarger			-	-	-				-				-				f f 610		26		0	0		0	_null_ _null_ ));
! DATA(insert ( 2119	n 0 float4larger	-				float4larger		-	-	-				-				-				f f 623		700		0	0		0	_null_ _null_ ));
! DATA(insert ( 2120	n 0 float8larger	-				float8larger		-	-	-				-				-				f f 674		701		0	0		0	_null_ _null_ ));
! DATA(insert ( 2121	n 0 int4larger		-				int4larger			-	-	-				-				-				f f 563		702		0	0		0	_null_ _null_ ));
! DATA(insert ( 2122	n 0 date_larger		-				date_larger			-	-	-				-				-				f f 1097	1082	0	0		0	_null_ _null_ ));
! DATA(insert ( 2123	n 0 time_larger		-				time_larger			-	-	-				-				-				f f 1112	1083	0	0		0	_null_ _null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	-				timetz_larger		-	-	-				-				-				f f 1554	1266	0	0		0	_null_ _null_ ));
! DATA(insert ( 2125	n 0 cashlarger		-				cashlarger			-	-	-				-				-				f f 903		790		0	0		0	_null_ _null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	-			timestamp_larger	-	-	-				-				-				f f 2064	1114	0	0		0	_null_ _null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	-			timestamptz_larger	-	-	-				-				-				f f 1324	1184	0	0		0	_null_ _null_ ));
! DATA(insert ( 2128	n 0 interval_larger -				interval_larger		-	-	-				-				-				f f 1334	1186	0	0		0	_null_ _null_ ));
! DATA(insert ( 2129	n 0 text_larger		-				text_larger			-	-	-				-				-				f f 666		25		0	0		0	_null_ _null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	-				numeric_larger		-	-	-				-				-				f f 1756	1700	0	0		0	_null_ _null_ ));
! DATA(insert ( 2050	n 0 array_larger	-				array_larger		-	-	-				-				-				f f 1073	2277	0	0		0	_null_ _null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	-				bpchar_larger		-	-	-				-				-				f f 1060	1042	0	0		0	_null_ _null_ ));
! DATA(insert ( 2797	n 0 tidlarger		-				tidlarger			-	-	-				-				-				f f 2800	27		0	0		0	_null_ _null_ ));
! DATA(insert ( 3526	n 0 enum_larger		-				enum_larger			-	-	-				-				-				f f 3519	3500	0	0		0	_null_ _null_ ));
! DATA(insert ( 3564	n 0 network_larger	-				network_larger		-	-	-				-				-				f f 1205	869		0	0		0	_null_ _null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		-				int8smaller			-	-	-				-				-				f f 412		20		0	0		0	_null_ _null_ ));
! DATA(insert ( 2132	n 0 int4smaller		-				int4smaller			-	-	-				-				-				f f 97		23		0	0		0	_null_ _null_ ));
! DATA(insert ( 2133	n 0 int2smaller		-				int2smaller			-	-	-				-				-				f f 95		21		0	0		0	_null_ _null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		-				oidsmaller			-	-	-				-				-				f f 609		26		0	0		0	_null_ _null_ ));
! DATA(insert ( 2135	n 0 float4smaller	-				float4smaller		-	-	-				-				-				f f 622		700		0	0		0	_null_ _null_ ));
! DATA(insert ( 2136	n 0 float8smaller	-				float8smaller		-	-	-				-				-				f f 672		701		0	0		0	_null_ _null_ ));
! DATA(insert ( 2137	n 0 int4smaller		-				int4smaller			-	-	-				-				-				f f 562		702		0	0		0	_null_ _null_ ));
! DATA(insert ( 2138	n 0 date_smaller	-				date_smaller		-	-	-				-				-				f f 1095	1082	0	0		0	_null_ _null_ ));
! DATA(insert ( 2139	n 0 time_smaller	-				time_smaller		-	-	-				-				-				f f 1110	1083	0	0		0	_null_ _null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	-				timetz_smaller		-	-	-				-				-				f f 1552	1266	0	0		0	_null_ _null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		-				cashsmaller			-	-	-				-				-				f f 902		790		0	0		0	_null_ _null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	-			timestamp_smaller	-	-	-				-				-				f f 2062	1114	0	0		0	_null_ _null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller -			timestamptz_smaller -	-	-				-				-				f f 1322	1184	0	0		0	_null_ _null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	-			interval_smaller	-	-	-				-				-				f f 1332	1186	0	0		0	_null_ _null_ ));
! DATA(insert ( 2145	n 0 text_smaller	-				text_smaller		-	-	-				-				-				f f 664		25		0	0		0	_null_ _null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller -				numeric_smaller		-	-	-				-				-				f f 1754	1700	0	0		0	_null_ _null_ ));
! DATA(insert ( 2051	n 0 array_smaller	-				array_smaller		-	-	-				-				-				f f 1072	2277	0	0		0	_null_ _null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	-				bpchar_smaller		-	-	-				-				-				f f 1058	1042	0	0		0	_null_ _null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		-				tidsmaller			-	-	-				-				-				f f 2799	27		0	0		0	_null_ _null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	-				enum_smaller		-	-	-				-				-				f f 3518	3500	0	0		0	_null_ _null_ ));
! DATA(insert ( 3565	n 0 network_smaller -				network_smaller		-	-	-				-				-				f f 1203	869		0	0		0	_null_ _null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		-				int8pl	-	-	int8inc_any		int8dec_any		-				f f 0		20		0	20		0	"0" "0" ));
! DATA(insert ( 2803	n 0 int8inc			-				int8pl	-	-	int8inc			int8dec			-				f f 0		20		0	20		0	"0" "0" ));
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum		numeric_var_pop			numeric_combine			numeric_serialize		numeric_deserialize			int8_accum		int8_accum_inv	numeric_var_pop			f f 0	2281	128 2281	128 _null_ _null_ ));
! DATA(insert ( 2719	n 0 int4_accum		numeric_poly_var_pop	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int4_accum		int4_accum_inv	numeric_poly_var_pop	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2720	n 0 int2_accum		numeric_poly_var_pop	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int2_accum		int2_accum_inv	numeric_poly_var_pop	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	float8_var_pop			float8_combine			-						-							-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2722	n 0 float8_accum	float8_var_pop			float8_combine			-						-							-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2723	n 0 numeric_accum	numeric_var_pop			numeric_combine			numeric_serialize		numeric_deserialize			numeric_accum	numeric_accum_inv numeric_var_pop		f f 0	2281	128 2281	128 _null_ _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum		numeric_var_samp		numeric_combine			numeric_serialize		numeric_deserialize			int8_accum		int8_accum_inv	numeric_var_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
! DATA(insert ( 2642	n 0 int4_accum		numeric_poly_var_samp	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int4_accum		int4_accum_inv	numeric_poly_var_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2643	n 0 int2_accum		numeric_poly_var_samp	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int2_accum		int2_accum_inv	numeric_poly_var_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	float8_var_samp			float8_combine			-						-							-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2645	n 0 float8_accum	float8_var_samp			float8_combine			-						-							-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2646	n 0 numeric_accum	numeric_var_samp		numeric_combine			numeric_serialize		numeric_deserialize			numeric_accum	numeric_accum_inv numeric_var_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum		numeric_var_samp		numeric_combine			numeric_serialize		numeric_deserialize			int8_accum		int8_accum_inv	numeric_var_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
! DATA(insert ( 2149	n 0 int4_accum		numeric_poly_var_samp	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int4_accum		int4_accum_inv	numeric_poly_var_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2150	n 0 int2_accum		numeric_poly_var_samp	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int2_accum		int2_accum_inv	numeric_poly_var_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	float8_var_samp			float8_combine			-						-							-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2152	n 0 float8_accum	float8_var_samp			float8_combine			-						-							-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2153	n 0 numeric_accum	numeric_var_samp		numeric_combine			numeric_serialize		numeric_deserialize			numeric_accum	numeric_accum_inv numeric_var_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum		numeric_stddev_pop		numeric_combine			numeric_serialize		numeric_deserialize			int8_accum		int8_accum_inv	numeric_stddev_pop		f f 0	2281	128 2281	128 _null_ _null_ ));
! DATA(insert ( 2725	n 0 int4_accum		numeric_poly_stddev_pop numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int4_accum		int4_accum_inv	numeric_poly_stddev_pop f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2726	n 0 int2_accum		numeric_poly_stddev_pop numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int2_accum		int2_accum_inv	numeric_poly_stddev_pop f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	float8_stddev_pop		float8_combine			-						-							-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2728	n 0 float8_accum	float8_stddev_pop		float8_combine			-						-							-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2729	n 0 numeric_accum	numeric_stddev_pop		numeric_combine			numeric_serialize		numeric_deserialize			numeric_accum	numeric_accum_inv numeric_stddev_pop	f f 0	2281	128 2281	128 _null_ _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum		numeric_stddev_samp			numeric_combine			numeric_serialize		numeric_deserialize			int8_accum	int8_accum_inv	numeric_stddev_samp			f f 0	2281	128 2281	128 _null_ _null_ ));
! DATA(insert ( 2713	n 0 int4_accum		numeric_poly_stddev_samp	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int4_accum	int4_accum_inv	numeric_poly_stddev_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2714	n 0 int2_accum		numeric_poly_stddev_samp	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int2_accum	int2_accum_inv	numeric_poly_stddev_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	float8_stddev_samp			float8_combine			-						-							-			-				-							f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2716	n 0 float8_accum	float8_stddev_samp			float8_combine			-						-							-			-				-							f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2717	n 0 numeric_accum	numeric_stddev_samp			numeric_combine			numeric_serialize		numeric_deserialize			numeric_accum numeric_accum_inv numeric_stddev_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum		numeric_stddev_samp			numeric_combine			numeric_serialize		numeric_deserialize			int8_accum		int8_accum_inv	numeric_stddev_samp			f f 0	2281	128 2281	128 _null_ _null_ ));
! DATA(insert ( 2155	n 0 int4_accum		numeric_poly_stddev_samp	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int4_accum		int4_accum_inv	numeric_poly_stddev_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2156	n 0 int2_accum		numeric_poly_stddev_samp	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int2_accum		int2_accum_inv	numeric_poly_stddev_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	float8_stddev_samp			float8_combine			-						-							-				-				-							f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2158	n 0 float8_accum	float8_stddev_samp			float8_combine			-						-							-				-				-							f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2159	n 0 numeric_accum	numeric_stddev_samp			numeric_combine			numeric_serialize		numeric_deserialize			numeric_accum	numeric_accum_inv numeric_stddev_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8	-					int8pl				-	-	-				-				-			f f 0	20		0	0		0	"0" _null_ ));
! DATA(insert ( 2819	n 0 float8_regr_accum	float8_regr_sxx			float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2820	n 0 float8_regr_accum	float8_regr_syy			float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2821	n 0 float8_regr_accum	float8_regr_sxy			float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2822	n 0 float8_regr_accum	float8_regr_avgx		float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2823	n 0 float8_regr_accum	float8_regr_avgy		float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2824	n 0 float8_regr_accum	float8_regr_r2			float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2825	n 0 float8_regr_accum	float8_regr_slope		float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2826	n 0 float8_regr_accum	float8_regr_intercept	float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2827	n 0 float8_regr_accum	float8_covar_pop		float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2828	n 0 float8_regr_accum	float8_covar_samp		float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2829	n 0 float8_regr_accum	float8_corr				float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	-	booland_statefunc	-	-	bool_accum	bool_accum_inv	bool_alltrue	f f 58	16	0	2281	16	_null_ _null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	-	boolor_statefunc	-	-	bool_accum	bool_accum_inv	bool_anytrue	f f 59	16	0	2281	16	_null_ _null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	-	booland_statefunc	-	-	bool_accum	bool_accum_inv	bool_alltrue	f f 58	16	0	2281	16	_null_ _null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		-				int2and -	-	-				-				-				f f 0	21		0	0		0	_null_ _null_ ));
! DATA(insert ( 2237	n 0 int2or		-				int2or	-	-	-				-				-				f f 0	21		0	0		0	_null_ _null_ ));
! DATA(insert ( 2238	n 0 int4and		-				int4and -	-	-				-				-				f f 0	23		0	0		0	_null_ _null_ ));
! DATA(insert ( 2239	n 0 int4or		-				int4or	-	-	-				-				-				f f 0	23		0	0		0	_null_ _null_ ));
! DATA(insert ( 2240	n 0 int8and		-				int8and -	-	-				-				-				f f 0	20		0	0		0	_null_ _null_ ));
! DATA(insert ( 2241	n 0 int8or		-				int8or	-	-	-				-				-				f f 0	20		0	0		0	_null_ _null_ ));
! DATA(insert ( 2242	n 0 bitand		-				bitand	-	-	-				-				-				f f 0	1560	0	0		0	_null_ _null_ ));
! DATA(insert ( 2243	n 0 bitor		-				bitor	-	-	-				-				-				f f 0	1560	0	0		0	_null_ _null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	-				-		-	-	-				-				-				f f 0	142		0	0		0	_null_ _null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn		array_agg_finalfn		-	-	-	-		-				-				t f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 4053	n 0 array_agg_array_transfn array_agg_array_finalfn -	-	-	-		-				-				t f 0	2281	0	0		0	_null_ _null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	string_agg_finalfn	-	-	-	-				-				-				f f 0	2281	0	0		0	_null_ _null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	bytea_string_agg_finalfn	-	-	-	-				-				-		f f 0	2281	0	0		0	_null_ _null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn	json_agg_finalfn			-	-	-	-				-				-				f f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3197	n 0 json_object_agg_transfn json_object_agg_finalfn -	-	-	-				-				-				f f 0	2281	0	0		0	_null_ _null_ ));
  
  /* jsonb */
! DATA(insert ( 3267	n 0 jsonb_agg_transfn	jsonb_agg_finalfn				-	-	-	-				-				-			f f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3270	n 0 jsonb_object_agg_transfn jsonb_object_agg_finalfn	-	-	-	-				-				-			f f 0	2281	0	0		0	_null_ _null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			percentile_disc_final					-	-	-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			percentile_cont_float8_final			-	-	-	-		-		-		f f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			percentile_cont_interval_final			-	-	-	-		-		-		f f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			percentile_disc_multi_final				-	-	-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			percentile_cont_float8_multi_final		-	-	-	-		-		-		f f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			percentile_cont_interval_multi_final	-	-	-	-		-		-		f f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			mode_final								-	-	-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	rank_final								-	-	-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	percent_rank_final						-	-	-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	cume_dist_final							-	-	-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	dense_rank_final						-	-	-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
  
  
  /*
--- 138,322 ----
   */
  
  /* avg */
! DATA(insert ( 2100	n 0 int8_avg_accum		0	numeric_poly_avg	int8_avg_combine	int8_avg_serialize		int8_avg_deserialize	int8_avg_accum	int8_avg_accum_inv	numeric_poly_avg	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2101	n 0 int4_avg_accum		0	int8_avg			int4_avg_combine	-						-						int4_avg_accum	int4_avg_accum_inv	int8_avg			f f 0	1016	0	1016	0	"{0,0}" "{0,0}" ));
! DATA(insert ( 2102	n 0 int2_avg_accum		0	int8_avg			int4_avg_combine	-						-						int2_avg_accum	int2_avg_accum_inv	int8_avg			f f 0	1016	0	1016	0	"{0,0}" "{0,0}" ));
! DATA(insert ( 2103	n 0 numeric_avg_accum	0	numeric_avg			numeric_avg_combine numeric_avg_serialize	numeric_avg_deserialize numeric_avg_accum numeric_accum_inv numeric_avg			f f 0	2281	128 2281	128 _null_ _null_ ));
! DATA(insert ( 2104	n 0 float4_accum		0	float8_avg			float8_combine		-						-						-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2105	n 0 float8_accum		0	float8_avg			float8_combine		-						-						-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2106	n 0 interval_accum		0	interval_avg		interval_combine	-						-						interval_accum	interval_accum_inv	interval_avg		f f 0	1187	0	1187	0	"{0 second,0 second}" "{0 second,0 second}" ));
  
  /* sum */
! DATA(insert ( 2107	n 0 int8_avg_accum		0	numeric_poly_sum	int8_avg_combine	int8_avg_serialize		int8_avg_deserialize	int8_avg_accum	int8_avg_accum_inv	numeric_poly_sum	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2108	n 0 int4_sum			0	-					int8pl				-						-						int4_avg_accum	int4_avg_accum_inv	int2int4_sum		f f 0	20		0	1016	0	_null_ "{0,0}" ));
! DATA(insert ( 2109	n 0 int2_sum			0	-					int8pl				-						-						int2_avg_accum	int2_avg_accum_inv	int2int4_sum		f f 0	20		0	1016	0	_null_ "{0,0}" ));
! DATA(insert ( 2110	n 0 float4pl			4002	-					float4pl			-						-						-				-					-					f f 0	700		0	0		0	_null_ _null_ ));
! DATA(insert ( 2111	n 0 float8pl			0	-					float8pl			-						-						-				-					-					f f 0	701		0	0		0	_null_ _null_ ));
! DATA(insert ( 2112	n 0 cash_pl				0	-					cash_pl				-						-						cash_pl			cash_mi				-					f f 0	790		0	790		0	_null_ _null_ ));
! DATA(insert ( 2113	n 0 interval_pl			0	-					interval_pl			-						-						interval_pl		interval_mi			-					f f 0	1186	0	1186	0	_null_ _null_ ));
! DATA(insert ( 2114	n 0 numeric_avg_accum	0	numeric_sum			numeric_avg_combine numeric_avg_serialize	numeric_avg_deserialize numeric_avg_accum numeric_accum_inv numeric_sum			f f 0	2281	128 2281	128 _null_ _null_ ));
  
  /* max */
! DATA(insert ( 2115	n 0 int8larger		0	-				int8larger			-	-	-				-				-				f f 413		20		0	0		0	_null_ _null_ ));
! DATA(insert ( 2116	n 0 int4larger		0	-				int4larger			-	-	-				-				-				f f 521		23		0	0		0	_null_ _null_ ));
! DATA(insert ( 2117	n 0 int2larger		0	-				int2larger			-	-	-				-				-				f f 520		21		0	0		0	_null_ _null_ ));
! DATA(insert ( 2118	n 0 oidlarger		0	-				oidlarger			-	-	-				-				-				f f 610		26		0	0		0	_null_ _null_ ));
! DATA(insert ( 2119	n 0 float4larger	0	-				float4larger		-	-	-				-				-				f f 623		700		0	0		0	_null_ _null_ ));
! DATA(insert ( 2120	n 0 float8larger	0	-				float8larger		-	-	-				-				-				f f 674		701		0	0		0	_null_ _null_ ));
! DATA(insert ( 2121	n 0 int4larger		0	-				int4larger			-	-	-				-				-				f f 563		702		0	0		0	_null_ _null_ ));
! DATA(insert ( 2122	n 0 date_larger		0	-				date_larger			-	-	-				-				-				f f 1097	1082	0	0		0	_null_ _null_ ));
! DATA(insert ( 2123	n 0 time_larger		0	-				time_larger			-	-	-				-				-				f f 1112	1083	0	0		0	_null_ _null_ ));
! DATA(insert ( 2124	n 0 timetz_larger	0	-				timetz_larger		-	-	-				-				-				f f 1554	1266	0	0		0	_null_ _null_ ));
! DATA(insert ( 2125	n 0 cashlarger		0	-				cashlarger			-	-	-				-				-				f f 903		790		0	0		0	_null_ _null_ ));
! DATA(insert ( 2126	n 0 timestamp_larger	0	-			timestamp_larger	-	-	-				-				-				f f 2064	1114	0	0		0	_null_ _null_ ));
! DATA(insert ( 2127	n 0 timestamptz_larger	0	-			timestamptz_larger	-	-	-				-				-				f f 1324	1184	0	0		0	_null_ _null_ ));
! DATA(insert ( 2128	n 0 interval_larger 0	-				interval_larger		-	-	-				-				-				f f 1334	1186	0	0		0	_null_ _null_ ));
! DATA(insert ( 2129	n 0 text_larger		0	-				text_larger			-	-	-				-				-				f f 666		25		0	0		0	_null_ _null_ ));
! DATA(insert ( 2130	n 0 numeric_larger	0	-				numeric_larger		-	-	-				-				-				f f 1756	1700	0	0		0	_null_ _null_ ));
! DATA(insert ( 2050	n 0 array_larger	0	-				array_larger		-	-	-				-				-				f f 1073	2277	0	0		0	_null_ _null_ ));
! DATA(insert ( 2244	n 0 bpchar_larger	0	-				bpchar_larger		-	-	-				-				-				f f 1060	1042	0	0		0	_null_ _null_ ));
! DATA(insert ( 2797	n 0 tidlarger		0	-				tidlarger			-	-	-				-				-				f f 2800	27		0	0		0	_null_ _null_ ));
! DATA(insert ( 3526	n 0 enum_larger		0	-				enum_larger			-	-	-				-				-				f f 3519	3500	0	0		0	_null_ _null_ ));
! DATA(insert ( 3564	n 0 network_larger	0	-				network_larger		-	-	-				-				-				f f 1205	869		0	0		0	_null_ _null_ ));
  
  /* min */
! DATA(insert ( 2131	n 0 int8smaller		0	-				int8smaller			-	-	-				-				-				f f 412		20		0	0		0	_null_ _null_ ));
! DATA(insert ( 2132	n 0 int4smaller		0	-				int4smaller			-	-	-				-				-				f f 97		23		0	0		0	_null_ _null_ ));
! DATA(insert ( 2133	n 0 int2smaller		0	-				int2smaller			-	-	-				-				-				f f 95		21		0	0		0	_null_ _null_ ));
! DATA(insert ( 2134	n 0 oidsmaller		0	-				oidsmaller			-	-	-				-				-				f f 609		26		0	0		0	_null_ _null_ ));
! DATA(insert ( 2135	n 0 float4smaller	0	-				float4smaller		-	-	-				-				-				f f 622		700		0	0		0	_null_ _null_ ));
! DATA(insert ( 2136	n 0 float8smaller	0	-				float8smaller		-	-	-				-				-				f f 672		701		0	0		0	_null_ _null_ ));
! DATA(insert ( 2137	n 0 int4smaller		0	-				int4smaller			-	-	-				-				-				f f 562		702		0	0		0	_null_ _null_ ));
! DATA(insert ( 2138	n 0 date_smaller	0	-				date_smaller		-	-	-				-				-				f f 1095	1082	0	0		0	_null_ _null_ ));
! DATA(insert ( 2139	n 0 time_smaller	0	-				time_smaller		-	-	-				-				-				f f 1110	1083	0	0		0	_null_ _null_ ));
! DATA(insert ( 2140	n 0 timetz_smaller	0	-				timetz_smaller		-	-	-				-				-				f f 1552	1266	0	0		0	_null_ _null_ ));
! DATA(insert ( 2141	n 0 cashsmaller		0	-				cashsmaller			-	-	-				-				-				f f 902		790		0	0		0	_null_ _null_ ));
! DATA(insert ( 2142	n 0 timestamp_smaller	0	-			timestamp_smaller	-	-	-				-				-				f f 2062	1114	0	0		0	_null_ _null_ ));
! DATA(insert ( 2143	n 0 timestamptz_smaller 0	-			timestamptz_smaller -	-	-				-				-				f f 1322	1184	0	0		0	_null_ _null_ ));
! DATA(insert ( 2144	n 0 interval_smaller	0	-			interval_smaller	-	-	-				-				-				f f 1332	1186	0	0		0	_null_ _null_ ));
! DATA(insert ( 2145	n 0 text_smaller	0	-				text_smaller		-	-	-				-				-				f f 664		25		0	0		0	_null_ _null_ ));
! DATA(insert ( 2146	n 0 numeric_smaller 0	-				numeric_smaller		-	-	-				-				-				f f 1754	1700	0	0		0	_null_ _null_ ));
! DATA(insert ( 2051	n 0 array_smaller	0	-				array_smaller		-	-	-				-				-				f f 1072	2277	0	0		0	_null_ _null_ ));
! DATA(insert ( 2245	n 0 bpchar_smaller	0	-				bpchar_smaller		-	-	-				-				-				f f 1058	1042	0	0		0	_null_ _null_ ));
! DATA(insert ( 2798	n 0 tidsmaller		0	-				tidsmaller			-	-	-				-				-				f f 2799	27		0	0		0	_null_ _null_ ));
! DATA(insert ( 3527	n 0 enum_smaller	0	-				enum_smaller		-	-	-				-				-				f f 3518	3500	0	0		0	_null_ _null_ ));
! DATA(insert ( 3565	n 0 network_smaller 0	-				network_smaller		-	-	-				-				-				f f 1203	869		0	0		0	_null_ _null_ ));
  
  /* count */
! DATA(insert ( 2147	n 0 int8inc_any		0	-				int8pl	-	-	int8inc_any		int8dec_any		-				f f 0		20		0	20		0	"0" "0" ));
! DATA(insert ( 2803	n 0 int8inc			0	-				int8pl	-	-	int8inc			int8dec			-				f f 0		20		0	20		0	"0" "0" ));
! #define COUNTFNOID 2803
  
  /* var_pop */
! DATA(insert ( 2718	n 0 int8_accum		0	numeric_var_pop			numeric_combine			numeric_serialize		numeric_deserialize			int8_accum		int8_accum_inv	numeric_var_pop			f f 0	2281	128 2281	128 _null_ _null_ ));
! DATA(insert ( 2719	n 0 int4_accum		0	numeric_poly_var_pop	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int4_accum		int4_accum_inv	numeric_poly_var_pop	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2720	n 0 int2_accum		0	numeric_poly_var_pop	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int2_accum		int2_accum_inv	numeric_poly_var_pop	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2721	n 0 float4_accum	0	float8_var_pop			float8_combine			-						-							-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2722	n 0 float8_accum	0	float8_var_pop			float8_combine			-						-							-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2723	n 0 numeric_accum	0	numeric_var_pop			numeric_combine			numeric_serialize		numeric_deserialize			numeric_accum	numeric_accum_inv numeric_var_pop		f f 0	2281	128 2281	128 _null_ _null_ ));
  
  /* var_samp */
! DATA(insert ( 2641	n 0 int8_accum		0	numeric_var_samp		numeric_combine			numeric_serialize		numeric_deserialize			int8_accum		int8_accum_inv	numeric_var_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
! DATA(insert ( 2642	n 0 int4_accum		0	numeric_poly_var_samp	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int4_accum		int4_accum_inv	numeric_poly_var_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2643	n 0 int2_accum		0	numeric_poly_var_samp	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int2_accum		int2_accum_inv	numeric_poly_var_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2644	n 0 float4_accum	0	float8_var_samp			float8_combine			-						-							-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2645	n 0 float8_accum	0	float8_var_samp			float8_combine			-						-							-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2646	n 0 numeric_accum	0	numeric_var_samp		numeric_combine			numeric_serialize		numeric_deserialize			numeric_accum	numeric_accum_inv numeric_var_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
  
  /* variance: historical Postgres syntax for var_samp */
! DATA(insert ( 2148	n 0 int8_accum		0	numeric_var_samp		numeric_combine			numeric_serialize		numeric_deserialize			int8_accum		int8_accum_inv	numeric_var_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
! DATA(insert ( 2149	n 0 int4_accum		0	numeric_poly_var_samp	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int4_accum		int4_accum_inv	numeric_poly_var_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2150	n 0 int2_accum		0	numeric_poly_var_samp	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int2_accum		int2_accum_inv	numeric_poly_var_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2151	n 0 float4_accum	0	float8_var_samp			float8_combine			-						-							-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2152	n 0 float8_accum	0	float8_var_samp			float8_combine			-						-							-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2153	n 0 numeric_accum	0	numeric_var_samp		numeric_combine			numeric_serialize		numeric_deserialize			numeric_accum	numeric_accum_inv numeric_var_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
  
  /* stddev_pop */
! DATA(insert ( 2724	n 0 int8_accum		0	numeric_stddev_pop		numeric_combine			numeric_serialize		numeric_deserialize			int8_accum		int8_accum_inv	numeric_stddev_pop		f f 0	2281	128 2281	128 _null_ _null_ ));
! DATA(insert ( 2725	n 0 int4_accum		0	numeric_poly_stddev_pop numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int4_accum		int4_accum_inv	numeric_poly_stddev_pop f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2726	n 0 int2_accum		0	numeric_poly_stddev_pop numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int2_accum		int2_accum_inv	numeric_poly_stddev_pop f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2727	n 0 float4_accum	0	float8_stddev_pop		float8_combine			-						-							-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2728	n 0 float8_accum	0	float8_stddev_pop		float8_combine			-						-							-				-				-						f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2729	n 0 numeric_accum	0	numeric_stddev_pop		numeric_combine			numeric_serialize		numeric_deserialize			numeric_accum	numeric_accum_inv numeric_stddev_pop	f f 0	2281	128 2281	128 _null_ _null_ ));
  
  /* stddev_samp */
! DATA(insert ( 2712	n 0 int8_accum		0	numeric_stddev_samp			numeric_combine			numeric_serialize		numeric_deserialize			int8_accum	int8_accum_inv	numeric_stddev_samp			f f 0	2281	128 2281	128 _null_ _null_ ));
! DATA(insert ( 2713	n 0 int4_accum		0	numeric_poly_stddev_samp	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int4_accum	int4_accum_inv	numeric_poly_stddev_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2714	n 0 int2_accum		0	numeric_poly_stddev_samp	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int2_accum	int2_accum_inv	numeric_poly_stddev_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2715	n 0 float4_accum	0	float8_stddev_samp			float8_combine			-						-							-			-				-							f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2716	n 0 float8_accum	0	float8_stddev_samp			float8_combine			-						-							-			-				-							f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2717	n 0 numeric_accum	0	numeric_stddev_samp			numeric_combine			numeric_serialize		numeric_deserialize			numeric_accum numeric_accum_inv numeric_stddev_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
  
  /* stddev: historical Postgres syntax for stddev_samp */
! DATA(insert ( 2154	n 0 int8_accum		0	numeric_stddev_samp			numeric_combine			numeric_serialize		numeric_deserialize			int8_accum		int8_accum_inv	numeric_stddev_samp			f f 0	2281	128 2281	128 _null_ _null_ ));
! DATA(insert ( 2155	n 0 int4_accum		0	numeric_poly_stddev_samp	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int4_accum		int4_accum_inv	numeric_poly_stddev_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2156	n 0 int2_accum		0	numeric_poly_stddev_samp	numeric_poly_combine	numeric_poly_serialize	numeric_poly_deserialize	int2_accum		int2_accum_inv	numeric_poly_stddev_samp	f f 0	2281	48	2281	48	_null_ _null_ ));
! DATA(insert ( 2157	n 0 float4_accum	0	float8_stddev_samp			float8_combine			-						-							-				-				-							f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2158	n 0 float8_accum	0	float8_stddev_samp			float8_combine			-						-							-				-				-							f f 0	1022	0	0		0	"{0,0,0}" _null_ ));
! DATA(insert ( 2159	n 0 numeric_accum	0	numeric_stddev_samp			numeric_combine			numeric_serialize		numeric_deserialize			numeric_accum	numeric_accum_inv numeric_stddev_samp		f f 0	2281	128 2281	128 _null_ _null_ ));
  
  /* SQL2003 binary regression aggregates */
! DATA(insert ( 2818	n 0 int8inc_float8_float8	0	-					int8pl				-	-	-				-				-			f f 0	20		0	0		0	"0" _null_ ));
! DATA(insert ( 2819	n 0 float8_regr_accum	0	float8_regr_sxx			float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2820	n 0 float8_regr_accum	0	float8_regr_syy			float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2821	n 0 float8_regr_accum	0	float8_regr_sxy			float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2822	n 0 float8_regr_accum	0	float8_regr_avgx		float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2823	n 0 float8_regr_accum	0	float8_regr_avgy		float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2824	n 0 float8_regr_accum	0	float8_regr_r2			float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2825	n 0 float8_regr_accum	0	float8_regr_slope		float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2826	n 0 float8_regr_accum	0	float8_regr_intercept	float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2827	n 0 float8_regr_accum	0	float8_covar_pop		float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2828	n 0 float8_regr_accum	0	float8_covar_samp		float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2829	n 0 float8_regr_accum	0	float8_corr				float8_regr_combine -	-	-				-				-			f f 0	1022	0	0		0	"{0,0,0,0,0,0}" _null_ ));
  
  /* boolean-and and boolean-or */
! DATA(insert ( 2517	n 0 booland_statefunc	0	-	booland_statefunc	-	-	bool_accum	bool_accum_inv	bool_alltrue	f f 58	16	0	2281	16	_null_ _null_ ));
! DATA(insert ( 2518	n 0 boolor_statefunc	0	-	boolor_statefunc	-	-	bool_accum	bool_accum_inv	bool_anytrue	f f 59	16	0	2281	16	_null_ _null_ ));
! DATA(insert ( 2519	n 0 booland_statefunc	0	-	booland_statefunc	-	-	bool_accum	bool_accum_inv	bool_alltrue	f f 58	16	0	2281	16	_null_ _null_ ));
  
  /* bitwise integer */
! DATA(insert ( 2236	n 0 int2and		0	-				int2and -	-	-				-				-				f f 0	21		0	0		0	_null_ _null_ ));
! DATA(insert ( 2237	n 0 int2or		0	-				int2or	-	-	-				-				-				f f 0	21		0	0		0	_null_ _null_ ));
! DATA(insert ( 2238	n 0 int4and		0	-				int4and -	-	-				-				-				f f 0	23		0	0		0	_null_ _null_ ));
! DATA(insert ( 2239	n 0 int4or		0	-				int4or	-	-	-				-				-				f f 0	23		0	0		0	_null_ _null_ ));
! DATA(insert ( 2240	n 0 int8and		0	-				int8and -	-	-				-				-				f f 0	20		0	0		0	_null_ _null_ ));
! DATA(insert ( 2241	n 0 int8or		0	-				int8or	-	-	-				-				-				f f 0	20		0	0		0	_null_ _null_ ));
! DATA(insert ( 2242	n 0 bitand		0	-				bitand	-	-	-				-				-				f f 0	1560	0	0		0	_null_ _null_ ));
! DATA(insert ( 2243	n 0 bitor		0	-				bitor	-	-	-				-				-				f f 0	1560	0	0		0	_null_ _null_ ));
  
  /* xml */
! DATA(insert ( 2901	n 0 xmlconcat2	0	-				-		-	-	-				-				-				f f 0	142		0	0		0	_null_ _null_ ));
  
  /* array */
! DATA(insert ( 2335	n 0 array_agg_transfn	0	array_agg_finalfn		-	-	-	-		-				-				t f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 4053	n 0 array_agg_array_transfn	0	array_agg_array_finalfn -	-	-	-		-				-				t f 0	2281	0	0		0	_null_ _null_ ));
  
  /* text */
! DATA(insert ( 3538	n 0 string_agg_transfn	0	string_agg_finalfn	-	-	-	-				-				-				f f 0	2281	0	0		0	_null_ _null_ ));
  
  /* bytea */
! DATA(insert ( 3545	n 0 bytea_string_agg_transfn	0	bytea_string_agg_finalfn	-	-	-	-				-				-		f f 0	2281	0	0		0	_null_ _null_ ));
  
  /* json */
! DATA(insert ( 3175	n 0 json_agg_transfn	0	json_agg_finalfn			-	-	-	-				-				-				f f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3197	n 0 json_object_agg_transfn 0	json_object_agg_finalfn -	-	-	-				-				-				f f 0	2281	0	0		0	_null_ _null_ ));
  
  /* jsonb */
! DATA(insert ( 3267	n 0 jsonb_agg_transfn	0	jsonb_agg_finalfn				-	-	-	-				-				-			f f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3270	n 0 jsonb_object_agg_transfn 0	jsonb_object_agg_finalfn	-	-	-	-				-				-			f f 0	2281	0	0		0	_null_ _null_ ));
  
  /* ordered-set and hypothetical-set aggregates */
! DATA(insert ( 3972	o 1 ordered_set_transition			0	percentile_disc_final					-	-	-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3974	o 1 ordered_set_transition			0	percentile_cont_float8_final			-	-	-	-		-		-		f f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3976	o 1 ordered_set_transition			0	percentile_cont_interval_final			-	-	-	-		-		-		f f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3978	o 1 ordered_set_transition			0	percentile_disc_multi_final				-	-	-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3980	o 1 ordered_set_transition			0	percentile_cont_float8_multi_final		-	-	-	-		-		-		f f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3982	o 1 ordered_set_transition			0	percentile_cont_interval_multi_final	-	-	-	-		-		-		f f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3984	o 0 ordered_set_transition			0	mode_final								-	-	-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3986	h 1 ordered_set_transition_multi	0	rank_final								-	-	-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3988	h 1 ordered_set_transition_multi	0	percent_rank_final						-	-	-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3990	h 1 ordered_set_transition_multi	0	cume_dist_final							-	-	-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
! DATA(insert ( 3992	h 1 ordered_set_transition_multi	0	dense_rank_final						-	-	-	-		-		-		t f 0	2281	0	0		0	_null_ _null_ ));
  
  
  /*
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
new file mode 100644
index aeb7927..3095380
*** a/src/include/catalog/pg_operator.h
--- b/src/include/catalog/pg_operator.h
*************** DATA(insert OID = 685 (  "-"	   PGNSP PG
*** 547,552 ****
--- 547,553 ----
  DESCR("subtract");
  DATA(insert OID = 686 (  "*"	   PGNSP PGUID b f f	20	20	20 686	 0 int8mul - - ));
  DESCR("multiply");
+ #define OPERATOR_INT8MUL 686
  DATA(insert OID = 687 (  "/"	   PGNSP PGUID b f f	20	20	20	 0	 0 int8div - - ));
  DESCR("divide");
  
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
new file mode 100644
index 37e022d..51d1b74
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("pg_controldata recovery state inf
*** 5345,5350 ****
--- 5345,5353 ----
  DATA(insert OID = 3444 ( pg_control_init PGNSP PGUID 12 1 0 0 0 f f f f t f v s 0 0 2249 "" "{23,23,23,23,23,23,23,23,23,16,16,16,23}" "{o,o,o,o,o,o,o,o,o,o,o,o,o}" "{max_data_alignment,database_block_size,blocks_per_segment,wal_block_size,bytes_per_wal_segment,max_identifier_length,max_index_columns,max_toast_chunk_size,large_object_chunk_size,bigint_timestamps,float4_pass_by_value,float8_pass_by_value,data_page_checksum_version}" _null_ _null_ pg_control_init _null_ _null_ _null_ ));
  DESCR("pg_controldata init state information as a function");
  
+ DATA(insert OID = 4002 (  float4_sum_mul	   PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 1022 "1022 20" _null_ _null_ _null_ _null_ _null_ float4_sum_mul _null_ _null_ _null_ ));
+ DESCR("aggregate state multiplication function");
+ 
  /*
   * Symbolic values for provolatile column: these indicate whether the result
   * of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
new file mode 100644
index f72ec24..dd76d9b
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
*************** typedef struct Aggref
*** 284,289 ****
--- 284,290 ----
  	bool		aggvariadic;	/* true if variadic arguments have been
  								 * combined into an array last argument */
  	char		aggkind;		/* aggregate kind (see pg_aggregate.h) */
+ 	Oid			aggtransmultifn;/* pg_aggregate(aggtransmultifn) */
  	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 e1d31c7..1e8ef3b
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct PlannerInfo
*** 252,257 ****
--- 252,262 ----
  
  	List	   *placeholder_list;		/* list of PlaceHolderInfos */
  
+ 	/* TODO Consider hashing. */
+ 	List		*grouped_var_list; /* List of GroupedVarInfos. */
+ 
+ 	bool		all_baserels_grouped; /* Should grouped rels be joined? */
+ 
  	List	   *fkey_list;		/* list of ForeignKeyOptInfos */
  
  	List	   *query_pathkeys; /* desired pathkeys for query_planner() */
*************** typedef struct RelOptInfo
*** 495,504 ****
--- 500,516 ----
  	/* default result targetlist for Paths scanning this relation */
  	struct PathTarget *reltarget;		/* list of Vars/Exprs, cost, width */
  
+ 	/* result targetlist if the base relation is grouped */
+ 	struct PathTarget *reltarget_grouped;
+ 
  	/* materialization information */
  	List	   *pathlist;		/* Path structures */
+ 	List	   *grouped_pathlist; /* paths with partial aggregation already
+ 								   * done. */
  	List	   *ppilist;		/* ParamPathInfos used in pathlist */
  	List	   *partial_pathlist;		/* partial Paths */
+ 	List	   *partial_grouped_pathlist;	/* partial Paths with partial
+ 											 * aggregation already done. */
  	struct Path *cheapest_startup_path;
  	struct Path *cheapest_total_path;
  	struct Path *cheapest_unique_path;
*************** typedef struct PlaceHolderInfo
*** 1905,1910 ****
--- 1917,1951 ----
  } PlaceHolderInfo;
  
  /*
+  * Variable of a "grouped relation" represents an aggregate or grouping
+  * expression that it evaluates. Input values of such expressions are not
+  * available above this relation, so the values of such expressions are passed
+  * as variables. Each variable-to-expression association is stored as an item
+  * of root->grouped_var_list and used by set_upper_references.
+  */
+ typedef struct GroupedVarInfo
+ {
+ 	/* The variable added to base relation instead of aggregate. */
+ 	Var		*var;
+ 
+ 	/*
+ 	 * The expression whose value this "grouped var" represents in the
+ 	 * target list of the (partially) grouped relation.
+ 	 */
+ 	Expr		*expr;
+ 
+ 	/*
+ 	 * If aggtransmultifn function (see Aggref) must be applied before the
+ 	 * final aggregation, expr_intermediate contains the function call. It'll
+ 	 * replace the variable in targetlist of the Result node that prepares
+ 	 * input for the final aggregation.
+ 	 */
+ 	Expr		*expr_intermediate;
+ 
+ 	Index	sortgroupref;
+ } GroupedVarInfo;
+ 
+ /*
   * This struct describes one potentially index-optimizable MIN/MAX aggregate
   * function.  MinMaxAggPath contains a list of these, and if we accept that
   * path, the list is stored into root->minmax_aggs for use during setrefs.c.
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
new file mode 100644
index d16f879..54b84c7
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern int compare_path_costs(Path *path
*** 25,37 ****
  extern int compare_fractional_path_costs(Path *path1, Path *path2,
  							  double fraction);
  extern void set_cheapest(RelOptInfo *parent_rel);
! extern void add_path(RelOptInfo *parent_rel, Path *new_path);
  extern bool add_path_precheck(RelOptInfo *parent_rel,
  				  Cost startup_cost, Cost total_cost,
! 				  List *pathkeys, Relids required_outer);
! extern void add_partial_path(RelOptInfo *parent_rel, Path *new_path);
  extern bool add_partial_path_precheck(RelOptInfo *parent_rel,
! 						  Cost total_cost, List *pathkeys);
  
  extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel,
  					Relids required_outer, int parallel_workers);
--- 25,39 ----
  extern int compare_fractional_path_costs(Path *path1, Path *path2,
  							  double fraction);
  extern void set_cheapest(RelOptInfo *parent_rel);
! extern void add_path(RelOptInfo *parent_rel, Path *new_path, bool grouped);
  extern bool add_path_precheck(RelOptInfo *parent_rel,
  				  Cost startup_cost, Cost total_cost,
! 							  List *pathkeys, Relids required_outer, bool grouped);
! extern void add_partial_path(RelOptInfo *parent_rel, Path *new_path,
! 							 bool grouped);
  extern bool add_partial_path_precheck(RelOptInfo *parent_rel,
! 									  Cost total_cost, List *pathkeys,
! 									  bool grouped);
  
  extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel,
  					Relids required_outer, int parallel_workers);
*************** extern HashPath *create_hashjoin_path(Pl
*** 134,140 ****
  					 Path *inner_path,
  					 List *restrict_clauses,
  					 Relids required_outer,
! 					 List *hashclauses);
  
  extern ProjectionPath *create_projection_path(PlannerInfo *root,
  					   RelOptInfo *rel,
--- 136,143 ----
  					 Path *inner_path,
  					 List *restrict_clauses,
  					 Relids required_outer,
! 					 List *hashclauses,
! 					 bool grouped);
  
  extern ProjectionPath *create_projection_path(PlannerInfo *root,
  					   RelOptInfo *rel,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
new file mode 100644
index 480f25f..0d29bec
*** a/src/include/optimizer/paths.h
--- b/src/include/optimizer/paths.h
*************** extern void set_dummy_rel_pathlist(RelOp
*** 52,58 ****
  extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,
  					 List *initial_rels);
  
! extern void generate_gather_paths(PlannerInfo *root, RelOptInfo *rel);
  
  #ifdef OPTIMIZER_DEBUG
  extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel);
--- 52,59 ----
  extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,
  					 List *initial_rels);
  
! extern void generate_gather_paths(PlannerInfo *root, RelOptInfo *rel,
! 								  bool grouped);
  
  #ifdef OPTIMIZER_DEBUG
  extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
new file mode 100644
index 8468b0c..dd2529b
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
*************** extern void add_base_rels_to_query(Plann
*** 75,80 ****
--- 75,81 ----
  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 build_base_rel_tlists_grouped(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 f80b31a..cb9d0c0
*** a/src/include/optimizer/tlist.h
--- b/src/include/optimizer/tlist.h
*************** extern void add_column_to_pathtarget(Pat
*** 61,67 ****
  extern void add_new_column_to_pathtarget(PathTarget *target, Expr *expr);
  extern void add_new_columns_to_pathtarget(PathTarget *target, List *exprs);
  extern void apply_pathtarget_labeling_to_tlist(List *tlist, PathTarget *target);
! 
  /* 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))
--- 61,70 ----
  extern void add_new_column_to_pathtarget(PathTarget *target, Expr *expr);
  extern void add_new_columns_to_pathtarget(PathTarget *target, List *exprs);
  extern void apply_pathtarget_labeling_to_tlist(List *tlist, PathTarget *target);
! extern PathTarget *create_intermediate_grouping_target(PlannerInfo *root,
! 													   PathTarget *src);
! extern List *restore_grouping_expressions(PlannerInfo *root, List *src);
! extern Expr *find_grouped_var_expr(PlannerInfo *root, Var *var);
  /* Convenience macro to get a PathTarget with valid cost/width fields */
  #define create_pathtarget(root, tlist) \
  	set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
new file mode 100644
index e1bb344..628b142
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum float84le(PG_FUNCTION_ARGS)
*** 475,480 ****
--- 475,481 ----
  extern Datum float84gt(PG_FUNCTION_ARGS);
  extern Datum float84ge(PG_FUNCTION_ARGS);
  extern Datum width_bucket_float8(PG_FUNCTION_ARGS);
+ extern Datum float4_sum_mul(PG_FUNCTION_ARGS);
  
  /* dbsize.c */
  extern Datum pg_tablespace_size_oid(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
new file mode 100644
index b43dae7..39f5dee
*** a/src/include/utils/selfuncs.h
--- b/src/include/utils/selfuncs.h
*************** extern double estimate_num_groups(Planne
*** 232,237 ****
--- 232,240 ----
  
  extern Selectivity estimate_hash_bucketsize(PlannerInfo *root, Node *hashkey,
  						 double nbuckets);
+ extern Size estimate_hashagg_tablesize(Path *path,
+ 									   const AggClauseCosts *agg_costs,
+ 									   double dNumGroups);
  
  extern List *deconstruct_indexquals(IndexPath *path);
  extern void genericcostestimate(PlannerInfo *root, IndexPath *path,
