mirror of
https://github.com/postgres/postgres.git
synced 2025-07-08 11:42:09 +03:00
Rethink node-level representation of partial-aggregation modes.
The original coding had three separate booleans representing partial
aggregation behavior, which was confusing, unreadable, and error-prone,
not least because the booleans weren't always listed in the same order.
It was also inadequate for the allegedly-desirable future extension to
support intermediate partial aggregation, because we'd need separate
markers for serialization and deserialization in such a case.
Merge these bools into an enum "AggSplit" to provide symbolic names for
the supported operating modes (and document what those are). By assigning
the values of the enum constants carefully, we can treat AggSplit values
as options bitmasks so that tests of what to do aren't noticeably more
expensive than before.
While at it, get rid of Aggref.aggoutputtype. That's not needed since
commit 59a3795c2
got rid of setrefs.c's special-purpose Aggref comparison
code, and it likewise seemed more confusing than helpful.
Assorted comment cleanup as well (there's still more that I want to do
in that line).
catversion bump for change in Aggref node contents. Should be the last
one for partial-aggregation changes.
Discussion: <29309.1466699160@sss.pgh.pa.us>
This commit is contained in:
@ -1304,9 +1304,7 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags)
|
||||
plan = (Plan *) make_agg(build_path_tlist(root, &best_path->path),
|
||||
NIL,
|
||||
AGG_HASHED,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
AGGSPLIT_SIMPLE,
|
||||
numGroupCols,
|
||||
groupColIdx,
|
||||
groupOperators,
|
||||
@ -1610,9 +1608,7 @@ create_agg_plan(PlannerInfo *root, AggPath *best_path)
|
||||
|
||||
plan = make_agg(tlist, quals,
|
||||
best_path->aggstrategy,
|
||||
best_path->combineStates,
|
||||
best_path->finalizeAggs,
|
||||
best_path->serialStates,
|
||||
best_path->aggsplit,
|
||||
list_length(best_path->groupClause),
|
||||
extract_grouping_cols(best_path->groupClause,
|
||||
subplan->targetlist),
|
||||
@ -1765,9 +1761,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
|
||||
agg_plan = (Plan *) make_agg(NIL,
|
||||
NIL,
|
||||
AGG_SORTED,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
AGGSPLIT_SIMPLE,
|
||||
list_length((List *) linitial(gsets)),
|
||||
new_grpColIdx,
|
||||
extract_grouping_ops(groupClause),
|
||||
@ -1802,9 +1796,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
|
||||
plan = make_agg(build_path_tlist(root, &best_path->path),
|
||||
best_path->qual,
|
||||
(numGroupCols > 0) ? AGG_SORTED : AGG_PLAIN,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
AGGSPLIT_SIMPLE,
|
||||
numGroupCols,
|
||||
top_grpColIdx,
|
||||
extract_grouping_ops(groupClause),
|
||||
@ -5652,8 +5644,7 @@ materialize_finished_plan(Plan *subplan)
|
||||
|
||||
Agg *
|
||||
make_agg(List *tlist, List *qual,
|
||||
AggStrategy aggstrategy,
|
||||
bool combineStates, bool finalizeAggs, bool serialStates,
|
||||
AggStrategy aggstrategy, AggSplit aggsplit,
|
||||
int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
|
||||
List *groupingSets, List *chain,
|
||||
double dNumGroups, Plan *lefttree)
|
||||
@ -5666,9 +5657,7 @@ make_agg(List *tlist, List *qual,
|
||||
numGroups = (long) Min(dNumGroups, (double) LONG_MAX);
|
||||
|
||||
node->aggstrategy = aggstrategy;
|
||||
node->combineStates = combineStates;
|
||||
node->finalizeAggs = finalizeAggs;
|
||||
node->serialStates = serialStates;
|
||||
node->aggsplit = aggsplit;
|
||||
node->numCols = numGroupCols;
|
||||
node->grpColIdx = grpColIdx;
|
||||
node->grpOperators = grpOperators;
|
||||
|
@ -3391,10 +3391,10 @@ create_grouping_paths(PlannerInfo *root,
|
||||
MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
|
||||
if (parse->hasAggs)
|
||||
{
|
||||
count_agg_clauses(root, (Node *) target->exprs, &agg_costs, true,
|
||||
false, false);
|
||||
count_agg_clauses(root, parse->havingQual, &agg_costs, true, false,
|
||||
false);
|
||||
get_agg_clause_costs(root, (Node *) target->exprs, AGGSPLIT_SIMPLE,
|
||||
&agg_costs);
|
||||
get_agg_clause_costs(root, parse->havingQual, AGGSPLIT_SIMPLE,
|
||||
&agg_costs);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -3480,14 +3480,17 @@ create_grouping_paths(PlannerInfo *root,
|
||||
if (parse->hasAggs)
|
||||
{
|
||||
/* partial phase */
|
||||
count_agg_clauses(root, (Node *) partial_grouping_target->exprs,
|
||||
&agg_partial_costs, false, false, true);
|
||||
get_agg_clause_costs(root, (Node *) partial_grouping_target->exprs,
|
||||
AGGSPLIT_INITIAL_SERIAL,
|
||||
&agg_partial_costs);
|
||||
|
||||
/* final phase */
|
||||
count_agg_clauses(root, (Node *) target->exprs, &agg_final_costs,
|
||||
true, true, true);
|
||||
count_agg_clauses(root, parse->havingQual, &agg_final_costs, true,
|
||||
true, true);
|
||||
get_agg_clause_costs(root, (Node *) target->exprs,
|
||||
AGGSPLIT_FINAL_DESERIAL,
|
||||
&agg_final_costs);
|
||||
get_agg_clause_costs(root, parse->havingQual,
|
||||
AGGSPLIT_FINAL_DESERIAL,
|
||||
&agg_final_costs);
|
||||
}
|
||||
|
||||
if (can_sort)
|
||||
@ -3523,13 +3526,11 @@ create_grouping_paths(PlannerInfo *root,
|
||||
path,
|
||||
partial_grouping_target,
|
||||
parse->groupClause ? AGG_SORTED : AGG_PLAIN,
|
||||
AGGSPLIT_INITIAL_SERIAL,
|
||||
parse->groupClause,
|
||||
NIL,
|
||||
&agg_partial_costs,
|
||||
dNumPartialGroups,
|
||||
false,
|
||||
false,
|
||||
true));
|
||||
dNumPartialGroups));
|
||||
else
|
||||
add_partial_path(grouped_rel, (Path *)
|
||||
create_group_path(root,
|
||||
@ -3565,13 +3566,11 @@ create_grouping_paths(PlannerInfo *root,
|
||||
cheapest_partial_path,
|
||||
partial_grouping_target,
|
||||
AGG_HASHED,
|
||||
AGGSPLIT_INITIAL_SERIAL,
|
||||
parse->groupClause,
|
||||
NIL,
|
||||
&agg_partial_costs,
|
||||
dNumPartialGroups,
|
||||
false,
|
||||
false,
|
||||
true));
|
||||
dNumPartialGroups));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3630,13 +3629,11 @@ create_grouping_paths(PlannerInfo *root,
|
||||
path,
|
||||
target,
|
||||
parse->groupClause ? AGG_SORTED : AGG_PLAIN,
|
||||
AGGSPLIT_SIMPLE,
|
||||
parse->groupClause,
|
||||
(List *) parse->havingQual,
|
||||
&agg_costs,
|
||||
dNumGroups,
|
||||
false,
|
||||
true,
|
||||
false));
|
||||
dNumGroups));
|
||||
}
|
||||
else if (parse->groupClause)
|
||||
{
|
||||
@ -3697,13 +3694,11 @@ create_grouping_paths(PlannerInfo *root,
|
||||
path,
|
||||
target,
|
||||
parse->groupClause ? AGG_SORTED : AGG_PLAIN,
|
||||
AGGSPLIT_FINAL_DESERIAL,
|
||||
parse->groupClause,
|
||||
(List *) parse->havingQual,
|
||||
&agg_final_costs,
|
||||
dNumGroups,
|
||||
true,
|
||||
true,
|
||||
true));
|
||||
dNumGroups));
|
||||
else
|
||||
add_path(grouped_rel, (Path *)
|
||||
create_group_path(root,
|
||||
@ -3740,13 +3735,11 @@ create_grouping_paths(PlannerInfo *root,
|
||||
cheapest_path,
|
||||
target,
|
||||
AGG_HASHED,
|
||||
AGGSPLIT_SIMPLE,
|
||||
parse->groupClause,
|
||||
(List *) parse->havingQual,
|
||||
&agg_costs,
|
||||
dNumGroups,
|
||||
false,
|
||||
true,
|
||||
false));
|
||||
dNumGroups));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -3779,13 +3772,11 @@ create_grouping_paths(PlannerInfo *root,
|
||||
path,
|
||||
target,
|
||||
AGG_HASHED,
|
||||
AGGSPLIT_FINAL_DESERIAL,
|
||||
parse->groupClause,
|
||||
(List *) parse->havingQual,
|
||||
&agg_final_costs,
|
||||
dNumGroups,
|
||||
true,
|
||||
true,
|
||||
true));
|
||||
dNumGroups));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4123,13 +4114,11 @@ create_distinct_paths(PlannerInfo *root,
|
||||
cheapest_input_path,
|
||||
cheapest_input_path->pathtarget,
|
||||
AGG_HASHED,
|
||||
AGGSPLIT_SIMPLE,
|
||||
parse->distinctClause,
|
||||
NIL,
|
||||
NULL,
|
||||
numDistinctRows,
|
||||
false,
|
||||
true,
|
||||
false));
|
||||
numDistinctRows));
|
||||
}
|
||||
|
||||
/* Give a helpful error if we failed to find any implementation */
|
||||
@ -4414,8 +4403,8 @@ make_partial_grouping_target(PlannerInfo *root, PathTarget *grouping_target)
|
||||
newaggref = makeNode(Aggref);
|
||||
memcpy(newaggref, aggref, sizeof(Aggref));
|
||||
|
||||
/* XXX assume serialization required */
|
||||
mark_partial_aggref(newaggref, true);
|
||||
/* For now, assume serialization is required */
|
||||
mark_partial_aggref(newaggref, AGGSPLIT_INITIAL_SERIAL);
|
||||
|
||||
lfirst(lc) = newaggref;
|
||||
}
|
||||
@ -4431,27 +4420,33 @@ make_partial_grouping_target(PlannerInfo *root, PathTarget *grouping_target)
|
||||
|
||||
/*
|
||||
* mark_partial_aggref
|
||||
* Adjust an Aggref to make it represent the output of partial aggregation.
|
||||
* Adjust an Aggref to make it represent a partial-aggregation step.
|
||||
*
|
||||
* The Aggref node is modified in-place; caller must do any copying required.
|
||||
*/
|
||||
void
|
||||
mark_partial_aggref(Aggref *agg, bool serialize)
|
||||
mark_partial_aggref(Aggref *agg, AggSplit aggsplit)
|
||||
{
|
||||
/* aggtranstype should be computed by this point */
|
||||
Assert(OidIsValid(agg->aggtranstype));
|
||||
/* ... but aggsplit should still be as the parser left it */
|
||||
Assert(agg->aggsplit == AGGSPLIT_SIMPLE);
|
||||
|
||||
/* Mark the Aggref with the intended partial-aggregation mode */
|
||||
agg->aggsplit = aggsplit;
|
||||
|
||||
/*
|
||||
* Normally, a partial aggregate returns the aggregate's transition type;
|
||||
* but if that's INTERNAL and we're serializing, it returns BYTEA instead.
|
||||
* Adjust result type if needed. Normally, a partial aggregate returns
|
||||
* the aggregate's transition type; but if that's INTERNAL and we're
|
||||
* serializing, it returns BYTEA instead.
|
||||
*/
|
||||
if (agg->aggtranstype == INTERNALOID && serialize)
|
||||
agg->aggoutputtype = BYTEAOID;
|
||||
else
|
||||
agg->aggoutputtype = agg->aggtranstype;
|
||||
|
||||
/* flag it as partial */
|
||||
agg->aggpartial = true;
|
||||
if (DO_AGGSPLIT_SKIPFINAL(aggsplit))
|
||||
{
|
||||
if (agg->aggtranstype == INTERNALOID && DO_AGGSPLIT_SERIALIZE(aggsplit))
|
||||
agg->aggtype = BYTEAOID;
|
||||
else
|
||||
agg->aggtype = agg->aggtranstype;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -679,7 +679,7 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
|
||||
* partial-aggregate subexpressions that will be available
|
||||
* from the child plan node.
|
||||
*/
|
||||
if (agg->combineStates)
|
||||
if (DO_AGGSPLIT_COMBINE(agg->aggsplit))
|
||||
{
|
||||
plan->targetlist = (List *)
|
||||
convert_combining_aggrefs((Node *) plan->targetlist,
|
||||
@ -1772,16 +1772,16 @@ convert_combining_aggrefs(Node *node, void *context)
|
||||
|
||||
/*
|
||||
* Now, set up child_agg to represent the first phase of partial
|
||||
* aggregation. XXX assume serialization required.
|
||||
* aggregation. For now, assume serialization is required.
|
||||
*/
|
||||
mark_partial_aggref(child_agg, true);
|
||||
mark_partial_aggref(child_agg, AGGSPLIT_INITIAL_SERIAL);
|
||||
|
||||
/*
|
||||
* And set up parent_agg to represent the second phase.
|
||||
*/
|
||||
parent_agg->args = list_make1(makeTargetEntry((Expr *) child_agg,
|
||||
1, NULL, false));
|
||||
parent_agg->aggcombine = true;
|
||||
mark_partial_aggref(parent_agg, AGGSPLIT_FINAL_DESERIAL);
|
||||
|
||||
return (Node *) parent_agg;
|
||||
}
|
||||
|
@ -861,13 +861,11 @@ make_union_unique(SetOperationStmt *op, Path *path, List *tlist,
|
||||
path,
|
||||
create_pathtarget(root, tlist),
|
||||
AGG_HASHED,
|
||||
AGGSPLIT_SIMPLE,
|
||||
groupList,
|
||||
NIL,
|
||||
NULL,
|
||||
dNumGroups,
|
||||
false,
|
||||
true,
|
||||
false);
|
||||
dNumGroups);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -60,11 +60,9 @@ typedef struct
|
||||
typedef struct
|
||||
{
|
||||
PlannerInfo *root;
|
||||
AggSplit aggsplit;
|
||||
AggClauseCosts *costs;
|
||||
bool finalizeAggs;
|
||||
bool combineStates;
|
||||
bool serialStates;
|
||||
} count_agg_clauses_context;
|
||||
} get_agg_clause_costs_context;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
@ -103,8 +101,8 @@ typedef struct
|
||||
static bool aggregates_allow_partial_walker(Node *node,
|
||||
partial_agg_context *context);
|
||||
static bool contain_agg_clause_walker(Node *node, void *context);
|
||||
static bool count_agg_clauses_walker(Node *node,
|
||||
count_agg_clauses_context *context);
|
||||
static bool get_agg_clause_costs_walker(Node *node,
|
||||
get_agg_clause_costs_context *context);
|
||||
static bool find_window_functions_walker(Node *node, WindowFuncLists *lists);
|
||||
static bool expression_returns_set_rows_walker(Node *node, double *count);
|
||||
static bool contain_subplans_walker(Node *node, void *context);
|
||||
@ -519,44 +517,43 @@ contain_agg_clause_walker(Node *node, void *context)
|
||||
}
|
||||
|
||||
/*
|
||||
* count_agg_clauses
|
||||
* Recursively count the Aggref nodes in an expression tree, and
|
||||
* accumulate other information about them too.
|
||||
* get_agg_clause_costs
|
||||
* Recursively find the Aggref nodes in an expression tree, and
|
||||
* accumulate cost information about them.
|
||||
*
|
||||
* Note: this also checks for nested aggregates, which are an error.
|
||||
* 'aggsplit' tells us the expected partial-aggregation mode, which affects
|
||||
* the cost estimates.
|
||||
*
|
||||
* We not only count the nodes, but estimate their execution costs, and
|
||||
* attempt to estimate the total space needed for their transition state
|
||||
* values if all are evaluated in parallel (as would be done in a HashAgg
|
||||
* plan). See AggClauseCosts for the exact set of statistics collected.
|
||||
* NOTE that the counts/costs are ADDED to those already in *costs ... so
|
||||
* the caller is responsible for zeroing the struct initially.
|
||||
*
|
||||
* We count the nodes, estimate their execution costs, and estimate the total
|
||||
* space needed for their transition state values if all are evaluated in
|
||||
* parallel (as would be done in a HashAgg plan). See AggClauseCosts for
|
||||
* the exact set of statistics collected.
|
||||
*
|
||||
* In addition, we mark Aggref nodes with the correct aggtranstype, so
|
||||
* that that doesn't need to be done repeatedly. (That makes this function's
|
||||
* name a bit of a misnomer.)
|
||||
*
|
||||
* NOTE that the counts/costs are ADDED to those already in *costs ... so
|
||||
* the caller is responsible for zeroing the struct initially.
|
||||
*
|
||||
* This does not descend into subqueries, and so should be used only after
|
||||
* reduction of sublinks to subplans, or in contexts where it's known there
|
||||
* are no subqueries. There mustn't be outer-aggregate references either.
|
||||
*/
|
||||
void
|
||||
count_agg_clauses(PlannerInfo *root, Node *clause, AggClauseCosts *costs,
|
||||
bool finalizeAggs, bool combineStates, bool serialStates)
|
||||
get_agg_clause_costs(PlannerInfo *root, Node *clause, AggSplit aggsplit,
|
||||
AggClauseCosts *costs)
|
||||
{
|
||||
count_agg_clauses_context context;
|
||||
get_agg_clause_costs_context context;
|
||||
|
||||
context.root = root;
|
||||
context.aggsplit = aggsplit;
|
||||
context.costs = costs;
|
||||
context.finalizeAggs = finalizeAggs;
|
||||
context.combineStates = combineStates;
|
||||
context.serialStates = serialStates;
|
||||
(void) count_agg_clauses_walker(clause, &context);
|
||||
(void) get_agg_clause_costs_walker(clause, &context);
|
||||
}
|
||||
|
||||
static bool
|
||||
count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
|
||||
get_agg_clause_costs_walker(Node *node, get_agg_clause_costs_context *context)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false;
|
||||
@ -628,34 +625,28 @@ count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
|
||||
* Add the appropriate component function execution costs to
|
||||
* appropriate totals.
|
||||
*/
|
||||
if (context->combineStates)
|
||||
if (DO_AGGSPLIT_COMBINE(context->aggsplit))
|
||||
{
|
||||
/* charge for combining previously aggregated states */
|
||||
costs->transCost.per_tuple += get_func_cost(aggcombinefn) * cpu_operator_cost;
|
||||
|
||||
/* charge for deserialization, when appropriate */
|
||||
if (context->serialStates && OidIsValid(aggdeserialfn))
|
||||
costs->transCost.per_tuple += get_func_cost(aggdeserialfn) * cpu_operator_cost;
|
||||
}
|
||||
else
|
||||
costs->transCost.per_tuple += get_func_cost(aggtransfn) * cpu_operator_cost;
|
||||
|
||||
if (context->finalizeAggs)
|
||||
{
|
||||
if (OidIsValid(aggfinalfn))
|
||||
costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost;
|
||||
}
|
||||
else if (context->serialStates)
|
||||
{
|
||||
if (OidIsValid(aggserialfn))
|
||||
costs->finalCost += get_func_cost(aggserialfn) * cpu_operator_cost;
|
||||
}
|
||||
if (DO_AGGSPLIT_DESERIALIZE(context->aggsplit) &&
|
||||
OidIsValid(aggdeserialfn))
|
||||
costs->transCost.per_tuple += get_func_cost(aggdeserialfn) * cpu_operator_cost;
|
||||
if (DO_AGGSPLIT_SERIALIZE(context->aggsplit) &&
|
||||
OidIsValid(aggserialfn))
|
||||
costs->finalCost += get_func_cost(aggserialfn) * cpu_operator_cost;
|
||||
if (!DO_AGGSPLIT_SKIPFINAL(context->aggsplit) &&
|
||||
OidIsValid(aggfinalfn))
|
||||
costs->finalCost += get_func_cost(aggfinalfn) * cpu_operator_cost;
|
||||
|
||||
/*
|
||||
* Some costs will already have been incurred by the initial aggregate
|
||||
* node, so we mustn't include these again.
|
||||
* These costs are incurred only by the initial aggregate node, so we
|
||||
* mustn't include them again at upper levels.
|
||||
*/
|
||||
if (!context->combineStates)
|
||||
if (!DO_AGGSPLIT_COMBINE(context->aggsplit))
|
||||
{
|
||||
/* add the input expressions' cost to per-input-row costs */
|
||||
cost_qual_eval_node(&argcosts, (Node *) aggref->args, context->root);
|
||||
@ -747,14 +738,12 @@ count_agg_clauses_walker(Node *node, count_agg_clauses_context *context)
|
||||
/*
|
||||
* We assume that the parser checked that there are no aggregates (of
|
||||
* this level anyway) in the aggregated arguments, direct arguments,
|
||||
* or filter clause. Hence, we need not recurse into any of them. (If
|
||||
* either the parser or the planner screws up on this point, the
|
||||
* executor will still catch it; see ExecInitExpr.)
|
||||
* or filter clause. Hence, we need not recurse into any of them.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
Assert(!IsA(node, SubLink));
|
||||
return expression_tree_walker(node, count_agg_clauses_walker,
|
||||
return expression_tree_walker(node, get_agg_clause_costs_walker,
|
||||
(void *) context);
|
||||
}
|
||||
|
||||
|
@ -2466,12 +2466,11 @@ create_upper_unique_path(PlannerInfo *root,
|
||||
* 'subpath' is the path representing the source of data
|
||||
* 'target' is the PathTarget to be computed
|
||||
* 'aggstrategy' is the Agg node's basic implementation strategy
|
||||
* 'aggsplit' is the Agg node's aggregate-splitting mode
|
||||
* 'groupClause' is a list of SortGroupClause's representing the grouping
|
||||
* 'qual' is the HAVING quals if any
|
||||
* 'aggcosts' contains cost info about the aggregate functions to be computed
|
||||
* 'numGroups' is the estimated number of groups (1 if not grouping)
|
||||
* 'combineStates' is set to true if the Agg node should combine agg states
|
||||
* 'finalizeAggs' is set to false if the Agg node should not call the finalfn
|
||||
*/
|
||||
AggPath *
|
||||
create_agg_path(PlannerInfo *root,
|
||||
@ -2479,13 +2478,11 @@ create_agg_path(PlannerInfo *root,
|
||||
Path *subpath,
|
||||
PathTarget *target,
|
||||
AggStrategy aggstrategy,
|
||||
AggSplit aggsplit,
|
||||
List *groupClause,
|
||||
List *qual,
|
||||
const AggClauseCosts *aggcosts,
|
||||
double numGroups,
|
||||
bool combineStates,
|
||||
bool finalizeAggs,
|
||||
bool serialStates)
|
||||
double numGroups)
|
||||
{
|
||||
AggPath *pathnode = makeNode(AggPath);
|
||||
|
||||
@ -2505,12 +2502,10 @@ create_agg_path(PlannerInfo *root,
|
||||
pathnode->subpath = subpath;
|
||||
|
||||
pathnode->aggstrategy = aggstrategy;
|
||||
pathnode->aggsplit = aggsplit;
|
||||
pathnode->numGroups = numGroups;
|
||||
pathnode->groupClause = groupClause;
|
||||
pathnode->qual = qual;
|
||||
pathnode->finalizeAggs = finalizeAggs;
|
||||
pathnode->combineStates = combineStates;
|
||||
pathnode->serialStates = serialStates;
|
||||
|
||||
cost_agg(&pathnode->path, root,
|
||||
aggstrategy, aggcosts,
|
||||
|
Reference in New Issue
Block a user