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 group_by_list grouping_set_list +%type group_by_item empty_grouping_set rollup_clause cube_clause +%type grouping_sets_clause grouping_set + %type opt_fdw_options fdw_options %type fdw_option @@ -425,7 +429,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type ExclusionConstraintList ExclusionConstraintElem %type func_arg_list %type func_arg_expr -%type row type_list array_expr_list +%type row explicit_row implicit_row type_list array_expr_list %type case_expr case_arg when_clause case_default %type when_clause_list %type 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 + * , 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; + +