diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 781a736..479ae7e 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -78,6 +78,9 @@ static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
 					   ExplainState *es);
 static void show_agg_keys(AggState *astate, List *ancestors,
 			  ExplainState *es);
+static void show_grouping_set_keys(PlanState *planstate, const char *qlabel,
+				int nkeys, AttrNumber *keycols, List *gsets,
+				List *ancestors, ExplainState *es);
 static void show_group_keys(GroupState *gstate, List *ancestors,
 				ExplainState *es);
 static void show_sort_group_keys(PlanState *planstate, const char *qlabel,
@@ -1778,17 +1781,80 @@ show_agg_keys(AggState *astate, List *ancestors,
 {
 	Agg		   *plan = (Agg *) astate->ss.ps.plan;
 
-	if (plan->numCols > 0)
+	if (plan->numCols > 0 || plan->groupingSets)
 	{
 		/* The key columns refer to the tlist of the child plan */
 		ancestors = lcons(astate, ancestors);
-		show_sort_group_keys(outerPlanState(astate), "Group Key",
-							 plan->numCols, plan->grpColIdx,
-							 ancestors, es);
+		if (plan->groupingSets)
+			show_grouping_set_keys(outerPlanState(astate), "Grouping Sets",
+								   plan->numCols, plan->grpColIdx,
+								   plan->groupingSets,
+								   ancestors, es);
+		else
+			show_sort_group_keys(outerPlanState(astate), "Group Key",
+								 plan->numCols, plan->grpColIdx,
+								 ancestors, es);
 		ancestors = list_delete_first(ancestors);
 	}
 }
 
+static void
+show_grouping_set_keys(PlanState *planstate, const char *qlabel,
+					   int nkeys, AttrNumber *keycols, List *gsets,
+					   List *ancestors, ExplainState *es)
+{
+	Plan	   *plan = planstate->plan;
+	List	   *context;
+	List	   *result = NIL;
+	bool		useprefix;
+	char	   *exprstr;
+	StringInfoData buf;
+	ListCell   *lc;
+	ListCell   *lc2;
+
+	if (gsets == NIL)
+		return;
+
+	/* Set up deparsing context */
+	context = deparse_context_for_planstate((Node *) planstate,
+											ancestors,
+											es->rtable,
+											es->rtable_names);
+	useprefix = (list_length(es->rtable) > 1 || es->verbose);
+
+	foreach(lc, gsets)
+	{
+		char *sep = "";
+
+		initStringInfo(&buf);
+		appendStringInfoString(&buf, "(");
+
+		foreach(lc2, (List *) lfirst(lc))
+		{
+			Index		i = lfirst_int(lc2);
+			AttrNumber	keyresno = keycols[i];
+			TargetEntry *target = get_tle_by_resno(plan->targetlist,
+												   keyresno);
+
+			if (!target)
+				elog(ERROR, "no tlist entry for key %d", keyresno);
+			/* Deparse the expression, showing any top-level cast */
+			exprstr = deparse_expression((Node *) target->expr, context,
+										 useprefix, true);
+
+			appendStringInfoString(&buf, sep);
+			appendStringInfoString(&buf, exprstr);
+			sep = ", ";
+		}
+
+		appendStringInfoString(&buf, ")");
+
+		result = lappend(result, buf.data);
+	}
+
+	ExplainPropertyList(qlabel, result, es);
+}
+
 /*
  * Show the grouping keys for a Group node.
  */
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 7cfa63f..ed4b241 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -74,6 +74,8 @@ static Datum ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
 				  bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext,
 					  bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalScalarGroupedVarFast(ExprState *exprstate, ExprContext *econtext,
+					  bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate,
 					ExprContext *econtext,
 					bool *isNull, ExprDoneCond *isDone);
@@ -181,6 +183,8 @@ static Datum ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
 						bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
 					  bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalGroupingExpr(GroupingState *gstate, ExprContext *econtext,
+								  bool *isNull, ExprDoneCond *isDone);
 
 
 /* ----------------------------------------------------------------
@@ -568,6 +572,8 @@ ExecEvalWindowFunc(WindowFuncExprState *wfunc, ExprContext *econtext,
  * Note: ExecEvalScalarVar is executed only the first time through in a given
  * plan; it changes the ExprState's function pointer to pass control directly
  * to ExecEvalScalarVarFast after making one-time checks.
+ *
+ * We share this code with GroupedVar for simplicity.
  * ----------------------------------------------------------------
  */
 static Datum
@@ -645,8 +651,24 @@ ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
 		}
 	}
 
-	/* Skip the checking on future executions of node */
-	exprstate->evalfunc = ExecEvalScalarVarFast;
+	if (IsA(variable, GroupedVar))
+	{
+		Assert(variable->varno == OUTER_VAR);
+
+		/* Skip the checking on future executions of node */
+		exprstate->evalfunc = ExecEvalScalarGroupedVarFast;
+
+		if (!bms_is_member(attnum, econtext->grouped_cols))
+		{
+			*isNull = true;
+			return (Datum) 0;
+		}
+	}
+	else
+	{
+		/* Skip the checking on future executions of node */
+		exprstate->evalfunc = ExecEvalScalarVarFast;
+	}
 
 	/* Fetch the value from the slot */
 	return slot_getattr(slot, attnum, isNull);
@@ -694,6 +716,31 @@ ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext,
 	return slot_getattr(slot, attnum, isNull);
 }
 
+static Datum
+ExecEvalScalarGroupedVarFast(ExprState *exprstate, ExprContext *econtext,
+							 bool *isNull, ExprDoneCond *isDone)
+{
+	GroupedVar *variable = (GroupedVar *) exprstate->expr;
+	TupleTableSlot *slot;
+	AttrNumber	attnum;
+
+	if (isDone)
+		*isDone = ExprSingleResult;
+
+	slot = econtext->ecxt_outertuple;
+
+	attnum = variable->varattno;
+
+	if (!bms_is_member(attnum, econtext->grouped_cols))
+	{
+		*isNull = true;
+		return (Datum) 0;
+	}
+
+	/* Fetch the value from the slot */
+	return slot_getattr(slot, attnum, isNull);
+}
+
 /* ----------------------------------------------------------------
  *		ExecEvalWholeRowVar
  *
@@ -2987,6 +3034,40 @@ ExecEvalCaseTestExpr(ExprState *exprstate,
 	return econtext->caseValue_datum;
 }
 
+/*
+ * ExecEvalGroupingExpr
+ * Return a bitmask with a bit for each column.
+ * A bit is set if the column is not a part of grouping.
+ */
+
+static Datum
+ExecEvalGroupingExpr(GroupingState *gstate,
+					 ExprContext *econtext,
+					 bool *isNull,
+					 ExprDoneCond *isDone)
+{
+	int result = 0;
+	int current_val= 0;
+	ListCell *lc;
+
+	if (isDone)
+		*isDone = ExprSingleResult;
+
+	*isNull = false;
+
+	foreach(lc, (gstate->clauses))
+	{
+		current_val = lfirst_int(lc);
+
+		result = result << 1;
+
+		if (!bms_is_member(current_val, econtext->grouped_cols))
+			result = result | 1;
+	}
+
+	return (Datum) result;
+}
+
 /* ----------------------------------------------------------------
  *		ExecEvalArray - ARRAY[] expressions
  * ----------------------------------------------------------------
@@ -4385,6 +4466,44 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				state->evalfunc = ExecEvalScalarVar;
 			}
 			break;
+		case T_GroupedVar:
+			Assert(((Var *) node)->varattno != InvalidAttrNumber);
+			state = (ExprState *) makeNode(ExprState);
+			state->evalfunc = ExecEvalScalarVar;
+			break;
+		case T_Grouping:
+			{
+				Grouping *grp_node = (Grouping *) node;
+				GroupingState *grp_state = makeNode(GroupingState);
+				List     *result_list = NIL;
+				ListCell *lc;
+				Agg *agg = NULL;
+
+				if (parent != NULL)
+					if (!(IsA((parent->plan), Agg)))
+						elog(ERROR, "Parent is not Agg node");
+
+				agg = (Agg *) (parent->plan);
+
+				if (agg->groupingSets)
+				{
+					foreach(lc, (grp_node->refs))
+					{
+						int current_index = lfirst_int(lc);
+						int result = 0;
+
+						result = agg->grpColIdx[current_index];
+
+						result_list = lappend_int(result_list, result);
+					}
+				}
+
+				grp_state->clauses = result_list;
+
+				state = (ExprState *) grp_state;
+				state->evalfunc = (ExprStateEvalFunc) ExecEvalGroupingExpr;
+			}
+			break;
 		case T_Const:
 			state = (ExprState *) makeNode(ExprState);
 			state->evalfunc = ExecEvalConst;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 510d1c5..7b67797 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -243,7 +243,7 @@ typedef struct AggStatePerAggData
 	 * rest.
 	 */
 
-	Tuplesortstate *sortstate;	/* sort object, if DISTINCT or ORDER BY */
+	Tuplesortstate **sortstate;	/* sort object, if DISTINCT or ORDER BY */
 
 	/*
 	 * This field is a pre-initialized FunctionCallInfo struct used for
@@ -304,7 +304,8 @@ typedef struct AggHashEntryData
 
 static void initialize_aggregates(AggState *aggstate,
 					  AggStatePerAgg peragg,
-					  AggStatePerGroup pergroup);
+					  AggStatePerGroup pergroup,
+					  int numReinitialize);
 static void advance_transition_function(AggState *aggstate,
 							AggStatePerAgg peraggstate,
 							AggStatePerGroup pergroupstate);
@@ -338,81 +339,101 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 static void
 initialize_aggregates(AggState *aggstate,
 					  AggStatePerAgg peragg,
-					  AggStatePerGroup pergroup)
+					  AggStatePerGroup pergroup,
+					  int numReinitialize)
 {
 	int			aggno;
+	int         numGroupingSets = Max(aggstate->numsets, 1);
+	int         i = 0;
+
+	if (numReinitialize < 1)
+		numReinitialize = numGroupingSets;
 
 	for (aggno = 0; aggno < aggstate->numaggs; aggno++)
 	{
 		AggStatePerAgg peraggstate = &peragg[aggno];
-		AggStatePerGroup pergroupstate = &pergroup[aggno];
 
 		/*
 		 * Start a fresh sort operation for each DISTINCT/ORDER BY aggregate.
 		 */
 		if (peraggstate->numSortCols > 0)
 		{
-			/*
-			 * In case of rescan, maybe there could be an uncompleted sort
-			 * operation?  Clean it up if so.
-			 */
-			if (peraggstate->sortstate)
-				tuplesort_end(peraggstate->sortstate);
+			for (i = 0; i < numReinitialize; i++)
+			{
+				/*
+				 * In case of rescan, maybe there could be an uncompleted sort
+				 * operation?  Clean it up if so.
+				 */
+				if (peraggstate->sortstate[i])
+					tuplesort_end(peraggstate->sortstate[i]);
 
-			/*
-			 * We use a plain Datum sorter when there's a single input column;
-			 * otherwise sort the full tuple.  (See comments for
-			 * process_ordered_aggregate_single.)
-			 */
-			peraggstate->sortstate =
-				(peraggstate->numInputs == 1) ?
-				tuplesort_begin_datum(peraggstate->evaldesc->attrs[0]->atttypid,
-									  peraggstate->sortOperators[0],
-									  peraggstate->sortCollations[0],
-									  peraggstate->sortNullsFirst[0],
-									  work_mem, false) :
-				tuplesort_begin_heap(peraggstate->evaldesc,
-									 peraggstate->numSortCols,
-									 peraggstate->sortColIdx,
-									 peraggstate->sortOperators,
-									 peraggstate->sortCollations,
-									 peraggstate->sortNullsFirst,
-									 work_mem, false);
+				/*
+				 * We use a plain Datum sorter when there's a single input column;
+				 * otherwise sort the full tuple.  (See comments for
+				 * process_ordered_aggregate_single.)
+				 */
+				peraggstate->sortstate[i] =
+					(peraggstate->numInputs == 1) ?
+					tuplesort_begin_datum(peraggstate->evaldesc->attrs[0]->atttypid,
+										  peraggstate->sortOperators[0],
+										  peraggstate->sortCollations[0],
+										  peraggstate->sortNullsFirst[0],
+										  work_mem, false) :
+					tuplesort_begin_heap(peraggstate->evaldesc,
+										 peraggstate->numSortCols,
+										 peraggstate->sortColIdx,
+										 peraggstate->sortOperators,
+										 peraggstate->sortCollations,
+										 peraggstate->sortNullsFirst,
+										 work_mem, false);
+			}
 		}
 
-		/*
-		 * (Re)set transValue to the initial value.
-		 *
-		 * Note that when the initial value is pass-by-ref, we must copy it
-		 * (into the aggcontext) since we will pfree the transValue later.
+		/* If ROLLUP is present, we need to iterate over all the groups
+		 * that are present with the current aggstate. If ROLLUP is not
+		 * present, we only have one groupstate associated with the
+		 * current aggstate.
 		 */
-		if (peraggstate->initValueIsNull)
-			pergroupstate->transValue = peraggstate->initValue;
-		else
+
+		for (i = 0; i < numReinitialize; i++)
 		{
-			MemoryContext oldContext;
+			AggStatePerGroup pergroupstate = &pergroup[aggno + (i * (aggstate->numaggs))];
 
-			oldContext = MemoryContextSwitchTo(aggstate->aggcontext);
-			pergroupstate->transValue = datumCopy(peraggstate->initValue,
-												  peraggstate->transtypeByVal,
-												  peraggstate->transtypeLen);
-			MemoryContextSwitchTo(oldContext);
-		}
-		pergroupstate->transValueIsNull = peraggstate->initValueIsNull;
+			/*
+			 * (Re)set transValue to the initial value.
+			 *
+			 * Note that when the initial value is pass-by-ref, we must copy it
+			 * (into the aggcontext) since we will pfree the transValue later.
+			 */
+			if (peraggstate->initValueIsNull)
+				pergroupstate->transValue = peraggstate->initValue;
+			else
+			{
+				MemoryContext oldContext;
 
-		/*
-		 * If the initial value for the transition state doesn't exist in the
-		 * pg_aggregate table then we will let the first non-NULL value
-		 * returned from the outer procNode become the initial value. (This is
-		 * useful for aggregates like max() and min().) The noTransValue flag
-		 * signals that we still need to do this.
-		 */
-		pergroupstate->noTransValue = peraggstate->initValueIsNull;
+				oldContext = MemoryContextSwitchTo(aggstate->aggcontext[i]->ecxt_per_tuple_memory);
+				pergroupstate->transValue = datumCopy(peraggstate->initValue,
+													  peraggstate->transtypeByVal,
+													  peraggstate->transtypeLen);
+				MemoryContextSwitchTo(oldContext);
+			}
+			pergroupstate->transValueIsNull = peraggstate->initValueIsNull;
+
+			/*
+			 * If the initial value for the transition state doesn't exist in the
+			 * pg_aggregate table then we will let the first non-NULL value
+			 * returned from the outer procNode become the initial value. (This is
+			 * useful for aggregates like max() and min().) The noTransValue flag
+			 * signals that we still need to do this.
+			 */
+			pergroupstate->noTransValue = peraggstate->initValueIsNull;
+		}
 	}
 }
 
 /*
- * Given new input value(s), advance the transition function of an aggregate.
+ * Given new input value(s), advance the transition function of one aggregate
+ * within one grouping set only (already set in aggstate->current_set)
  *
  * The new values (and null flags) have been preloaded into argument positions
  * 1 and up in peraggstate->transfn_fcinfo, so that we needn't copy them again
@@ -455,7 +476,7 @@ advance_transition_function(AggState *aggstate,
 			 * We must copy the datum into aggcontext if it is pass-by-ref. We
 			 * do not need to pfree the old transValue, since it's NULL.
 			 */
-			oldContext = MemoryContextSwitchTo(aggstate->aggcontext);
+			oldContext = MemoryContextSwitchTo(aggstate->aggcontext[aggstate->current_set]->ecxt_per_tuple_memory);
 			pergroupstate->transValue = datumCopy(fcinfo->arg[1],
 												  peraggstate->transtypeByVal,
 												  peraggstate->transtypeLen);
@@ -503,7 +524,7 @@ advance_transition_function(AggState *aggstate,
 	{
 		if (!fcinfo->isnull)
 		{
-			MemoryContextSwitchTo(aggstate->aggcontext);
+			MemoryContextSwitchTo(aggstate->aggcontext[aggstate->current_set]->ecxt_per_tuple_memory);
 			newVal = datumCopy(newVal,
 							   peraggstate->transtypeByVal,
 							   peraggstate->transtypeLen);
@@ -530,11 +551,13 @@ static void
 advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
 {
 	int			aggno;
+	int         groupno = 0;
+	int         numGroupingSets = Max(aggstate->numsets, 1);
+	int         numAggs = aggstate->numaggs;
 
-	for (aggno = 0; aggno < aggstate->numaggs; aggno++)
+	for (aggno = 0; aggno < numAggs; aggno++)
 	{
 		AggStatePerAgg peraggstate = &aggstate->peragg[aggno];
-		AggStatePerGroup pergroupstate = &pergroup[aggno];
 		ExprState  *filter = peraggstate->aggrefstate->aggfilter;
 		int			numTransInputs = peraggstate->numTransInputs;
 		int			i;
@@ -578,13 +601,16 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
 					continue;
 			}
 
-			/* OK, put the tuple into the tuplesort object */
-			if (peraggstate->numInputs == 1)
-				tuplesort_putdatum(peraggstate->sortstate,
-								   slot->tts_values[0],
-								   slot->tts_isnull[0]);
-			else
-				tuplesort_puttupleslot(peraggstate->sortstate, slot);
+			for (groupno = 0; groupno < numGroupingSets; groupno++)
+			{
+				/* OK, put the tuple into the tuplesort object */
+				if (peraggstate->numInputs == 1)
+					tuplesort_putdatum(peraggstate->sortstate[groupno],
+									   slot->tts_values[0],
+									   slot->tts_isnull[0]);
+				else
+					tuplesort_puttupleslot(peraggstate->sortstate[groupno], slot);
+			}
 		}
 		else
 		{
@@ -600,7 +626,14 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
 				fcinfo->argnull[i + 1] = slot->tts_isnull[i];
 			}
 
-			advance_transition_function(aggstate, peraggstate, pergroupstate);
+			for (groupno = 0; groupno < numGroupingSets; groupno++)
+			{
+				AggStatePerGroup pergroupstate = &pergroup[aggno + (groupno * numAggs)];
+
+				aggstate->current_set = groupno;
+
+				advance_transition_function(aggstate, peraggstate, pergroupstate);
+			}
 		}
 	}
 }
@@ -623,6 +656,9 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
  * is around 300% faster.  (The speedup for by-reference types is less
  * but still noticeable.)
  *
+ * This function handles only one grouping set (already set in
+ * aggstate->current_set).
+ *
  * When called, CurrentMemoryContext should be the per-query context.
  */
 static void
@@ -642,7 +678,7 @@ process_ordered_aggregate_single(AggState *aggstate,
 
 	Assert(peraggstate->numDistinctCols < 2);
 
-	tuplesort_performsort(peraggstate->sortstate);
+	tuplesort_performsort(peraggstate->sortstate[aggstate->current_set]);
 
 	/* Load the column into argument 1 (arg 0 will be transition value) */
 	newVal = fcinfo->arg + 1;
@@ -654,7 +690,7 @@ process_ordered_aggregate_single(AggState *aggstate,
 	 * pfree them when they are no longer needed.
 	 */
 
-	while (tuplesort_getdatum(peraggstate->sortstate, true,
+	while (tuplesort_getdatum(peraggstate->sortstate[aggstate->current_set], true,
 							  newVal, isNull))
 	{
 		/*
@@ -698,8 +734,8 @@ process_ordered_aggregate_single(AggState *aggstate,
 	if (!oldIsNull && !peraggstate->inputtypeByVal)
 		pfree(DatumGetPointer(oldVal));
 
-	tuplesort_end(peraggstate->sortstate);
-	peraggstate->sortstate = NULL;
+	tuplesort_end(peraggstate->sortstate[aggstate->current_set]);
+	peraggstate->sortstate[aggstate->current_set] = NULL;
 }
 
 /*
@@ -709,6 +745,9 @@ process_ordered_aggregate_single(AggState *aggstate,
  * sort, read out the values in sorted order, and run the transition
  * function on each value (applying DISTINCT if appropriate).
  *
+ * This function handles only one grouping set (already set in
+ * aggstate->current_set).
+ *
  * When called, CurrentMemoryContext should be the per-query context.
  */
 static void
@@ -725,13 +764,13 @@ process_ordered_aggregate_multi(AggState *aggstate,
 	bool		haveOldValue = false;
 	int			i;
 
-	tuplesort_performsort(peraggstate->sortstate);
+	tuplesort_performsort(peraggstate->sortstate[aggstate->current_set]);
 
 	ExecClearTuple(slot1);
 	if (slot2)
 		ExecClearTuple(slot2);
 
-	while (tuplesort_gettupleslot(peraggstate->sortstate, true, slot1))
+	while (tuplesort_gettupleslot(peraggstate->sortstate[aggstate->current_set], true, slot1))
 	{
 		/*
 		 * Extract the first numTransInputs columns as datums to pass to the
@@ -779,8 +818,8 @@ process_ordered_aggregate_multi(AggState *aggstate,
 	if (slot2)
 		ExecClearTuple(slot2);
 
-	tuplesort_end(peraggstate->sortstate);
-	peraggstate->sortstate = NULL;
+	tuplesort_end(peraggstate->sortstate[aggstate->current_set]);
+	peraggstate->sortstate[aggstate->current_set] = NULL;
 }
 
 /*
@@ -832,7 +871,7 @@ finalize_aggregate(AggState *aggstate,
 		/* set up aggstate->curperagg for AggGetAggref() */
 		aggstate->curperagg = peraggstate;
 
-		InitFunctionCallInfoData(fcinfo, &(peraggstate->finalfn),
+		InitFunctionCallInfoData(fcinfo, &peraggstate->finalfn,
 								 numFinalArgs,
 								 peraggstate->aggCollation,
 								 (void *) aggstate, NULL);
@@ -916,7 +955,8 @@ find_unaggregated_cols_walker(Node *node, Bitmapset **colnos)
 		*colnos = bms_add_member(*colnos, var->varattno);
 		return false;
 	}
-	if (IsA(node, Aggref))		/* do not descend into aggregate exprs */
+	if (IsA(node, Aggref) || IsA(node, Grouping))
+		/* do not descend into aggregate exprs */
 		return false;
 	return expression_tree_walker(node, find_unaggregated_cols_walker,
 								  (void *) colnos);
@@ -946,7 +986,7 @@ build_hash_table(AggState *aggstate)
 											  aggstate->hashfunctions,
 											  node->numGroups,
 											  entrysize,
-											  aggstate->aggcontext,
+											  aggstate->aggcontext[0]->ecxt_per_tuple_memory,
 											  tmpmem);
 }
 
@@ -1057,7 +1097,7 @@ lookup_hash_entry(AggState *aggstate, TupleTableSlot *inputslot)
 	if (isnew)
 	{
 		/* initialize aggregates for new tuple group */
-		initialize_aggregates(aggstate, aggstate->peragg, entry->pergroup);
+		initialize_aggregates(aggstate, aggstate->peragg, entry->pergroup, 0);
 	}
 
 	return entry;
@@ -1131,7 +1171,13 @@ agg_retrieve_direct(AggState *aggstate)
 	AggStatePerGroup pergroup;
 	TupleTableSlot *outerslot;
 	TupleTableSlot *firstSlot;
-	int			aggno;
+	int			   aggno;
+	bool           hasRollup = aggstate->numsets > 0;
+	int            numGroupingSets = Max(aggstate->numsets, 1);
+	int            currentGroup = 0;
+	int            currentSize = 0;
+	int            numReset = 1;
+	int            i;
 
 	/*
 	 * get state info from node
@@ -1150,131 +1196,233 @@ agg_retrieve_direct(AggState *aggstate)
 	/*
 	 * We loop retrieving groups until we find one matching
 	 * aggstate->ss.ps.qual
+	 *
+	 * For grouping sets, we have the invariant that aggstate->projected_set is
+	 * either -1 (initial call) or the index (starting from 0) in gset_lengths
+	 * for the group we just completed (either by projecting a row or by
+	 * discarding it in the qual).
 	 */
 	while (!aggstate->agg_done)
 	{
 		/*
-		 * If we don't already have the first tuple of the new group, fetch it
-		 * from the outer plan.
-		 */
-		if (aggstate->grp_firstTuple == NULL)
-		{
-			outerslot = ExecProcNode(outerPlan);
-			if (!TupIsNull(outerslot))
-			{
-				/*
-				 * Make a copy of the first input tuple; we will use this for
-				 * comparisons (in group mode) and for projection.
-				 */
-				aggstate->grp_firstTuple = ExecCopySlotTuple(outerslot);
-			}
-			else
-			{
-				/* outer plan produced no tuples at all */
-				aggstate->agg_done = true;
-				/* If we are grouping, we should produce no tuples too */
-				if (node->aggstrategy != AGG_PLAIN)
-					return NULL;
-			}
-		}
-
-		/*
 		 * Clear the per-output-tuple context for each group, as well as
 		 * aggcontext (which contains any pass-by-ref transvalues of the old
 		 * group).  We also clear any child contexts of the aggcontext; some
 		 * aggregate functions store working state in such contexts.
 		 *
 		 * We use ReScanExprContext not just ResetExprContext because we want
-		 * any registered shutdown callbacks to be called.  That allows
+		 * any registered shutdown callbacks to be called.	That allows
 		 * aggregate functions to ensure they've cleaned up any non-memory
 		 * resources.
 		 */
 		ReScanExprContext(econtext);
 
-		MemoryContextResetAndDeleteChildren(aggstate->aggcontext);
+		if (aggstate->projected_set >= 0 && aggstate->projected_set < numGroupingSets)
+			numReset = aggstate->projected_set + 1;
+		else
+			numReset = numGroupingSets;
 
-		/*
-		 * Initialize working state for a new input tuple group
+		for (i = 0; i < numReset; i++)
+		{
+			ReScanExprContext(aggstate->aggcontext[i]);
+			MemoryContextDeleteChildren(aggstate->aggcontext[i]->ecxt_per_tuple_memory);
+		}
+
+		/* Check if input is complete and there are no more groups to project. */
+		if (aggstate->input_done == true
+			&& aggstate->projected_set >= (numGroupingSets - 1))
+		{
+			aggstate->agg_done = true;
+			break;
+		}
+
+		if (aggstate->projected_set >= 0 && aggstate->projected_set < (numGroupingSets - 1))
+			currentSize = aggstate->gset_lengths[aggstate->projected_set + 1];
+		else
+			currentSize = 0;
+
+		/*-
+		 * If a subgroup for the current grouping set is present, project it.
+		 *
+		 * We have a new group if:
+		 *  - we're out of input but haven't projected all grouping sets
+		 *    (checked above)
+		 * OR
+		 *    - we already projected a row that wasn't from the last grouping
+		 *      set
+		 *    AND
+		 *    - the next grouping set has at least one grouping column (since
+		 *      empty grouping sets project only once input is exhausted)
+		 *    AND
+		 *    - the previous and pending rows differ on the grouping columns
+		 *      of the next grouping set
 		 */
-		initialize_aggregates(aggstate, peragg, pergroup);
+		if (aggstate->input_done
+			|| (node->aggstrategy == AGG_SORTED
+				&& aggstate->projected_set != -1
+				&& aggstate->projected_set < (numGroupingSets - 1)
+				&& currentSize > 0
+				&& !execTuplesMatch(econtext->ecxt_outertuple,
+									tmpcontext->ecxt_outertuple,
+									currentSize,
+									node->grpColIdx,
+									aggstate->eqfunctions,
+									tmpcontext->ecxt_per_tuple_memory)))
+		{
+			++aggstate->projected_set;
 
-		if (aggstate->grp_firstTuple != NULL)
+			Assert(aggstate->projected_set < numGroupingSets);
+			Assert(currentSize > 0 || aggstate->input_done);
+		}
+		else
 		{
 			/*
-			 * Store the copied first input tuple in the tuple table slot
-			 * reserved for it.  The tuple will be deleted when it is cleared
-			 * from the slot.
+			 * we no longer care what group we just projected, the next projection
+			 * will always be the first (or only) grouping set (unless the input
+			 * proves to be empty).
 			 */
-			ExecStoreTuple(aggstate->grp_firstTuple,
-						   firstSlot,
-						   InvalidBuffer,
-						   true);
-			aggstate->grp_firstTuple = NULL;	/* don't keep two pointers */
-
-			/* set up for first advance_aggregates call */
-			tmpcontext->ecxt_outertuple = firstSlot;
+			aggstate->projected_set = 0;
 
 			/*
-			 * Process each outer-plan tuple, and then fetch the next one,
-			 * until we exhaust the outer plan or cross a group boundary.
+			 * If we don't already have the first tuple of the new group, fetch it
+			 * from the outer plan.
 			 */
-			for (;;)
+			if (aggstate->grp_firstTuple == NULL)
 			{
-				advance_aggregates(aggstate, pergroup);
-
-				/* Reset per-input-tuple context after each tuple */
-				ResetExprContext(tmpcontext);
-
 				outerslot = ExecProcNode(outerPlan);
-				if (TupIsNull(outerslot))
+				if (!TupIsNull(outerslot))
 				{
-					/* no more outer-plan tuples available */
-					aggstate->agg_done = true;
-					break;
+					/*
+					 * Make a copy of the first input tuple; we will use this for
+					 * comparisons (in group mode) and for projection.
+					 */
+					aggstate->grp_firstTuple = ExecCopySlotTuple(outerslot);
 				}
-				/* set up for next advance_aggregates call */
-				tmpcontext->ecxt_outertuple = outerslot;
+				else
+				{
+					/* outer plan produced no tuples at all */
+					if (hasRollup)
+					{
+						/*
+						 * If there was no input at all, we need to project
+						 * rows only if there are grouping sets of size 0.
+						 * Note that this implies that there can't be any
+						 * references to ungrouped Vars, which would otherwise
+						 * cause issues with the empty output slot.
+						 */
+						aggstate->input_done = true;
+
+						while (aggstate->gset_lengths[aggstate->projected_set] > 0)
+						{
+							aggstate->projected_set += 1;
+							if (aggstate->projected_set >= numGroupingSets)
+							{
+								aggstate->agg_done = true;
+								return NULL;
+							}
+						}
+					}
+					else
+					{
+						aggstate->agg_done = true;
+						/* If we are grouping, we should produce no tuples too */
+						if (node->aggstrategy != AGG_PLAIN)
+							return NULL;
+					}
+				}
+			}
+
+			/*
+			 * Initialize working state for a new input tuple group
+			 */
+			initialize_aggregates(aggstate, peragg, pergroup, numReset);
+
+			if (aggstate->grp_firstTuple != NULL)
+			{
+				/*
+				 * Store the copied first input tuple in the tuple table slot
+				 * reserved for it.  The tuple will be deleted when it is cleared
+				 * from the slot.
+				 */
+				ExecStoreTuple(aggstate->grp_firstTuple,
+							   firstSlot,
+							   InvalidBuffer,
+							   true);
+				aggstate->grp_firstTuple = NULL;	/* don't keep two pointers */
+
+				/* set up for first advance_aggregates call */
+				tmpcontext->ecxt_outertuple = firstSlot;
 
 				/*
-				 * If we are grouping, check whether we've crossed a group
-				 * boundary.
+				 * Process each outer-plan tuple, and then fetch the next one,
+				 * until we exhaust the outer plan or cross a group boundary.
 				 */
-				if (node->aggstrategy == AGG_SORTED)
+				for (;;)
 				{
-					if (!execTuplesMatch(firstSlot,
-										 outerslot,
-										 node->numCols, node->grpColIdx,
-										 aggstate->eqfunctions,
-										 tmpcontext->ecxt_per_tuple_memory))
+					advance_aggregates(aggstate, pergroup);
+
+					/* Reset per-input-tuple context after each tuple */
+					ResetExprContext(tmpcontext);
+
+					outerslot = ExecProcNode(outerPlan);
+					if (TupIsNull(outerslot))
 					{
-						/*
-						 * Save the first input tuple of the next group.
-						 */
-						aggstate->grp_firstTuple = ExecCopySlotTuple(outerslot);
-						break;
+						/* no more outer-plan tuples available */
+						if (hasRollup)
+						{
+							aggstate->input_done = true;
+							break;
+						}
+						else
+						{
+							aggstate->agg_done = true;
+							break;
+						}
+					}
+					/* set up for next advance_aggregates call */
+					tmpcontext->ecxt_outertuple = outerslot;
+
+					/*
+					 * If we are grouping, check whether we've crossed a group
+					 * boundary.
+					 */
+					if (node->aggstrategy == AGG_SORTED)
+					{
+						if (!execTuplesMatch(firstSlot,
+											 outerslot,
+											 node->numCols,
+											 node->grpColIdx,
+											 aggstate->eqfunctions,
+											 tmpcontext->ecxt_per_tuple_memory))
+						{
+							aggstate->grp_firstTuple = ExecCopySlotTuple(outerslot);
+							break;
+						}
 					}
 				}
 			}
+
+			/*
+			 * Use the representative input tuple for any references to
+			 * non-aggregated input columns in aggregate direct args, the node
+			 * qual, and the tlist.  (If we are not grouping, and there are no
+			 * input rows at all, we will come here with an empty firstSlot ...
+			 * but if not grouping, there can't be any references to
+			 * non-aggregated input columns, so no problem.)
+			 */
+			econtext->ecxt_outertuple = firstSlot;
 		}
 
-		/*
-		 * Use the representative input tuple for any references to
-		 * non-aggregated input columns in aggregate direct args, the node
-		 * qual, and the tlist.  (If we are not grouping, and there are no
-		 * input rows at all, we will come here with an empty firstSlot ...
-		 * but if not grouping, there can't be any references to
-		 * non-aggregated input columns, so no problem.)
-		 */
-		econtext->ecxt_outertuple = firstSlot;
+		Assert(aggstate->projected_set >= 0);
+
+		aggstate->current_set = currentGroup = aggstate->projected_set;
 
-		/*
-		 * Done scanning input tuple group. Finalize each aggregate
-		 * calculation, and stash results in the per-output-tuple context.
-		 */
 		for (aggno = 0; aggno < aggstate->numaggs; aggno++)
 		{
 			AggStatePerAgg peraggstate = &peragg[aggno];
-			AggStatePerGroup pergroupstate = &pergroup[aggno];
+			AggStatePerGroup pergroupstate;
+
+			pergroupstate = &pergroup[aggno + (currentGroup * (aggstate->numaggs))];
 
 			if (peraggstate->numSortCols > 0)
 			{
@@ -1292,6 +1440,9 @@ agg_retrieve_direct(AggState *aggstate)
 							   &aggvalues[aggno], &aggnulls[aggno]);
 		}
 
+		if (hasRollup)
+			econtext->grouped_cols = aggstate->grouped_cols[currentGroup];
+
 		/*
 		 * Check the qual (HAVING clause); if the group does not match, ignore
 		 * it and loop back to try to process another group.
@@ -1306,6 +1457,7 @@ agg_retrieve_direct(AggState *aggstate)
 			ExprDoneCond isDone;
 
 			result = ExecProject(aggstate->ss.ps.ps_ProjInfo, &isDone);
+			slot_getallattrs(result);
 
 			if (isDone != ExprEndResult)
 			{
@@ -1495,6 +1647,10 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	int			numaggs,
 				aggno;
 	ListCell   *l;
+	int        numGroupingSets = 1;
+	int        currentsortno = 0;
+	int        i = 0;
+	int        j = 0;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1508,38 +1664,69 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 
 	aggstate->aggs = NIL;
 	aggstate->numaggs = 0;
+	aggstate->numsets = 0;
 	aggstate->eqfunctions = NULL;
 	aggstate->hashfunctions = NULL;
+	aggstate->projected_set = -1;
+	aggstate->current_set = 0;
 	aggstate->peragg = NULL;
 	aggstate->curperagg = NULL;
 	aggstate->agg_done = false;
+	aggstate->input_done = false;
 	aggstate->pergroup = NULL;
 	aggstate->grp_firstTuple = NULL;
 	aggstate->hashtable = NULL;
 
+	if (node->groupingSets)
+	{
+		Assert(node->aggstrategy != AGG_HASHED);
+
+		numGroupingSets = list_length(node->groupingSets);
+		aggstate->numsets = numGroupingSets;
+		aggstate->gset_lengths = palloc(numGroupingSets * sizeof(int));
+		aggstate->grouped_cols = palloc(numGroupingSets * sizeof(Bitmapset *));
+
+		i = 0;
+		foreach(l, node->groupingSets)
+		{
+			int current_length = list_length(lfirst(l));
+			Bitmapset *cols = NULL;
+
+			/* planner forces this to be correct */
+			for (j = 0; j < current_length; ++j)
+				cols = bms_add_member(cols, node->grpColIdx[j]);
+
+			aggstate->grouped_cols[i] = cols;
+			aggstate->gset_lengths[i] = current_length;
+			++i;
+		}
+	}
+
+	aggstate->aggcontext = (ExprContext **) palloc0(sizeof(ExprContext *) * numGroupingSets);
+
 	/*
-	 * Create expression contexts.  We need two, one for per-input-tuple
-	 * processing and one for per-output-tuple processing.  We cheat a little
-	 * by using ExecAssignExprContext() to build both.
+	 * Create expression contexts.  We need three or more, one for
+	 * per-input-tuple processing, one for per-output-tuple processing, and one
+	 * for each grouping set.  The per-tuple memory context of the
+	 * per-grouping-set ExprContexts replaces the standalone memory context
+	 * formerly used to hold transition values.  We cheat a little by using
+	 * ExecAssignExprContext() to build all of them.
+	 *
+	 * NOTE: the details of what is stored in aggcontext and what is stored in
+	 * the regular per-query memory context are driven by a simple decision: we
+	 * want to reset the aggcontext at group boundaries (if not hashing) and in
+	 * ExecReScanAgg to recover no-longer-wanted space.
 	 */
 	ExecAssignExprContext(estate, &aggstate->ss.ps);
 	aggstate->tmpcontext = aggstate->ss.ps.ps_ExprContext;
-	ExecAssignExprContext(estate, &aggstate->ss.ps);
 
-	/*
-	 * We also need a long-lived memory context for holding hashtable data
-	 * structures and transition values.  NOTE: the details of what is stored
-	 * in aggcontext and what is stored in the regular per-query memory
-	 * context are driven by a simple decision: we want to reset the
-	 * aggcontext at group boundaries (if not hashing) and in ExecReScanAgg to
-	 * recover no-longer-wanted space.
-	 */
-	aggstate->aggcontext =
-		AllocSetContextCreate(CurrentMemoryContext,
-							  "AggContext",
-							  ALLOCSET_DEFAULT_MINSIZE,
-							  ALLOCSET_DEFAULT_INITSIZE,
-							  ALLOCSET_DEFAULT_MAXSIZE);
+	for (i = 0; i < numGroupingSets; ++i)
+	{
+		ExecAssignExprContext(estate, &aggstate->ss.ps);
+		aggstate->aggcontext[i] = aggstate->ss.ps.ps_ExprContext;
+	}
+
+	ExecAssignExprContext(estate, &aggstate->ss.ps);
 
 	/*
 	 * tuple table initialization
@@ -1645,7 +1832,8 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	{
 		AggStatePerGroup pergroup;
 
-		pergroup = (AggStatePerGroup) palloc0(sizeof(AggStatePerGroupData) * numaggs);
+		pergroup = (AggStatePerGroup) palloc0(sizeof(AggStatePerGroupData) * numaggs * numGroupingSets);
+
 		aggstate->pergroup = pergroup;
 	}
 
@@ -1708,7 +1896,10 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 		/* Begin filling in the peraggstate data */
 		peraggstate->aggrefstate = aggrefstate;
 		peraggstate->aggref = aggref;
-		peraggstate->sortstate = NULL;
+		peraggstate->sortstate = (Tuplesortstate**) palloc0(sizeof(Tuplesortstate*) * numGroupingSets);
+
+		for (currentsortno = 0; currentsortno < numGroupingSets; currentsortno++)
+			peraggstate->sortstate[currentsortno] = NULL;
 
 		/* Fetch the pg_aggregate row */
 		aggTuple = SearchSysCache1(AGGFNOID,
@@ -2016,31 +2207,35 @@ ExecEndAgg(AggState *node)
 {
 	PlanState  *outerPlan;
 	int			aggno;
+	int			numGroupingSets = Max(node->numsets, 1);
+	int			i = 0;
 
 	/* Make sure we have closed any open tuplesorts */
 	for (aggno = 0; aggno < node->numaggs; aggno++)
 	{
 		AggStatePerAgg peraggstate = &node->peragg[aggno];
 
-		if (peraggstate->sortstate)
-			tuplesort_end(peraggstate->sortstate);
+		for (i = 0; i < numGroupingSets; i++)
+		{
+			if (peraggstate->sortstate[i])
+				tuplesort_end(peraggstate->sortstate[i]);
+		}
 	}
 
 	/* And ensure any agg shutdown callbacks have been called */
-	ReScanExprContext(node->ss.ps.ps_ExprContext);
+	for (i = 0; i < numGroupingSets; ++i)
+		ReScanExprContext(node->aggcontext[i]);
 
 	/*
-	 * Free both the expr contexts.
+	 * We don't actually free any ExprContexts here (see comment in
+	 * ExecFreeExprContext), just unlinking the output one from the plan node
+	 * suffices.
 	 */
 	ExecFreeExprContext(&node->ss.ps);
-	node->ss.ps.ps_ExprContext = node->tmpcontext;
-	ExecFreeExprContext(&node->ss.ps);
 
 	/* clean up tuple table */
 	ExecClearTuple(node->ss.ss_ScanTupleSlot);
 
-	MemoryContextDelete(node->aggcontext);
-
 	outerPlan = outerPlanState(node);
 	ExecEndNode(outerPlan);
 }
@@ -2049,13 +2244,17 @@ void
 ExecReScanAgg(AggState *node)
 {
 	ExprContext *econtext = node->ss.ps.ps_ExprContext;
+	Agg		   *aggnode = (Agg *) node->ss.ps.plan;
 	int			aggno;
+	int         numGroupingSets = Max(node->numsets, 1);
+	int         groupno;
+	int         i;
 
 	node->agg_done = false;
 
 	node->ss.ps.ps_TupFromTlist = false;
 
-	if (((Agg *) node->ss.ps.plan)->aggstrategy == AGG_HASHED)
+	if (aggnode->aggstrategy == AGG_HASHED)
 	{
 		/*
 		 * In the hashed case, if we haven't yet built the hash table then we
@@ -2081,14 +2280,35 @@ ExecReScanAgg(AggState *node)
 	/* Make sure we have closed any open tuplesorts */
 	for (aggno = 0; aggno < node->numaggs; aggno++)
 	{
-		AggStatePerAgg peraggstate = &node->peragg[aggno];
+		for (groupno = 0; groupno < numGroupingSets; groupno++)
+		{
+			AggStatePerAgg peraggstate = &node->peragg[aggno];
 
-		if (peraggstate->sortstate)
-			tuplesort_end(peraggstate->sortstate);
-		peraggstate->sortstate = NULL;
+			if (peraggstate->sortstate[groupno])
+			{
+				tuplesort_end(peraggstate->sortstate[groupno]);
+				peraggstate->sortstate[groupno] = NULL;
+			}
+		}
 	}
 
-	/* We don't need to ReScanExprContext here; ExecReScan already did it */
+	/*
+	 * We don't need to ReScanExprContext the output tuple context here;
+	 * ExecReScan already did it. But we do need to reset our per-grouping-set
+	 * contexts, which may have transvalues stored in them.
+	 *
+	 * Note that with AGG_HASHED, the hash table is allocated in a sub-context
+	 * of the aggcontext. We're going to rebuild the hash table from scratch,
+	 * so we need to use MemoryContextDeleteChildren() to avoid leaking the old
+	 * hash table's memory context header. (ReScanExprContext does the actual
+	 * reset, but it doesn't delete child contexts.)
+	 */
+
+	for (i = 0; i < numGroupingSets; ++i)
+	{
+		ReScanExprContext(node->aggcontext[i]);
+		MemoryContextDeleteChildren(node->aggcontext[i]->ecxt_per_tuple_memory);
+	}
 
 	/* Release first tuple of group, if we have made a copy */
 	if (node->grp_firstTuple != NULL)
@@ -2101,16 +2321,7 @@ ExecReScanAgg(AggState *node)
 	MemSet(econtext->ecxt_aggvalues, 0, sizeof(Datum) * node->numaggs);
 	MemSet(econtext->ecxt_aggnulls, 0, sizeof(bool) * node->numaggs);
 
-	/*
-	 * Release all temp storage. Note that with AGG_HASHED, the hash table is
-	 * allocated in a sub-context of the aggcontext. We're going to rebuild
-	 * the hash table from scratch, so we need to use
-	 * MemoryContextResetAndDeleteChildren() to avoid leaking the old hash
-	 * table's memory context header.
-	 */
-	MemoryContextResetAndDeleteChildren(node->aggcontext);
-
-	if (((Agg *) node->ss.ps.plan)->aggstrategy == AGG_HASHED)
+	if (aggnode->aggstrategy == AGG_HASHED)
 	{
 		/* Rebuild an empty hash table */
 		build_hash_table(node);
@@ -2122,7 +2333,7 @@ ExecReScanAgg(AggState *node)
 		 * Reset the per-group state (in particular, mark transvalues null)
 		 */
 		MemSet(node->pergroup, 0,
-			   sizeof(AggStatePerGroupData) * node->numaggs);
+			   sizeof(AggStatePerGroupData) * node->numaggs * numGroupingSets);
 	}
 
 	/*
@@ -2150,8 +2361,11 @@ ExecReScanAgg(AggState *node)
  * values could conceivably appear in future.)
  *
  * If aggcontext isn't NULL, the function also stores at *aggcontext the
- * identity of the memory context that aggregate transition values are
- * being stored in.
+ * identity of the memory context that aggregate transition values are being
+ * stored in.  Note that the same aggregate call site (flinfo) may be called
+ * interleaved on different transition values in different contexts, so it's
+ * not kosher to cache aggcontext under fn_extra.  It is, however, kosher to
+ * cache it in the transvalue itself (for internal-type transvalues).
  */
 int
 AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
@@ -2159,7 +2373,11 @@ AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext)
 	if (fcinfo->context && IsA(fcinfo->context, AggState))
 	{
 		if (aggcontext)
-			*aggcontext = ((AggState *) fcinfo->context)->aggcontext;
+		{
+			AggState    *aggstate = ((AggState *) fcinfo->context);
+			ExprContext *cxt  = aggstate->aggcontext[aggstate->current_set];
+			*aggcontext = cxt->ecxt_per_tuple_memory;
+		}
 		return AGG_CONTEXT_AGGREGATE;
 	}
 	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
@@ -2243,8 +2461,9 @@ AggRegisterCallback(FunctionCallInfo fcinfo,
 	if (fcinfo->context && IsA(fcinfo->context, AggState))
 	{
 		AggState   *aggstate = (AggState *) fcinfo->context;
+		ExprContext *cxt  = aggstate->aggcontext[aggstate->current_set];
 
-		RegisterExprContextCallback(aggstate->ss.ps.ps_ExprContext, func, arg);
+		RegisterExprContextCallback(cxt, func, arg);
 
 		return;
 	}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3088578..6757763 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -779,6 +779,7 @@ _copyAgg(const Agg *from)
 		COPY_POINTER_FIELD(grpOperators, from->numCols * sizeof(Oid));
 	}
 	COPY_SCALAR_FIELD(numGroups);
+	COPY_NODE_FIELD(groupingSets);
 
 	return newnode;
 }
@@ -1065,6 +1066,58 @@ _copyVar(const Var *from)
 }
 
 /*
+ * _copyGrouping
+ */
+static Grouping *
+_copyGrouping(const Grouping *from)
+{
+	Grouping		   *newnode = makeNode(Grouping);
+
+	COPY_NODE_FIELD(args);
+	COPY_NODE_FIELD(refs);
+	COPY_LOCATION_FIELD(location);
+	COPY_SCALAR_FIELD(agglevelsup);
+
+	return newnode;
+}
+
+/*
+ * _copyGroupedVar
+ */
+static GroupedVar *
+_copyGroupedVar(const GroupedVar *from)
+{
+	GroupedVar		   *newnode = makeNode(GroupedVar);
+
+	COPY_SCALAR_FIELD(varno);
+	COPY_SCALAR_FIELD(varattno);
+	COPY_SCALAR_FIELD(vartype);
+	COPY_SCALAR_FIELD(vartypmod);
+	COPY_SCALAR_FIELD(varcollid);
+	COPY_SCALAR_FIELD(varlevelsup);
+	COPY_SCALAR_FIELD(varnoold);
+	COPY_SCALAR_FIELD(varoattno);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
+ * _copyGroupingSet
+ */
+static GroupingSet *
+_copyGroupingSet(const GroupingSet *from)
+{
+	GroupingSet		   *newnode = makeNode(GroupingSet);
+
+	COPY_SCALAR_FIELD(kind);
+	COPY_NODE_FIELD(content);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+/*
  * _copyConst
  */
 static Const *
@@ -2495,6 +2548,7 @@ _copyQuery(const Query *from)
 	COPY_NODE_FIELD(withCheckOptions);
 	COPY_NODE_FIELD(returningList);
 	COPY_NODE_FIELD(groupClause);
+	COPY_NODE_FIELD(groupingSets);
 	COPY_NODE_FIELD(havingQual);
 	COPY_NODE_FIELD(windowClause);
 	COPY_NODE_FIELD(distinctClause);
@@ -4079,6 +4133,15 @@ copyObject(const void *from)
 		case T_Var:
 			retval = _copyVar(from);
 			break;
+		case T_GroupedVar:
+			retval = _copyGroupedVar(from);
+			break;
+		case T_Grouping:
+			retval = _copyGrouping(from);
+			break;
+		case T_GroupingSet:
+			retval = _copyGroupingSet(from);
+			break;
 		case T_Const:
 			retval = _copyConst(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1b07db6..59ce09d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -153,6 +153,52 @@ _equalVar(const Var *a, const Var *b)
 }
 
 static bool
+_equalGrouping(const Grouping *a, const Grouping *b)
+{
+	COMPARE_NODE_FIELD(args);
+
+	/*
+	 * Special-case the refs field: we might compare nodes where one has been
+	 * filled in and the other has not yet.  (But out of sheer paranoia, if
+	 * both are filled in, compare them.)
+	 */
+
+	if (a->refs != NIL && b->refs != NIL)
+		COMPARE_NODE_FIELD(refs);
+
+	COMPARE_LOCATION_FIELD(location);
+	COMPARE_SCALAR_FIELD(agglevelsup);
+
+	return true;
+}
+
+static bool
+_equalGroupedVar(const GroupedVar *a, const GroupedVar *b)
+{
+	COMPARE_SCALAR_FIELD(varno);
+	COMPARE_SCALAR_FIELD(varattno);
+	COMPARE_SCALAR_FIELD(vartype);
+	COMPARE_SCALAR_FIELD(vartypmod);
+	COMPARE_SCALAR_FIELD(varcollid);
+	COMPARE_SCALAR_FIELD(varlevelsup);
+	COMPARE_SCALAR_FIELD(varnoold);
+	COMPARE_SCALAR_FIELD(varoattno);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalGroupingSet(const GroupingSet *a, const GroupingSet *b)
+{
+	COMPARE_SCALAR_FIELD(kind);
+	COMPARE_NODE_FIELD(content);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
 _equalConst(const Const *a, const Const *b)
 {
 	COMPARE_SCALAR_FIELD(consttype);
@@ -864,6 +910,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_NODE_FIELD(withCheckOptions);
 	COMPARE_NODE_FIELD(returningList);
 	COMPARE_NODE_FIELD(groupClause);
+	COMPARE_NODE_FIELD(groupingSets);
 	COMPARE_NODE_FIELD(havingQual);
 	COMPARE_NODE_FIELD(windowClause);
 	COMPARE_NODE_FIELD(distinctClause);
@@ -2556,6 +2603,15 @@ equal(const void *a, const void *b)
 		case T_Var:
 			retval = _equalVar(a, b);
 			break;
+		case T_GroupedVar:
+			retval = _equalGroupedVar(a, b);
+			break;
+		case T_Grouping:
+			retval = _equalGrouping(a, b);
+			break;
+		case T_GroupingSet:
+			retval = _equalGroupingSet(a, b);
+			break;
 		case T_Const:
 			retval = _equalConst(a, b);
 			break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 5c09d2f..f878d1f 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -823,6 +823,32 @@ list_intersection(const List *list1, const List *list2)
 }
 
 /*
+ * As list_intersection but operates on lists of integers.
+ */
+List *
+list_intersection_int(const List *list1, const List *list2)
+{
+	List	   *result;
+	const ListCell *cell;
+
+	if (list1 == NIL || list2 == NIL)
+		return NIL;
+
+	Assert(IsIntegerList(list1));
+	Assert(IsIntegerList(list2));
+
+	result = NIL;
+	foreach(cell, list1)
+	{
+		if (list_member_int(list2, lfirst_int(cell)))
+			result = lappend_int(result, lfirst_int(cell));
+	}
+
+	check_list_invariants(result);
+	return result;
+}
+
+/*
  * Return a list that contains all the cells in list1 that are not in
  * list2. The returned list is freshly allocated via palloc(), but the
  * cells themselves point to the same objects as the cells of the
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index da59c58..e930cef 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -554,3 +554,18 @@ makeFuncCall(List *name, List *args, int location)
 	n->location = location;
 	return n;
 }
+
+/*
+ * makeGroupingSet
+ *
+ */
+GroupingSet *
+makeGroupingSet(GroupingSetKind kind, List *content, int location)
+{
+	GroupingSet	   *n = makeNode(GroupingSet);
+
+	n->kind = kind;
+	n->content = content;
+	n->location = location;
+	return n;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 41e973b..6a63d1b 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -45,6 +45,12 @@ exprType(const Node *expr)
 		case T_Var:
 			type = ((const Var *) expr)->vartype;
 			break;
+		case T_Grouping:
+			type = INT4OID;
+			break;
+		case T_GroupedVar:
+			type = ((const GroupedVar *) expr)->vartype;
+			break;
 		case T_Const:
 			type = ((const Const *) expr)->consttype;
 			break;
@@ -261,6 +267,10 @@ exprTypmod(const Node *expr)
 	{
 		case T_Var:
 			return ((const Var *) expr)->vartypmod;
+		case T_Grouping:
+			return -1;
+		case T_GroupedVar:
+			return ((const GroupedVar *) expr)->vartypmod;
 		case T_Const:
 			return ((const Const *) expr)->consttypmod;
 		case T_Param:
@@ -734,6 +744,12 @@ exprCollation(const Node *expr)
 		case T_Var:
 			coll = ((const Var *) expr)->varcollid;
 			break;
+		case T_Grouping:
+			coll = InvalidOid;
+			break;
+		case T_GroupedVar:
+			coll = ((const GroupedVar *) expr)->varcollid;
+			break;
 		case T_Const:
 			coll = ((const Const *) expr)->constcollid;
 			break;
@@ -967,6 +983,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_Var:
 			((Var *) expr)->varcollid = collation;
 			break;
+		case T_GroupedVar:
+			((GroupedVar *) expr)->varcollid = collation;
+			break;
 		case T_Const:
 			((Const *) expr)->constcollid = collation;
 			break;
@@ -1003,6 +1022,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_BoolExpr:
 			Assert(!OidIsValid(collation));		/* result is always boolean */
 			break;
+		case T_Grouping:
+			Assert(!OidIsValid(collation));
+			break;
 		case T_SubLink:
 #ifdef USE_ASSERT_CHECKING
 			{
@@ -1182,6 +1204,15 @@ exprLocation(const Node *expr)
 		case T_Var:
 			loc = ((const Var *) expr)->location;
 			break;
+		case T_Grouping:
+			loc = ((const Grouping *) expr)->location;
+			break;
+		case T_GroupedVar:
+			loc = ((const GroupedVar *) expr)->location;
+			break;
+		case T_GroupingSet:
+			loc = ((const GroupingSet *) expr)->location;
+			break;
 		case T_Const:
 			loc = ((const Const *) expr)->location;
 			break;
@@ -1622,6 +1653,7 @@ expression_tree_walker(Node *node,
 	switch (nodeTag(node))
 	{
 		case T_Var:
+		case T_GroupedVar:
 		case T_Const:
 		case T_Param:
 		case T_CoerceToDomainValue:
@@ -1655,6 +1687,15 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_Grouping:
+			{
+				Grouping   *grouping = (Grouping *) node;
+
+				if (expression_tree_walker((Node *) grouping->args,
+										   walker, context))
+					return true;
+			}
+			break;
 		case T_WindowFunc:
 			{
 				WindowFunc *expr = (WindowFunc *) node;
@@ -2144,6 +2185,15 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_GroupedVar:
+			{
+				GroupedVar         *groupedvar = (GroupedVar *) node;
+				GroupedVar		   *newnode;
+
+				FLATCOPY(newnode, groupedvar, GroupedVar);
+				return (Node *) newnode;
+			}
+			break;
 		case T_Const:
 			{
 				Const	   *oldnode = (Const *) node;
@@ -2162,6 +2212,17 @@ expression_tree_mutator(Node *node,
 		case T_RangeTblRef:
 		case T_SortGroupClause:
 			return (Node *) copyObject(node);
+		case T_Grouping:
+			{
+				Grouping	   *grouping = (Grouping *) node;
+				Grouping	   *newnode;
+
+				FLATCOPY(newnode, grouping, Grouping);
+				MUTATE(newnode->args, grouping->args, List *);
+				/* assume no need to copy or mutate the refs list */
+				return (Node *) newnode;
+			}
+			break;
 		case T_WithCheckOption:
 			{
 				WithCheckOption *wco = (WithCheckOption *) node;
@@ -3209,6 +3270,8 @@ raw_expression_tree_walker(Node *node,
 			return walker(((WithClause *) node)->ctes, context);
 		case T_CommonTableExpr:
 			return walker(((CommonTableExpr *) node)->ctequery, context);
+		case T_GroupingSet:
+			return walker(((GroupingSet *) node)->content, context);
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e686a6c..64a888e 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -643,6 +643,8 @@ _outAgg(StringInfo str, const Agg *node)
 		appendStringInfo(str, " %u", node->grpOperators[i]);
 
 	WRITE_LONG_FIELD(numGroups);
+
+	WRITE_NODE_FIELD(groupingSets);
 }
 
 static void
@@ -912,6 +914,43 @@ _outVar(StringInfo str, const Var *node)
 }
 
 static void
+_outGrouping(StringInfo str, const Grouping *node)
+{
+	WRITE_NODE_TYPE("GROUPING");
+
+	WRITE_NODE_FIELD(args);
+	WRITE_NODE_FIELD(refs);
+	WRITE_LOCATION_FIELD(location);
+	WRITE_INT_FIELD(agglevelsup);
+}
+
+static void
+_outGroupedVar(StringInfo str, const GroupedVar *node)
+{
+	WRITE_NODE_TYPE("GROUPEDVAR");
+
+	WRITE_UINT_FIELD(varno);
+	WRITE_INT_FIELD(varattno);
+	WRITE_OID_FIELD(vartype);
+	WRITE_INT_FIELD(vartypmod);
+	WRITE_OID_FIELD(varcollid);
+	WRITE_UINT_FIELD(varlevelsup);
+	WRITE_UINT_FIELD(varnoold);
+	WRITE_INT_FIELD(varoattno);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outGroupingSet(StringInfo str, const GroupingSet *node)
+{
+	WRITE_NODE_TYPE("GROUPINGSET");
+
+	WRITE_ENUM_FIELD(kind, GroupingSetKind);
+	WRITE_NODE_FIELD(content);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
 _outConst(StringInfo str, const Const *node)
 {
 	WRITE_NODE_TYPE("CONST");
@@ -2270,6 +2309,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_NODE_FIELD(withCheckOptions);
 	WRITE_NODE_FIELD(returningList);
 	WRITE_NODE_FIELD(groupClause);
+	WRITE_NODE_FIELD(groupingSets);
 	WRITE_NODE_FIELD(havingQual);
 	WRITE_NODE_FIELD(windowClause);
 	WRITE_NODE_FIELD(distinctClause);
@@ -2914,6 +2954,15 @@ _outNode(StringInfo str, const void *obj)
 			case T_Var:
 				_outVar(str, obj);
 				break;
+			case T_GroupedVar:
+				_outGroupedVar(str, obj);
+				break;
+			case T_Grouping:
+				_outGrouping(str, obj);
+				break;
+			case T_GroupingSet:
+				_outGroupingSet(str, obj);
+				break;
 			case T_Const:
 				_outConst(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 69d9989..3a55154 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -215,6 +215,7 @@ _readQuery(void)
 	READ_NODE_FIELD(withCheckOptions);
 	READ_NODE_FIELD(returningList);
 	READ_NODE_FIELD(groupClause);
+	READ_NODE_FIELD(groupingSets);
 	READ_NODE_FIELD(havingQual);
 	READ_NODE_FIELD(windowClause);
 	READ_NODE_FIELD(distinctClause);
@@ -439,6 +440,52 @@ _readVar(void)
 	READ_DONE();
 }
 
+static Grouping *
+_readGrouping(void)
+{
+	READ_LOCALS(Grouping);
+
+	READ_NODE_FIELD(args);
+	READ_NODE_FIELD(refs);
+	READ_LOCATION_FIELD(location);
+	READ_INT_FIELD(agglevelsup);
+
+	READ_DONE();
+}
+
+/*
+ * _readGroupedVar
+ */
+static GroupedVar *
+_readGroupedVar(void)
+{
+	READ_LOCALS(GroupedVar);
+
+	READ_UINT_FIELD(varno);
+	READ_INT_FIELD(varattno);
+	READ_OID_FIELD(vartype);
+	READ_INT_FIELD(vartypmod);
+	READ_OID_FIELD(varcollid);
+	READ_UINT_FIELD(varlevelsup);
+	READ_UINT_FIELD(varnoold);
+	READ_INT_FIELD(varoattno);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
+static GroupingSet *
+_readGroupingSet(void)
+{
+	READ_LOCALS(GroupingSet);
+
+	READ_ENUM_FIELD(kind, GroupingSetKind);
+	READ_NODE_FIELD(content);
+	READ_LOCATION_FIELD(location);
+
+	READ_DONE();
+}
+
 /*
  * _readConst
  */
@@ -1320,6 +1367,12 @@ parseNodeString(void)
 		return_value = _readIntoClause();
 	else if (MATCH("VAR", 3))
 		return_value = _readVar();
+	else if (MATCH("GROUPEDVAR", 10))
+		return_value = _readGroupedVar();
+	else if (MATCH("GROUPING", 8))
+		return_value = _readGrouping();
+	else if (MATCH("GROUPINGSET", 11))
+		return_value = _readGroupingSet();
 	else if (MATCH("CONST", 5))
 		return_value = _readConst();
 	else if (MATCH("PARAM", 5))
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index c81efe9..a16df6f 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -1231,6 +1231,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	 */
 	if (parse->hasAggs ||
 		parse->groupClause ||
+		parse->groupingSets ||
 		parse->havingQual ||
 		parse->distinctClause ||
 		parse->sortClause ||
@@ -2104,7 +2105,7 @@ subquery_push_qual(Query *subquery, RangeTblEntry *rte, Index rti, Node *qual)
 		 * subquery uses grouping or aggregation, put it in HAVING (since the
 		 * qual really refers to the group-result rows).
 		 */
-		if (subquery->hasAggs || subquery->groupClause || subquery->havingQual)
+		if (subquery->hasAggs || subquery->groupClause || subquery->groupingSets || subquery->havingQual)
 			subquery->havingQual = make_and_qual(subquery->havingQual, qual);
 		else
 			subquery->jointree->quals =
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 773f8a4..e8b6671 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -580,6 +580,7 @@ query_supports_distinctness(Query *query)
 {
 	if (query->distinctClause != NIL ||
 		query->groupClause != NIL ||
+		query->groupingSets != NIL ||
 		query->hasAggs ||
 		query->havingQual ||
 		query->setOperations)
@@ -648,10 +649,10 @@ query_is_distinct_for(Query *query, List *colnos, List *opids)
 	}
 
 	/*
-	 * Similarly, GROUP BY guarantees uniqueness if all the grouped columns
-	 * appear in colnos and operator semantics match.
+	 * Similarly, GROUP BY without GROUPING SETS guarantees uniqueness if all
+	 * the grouped columns appear in colnos and operator semantics match.
 	 */
-	if (query->groupClause)
+	if (query->groupClause && !query->groupingSets)
 	{
 		foreach(l, query->groupClause)
 		{
@@ -667,6 +668,27 @@ query_is_distinct_for(Query *query, List *colnos, List *opids)
 		if (l == NULL)			/* had matches for all? */
 			return true;
 	}
+	else if (query->groupingSets)
+	{
+		/*
+		 * If we have grouping sets with expressions, we probably
+		 * don't have uniqueness and analysis would be hard. Punt.
+		 */
+		if (query->groupClause)
+			return false;
+
+		/*
+		 * If we have no groupClause (therefore no grouping expressions),
+		 * we might have one or many empty grouping sets. If there's just
+		 * one, then we're returning only one row and are certainly unique.
+		 * But otherwise, we know we're certainly not unique.
+		 */
+		if (list_length(query->groupingSets) == 1
+			&& ((GroupingSet *)linitial(query->groupingSets))->kind == GROUPING_SET_EMPTY)
+			return true;
+		else
+			return false;
+	}
 	else
 	{
 		/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 4b641a2..1a47f0f 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -1015,6 +1015,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
 								 numGroupCols,
 								 groupColIdx,
 								 groupOperators,
+								 NIL,
 								 numGroups,
 								 subplan);
 	}
@@ -4265,6 +4266,7 @@ Agg *
 make_agg(PlannerInfo *root, List *tlist, List *qual,
 		 AggStrategy aggstrategy, const AggClauseCosts *aggcosts,
 		 int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
+		 List *groupingSets,
 		 long numGroups,
 		 Plan *lefttree)
 {
@@ -4294,10 +4296,12 @@ make_agg(PlannerInfo *root, List *tlist, List *qual,
 	 * group otherwise.
 	 */
 	if (aggstrategy == AGG_PLAIN)
-		plan->plan_rows = 1;
+		plan->plan_rows = groupingSets ? list_length(groupingSets) : 1;
 	else
 		plan->plan_rows = numGroups;
 
+	node->groupingSets = groupingSets;
+
 	/*
 	 * We also need to account for the cost of evaluation of the qual (ie, the
 	 * HAVING clause) and the tlist.  Note that cost_qual_eval doesn't charge
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index e1480cd..9b4722d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -22,6 +22,7 @@
 #include "executor/nodeAgg.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #ifdef OPTIMIZER_DEBUG
 #include "nodes/print.h"
 #endif
@@ -37,6 +38,7 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parsetree.h"
+#include "parser/parse_agg.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/rel.h"
 #include "utils/selfuncs.h"
@@ -77,7 +79,10 @@ static double preprocess_limit(PlannerInfo *root,
 				 double tuple_fraction,
 				 int64 *offset_est, int64 *count_est);
 static bool limit_needed(Query *parse);
-static void preprocess_groupclause(PlannerInfo *root);
+static void preprocess_groupclause(PlannerInfo *root, List *force);
+static List *extract_rollup_sets(List *groupingSets, List *sortclause, List **remainder);
+static void fixup_grouping_exprs(Node *clause, int *refmap);
+static bool fixup_grouping_exprs_walker(Node *clause, int *refmap);
 static void standard_qp_callback(PlannerInfo *root, void *extra);
 static bool choose_hashed_grouping(PlannerInfo *root,
 					   double tuple_fraction, double limit_tuples,
@@ -531,7 +536,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 
 		if (contain_agg_clause(havingclause) ||
 			contain_volatile_functions(havingclause) ||
-			contain_subplans(havingclause))
+			contain_subplans(havingclause) ||
+			parse->groupingSets)
 		{
 			/* keep it in HAVING */
 			newHaving = lappend(newHaving, havingclause);
@@ -1187,15 +1193,81 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 		bool		use_hashed_grouping = false;
 		WindowFuncLists *wflists = NULL;
 		List	   *activeWindows = NIL;
+		int		   *refmap = NULL;
 
 		MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
 
 		/* A recursive query should always have setOperations */
 		Assert(!root->hasRecursion);
 
-		/* Preprocess GROUP BY clause, if any */
-		if (parse->groupClause)
-			preprocess_groupclause(root);
+		/* Preprocess Grouping set, if any */
+		if (parse->groupingSets)
+			parse->groupingSets = expand_grouping_sets(parse->groupingSets);
+
+		elog(DEBUG1, "grouping sets 1: %s", nodeToString(parse->groupingSets));
+
+		if (parse->groupingSets)
+		{
+			ListCell   *lc;
+			ListCell   *lc2;
+			int			maxref = 0;
+			int			ref = 0;
+			List	   *remaining_sets = NIL;
+			List	   *usable_sets = extract_rollup_sets(parse->groupingSets,
+														  parse->sortClause,
+														  &remaining_sets);
+
+			/*
+			 * TODO - if the grouping set list can't be handled as one rollup...
+			 */
+
+			if (remaining_sets != NIL)
+				elog(ERROR, "not implemented yet");
+
+			parse->groupingSets = usable_sets;
+
+			if (parse->groupClause)
+				preprocess_groupclause(root, linitial(parse->groupingSets));
+
+			/*
+			 * Now that we've pinned down an order for the groupClause for this
+			 * list of grouping sets, remap the entries in the grouping sets
+			 * from sortgrouprefs to plain indices into the groupClause.
+			 */
+
+			foreach(lc, parse->groupClause)
+			{
+				SortGroupClause *gc = lfirst(lc);
+				if (gc->tleSortGroupRef > maxref)
+					maxref = gc->tleSortGroupRef;
+			}
+
+			refmap = palloc0(sizeof(int) * (maxref + 1));
+
+			foreach(lc, parse->groupClause)
+			{
+				SortGroupClause *gc = lfirst(lc);
+				refmap[gc->tleSortGroupRef] = ++ref;
+			}
+
+			foreach(lc, usable_sets)
+			{
+				foreach(lc2, (List *) lfirst(lc))
+				{
+					Assert(refmap[lfirst_int(lc2)] > 0);
+					lfirst_int(lc2) = refmap[lfirst_int(lc2)] - 1;
+				}
+			}
+
+			elog(DEBUG1, "grouping sets 2: %s", nodeToString(parse->groupingSets));
+		}
+		else
+		{
+			/* Preprocess GROUP BY clause, if any */
+			if (parse->groupClause)
+				preprocess_groupclause(root, NIL);
+		}
+
 		numGroupCols = list_length(parse->groupClause);
 
 		/* Preprocess targetlist */
@@ -1241,6 +1313,13 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 		if (parse->hasAggs)
 		{
 			/*
+			 * Fix up any GROUPING nodes to refer to indexes in the final
+			 * groupClause list.
+			 */
+			fixup_grouping_exprs((Node *) tlist, refmap);
+			fixup_grouping_exprs(parse->havingQual, refmap);
+
+			/*
 			 * Collect statistics about aggregates for estimating costs. Note:
 			 * we do not attempt to detect duplicate aggregates here; a
 			 * somewhat-overestimated cost is okay for our present purposes.
@@ -1257,6 +1336,9 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 			preprocess_minmax_aggregates(root, tlist);
 		}
 
+		if (refmap)
+			pfree(refmap);
+
 		/* Make tuple_fraction accessible to lower-level routines */
 		root->tuple_fraction = tuple_fraction;
 
@@ -1267,6 +1349,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 		 * grouping/aggregation operations.
 		 */
 		if (parse->groupClause ||
+			parse->groupingSets ||
 			parse->distinctClause ||
 			parse->hasAggs ||
 			parse->hasWindowFuncs ||
@@ -1312,7 +1395,23 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 
 			groupExprs = get_sortgrouplist_exprs(parse->groupClause,
 												 parse->targetList);
-			dNumGroups = estimate_num_groups(root, groupExprs, path_rows);
+			if (parse->groupingSets)
+			{
+				ListCell   *lc;
+
+				dNumGroups = 0;
+
+				foreach(lc, parse->groupingSets)
+				{
+					dNumGroups += estimate_num_groups(root,
+													  groupExprs,
+													  path_rows,
+													  (List **) &(lfirst(lc)));
+				}
+			}
+			else
+				dNumGroups = estimate_num_groups(root, groupExprs, path_rows,
+												 NULL);
 
 			/*
 			 * In GROUP BY mode, an absolute LIMIT is relative to the number
@@ -1338,7 +1437,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 									   root->group_pathkeys))
 				tuple_fraction = 0.0;
 		}
-		else if (parse->hasAggs || root->hasHavingQual)
+		else if (parse->hasAggs || root->hasHavingQual || parse->groupingSets)
 		{
 			/*
 			 * Ungrouped aggregate will certainly want to read all the tuples,
@@ -1360,7 +1459,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 
 			distinctExprs = get_sortgrouplist_exprs(parse->distinctClause,
 													parse->targetList);
-			dNumGroups = estimate_num_groups(root, distinctExprs, path_rows);
+			dNumGroups = estimate_num_groups(root, distinctExprs, path_rows, NULL);
 
 			/*
 			 * Adjust tuple_fraction the same way as for GROUP BY, too.
@@ -1443,13 +1542,24 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 		{
 			/*
 			 * If grouping, decide whether to use sorted or hashed grouping.
+			 * If grouping sets are present, we can currently do only sorted
+			 * grouping
 			 */
-			use_hashed_grouping =
-				choose_hashed_grouping(root,
-									   tuple_fraction, limit_tuples,
-									   path_rows, path_width,
-									   cheapest_path, sorted_path,
-									   dNumGroups, &agg_costs);
+
+			if (parse->groupingSets)
+			{
+				use_hashed_grouping = false;
+			}
+			else
+			{
+				use_hashed_grouping =
+					choose_hashed_grouping(root,
+										   tuple_fraction, limit_tuples,
+										   path_rows, path_width,
+										   cheapest_path, sorted_path,
+										   dNumGroups, &agg_costs);
+			}
+
 			/* Also convert # groups to long int --- but 'ware overflow! */
 			numGroups = (long) Min(dNumGroups, (double) LONG_MAX);
 		}
@@ -1591,12 +1701,13 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 												numGroupCols,
 												groupColIdx,
 									extract_grouping_ops(parse->groupClause),
+												NIL,
 												numGroups,
 												result_plan);
 				/* Hashed aggregation produces randomly-ordered results */
 				current_pathkeys = NIL;
 			}
-			else if (parse->hasAggs)
+			else if (parse->hasAggs || parse->groupingSets)
 			{
 				/* Plain aggregate plan --- sort if needed */
 				AggStrategy aggstrategy;
@@ -1622,7 +1733,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 				else
 				{
 					aggstrategy = AGG_PLAIN;
-					/* Result will be only one row anyway; no sort order */
+					/* Result will have no sort order */
 					current_pathkeys = NIL;
 				}
 
@@ -1634,6 +1745,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 												numGroupCols,
 												groupColIdx,
 									extract_grouping_ops(parse->groupClause),
+												parse->groupingSets,
 												numGroups,
 												result_plan);
 			}
@@ -1849,7 +1961,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 		 * result was already mostly unique).  If not, use the number of
 		 * distinct-groups calculated previously.
 		 */
-		if (parse->groupClause || root->hasHavingQual || parse->hasAggs)
+		if (parse->groupClause || parse->groupingSets || root->hasHavingQual || parse->hasAggs)
 			dNumDistinctRows = result_plan->plan_rows;
 		else
 			dNumDistinctRows = dNumGroups;
@@ -1890,6 +2002,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 								 extract_grouping_cols(parse->distinctClause,
 													result_plan->targetlist),
 								 extract_grouping_ops(parse->distinctClause),
+											NIL,
 											numDistinctRows,
 											result_plan);
 			/* Hashed aggregation produces randomly-ordered results */
@@ -2508,6 +2621,7 @@ limit_needed(Query *parse)
 }
 
 
+
 /*
  * preprocess_groupclause - do preparatory work on GROUP BY clause
  *
@@ -2525,14 +2639,30 @@ limit_needed(Query *parse)
  * the parser already enforced that that matches ORDER BY.
  */
 static void
-preprocess_groupclause(PlannerInfo *root)
+preprocess_groupclause(PlannerInfo *root, List *force)
 {
 	Query	   *parse = root->parse;
-	List	   *new_groupclause;
+	List	   *new_groupclause = NIL;
 	bool		partial_match;
 	ListCell   *sl;
 	ListCell   *gl;
 
+	/* For grouping sets, we may need to force the ordering */
+	if (force)
+	{
+		foreach(sl, force)
+		{
+			Index ref = lfirst_int(sl);
+			SortGroupClause *cl = get_sortgroupref_clause(ref, parse->groupClause);
+
+			new_groupclause = lappend(new_groupclause, cl);
+		}
+
+		Assert(list_length(parse->groupClause) == list_length(new_groupclause));
+		parse->groupClause = new_groupclause;
+		return;
+	}
+
 	/* If no ORDER BY, nothing useful to do here */
 	if (parse->sortClause == NIL)
 		return;
@@ -2543,7 +2673,6 @@ preprocess_groupclause(PlannerInfo *root)
 	 *
 	 * This code assumes that the sortClause contains no duplicate items.
 	 */
-	new_groupclause = NIL;
 	foreach(sl, parse->sortClause)
 	{
 		SortGroupClause *sc = (SortGroupClause *) lfirst(sl);
@@ -2595,6 +2724,145 @@ preprocess_groupclause(PlannerInfo *root)
 	parse->groupClause = new_groupclause;
 }
 
+
+/*
+ * Extract a list of grouping sets that can be implemented using a single
+ * rollup-type aggregate pass. The order of elements in each returned set is
+ * modified to ensure proper prefix relationships; the sets are returned in
+ * decreasing order of size. (The input must also be in descending order of
+ * size.)
+ *
+ * If we're passed in a sortclause, we follow its order of columns to the
+ * extent possible, to minimize the chance that we add unnecessary sorts.
+ *
+ * Sets that can't be accomodated within a rollup that includes the first
+ * (and therefore largest) grouping set in the input are added to the
+ * remainder list.
+ */
+
+static List *
+extract_rollup_sets(List *groupingSets, List *sortclause, List **remainder)
+{
+	ListCell   *lc;
+	ListCell   *lc2;
+	List	   *previous = linitial(groupingSets);
+	List	   *tmp_result = list_make1(previous);
+	List	   *result = NIL;
+
+	for_each_cell(lc, lnext(list_head(groupingSets)))
+	{
+		List   *candidate = lfirst(lc);
+		bool	ok = true;
+
+		foreach(lc2, candidate)
+		{
+			int ref = lfirst_int(lc2);
+			if (!list_member_int(previous, ref))
+			{
+				ok = false;
+				break;
+			}
+		}
+
+		if (ok)
+		{
+			tmp_result = lcons(candidate, tmp_result);
+			previous = candidate;
+		}
+		else
+			*remainder = lappend(*remainder, candidate);
+	}
+
+	/*
+	 * reorder the list elements so that shorter sets are strict
+	 * prefixes of longer ones, and if we ever have a choice, try
+	 * and follow the sortclause if there is one. (We're trying
+	 * here to ensure that GROUPING SETS ((a,b),(b)) ORDER BY b,a
+	 * gets implemented in one pass.)
+	 */
+
+	previous = NIL;
+
+	foreach(lc, tmp_result)
+	{
+		List   *candidate = lfirst(lc);
+		List   *new_elems = list_difference_int(candidate, previous);
+
+		if (list_length(new_elems) > 0)
+		{
+			while (list_length(sortclause) > list_length(previous))
+			{
+				SortGroupClause *sc = list_nth(sortclause, list_length(previous));
+				int ref = sc->tleSortGroupRef;
+				if (list_member_int(new_elems, ref))
+				{
+					previous = lappend_int(previous, ref);
+					new_elems = list_delete_int(new_elems, ref);
+				}
+				else
+				{
+					sortclause = NIL;
+					break;
+				}
+			}
+
+			foreach(lc2, new_elems)
+			{
+				previous = lappend_int(previous, lfirst_int(lc2));
+			}
+		}
+
+		result = lcons(list_copy(previous), result);
+		list_free(new_elems);
+	}
+
+	list_free(previous);
+	list_free(tmp_result);
+
+	return result;
+}
+
+
+static void
+fixup_grouping_exprs(Node *clause, int *refmap)
+{
+	(void) fixup_grouping_exprs_walker(clause, refmap);
+}
+
+static bool
+fixup_grouping_exprs_walker(Node *node, int *refmap)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Grouping))
+	{
+		Grouping *g = (Grouping *) node;
+
+		/* If there are no grouping sets, we don't need this. */
+		if (!refmap)
+		{
+			g->refs = NIL;
+		}
+		else
+		{
+			ListCell *lc;
+
+			foreach(lc, g->refs)
+			{
+				Assert(refmap[lfirst_int(lc)] > 0);
+				lfirst_int(lc) = refmap[lfirst_int(lc)] - 1;
+			}
+		}
+
+		/* No need to recurse into args. */
+		return false;
+	}
+	Assert(!IsA(node, SubLink));
+	return expression_tree_walker(node, fixup_grouping_exprs_walker,
+								  (void *) refmap);
+}
+
+
 /*
  * Compute query_pathkeys and other pathkeys during plan generation
  */
@@ -3040,7 +3308,7 @@ make_subplanTargetList(PlannerInfo *root,
 	 * If we're not grouping or aggregating, there's nothing to do here;
 	 * query_planner should receive the unmodified target list.
 	 */
-	if (!parse->hasAggs && !parse->groupClause && !root->hasHavingQual &&
+	if (!parse->hasAggs && !parse->groupClause && !parse->groupingSets && !root->hasHavingQual &&
 		!parse->hasWindowFuncs)
 	{
 		*need_tlist_eval = true;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 4d717df..ddec675 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -68,6 +68,12 @@ typedef struct
 	int			rtoffset;
 } fix_upper_expr_context;
 
+typedef struct
+{
+	PlannerInfo *root;
+	Bitmapset   *groupedcols;
+} set_group_vars_context;
+
 /*
  * Check if a Const node is a regclass value.  We accept plain OID too,
  * since a regclass Const will get folded to that type if it's an argument
@@ -134,6 +140,8 @@ static List *set_returning_clause_references(PlannerInfo *root,
 static bool fix_opfuncids_walker(Node *node, void *context);
 static bool extract_query_dependencies_walker(Node *node,
 								  PlannerInfo *context);
+static void set_group_vars(PlannerInfo *root, Agg *agg);
+static Node *set_group_vars_mutator(Node *node, set_group_vars_context *context);
 
 
 /*****************************************************************************
@@ -647,6 +655,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 		case T_Agg:
+			set_upper_references(root, plan, rtoffset);
+			set_group_vars(root, (Agg *) plan);
+			break;
 		case T_Group:
 			set_upper_references(root, plan, rtoffset);
 			break;
@@ -1246,6 +1257,67 @@ fix_scan_expr_walker(Node *node, fix_scan_expr_context *context)
 								  (void *) context);
 }
 
+
+/*
+ * set_group_vars
+ *    Modify any Var references in the target list of a non-trivial
+ *    (i.e. contains grouping sets) Agg node to use GroupedVar instead,
+ *    which will conditionally replace them with nulls at runtime.
+ */
+static void
+set_group_vars(PlannerInfo *root, Agg *agg)
+{
+	set_group_vars_context context;
+	int i;
+	Bitmapset *cols = NULL;
+
+	if (!agg->groupingSets)
+		return;
+
+	context.root = root;
+
+	for (i = 0; i < agg->numCols; ++i)
+		cols = bms_add_member(cols, agg->grpColIdx[i]);
+
+	context.groupedcols = cols;
+
+	agg->plan.targetlist = (List *) set_group_vars_mutator((Node *) agg->plan.targetlist,
+														   &context);
+	agg->plan.qual = (List *) set_group_vars_mutator((Node *) agg->plan.qual,
+													 &context);
+}
+
+static Node *
+set_group_vars_mutator(Node *node, set_group_vars_context *context)
+{
+	if (node == NULL)
+		return NULL;
+	if (IsA(node, Var))
+	{
+		Var *var = (Var *) node;
+
+		if (var->varno == OUTER_VAR
+			&& bms_is_member(var->varattno, context->groupedcols))
+		{
+			var = copyVar(var);
+			var->xpr.type = T_GroupedVar;
+		}
+
+		return (Node *) var;
+	}
+	else if (IsA(node, Aggref) || IsA(node,Grouping))
+	{
+		/*
+		 * don't recurse into Aggrefs, since they see the values prior
+		 * to grouping.
+		 */
+		return node;
+	}
+	return expression_tree_mutator(node, set_group_vars_mutator,
+								   (void *) context);
+}
+
+
 /*
  * set_join_references
  *	  Modify the target list and quals of a join node to reference its
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 3e7dc85..e0a2ca7 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -336,6 +336,48 @@ replace_outer_agg(PlannerInfo *root, Aggref *agg)
 }
 
 /*
+ * Generate a Param node to replace the given Grouping expression
+ * which is expected to have agglevelsup > 0 (ie, it is not local).
+ */
+static Param *
+replace_outer_grouping(PlannerInfo *root, Grouping *grp)
+{
+	Param	   *retval;
+	PlannerParamItem *pitem;
+	Index		levelsup;
+
+	Assert(grp->agglevelsup > 0 && grp->agglevelsup < root->query_level);
+
+	/* Find the query level the Grouping belongs to */
+	for (levelsup = grp->agglevelsup; levelsup > 0; levelsup--)
+		root = root->parent_root;
+
+	/*
+	 * It does not seem worthwhile to try to match duplicate outer aggs. Just
+	 * make a new slot every time.
+	 */
+	grp = (Grouping *) copyObject(grp);
+	IncrementVarSublevelsUp((Node *) grp, -((int) grp->agglevelsup), 0);
+	Assert(grp->agglevelsup == 0);
+
+	pitem = makeNode(PlannerParamItem);
+	pitem->item = (Node *) grp;
+	pitem->paramId = root->glob->nParamExec++;
+
+	root->plan_params = lappend(root->plan_params, pitem);
+
+	retval = makeNode(Param);
+	retval->paramkind = PARAM_EXEC;
+	retval->paramid = pitem->paramId;
+	retval->paramtype = exprType((Node *) grp);
+	retval->paramtypmod = -1;
+	retval->paramcollid = InvalidOid;
+	retval->location = grp->location;
+
+	return retval;
+}
+
+/*
  * Generate a new Param node that will not conflict with any other.
  *
  * This is used to create Params representing subplan outputs.
@@ -1490,13 +1532,14 @@ simplify_EXISTS_query(Query *query)
 {
 	/*
 	 * We don't try to simplify at all if the query uses set operations,
-	 * aggregates, modifying CTEs, HAVING, LIMIT/OFFSET, or FOR UPDATE/SHARE;
-	 * none of these seem likely in normal usage and their possible effects
-	 * are complex.
+	 * aggregates, grouping sets, modifying CTEs, HAVING, LIMIT/OFFSET, or FOR
+	 * UPDATE/SHARE; none of these seem likely in normal usage and their
+	 * possible effects are complex.
 	 */
 	if (query->commandType != CMD_SELECT ||
 		query->setOperations ||
 		query->hasAggs ||
+		query->groupingSets ||
 		query->hasWindowFuncs ||
 		query->hasModifyingCTE ||
 		query->havingQual ||
@@ -1813,6 +1856,11 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root)
 		if (((Aggref *) node)->agglevelsup > 0)
 			return (Node *) replace_outer_agg(root, (Aggref *) node);
 	}
+	if (IsA(node, Grouping))
+	{
+		if (((Grouping *) node)->agglevelsup > 0)
+			return (Node *) replace_outer_grouping(root, (Grouping *) node);
+	}
 	return expression_tree_mutator(node,
 								   replace_correlation_vars_mutator,
 								   (void *) root);
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 9cb1378..cb8aeb6 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1297,6 +1297,7 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte,
 	if (subquery->hasAggs ||
 		subquery->hasWindowFuncs ||
 		subquery->groupClause ||
+		subquery->groupingSets ||
 		subquery->havingQual ||
 		subquery->sortClause ||
 		subquery->distinctClause ||
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 0410fdd..3c71d7f 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -268,13 +268,15 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		 */
 		if (pNumGroups)
 		{
-			if (subquery->groupClause || subquery->distinctClause ||
+			if (subquery->groupClause || subquery->groupingSets ||
+				subquery->distinctClause ||
 				subroot->hasHavingQual || subquery->hasAggs)
 				*pNumGroups = subplan->plan_rows;
 			else
 				*pNumGroups = estimate_num_groups(subroot,
 								get_tlist_exprs(subquery->targetList, false),
-												  subplan->plan_rows);
+												  subplan->plan_rows,
+												  NULL);
 		}
 
 		/*
@@ -771,6 +773,7 @@ make_union_unique(SetOperationStmt *op, Plan *plan,
 								 extract_grouping_cols(groupList,
 													   plan->targetlist),
 								 extract_grouping_ops(groupList),
+								 NIL,
 								 numGroups,
 								 plan);
 		/* Hashed aggregation produces randomly-ordered results */
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 19b5cf7..1152195 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4294,6 +4294,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
 		querytree->jointree->fromlist ||
 		querytree->jointree->quals ||
 		querytree->groupClause ||
+		querytree->groupingSets ||
 		querytree->havingQual ||
 		querytree->windowClause ||
 		querytree->distinctClause ||
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 319e8b2..a7bbacf 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1338,7 +1338,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
 	}
 
 	/* Estimate number of output rows */
-	pathnode->path.rows = estimate_num_groups(root, uniq_exprs, rel->rows);
+	pathnode->path.rows = estimate_num_groups(root, uniq_exprs, rel->rows, NULL);
 	numCols = list_length(uniq_exprs);
 
 	if (all_btree)
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index b5c6a44..efed20a 100644
--- a/src/backend/optimizer/util/tlist.c
+++ b/src/backend/optimizer/util/tlist.c
@@ -395,6 +395,28 @@ get_sortgrouplist_exprs(List *sgClauses, List *targetList)
  *****************************************************************************/
 
 /*
+ * get_sortgroupref_clause
+ *		Find the SortGroupClause matching the given SortGroupRef index,
+ *		and return it.
+ */
+SortGroupClause *
+get_sortgroupref_clause(Index sortref, List *clauses)
+{
+	ListCell   *l;
+
+	foreach(l, clauses)
+	{
+		SortGroupClause *cl = (SortGroupClause *) lfirst(l);
+
+		if (cl->tleSortGroupRef == sortref)
+			return cl;
+	}
+
+	elog(ERROR, "ORDER/GROUP BY expression not found in list");
+	return NULL;				/* keep compiler quiet */
+}
+
+/*
  * extract_grouping_ops - make an array of the equality operator OIDs
  *		for a SortGroupClause list
  */
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index d4f46b8..c8a7b43 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -564,6 +564,28 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
 				break;
 		}
 	}
+	else if (IsA(node, Grouping))
+	{
+		if (((Grouping *) node)->agglevelsup != 0)
+			elog(ERROR, "Upper-level GROUPING found where not expected");
+		switch (context->aggbehavior)
+		{
+			case PVC_REJECT_AGGREGATES:
+				elog(ERROR, "GROUPING found where not expected");
+				break;
+			case PVC_INCLUDE_AGGREGATES:
+			case PVC_RECURSE_AGGREGATES:
+				/* We don't include the Grouping node in the result */
+				/*
+				 * we do NOT descend into the contained expression,
+				 * even if the caller asked for it, because we never
+				 * actually evaluate it - the result is driven entirely
+				 * off the associated GROUP BY clause, so we never need
+				 * to extract the actual Vars here.
+				 */
+				return false;
+		}
+	}
 	else if (IsA(node, PlaceHolderVar))
 	{
 		if (((PlaceHolderVar *) node)->phlevelsup != 0)
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index fb6c44c..96ef36c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -968,6 +968,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 
 	qry->groupClause = transformGroupClause(pstate,
 											stmt->groupClause,
+											&qry->groupingSets,
 											&qry->targetList,
 											qry->sortClause,
 											EXPR_KIND_GROUP_BY,
@@ -1014,7 +1015,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasAggs = pstate->p_hasAggs;
-	if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
+	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
 		parseCheckAggregates(pstate, qry);
 
 	foreach(l, stmt->lockingClause)
@@ -1474,7 +1475,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasAggs = pstate->p_hasAggs;
-	if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
+	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
 		parseCheckAggregates(pstate, qry);
 
 	foreach(l, lockingClause)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a113809..675f0a0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -361,6 +361,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				create_generic_options alter_generic_options
 				relation_expr_list dostmt_opt_list
 
+%type <list>	group_by_list grouping_set_list
+%type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
+%type <node>	grouping_sets_clause grouping_set
+
 %type <list>	opt_fdw_options fdw_options
 %type <defelt>	fdw_option
 
@@ -425,7 +429,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <list>	ExclusionConstraintList ExclusionConstraintElem
 %type <list>	func_arg_list
 %type <node>	func_arg_expr
-%type <list>	row type_list array_expr_list
+%type <list>	row explicit_row implicit_row type_list array_expr_list
 %type <node>	case_expr case_arg when_clause case_default
 %type <list>	when_clause_list
 %type <ival>	sub_type
@@ -547,7 +551,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CLUSTER COALESCE COLLATE COLLATION COLUMN COMMENT COMMENTS COMMIT
 	COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
 	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
-	CROSS CSV CURRENT_P
+	CROSS CSV CUBE CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
 	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
 
@@ -562,7 +566,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR
 	FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
 
-	GLOBAL GRANT GRANTED GREATEST GROUP_P
+	GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING
 
 	HANDLER HAVING HEADER_P HOLD HOUR_P
 
@@ -596,11 +600,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFRESH REINDEX
 	RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
-	RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
+	RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
 	ROW ROWS RULE
 
 	SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
-	SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
+	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE
 	SHOW SIMILAR SIMPLE SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START
 	STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
 	SYMMETRIC SYSID SYSTEM_P
@@ -9832,11 +9836,86 @@ first_or_next: FIRST_P								{ $$ = 0; }
 		;
 
 
+/*
+ * This syntax for group_clause tries to follow the spec quite closely.
+ * However, the spec allows only column references, not expressions,
+ * which introduces an ambiguity between implicit row constructors
+ * (a,b) and lists of column references.
+ *
+ * We handle this by using the a_expr production for what the spec calls
+ * <ordinary grouping set>, which in the spec represents either one column
+ * reference or a parenthesized list of column references. Then, we check the
+ * top node of the a_expr to see if it's an implicit RowExpr, and if so, just
+ * grab and use the list, discarding the node. (this is done in parse analysis,
+ * not here)
+ *
+ * (we abuse the row_format field of RowExpr to distinguish implicit and
+ * explicit row constructors; it's debatable if anyone sanely wants to use them
+ * in a group clause, but if they have a reason to, we make it possible.)
+ *
+ * Each item in the group_clause list is either an expression tree or a
+ * GroupingSet node of some type.
+ */
+
 group_clause:
-			GROUP_P BY expr_list					{ $$ = $3; }
+			GROUP_P BY group_by_list				{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+group_by_list:
+			group_by_item							{ $$ = list_make1($1); }
+			| group_by_list ',' group_by_item		{ $$ = lappend($1,$3); }
+		;
+
+group_by_item:
+			a_expr									{ $$ = $1; }
+			| empty_grouping_set					{ $$ = $1; }
+			| rollup_clause							{ $$ = $1; }
+			| cube_clause							{ $$ = $1; }
+			| grouping_sets_clause					{ $$ = $1; }
+		;
+
+empty_grouping_set:
+			'(' ')'
+				{
+					$$ = (Node *) makeGroupingSet(GROUPING_SET_EMPTY, NIL, @1);
+				}
+		;
+
+rollup_clause:
+			ROLLUP '(' expr_list ')'
+				{
+					$$ = (Node *) makeGroupingSet(GROUPING_SET_ROLLUP, $3, @1);
+				}
+		;
+
+cube_clause:
+			CUBE '(' expr_list ')'
+				{
+					$$ = (Node *) makeGroupingSet(GROUPING_SET_CUBE, $3, @1);
+				}
+		;
+
+grouping_sets_clause:
+			GROUPING SETS '(' grouping_set_list ')'
+				{
+					$$ = (Node *) makeGroupingSet(GROUPING_SET_SETS, $4, @1);
+				}
+		;
+
+grouping_set:
+			a_expr									{ $$ = $1; }
+			| empty_grouping_set					{ $$ = $1; }
+			| rollup_clause							{ $$ = $1; }
+			| cube_clause							{ $$ = $1; }
+			| grouping_sets_clause					{ $$ = $1; }
+		;
+
+grouping_set_list:
+			grouping_set 							{ $$ = list_make1($1); }
+			| grouping_set_list ',' grouping_set	{ $$ = lappend($1,$3); }
+		;
+
 having_clause:
 			HAVING a_expr							{ $$ = $2; }
 			| /*EMPTY*/								{ $$ = NULL; }
@@ -11415,15 +11494,33 @@ c_expr:		columnref								{ $$ = $1; }
 					n->location = @1;
 					$$ = (Node *)n;
 				}
-			| row
+			| explicit_row
 				{
 					RowExpr *r = makeNode(RowExpr);
 					r->args = $1;
 					r->row_typeid = InvalidOid;	/* not analyzed yet */
 					r->colnames = NIL;	/* to be filled in during analysis */
+					r->row_format = COERCE_EXPLICIT_CALL; /* abuse */
 					r->location = @1;
 					$$ = (Node *)r;
 				}
+			| implicit_row
+				{
+					RowExpr *r = makeNode(RowExpr);
+					r->args = $1;
+					r->row_typeid = InvalidOid;	/* not analyzed yet */
+					r->colnames = NIL;	/* to be filled in during analysis */
+					r->row_format = COERCE_IMPLICIT_CAST; /* abuse */
+					r->location = @1;
+					$$ = (Node *)r;
+				}
+			| GROUPING '(' expr_list ')'
+			  {
+				  Grouping *g = makeNode(Grouping);
+				  g->args = $3;
+				  g->location = @1;
+				  $$ = (Node *)g;
+			  }
 		;
 
 func_application: func_name '(' ')'
@@ -12173,6 +12270,13 @@ row:		ROW '(' expr_list ')'					{ $$ = $3; }
 			| '(' expr_list ',' a_expr ')'			{ $$ = lappend($2, $4); }
 		;
 
+explicit_row:	ROW '(' expr_list ')'				{ $$ = $3; }
+			| ROW '(' ')'							{ $$ = NIL; }
+		;
+
+implicit_row:	'(' expr_list ',' a_expr ')'		{ $$ = lappend($2, $4); }
+		;
+
 sub_type:	ANY										{ $$ = ANY_SUBLINK; }
 			| SOME									{ $$ = ANY_SUBLINK; }
 			| ALL									{ $$ = ALL_SUBLINK; }
@@ -13071,6 +13175,7 @@ unreserved_keyword:
 			| SERVER
 			| SESSION
 			| SET
+			| SETS
 			| SHARE
 			| SHOW
 			| SIMPLE
@@ -13147,6 +13252,7 @@ col_name_keyword:
 			| CHAR_P
 			| CHARACTER
 			| COALESCE
+			| CUBE
 			| DEC
 			| DECIMAL_P
 			| EXISTS
@@ -13168,6 +13274,7 @@ col_name_keyword:
 			| POSITION
 			| PRECISION
 			| REAL
+			| ROLLUP
 			| ROW
 			| SETOF
 			| SMALLINT
@@ -13269,6 +13376,7 @@ reserved_keyword:
 			| FROM
 			| GRANT
 			| GROUP_P
+			| GROUPING
 			| HAVING
 			| IN_P
 			| INITIALLY
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index c984b7d..15a06df 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -42,7 +42,9 @@ typedef struct
 {
 	ParseState *pstate;
 	Query	   *qry;
+	PlannerInfo *root;
 	List	   *groupClauses;
+	List	   *groupClauseVars;
 	bool		have_non_var_grouping;
 	List	  **func_grouped_rels;
 	int			sublevels_up;
@@ -56,11 +58,248 @@ static int check_agg_arguments(ParseState *pstate,
 static bool check_agg_arguments_walker(Node *node,
 						   check_agg_arguments_context *context);
 static void check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
-						List *groupClauses, bool have_non_var_grouping,
+						List *groupClauses, List *groupClauseVars,
+						bool have_non_var_grouping,
 						List **func_grouped_rels);
 static bool check_ungrouped_columns_walker(Node *node,
 							   check_ungrouped_columns_context *context);
+static void finalize_grouping_exprs(Node *node, ParseState *pstate, Query *qry,
+									List *groupClauses, PlannerInfo *root,
+									bool have_non_var_grouping);
+static bool finalize_grouping_exprs_walker(Node *node,
+							   check_ungrouped_columns_context *context);
+static void check_agglevels_and_constraints(ParseState *pstate,Node *expr);
+static List *expand_groupingset_node(GroupingSet *gs);
+
+
+static void check_agglevels_and_constraints(ParseState *pstate, Node *expr)
+{
+	List	   *directargs = NULL;
+	List	   *args = NULL;
+	Expr	   *filter = NULL;
+	int			min_varlevel;
+	int			location = -1;
+	const char *err;
+	bool		errkind;
+	bool		isAgg = false;
+
+	if (IsA(expr, Aggref))
+	{
+		Aggref *agg = (Aggref *) expr;
+
+		directargs = agg->aggdirectargs;
+		args = agg->args;
+		filter = agg->aggfilter;
+
+		location = agg->location;
+
+		isAgg = true;
+	}
+	else if (IsA(expr, Grouping))
+	{
+		Grouping *grp = (Grouping *) expr;
+
+		args = grp->args;
+
+		location = grp->location;
+	}
+
+	/*
+	 * Check the arguments to compute the aggregate's level and detect
+	 * improper nesting.
+	 */
+	min_varlevel = check_agg_arguments(pstate,
+									   directargs,
+									   args,
+									   filter);
+
+	if (IsA(expr, Aggref))
+		((Aggref *) expr)->agglevelsup = min_varlevel;
+	else if (IsA(expr, Grouping))
+		((Grouping *) expr)->agglevelsup = min_varlevel;
+
+	/* Mark the correct pstate level as having aggregates */
+	while (min_varlevel-- > 0)
+		pstate = pstate->parentParseState;
+	pstate->p_hasAggs = true;
+
+	/*
+	 * Check to see if the aggregate function is in an invalid place within
+	 * its aggregation query.
+	 *
+	 * For brevity we support two schemes for reporting an error here: set
+	 * "err" to a custom message, or set "errkind" true if the error context
+	 * is sufficiently identified by what ParseExprKindName will return, *and*
+	 * what it will return is just a SQL keyword.  (Otherwise, use a custom
+	 * message to avoid creating translation problems.)
+	 */
+	err = NULL;
+	errkind = false;
+	switch (pstate->p_expr_kind)
+	{
+		case EXPR_KIND_NONE:
+			Assert(false);		/* can't happen */
+			break;
+		case EXPR_KIND_OTHER:
+			/* Accept aggregate/grouping here; caller must throw error if wanted */
+			break;
+		case EXPR_KIND_JOIN_ON:
+		case EXPR_KIND_JOIN_USING:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in JOIN conditions");
+			else
+				err = _("Grouping is not allowed in JOIN conditions");
+
+			break;
+		case EXPR_KIND_FROM_SUBSELECT:
+			/* Should only be possible in a LATERAL subquery */
+			Assert(pstate->p_lateral_active);
+			/* Aggregate/grouping scope rules make it worth being explicit here */
+			if (isAgg)
+				err = _("aggregate functions are not allowed in FROM clause of their own query level");
+			else
+				err = _("Grouping is not allowed in FROM clause of its own query level");
+
+			break;
+		case EXPR_KIND_FROM_FUNCTION:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in functions in FROM");
+			else
+				err = _("Grouping is not allowed in functions in FROM");
+
+			break;
+		case EXPR_KIND_WHERE:
+			errkind = true;
+			break;
+		case EXPR_KIND_HAVING:
+			/* okay */
+			break;
+		case EXPR_KIND_FILTER:
+			errkind = true;
+			break;
+		case EXPR_KIND_WINDOW_PARTITION:
+			/* okay */
+			break;
+		case EXPR_KIND_WINDOW_ORDER:
+			/* okay */
+			break;
+		case EXPR_KIND_WINDOW_FRAME_RANGE:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in window RANGE");
+			else
+				err = _("Grouping is not allowed in window RANGE");
 
+			break;
+		case EXPR_KIND_WINDOW_FRAME_ROWS:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in window ROWS");
+			else
+				err = _("Grouping is not allowed in window ROWS");
+
+			break;
+		case EXPR_KIND_SELECT_TARGET:
+			/* okay */
+			break;
+		case EXPR_KIND_INSERT_TARGET:
+		case EXPR_KIND_UPDATE_SOURCE:
+		case EXPR_KIND_UPDATE_TARGET:
+			errkind = true;
+			break;
+		case EXPR_KIND_GROUP_BY:
+			errkind = true;
+			break;
+		case EXPR_KIND_ORDER_BY:
+			/* okay */
+			break;
+		case EXPR_KIND_DISTINCT_ON:
+			/* okay */
+			break;
+		case EXPR_KIND_LIMIT:
+		case EXPR_KIND_OFFSET:
+			errkind = true;
+			break;
+		case EXPR_KIND_RETURNING:
+			errkind = true;
+			break;
+		case EXPR_KIND_VALUES:
+			errkind = true;
+			break;
+		case EXPR_KIND_CHECK_CONSTRAINT:
+		case EXPR_KIND_DOMAIN_CHECK:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in check constraints");
+			else
+				err = _("Grouping is not allowed in check constraints");
+
+			break;
+		case EXPR_KIND_COLUMN_DEFAULT:
+		case EXPR_KIND_FUNCTION_DEFAULT:
+
+			if (isAgg)
+				err = _("aggregate functions are not allowed in DEFAULT expressions");
+			else
+				err = _("Grouping is not allowed in DEFAULT expressions");
+
+			break;
+		case EXPR_KIND_INDEX_EXPRESSION:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in index expressions");
+			else
+				err = _("Grouping is not allowed in index expressions");
+
+			break;
+		case EXPR_KIND_INDEX_PREDICATE:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in index predicates");
+			else
+				err = _("Grouping is not allowed in index predicates");
+
+			break;
+		case EXPR_KIND_ALTER_COL_TRANSFORM:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in transform expressions");
+			else
+				err = _("Grouping is not allowed in transform expressions");
+
+			break;
+		case EXPR_KIND_EXECUTE_PARAMETER:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in EXECUTE parameters");
+			else
+				err = _("Grouping is not allowed in EXECUTE parameters");
+
+			break;
+		case EXPR_KIND_TRIGGER_WHEN:
+			if (isAgg)
+				err = _("aggregate functions are not allowed in trigger WHEN conditions");
+			else
+				err = _("Grouping is not allowed in trigger WHEN conditions");
+
+			break;
+
+			/*
+			 * There is intentionally no default: case here, so that the
+			 * compiler will warn if we add a new ParseExprKind without
+			 * extending this switch.  If we do see an unrecognized value at
+			 * runtime, the behavior will be the same as for EXPR_KIND_OTHER,
+			 * which is sane anyway.
+			 */
+	}
+
+	if (err)
+		ereport(ERROR,
+				(errcode(ERRCODE_GROUPING_ERROR),
+				 errmsg_internal("%s", err),
+				 parser_errposition(pstate, location)));
+
+	if (errkind)
+		ereport(ERROR,
+				(errcode(ERRCODE_GROUPING_ERROR),
+				 /* translator: %s is name of a SQL construct, eg GROUP BY */
+				 errmsg("aggregate functions are not allowed in %s",
+						ParseExprKindName(pstate->p_expr_kind)),
+				 parser_errposition(pstate, location)));
+}
 
 /*
  * transformAggregateCall -
@@ -96,10 +335,7 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 	List	   *tdistinct = NIL;
 	AttrNumber	attno = 1;
 	int			save_next_resno;
-	int			min_varlevel;
 	ListCell   *lc;
-	const char *err;
-	bool		errkind;
 
 	if (AGGKIND_IS_ORDERED_SET(agg->aggkind))
 	{
@@ -214,148 +450,44 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 	agg->aggorder = torder;
 	agg->aggdistinct = tdistinct;
 
-	/*
-	 * Check the arguments to compute the aggregate's level and detect
-	 * improper nesting.
-	 */
-	min_varlevel = check_agg_arguments(pstate,
-									   agg->aggdirectargs,
-									   agg->args,
-									   agg->aggfilter);
-	agg->agglevelsup = min_varlevel;
+	check_agglevels_and_constraints(pstate, (Node *) agg);
+}
 
-	/* Mark the correct pstate level as having aggregates */
-	while (min_varlevel-- > 0)
-		pstate = pstate->parentParseState;
-	pstate->p_hasAggs = true;
+/* transformGroupingExpr
+ * Transform a grouping expression
+ */
+Node *
+transformGroupingExpr(ParseState *pstate, Grouping *p)
+{
+	ListCell   *lc;
+	List	   *args = p->args;
+	List	   *result_list = NIL;
+	Grouping   *result = makeNode(Grouping);
 
-	/*
-	 * Check to see if the aggregate function is in an invalid place within
-	 * its aggregation query.
-	 *
-	 * For brevity we support two schemes for reporting an error here: set
-	 * "err" to a custom message, or set "errkind" true if the error context
-	 * is sufficiently identified by what ParseExprKindName will return, *and*
-	 * what it will return is just a SQL keyword.  (Otherwise, use a custom
-	 * message to avoid creating translation problems.)
-	 */
-	err = NULL;
-	errkind = false;
-	switch (pstate->p_expr_kind)
+	if (list_length(args) > 31)
+		ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+				 errmsg("GROUPING must have fewer than 32 arguments"),
+				 parser_errposition(pstate, p->location)));
+
+	foreach(lc, args)
 	{
-		case EXPR_KIND_NONE:
-			Assert(false);		/* can't happen */
-			break;
-		case EXPR_KIND_OTHER:
-			/* Accept aggregate here; caller must throw error if wanted */
-			break;
-		case EXPR_KIND_JOIN_ON:
-		case EXPR_KIND_JOIN_USING:
-			err = _("aggregate functions are not allowed in JOIN conditions");
-			break;
-		case EXPR_KIND_FROM_SUBSELECT:
-			/* Should only be possible in a LATERAL subquery */
-			Assert(pstate->p_lateral_active);
-			/* Aggregate scope rules make it worth being explicit here */
-			err = _("aggregate functions are not allowed in FROM clause of their own query level");
-			break;
-		case EXPR_KIND_FROM_FUNCTION:
-			err = _("aggregate functions are not allowed in functions in FROM");
-			break;
-		case EXPR_KIND_WHERE:
-			errkind = true;
-			break;
-		case EXPR_KIND_HAVING:
-			/* okay */
-			break;
-		case EXPR_KIND_FILTER:
-			errkind = true;
-			break;
-		case EXPR_KIND_WINDOW_PARTITION:
-			/* okay */
-			break;
-		case EXPR_KIND_WINDOW_ORDER:
-			/* okay */
-			break;
-		case EXPR_KIND_WINDOW_FRAME_RANGE:
-			err = _("aggregate functions are not allowed in window RANGE");
-			break;
-		case EXPR_KIND_WINDOW_FRAME_ROWS:
-			err = _("aggregate functions are not allowed in window ROWS");
-			break;
-		case EXPR_KIND_SELECT_TARGET:
-			/* okay */
-			break;
-		case EXPR_KIND_INSERT_TARGET:
-		case EXPR_KIND_UPDATE_SOURCE:
-		case EXPR_KIND_UPDATE_TARGET:
-			errkind = true;
-			break;
-		case EXPR_KIND_GROUP_BY:
-			errkind = true;
-			break;
-		case EXPR_KIND_ORDER_BY:
-			/* okay */
-			break;
-		case EXPR_KIND_DISTINCT_ON:
-			/* okay */
-			break;
-		case EXPR_KIND_LIMIT:
-		case EXPR_KIND_OFFSET:
-			errkind = true;
-			break;
-		case EXPR_KIND_RETURNING:
-			errkind = true;
-			break;
-		case EXPR_KIND_VALUES:
-			errkind = true;
-			break;
-		case EXPR_KIND_CHECK_CONSTRAINT:
-		case EXPR_KIND_DOMAIN_CHECK:
-			err = _("aggregate functions are not allowed in check constraints");
-			break;
-		case EXPR_KIND_COLUMN_DEFAULT:
-		case EXPR_KIND_FUNCTION_DEFAULT:
-			err = _("aggregate functions are not allowed in DEFAULT expressions");
-			break;
-		case EXPR_KIND_INDEX_EXPRESSION:
-			err = _("aggregate functions are not allowed in index expressions");
-			break;
-		case EXPR_KIND_INDEX_PREDICATE:
-			err = _("aggregate functions are not allowed in index predicates");
-			break;
-		case EXPR_KIND_ALTER_COL_TRANSFORM:
-			err = _("aggregate functions are not allowed in transform expressions");
-			break;
-		case EXPR_KIND_EXECUTE_PARAMETER:
-			err = _("aggregate functions are not allowed in EXECUTE parameters");
-			break;
-		case EXPR_KIND_TRIGGER_WHEN:
-			err = _("aggregate functions are not allowed in trigger WHEN conditions");
-			break;
+		Node *current_result;
+
+		current_result = transformExpr(pstate, (Node*) lfirst(lc), pstate->p_expr_kind);
+
+		/* acceptability of expressions is checked later */
 
-			/*
-			 * There is intentionally no default: case here, so that the
-			 * compiler will warn if we add a new ParseExprKind without
-			 * extending this switch.  If we do see an unrecognized value at
-			 * runtime, the behavior will be the same as for EXPR_KIND_OTHER,
-			 * which is sane anyway.
-			 */
+		result_list = lappend(result_list, current_result);
 	}
-	if (err)
-		ereport(ERROR,
-				(errcode(ERRCODE_GROUPING_ERROR),
-				 errmsg_internal("%s", err),
-				 parser_errposition(pstate, agg->location)));
-	if (errkind)
-		ereport(ERROR,
-				(errcode(ERRCODE_GROUPING_ERROR),
-		/* translator: %s is name of a SQL construct, eg GROUP BY */
-				 errmsg("aggregate functions are not allowed in %s",
-						ParseExprKindName(pstate->p_expr_kind)),
-				 parser_errposition(pstate, agg->location)));
-}
 
+	result->args = result_list;
+	result->location = p->location;
+
+	check_agglevels_and_constraints(pstate, (Node *) result);
+
+	return (Node *) result;
+}
 /*
  * check_agg_arguments
  *	  Scan the arguments of an aggregate function to determine the
@@ -527,6 +659,23 @@ check_agg_arguments_walker(Node *node,
 		context->sublevels_up--;
 		return result;
 	}
+
+	if (IsA(node, Grouping))
+	{
+		int			agglevelsup = ((Grouping *) node)->agglevelsup;
+
+		/* convert levelsup to frame of reference of original query */
+		agglevelsup -= context->sublevels_up;
+		/* ignore local aggs of subqueries */
+		if (agglevelsup >= 0)
+		{
+			if (context->min_agglevel < 0 ||
+				context->min_agglevel > agglevelsup)
+				context->min_agglevel = agglevelsup;
+		}
+		/* Continue and descend into subtree */
+	}
+
 	return expression_tree_walker(node,
 								  check_agg_arguments_walker,
 								  (void *) context);
@@ -770,17 +919,41 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 void
 parseCheckAggregates(ParseState *pstate, Query *qry)
 {
+	List       *gset_common = NIL;
 	List	   *groupClauses = NIL;
+	List	   *groupClauseVars = NIL;
 	bool		have_non_var_grouping;
 	List	   *func_grouped_rels = NIL;
 	ListCell   *l;
 	bool		hasJoinRTEs;
 	bool		hasSelfRefRTEs;
-	PlannerInfo *root;
+	PlannerInfo *root = NULL;
 	Node	   *clause;
 
 	/* This should only be called if we found aggregates or grouping */
-	Assert(pstate->p_hasAggs || qry->groupClause || qry->havingQual);
+	Assert(pstate->p_hasAggs || qry->groupClause || qry->havingQual || qry->groupingSets);
+
+	/*
+	 * If we have grouping sets, expand them and find the intersection of
+	 * all sets (which will often be empty, so help things along by
+	 * seeding the intersect with the smallest set).
+	 */
+	if (qry->groupingSets)
+	{
+		List *gsets = expand_grouping_sets(qry->groupingSets);
+
+		gset_common = llast(gsets);
+
+		if (gset_common)
+		{
+			foreach(l, gsets)
+			{
+				gset_common = list_intersection_int(gset_common, lfirst(l));
+				if (!gset_common)
+					break;
+			}
+		}
+	}
 
 	/*
 	 * Scan the range table to see if there are JOIN or self-reference CTE
@@ -800,15 +973,19 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
 	/*
 	 * Build a list of the acceptable GROUP BY expressions for use by
 	 * check_ungrouped_columns().
+	 *
+	 * We get the TLE, not just the expr, because GROUPING wants to know
+	 * the sortgroupref.
 	 */
 	foreach(l, qry->groupClause)
 	{
 		SortGroupClause *grpcl = (SortGroupClause *) lfirst(l);
-		Node	   *expr;
+		TargetEntry	   *expr;
 
-		expr = get_sortgroupclause_expr(grpcl, qry->targetList);
+		expr = get_sortgroupclause_tle(grpcl, qry->targetList);
 		if (expr == NULL)
 			continue;			/* probably cannot happen */
+
 		groupClauses = lcons(expr, groupClauses);
 	}
 
@@ -830,21 +1007,28 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
 		groupClauses = (List *) flatten_join_alias_vars(root,
 													  (Node *) groupClauses);
 	}
-	else
-		root = NULL;			/* keep compiler quiet */
 
 	/*
 	 * Detect whether any of the grouping expressions aren't simple Vars; if
 	 * they're all Vars then we don't have to work so hard in the recursive
 	 * scans.  (Note we have to flatten aliases before this.)
+	 *
+	 * Track Vars that are included in all grouping sets separately in
+	 * groupClauseVars, since these are the only ones we can use to check
+	 * for functional dependencies.
 	 */
 	have_non_var_grouping = false;
 	foreach(l, groupClauses)
 	{
-		if (!IsA((Node *) lfirst(l), Var))
+		TargetEntry *tle = lfirst(l);
+		if (!IsA(tle->expr, Var))
 		{
 			have_non_var_grouping = true;
-			break;
+		}
+		else if (!qry->groupingSets
+				 || list_member_int(gset_common, tle->ressortgroupref))
+		{
+			groupClauseVars = lappend(groupClauseVars, tle->expr);
 		}
 	}
 
@@ -857,17 +1041,25 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
 	 * grouping expressions themselves --- but they'll all pass the test ...
 	 */
 	clause = (Node *) qry->targetList;
+	finalize_grouping_exprs(clause, pstate, qry,
+							groupClauses, root,
+							have_non_var_grouping);
 	if (hasJoinRTEs)
 		clause = flatten_join_alias_vars(root, clause);
 	check_ungrouped_columns(clause, pstate, qry,
-							groupClauses, have_non_var_grouping,
+							groupClauses, groupClauseVars,
+							have_non_var_grouping,
 							&func_grouped_rels);
 
 	clause = (Node *) qry->havingQual;
+	finalize_grouping_exprs(clause, pstate, qry,
+							groupClauses, root,
+							have_non_var_grouping);
 	if (hasJoinRTEs)
 		clause = flatten_join_alias_vars(root, clause);
 	check_ungrouped_columns(clause, pstate, qry,
-							groupClauses, have_non_var_grouping,
+							groupClauses, groupClauseVars,
+							have_non_var_grouping,
 							&func_grouped_rels);
 
 	/*
@@ -904,14 +1096,17 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
  */
 static void
 check_ungrouped_columns(Node *node, ParseState *pstate, Query *qry,
-						List *groupClauses, bool have_non_var_grouping,
+						List *groupClauses, List *groupClauseVars,
+						bool have_non_var_grouping,
 						List **func_grouped_rels)
 {
 	check_ungrouped_columns_context context;
 
 	context.pstate = pstate;
 	context.qry = qry;
+	context.root = NULL;
 	context.groupClauses = groupClauses;
+	context.groupClauseVars = groupClauseVars;
 	context.have_non_var_grouping = have_non_var_grouping;
 	context.func_grouped_rels = func_grouped_rels;
 	context.sublevels_up = 0;
@@ -965,6 +1160,16 @@ check_ungrouped_columns_walker(Node *node,
 			return false;
 	}
 
+	if (IsA(node, Grouping))
+	{
+		Grouping *grp = (Grouping *) node;
+
+		/* we handled Grouping separately, no need to recheck at this level. */
+
+		if ((int) grp->agglevelsup >= context->sublevels_up)
+			return false;
+	}
+
 	/*
 	 * If we have any GROUP BY items that are not simple Vars, check to see if
 	 * subexpression as a whole matches any GROUP BY item. We need to do this
@@ -976,7 +1181,9 @@ check_ungrouped_columns_walker(Node *node,
 	{
 		foreach(gl, context->groupClauses)
 		{
-			if (equal(node, lfirst(gl)))
+			TargetEntry *tle = lfirst(gl);
+
+			if (equal(node, tle->expr))
 				return false;	/* acceptable, do not descend more */
 		}
 	}
@@ -1003,13 +1210,15 @@ check_ungrouped_columns_walker(Node *node,
 		{
 			foreach(gl, context->groupClauses)
 			{
-				Var		   *gvar = (Var *) lfirst(gl);
+				Var		   *gvar = (Var *) ((TargetEntry *)lfirst(gl))->expr;
 
 				if (IsA(gvar, Var) &&
 					gvar->varno == var->varno &&
 					gvar->varattno == var->varattno &&
 					gvar->varlevelsup == 0)
+				{
 					return false;		/* acceptable, we're okay */
+				}
 			}
 		}
 
@@ -1040,7 +1249,7 @@ check_ungrouped_columns_walker(Node *node,
 			if (check_functional_grouping(rte->relid,
 										  var->varno,
 										  0,
-										  context->groupClauses,
+										  context->groupClauseVars,
 										  &context->qry->constraintDeps))
 			{
 				*context->func_grouped_rels =
@@ -1085,6 +1294,384 @@ check_ungrouped_columns_walker(Node *node,
 }
 
 /*
+ * finalize_grouping_exprs -
+ *	  Scan the given expression tree for GROUPING() and related calls,
+ *    and validate and process their arguments.
+ *
+ * This is split out from check_ungrouped_columns above because it needs
+ * to modify the nodes (which it does in-place, not via a mutator) while
+ * check_ungrouped_columns may see only a copy of the original thanks to
+ * flattening of join alias vars. So here, we flatten each individual
+ * GROUPING argument as we see it before comparing it.
+ */
+static void
+finalize_grouping_exprs(Node *node, ParseState *pstate, Query *qry,
+						List *groupClauses, PlannerInfo *root,
+						bool have_non_var_grouping)
+{
+	check_ungrouped_columns_context context;
+
+	context.pstate = pstate;
+	context.qry = qry;
+	context.root = root;
+	context.groupClauses = groupClauses;
+	context.groupClauseVars = NIL;
+	context.have_non_var_grouping = have_non_var_grouping;
+	context.func_grouped_rels = NULL;
+	context.sublevels_up = 0;
+	context.in_agg_direct_args = false;
+	finalize_grouping_exprs_walker(node, &context);
+}
+
+static bool
+finalize_grouping_exprs_walker(Node *node,
+							   check_ungrouped_columns_context *context)
+{
+	ListCell   *gl;
+
+	if (node == NULL)
+		return false;
+	if (IsA(node, Const) ||
+		IsA(node, Param))
+		return false;			/* constants are always acceptable */
+
+	if (IsA(node, Aggref))
+	{
+		Aggref	   *agg = (Aggref *) node;
+
+		if ((int) agg->agglevelsup == context->sublevels_up)
+		{
+			/*
+			 * If we find an aggregate call of the original level, do not
+			 * recurse into its normal arguments, ORDER BY arguments, or
+			 * filter; GROUPING exprs of this level are not allowed there. But
+			 * check direct arguments as though they weren't in an aggregate.
+			 */
+			bool		result;
+
+			Assert(!context->in_agg_direct_args);
+			context->in_agg_direct_args = true;
+			result = finalize_grouping_exprs_walker((Node *) agg->aggdirectargs,
+													context);
+			context->in_agg_direct_args = false;
+			return result;
+		}
+
+		/*
+		 * We can skip recursing into aggregates of higher levels altogether,
+		 * since they could not possibly contain exprs of concern to us (see
+		 * transformAggregateCall).  We do need to look at aggregates of lower
+		 * levels, however.
+		 */
+		if ((int) agg->agglevelsup > context->sublevels_up)
+			return false;
+	}
+
+	if (IsA(node, Grouping))
+	{
+		Grouping *grp = (Grouping *) node;
+
+		/*
+		 * We only need to check Grouping nodes at the exact level to which
+		 * they belong, since they cannot mix levels in arguments.
+		 */
+
+		if ((int) grp->agglevelsup == context->sublevels_up)
+		{
+			ListCell  *lc;
+			List 	  *ref_list = NIL;
+
+			foreach(lc, (grp->args))
+			{
+				Node   *expr = lfirst(lc);
+				Index	ref = 0;
+
+				if (context->root)
+					expr = flatten_join_alias_vars(context->root, expr);
+
+				/*
+				 * Each expression must match a grouping entry at the current
+				 * query level. Unlike the general expression case, we don't
+				 * allow functional dependencies or outer references.
+				 */
+
+				if (IsA(expr, Var))
+				{
+					Var *var = (Var *) expr;
+
+					if (var->varlevelsup == context->sublevels_up)
+					{
+						foreach(gl, context->groupClauses)
+						{
+							TargetEntry *tle = lfirst(gl);
+							Var	  		*gvar = (Var *) tle->expr;
+
+							if (IsA(gvar, Var) &&
+								gvar->varno == var->varno &&
+								gvar->varattno == var->varattno &&
+								gvar->varlevelsup == 0)
+							{
+								ref = tle->ressortgroupref;
+								break;
+							}
+						}
+					}
+				}
+				else if (context->have_non_var_grouping && context->sublevels_up == 0)
+				{
+					foreach(gl, context->groupClauses)
+					{
+						TargetEntry *tle = lfirst(gl);
+
+						if (equal(expr, tle->expr))
+						{
+							ref = tle->ressortgroupref;
+							break;
+						}
+					}
+				}
+
+				if (ref == 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_GROUPING_ERROR),
+							 errmsg("Arguments to GROUPING must be grouping expressions of the associated query level"),
+							 parser_errposition(context->pstate, grp->location)));
+
+				ref_list = lappend_int(ref_list, ref);
+			}
+
+			grp->refs = ref_list;
+		}
+
+		if ((int) grp->agglevelsup > context->sublevels_up)
+			return false;
+	}
+
+	if (IsA(node, Query))
+	{
+		/* Recurse into subselects */
+		bool		result;
+
+		context->sublevels_up++;
+		result = query_tree_walker((Query *) node,
+								   finalize_grouping_exprs_walker,
+								   (void *) context,
+								   0);
+		context->sublevels_up--;
+		return result;
+	}
+	return expression_tree_walker(node, finalize_grouping_exprs_walker,
+								  (void *) context);
+}
+
+
+/*
+ * Given a GroupingSet node, expand it and return a list of lists.
+ *
+ * For EMPTY nodes, return a list of one empty list.
+ *
+ * For SIMPLE nodes, return a list of one list, which is the node content.
+ *
+ * For CUBE and ROLLUP nodes, return a list of the expansions.
+ *
+ * For SET nodes, recursively expand contained CUBE and ROLLUP.
+ */
+static List*
+expand_groupingset_node(GroupingSet *gs)
+{
+	List * result = NIL;
+
+	switch (gs->kind)
+	{
+		case GROUPING_SET_EMPTY:
+			result = list_make1(NIL);
+			break;
+
+		case GROUPING_SET_SIMPLE:
+			result = list_make1(gs->content);
+			break;
+
+		case GROUPING_SET_ROLLUP:
+			{
+				List	   *rollup_val = gs->content;
+				ListCell   *lc;
+				int			curgroup_size = list_length(gs->content);
+
+				while (curgroup_size > 0)
+				{
+					List   *current_result = NIL;
+					int		i = curgroup_size;
+
+					foreach(lc, rollup_val)
+					{
+						GroupingSet *gs_current = (GroupingSet *) lfirst(lc);
+
+						Assert(gs_current->kind == GROUPING_SET_SIMPLE);
+
+						current_result = list_concat(current_result,
+													 list_copy(gs_current->content));
+
+						/* If we are done with making the current group, break */
+						if (--i == 0)
+							break;
+					}
+
+					result = lappend(result, current_result);
+					--curgroup_size;
+				}
+
+				result = lappend(result, NIL);
+			}
+			break;
+
+		case GROUPING_SET_CUBE:
+			{
+				List   *cube_list = gs->content;
+				int		number_bits = list_length(cube_list);
+				uint32	num_sets;
+				uint32	i;
+
+				/* parser should cap this much lower */
+				Assert(number_bits < 31);
+
+				num_sets = (1U << number_bits);
+
+				for (i = 0; i < num_sets; i++)
+				{
+					List *current_result = NIL;
+					ListCell *lc;
+					uint32 mask = 1U;
+
+					foreach(lc, cube_list)
+					{
+						GroupingSet *gs_current = (GroupingSet *) lfirst(lc);
+
+						Assert(gs_current->kind == GROUPING_SET_SIMPLE);
+
+						if (mask & i)
+						{
+							current_result = list_concat(current_result,
+														 list_copy(gs_current->content));
+						}
+
+						mask <<= 1;
+					}
+
+					result = lappend(result, current_result);
+				}
+			}
+			break;
+
+		case GROUPING_SET_SETS:
+			{
+				ListCell   *lc;
+
+				foreach(lc, gs->content)
+				{
+					List *current_result = expand_groupingset_node(lfirst(lc));
+
+					result = list_concat(result, current_result);
+				}
+			}
+			break;
+	}
+
+	return result;
+}
+
+static int
+cmp_list_len_desc(const void *a, const void *b)
+{
+	int la = list_length(*(List*const*)a);
+	int lb = list_length(*(List*const*)b);
+	return (la > lb) ? -1 : (la == lb) ? 0 : 1;
+}
+
+/*
+ * Expand a groupingSets clause to a flat list of grouping sets.
+ *
+ * This is mainly for the planner, but we use it here too to do
+ * some consistency checks.
+ */
+
+List *
+expand_grouping_sets(List *groupingSets)
+{
+	List	   *expanded_groups = NIL;
+	List       *result = NIL;
+	ListCell   *lc;
+
+	if (groupingSets == NIL)
+		return NIL;
+
+	foreach(lc, groupingSets)
+	{
+		List *current_result = NIL;
+		GroupingSet *gs = lfirst(lc);
+
+		current_result = expand_groupingset_node(gs);
+
+		Assert(current_result != NIL);
+
+		expanded_groups = lappend(expanded_groups, current_result);
+	}
+
+	/*
+	 * Do cartesian product between sublists of expanded_groups.
+	 * While at it, remove any duplicate elements from individual
+	 * grouping sets (we must NOT change the number of sets though)
+	 */
+
+	foreach(lc, (List *) linitial(expanded_groups))
+	{
+		result = lappend(result, list_union_int(NIL, (List *) lfirst(lc)));
+	}
+
+	for_each_cell(lc, lnext(list_head(expanded_groups)))
+	{
+		List	   *p = lfirst(lc);
+		List	   *new_result = NIL;
+		ListCell   *lc2;
+
+		foreach(lc2, result)
+		{
+			List	   *q = lfirst(lc2);
+			ListCell   *lc3;
+
+			foreach(lc3, p)
+			{
+				new_result = lappend(new_result, list_union_int(q, (List *) lfirst(lc3)));
+			}
+		}
+		result = new_result;
+	}
+
+	if (list_length(result) > 1)
+	{
+		int		result_len = list_length(result);
+		List  **buf = palloc(sizeof(List*) * result_len);
+		List  **ptr = buf;
+
+		foreach(lc, result)
+		{
+			*ptr++ = lfirst(lc);
+		}
+
+		qsort(buf, result_len, sizeof(List*), cmp_list_len_desc);
+
+		result = NIL;
+		ptr = buf;
+
+		while (result_len-- > 0)
+			result = lappend(result, *ptr++);
+
+		pfree(buf);
+	}
+
+	return result;
+}
+
+/*
  * get_aggregate_argtypes
  *	Identify the specific datatypes passed to an aggregate call.
  *
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 4931dca..f53e452 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -1663,40 +1663,160 @@ findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist,
 	return target_result;
 }
 
+
 /*
- * transformGroupClause -
- *	  transform a GROUP BY clause
+ * Flatten out parenthesized sublists in grouping lists, and some cases
+ * of nested grouping sets.
  *
- * GROUP BY items will be added to the targetlist (as resjunk columns)
- * if not already present, so the targetlist must be passed by reference.
+ * Inside a grouping set (ROLLUP, CUBE, or GROUPING SETS), we expect the
+ * content to be nested no more than 2 deep: i.e. ROLLUP((a,b),(c,d)) is
+ * ok, but ROLLUP((a,(b,c)),d) is flattened to ((a,b,c),d), which we then
+ * normalize to ((a,b,c),(d)).
  *
- * This is also used for window PARTITION BY clauses (which act almost the
- * same, but are always interpreted per SQL99 rules).
+ * CUBE or ROLLUP can be nested inside GROUPING SETS (but not the reverse),
+ * and we leave that alone if we find it. But if we see GROUPING SETS inside
+ * GROUPING SETS, we can flatten and normalize as follows:
+ *   GROUPING SETS (a, (b,c), GROUPING SETS ((c,d),(e)), (f,g))
+ * becomes
+ *   GROUPING SETS ((a), (b,c), (c,d), (e), (f,g))
+ *
+ * This is per the spec's syntax transformations, but these are the only such
+ * transformations we do in parse analysis, so that queries retain the
+ * originally specified grouping set syntax for CUBE and ROLLUP as much as
+ * possible when deparsed. (Full expansion of the result into a list of
+ * grouping sets is left to the planner.)
+ *
+ * When we're done, the resulting list should contain only these possible
+ * elements:
+ *   - an expression
+ *   - a CUBE or ROLLUP with a list of expressions nested 2 deep
+ *   - a GROUPING SET containing any of:
+ *      - expression lists
+ *      - empty grouping sets
+ *      - CUBE or ROLLUP nodes with lists nested 2 deep
+ * The return is a new list, but doesn't deep-copy the old nodes except for
+ * GroupingSet nodes.
+ *
+ * As a side effect, flag whether the list has any GroupingSet nodes.
  */
-List *
-transformGroupClause(ParseState *pstate, List *grouplist,
-					 List **targetlist, List *sortClause,
-					 ParseExprKind exprKind, bool useSQL99)
+
+static Node *
+flatten_grouping_sets(Node *expr, bool toplevel, bool *hasGroupingSets)
 {
-	List	   *result = NIL;
-	ListCell   *gl;
+	if (expr == (Node *) NIL)
+		return (Node *) NIL;
 
-	foreach(gl, grouplist)
+	switch (expr->type)
 	{
-		Node	   *gexpr = (Node *) lfirst(gl);
-		TargetEntry *tle;
-		bool		found = false;
+		case T_RowExpr:
+			{
+				RowExpr *r = (RowExpr *) expr;
+				if (r->row_format == COERCE_IMPLICIT_CAST)
+					return flatten_grouping_sets((Node *) r->args,
+												 false, NULL);
+			}
+			break;
+		case T_GroupingSet:
+			{
+				GroupingSet *gset = (GroupingSet *) expr;
+				ListCell   *l2;
+				List	   *result_set = NIL;
 
-		if (useSQL99)
-			tle = findTargetlistEntrySQL99(pstate, gexpr,
-										   targetlist, exprKind);
-		else
-			tle = findTargetlistEntrySQL92(pstate, gexpr,
-										   targetlist, exprKind);
+				if (hasGroupingSets)
+					*hasGroupingSets = true;
 
-		/* Eliminate duplicates (GROUP BY x, x) */
-		if (targetIsInSortList(tle, InvalidOid, result))
-			continue;
+				/*
+				 * at the top level, we skip over all empty grouping sets; the
+				 * caller can supply the canonical GROUP BY () if nothing is left.
+				 */
+
+				if (toplevel && gset->kind == GROUPING_SET_EMPTY)
+					return (Node *) NIL;
+
+				foreach(l2, gset->content)
+				{
+					Node   *n2 = flatten_grouping_sets(lfirst(l2), false, NULL);
+
+					result_set = lappend(result_set, n2);
+				}
+
+				/*
+				 * At top level, keep the grouping set node; but if we're in a nested
+				 * grouping set, then we need to concat the flattened result into the
+				 * outer list if it's simply nested.
+				 */
+
+				if (toplevel || (gset->kind != GROUPING_SET_SETS))
+				{
+					return (Node *) makeGroupingSet(gset->kind, result_set, gset->location);
+				}
+				else
+					return (Node *) result_set;
+			}
+		case T_List:
+			{
+				List	   *result = NIL;
+				ListCell   *l;
+
+				foreach(l, (List *)expr)
+				{
+					Node   *n = flatten_grouping_sets(lfirst(l), toplevel, hasGroupingSets);
+					if (n != (Node *) NIL)
+					{
+						if (IsA(n,List))
+							result = list_concat(result, (List *) n);
+						else
+							result = lappend(result, n);
+					}
+				}
+
+				return (Node *) result;
+			}
+		default:
+			break;
+	}
+
+	return expr;
+}
+
+static Index
+transformGroupClauseExpr(List **flatresult, Bitmapset *seen_local,
+						 ParseState *pstate, Node *gexpr,
+						 List **targetlist, List *sortClause,
+						 ParseExprKind exprKind, bool useSQL99, bool toplevel)
+{
+	TargetEntry *tle;
+	bool		found = false;
+
+	if (useSQL99)
+		tle = findTargetlistEntrySQL99(pstate, gexpr,
+									   targetlist, exprKind);
+	else
+		tle = findTargetlistEntrySQL92(pstate, gexpr,
+									   targetlist, exprKind);
+
+	if (tle->ressortgroupref > 0)
+	{
+		ListCell   *sl;
+
+		/*
+		 * Eliminate duplicates (GROUP BY x, x) but only at local level.
+		 * (Duplicates in grouping sets can affect the number of returned
+		 * rows, so can't be dropped indiscriminately.)
+		 *
+		 * Since we don't care about anything except the sortgroupref,
+		 * we can use a bitmapset rather than scanning lists.
+		 */
+		if (bms_is_member(tle->ressortgroupref,seen_local))
+			return 0;
+
+		/*
+		 * If we're already in the flat clause list, we don't need
+		 * to consider adding ourselves again.
+		 */
+		found = targetIsInSortList(tle, InvalidOid, *flatresult);
+		if (found)
+			return tle->ressortgroupref;
 
 		/*
 		 * If the GROUP BY tlist entry also appears in ORDER BY, copy operator
@@ -1708,35 +1828,257 @@ transformGroupClause(ParseState *pstate, List *grouplist,
 		 * sort step, and it allows the user to choose the equality semantics
 		 * used by GROUP BY, should she be working with a datatype that has
 		 * more than one equality operator.
+		 *
+		 * If we're in a grouping set, though, we force our requested ordering
+		 * to be NULLS LAST, because if we have any hope of using a sorted agg
+		 * for the job, we're going to be tacking on generated NULL values
+		 * after the corresponding groups. If the user demands nulls first,
+		 * another sort step is going to be inevitable, but that's the
+		 * planner's problem.
 		 */
-		if (tle->ressortgroupref > 0)
+
+		foreach(sl, sortClause)
 		{
-			ListCell   *sl;
+			SortGroupClause *sc = (SortGroupClause *) lfirst(sl);
 
-			foreach(sl, sortClause)
+			if (sc->tleSortGroupRef == tle->ressortgroupref)
 			{
-				SortGroupClause *sc = (SortGroupClause *) lfirst(sl);
+				SortGroupClause *grpc = copyObject(sc);
+				if (!toplevel)
+					grpc->nulls_first = false;
+				*flatresult = lappend(*flatresult, grpc);
+				found = true;
+				break;
+			}
+		}
+	}
 
-				if (sc->tleSortGroupRef == tle->ressortgroupref)
-				{
-					result = lappend(result, copyObject(sc));
-					found = true;
+	/*
+	 * If no match in ORDER BY, just add it to the result using default
+	 * sort/group semantics.
+	 */
+	if (!found)
+		*flatresult = addTargetToGroupList(pstate, tle,
+										   *flatresult, *targetlist,
+										   exprLocation(gexpr),
+										   true);
+
+	/*
+	 * _something_ must have assigned us a sortgroupref by now...
+	 */
+
+	return tle->ressortgroupref;
+}
+
+
+static List *
+transformGroupClauseList(List **flatresult,
+						 ParseState *pstate, List *list,
+						 List **targetlist, List *sortClause,
+						 ParseExprKind exprKind, bool useSQL99, bool toplevel)
+{
+	Bitmapset  *seen_local = NULL;
+	List	   *result = NIL;
+	ListCell   *gl;
+
+	foreach(gl, list)
+	{
+		Node        *gexpr = (Node *) lfirst(gl);
+
+		Index ref = transformGroupClauseExpr(flatresult,
+											 seen_local,
+											 pstate,
+											 gexpr,
+											 targetlist,
+											 sortClause,
+											 exprKind,
+											 useSQL99,
+											 toplevel);
+		if (ref > 0)
+		{
+			seen_local = bms_add_member(seen_local, ref);
+			result = lappend_int(result, ref);
+		}
+	}
+
+	return result;
+}
+
+static Node *
+transformGroupingSet(List **flatresult,
+					 ParseState *pstate, GroupingSet *gset,
+					 List **targetlist, List *sortClause,
+					 ParseExprKind exprKind, bool useSQL99, bool toplevel)
+{
+	ListCell   *gl;
+	List	   *content = NIL;
+
+	Assert(toplevel || gset->kind != GROUPING_SET_SETS);
+
+	foreach(gl, gset->content)
+	{
+		Node   *n = lfirst(gl);
+
+		if (IsA(n, List))
+		{
+			List *l = transformGroupClauseList(flatresult,
+											   pstate, (List *) n,
+											   targetlist, sortClause,
+											   exprKind, useSQL99, false);
+
+			content = lappend(content, makeGroupingSet(GROUPING_SET_SIMPLE,
+													   l,
+													   exprLocation(n)));
+		}
+		else if (IsA(n, GroupingSet))
+		{
+			GroupingSet *gset2 = (GroupingSet *) lfirst(gl);
+
+			content = lappend(content, transformGroupingSet(flatresult,
+															pstate, gset2,
+															targetlist, sortClause,
+															exprKind, useSQL99, false));
+		}
+		else
+		{
+			Index ref = transformGroupClauseExpr(flatresult,
+												 NULL,
+												 pstate,
+												 n,
+												 targetlist,
+												 sortClause,
+												 exprKind,
+												 useSQL99,
+												 false);
+
+			content = lappend(content, makeGroupingSet(GROUPING_SET_SIMPLE,
+													   list_make1_int(ref),
+													   exprLocation(n)));
+		}
+	}
+
+	/* Arbitrarily cap the size of CUBE, which has exponential growth */
+	if (gset->kind == GROUPING_SET_CUBE)
+	{
+		if (list_length(content) > 16)
+			ereport(ERROR,
+					(errcode(ERRCODE_TOO_MANY_COLUMNS),
+					 errmsg("CUBE is limited to 16 elements"),
+					 parser_errposition(pstate, gset->location)));
+	}
+
+	return (Node *) makeGroupingSet(gset->kind, content, gset->location);
+}
+
+
+/*
+ * transformGroupClause -
+ *	  transform a GROUP BY clause
+ *
+ * GROUP BY items will be added to the targetlist (as resjunk columns)
+ * if not already present, so the targetlist must be passed by reference.
+ *
+ * This is also used for window PARTITION BY clauses (which act almost the
+ * same, but are always interpreted per SQL99 rules).
+ *
+ * Grouping sets make this a lot more complex than it was. Our goal here is
+ * twofold: we make a flat list of SortGroupClause nodes referencing each
+ * distinct expression used for grouping, with those expressions added to the
+ * targetlist if needed. At the same time, we build the groupingSets tree,
+ * which stores only ressortgrouprefs as integer lists inside GroupingSet nodes
+ * (possibly nested, but limited in depth: a GROUPING_SET_SETS node can contain
+ * nested SIMPLE, CUBE or ROLLUP nodes, but not more sets - we flatten that
+ * out; while CUBE and ROLLUP can contain only SIMPLE nodes).
+ *
+ * We skip much of the hard work if there are no grouping sets.
+ */
+List *
+transformGroupClause(ParseState *pstate, List *grouplist, List **groupingSets,
+					 List **targetlist, List *sortClause,
+					 ParseExprKind exprKind, bool useSQL99)
+{
+	List	   *result = NIL;
+	List	   *flat_grouplist;
+	List	   *gsets = NIL;
+	ListCell   *gl;
+	bool        hasGroupingSets = false;
+	Bitmapset  *seen_local = NULL;
+
+	/*
+	 * Recursively flatten implicit RowExprs. (Technically this is only
+	 * needed for GROUP BY, per the syntax rules for grouping sets, but
+	 * we do it anyway.)
+	 */
+	flat_grouplist = (List *) flatten_grouping_sets((Node *) grouplist,
+													true,
+													&hasGroupingSets);
+
+	/*
+	 * If the list is now empty, but hasGroupingSets is true, it's because
+	 * we elided redundant empty grouping sets. Restore a single empty
+	 * grouping set to leave a canonical form: GROUP BY ()
+	 */
+
+	if (flat_grouplist == NIL && hasGroupingSets)
+	{
+		flat_grouplist = list_make1(makeGroupingSet(GROUPING_SET_EMPTY,
+													NIL,
+													exprLocation((Node *) grouplist)));
+	}
+
+	foreach(gl, flat_grouplist)
+	{
+		Node        *gexpr = (Node *) lfirst(gl);
+
+		if (IsA(gexpr, GroupingSet))
+		{
+			GroupingSet *gset = (GroupingSet *) gexpr;
+
+			switch (gset->kind)
+			{
+				case GROUPING_SET_EMPTY:
+					gsets = lappend(gsets, gset);
+					break;
+				case GROUPING_SET_SIMPLE:
+					/* can't happen */
+					Assert(false);
+					break;
+				case GROUPING_SET_SETS:
+				case GROUPING_SET_CUBE:
+				case GROUPING_SET_ROLLUP:
+					gsets = lappend(gsets,
+									transformGroupingSet(&result,
+														 pstate, gset,
+														 targetlist, sortClause,
+														 exprKind, useSQL99, true));
 					break;
-				}
 			}
 		}
+		else
+		{
+			Index ref = transformGroupClauseExpr(&result, seen_local,
+												 pstate, gexpr,
+												 targetlist, sortClause,
+												 exprKind, useSQL99, true);
 
-		/*
-		 * If no match in ORDER BY, just add it to the result using default
-		 * sort/group semantics.
-		 */
-		if (!found)
-			result = addTargetToGroupList(pstate, tle,
-										  result, *targetlist,
-										  exprLocation(gexpr),
-										  true);
+			if (ref > 0)
+			{
+				seen_local = bms_add_member(seen_local, ref);
+				if (hasGroupingSets)
+					gsets = lappend(gsets,
+									makeGroupingSet(GROUPING_SET_SIMPLE,
+													list_make1_int(ref),
+													exprLocation(gexpr)));
+			}
+		}
 	}
 
+	/* parser should prevent this */
+	Assert(gsets == NIL || groupingSets != NULL);
+
+	if (groupingSets)
+		*groupingSets = gsets;
+
 	return result;
 }
 
@@ -1841,6 +2183,7 @@ transformWindowDefinitions(ParseState *pstate,
 										  true /* force SQL99 rules */ );
 		partitionClause = transformGroupClause(pstate,
 											   windef->partitionClause,
+											   NULL,
 											   targetlist,
 											   orderClause,
 											   EXPR_KIND_WINDOW_PARTITION,
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 4a8aaf6..740ae3a 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -32,6 +32,7 @@
 #include "parser/parse_relation.h"
 #include "parser/parse_target.h"
 #include "parser/parse_type.h"
+#include "parser/parse_agg.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/xml.h"
@@ -166,6 +167,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 										InvalidOid, InvalidOid, -1);
 			break;
 
+		case T_Grouping:
+			result = transformGroupingExpr(pstate, (Grouping *) expr);
+			break;
+
 		case T_TypeCast:
 			{
 				TypeCast   *tc = (TypeCast *) expr;
@@ -1483,6 +1488,8 @@ transformCaseExpr(ParseState *pstate, CaseExpr *c)
 	return (Node *) newc;
 }
 
+
+
 static Node *
 transformSubLink(ParseState *pstate, SubLink *sublink)
 {
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 328e0c6..1e48346 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1628,6 +1628,9 @@ FigureColnameInternal(Node *node, char **name)
 				}
 			}
 			break;
+		case T_Grouping:
+			*name = "grouping";
+			return 2;
 		case T_A_Indirection:
 			{
 				A_Indirection *ind = (A_Indirection *) node;
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index e6c5530..5c4e201 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2063,7 +2063,7 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
 	if (viewquery->distinctClause != NIL)
 		return gettext_noop("Views containing DISTINCT are not automatically updatable.");
 
-	if (viewquery->groupClause != NIL)
+	if (viewquery->groupClause != NIL || viewquery->groupingSets)
 		return gettext_noop("Views containing GROUP BY are not automatically updatable.");
 
 	if (viewquery->havingQual != NULL)
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index fb20314..dd939cd 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -104,6 +104,12 @@ contain_aggs_of_level_walker(Node *node,
 		context->sublevels_up--;
 		return result;
 	}
+	if (IsA(node, Grouping))
+	{
+		if (((Grouping *) node)->agglevelsup == context->sublevels_up)
+			return true;
+	}
+
 	return expression_tree_walker(node, contain_aggs_of_level_walker,
 								  (void *) context);
 }
@@ -169,6 +175,16 @@ locate_agg_of_level_walker(Node *node,
 		context->sublevels_up--;
 		return result;
 	}
+	if (IsA(node, Grouping))
+	{
+		if (((Grouping *) node)->agglevelsup == context->sublevels_up &&
+			((Grouping *) node)->location >= 0)
+		{
+			context->agg_location = ((Aggref *) node)->location;
+			return true;		/* abort the tree traversal and return true */
+		}
+	}
+
 	return expression_tree_walker(node, locate_agg_of_level_walker,
 								  (void *) context);
 }
@@ -705,6 +721,14 @@ IncrementVarSublevelsUp_walker(Node *node,
 			agg->agglevelsup += context->delta_sublevels_up;
 		/* fall through to recurse into argument */
 	}
+	if (IsA(node, Grouping))
+	{
+		Grouping	   *grp = (Grouping *) node;
+
+		if (grp->agglevelsup >= context->min_sublevels_up)
+			grp->agglevelsup += context->delta_sublevels_up;
+		/* fall through to recurse into argument */
+	}
 	if (IsA(node, PlaceHolderVar))
 	{
 		PlaceHolderVar *phv = (PlaceHolderVar *) node;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7237e5d..a598470 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -360,9 +360,11 @@ static void get_target_list(List *targetList, deparse_context *context,
 static void get_setop_query(Node *setOp, Query *query,
 				deparse_context *context,
 				TupleDesc resultDesc);
-static Node *get_rule_sortgroupclause(SortGroupClause *srt, List *tlist,
+static Node *get_rule_sortgroupclause(Index ref, List *tlist,
 						 bool force_colno,
 						 deparse_context *context);
+static void get_rule_groupingset(GroupingSet *gset, List *targetlist,
+								 bool omit_parens, deparse_context *context);
 static void get_rule_orderby(List *orderList, List *targetList,
 				 bool force_colno, deparse_context *context);
 static void get_rule_windowclause(Query *query, deparse_context *context);
@@ -4535,7 +4537,7 @@ get_basic_select_query(Query *query, deparse_context *context,
 				SortGroupClause *srt = (SortGroupClause *) lfirst(l);
 
 				appendStringInfoString(buf, sep);
-				get_rule_sortgroupclause(srt, query->targetList,
+				get_rule_sortgroupclause(srt->tleSortGroupRef, query->targetList,
 										 false, context);
 				sep = ", ";
 			}
@@ -4560,19 +4562,35 @@ get_basic_select_query(Query *query, deparse_context *context,
 	}
 
 	/* Add the GROUP BY clause if given */
-	if (query->groupClause != NULL)
+	if (query->groupClause != NULL || query->groupingSets != NULL)
 	{
 		appendContextKeyword(context, " GROUP BY ",
 							 -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
-		sep = "";
-		foreach(l, query->groupClause)
+
+		if (query->groupingSets == NIL)
 		{
-			SortGroupClause *grp = (SortGroupClause *) lfirst(l);
+			sep = "";
+			foreach(l, query->groupClause)
+			{
+				SortGroupClause *grp = (SortGroupClause *) lfirst(l);
 
-			appendStringInfoString(buf, sep);
-			get_rule_sortgroupclause(grp, query->targetList,
-									 false, context);
-			sep = ", ";
+				appendStringInfoString(buf, sep);
+				get_rule_sortgroupclause(grp->tleSortGroupRef, query->targetList,
+										 false, context);
+				sep = ", ";
+			}
+		}
+		else
+		{
+			sep = "";
+			foreach(l, query->groupingSets)
+			{
+				GroupingSet *grp = lfirst(l);
+
+				appendStringInfoString(buf, sep);
+				get_rule_groupingset(grp, query->targetList, true, context);
+				sep = ", ";
+			}
 		}
 	}
 
@@ -4640,7 +4658,7 @@ get_target_list(List *targetList, deparse_context *context,
 		 * different from a whole-row Var).  We need to call get_variable
 		 * directly so that we can tell it to do the right thing.
 		 */
-		if (tle->expr && IsA(tle->expr, Var))
+		if (tle->expr && (IsA(tle->expr, Var) || IsA(tle->expr, GroupedVar)))
 		{
 			attname = get_variable((Var *) tle->expr, 0, true, context);
 		}
@@ -4859,14 +4877,14 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
  * Also returns the expression tree, so caller need not find it again.
  */
 static Node *
-get_rule_sortgroupclause(SortGroupClause *srt, List *tlist, bool force_colno,
+get_rule_sortgroupclause(Index ref, List *tlist, bool force_colno,
 						 deparse_context *context)
 {
 	StringInfo	buf = context->buf;
 	TargetEntry *tle;
 	Node	   *expr;
 
-	tle = get_sortgroupclause_tle(srt, tlist);
+	tle = get_sortgroupref_tle(ref, tlist);
 	expr = (Node *) tle->expr;
 
 	/*
@@ -4891,6 +4909,66 @@ get_rule_sortgroupclause(SortGroupClause *srt, List *tlist, bool force_colno,
 }
 
 /*
+ * Display a GroupingSet
+ */
+static void
+get_rule_groupingset(GroupingSet *gset, List *targetlist,
+					 bool omit_parens, deparse_context *context)
+{
+	ListCell   *l;
+	StringInfo	buf = context->buf;
+	bool		omit_child_parens = true;
+	char	   *sep = "";
+
+	switch (gset->kind)
+	{
+		case GROUPING_SET_EMPTY:
+			appendStringInfoString(buf, "()");
+			return;
+
+		case GROUPING_SET_SIMPLE:
+			{
+				if (!omit_parens || list_length(gset->content) != 1)
+					appendStringInfoString(buf, "(");
+
+				foreach(l, gset->content)
+				{
+					Index ref = lfirst_int(l);
+
+					appendStringInfoString(buf, sep);
+					get_rule_sortgroupclause(ref, targetlist,
+											 false, context);
+					sep = ", ";
+				}
+
+				if (!omit_parens || list_length(gset->content) != 1)
+					appendStringInfoString(buf, ")");
+			}
+			return;
+
+		case GROUPING_SET_ROLLUP:
+			appendStringInfoString(buf, "ROLLUP(");
+			break;
+		case GROUPING_SET_CUBE:
+			appendStringInfoString(buf, "CUBE(");
+			break;
+		case GROUPING_SET_SETS:
+			appendStringInfoString(buf, "GROUPING SETS (");
+			omit_child_parens = false;
+			break;
+	}
+
+	foreach(l, gset->content)
+	{
+		appendStringInfoString(buf, sep);
+		get_rule_groupingset(lfirst(l), targetlist, omit_child_parens, context);
+		sep = ", ";
+	}
+
+	appendStringInfoString(buf, ")");
+}
+
+/*
  * Display an ORDER BY list.
  */
 static void
@@ -4910,7 +4988,7 @@ get_rule_orderby(List *orderList, List *targetList,
 		TypeCacheEntry *typentry;
 
 		appendStringInfoString(buf, sep);
-		sortexpr = get_rule_sortgroupclause(srt, targetList,
+		sortexpr = get_rule_sortgroupclause(srt->tleSortGroupRef, targetList,
 											force_colno, context);
 		sortcoltype = exprType(sortexpr);
 		/* See whether operator is default < or > for datatype */
@@ -5010,7 +5088,7 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
 			SortGroupClause *grp = (SortGroupClause *) lfirst(l);
 
 			appendStringInfoString(buf, sep);
-			get_rule_sortgroupclause(grp, targetList,
+			get_rule_sortgroupclause(grp->tleSortGroupRef, targetList,
 									 false, context);
 			sep = ", ";
 		}
@@ -6684,6 +6762,10 @@ get_rule_expr(Node *node, deparse_context *context,
 			(void) get_variable((Var *) node, 0, false, context);
 			break;
 
+		case T_GroupedVar:
+			(void) get_variable((Var *) node, 0, false, context);
+			break;
+
 		case T_Const:
 			get_const_expr((Const *) node, context, 0);
 			break;
@@ -7580,6 +7662,16 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_Grouping:
+			{
+				Grouping *gexpr = (Grouping *) node;
+
+				appendStringInfoString(buf, "GROUPING(");
+				get_rule_expr((Node *) gexpr->args, context, true);
+				appendStringInfoChar(buf, ')');
+			}
+			break;
+
 		case T_List:
 			{
 				char	   *sep;
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index e932ccf..c769e83 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -3158,6 +3158,8 @@ add_unique_group_var(PlannerInfo *root, List *varinfos,
  *	groupExprs - list of expressions being grouped by
  *	input_rows - number of rows estimated to arrive at the group/unique
  *		filter step
+ *  pgset - NULL, or a List** pointing to a grouping set to filter the
+ *      groupExprs against
  *
  * Given the lack of any cross-correlation statistics in the system, it's
  * impossible to do anything really trustworthy with GROUP BY conditions
@@ -3205,11 +3207,13 @@ add_unique_group_var(PlannerInfo *root, List *varinfos,
  * but we don't have the info to do better).
  */
 double
-estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows)
+estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
+					List **pgset)
 {
 	List	   *varinfos = NIL;
 	double		numdistinct;
 	ListCell   *l;
+	int			i;
 
 	/*
 	 * We don't ever want to return an estimate of zero groups, as that tends
@@ -3224,7 +3228,7 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows)
 	 * for normal cases with GROUP BY or DISTINCT, but it is possible for
 	 * corner cases with set operations.)
 	 */
-	if (groupExprs == NIL)
+	if (groupExprs == NIL || (pgset && list_length(*pgset) < 1))
 		return 1.0;
 
 	/*
@@ -3236,6 +3240,7 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows)
 	 */
 	numdistinct = 1.0;
 
+	i = 0;
 	foreach(l, groupExprs)
 	{
 		Node	   *groupexpr = (Node *) lfirst(l);
@@ -3243,6 +3248,10 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows)
 		List	   *varshere;
 		ListCell   *l2;
 
+		/* is expression in this grouping set? */
+		if (pgset && !list_member_int(*pgset, i++))
+			continue;
+
 		/* Short-circuit for expressions returning boolean */
 		if (exprType(groupexpr) == BOOLOID)
 		{
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b271f21..ee1fe74 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -130,6 +130,8 @@ typedef struct ExprContext
 	Datum	   *ecxt_aggvalues; /* precomputed values for aggs/windowfuncs */
 	bool	   *ecxt_aggnulls;	/* null flags for aggs/windowfuncs */
 
+	Bitmapset  *grouped_cols;   /* which columns exist in current grouping set */
+
 	/* Value to substitute for CaseTestExpr nodes in expression */
 	Datum		caseValue_datum;
 	bool		caseValue_isNull;
@@ -911,6 +913,16 @@ typedef struct MinMaxExprState
 } MinMaxExprState;
 
 /* ----------------
+ *		GroupingState node
+ * ----------------
+ */
+typedef struct GroupingState
+{
+	ExprState	xprstate;
+	List        *clauses;
+} GroupingState;
+
+/* ----------------
  *		XmlExprState node
  * ----------------
  */
@@ -1701,19 +1713,26 @@ typedef struct GroupState
 /* these structs are private in nodeAgg.c: */
 typedef struct AggStatePerAggData *AggStatePerAgg;
 typedef struct AggStatePerGroupData *AggStatePerGroup;
+typedef struct AggStatePerGroupingSetData *AggStatePerGroupingSet;
 
 typedef struct AggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
 	List	   *aggs;			/* all Aggref nodes in targetlist & quals */
 	int			numaggs;		/* length of list (could be zero!) */
+	int			numsets;		/* number of grouping sets (or 0) */
 	FmgrInfo   *eqfunctions;	/* per-grouping-field equality fns */
 	FmgrInfo   *hashfunctions;	/* per-grouping-field hash fns */
 	AggStatePerAgg peragg;		/* per-Aggref information */
-	MemoryContext aggcontext;	/* memory context for long-lived data */
+	ExprContext **aggcontext;	/* econtexts for long-lived data */
 	ExprContext *tmpcontext;	/* econtext for input expressions */
 	AggStatePerAgg curperagg;	/* identifies currently active aggregate */
+	bool        input_done;     /* indicates end of input */
 	bool		agg_done;		/* indicates completion of Agg scan */
+	int			projected_set;	/* The last projected grouping set */
+	int			current_set;	/* The current grouping set being evaluated */
+	Bitmapset **grouped_cols;   /* column groupings for rollup */
+	int        *gset_lengths;	/* lengths of grouping sets */
 	/* these fields are used in AGG_PLAIN and AGG_SORTED modes: */
 	AggStatePerGroup pergroup;	/* per-Aggref-per-group working state */
 	HeapTuple	grp_firstTuple; /* copy of first tuple of current group */
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index e108b85..bd3b2a5 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -81,4 +81,6 @@ extern DefElem *makeDefElem(char *name, Node *arg);
 extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 					DefElemAction defaction);
 
+extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
+
 #endif   /* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 067c768..a753809 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -115,6 +115,7 @@ typedef enum NodeTag
 	T_SortState,
 	T_GroupState,
 	T_AggState,
+	T_GroupingState,
 	T_WindowAggState,
 	T_UniqueState,
 	T_HashState,
@@ -171,6 +172,9 @@ typedef enum NodeTag
 	T_JoinExpr,
 	T_FromExpr,
 	T_IntoClause,
+	T_GroupedVar,
+	T_Grouping,
+	T_GroupingSet,
 
 	/*
 	 * TAGS FOR EXPRESSION STATE NODES (execnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8364bef..da33155 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -134,6 +134,8 @@ typedef struct Query
 
 	List	   *groupClause;	/* a list of SortGroupClause's */
 
+	List	   *groupingSets;	/* a list of grouping sets if present */
+
 	Node	   *havingQual;		/* qualifications applied to groups */
 
 	List	   *windowClause;	/* a list of WindowClause's */
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index c545115..45eacda 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -229,8 +229,9 @@ extern List *list_union_int(const List *list1, const List *list2);
 extern List *list_union_oid(const List *list1, const List *list2);
 
 extern List *list_intersection(const List *list1, const List *list2);
+extern List *list_intersection_int(const List *list1, const List *list2);
 
-/* currently, there's no need for list_intersection_int etc */
+/* currently, there's no need for list_intersection_ptr etc */
 
 extern List *list_difference(const List *list1, const List *list2);
 extern List *list_difference_ptr(const List *list1, const List *list2);
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 3b9c683..077ae9f 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -631,6 +631,7 @@ typedef struct Agg
 	AttrNumber *grpColIdx;		/* their indexes in the target list */
 	Oid		   *grpOperators;	/* equality operators to compare with */
 	long		numGroups;		/* estimated number of groups in input */
+	List	   *groupingSets;	/* grouping sets to use */
 } Agg;
 
 /* ----------------
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 6d9f3d9..4dd775e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -159,6 +159,26 @@ typedef struct Var
 	int			location;		/* token location, or -1 if unknown */
 } Var;
 
+/* GroupedVar - expression node representing a grouping set variable.
+ * This is identical to Var node. It is a logical representation of
+ * a grouping set column and is also used during projection of rows
+ * in execution of a query having grouping sets.
+ */
+
+typedef Var GroupedVar;
+
+/*
+ * Grouping
+ */
+typedef struct Grouping
+{
+	Expr xpr;
+	List *args;
+	List *refs;
+	int location;
+	int agglevelsup;
+} Grouping;
+
 /*
  * Const
  */
@@ -1147,6 +1167,32 @@ typedef struct CurrentOfExpr
 	int			cursor_param;	/* refcursor parameter number, or 0 */
 } CurrentOfExpr;
 
+/*
+ * Node representing substructure in GROUPING SETS
+ *
+ * This is not actually executable, but it's used in the raw parsetree
+ * representation of GROUP BY, and in the groupingSets field of Query, to
+ * preserve the original structure of rollup/cube clauses for readability
+ * rather than reducing everything to grouping sets.
+ */
+
+typedef enum
+{
+	GROUPING_SET_EMPTY,
+	GROUPING_SET_SIMPLE,
+	GROUPING_SET_ROLLUP,
+	GROUPING_SET_CUBE,
+	GROUPING_SET_SETS
+} GroupingSetKind;
+
+typedef struct GroupingSet
+{
+	Expr		xpr;
+	GroupingSetKind kind;
+	List	   *content;
+	int			location;
+} GroupingSet;
+
 /*--------------------
  * TargetEntry -
  *	   a target entry (used in query target lists)
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 4504250..64f3aa3 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -58,6 +58,7 @@ extern Sort *make_sort_from_groupcols(PlannerInfo *root, List *groupcls,
 extern Agg *make_agg(PlannerInfo *root, List *tlist, List *qual,
 		 AggStrategy aggstrategy, const AggClauseCosts *aggcosts,
 		 int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
+		 List *groupingSets,
 		 long numGroups,
 		 Plan *lefttree);
 extern WindowAgg *make_windowagg(PlannerInfo *root, List *tlist,
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index 1ebb635..c8b1c93 100644
--- a/src/include/optimizer/tlist.h
+++ b/src/include/optimizer/tlist.h
@@ -43,6 +43,9 @@ extern Node *get_sortgroupclause_expr(SortGroupClause *sgClause,
 extern List *get_sortgrouplist_exprs(List *sgClauses,
 						List *targetList);
 
+extern SortGroupClause *get_sortgroupref_clause(Index sortref,
+					 List *clauses);
+
 extern Oid *extract_grouping_ops(List *groupClause);
 extern AttrNumber *extract_grouping_cols(List *groupClause, List *tlist);
 extern bool grouping_is_sortable(List *groupClause);
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b52e507..98dcea7 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -98,6 +98,7 @@ PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD)
 PG_KEYWORD("create", CREATE, RESERVED_KEYWORD)
 PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("csv", CSV, UNRESERVED_KEYWORD)
+PG_KEYWORD("cube", CUBE, COL_NAME_KEYWORD)
 PG_KEYWORD("current", CURRENT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("current_catalog", CURRENT_CATALOG, RESERVED_KEYWORD)
 PG_KEYWORD("current_date", CURRENT_DATE, RESERVED_KEYWORD)
@@ -173,6 +174,7 @@ PG_KEYWORD("grant", GRANT, RESERVED_KEYWORD)
 PG_KEYWORD("granted", GRANTED, UNRESERVED_KEYWORD)
 PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD)
 PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD)
+PG_KEYWORD("grouping", GROUPING, RESERVED_KEYWORD)
 PG_KEYWORD("handler", HANDLER, UNRESERVED_KEYWORD)
 PG_KEYWORD("having", HAVING, RESERVED_KEYWORD)
 PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD)
@@ -321,6 +323,7 @@ PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD)
 PG_KEYWORD("right", RIGHT, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("role", ROLE, UNRESERVED_KEYWORD)
 PG_KEYWORD("rollback", ROLLBACK, UNRESERVED_KEYWORD)
+PG_KEYWORD("rollup", ROLLUP, COL_NAME_KEYWORD)
 PG_KEYWORD("row", ROW, COL_NAME_KEYWORD)
 PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
 PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
@@ -339,6 +342,7 @@ PG_KEYWORD("session", SESSION, UNRESERVED_KEYWORD)
 PG_KEYWORD("session_user", SESSION_USER, RESERVED_KEYWORD)
 PG_KEYWORD("set", SET, UNRESERVED_KEYWORD)
 PG_KEYWORD("setof", SETOF, COL_NAME_KEYWORD)
+PG_KEYWORD("sets", SETS, UNRESERVED_KEYWORD)
 PG_KEYWORD("share", SHARE, UNRESERVED_KEYWORD)
 PG_KEYWORD("show", SHOW, UNRESERVED_KEYWORD)
 PG_KEYWORD("similar", SIMILAR, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index 3f55ec7..711755b 100644
--- a/src/include/parser/parse_agg.h
+++ b/src/include/parser/parse_agg.h
@@ -18,11 +18,16 @@
 extern void transformAggregateCall(ParseState *pstate, Aggref *agg,
 					   List *args, List *aggorder,
 					   bool agg_distinct);
+
+extern Node *transformGroupingExpr(ParseState *pstate, Grouping *g);
+
 extern void transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 						WindowDef *windef);
 
 extern void parseCheckAggregates(ParseState *pstate, Query *qry);
 
+extern List *expand_grouping_sets(List *groupingSets);
+
 extern int	get_aggregate_argtypes(Aggref *aggref, Oid *inputTypes);
 
 extern Oid resolve_aggregate_transtype(Oid aggfuncid,
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index e9e7cdc..58d88f0 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -27,6 +27,7 @@ extern Node *transformWhereClause(ParseState *pstate, Node *clause,
 extern Node *transformLimitClause(ParseState *pstate, Node *clause,
 					 ParseExprKind exprKind, const char *constructName);
 extern List *transformGroupClause(ParseState *pstate, List *grouplist,
+								  List **groupingSets,
 					 List **targetlist, List *sortClause,
 					 ParseExprKind exprKind, bool useSQL99);
 extern List *transformSortClause(ParseState *pstate, List *orderlist,
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 0f662ec..9d9c9b3 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -185,7 +185,7 @@ extern void mergejoinscansel(PlannerInfo *root, Node *clause,
 				 Selectivity *rightstart, Selectivity *rightend);
 
 extern double estimate_num_groups(PlannerInfo *root, List *groupExprs,
-					double input_rows);
+								  double input_rows, List **pgset);
 
 extern Selectivity estimate_hash_bucketsize(PlannerInfo *root, Node *hashkey,
 						 double nbuckets);
diff --git a/src/test/regress/expected/groupingsets.out b/src/test/regress/expected/groupingsets.out
new file mode 100644
index 0000000..bfbceb8
--- /dev/null
+++ b/src/test/regress/expected/groupingsets.out
@@ -0,0 +1,265 @@
+select a, b from (values (1,2)) v(a,b) group by rollup (a,b);
+ a | b 
+---+---
+ 1 | 2
+ 1 |  
+   |  
+(3 rows)
+
+select a, sum(b) from (values (1,10),(1,20),(2,40)) v(a,b) group by rollup (a);
+ a | sum 
+---+-----
+ 1 |  30
+ 2 |  40
+   |  70
+(3 rows)
+
+select a, b, sum(c) from (values (1,1,10),(1,1,11),(1,2,12),(1,2,13),(1,3,14),(2,3,15),(3,3,16),(3,4,17),(4,1,18),(4,1,19)) v(a,b,c) group by rollup (a,b); 
+ a | b | sum 
+---+---+-----
+ 1 | 1 |  21
+ 1 | 2 |  25
+ 1 | 3 |  14
+ 1 |   |  60
+ 2 | 3 |  15
+ 2 |   |  15
+ 3 | 3 |  16
+ 3 | 4 |  17
+ 3 |   |  33
+ 4 | 1 |  37
+ 4 |   |  37
+   |   | 145
+(12 rows)
+
+select (select grouping(a,b) from (values (1)) v2(b)) from (values (1)) v1(a) group by a;
+ERROR:  Arguments to GROUPING must be grouping expressions of the associated query level
+LINE 1: select (select grouping(a,b) from (values (1)) v2(b)) from (...
+                       ^
+select grouping(p), percentile_disc(p) within group (order by x::float8), array_agg(p)
+from generate_series(1,5) x,
+     (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p)
+group by rollup (p) order by p;
+ grouping | percentile_disc |                                                                                  array_agg                                                                                  
+----------+-----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+        0 |               1 | {0,0,0,0,0}
+        0 |               1 | {0.1,0.1,0.1,0.1,0.1}
+        0 |               2 | {0.25,0.25,0.25,0.25,0.25}
+        0 |               2 | {0.4,0.4,0.4,0.4,0.4}
+        0 |               3 | {0.5,0.5,0.5,0.5,0.5}
+        0 |               3 | {0.6,0.6,0.6,0.6,0.6}
+        0 |               4 | {0.75,0.75,0.75,0.75,0.75}
+        0 |               5 | {0.9,0.9,0.9,0.9,0.9}
+        0 |               5 | {1,1,1,1,1}
+        1 |               5 | {0,0,0,0,0,0.1,0.1,0.1,0.1,0.1,0.25,0.25,0.25,0.25,0.25,0.4,0.4,0.4,0.4,0.4,0.5,0.5,0.5,0.5,0.5,0.6,0.6,0.6,0.6,0.6,0.75,0.75,0.75,0.75,0.75,0.9,0.9,0.9,0.9,0.9,1,1,1,1,1}
+(10 rows)
+
+select a, array_agg(b) from (values (1,10),(1,20),(2,40)) v(a,b) group by rollup (a);
+ a | array_agg  
+---+------------
+ 1 | {10,20}
+ 2 | {40}
+   | {10,20,40}
+(3 rows)
+
+select grouping(a), array_agg(b) from (values (1,10),(1,20),(2,40)) v(a,b) group by rollup (a);
+ grouping | array_agg  
+----------+------------
+        0 | {10,20}
+        0 | {40}
+        1 | {10,20,40}
+(3 rows)
+
+select a, sum(b) from aggtest v(a,b) group by rollup (a);
+  a  |   sum   
+-----+---------
+   0 | 0.09561
+  42 |  324.78
+  56 |     7.8
+ 100 |  99.097
+     | 431.773
+(5 rows)
+
+select grouping(a), sum(b) from aggtest v(a,b) group by rollup (a);
+ grouping |   sum   
+----------+---------
+        0 | 0.09561
+        0 |  324.78
+        0 |     7.8
+        0 |  99.097
+        1 | 431.773
+(5 rows)
+
+select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by a,b;
+ grouping 
+----------
+        0
+(1 row)
+
+SELECT four, ten, SUM(SUM(four)) OVER (PARTITION BY four), AVG(ten) FROM tenk1
+GROUP BY ROLLUP(four, ten) ORDER BY four, ten;
+ four | ten |  sum  |          avg           
+------+-----+-------+------------------------
+    0 |   0 |     0 | 0.00000000000000000000
+    0 |   2 |     0 |     2.0000000000000000
+    0 |   4 |     0 |     4.0000000000000000
+    0 |   6 |     0 |     6.0000000000000000
+    0 |   8 |     0 |     8.0000000000000000
+    0 |     |     0 |     4.0000000000000000
+    1 |   1 |  5000 | 1.00000000000000000000
+    1 |   3 |  5000 |     3.0000000000000000
+    1 |   5 |  5000 |     5.0000000000000000
+    1 |   7 |  5000 |     7.0000000000000000
+    1 |   9 |  5000 |     9.0000000000000000
+    1 |     |  5000 |     5.0000000000000000
+    2 |   0 | 10000 | 0.00000000000000000000
+    2 |   2 | 10000 |     2.0000000000000000
+    2 |   4 | 10000 |     4.0000000000000000
+    2 |   6 | 10000 |     6.0000000000000000
+    2 |   8 | 10000 |     8.0000000000000000
+    2 |     | 10000 |     4.0000000000000000
+    3 |   1 | 15000 | 1.00000000000000000000
+    3 |   3 | 15000 |     3.0000000000000000
+    3 |   5 | 15000 |     5.0000000000000000
+    3 |   7 | 15000 |     7.0000000000000000
+    3 |   9 | 15000 |     9.0000000000000000
+    3 |     | 15000 |     5.0000000000000000
+      |     | 15000 |     4.5000000000000000
+(25 rows)
+
+select a, b from (values (1,2),(2,3)) v(a,b) group by grouping sets((a,b),());
+ a | b 
+---+---
+ 1 | 2
+ 2 | 3
+   |  
+(3 rows)
+
+select a, b from (values (1,2),(2,3)) v(a,b) group by rollup((a,b));
+ a | b 
+---+---
+ 1 | 2
+ 2 | 3
+   |  
+(3 rows)
+
+select a, b, sum(c) from (values (1,1,10,5),(1,1,11,5),(1,2,12,5),(1,2,13,5),(1,3,14,5),(2,3,15,5),(3,3,16,5),(3,4,17,5),(4,1,18,5),(4,1,19,5)) v(a,b,c,d) group by rollup ((a,b));
+ a | b | sum 
+---+---+-----
+ 1 | 1 |  21
+ 1 | 2 |  25
+ 1 | 3 |  14
+ 2 | 3 |  15
+ 3 | 3 |  16
+ 3 | 4 |  17
+ 4 | 1 |  37
+   |   | 145
+(8 rows)
+
+create temp view tv2(a,b,c,d,e,f,g) as select a[1], a[2], a[3], a[4], a[5], a[6], generate_series(1,3) from (select (array[1,1,1,1,1,1])[1:6-i] || (array[2,2,2,2,2,2])[7-i:6] as a from generate_series(0,6) i) s;
+select a,b, sum(g) from tv2 group by grouping sets ((a,b,c),(a,b));
+ a | b | sum 
+---+---+-----
+ 1 | 1 |  24
+ 1 | 1 |   6
+ 1 | 1 |  30
+ 1 | 2 |   6
+ 1 | 2 |   6
+ 2 | 2 |   6
+ 2 | 2 |   6
+(7 rows)
+
+SELECT grouping(onek.four),grouping(tenk1.four) FROM onek,tenk1 GROUP BY ROLLUP(onek.four,tenk1.four);
+ grouping | grouping 
+----------+----------
+        0 |        0
+        0 |        0
+        0 |        0
+        0 |        0
+        0 |        1
+        0 |        0
+        0 |        0
+        0 |        0
+        0 |        0
+        0 |        1
+        0 |        0
+        0 |        0
+        0 |        0
+        0 |        0
+        0 |        1
+        0 |        0
+        0 |        0
+        0 |        0
+        0 |        0
+        0 |        1
+        1 |        1
+(21 rows)
+
+CREATE TEMP TABLE testgs_emptytable(a int,b int,c int);
+SELECT sum(a) FROM testgs_emptytable GROUP BY ROLLUP(a,b);
+ sum 
+-----
+    
+(1 row)
+
+SELECT grouping(four), ten FROM tenk1
+GROUP BY ROLLUP(four, ten) ORDER BY four, ten;
+ grouping | ten 
+----------+-----
+        0 |   0
+        0 |   2
+        0 |   4
+        0 |   6
+        0 |   8
+        0 |    
+        0 |   1
+        0 |   3
+        0 |   5
+        0 |   7
+        0 |   9
+        0 |    
+        0 |   0
+        0 |   2
+        0 |   4
+        0 |   6
+        0 |   8
+        0 |    
+        0 |   1
+        0 |   3
+        0 |   5
+        0 |   7
+        0 |   9
+        0 |    
+        1 |    
+(25 rows)
+
+SELECT grouping(four), ten FROM tenk1
+GROUP BY ROLLUP(four, ten) ORDER BY ten;
+ grouping | ten 
+----------+-----
+        0 |   0
+        0 |   0
+        0 |   1
+        0 |   1
+        0 |   2
+        0 |   2
+        0 |   3
+        0 |   3
+        0 |   4
+        0 |   4
+        0 |   5
+        0 |   5
+        0 |   6
+        0 |   6
+        0 |   7
+        0 |   7
+        0 |   8
+        0 |   8
+        0 |   9
+        0 |   9
+        0 |    
+        0 |    
+        0 |    
+        0 |    
+        1 |    
+(25 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index c0416f4..b15119e 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: privileges security_label collate matview lock replica_identity
+test: privileges security_label collate matview lock replica_identity groupingsets
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 16a1905..5e64468 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -84,6 +84,7 @@ test: union
 test: case
 test: join
 test: aggregates
+test: groupingsets
 test: transactions
 ignore: random
 test: random
diff --git a/src/test/regress/sql/groupingsets.sql b/src/test/regress/sql/groupingsets.sql
new file mode 100644
index 0000000..c659c8a
--- /dev/null
+++ b/src/test/regress/sql/groupingsets.sql
@@ -0,0 +1,49 @@
+select a, b from (values (1,2)) v(a,b) group by rollup (a,b);
+
+select a, sum(b) from (values (1,10),(1,20),(2,40)) v(a,b) group by rollup (a);
+
+select a, b, sum(c) from (values (1,1,10),(1,1,11),(1,2,12),(1,2,13),(1,3,14),(2,3,15),(3,3,16),(3,4,17),(4,1,18),(4,1,19)) v(a,b,c) group by rollup (a,b); 
+
+select (select grouping(a,b) from (values (1)) v2(b)) from (values (1)) v1(a) group by a;
+
+select grouping(p), percentile_disc(p) within group (order by x::float8), array_agg(p)
+from generate_series(1,5) x,
+     (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p)
+group by rollup (p) order by p;
+
+select a, array_agg(b) from (values (1,10),(1,20),(2,40)) v(a,b) group by rollup (a);
+
+select grouping(a), array_agg(b) from (values (1,10),(1,20),(2,40)) v(a,b) group by rollup (a);
+
+select a, sum(b) from aggtest v(a,b) group by rollup (a);
+
+select grouping(a), sum(b) from aggtest v(a,b) group by rollup (a);
+
+select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by a,b;
+
+SELECT four, ten, SUM(SUM(four)) OVER (PARTITION BY four), AVG(ten) FROM tenk1
+GROUP BY ROLLUP(four, ten) ORDER BY four, ten;
+
+select a, b from (values (1,2),(2,3)) v(a,b) group by grouping sets((a,b),());
+
+select a, b from (values (1,2),(2,3)) v(a,b) group by rollup((a,b));
+
+select a, b, sum(c) from (values (1,1,10,5),(1,1,11,5),(1,2,12,5),(1,2,13,5),(1,3,14,5),(2,3,15,5),(3,3,16,5),(3,4,17,5),(4,1,18,5),(4,1,19,5)) v(a,b,c,d) group by rollup ((a,b));
+
+create temp view tv2(a,b,c,d,e,f,g) as select a[1], a[2], a[3], a[4], a[5], a[6], generate_series(1,3) from (select (array[1,1,1,1,1,1])[1:6-i] || (array[2,2,2,2,2,2])[7-i:6] as a from generate_series(0,6) i) s;
+
+select a,b, sum(g) from tv2 group by grouping sets ((a,b,c),(a,b));
+
+SELECT grouping(onek.four),grouping(tenk1.four) FROM onek,tenk1 GROUP BY ROLLUP(onek.four,tenk1.four);
+
+CREATE TEMP TABLE testgs_emptytable(a int,b int,c int);
+
+SELECT sum(a) FROM testgs_emptytable GROUP BY ROLLUP(a,b);
+
+SELECT grouping(four), ten FROM tenk1
+GROUP BY ROLLUP(four, ten) ORDER BY four, ten;
+
+SELECT grouping(four), ten FROM tenk1
+GROUP BY ROLLUP(four, ten) ORDER BY ten;
+
+
