aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/executor/execExpr.c52
-rw-r--r--src/backend/executor/execExprInterp.c102
-rw-r--r--src/backend/executor/nodeAgg.c34
-rw-r--r--src/backend/jit/llvm/llvmjit_expr.c48
-rw-r--r--src/backend/jit/llvm/llvmjit_types.c2
-rw-r--r--src/backend/optimizer/path/pathkeys.c45
-rw-r--r--src/backend/optimizer/plan/planagg.c2
-rw-r--r--src/backend/optimizer/plan/planner.c310
-rw-r--r--src/backend/optimizer/prep/prepagg.c7
-rw-r--r--src/backend/parser/parse_expr.c1
-rw-r--r--src/backend/parser/parse_func.c1
11 files changed, 566 insertions, 38 deletions
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index c8d7145fe38..d0a57c7aaee 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -3666,13 +3666,17 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
scratch.resnull = &state->resnull;
}
argno++;
+
+ Assert(pertrans->numInputs == argno);
}
- else if (pertrans->numSortCols == 0)
+ else if (!pertrans->aggsortrequired)
{
ListCell *arg;
/*
- * Normal transition function without ORDER BY / DISTINCT.
+ * Normal transition function without ORDER BY / DISTINCT or with
+ * ORDER BY / DISTINCT but the planner has given us pre-sorted
+ * input.
*/
strictargs = trans_fcinfo->args + 1;
@@ -3681,6 +3685,13 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
TargetEntry *source_tle = (TargetEntry *) lfirst(arg);
/*
+ * Don't initialize args for any ORDER BY clause that might
+ * exist in a presorted aggregate.
+ */
+ if (argno == pertrans->numTransInputs)
+ break;
+
+ /*
* Start from 1, since the 0th arg will be the transition
* value
*/
@@ -3689,11 +3700,13 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
&trans_fcinfo->args[argno + 1].isnull);
argno++;
}
+ Assert(pertrans->numTransInputs == argno);
}
else if (pertrans->numInputs == 1)
{
/*
- * DISTINCT and/or ORDER BY case, with a single column sorted on.
+ * Non-presorted DISTINCT and/or ORDER BY case, with a single
+ * column sorted on.
*/
TargetEntry *source_tle =
(TargetEntry *) linitial(pertrans->aggref->args);
@@ -3705,11 +3718,14 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
&state->resnull);
strictnulls = &state->resnull;
argno++;
+
+ Assert(pertrans->numInputs == argno);
}
else
{
/*
- * DISTINCT and/or ORDER BY case, with multiple columns sorted on.
+ * Non-presorted DISTINCT and/or ORDER BY case, with multiple
+ * columns sorted on.
*/
Datum *values = pertrans->sortslot->tts_values;
bool *nulls = pertrans->sortslot->tts_isnull;
@@ -3725,8 +3741,8 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
&values[argno], &nulls[argno]);
argno++;
}
+ Assert(pertrans->numInputs == argno);
}
- Assert(pertrans->numInputs == argno);
/*
* For a strict transfn, nothing happens when there's a NULL input; we
@@ -3748,6 +3764,21 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
state->steps_len - 1);
}
+ /* Handle DISTINCT aggregates which have pre-sorted input */
+ if (pertrans->numDistinctCols > 0 && !pertrans->aggsortrequired)
+ {
+ if (pertrans->numDistinctCols > 1)
+ scratch.opcode = EEOP_AGG_PRESORTED_DISTINCT_MULTI;
+ else
+ scratch.opcode = EEOP_AGG_PRESORTED_DISTINCT_SINGLE;
+
+ scratch.d.agg_presorted_distinctcheck.pertrans = pertrans;
+ scratch.d.agg_presorted_distinctcheck.jumpdistinct = -1; /* adjust later */
+ ExprEvalPushStep(state, &scratch);
+ adjust_bailout = lappend_int(adjust_bailout,
+ state->steps_len - 1);
+ }
+
/*
* Call transition function (once for each concurrently evaluated
* grouping set). Do so for both sort and hash based computations, as
@@ -3808,6 +3839,12 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
Assert(as->d.agg_deserialize.jumpnull == -1);
as->d.agg_deserialize.jumpnull = state->steps_len;
}
+ else if (as->opcode == EEOP_AGG_PRESORTED_DISTINCT_SINGLE ||
+ as->opcode == EEOP_AGG_PRESORTED_DISTINCT_MULTI)
+ {
+ Assert(as->d.agg_presorted_distinctcheck.jumpdistinct == -1);
+ as->d.agg_presorted_distinctcheck.jumpdistinct = state->steps_len;
+ }
else
Assert(false);
}
@@ -3857,7 +3894,8 @@ ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
/*
* Determine appropriate transition implementation.
*
- * For non-ordered aggregates:
+ * For non-ordered aggregates and ORDER BY / DISTINCT aggregates with
+ * presorted input:
*
* 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
@@ -3887,7 +3925,7 @@ ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
* process_ordered_aggregate_{single, multi} and
* advance_transition_function.
*/
- if (pertrans->numSortCols == 0)
+ if (!pertrans->aggsortrequired)
{
if (pertrans->transtypeByVal)
{
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 723770fda0e..636794ca6f1 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -502,6 +502,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYREF,
&&CASE_EEOP_AGG_PLAIN_TRANS_STRICT_BYREF,
&&CASE_EEOP_AGG_PLAIN_TRANS_BYREF,
+ &&CASE_EEOP_AGG_PRESORTED_DISTINCT_SINGLE,
+ &&CASE_EEOP_AGG_PRESORTED_DISTINCT_MULTI,
&&CASE_EEOP_AGG_ORDERED_TRANS_DATUM,
&&CASE_EEOP_AGG_ORDERED_TRANS_TUPLE,
&&CASE_EEOP_LAST
@@ -1786,6 +1788,28 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_AGG_PRESORTED_DISTINCT_SINGLE)
+ {
+ AggStatePerTrans pertrans = op->d.agg_presorted_distinctcheck.pertrans;
+ AggState *aggstate = castNode(AggState, state->parent);
+
+ if (ExecEvalPreOrderedDistinctSingle(aggstate, pertrans))
+ EEO_NEXT();
+ else
+ EEO_JUMP(op->d.agg_presorted_distinctcheck.jumpdistinct);
+ }
+
+ EEO_CASE(EEOP_AGG_PRESORTED_DISTINCT_MULTI)
+ {
+ AggState *aggstate = castNode(AggState, state->parent);
+ AggStatePerTrans pertrans = op->d.agg_presorted_distinctcheck.pertrans;
+
+ if (ExecEvalPreOrderedDistinctMulti(aggstate, pertrans))
+ EEO_NEXT();
+ else
+ EEO_JUMP(op->d.agg_presorted_distinctcheck.jumpdistinct);
+ }
+
/* process single-column ordered aggregate datum */
EEO_CASE(EEOP_AGG_ORDERED_TRANS_DATUM)
{
@@ -4403,6 +4427,84 @@ ExecAggTransReparent(AggState *aggstate, AggStatePerTrans pertrans,
}
/*
+ * ExecEvalPreOrderedDistinctSingle
+ * Returns true when the aggregate transition value Datum is distinct
+ * from the previous input Datum and returns false when the input Datum
+ * matches the previous input Datum.
+ */
+bool
+ExecEvalPreOrderedDistinctSingle(AggState *aggstate, AggStatePerTrans pertrans)
+{
+ Datum value = pertrans->transfn_fcinfo->args[1].value;
+ bool isnull = pertrans->transfn_fcinfo->args[1].isnull;
+
+ if (!pertrans->haslast ||
+ pertrans->lastisnull != isnull ||
+ !DatumGetBool(FunctionCall2Coll(&pertrans->equalfnOne,
+ pertrans->aggCollation,
+ pertrans->lastdatum, value)))
+ {
+ if (pertrans->haslast && !pertrans->inputtypeByVal)
+ pfree(DatumGetPointer(pertrans->lastdatum));
+
+ pertrans->haslast = true;
+ if (!isnull)
+ {
+ MemoryContext oldContext;
+
+ oldContext = MemoryContextSwitchTo(aggstate->curaggcontext->ecxt_per_tuple_memory);
+
+ pertrans->lastdatum = datumCopy(value, pertrans->inputtypeByVal,
+ pertrans->inputtypeLen);
+
+ MemoryContextSwitchTo(oldContext);
+ }
+ else
+ pertrans->lastdatum = (Datum) 0;
+ pertrans->lastisnull = isnull;
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * ExecEvalPreOrderedDistinctMulti
+ * Returns true when the aggregate input is distinct from the previous
+ * input and returns false when the input matches the previous input.
+ */
+bool
+ExecEvalPreOrderedDistinctMulti(AggState *aggstate, AggStatePerTrans pertrans)
+{
+ ExprContext *tmpcontext = aggstate->tmpcontext;
+
+ for (int i = 0; i < pertrans->numTransInputs; i++)
+ {
+ pertrans->sortslot->tts_values[i] = pertrans->transfn_fcinfo->args[i + 1].value;
+ pertrans->sortslot->tts_isnull[i] = pertrans->transfn_fcinfo->args[i + 1].isnull;
+ }
+
+ ExecClearTuple(pertrans->sortslot);
+ pertrans->sortslot->tts_nvalid = pertrans->numInputs;
+ ExecStoreVirtualTuple(pertrans->sortslot);
+
+ tmpcontext->ecxt_outertuple = pertrans->sortslot;
+ tmpcontext->ecxt_innertuple = pertrans->uniqslot;
+
+ if (!pertrans->haslast ||
+ !ExecQual(pertrans->equalfnMulti, tmpcontext))
+ {
+ if (pertrans->haslast)
+ ExecClearTuple(pertrans->uniqslot);
+
+ pertrans->haslast = true;
+ ExecCopySlot(pertrans->uniqslot, pertrans->sortslot);
+ return true;
+ }
+ return false;
+}
+
+/*
* Invoke ordered transition function, with a datum argument.
*/
void
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 2fc606cf29d..96d200e4461 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -583,7 +583,7 @@ initialize_aggregate(AggState *aggstate, AggStatePerTrans pertrans,
/*
* Start a fresh sort operation for each DISTINCT/ORDER BY aggregate.
*/
- if (pertrans->numSortCols > 0)
+ if (pertrans->aggsortrequired)
{
/*
* In case of rescan, maybe there could be an uncompleted sort
@@ -1309,7 +1309,7 @@ finalize_aggregates(AggState *aggstate,
pergroupstate = &pergroup[transno];
- if (pertrans->numSortCols > 0)
+ if (pertrans->aggsortrequired)
{
Assert(aggstate->aggstrategy != AGG_HASHED &&
aggstate->aggstrategy != AGG_MIXED);
@@ -1323,6 +1323,21 @@ finalize_aggregates(AggState *aggstate,
pertrans,
pergroupstate);
}
+ else if (pertrans->numDistinctCols > 0 && pertrans->haslast)
+ {
+ pertrans->haslast = false;
+
+ if (pertrans->numDistinctCols == 1)
+ {
+ if (!pertrans->inputtypeByVal && !pertrans->lastisnull)
+ pfree(DatumGetPointer(pertrans->lastdatum));
+
+ pertrans->lastisnull = false;
+ pertrans->lastdatum = (Datum) 0;
+ }
+ else
+ ExecClearTuple(pertrans->uniqslot);
+ }
}
/*
@@ -4127,6 +4142,12 @@ build_pertrans_for_aggref(AggStatePerTrans pertrans,
* stick them into arrays. We ignore ORDER BY for an ordered-set agg,
* however; the agg's transfn and finalfn are responsible for that.
*
+ * When the planner has set the aggpresorted flag, the input to the
+ * aggregate is already correctly sorted. For ORDER BY aggregates we can
+ * simply treat these as normal aggregates. For presorted DISTINCT
+ * aggregates an extra step must be added to remove duplicate consecutive
+ * inputs.
+ *
* Note that by construction, if there is a DISTINCT clause then the ORDER
* BY clause is a prefix of it (see transformDistinctClause).
*/
@@ -4134,18 +4155,27 @@ build_pertrans_for_aggref(AggStatePerTrans pertrans,
{
sortlist = NIL;
numSortCols = numDistinctCols = 0;
+ pertrans->aggsortrequired = false;
+ }
+ else if (aggref->aggpresorted && aggref->aggdistinct == NIL)
+ {
+ sortlist = NIL;
+ numSortCols = numDistinctCols = 0;
+ pertrans->aggsortrequired = false;
}
else if (aggref->aggdistinct)
{
sortlist = aggref->aggdistinct;
numSortCols = numDistinctCols = list_length(sortlist);
Assert(numSortCols >= list_length(aggref->aggorder));
+ pertrans->aggsortrequired = !aggref->aggpresorted;
}
else
{
sortlist = aggref->aggorder;
numSortCols = list_length(sortlist);
numDistinctCols = 0;
+ pertrans->aggsortrequired = (numSortCols > 0);
}
pertrans->numSortCols = numSortCols;
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index b6b6512ef1f..bd3965143da 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -2335,6 +2335,54 @@ llvm_compile_expr(ExprState *state)
break;
}
+ case EEOP_AGG_PRESORTED_DISTINCT_SINGLE:
+ {
+ AggState *aggstate = castNode(AggState, state->parent);
+ AggStatePerTrans pertrans = op->d.agg_presorted_distinctcheck.pertrans;
+ int jumpdistinct = op->d.agg_presorted_distinctcheck.jumpdistinct;
+
+ LLVMValueRef v_fn = llvm_pg_func(mod, "ExecEvalPreOrderedDistinctSingle");
+ LLVMValueRef v_args[2];
+ LLVMValueRef v_ret;
+
+ v_args[0] = l_ptr_const(aggstate, l_ptr(StructAggState));
+ v_args[1] = l_ptr_const(pertrans, l_ptr(StructAggStatePerTransData));
+
+ v_ret = LLVMBuildCall(b, v_fn, v_args, 2, "");
+ v_ret = LLVMBuildZExt(b, v_ret, TypeStorageBool, "");
+
+ LLVMBuildCondBr(b,
+ LLVMBuildICmp(b, LLVMIntEQ, v_ret,
+ l_sbool_const(1), ""),
+ opblocks[opno + 1],
+ opblocks[jumpdistinct]);
+ break;
+ }
+
+ case EEOP_AGG_PRESORTED_DISTINCT_MULTI:
+ {
+ AggState *aggstate = castNode(AggState, state->parent);
+ AggStatePerTrans pertrans = op->d.agg_presorted_distinctcheck.pertrans;
+ int jumpdistinct = op->d.agg_presorted_distinctcheck.jumpdistinct;
+
+ LLVMValueRef v_fn = llvm_pg_func(mod, "ExecEvalPreOrderedDistinctMulti");
+ LLVMValueRef v_args[2];
+ LLVMValueRef v_ret;
+
+ v_args[0] = l_ptr_const(aggstate, l_ptr(StructAggState));
+ v_args[1] = l_ptr_const(pertrans, l_ptr(StructAggStatePerTransData));
+
+ v_ret = LLVMBuildCall(b, v_fn, v_args, 2, "");
+ v_ret = LLVMBuildZExt(b, v_ret, TypeStorageBool, "");
+
+ LLVMBuildCondBr(b,
+ LLVMBuildICmp(b, LLVMIntEQ, v_ret,
+ l_sbool_const(1), ""),
+ opblocks[opno + 1],
+ opblocks[jumpdistinct]);
+ break;
+ }
+
case EEOP_AGG_ORDERED_TRANS_DATUM:
build_EvalXFunc(b, mod, "ExecEvalAggOrderedTransDatum",
v_state, op, v_econtext);
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index b2bda868893..373471ad27f 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -103,6 +103,8 @@ void *referenced_functions[] =
{
ExecAggInitGroup,
ExecAggTransReparent,
+ ExecEvalPreOrderedDistinctSingle,
+ ExecEvalPreOrderedDistinctMulti,
ExecEvalAggOrderedTransDatum,
ExecEvalAggOrderedTransTuple,
ExecEvalArrayCoerce,
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 2a1c923b4a8..1fa7fc99b51 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -104,6 +104,28 @@ make_canonical_pathkey(PlannerInfo *root,
}
/*
+ * append_pathkeys
+ * Append all non-redundant PathKeys in 'source' onto 'target' and
+ * returns the updated 'target' list.
+ */
+List *
+append_pathkeys(List *target, List *source)
+{
+ ListCell *lc;
+
+ Assert(target != NIL);
+
+ foreach(lc, source)
+ {
+ PathKey *pk = lfirst_node(PathKey, lc);
+
+ if (!pathkey_is_redundant(pk, target))
+ target = lappend(target, pk);
+ }
+ return target;
+}
+
+/*
* pathkey_is_redundant
* Is a pathkey redundant with one already in the given list?
*
@@ -746,7 +768,8 @@ get_cheapest_group_keys_order(PlannerInfo *root, double nrows,
List *
get_useful_group_keys_orderings(PlannerInfo *root, double nrows,
List *path_pathkeys,
- List *group_pathkeys, List *group_clauses)
+ List *group_pathkeys, List *group_clauses,
+ List *aggregate_pathkeys)
{
Query *parse = root->parse;
List *infos = NIL;
@@ -758,7 +781,10 @@ get_useful_group_keys_orderings(PlannerInfo *root, double nrows,
/* always return at least the original pathkeys/clauses */
info = makeNode(PathKeyInfo);
- info->pathkeys = pathkeys;
+ if (aggregate_pathkeys != NIL)
+ info->pathkeys = list_concat_copy(pathkeys, aggregate_pathkeys);
+ else
+ info->pathkeys = pathkeys;
info->clauses = clauses;
infos = lappend(infos, info);
@@ -783,7 +809,10 @@ get_useful_group_keys_orderings(PlannerInfo *root, double nrows,
n_preordered))
{
info = makeNode(PathKeyInfo);
- info->pathkeys = pathkeys;
+ if (aggregate_pathkeys != NIL)
+ info->pathkeys = list_concat_copy(pathkeys, aggregate_pathkeys);
+ else
+ info->pathkeys = pathkeys;
info->clauses = clauses;
infos = lappend(infos, info);
@@ -822,7 +851,10 @@ get_useful_group_keys_orderings(PlannerInfo *root, double nrows,
* still want to keep the keys reordered to path_pathkeys.
*/
info = makeNode(PathKeyInfo);
- info->pathkeys = pathkeys;
+ if (aggregate_pathkeys != NIL)
+ info->pathkeys = list_concat_copy(pathkeys, aggregate_pathkeys);
+ else
+ info->pathkeys = pathkeys;
info->clauses = clauses;
infos = lappend(infos, info);
@@ -850,7 +882,10 @@ get_useful_group_keys_orderings(PlannerInfo *root, double nrows,
/* keep the group keys reordered to match ordering of input path */
info = makeNode(PathKeyInfo);
- info->pathkeys = pathkeys;
+ if (aggregate_pathkeys != NIL)
+ info->pathkeys = list_concat_copy(pathkeys, aggregate_pathkeys);
+ else
+ info->pathkeys = pathkeys;
info->clauses = clauses;
infos = lappend(infos, info);
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index e0e357960f2..ab3f7abba19 100644
--- a/src/backend/optimizer/plan/planagg.c
+++ b/src/backend/optimizer/plan/planagg.c
@@ -245,7 +245,7 @@ can_minmax_aggs(PlannerInfo *root, List **context)
foreach(lc, root->agginfos)
{
AggInfo *agginfo = lfirst_node(AggInfo, lc);
- Aggref *aggref = agginfo->representative_aggref;
+ Aggref *aggref = linitial_node(Aggref, agginfo->aggrefs);
Oid aggsortop;
TargetEntry *curTarget;
MinMaxAggInfo *mminfo;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 06ad856eac1..64632db73cd 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -24,6 +24,7 @@
#include "access/sysattr.h"
#include "access/table.h"
#include "access/xact.h"
+#include "catalog/pg_aggregate.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_proc.h"
@@ -3126,6 +3127,217 @@ reorder_grouping_sets(List *groupingsets, List *sortclause)
}
/*
+ * make_pathkeys_for_groupagg
+ * Determine the pathkeys for the GROUP BY clause and/or any ordered
+ * aggregates. We expect at least one of these here.
+ *
+ * Building the pathkeys for the GROUP BY is simple. Most of the complexity
+ * involved here comes from calculating the best pathkeys for ordered
+ * aggregates. We define "best" as the pathkeys that suit the most number of
+ * aggregate functions. We find these by looking at the first ORDER BY /
+ * DISTINCT aggregate and take the pathkeys for that before searching for
+ * other aggregates that require the same or a more strict variation of the
+ * same pathkeys. We then repeat that process for any remaining aggregates
+ * with different pathkeys and if we find another set of pathkeys that suits a
+ * larger number of aggregates then we return those pathkeys instead.
+ *
+ * *number_groupby_pathkeys gets set to the number of elements in the returned
+ * list that belong to the GROUP BY clause. Any elements above this number
+ * must belong to ORDER BY / DISTINCT aggregates.
+ *
+ * When the best pathkeys are found we also mark each Aggref that can use
+ * those pathkeys as aggpresorted = true.
+ */
+static List *
+make_pathkeys_for_groupagg(PlannerInfo *root, List *groupClause, List *tlist,
+ int *number_groupby_pathkeys)
+{
+ List *grouppathkeys = NIL;
+ List *bestpathkeys;
+ Bitmapset *bestaggs;
+ Bitmapset *unprocessed_aggs;
+ ListCell *lc;
+ int i;
+
+ Assert(groupClause != NIL || root->numOrderedAggs > 0);
+
+ if (groupClause != NIL)
+ {
+ /* no pathkeys possible if there's an unsortable GROUP BY */
+ if (!grouping_is_sortable(groupClause))
+ {
+ *number_groupby_pathkeys = 0;
+ return NIL;
+ }
+
+ grouppathkeys = make_pathkeys_for_sortclauses(root, groupClause,
+ tlist);
+ *number_groupby_pathkeys = list_length(grouppathkeys);
+ }
+ else
+ *number_groupby_pathkeys = 0;
+
+ /*
+ * We can't add pathkeys for ordered aggregates if there are any grouping
+ * sets. All handling specific to ordered aggregates must be done by the
+ * executor in that case.
+ */
+ if (root->numOrderedAggs == 0 || root->parse->groupingSets != NIL)
+ return grouppathkeys;
+
+ /*
+ * Make a first pass over all AggInfos to collect a Bitmapset containing
+ * the indexes of all AggInfos to be processed below.
+ */
+ unprocessed_aggs = NULL;
+ foreach(lc, root->agginfos)
+ {
+ AggInfo *agginfo = lfirst_node(AggInfo, lc);
+ Aggref *aggref = linitial_node(Aggref, agginfo->aggrefs);
+
+ if (AGGKIND_IS_ORDERED_SET(aggref->aggkind))
+ continue;
+
+ /* only add aggregates with a DISTINCT or ORDER BY */
+ if (aggref->aggdistinct != NIL || aggref->aggorder != NIL)
+ unprocessed_aggs = bms_add_member(unprocessed_aggs,
+ foreach_current_index(lc));
+ }
+
+ /*
+ * Now process all the unprocessed_aggs to find the best set of pathkeys
+ * for the given set of aggregates.
+ *
+ * On the first outer loop here 'bestaggs' will be empty. We'll populate
+ * this during the first loop using the pathkeys for the very first
+ * AggInfo then taking any stronger pathkeys from any other AggInfos with
+ * a more strict set of compatible pathkeys. Once the outer loop is
+ * complete, we mark off all the aggregates with compatible pathkeys then
+ * remove those from the unprocessed_aggs and repeat the process to try to
+ * find another set of pathkeys that are suitable for a larger number of
+ * aggregates. The outer loop will stop when there are not enough
+ * unprocessed aggregates for it to be possible to find a set of pathkeys
+ * to suit a larger number of aggregates.
+ */
+ bestpathkeys = NIL;
+ bestaggs = NULL;
+ while (bms_num_members(unprocessed_aggs) > bms_num_members(bestaggs))
+ {
+ Bitmapset *aggindexes = NULL;
+ List *currpathkeys = NIL;
+
+ i = -1;
+ while ((i = bms_next_member(unprocessed_aggs, i)) >= 0)
+ {
+ AggInfo *agginfo = list_nth_node(AggInfo, root->agginfos, i);
+ Aggref *aggref = linitial_node(Aggref, agginfo->aggrefs);
+ List *sortlist;
+
+ if (aggref->aggdistinct != NIL)
+ sortlist = aggref->aggdistinct;
+ else
+ sortlist = aggref->aggorder;
+
+ /*
+ * When not set yet, take the pathkeys from the first unprocessed
+ * aggregate.
+ */
+ if (currpathkeys == NIL)
+ {
+ currpathkeys = make_pathkeys_for_sortclauses(root, sortlist,
+ aggref->args);
+
+ /* include the GROUP BY pathkeys, if they exist */
+ if (grouppathkeys != NIL)
+ currpathkeys = append_pathkeys(list_copy(grouppathkeys),
+ currpathkeys);
+
+ /* record that we found pathkeys for this aggregate */
+ aggindexes = bms_add_member(aggindexes, i);
+ }
+ else
+ {
+ List *pathkeys;
+
+ /* now look for a stronger set of matching pathkeys */
+ pathkeys = make_pathkeys_for_sortclauses(root, sortlist,
+ aggref->args);
+
+ /* include the GROUP BY pathkeys, if they exist */
+ if (grouppathkeys != NIL)
+ pathkeys = append_pathkeys(list_copy(grouppathkeys),
+ pathkeys);
+
+ /* are 'pathkeys' compatible or better than 'currpathkeys'? */
+ switch (compare_pathkeys(currpathkeys, pathkeys))
+ {
+ case PATHKEYS_BETTER2:
+ /* 'pathkeys' are stronger, use these ones instead */
+ currpathkeys = pathkeys;
+ /* FALLTHROUGH */
+
+ case PATHKEYS_BETTER1:
+ /* 'pathkeys' are less strict */
+ /* FALLTHROUGH */
+
+ case PATHKEYS_EQUAL:
+ /* mark this aggregate as covered by 'currpathkeys' */
+ aggindexes = bms_add_member(aggindexes, i);
+ break;
+
+ case PATHKEYS_DIFFERENT:
+ break;
+ }
+ }
+ }
+
+ /* remove the aggregates that we've just processed */
+ unprocessed_aggs = bms_del_members(unprocessed_aggs, aggindexes);
+
+ /*
+ * If this pass included more aggregates than the previous best then
+ * use these ones as the best set.
+ */
+ if (bms_num_members(aggindexes) > bms_num_members(bestaggs))
+ {
+ bestaggs = aggindexes;
+ bestpathkeys = currpathkeys;
+ }
+ }
+
+ /*
+ * Now that we've found the best set of aggregates we can set the
+ * presorted flag to indicate to the executor that it needn't bother
+ * performing a sort for these Aggrefs. We're able to do this now as
+ * there's no chance of a Hash Aggregate plan as create_grouping_paths
+ * will not mark the GROUP BY as GROUPING_CAN_USE_HASH due to the presence
+ * of ordered aggregates.
+ */
+ i = -1;
+ while ((i = bms_next_member(bestaggs, i)) >= 0)
+ {
+ AggInfo *agginfo = list_nth_node(AggInfo, root->agginfos, i);
+
+ foreach(lc, agginfo->aggrefs)
+ {
+ Aggref *aggref = lfirst_node(Aggref, lc);
+
+ aggref->aggpresorted = true;
+ }
+ }
+
+ /*
+ * bestpathkeys includes the GROUP BY pathkeys, so if we found any ordered
+ * aggregates, then return bestpathkeys, otherwise return the
+ * grouppathkeys.
+ */
+ if (bestpathkeys != NIL)
+ return bestpathkeys;
+
+ return grouppathkeys;
+}
+
+/*
* Compute query_pathkeys and other pathkeys during plan generation
*/
static void
@@ -3137,18 +3349,19 @@ standard_qp_callback(PlannerInfo *root, void *extra)
List *activeWindows = qp_extra->activeWindows;
/*
- * Calculate pathkeys that represent grouping/ordering requirements. The
- * sortClause is certainly sort-able, but GROUP BY and DISTINCT might not
- * be, in which case we just leave their pathkeys empty.
+ * Calculate pathkeys that represent grouping/ordering and/or ordered
+ * aggregate requirements.
*/
- if (qp_extra->groupClause &&
- grouping_is_sortable(qp_extra->groupClause))
- root->group_pathkeys =
- make_pathkeys_for_sortclauses(root,
- qp_extra->groupClause,
- tlist);
+ if (qp_extra->groupClause != NIL || root->numOrderedAggs > 0)
+ root->group_pathkeys = make_pathkeys_for_groupagg(root,
+ qp_extra->groupClause,
+ tlist,
+ &root->num_groupby_pathkeys);
else
+ {
root->group_pathkeys = NIL;
+ root->num_groupby_pathkeys = 0;
+ }
/* We consider only the first (bottom) window in pathkeys logic */
if (activeWindows != NIL)
@@ -6222,6 +6435,26 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
if (can_sort)
{
+ List *group_pathkeys;
+ List *orderAggPathkeys;
+ int numAggPathkeys;
+
+ numAggPathkeys = list_length(root->group_pathkeys) -
+ root->num_groupby_pathkeys;
+
+ if (numAggPathkeys > 0)
+ {
+ group_pathkeys = list_copy_head(root->group_pathkeys,
+ root->num_groupby_pathkeys);
+ orderAggPathkeys = list_copy_tail(root->group_pathkeys,
+ root->num_groupby_pathkeys);
+ }
+ else
+ {
+ group_pathkeys = root->group_pathkeys;
+ orderAggPathkeys = NIL;
+ }
+
/*
* Use any available suitably-sorted path as input, and also consider
* sorting the cheapest-total path.
@@ -6234,7 +6467,6 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
List *pathkey_orderings = NIL;
- List *group_pathkeys = root->group_pathkeys;
List *group_clauses = parse->groupClause;
/* generate alternative group orderings that might be useful */
@@ -6242,7 +6474,8 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
path->rows,
path->pathkeys,
group_pathkeys,
- group_clauses);
+ group_clauses,
+ orderAggPathkeys);
Assert(list_length(pathkey_orderings) > 0);
@@ -6414,7 +6647,8 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
path->rows,
path->pathkeys,
group_pathkeys,
- group_clauses);
+ group_clauses,
+ orderAggPathkeys);
Assert(list_length(pathkey_orderings) > 0);
@@ -6717,6 +6951,26 @@ create_partial_grouping_paths(PlannerInfo *root,
if (can_sort && cheapest_total_path != NULL)
{
+ List *group_pathkeys;
+ List *orderAggPathkeys;
+ int numAggPathkeys;
+
+ numAggPathkeys = list_length(root->group_pathkeys) -
+ root->num_groupby_pathkeys;
+
+ if (numAggPathkeys > 0)
+ {
+ group_pathkeys = list_copy_head(root->group_pathkeys,
+ root->num_groupby_pathkeys);
+ orderAggPathkeys = list_copy_tail(root->group_pathkeys,
+ root->num_groupby_pathkeys);
+ }
+ else
+ {
+ group_pathkeys = root->group_pathkeys;
+ orderAggPathkeys = NIL;
+ }
+
/* This should have been checked previously */
Assert(parse->hasAggs || parse->groupClause);
@@ -6729,10 +6983,7 @@ create_partial_grouping_paths(PlannerInfo *root,
ListCell *lc2;
Path *path = (Path *) lfirst(lc);
Path *path_save = path;
-
List *pathkey_orderings = NIL;
-
- List *group_pathkeys = root->group_pathkeys;
List *group_clauses = parse->groupClause;
/* generate alternative group orderings that might be useful */
@@ -6740,7 +6991,8 @@ create_partial_grouping_paths(PlannerInfo *root,
path->rows,
path->pathkeys,
group_pathkeys,
- group_clauses);
+ group_clauses,
+ orderAggPathkeys);
Assert(list_length(pathkey_orderings) > 0);
@@ -6856,16 +7108,33 @@ create_partial_grouping_paths(PlannerInfo *root,
if (can_sort && cheapest_partial_path != NULL)
{
+ List *group_pathkeys;
+ List *orderAggPathkeys;
+ int numAggPathkeys;
+
+ numAggPathkeys = list_length(root->group_pathkeys) -
+ root->num_groupby_pathkeys;
+
+ if (numAggPathkeys > 0)
+ {
+ group_pathkeys = list_copy_head(root->group_pathkeys,
+ root->num_groupby_pathkeys);
+ orderAggPathkeys = list_copy_tail(root->group_pathkeys,
+ root->num_groupby_pathkeys);
+ }
+ else
+ {
+ group_pathkeys = root->group_pathkeys;
+ orderAggPathkeys = NIL;
+ }
+
/* Similar to above logic, but for partial paths. */
foreach(lc, input_rel->partial_pathlist)
{
ListCell *lc2;
Path *path = (Path *) lfirst(lc);
Path *path_original = path;
-
List *pathkey_orderings = NIL;
-
- List *group_pathkeys = root->group_pathkeys;
List *group_clauses = parse->groupClause;
/* generate alternative group orderings that might be useful */
@@ -6873,7 +7142,8 @@ create_partial_grouping_paths(PlannerInfo *root,
path->rows,
path->pathkeys,
group_pathkeys,
- group_clauses);
+ group_clauses,
+ orderAggPathkeys);
Assert(list_length(pathkey_orderings) > 0);
diff --git a/src/backend/optimizer/prep/prepagg.c b/src/backend/optimizer/prep/prepagg.c
index 5b12937eada..da89b55402c 100644
--- a/src/backend/optimizer/prep/prepagg.c
+++ b/src/backend/optimizer/prep/prepagg.c
@@ -225,6 +225,7 @@ preprocess_aggref(Aggref *aggref, PlannerInfo *root)
{
AggInfo *agginfo = list_nth_node(AggInfo, root->agginfos, aggno);
+ agginfo->aggrefs = lappend(agginfo->aggrefs, aggref);
transno = agginfo->transno;
}
else
@@ -232,7 +233,7 @@ preprocess_aggref(Aggref *aggref, PlannerInfo *root)
AggInfo *agginfo = makeNode(AggInfo);
agginfo->finalfn_oid = aggfinalfn;
- agginfo->representative_aggref = aggref;
+ agginfo->aggrefs = list_make1(aggref);
agginfo->shareable = shareable;
aggno = list_length(root->agginfos);
@@ -386,7 +387,7 @@ find_compatible_agg(PlannerInfo *root, Aggref *newagg,
aggno++;
- existingRef = agginfo->representative_aggref;
+ existingRef = linitial_node(Aggref, agginfo->aggrefs);
/* all of the following must be the same or it's no match */
if (newagg->inputcollid != existingRef->inputcollid ||
@@ -648,7 +649,7 @@ get_agg_clause_costs(PlannerInfo *root, AggSplit aggsplit, AggClauseCosts *costs
foreach(lc, root->agginfos)
{
AggInfo *agginfo = lfirst_node(AggInfo, lc);
- Aggref *aggref = agginfo->representative_aggref;
+ Aggref *aggref = linitial_node(Aggref, agginfo->aggrefs);
/*
* Add the appropriate component function execution costs to
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 059cb7097c7..fabb5f72076 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -3816,6 +3816,7 @@ transformJsonAggConstructor(ParseState *pstate, JsonAggConstructor *agg_ctor,
aggref->aggstar = false;
aggref->aggvariadic = false;
aggref->aggkind = AGGKIND_NORMAL;
+ aggref->aggpresorted = false;
/* agglevelsup will be set by transformAggregateCall */
aggref->aggsplit = AGGSPLIT_SIMPLE; /* planner might change this */
aggref->location = agg_ctor->location;
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index f71a682cd65..827989f3799 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -774,6 +774,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
aggref->aggstar = agg_star;
aggref->aggvariadic = func_variadic;
aggref->aggkind = aggkind;
+ aggref->aggpresorted = false;
/* agglevelsup will be set by transformAggregateCall */
aggref->aggsplit = AGGSPLIT_SIMPLE; /* planner might change this */
aggref->aggno = -1; /* planner will set aggno and aggtransno */