diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 4df4a9b..aeeac7b 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -4517,6 +4517,15 @@ ExecInitExpr(Expr *node, PlanState *parent)
 					AggState   *aggstate = (AggState *) parent;
 					Aggref	   *aggref = (Aggref *) node;
 
+					if (aggstate->finalizeAggs == aggref->aggpartial)
+					{
+						/* planner messed up */
+						if (aggref->aggpartial)
+							elog(ERROR, "partial Aggref found in finalized aggregate node");
+						else
+							elog(ERROR, "non-partial Aggref found in partial aggregate node");
+					}
+
 					if (aggstate->finalizeAggs &&
 						aggref->aggoutputtype != aggref->aggtype)
 					{
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a21928b..04077ce 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1244,6 +1244,7 @@ _copyAggref(const Aggref *from)
 	COPY_NODE_FIELD(aggfilter);
 	COPY_SCALAR_FIELD(aggstar);
 	COPY_SCALAR_FIELD(aggvariadic);
+	COPY_SCALAR_FIELD(aggpartial);
 	COPY_SCALAR_FIELD(aggkind);
 	COPY_SCALAR_FIELD(agglevelsup);
 	COPY_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3c6c567..213cb15 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -202,6 +202,7 @@ _equalAggref(const Aggref *a, const Aggref *b)
 	COMPARE_NODE_FIELD(aggfilter);
 	COMPARE_SCALAR_FIELD(aggstar);
 	COMPARE_SCALAR_FIELD(aggvariadic);
+	COMPARE_SCALAR_FIELD(aggpartial);
 	COMPARE_SCALAR_FIELD(aggkind);
 	COMPARE_SCALAR_FIELD(agglevelsup);
 	COMPARE_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 5ac7446..e8eaf10 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1040,6 +1040,7 @@ _outAggref(StringInfo str, const Aggref *node)
 	WRITE_NODE_FIELD(aggfilter);
 	WRITE_BOOL_FIELD(aggstar);
 	WRITE_BOOL_FIELD(aggvariadic);
+	WRITE_BOOL_FIELD(aggpartial);
 	WRITE_CHAR_FIELD(aggkind);
 	WRITE_UINT_FIELD(agglevelsup);
 	WRITE_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 8059594..7ff9c91 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -556,6 +556,7 @@ _readAggref(void)
 	READ_NODE_FIELD(aggfilter);
 	READ_BOOL_FIELD(aggstar);
 	READ_BOOL_FIELD(aggvariadic);
+	READ_BOOL_FIELD(aggpartial);
 	READ_CHAR_FIELD(aggkind);
 	READ_UINT_FIELD(agglevelsup);
 	READ_LOCATION_FIELD(location);
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 5537c14..a1b1db4 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -2464,6 +2464,13 @@ fix_combine_agg_expr_mutator(Node *node, fix_upper_expr_context *context)
 			newaggref = (Aggref *) copyObject(aggref);
 			newaggref->args = list_make1(newtle);
 
+			/*
+			 * A combine aggregate node does not perform FILTER, so we'd
+			 * better remove any filter clauses so they're not shown in
+			 * EXPLAIN.
+			 */
+			newaggref->aggfilter = NULL;
+
 			return (Node *) newaggref;
 		}
 		else
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index 4c8c83d..c7872b6 100644
--- a/src/backend/optimizer/util/tlist.c
+++ b/src/backend/optimizer/util/tlist.c
@@ -785,6 +785,7 @@ apply_partialaggref_adjustment(PathTarget *target)
 			aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
 
 			newaggref = (Aggref *) copyObject(aggref);
+			newaggref->aggpartial = true;
 
 			/* use the serialization type, if one exists */
 			if (OidIsValid(aggform->aggserialtype))
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 2b47e95..d22eb15 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8215,6 +8215,31 @@ get_agg_expr(Aggref *aggref, deparse_context *context)
 	/* Extract the argument types as seen by the parser */
 	nargs = get_aggregate_argtypes(aggref, argtypes);
 
+	/*
+	 * We prefix partial aggregates which have final functions with the
+	 * keyword "PARTIAL". This avoids printing confusing things like
+	 * avg(avg(x)). We'll happily print max(max(x)), since aggregates like max
+	 * have no final function.
+	 */
+	if (aggref->aggpartial)
+	{
+		HeapTuple	aggTuple;
+		Form_pg_aggregate aggform;
+
+		/* check if the aggregate has a final function */
+		aggTuple = SearchSysCache1(AGGFNOID,
+							   ObjectIdGetDatum(aggref->aggfnoid));
+		if (!HeapTupleIsValid(aggTuple))
+			elog(ERROR, "cache lookup failed for aggregate %u",
+				 aggref->aggfnoid);
+		aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
+
+		if (OidIsValid(aggform->aggfinalfn))
+			appendStringInfoString(buf, "PARTIAL ");
+
+		ReleaseSysCache(aggTuple);
+	}
+
 	/* Print the aggregate name, schema-qualified if needed */
 	appendStringInfo(buf, "%s(%s",
 					 generate_function_name(aggref->aggfnoid, nargs,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1ffc0a1..ac23cab 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -280,6 +280,7 @@ typedef struct Aggref
 	bool		aggstar;		/* TRUE if argument list was really '*' */
 	bool		aggvariadic;	/* true if variadic arguments have been
 								 * combined into an array last argument */
+	bool		aggpartial;		/* true if belongs to partial agg node */
 	char		aggkind;		/* aggregate kind (see pg_aggregate.h) */
 	Index		agglevelsup;	/* > 0 if agg belongs to outer query */
 	int			location;		/* token location, or -1 if unknown */
