diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index ad37a74221..ef4d8ab120 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -100,8 +100,21 @@ typedef struct deparse_expr_cxt
 								 * a base relation. */
 	StringInfo	buf;			/* output buffer to append to */
 	List	  **params_list;	/* exprs that will become remote Params */
+	RelAggInfo *agg_info;
+	List      **avg_attrs;
+	List      **sum_attrs;
+	AggSplit    aggsplit;
 } deparse_expr_cxt;
 
+typedef enum {
+	AGGFUNC_SUM,
+	AGGFUNC_AVG,
+	AGGFUNC_COUNT,
+	AGGFUNC_MAX,
+	AGGFUNC_MIN,
+	AGGFUNC_OTHER
+} AggFuncType;
+
 #define REL_ALIAS_PREFIX	"r"
 /* Handy macro to add relation name qualification */
 #define ADD_REL_QUALIFIER(buf, varno)	\
@@ -184,6 +197,7 @@ static void appendAggOrderBy(List *orderList, List *targetList,
 static void appendFunctionName(Oid funcid, deparse_expr_cxt *context);
 static Node *deparseSortGroupClause(Index ref, List *tlist, bool force_colno,
 									deparse_expr_cxt *context);
+static AggFuncType getAggFunc(Aggref* aggref);
 
 /*
  * Helper functions
@@ -693,6 +707,7 @@ foreign_expr_walker(Node *node,
 		case T_Aggref:
 			{
 				Aggref	   *agg = (Aggref *) node;
+				AggFuncType aggfunc_type = AGGFUNC_OTHER;
 				ListCell   *lc;
 
 				/* Not safe to pushdown when not in grouping context */
@@ -700,8 +715,15 @@ foreign_expr_walker(Node *node,
 					return false;
 
 				/* Only non-split aggregates are pushable. */
-				if (agg->aggsplit != AGGSPLIT_SIMPLE)
+				if (agg->aggsplit == AGGSPLIT_INITIAL_SERIAL) {
+					fpinfo->aggsplit = AGGSPLIT_INITIAL_SERIAL;
+					aggfunc_type = getAggFunc(agg);
+					if (aggfunc_type == AGGFUNC_OTHER) {
+						return false;
+					}
+				} else if (agg->aggsplit != AGGSPLIT_SIMPLE) {
 					return false;
+				}
 
 				/* As usual, it must be shippable. */
 				if (!is_shippable(agg->aggfnoid, ProcedureRelationId, fpinfo))
@@ -723,6 +745,18 @@ foreign_expr_walker(Node *node,
 
 						n = (Node *) tle->expr;
 					}
+					if (fpinfo->aggsplit == AGGSPLIT_INITIAL_SERIAL) {
+						bool pushdown = false;
+						if (nodeTag(n) == T_Var) {
+							Var *var = (Var*)n;
+							if (var->vartype == INT8OID) {
+								pushdown = true;
+							}
+						}
+						if (pushdown == false) {
+							return false;
+						}
+					}
 
 					if (!foreign_expr_walker(n, glob_cxt, &inner_cxt))
 						return false;
@@ -995,7 +1029,8 @@ void
 deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel,
 						List *tlist, List *remote_conds, List *pathkeys,
 						bool has_final_sort, bool has_limit, bool is_subquery,
-						List **retrieved_attrs, List **params_list)
+						List **retrieved_attrs, List **params_list, List **avg_attrs,
+						List **sum_attrs)
 {
 	deparse_expr_cxt context;
 	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
@@ -1013,6 +1048,10 @@ deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel,
 	context.foreignrel = rel;
 	context.scanrel = IS_UPPER_REL(rel) ? fpinfo->outerrel : rel;
 	context.params_list = params_list;
+	context.agg_info = fpinfo->agg_info;
+	context.avg_attrs = avg_attrs;
+	context.sum_attrs = sum_attrs;
+	context.aggsplit = fpinfo->aggsplit;
 
 	/* Construct SELECT clause */
 	deparseSelectSql(tlist, is_subquery, retrieved_attrs, &context);
@@ -1075,7 +1114,7 @@ deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel,
  */
 static void
 deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs,
-				 deparse_expr_cxt *context)
+							  deparse_expr_cxt *context)
 {
 	StringInfo	buf = context->buf;
 	RelOptInfo *foreignrel = context->foreignrel;
@@ -1391,6 +1430,142 @@ get_jointype_name(JoinType jointype)
 	return NULL;
 }
 
+/* Get function name */
+static StringInfo getFuncName(Oid funcid) {
+	StringInfo	buf;
+	HeapTuple	proctup;
+	Form_pg_proc procform;
+	const char* proname;
+
+	buf = makeStringInfo();
+	initStringInfo(buf);
+
+	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+	if (!HeapTupleIsValid(proctup))
+		elog(ERROR, "cache lookup failed for function %u", funcid);
+	procform = (Form_pg_proc)GETSTRUCT(proctup);
+
+	/* Print schema name only if it's not pg_catalog */
+	if (procform->pronamespace != PG_CATALOG_NAMESPACE)
+	{
+		const char* schemaname;
+
+		schemaname = get_namespace_name(procform->pronamespace);
+		appendStringInfo(buf, "%s.", quote_identifier(schemaname));
+	}
+
+	/* Always print the function name */
+	proname = NameStr(procform->proname);
+	appendStringInfoString(buf, quote_identifier(proname));
+
+	ReleaseSysCache(proctup);
+
+	return buf;
+}
+
+/* Get name of aggregation function */
+static AggFuncType getAggFunc(Aggref *aggref) {
+	AggFuncType type = AGGFUNC_OTHER;
+	StringInfo buf = getFuncName(aggref->aggfnoid);
+	if (strcmp(buf->data, "avg") == 0) {
+		type = AGGFUNC_AVG;
+	} else if (strcmp(buf->data, "sum") == 0) {
+		type = AGGFUNC_SUM;
+	} else if (strcmp(buf->data, "count") == 0) {
+		type = AGGFUNC_COUNT;
+	} else if (strcmp(buf->data, "min") == 0) {
+		type = AGGFUNC_MIN;
+	} else if (strcmp(buf->data, "max") == 0) {
+		type = AGGFUNC_MAX;
+	} else {
+		type = AGGFUNC_OTHER;
+	}
+	return type;
+}
+
+/* Deparse arg of aggref */
+static void deparseAggrefArg(Aggref * node, deparse_expr_cxt *context){
+	StringInfo	buf = context->buf;
+	bool		use_variadic;
+
+	use_variadic = node->aggvariadic;
+
+	appendStringInfoChar(buf, '(');
+
+	/* Add DISTINCT */
+	appendStringInfoString(buf, (node->aggdistinct != NIL) ? "DISTINCT " : "");
+
+	if (AGGKIND_IS_ORDERED_SET(node->aggkind))
+	{
+		/* Add WITHIN GROUP (ORDER BY ..) */
+		ListCell* arg;
+		bool		first = true;
+
+		Assert(!node->aggvariadic);
+		Assert(node->aggorder != NIL);
+
+		foreach(arg, node->aggdirectargs)
+		{
+			if (!first)
+				appendStringInfoString(buf, ", ");
+			first = false;
+
+			deparseExpr((Expr*)lfirst(arg), context);
+		}
+
+		appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY ");
+		appendAggOrderBy(node->aggorder, node->args, context);
+	}
+	else
+	{
+		/* aggstar can be set only in zero-argument aggregates */
+		if (node->aggstar)
+			appendStringInfoChar(buf, '*');
+		else
+		{
+			ListCell* arg;
+			bool		first = true;
+
+			/* Add all the arguments */
+			foreach(arg, node->args)
+			{
+				TargetEntry* tle = (TargetEntry*)lfirst(arg);
+				Node* n = (Node*)tle->expr;
+
+				if (tle->resjunk)
+					continue;
+
+				if (!first)
+					appendStringInfoString(buf, ", ");
+				first = false;
+
+				/* Add VARIADIC */
+				if (use_variadic && lnext(node->args, arg) == NULL)
+					appendStringInfoString(buf, "VARIADIC ");
+
+				deparseExpr((Expr*)n, context);
+			}
+		}
+
+		/* Add ORDER BY */
+		if (node->aggorder != NIL)
+		{
+			appendStringInfoString(buf, " ORDER BY ");
+			appendAggOrderBy(node->aggorder, node->args, context);
+		}
+	}
+
+	/* Add FILTER (WHERE ..) */
+	if (node->aggfilter != NULL)
+	{
+		appendStringInfoString(buf, ") FILTER (WHERE ");
+		deparseExpr((Expr*)node->aggfilter, context);
+	}
+
+	appendStringInfoChar(buf, ')');
+}
+
+
 /*
  * Deparse given targetlist and append it to context->buf.
  *
@@ -1417,13 +1592,33 @@ deparseExplicitTargetList(List *tlist,
 	foreach(lc, tlist)
 	{
 		TargetEntry *tle = lfirst_node(TargetEntry, lc);
+		AggFuncType type;
 
 		if (i > 0)
 			appendStringInfoString(buf, ", ");
 		else if (is_returning)
 			appendStringInfoString(buf, " RETURNING ");
 
-		deparseExpr((Expr *) tle->expr, context);
+		if ((nodeTag((Expr*)tle->expr) == T_Aggref)
+				&& (context->aggsplit == AGGSPLIT_INITIAL_SERIAL)) {
+			type = getAggFunc((Aggref*)tle->expr);
+			if (type == AGGFUNC_AVG) {
+				*(context->avg_attrs) = lappend_int(*(context->avg_attrs), i + 1);
+				appendStringInfoString(buf, "count");
+				deparseAggrefArg((Aggref*)tle->expr, context);
+
+				appendStringInfoString(buf, ", ");
+				appendStringInfoString(buf, "sum");
+				deparseAggrefArg((Aggref*)tle->expr, context);
+			} else if (type == AGGFUNC_SUM) {
+				*(context->sum_attrs) = lappend_int(*(context->sum_attrs), i + 1);
+				deparseExpr((Expr*)tle->expr, context);
+			} else {
+				deparseExpr((Expr*)tle->expr, context);
+			}
+		} else {
+			deparseExpr((Expr*)tle->expr, context);
+		}
 
 		*retrieved_attrs = lappend_int(*retrieved_attrs, i + 1);
 		i++;
@@ -1646,6 +1841,8 @@ deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel,
 	if (make_subquery)
 	{
 		List	   *retrieved_attrs;
+		List       *avg_attrs = NIL;
+		List       *sum_attrs = NIL;
 		int			ncols;
 
 		/*
@@ -1661,7 +1858,8 @@ deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel,
 		deparseSelectStmtForRel(buf, root, foreignrel, NIL,
 								fpinfo->remote_conds, NIL,
 								false, false, true,
-								&retrieved_attrs, params_list);
+								&retrieved_attrs, params_list, &avg_attrs,
+								&sum_attrs);
 		appendStringInfoChar(buf, ')');
 
 		/* Append the relation alias. */
@@ -3148,9 +3346,17 @@ appendGroupByClause(List *tlist, deparse_expr_cxt *context)
 	Query	   *query = context->root->parse;
 	ListCell   *lc;
 	bool		first = true;
+	List       *groupClause;
+
+	if (context->agg_info) {
+		groupClause = context->agg_info->group_clauses;
+	}
+	else {
+		groupClause = query->groupClause;
+	}
 
 	/* Nothing to be done, if there's no GROUP BY clause in the query. */
-	if (!query->groupClause)
+	if (!groupClause)
 		return;
 
 	appendStringInfoString(buf, " GROUP BY ");
@@ -3161,7 +3367,7 @@ appendGroupByClause(List *tlist, deparse_expr_cxt *context)
 	 */
 	Assert(!query->groupingSets);
 
-	foreach(lc, query->groupClause)
+	foreach(lc, groupClause)
 	{
 		SortGroupClause *grp = (SortGroupClause *) lfirst(lc);
 
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index a46834a377..f99a0e2b7f 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -44,6 +44,8 @@
 #include "utils/rel.h"
 #include "utils/sampling.h"
 #include "utils/selfuncs.h"
+#include "utils/numeric.h"
+#include "libpq/pqformat.h"
 
 PG_MODULE_MAGIC;
 
@@ -72,6 +74,10 @@ enum FdwScanPrivateIndex
 	/* Integer representing the desired fetch_size */
 	FdwScanPrivateFetchSize,
 
+	FdwScanPrivateAvgAttrs,
+	FdwScanPrivateSumAttrs,
+	FdwScanPrivateAggSplit,
+
 	/*
 	 * String describing join i.e. names of relations being joined and types
 	 * of join, added when the scan is join
@@ -159,6 +165,10 @@ typedef struct PgFdwScanState
 	MemoryContext temp_cxt;		/* context for per-tuple temporary data */
 
 	int			fetch_size;		/* number of tuples per fetch */
+
+	List       *avg_attrs;
+	List       *sum_attrs;
+	AggSplit    aggsplit;
 } PgFdwScanState;
 
 /*
@@ -481,7 +491,7 @@ static bool foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel,
 							JoinType jointype, RelOptInfo *outerrel, RelOptInfo *innerrel,
 							JoinPathExtraData *extra);
 static bool foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel,
-								Node *havingQual);
+					Node *havingQual, RelAggInfo *agg_info);
 static List *get_useful_pathkeys_for_relation(PlannerInfo *root,
 											  RelOptInfo *rel);
 static List *get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel);
@@ -1190,6 +1200,8 @@ postgresGetForeignPlan(PlannerInfo *root,
 	bool		has_final_sort = false;
 	bool		has_limit = false;
 	ListCell   *lc;
+	List       *avg_attrs = NIL;
+	List       *sum_attrs = NIL;
 
 	/*
 	 * Get FDW private data created by postgresGetForeignUpperPaths(), if any.
@@ -1351,7 +1363,8 @@ postgresGetForeignPlan(PlannerInfo *root,
 	deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist,
 							remote_exprs, best_path->path.pathkeys,
 							has_final_sort, has_limit, false,
-							&retrieved_attrs, &params_list);
+							&retrieved_attrs, &params_list, &avg_attrs,
+							&sum_attrs);
 
 	/* Remember remote_exprs for possible use by postgresPlanDirectModify */
 	fpinfo->final_remote_exprs = remote_exprs;
@@ -1360,9 +1373,12 @@ postgresGetForeignPlan(PlannerInfo *root,
 	 * Build the fdw_private list that will be available to the executor.
 	 * Items in the list must match order in enum FdwScanPrivateIndex.
 	 */
-	fdw_private = list_make3(makeString(sql.data),
+	fdw_private = list_make4(makeString(sql.data),
 							 retrieved_attrs,
-							 makeInteger(fpinfo->fetch_size));
+							 makeInteger(fpinfo->fetch_size),
+							 avg_attrs);
+	fdw_private = lappend(fdw_private, sum_attrs);
+	fdw_private = lappend(fdw_private, makeInteger(fpinfo->aggsplit));
 	if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel))
 		fdw_private = lappend(fdw_private,
 							  makeString(fpinfo->relation_name));
@@ -1448,6 +1464,15 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 	fsstate->fetch_size = intVal(list_nth(fsplan->fdw_private,
 										  FdwScanPrivateFetchSize));
 
+	fsstate->avg_attrs = (List*)(list_nth(fsplan->fdw_private,
+		FdwScanPrivateAvgAttrs));
+
+	fsstate->sum_attrs = (List*)(list_nth(fsplan->fdw_private,
+		FdwScanPrivateSumAttrs));
+
+	fsstate->aggsplit = intVal(list_nth(fsplan->fdw_private,
+		FdwScanPrivateAggSplit));
+
 	/* Create contexts for batches of tuples and per-tuple temp workspace. */
 	fsstate->batch_cxt = AllocSetContextCreate(estate->es_query_cxt,
 											   "postgres_fdw tuple data",
@@ -2710,6 +2735,8 @@ estimate_path_cost_size(PlannerInfo *root,
 
 		/* Required only to be passed to deparseSelectStmtForRel */
 		List	   *retrieved_attrs;
+		List       *avg_attrs;
+		List       *sum_attrs;
 
 		/*
 		 * param_join_conds might contain both clauses that are safe to send
@@ -2743,7 +2770,8 @@ estimate_path_cost_size(PlannerInfo *root,
 								remote_conds, pathkeys,
 								fpextra ? fpextra->has_final_sort : false,
 								fpextra ? fpextra->has_limit : false,
-								false, &retrieved_attrs, NULL);
+								false, &retrieved_attrs, NULL, &avg_attrs,
+								&sum_attrs);
 
 		/* Get the remote estimate */
 		conn = GetConnection(fpinfo->user, false);
@@ -2920,6 +2948,7 @@ estimate_path_cost_size(PlannerInfo *root,
 			double		input_rows;
 			int			numGroupCols;
 			double		numGroups = 1;
+			List       *group_exprs;
 
 			/* The upper relation should have its outer relation set */
 			Assert(outerrel);
@@ -2957,9 +2986,16 @@ estimate_path_cost_size(PlannerInfo *root,
 
 			/* Get number of grouping columns and possible number of groups */
 			numGroupCols = list_length(root->parse->groupClause);
+			numGroups = 100;
+			if (fpinfo->agg_info) {
+				group_exprs = fpinfo->agg_info->group_exprs;
+			}
+			else {
+				group_exprs = get_sortgrouplist_exprs(root->parse->groupClause,
+					fpinfo->grouped_tlist);
+			}
 			numGroups = estimate_num_groups(root,
-											get_sortgrouplist_exprs(root->parse->groupClause,
-																	fpinfo->grouped_tlist),
+											group_exprs,
 											input_rows, NULL);
 
 			/*
@@ -5568,7 +5604,7 @@ postgresGetForeignJoinPaths(PlannerInfo *root,
  */
 static bool
 foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel,
-					Node *havingQual)
+					Node *havingQual, RelAggInfo *agg_info)
 {
 	Query	   *query = root->parse;
 	PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) grouped_rel->fdw_private;
@@ -5615,9 +5651,10 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel,
 		Expr	   *expr = (Expr *) lfirst(lc);
 		Index		sgref = get_pathtarget_sortgroupref(grouping_target, i);
 		ListCell   *l;
+		List *groupClause = (agg_info != NULL) ? agg_info->group_clauses : query->groupClause;
 
 		/* Check whether this expression is part of GROUP BY clause */
-		if (sgref && get_sortgroupref_clause_noerr(sgref, query->groupClause))
+		if (sgref && get_sortgroupref_clause_noerr(sgref, groupClause))
 		{
 			TargetEntry *tle;
 
@@ -5888,6 +5925,7 @@ add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
 	fpinfo->table = ifpinfo->table;
 	fpinfo->server = ifpinfo->server;
 	fpinfo->user = ifpinfo->user;
+	fpinfo->agg_info = extra->agg_info;
 	merge_fdw_options(fpinfo, ifpinfo, NULL);
 
 	/*
@@ -5896,7 +5934,8 @@ add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
 	 * Use HAVING qual from extra. In case of child partition, it will have
 	 * translated Vars.
 	 */
-	if (!foreign_grouping_ok(root, grouped_rel, extra->havingQual))
+	if (!foreign_grouping_ok(root, grouped_rel, extra->havingQual,
+			extra->agg_info))
 		return;
 
 	/*
@@ -6329,6 +6368,7 @@ make_tuple_from_result_row(PGresult *res,
 	MemoryContext oldcontext;
 	ListCell   *lc;
 	int			j;
+	PgFdwScanState *festate = (fsstate == NULL) ? NULL : (PgFdwScanState*)fsstate->fdw_state;
 
 	Assert(row < PQntuples(res));
 
@@ -6370,13 +6410,43 @@ make_tuple_from_result_row(PGresult *res,
 	foreach(lc, retrieved_attrs)
 	{
 		int			i = lfirst_int(lc);
-		char	   *valstr;
-
-		/* fetch next column's textual value */
-		if (PQgetisnull(res, row, j))
-			valstr = NULL;
-		else
-			valstr = PQgetvalue(res, row, j);
+		char	   *valstr = NULL;
+		char       *countstr = NULL;
+		char       *sumstr = NULL;
+		bool        split_serial = false;
+
+		if (festate == NULL) {
+			split_serial = false;
+		} else if (festate->aggsplit == AGGSPLIT_INITIAL_SERIAL) {
+			split_serial = true;
+		} else {
+			split_serial = false;
+		}
+		if (split_serial == true) {
+			if (list_member_int(festate->avg_attrs, i)) {
+				/* fetch next column's textual value */
+				if (PQgetisnull(res, row, j)) {
+					countstr = NULL;
+					sumstr = NULL;
+				} else {
+					countstr = PQgetvalue(res, row, j);
+					j++;
+					sumstr = PQgetvalue(res, row, j);
+				}
+			} else {
+				/* fetch next column's textual value */
+				if (PQgetisnull(res, row, j))
+					valstr = NULL;
+				else
+					valstr = PQgetvalue(res, row, j);
+			}
+		} else {
+			/* fetch next column's textual value */
+			if (PQgetisnull(res, row, j))
+				valstr = NULL;
+			else
+				valstr = PQgetvalue(res, row, j);
+		}
 
 		/*
 		 * convert value to internal representation
@@ -6388,12 +6458,63 @@ make_tuple_from_result_row(PGresult *res,
 		{
 			/* ordinary column */
 			Assert(i <= tupdesc->natts);
-			nulls[i - 1] = (valstr == NULL);
-			/* Apply the input function even to nulls, to support domains */
-			values[i - 1] = InputFunctionCall(&attinmeta->attinfuncs[i - 1],
-											  valstr,
-											  attinmeta->attioparams[i - 1],
-											  attinmeta->atttypmods[i - 1]);
+
+			if (split_serial == false) {
+				nulls[i - 1] = (valstr == NULL);
+				/* Apply the input function even to nulls, to support domains */
+				values[i - 1] = InputFunctionCall(&attinmeta->attinfuncs[i - 1],
+					valstr,
+					attinmeta->attioparams[i - 1],
+					attinmeta->atttypmods[i - 1]);
+			} else {
+				if (list_member_int(festate->avg_attrs, i)) {
+					StringInfoData buf;
+					int64 count;
+					Numeric sumN;
+					bytea* sumX;
+					Datum temp;
+					bytea* result;
+
+					nulls[i - 1] = (sumstr == NULL);
+
+					count = DatumGetInt32(DirectFunctionCall1(int8in, PointerGetDatum(countstr)));
+
+					sumN = DatumGetNumeric(DirectFunctionCall3(numeric_in, PointerGetDatum(sumstr), 0,
+						Int32GetDatum(attinmeta->atttypmods[i - 1])));
+					temp = DirectFunctionCall1(numeric_send, NumericGetDatum(sumN));
+					sumX = DatumGetByteaPP(temp);
+					pq_begintypsend(&buf);
+					pq_sendint64(&buf, count);
+					pq_sendbytes(&buf, VARDATA_ANY(sumX), VARSIZE_ANY_EXHDR(sumX));
+					result = pq_endtypsend(&buf);
+					values[i - 1] = PointerGetDatum(result);
+				} else if (list_member_int(festate->sum_attrs, i)) {
+					StringInfoData buf;
+					Numeric sumN;
+					bytea* sumX;
+					Datum temp;
+					bytea* result;
+
+					nulls[i - 1] = (valstr == NULL);
+
+					sumN = DatumGetNumeric(DirectFunctionCall3(numeric_in, PointerGetDatum(valstr), 0,
+						Int32GetDatum(attinmeta->atttypmods[i - 1])));
+					temp = DirectFunctionCall1(numeric_send, NumericGetDatum(sumN));
+					sumX = DatumGetByteaPP(temp);
+					pq_begintypsend(&buf);
+					pq_sendint64(&buf, 1);
+					pq_sendbytes(&buf, VARDATA_ANY(sumX), VARSIZE_ANY_EXHDR(sumX));
+					result = pq_endtypsend(&buf);
+					values[i - 1] = PointerGetDatum(result);
+				} else {
+					nulls[i - 1] = (valstr == NULL);
+					/* Apply the input function even to nulls, to support domains */
+					values[i - 1] = InputFunctionCall(&attinmeta->attinfuncs[i - 1],
+						valstr,
+						attinmeta->attioparams[i - 1],
+						attinmeta->atttypmods[i - 1]);
+				}
+			}
 		}
 		else if (i == SelfItemPointerAttributeNumber)
 		{
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index eef410db39..40c564abef 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -122,6 +122,8 @@ typedef struct PgFdwRelationInfo
 	 * representing the relation.
 	 */
 	int			relation_index;
+	RelAggInfo *agg_info;
+	AggSplit    aggsplit;
 } PgFdwRelationInfo;
 
 /* in postgres_fdw.c */
@@ -200,8 +202,9 @@ extern void deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root,
 									RelOptInfo *foreignrel, List *tlist,
 									List *remote_conds, List *pathkeys,
 									bool has_final_sort, bool has_limit,
-									bool is_subquery,
-									List **retrieved_attrs, List **params_list);
+									bool is_subquery, List **retrieved_attrs,
+									List **params_list, List **avg_attrs,
+									List **sum_attrs);
 extern const char *get_jointype_name(JoinType jointype);
 
 /* in shippable.c */
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index e0b5418de4..0fd00ef2bd 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -49,7 +49,6 @@
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
 
-
 /* results of subquery_is_pushdown_safe */
 typedef struct pushdown_safety_info
 {
@@ -540,8 +539,37 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 			case RTE_RELATION:
 				if (rte->relkind == RELKIND_FOREIGN_TABLE)
 				{
+					RelOptInfo* rel_grouped;
+					RelAggInfo* agg_info;
+					GroupPathExtraData extra;
+
 					/* Foreign table */
 					set_foreign_pathlist(root, rel, rte);
+
+					/* Add paths to the grouped relation if one exists. */
+					rel_grouped = find_grouped_rel(root, rel->relids,
+						&agg_info);
+					if (rel_grouped) {
+						extra.target_parallel_safe = false;
+						extra.targetList = root->parse->targetList;
+						extra.havingQual = root->parse->havingQual;
+						extra.partial_costs_set = false;
+						extra.agg_info = agg_info;
+
+						if (enable_partitionwise_aggregate && !root->parse->groupingSets)
+							extra.patype = PARTITIONWISE_AGGREGATE_FULL;
+						else
+							extra.patype = PARTITIONWISE_AGGREGATE_NONE;
+
+						rel_grouped->fdwroutine->GetForeignUpperPaths(root, UPPERREL_GROUP_AGG,
+							rel, rel_grouped, (void*)&extra);
+						if (rel_grouped->pathlist != NIL) {
+							set_cheapest(rel_grouped);
+						}
+						else {
+							del_grouped_rel(root, rel_grouped, agg_info);
+						}
+					}
 				}
 				else if (rte->tablesample != NULL)
 				{
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 6fe12d08ce..0f6b787a91 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -3882,6 +3882,7 @@ create_grouping_paths(PlannerInfo *root,
 		extra.havingQual = parse->havingQual;
 		extra.targetList = parse->targetList;
 		extra.partial_costs_set = false;
+		extra.agg_info = NULL;
 
 		/*
 		 * Determine whether partitionwise aggregation is in theory possible.
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 4164f4b0e4..0854d426f9 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -412,9 +412,7 @@ build_simple_grouped_rel(PlannerInfo *root, int relid,
 	 * TODO Consider relaxing some of these restrictions.
 	 */
 	rte = root->simple_rte_array[rel_plain->relid];
-	if (rte->rtekind != RTE_RELATION ||
-		rte->relkind == RELKIND_FOREIGN_TABLE ||
-		rte->tablesample != NULL)
+	if (rte->rtekind != RTE_RELATION || rte->tablesample != NULL)
 		return NULL;
 
 	/*
@@ -443,6 +441,10 @@ build_simple_grouped_rel(PlannerInfo *root, int relid,
 	 */
 	rel_grouped = makeNode(RelOptInfo);
 	memcpy(rel_grouped, rel_plain, sizeof(RelOptInfo));
+	if (rte->relkind == RELKIND_FOREIGN_TABLE) {
+		rel_grouped->fdw_private = NULL;
+		rel_grouped->reloptkind = RELOPT_UPPER_REL;
+	}
 
 	/*
 	 * Note on consider_startup: while the AGG_HASHED strategy needs the whole
@@ -658,6 +660,39 @@ add_rel_info(RelInfoList *list, void *data)
 	}
 }
 
+/*
+ * del_rel_info
+ *		Delete relation specific info to a list, and also add it to the auxiliary
+ *		hashtable if there is one.
+ */
+static void
+del_rel_info(RelInfoList* list, void* data)
+{
+	Assert(IsA(data, RelOptInfo) || IsA(data, RelAggInfo));
+
+	/* GEQO requires us to append the new joinrel to the end of the list! */
+	list->items = list_delete(list->items, data);
+
+	/* store it into the auxiliary hashtable if there is one. */
+	if (list->hash)
+	{
+		Relids		relids;
+		bool		found;
+
+		if (IsA(data, RelOptInfo))
+			relids = ((RelOptInfo*)data)->relids;
+		else if (IsA(data, RelAggInfo))
+			relids = ((RelAggInfo*)data)->relids;
+
+		hash_search(list->hash,
+			&relids,
+			HASH_REMOVE,
+			&found);
+		Assert(!found);
+	}
+}
+
+
 /*
  * add_join_rel
  *		Add given join relation to the list of join relations in the given
@@ -682,6 +717,19 @@ add_grouped_rel(PlannerInfo *root, RelOptInfo *rel, RelAggInfo *agg_info)
 	add_rel_info(root->agg_info_list, agg_info);
 }
 
+/*
+ * del_grouped_rel
+ *		Del grouped base or join relation to the list of grouped relations in
+ *		the given PlannerInfo. Also add the corresponding RelAggInfo to
+ *		agg_info_list.
+ */
+void
+del_grouped_rel(PlannerInfo* root, RelOptInfo* rel, RelAggInfo* agg_info)
+{
+	del_rel_info(&root->upper_rels[UPPERREL_PARTIAL_GROUP_AGG], rel);
+	del_rel_info(root->agg_info_list, agg_info);
+}
+
 /*
  * find_grouped_rel
  *	  Returns grouped relation entry (base or join relation) corresponding to
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 1773fa292e..4cf757c676 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -4874,7 +4874,6 @@ int8_avg_serialize(PG_FUNCTION_ARGS)
 	{
 		Datum		temp;
 		NumericVar	num;
-
 		init_var(&num);
 
 #ifdef HAVE_INT128
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 98be8b7de2..1b35d94035 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -2593,6 +2593,7 @@ typedef struct
 	Node	   *havingQual;
 	List	   *targetList;
 	PartitionwiseAggregateType patype;
+	RelAggInfo* agg_info;
 } GroupPathExtraData;
 
 /*
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 47afa7ed1a..e902f11da0 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -301,6 +301,8 @@ extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
 extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids);
 extern void add_grouped_rel(PlannerInfo *root, RelOptInfo *rel,
 							RelAggInfo *agg_info);
+extern void del_grouped_rel(PlannerInfo* root, RelOptInfo* rel,
+	RelAggInfo* agg_info);
 extern RelOptInfo *find_grouped_rel(PlannerInfo *root, Relids relids,
 									RelAggInfo **agg_info_p);
 extern RelOptInfo *build_join_rel(PlannerInfo *root,
