Partial Aggregation / GROUP BY before JOIN
I've been spending time working on allowing the planner to perform
aggregation before the final join relation is created.
Now, I should warn that at the moment the patch's status is WIP. There's
still many things to work out, although there's many parts of it that I
think are a bit closer to being final.
The main purpose of this email is to get community approval on my proposed
implementation. I'd also like to gather up any ideas that people may have
about better ways to do certain things.
Let me start by just giving a very quick example and overview of the
problem that this is trying so solve: (please skip to the proposed
implementation section, if you already know)
This is likely best done by example, so let say we have a "sale" table
which contains millions of records of sold products. We also have a
"product" table, which contains a small number of products that are sold.
A query such as:
SELECT p.product_id,p.description,sum(s.quantity)
FROM sale s
INNER JOIN product p ON p.product_id = s.product_id
GROUP BY p.product_id;
For now, the planner will first join sale and product, and perform the
grouping on the final joined relation. Now, if we assume that we have a
small number of products, and the sale table stores multiple records for
each product, then this query will be better executed as if it had been
written as:
SELECT p.product_id,p.description,s.quantity
FROM (SELECT product_id,SUM(quantity) AS quantity FROM sale GROUP BY
product_id) s
INNER JOIN product p ON p.product_id = s.product_id;
Although, what I want to demonstrate is that this is not always the case.
Imagine:
SELECT p.product_id,p.description,sum(s.quantity)
FROM sale s
INNER JOIN product p ON p.product_id = s.product_id
WHERE p.description = 'something highly selective'
GROUP BY s.product_id;
In this case, since the qual on p.description could not be pushed down into
the subquery, it is more likely that the query is better executed by
performing the join and then performing the grouping.
For this reason the planner must take into account the cost of both methods
before it decided which it should use.
Proposed Implementation
----------------------------------
Due to what I explained above about costing. I'd like to propose that this
patch introduces a new RelOptKind named RELOPT_ALTREL, which will allow the
rel to be stored in PlannerInfo's simple_rel_array, but not have the
relation considered in make_one_rel(). Instead, I propose that this
"Alternative Relation" be tagged onto the RelOptInfo that it is an
alternative of in a new List field, and when we perform the standard join
search, whenever we consider a relation which has alternatives listed, we
also consider each alternative relation. Having this as RELOPT_ALTREL
instead of RELOPT_DEADREL will allow relation sizes to be gathered for
these relations (e.g. in set_base_rel_sizes()). I believe this method may
also help in the future for allowing materialized views to be used instead
of base tables (for when we have auto-update MVs).
The idea here is that during planning, we will analyze the query, and check
if all aggregate function parameters belong to just 1 relation, and we'll
also check that all those aggregate functions have a combine function set
(which is a concept that this patch introduces). We'll also check to ensure
that the aggregates don't have an ORDER BY or DISTINCT. If the aggregates
are found to be suitable, then we'll construct a subquery which performs
the required grouping on the single relation.
Now, there's also more complex cases where the GROUP BY clause contains
Vars from a relation that's not the same as the relation seen in the
aggregate function's parameters. I plan to handle this too, but I don't
want to bloat this email with the complexities of that, but I do need to
mention the fact that the subquery may only be able to partially group the
tuples, and that a final aggregate stage may need to happen too. For
example:
SELECT p.description,sum(s.quantity)
FROM sale s
INNER JOIN product p ON p.product_id = s.product_id
GROUP BY p.description;
Here we might want to group sales on s.product_id, pass those partially
grouped results down to be joined to "product" and then perform the final
grouping on p.description. This may or may not further reduce the number of
groups. For this reason we need a partial grouping stage and also a final
grouping stage, and to allow this we need to be able to instruct the
subquery to only perform partial grouping (i.e. send us the non-finalised
aggregate states without the aggregate's final function being called on
them). To implement this I've made various changes to nodeAgg.c to allow 4
possible modes of aggregation, although only 3 of these are currently used.
These modes are controlled by 2 new boolean parameters:
bool combineStates; /* input tuples contain transition states */
bool finalizeAggs; /* should we call the finalfn on agg states? */
So, when we generate this subquery to perform the partial aggregation for
us, we need to tell it not to perform the finalise stage. In the attached
patch this is done by adding a new parameter to subquery_planner()
Plan *
subquery_planner(PlannerGlobal *glob, Query *parse,
PlannerInfo *parent_root,
bool hasRecursion, bool finalize_aggregates,
double tuple_fraction, PlannerInfo **subroot)
The main query must set combineStates to true if a subquery RTE exists
which has been marked as finalize_aggregates = false. This will instruct
the top level nodeAgg to call the combine function rather than transition
function.
Status of current patch
------------------------------
1. I believe that I have made all the changes required to nodeAgg.c which
supports this partial and final aggregation concept. (Likely this needs
more testing, but there's other priorities)
2. The EXPLAIN changes are complete. When combineStates is true we see
"Finalize Aggregate", and when finalizeAggs is false we see "Partial
Aggregate". If finalizeAggs is true and combineStates is false, then we
don't see any changes to the EXPLAIN output for Aggregate nodes.
3. The Alternative relation stuff and the changes to the standard join
search are not done yet. I want to get 4 finished before I start on this,
and also want some feedback about the idea.
4. Currenrly the code which constructs the subquery which is to perform the
aggregation is very much a dumping ground of unfinished and quite broken
code, that only works for a handful of cases so far. I keep hacking away at
this trying to find a nice neat way to do this, but so far I've only
managed to get what you see in joinpath.c (which of course is almost
certainly not the final resting place for this code). At the moment I'm
just adding the new subquery rel and removing the original version from the
joinlist. The final version won't do that part, but doing it this way is
good for testing as it'll always perform Group before join when it's
possible.
The patch is however so far capable of giving us extremely nice performance
improvements for some (likely artificial) queries.
Let's look at a quick example:
CREATE TABLE product (product_id INT NOT NULL,product_code VARCHAR(64) NOT
NULL, PRIMARY KEY(product_id));
CREATE UNIQUE INDEX product_product_code_uidx ON product (product_code);
-- create small list of products
INSERT INTO product SELECT g.id,'ABC' || CAST(g.id AS TEXT) FROM
generate_series(1,100) g(id);
CREATE TABLE sale (sale_id INT NOT NULL, product_id INT NOT NULL, quantity
INT NOT NULL);
INSERT INTO sale (sale_id, product_id,quantity) SELECT
x.x,x.x%100+1,CAST(random() * 1000 AS INT) FROM
generate_series(1,100000000) x(x);
ALTER TABLE sale ADD CONSTRAINT sale_pkey PRIMARY KEY(sale_id);
test=# SELECT count(sale.sale_id) FROM sale, product;
count
-------------
10000000000
(1 row)
Time: 10323.053 ms
And if I disable the optimisation:
test=# set enable_earlygrouping = off;
SET
Time: 0.302 ms
test=# SELECT count(sale.sale_id) FROM sale, product;
count
-------------
10000000000
(1 row)
Time: 775790.764 ms
So, in this probably rather unlikely query, we get something around a 7500%
performance increase. Of course as the ratio of groups per underlying
tuples increase, the performance increase will tail off.
The explain output from the optimised version is as follows:
QUERY PLAN
------------------------------------------------------------------------------------
Finalize Aggregate (cost=1790544.37..1790544.38 rows=1 width=4)
-> Nested Loop (cost=1790541.10..1790544.12 rows=100 width=4)
-> Partial Aggregate (cost=1790541.10..1790541.11 rows=1 width=4)
-> Seq Scan on sale (cost=0.00..1540541.08 rows=100000008
width=4)
-> Seq Scan on product (cost=0.00..2.00 rows=100 width=0)
I also know that Tom is making a seriously big change to allow subquery
path-ification. I believe my changes in the areas he'll be touching are
quite small, and I'm not predicting too big a conflict when his changes are
pushed. I may of course be wrong about that.
Any constructive comments are welcome.
--
David Rowley http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/>
PostgreSQL Development, 24x7 Support, Training & Services
Attachments:
group_before_join_2015-09-28_14a3ec0.patchapplication/octet-stream; name=group_before_join_2015-09-28_14a3ec0.patchDownload
diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml
index eaa410b..f0e4407 100644
--- a/doc/src/sgml/ref/create_aggregate.sgml
+++ b/doc/src/sgml/ref/create_aggregate.sgml
@@ -27,6 +27,7 @@ CREATE AGGREGATE <replaceable class="parameter">name</replaceable> ( [ <replacea
[ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
[ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
[ , FINALFUNC_EXTRA ]
+ [ , CFUNC = <replaceable class="PARAMETER">cfunc</replaceable> ]
[ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
[ , MSFUNC = <replaceable class="PARAMETER">msfunc</replaceable> ]
[ , MINVFUNC = <replaceable class="PARAMETER">minvfunc</replaceable> ]
@@ -45,6 +46,7 @@ CREATE AGGREGATE <replaceable class="parameter">name</replaceable> ( [ [ <replac
[ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
[ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
[ , FINALFUNC_EXTRA ]
+ [ , CFUNC = <replaceable class="PARAMETER">cfunc</replaceable> ]
[ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
[ , HYPOTHETICAL ]
)
@@ -58,6 +60,7 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
[ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
[ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
[ , FINALFUNC_EXTRA ]
+ [ , CFUNC = <replaceable class="PARAMETER">cfunc</replaceable> ]
[ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
[ , MSFUNC = <replaceable class="PARAMETER">msfunc</replaceable> ]
[ , MINVFUNC = <replaceable class="PARAMETER">minvfunc</replaceable> ]
@@ -105,12 +108,15 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
functions:
a state transition function
<replaceable class="PARAMETER">sfunc</replaceable>,
- and an optional final calculation function
- <replaceable class="PARAMETER">ffunc</replaceable>.
+ an optional final calculation function
+ <replaceable class="PARAMETER">ffunc</replaceable>,
+ and an optional combine function
+ <replaceable class="PARAMETER">cfunc</replaceable>.
These are used as follows:
<programlisting>
<replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state
<replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value
+<replaceable class="PARAMETER">cfunc</replaceable>( internal-state, internal-state ) ---> next-internal-state
</programlisting>
</para>
@@ -128,6 +134,13 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
</para>
<para>
+ An aggregate function may also supply a combining function, which allows
+ the aggregation process to be broken down into multiple steps. This
+ facilitates query optimization techniques such as parallel query,
+ pre-join aggregation and aggregation while sorting.
+ </para>
+
+ <para>
An aggregate function can provide an initial condition,
that is, an initial value for the internal state value.
This is specified and stored in the database as a value of type
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 121c27f..820d982 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -57,6 +57,7 @@ AggregateCreate(const char *aggName,
Oid variadicArgType,
List *aggtransfnName,
List *aggfinalfnName,
+ List *aggcombinefnName,
List *aggmtransfnName,
List *aggminvtransfnName,
List *aggmfinalfnName,
@@ -77,6 +78,7 @@ AggregateCreate(const char *aggName,
Form_pg_proc proc;
Oid transfn;
Oid finalfn = InvalidOid; /* can be omitted */
+ Oid combinefn = InvalidOid; /* can be omitted */
Oid mtransfn = InvalidOid; /* can be omitted */
Oid minvtransfn = InvalidOid; /* can be omitted */
Oid mfinalfn = InvalidOid; /* can be omitted */
@@ -396,6 +398,20 @@ AggregateCreate(const char *aggName,
}
Assert(OidIsValid(finaltype));
+ /* handle the combinefn, if supplied */
+ if (aggcombinefnName)
+ {
+ /*
+ * Combine function must have 2 argument, each of which is the
+ * trans type
+ */
+ fnArgs[0] = aggTransType;
+ fnArgs[1] = aggTransType;
+
+ combinefn = lookup_agg_function(aggcombinefnName, 2, fnArgs,
+ variadicArgType, &finaltype);
+ }
+
/*
* If finaltype (i.e. aggregate return type) is polymorphic, inputs must
* be polymorphic also, else parser will fail to deduce result type.
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 894c89d..035882e 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -61,6 +61,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
char aggKind = AGGKIND_NORMAL;
List *transfuncName = NIL;
List *finalfuncName = NIL;
+ List *combinefuncName = NIL;
List *mtransfuncName = NIL;
List *minvtransfuncName = NIL;
List *mfinalfuncName = NIL;
@@ -124,6 +125,8 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
transfuncName = defGetQualifiedName(defel);
else if (pg_strcasecmp(defel->defname, "finalfunc") == 0)
finalfuncName = defGetQualifiedName(defel);
+ else if (pg_strcasecmp(defel->defname, "cfunc") == 0)
+ combinefuncName = defGetQualifiedName(defel);
else if (pg_strcasecmp(defel->defname, "msfunc") == 0)
mtransfuncName = defGetQualifiedName(defel);
else if (pg_strcasecmp(defel->defname, "minvfunc") == 0)
@@ -383,6 +386,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
variadicArgType,
transfuncName, /* step function name */
finalfuncName, /* final function name */
+ combinefuncName, /* combine function name */
mtransfuncName, /* fwd trans function name */
minvtransfuncName, /* inv trans function name */
mfinalfuncName, /* final function name */
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index f0d9e94..df9d2e8 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -904,25 +904,38 @@ ExplainNode(PlanState *planstate, List *ancestors,
pname = sname = "Group";
break;
case T_Agg:
- sname = "Aggregate";
- switch (((Agg *) plan)->aggstrategy)
{
- case AGG_PLAIN:
- pname = "Aggregate";
- strategy = "Plain";
- break;
- case AGG_SORTED:
- pname = "GroupAggregate";
- strategy = "Sorted";
- break;
- case AGG_HASHED:
- pname = "HashAggregate";
- strategy = "Hashed";
- break;
- default:
- pname = "Aggregate ???";
- strategy = "???";
- break;
+ char *modifier;
+ Agg *agg = (Agg *) plan;
+
+ sname = "Aggregate";
+
+ if (agg->finalizeAggs == false)
+ modifier = "Partial ";
+ else if (agg->combineStates == true)
+ modifier = "Finalize ";
+ else
+ modifier = "";
+
+ switch (agg->aggstrategy)
+ {
+ case AGG_PLAIN:
+ pname = psprintf("%sAggregate", modifier);
+ strategy = "Plain";
+ break;
+ case AGG_SORTED:
+ pname = psprintf("%sGroupAggregate", modifier);
+ strategy = "Sorted";
+ break;
+ case AGG_HASHED:
+ pname = psprintf("%sHashAggregate", modifier);
+ strategy = "Hashed";
+ break;
+ default:
+ pname = "Aggregate ???";
+ strategy = "???";
+ break;
+ }
}
break;
case T_WindowAgg:
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 2e36855..0336220 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3,15 +3,24 @@
* nodeAgg.c
* Routines to handle aggregate nodes.
*
- * ExecAgg evaluates each aggregate in the following steps:
+ * ExecAgg normally evaluates each aggregate in the following steps:
*
* transvalue = initcond
* foreach input_tuple do
* transvalue = transfunc(transvalue, input_value(s))
* result = finalfunc(transvalue, direct_argument(s))
*
- * If a finalfunc is not supplied then the result is just the ending
- * value of transvalue.
+ * If a finalfunc is not supplied or finalizeAggs is false, then the result
+ * is just the ending value of transvalue.
+ *
+ * If combineStates is true then we assume that input values are other
+ * transvalues. In this case we use the aggregate's combinefunc to 'add'
+ * the passed in transvalue to the transvalue being operated on. This allows
+ * aggregation to happen in multiple stages. 'combineStates' will only be
+ * true if another nodeAgg is below this one in the plan tree.
+ *
+ * 'finalizeAggs' should be false for all nodeAggs apart from the upper most
+ * one in the plan tree.
*
* If a normal aggregate call specifies DISTINCT or ORDER BY, we sort the
* input tuples and eliminate duplicates (if required) before performing
@@ -197,7 +206,7 @@ typedef struct AggStatePerTransData
*/
int numTransInputs;
- /* Oid of the state transition function */
+ /* Oid of the state transition or combine function */
Oid transfn_oid;
/* Oid of state value's datatype */
@@ -209,8 +218,8 @@ typedef struct AggStatePerTransData
List *aggdirectargs; /* states of direct-argument expressions */
/*
- * fmgr lookup data for transition function. Note in particular that the
- * fn_strict flag is kept here.
+ * fmgr lookup data for transition function or combination function. Note
+ * in particular that the fn_strict flag is kept here.
*/
FmgrInfo transfn;
@@ -421,6 +430,10 @@ static void advance_transition_function(AggState *aggstate,
AggStatePerTrans pertrans,
AggStatePerGroup pergroupstate);
static void advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup);
+static void advance_combination_function(AggState *aggstate,
+ AggStatePerTrans pertrans,
+ AggStatePerGroup pergroupstate);
+static void combine_aggregates(AggState *aggstate, AggStatePerGroup pergroup);
static void process_ordered_aggregate_single(AggState *aggstate,
AggStatePerTrans pertrans,
AggStatePerGroup pergroupstate);
@@ -796,6 +809,8 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
int numGroupingSets = Max(aggstate->phase->numsets, 1);
int numTrans = aggstate->numtrans;
+ Assert(!aggstate->combineStates);
+
for (transno = 0; transno < numTrans; transno++)
{
AggStatePerTrans pertrans = &aggstate->pertrans[transno];
@@ -879,6 +894,109 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
}
}
+static void
+combine_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
+{
+ int transno;
+ int numTrans = aggstate->numtrans;
+
+ /* combine not supported with grouping sets */
+ Assert(aggstate->phase->numsets == 0);
+ Assert(aggstate->combineStates);
+
+ for (transno = 0; transno < numTrans; transno++)
+ {
+ AggStatePerTrans pertrans = &aggstate->pertrans[transno];
+ TupleTableSlot *slot;
+ FunctionCallInfo fcinfo = &pertrans->transfn_fcinfo;
+ AggStatePerGroup pergroupstate = &pergroup[transno];
+
+ /* Evaluate the current input expressions for this aggregate */
+ slot = ExecProject(pertrans->evalproj, NULL);
+ Assert(slot->tts_nvalid >= 1);
+
+ fcinfo->arg[1] = slot->tts_values[0];
+ fcinfo->argnull[1] = slot->tts_isnull[0];
+
+ advance_combination_function(aggstate, pertrans, pergroupstate);
+ }
+}
+
+/*
+ * Perform combination of states between 2 aggregate states. Effectively this
+ * 'adds' two states together by whichever logic is defined in the aggregate
+ * function's combine function.
+ *
+ * Note that in this case transfn is set to the combination function. This
+ * perhaps should be changed to avoid confusion, but one field is ok for now
+ * as they'll never be needed at the same time.
+ */
+static void
+advance_combination_function(AggState *aggstate,
+ AggStatePerTrans pertrans,
+ AggStatePerGroup pergroupstate)
+{
+ FunctionCallInfo fcinfo = &pertrans->transfn_fcinfo;
+ MemoryContext oldContext;
+ Datum newVal;
+
+ if (pertrans->transfn.fn_strict)
+ {
+ /* if we're asked to merge to a NULL state, then do nothing */
+ if (fcinfo->argnull[1])
+ return;
+
+ if (pergroupstate->noTransValue)
+ {
+ pergroupstate->transValue = fcinfo->arg[1];
+ pergroupstate->transValueIsNull = false;
+ return;
+ }
+ }
+
+ /* We run the combine functions in per-input-tuple memory context */
+ oldContext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
+
+ /* set up aggstate->curpertrans for AggGetAggref() */
+ aggstate->curpertrans = pertrans;
+
+ /*
+ * OK to call the combine function
+ */
+ fcinfo->arg[0] = pergroupstate->transValue;
+ fcinfo->argnull[0] = pergroupstate->transValueIsNull;
+ fcinfo->isnull = false; /* just in case combine func doesn't set it */
+
+ newVal = FunctionCallInvoke(fcinfo);
+
+ aggstate->curpertrans = NULL;
+
+ /*
+ * If pass-by-ref datatype, must copy the new value into aggcontext and
+ * pfree the prior transValue. But if the combine function returned a
+ * pointer to its first input, we don't need to do anything.
+ */
+ if (!pertrans->transtypeByVal &&
+ DatumGetPointer(newVal) != DatumGetPointer(pergroupstate->transValue))
+ {
+ if (!fcinfo->isnull)
+ {
+ MemoryContextSwitchTo(aggstate->aggcontexts[aggstate->current_set]->ecxt_per_tuple_memory);
+ newVal = datumCopy(newVal,
+ pertrans->transtypeByVal,
+ pertrans->transtypeLen);
+ }
+ if (!pergroupstate->transValueIsNull)
+ pfree(DatumGetPointer(pergroupstate->transValue));
+ }
+
+ pergroupstate->transValue = newVal;
+ pergroupstate->transValueIsNull = fcinfo->isnull;
+
+ MemoryContextSwitchTo(oldContext);
+
+}
+
/*
* Run the transition function for a DISTINCT or ORDER BY aggregate
@@ -1278,8 +1396,14 @@ finalize_aggregates(AggState *aggstate,
pergroupstate);
}
- finalize_aggregate(aggstate, peragg, pergroupstate,
- &aggvalues[aggno], &aggnulls[aggno]);
+ if (aggstate->finalizeAggs)
+ finalize_aggregate(aggstate, peragg, pergroupstate,
+ &aggvalues[aggno], &aggnulls[aggno]);
+ else
+ {
+ aggvalues[aggno] = pergroupstate->transValue;
+ aggnulls[aggno] = pergroupstate->transValueIsNull;
+ }
}
}
@@ -1294,9 +1418,11 @@ project_aggregates(AggState *aggstate)
ExprContext *econtext = aggstate->ss.ps.ps_ExprContext;
/*
- * Check the qual (HAVING clause); if the group does not match, ignore it.
+ * iif performing the final aggregate stage we'll check the qual (HAVING
+ * clause); if the group does not match, ignore it.
*/
- if (ExecQual(aggstate->ss.ps.qual, econtext, false))
+ if (aggstate->finalizeAggs == false ||
+ ExecQual(aggstate->ss.ps.qual, econtext, false))
{
/*
* Form and return or store a projection tuple using the aggregate
@@ -1811,7 +1937,10 @@ agg_retrieve_direct(AggState *aggstate)
*/
for (;;)
{
- advance_aggregates(aggstate, pergroup);
+ if (!aggstate->combineStates)
+ advance_aggregates(aggstate, pergroup);
+ else
+ combine_aggregates(aggstate, pergroup);
/* Reset per-input-tuple context after each tuple */
ResetExprContext(tmpcontext);
@@ -1919,7 +2048,10 @@ agg_fill_hash_table(AggState *aggstate)
entry = lookup_hash_entry(aggstate, outerslot);
/* Advance the aggregates */
- advance_aggregates(aggstate, entry->pergroup);
+ if (!aggstate->combineStates)
+ advance_aggregates(aggstate, entry->pergroup);
+ else
+ combine_aggregates(aggstate, entry->pergroup);
/* Reset per-input-tuple context after each tuple */
ResetExprContext(tmpcontext);
@@ -2051,6 +2183,8 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
aggstate->pertrans = NULL;
aggstate->curpertrans = NULL;
aggstate->agg_done = false;
+ aggstate->combineStates = node->combineStates;
+ aggstate->finalizeAggs = node->finalizeAggs;
aggstate->input_done = false;
aggstate->pergroup = NULL;
aggstate->grp_firstTuple = NULL;
@@ -2402,7 +2536,21 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
get_func_name(aggref->aggfnoid));
InvokeFunctionExecuteHook(aggref->aggfnoid);
- transfn_oid = aggform->aggtransfn;
+ /*
+ * if this aggregation is performing state combines, then instead of
+ * using the transition function, we'll use the combine function
+ */
+ if (aggstate->combineStates)
+ {
+ transfn_oid = aggform->aggcombinefn;
+
+ /* If not set then the planner messed up */
+ if (!OidIsValid(transfn_oid))
+ elog(ERROR, "combinefn not set during aggregate state combine phase");
+ }
+ else
+ transfn_oid = aggform->aggtransfn;
+
peragg->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
/* Check that aggregate owner has permission to call component fns */
@@ -2583,44 +2731,69 @@ build_pertrans_for_aggref(AggStatePerTrans pertrans,
pertrans->numTransInputs = numArguments;
/*
- * Set up infrastructure for calling the transfn
+ * When combining states, we have no use at all for the aggregate
+ * function's transfn. Instead we use the combinefn. However we do
+ * reuse the transfnexpr for the combinefn, perhaps this should change
*/
- build_aggregate_transfn_expr(inputTypes,
- numArguments,
- numDirectArgs,
- aggref->aggvariadic,
- aggtranstype,
- aggref->inputcollid,
- aggtransfn,
- InvalidOid, /* invtrans is not needed here */
- &transfnexpr,
- NULL);
- fmgr_info(aggtransfn, &pertrans->transfn);
- fmgr_info_set_expr((Node *) transfnexpr, &pertrans->transfn);
-
- InitFunctionCallInfoData(pertrans->transfn_fcinfo,
- &pertrans->transfn,
- pertrans->numTransInputs + 1,
- pertrans->aggCollation,
- (void *) aggstate, NULL);
+ if (aggstate->combineStates)
+ {
+ build_aggregate_combinefn_expr(aggref->aggvariadic,
+ aggtranstype,
+ aggref->inputcollid,
+ aggtransfn,
+ &transfnexpr);
+ fmgr_info(aggtransfn, &pertrans->transfn);
+ fmgr_info_set_expr((Node *) transfnexpr, &pertrans->transfn);
+
+ InitFunctionCallInfoData(pertrans->transfn_fcinfo,
+ &pertrans->transfn,
+ 2,
+ pertrans->aggCollation,
+ (void *) aggstate, NULL);
- /*
- * If the transfn is strict and the initval is NULL, make sure input type
- * and transtype are the same (or at least binary-compatible), so that
- * it's OK to use the first aggregated input value as the initial
- * transValue. This should have been checked at agg definition time, but
- * we must check again in case the transfn's strictness property has been
- * changed.
- */
- if (pertrans->transfn.fn_strict && pertrans->initValueIsNull)
+ }
+ else
{
- if (numArguments <= numDirectArgs ||
- !IsBinaryCoercible(inputTypes[numDirectArgs],
- aggtranstype))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("aggregate %u needs to have compatible input type and transition type",
- aggref->aggfnoid)));
+ /*
+ * Set up infrastructure for calling the transfn
+ */
+ build_aggregate_transfn_expr(inputTypes,
+ numArguments,
+ numDirectArgs,
+ aggref->aggvariadic,
+ aggtranstype,
+ aggref->inputcollid,
+ aggtransfn,
+ InvalidOid, /* invtrans is not needed here */
+ &transfnexpr,
+ NULL);
+ fmgr_info(aggtransfn, &pertrans->transfn);
+ fmgr_info_set_expr((Node *) transfnexpr, &pertrans->transfn);
+
+ InitFunctionCallInfoData(pertrans->transfn_fcinfo,
+ &pertrans->transfn,
+ pertrans->numTransInputs + 1,
+ pertrans->aggCollation,
+ (void *) aggstate, NULL);
+
+ /*
+ * If the transfn is strict and the initval is NULL, make sure input type
+ * and transtype are the same (or at least binary-compatible), so that
+ * it's OK to use the first aggregated input value as the initial
+ * transValue. This should have been checked at agg definition time, but
+ * we must check again in case the transfn's strictness property has been
+ * changed.
+ */
+ if (pertrans->transfn.fn_strict && pertrans->initValueIsNull)
+ {
+ if (numArguments <= numDirectArgs ||
+ !IsBinaryCoercible(inputTypes[numDirectArgs],
+ aggtranstype))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("aggregate %u needs to have compatible input type and transition type",
+ aggref->aggfnoid)));
+ }
}
/* get info about the state value's datatype */
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8fc1cfd..3040ca7 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -1426,8 +1426,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Generate the plan for the subquery */
rel->subplan = subquery_planner(root->glob, subquery,
root,
- false, tuple_fraction,
- &subroot);
+ false, !rel->partial_aggregate,
+ tuple_fraction, &subroot);
rel->subroot = subroot;
/* Isolate the params needed by this specific subplan */
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index d107d76..6f0a6f6 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -118,6 +118,7 @@ bool enable_nestloop = true;
bool enable_material = true;
bool enable_mergejoin = true;
bool enable_hashjoin = true;
+bool enable_earlygrouping = true;
typedef struct
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index a35c881..c177ca5 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -16,11 +16,25 @@
#include <math.h>
+#include "access/htup_details.h"
+#include "catalog/pg_aggregate.h"
#include "executor/executor.h"
#include "foreign/fdwapi.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
+#include "optimizer/planmain.h"
+#include "optimizer/planner.h"
+#include "optimizer/tlist.h"
+#include "optimizer/var.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_oper.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteManip.h"
+#include "utils/syscache.h"
/* Hook for plugins to get control in add_paths_to_joinrel() */
set_join_pathlist_hook_type set_join_pathlist_hook = NULL;
@@ -1474,3 +1488,594 @@ select_mergejoin_clauses(PlannerInfo *root,
return result_list;
}
+
+static List *
+remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved)
+{
+ List *result = NIL;
+ ListCell *jl;
+
+ foreach(jl, joinlist)
+ {
+ Node *jlnode = (Node *) lfirst(jl);
+
+ if (IsA(jlnode, RangeTblRef))
+ {
+ int varno = ((RangeTblRef *) jlnode)->rtindex;
+
+ if (varno == relid)
+ (*nremoved)++;
+ else
+ result = lappend(result, jlnode);
+ }
+ else if (IsA(jlnode, List))
+ {
+ /* Recurse to handle subproblem */
+ List *sublist;
+
+ sublist = remove_rel_from_joinlist((List *) jlnode,
+ relid, nremoved);
+ /* Avoid including empty sub-lists in the result */
+ if (sublist)
+ result = lappend(result, sublist);
+ }
+ else
+ {
+ elog(ERROR, "unrecognized joinlist node type: %d",
+ (int) nodeTag(jlnode));
+ }
+ }
+
+ return result;
+}
+
+static Expr *
+substitute_expr_vars(Expr *expr, int srcrelid, int tarrelid)
+{
+ Expr *newexpr = (Expr *) copyObject(expr);
+ List *vars = pull_vars_of_level((Node *) newexpr, 0);
+ ListCell *l;
+
+ foreach(l, vars)
+ {
+ Var *var = lfirst(l);
+ if (var->varno == srcrelid)
+ var->varno = tarrelid;
+ }
+ return newexpr;
+}
+
+static void
+duplicate_eclass_exprs(PlannerInfo *root, int srcrelid, int tarrelid, List *tlist)
+{
+ ListCell *lc;
+
+ foreach(lc, root->eq_classes)
+ {
+ EquivalenceClass *ec = lfirst(lc);
+ ListCell *l2;
+ List *newmembers = NIL;
+
+ /* Skip classes which don't contain any members for srcrelid */
+ if (!bms_is_member(srcrelid, ec->ec_relids))
+ continue;
+
+ /*
+ * Now look at each member to find out if there's Vars for the
+ * srcrelid. All expression which have Vars belonging to the source
+ * rel must be copied, and the Vars replaced with Vars from the target
+ * relation.
+ */
+ foreach(l2, ec->ec_members)
+ {
+ EquivalenceMember *em = lfirst(l2);
+ Expr *newexpr;
+ EquivalenceMember *newem;
+ ListCell *l3;
+ List *vars;
+
+ /*
+ * No point in looking at Consts, and skip if no Vars belong to
+ * srcrelid.
+ */
+ if (em->em_is_const || !bms_is_member(srcrelid, em->em_relids))
+ continue;
+
+ newexpr = (Expr *) copyObject(em->em_expr);
+ vars = pull_vars_of_level((Node *) newexpr, 0);
+
+ foreach(l3, vars)
+ {
+ Var *var = lfirst(l3);
+ if (var->varno == srcrelid)
+ {
+ AttrNumber resno = 1;
+ ListCell *l4;
+ bool found = false;
+ foreach(l4, tlist)
+ {
+ Expr *expr = lfirst(l4);
+
+ if (equal(expr, em->em_expr))
+ {
+ var->varattno = resno;
+ found = true;
+ break;
+ }
+
+ resno++;
+ }
+
+ if (!found)
+ elog(ERROR, "missing item from tlist");
+
+ var->varno = tarrelid;
+ }
+ }
+
+ /*
+ * Ok, now we just need to generate a new member for the new
+ * expression. We can't append it to the members list of the eclass
+ * just yet, as we're looping over it. Instead we'll just generate
+ * a new list of members to append, and we'll do that after we're
+ * done looping.
+ */
+ newem = makeNode(EquivalenceMember);
+ newem->em_datatype = em->em_datatype;
+ newem->em_expr = newexpr;
+ newem->em_is_child = em->em_is_child;
+ newem->em_is_const = false;
+ newem->em_nullable_relids = bms_copy(em->em_nullable_relids);
+ newem->em_relids = pull_varnos((Node *) newexpr);
+
+ newmembers = lappend(newmembers, newem);
+ }
+
+ /* There should be new members, let's check anyway... */
+ if (newmembers != NIL)
+ {
+ ec->ec_members = list_concat(ec->ec_members, newmembers);
+ ec->ec_relids = bms_add_member(ec->ec_relids, tarrelid);
+ }
+ }
+}
+
+/*
+ * setup_grouping_rel
+ * Build a new subquery RTE which performs the aggregation required of the
+ * outer query, but without joining to any of the rels which are not
+ * required of the aggregate functions.
+ *
+ * Note: For now this removes the original RTE from the joinlist. This will
+ * be changed in a more complete version.
+ */
+List *
+setup_grouping_rel(PlannerInfo *root, List *joinlist)
+{
+ ListCell *lc;
+ int aggregaterel = root->aggregaterel;
+ int groupbyrel = root->groupbyrel;
+ int groupbeforejoinrel = root->groupbeforejoinrel;
+ List *joinconditions = NIL;
+ List *groupexprs = NIL;
+ Query *parse;
+ RangeTblEntry *rte;
+ RangeTblEntry *subrte;
+ RangeTblRef *rtr;
+ RelOptInfo *rel;
+ List *sub_tlist;
+ List *tlist;
+ List *oldtlist;
+ AttrNumber resno;
+ int nremoved;
+
+ /*
+ * If the query's GROUP BY clause does not contain columns from the
+ * aggregate relation, then we must be sure that the GROUP BY clause is
+ * functionally dependent on the join clause to that relation. The reason
+ * for this is that we must ensure that we do not perform a partial
+ * grouping which creates a more tightly grouped set than what is required
+ * of the query's main GROUP BY clause. We can test this simply by looking
+ * for a unique index in the join expressions to the grouprel. If we find
+ * a supporting unique index then we can be certain that early grouping is
+ * legal.
+ */
+ if (groupbyrel != 0 && groupbyrel != aggregaterel)
+ {
+ RelOptInfo *grouprel = find_base_rel(root, groupbyrel);
+
+ foreach(lc, joinlist)
+ {
+ RangeTblRef *rtr = (RangeTblRef *) lfirst(lc);
+ RelOptInfo *joinrel;
+ List *joininfo;
+ Relids joinrelids;
+
+ if (!IsA(rtr, RangeTblRef) || rtr->rtindex == groupbyrel)
+ continue;
+
+ joinrel = find_base_rel(root, rtr->rtindex);
+ joinrelids = bms_union(joinrel->relids, grouprel->relids);
+
+ joininfo = generate_join_implied_equalities(root, joinrelids,
+ joinrel->relids, grouprel);
+
+ if (joininfo != NIL)
+ {
+ ListCell *lc2;
+
+ foreach(lc2, joininfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc2);
+ Node *expr;
+ clause_sides_match_join(rinfo, joinrel, grouprel);
+
+ if (rinfo->outer_is_left)
+ expr = get_rightop(rinfo->clause);
+ else
+ expr = get_leftop(rinfo->clause);
+
+ groupexprs = list_append_unique(groupexprs, expr);
+
+ joinconditions = lappend(joinconditions, rinfo);
+ }
+ }
+ }
+
+ /* If no unique index is found then we cannot perform the group before join */
+ if (!relation_has_unique_index_for(root, grouprel, joinconditions, NIL, NIL))
+ return joinlist;
+
+ foreach(lc, joinconditions)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+ Node *rexpr;
+
+ if (rinfo->outer_is_left)
+ rexpr = get_rightop(rinfo->clause);
+ else
+ rexpr = get_leftop(rinfo->clause);
+
+ groupexprs = list_append_unique(groupexprs, rexpr);
+ }
+ }
+ else if (root->parse->groupClause)
+ {
+ List *tempexprs = get_sortgrouplist_exprs(root->parse->groupClause,
+ root->parse->targetList);
+
+ /*
+ * All grouping expressions come from the aggregate relation. We can
+ * simply just copy these expressions and replace the varno of each
+ * var with the first RTE in the new subquery that we're creating
+ */
+ foreach(lc, tempexprs)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+ Expr *newexpr = substitute_expr_vars(expr, aggregaterel, 1);
+ groupexprs = lappend(groupexprs, newexpr);
+ }
+ }
+
+ parse = makeNode(Query);
+ /************ The following code is still under construction! ************/
+ rte = copyObject(root->simple_rte_array[aggregaterel]);
+ rte->rtekind = RTE_SUBQUERY;
+ rte->subquery = parse;
+ root->simple_rte_array[groupbeforejoinrel] = rte;
+ list_nth_cell(root->parse->rtable, groupbeforejoinrel - 1)->data.ptr_value = rte;
+
+ rel = build_simple_rel(root, groupbeforejoinrel, RELOPT_BASEREL);
+ rel->rtekind = RTE_SUBQUERY;
+ rel->partial_aggregate = true;
+ root->simple_rel_array[groupbeforejoinrel] = rel;
+
+ subrte = copyObject(root->simple_rte_array[aggregaterel]);
+
+ parse->rtable = list_make1(subrte);
+ parse->jointree = makeNode(FromExpr);
+ parse->canSetTag = true;
+ parse->hasAggs = root->parse->hasAggs;
+
+ joinlist = remove_rel_from_joinlist(joinlist, aggregaterel, &nremoved);
+
+ rtr = makeNode(RangeTblRef);
+ rtr->rtindex = groupbeforejoinrel;
+ joinlist = lappend(joinlist, rtr);
+ /* for now don't bother considering the non group before join version */
+ root->simple_rel_array[aggregaterel]->reloptkind = RELOPT_DEADREL;
+
+ oldtlist = tlist = sub_tlist = NIL;
+ resno = 1;
+
+ foreach(lc, root->parse->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ Relids relids = pull_varnos((Node *) tle->expr);
+
+ if (bms_is_member(aggregaterel, relids))
+ {
+ TargetEntry *newtle;
+ Var *var;
+ Expr *newexpr;
+
+ /* We should have ensured this earlier */
+ Assert(bms_membership(relids) == BMS_SINGLETON);
+
+ newexpr = substitute_expr_vars(tle->expr, aggregaterel, 1);
+ newtle = makeTargetEntry(newexpr, resno, tle->resname ? pstrdup(tle->resname) : NULL, false);
+
+ sub_tlist = lappend(sub_tlist, newtle);
+ /* we need to copy this as the tle will be modified later */
+ oldtlist = lappend(oldtlist, copyObject(tle->expr));
+ var = makeVarFromTargetEntry(groupbeforejoinrel, newtle);
+ tlist = lappend(tlist, var);
+ resno++;
+ }
+ }
+
+ parse->targetList = sub_tlist;
+ add_vars_to_targetlist(root, tlist, bms_make_singleton(0), false);
+
+ pull_varattnos((Node *) sub_tlist, aggregaterel, &rte->selectedCols);
+
+ foreach(lc, groupexprs)
+ {
+ Node *node = (Node *) lfirst(lc);
+ TargetEntry *tle;
+ Oid sortop;
+ Oid eqop;
+ bool hashable;
+ SortGroupClause *sgc;
+
+ /* determine the eqop and optional sortop */
+ get_sort_group_operators(exprType(node),
+ false, true, false,
+ &sortop, &eqop, NULL,
+ &hashable);
+
+ tle = tlist_member(node, parse->targetList);
+
+ if (!tle) /* shouldn't happen */
+ elog(ERROR, "failed to find group expression in subplan tlist");
+
+ sgc = makeNode(SortGroupClause);
+ sgc->eqop = eqop;
+ sgc->hashable = hashable;
+ sgc->nulls_first = false;
+ sgc->sortop = sortop;
+ tle->ressortgroupref = sgc->tleSortGroupRef = assignSortGroupRef(tle, parse->targetList);
+
+ parse->groupClause = lappend(parse->groupClause, sgc);
+ }
+
+ rtr = makeNode(RangeTblRef);
+ rtr->rtindex = 1;
+ parse->jointree->fromlist = list_make1(rtr);
+ parse->resultRelation = 0;
+ parse->commandType = CMD_SELECT;
+
+ //root->parse = (Query *) ReplaceVarsFromTargetList((Node *) root->parse,
+ // aggregaterel, 0,
+ // rt_fetch(aggregaterel, root->parse->rtable),
+ // parse->targetList,
+ // REPLACEVARS_SUBSTITUTE_NULL, 0,
+ // &parse->hasSubLinks);
+
+ //root->parse->groupClause = NIL;
+ /*** TEMP HACKS SECTION ***/
+ {
+ List *vars = pull_vars_of_level((Node *) root->parse->targetList, 0);
+ foreach(lc, vars)
+ {
+ Var *v = lfirst(lc);
+ if (v->varno == aggregaterel)
+ v->varno = groupbeforejoinrel;
+ }
+ }
+
+ duplicate_eclass_exprs(root, aggregaterel, groupbeforejoinrel, oldtlist);
+ return joinlist;
+}
+
+/*
+ * prepare_group_before_join
+ * Phase 1 in determining if GROUP BY and/or aggregation may be possible
+ * before joining to other relations. This determination is performed in
+ * multiple phases. In this phase we perform some simple checks in order
+ * to eliminated cases where this is absolutely not possible.
+ * If we determine the optimization maybe possible we add a dummy item to
+ * the rtable so as to reserve a slot in the simple rel arrays for this
+ * alternative way of performing the query. We return false if group
+ * before join is absolutely not possible, and true if it may be possible.
+ */
+bool
+prepare_group_before_join(PlannerInfo *root, Query *parse)
+{
+ List *tlist = parse->targetList;
+ Index aggregaterel = 0;
+ Index grouprel = 0;
+ ListCell *l;
+ Relids relids;
+
+ /* bail if earlygrouping GUC is disabled */
+ if (!enable_earlygrouping)
+ return false;
+
+ /* Nothing to do for queries which don't need aggregation */
+ if (!parse->hasAggs &&
+ parse->havingQual == NULL &&
+ parse->groupClause == NIL)
+ return false;
+
+ /* No joins? It'll be hard to group before that! */
+ if (list_length(parse->rtable) == 1)
+ return false;
+
+ /* XXX do we need to disable in this case? */
+ if (expression_returns_set((Node *) parse->targetList))
+ return false;
+
+ /*
+ * For queries with aggregate functions, we're only able to perform this
+ * group before join optimization when all aggregate function parameters
+ * contain Vars from a single relation. If they do, then this relation will
+ * become the relation which we perform the grouping on.
+ */
+
+ /*
+ * We can skip performing any analysis on aggregate functions for queries
+ * which have none.
+ */
+ if (parse->hasAggs)
+ {
+ Index curaggrel;
+
+ foreach(l, tlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+ Aggref *aggref = (Aggref *) tle->expr;
+ Oid aggcombinefn;
+ HeapTuple aggTuple;
+ Form_pg_aggregate aggform;
+
+ /* Skip over anything that's not an Aggref */
+ if (!IsA(aggref, Aggref))
+ continue;
+
+ /*
+ * We're unable to apply the optimization if any of the aggregates
+ * have a DISTINCT or ORDER BY. The problem with this is that we
+ * currently have no infrastructure to ensure that the groups are
+ * not duplicated by some join that takes place after the grouping
+ * is performed.
+ */
+ if (aggref->aggdistinct != NIL || aggref->aggorder != NIL)
+ return false;
+
+ /*
+ * Now we must check if the aggregate function has a state combine
+ * function set. It's not possible to perform the final aggregate
+ * stage for aggregates which don't have one of these.
+ */
+ aggTuple = SearchSysCache1(AGGFNOID,
+ ObjectIdGetDatum(aggref->aggfnoid));
+ if (!HeapTupleIsValid(aggTuple))
+ elog(ERROR, "cache lookup failed for aggregate %u",
+ aggref->aggfnoid);
+ aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
+ aggcombinefn = aggform->aggcombinefn;
+ ReleaseSysCache(aggTuple);
+
+ if (!OidIsValid(aggcombinefn))
+ return false;
+
+ if (!aggref->aggstar)
+ {
+ relids = pull_varnos((Node *) aggref);
+
+ /*
+ * Check if the aggregate's parameters contain Vars from more
+ * than one rel, if it does then we'll need to disable the
+ * optimization, as we can only optimize cases where there's
+ * a single grouping relation.
+ */
+ if (relids && !bms_get_singleton_member(relids, &curaggrel))
+ return false;
+
+ bms_free(relids);
+
+ /*
+ * If we've found an Aggref where the parameter contains rels
+ * different from the last aggregate function call then we can't
+ * optimize this either, for the same reason as above.
+ */
+ if (aggregaterel != 0 && aggregaterel != curaggrel)
+ return false;
+
+ aggregaterel = curaggrel;
+ }
+ }
+ }
+
+ /*
+ * If the query has no aggs, or the aggs contain no vars, e.g COUNT(*),
+ * then we'll just select any relation as the one to aggregate on
+ */
+ if (aggregaterel == 0)
+ aggregaterel = 1;
+
+ /*
+ * We'll make another pass over the targetlist, this time we know which
+ * relation that we might like to try grouping on. We'll need to ensure
+ * that no targets expression exist which have Vars which belong to our
+ * aggregate relation, and also contain Vars which don't belong to our
+ * aggregate relation. We'll also ensure that any Vars belonging to the
+ * aggregate relation are also present in the GROUP BY clause. This is
+ * possible as the SQL standard does allow columns which are functionally
+ * dependant on the GROUP BY clause.
+ */
+ foreach(l, tlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+ Expr *expr = tle->expr;
+
+ /* Skip over any Aggrefs, we've already checked these above. */
+ if (IsA(expr, Aggref))
+ continue;
+
+ relids = pull_varnos((Node *) expr);
+
+ /* Does this expression require the aggregate relation? */
+ if (bms_is_member(aggregaterel, relids))
+ {
+ if (bms_membership(relids) == BMS_MULTIPLE)
+ return false;
+
+ if (tle->ressortgroupref == 0)
+ return false;
+ }
+ }
+
+ /*
+ * Now we'll have a look at the GROUP BY clause if one exists. We need to
+ * ensure that all of the grouping expressions only contain Vars from a
+ * single rel. This does not have to be the same rel as the one we may have
+ * seen in the Aggref parameters above. The reason for this is that later
+ * we need to perform some analysis to ensure that the GROUP BY columns are
+ * functionally dependant on the join clause to our grouping relation, and
+ * the only way we currently have to test functional dependency is by
+ * looking at unique indexes, and a unique index can only be on a single
+ * relation.
+ */
+ if (root->parse->groupClause != NIL)
+ {
+ List *groupExprs;
+ Relids grouprels;
+
+ groupExprs = get_sortgrouplist_exprs(parse->groupClause, tlist);
+ grouprels = pull_varnos((Node *) groupExprs);
+
+ if (!bms_get_singleton_member(grouprels, &grouprel))
+ return false;
+ }
+
+ /*
+ * At this stage we cannot be sure that performing GROUP BY early is
+ * actually possible as we still need to analyze the join condition for
+ * relations being joined to this one as we need to ensure we don't
+ * accidentally generate a smaller number of groups than is required of the
+ * main GROUP BY clause.
+ */
+ root->aggregaterel = aggregaterel;
+ root->groupbyrel = grouprel;
+ root->groupbeforejoinrel = list_length(parse->rtable) + 1;
+ /*
+ * Another thing we must also do now is add an item to the query's rtable.
+ * We'll need this so we can consider a grouped RelOptInfo during the join
+ * search. We need to do this before setup_simple_rel_arrays() is called.
+ */
+ parse->rtable = lappend(parse->rtable, makeNode(RangeTblEntry));
+
+ return true; /* Group before join may be possible */
+}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 404c6f5..3ccb5ef 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -1045,6 +1045,8 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
groupOperators,
NIL,
numGroups,
+ false,
+ true,
subplan);
}
else
@@ -4501,9 +4503,8 @@ 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)
+ List *groupingSets, long numGroups, bool combineStates,
+ bool finalizeAggs, Plan *lefttree)
{
Agg *node = makeNode(Agg);
Plan *plan = &node->plan;
@@ -4512,6 +4513,8 @@ make_agg(PlannerInfo *root, List *tlist, List *qual,
node->aggstrategy = aggstrategy;
node->numCols = numGroupCols;
+ node->combineStates = combineStates;
+ node->finalizeAggs = finalizeAggs;
node->grpColIdx = grpColIdx;
node->grpOperators = grpOperators;
node->numGroups = numGroups;
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 848df97..3651de6 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -20,11 +20,14 @@
*/
#include "postgres.h"
+#include "nodes/nodeFuncs.h"
#include "optimizer/orclauses.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/planmain.h"
+#include "optimizer/tlist.h"
+#include "optimizer/var.h"
/*
@@ -60,6 +63,7 @@ query_planner(PlannerInfo *root, List *tlist,
RelOptInfo *final_rel;
Index rti;
double total_pages;
+ bool try_group_before_join;
/*
* If the query has an empty join tree, then it's something easy like
@@ -107,6 +111,12 @@ query_planner(PlannerInfo *root, List *tlist,
root->initial_rels = NIL;
/*
+ * Determine if it might be possible to apply grouping of one relation
+ * before joining it to any other relations.
+ */
+ try_group_before_join = prepare_group_before_join(root, parse);
+
+ /*
* Make a flattened version of the rangetable for faster access (this is
* OK because the rangetable won't change any more), and set up an empty
* array for indexing base relations.
@@ -201,6 +211,10 @@ query_planner(PlannerInfo *root, List *tlist,
*/
extract_restriction_or_clauses(root);
+ /* run the final phase to determine if group before join is possible */
+ if (try_group_before_join)
+ joinlist = setup_grouping_rel(root, joinlist);
+
/*
* We should now have size estimates for every actual table involved in
* the query, and we also know which if any have been deleted from the
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 06be922..4861093 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -80,7 +80,8 @@ typedef struct
static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
static Plan *inheritance_planner(PlannerInfo *root);
-static Plan *grouping_planner(PlannerInfo *root, double tuple_fraction);
+static Plan *grouping_planner(PlannerInfo *root, double tuple_fraction,
+ bool finalize_aggregates);
static void preprocess_rowmarks(PlannerInfo *root);
static double preprocess_limit(PlannerInfo *root,
double tuple_fraction,
@@ -271,7 +272,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
/* primary planning entry point (may recurse for subqueries) */
top_plan = subquery_planner(glob, parse, NULL,
- false, tuple_fraction, &root);
+ false, true, tuple_fraction, &root);
/*
* If creating a plan for a scrollable cursor, make sure it can run
@@ -373,8 +374,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
Plan *
subquery_planner(PlannerGlobal *glob, Query *parse,
PlannerInfo *parent_root,
- bool hasRecursion, double tuple_fraction,
- PlannerInfo **subroot)
+ bool hasRecursion, bool finalize_aggregates,
+ double tuple_fraction, PlannerInfo **subroot)
{
PlannerInfo *root;
Plan *plan;
@@ -673,7 +674,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
plan = inheritance_planner(root);
else
{
- plan = grouping_planner(root, tuple_fraction);
+ plan = grouping_planner(root, tuple_fraction, finalize_aggregates);
/* If it's not SELECT, we need a ModifyTable node */
if (parse->commandType != CMD_SELECT)
{
@@ -1129,7 +1130,7 @@ inheritance_planner(PlannerInfo *root)
subroot.hasInheritedTarget = true;
/* Generate plan */
- subplan = grouping_planner(&subroot, 0.0 /* retrieve all tuples */ );
+ subplan = grouping_planner(&subroot, 0.0 /* retrieve all tuples */ ,true);
/*
* Planning may have modified the query result relation (if there were
@@ -1310,7 +1311,8 @@ inheritance_planner(PlannerInfo *root)
*--------------------
*/
static Plan *
-grouping_planner(PlannerInfo *root, double tuple_fraction)
+grouping_planner(PlannerInfo *root, double tuple_fraction,
+ bool finalize_aggregates)
{
Query *parse = root->parse;
List *tlist = parse->targetList;
@@ -1425,6 +1427,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
Path *cheapest_path;
Path *sorted_path;
Path *best_path;
+ bool combine_aggregates;
MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
@@ -1603,6 +1606,19 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
final_rel = query_planner(root, sub_tlist,
standard_qp_callback, &qp_extra);
+ if (bms_is_member(root->groupbeforejoinrel, final_rel->relids))
+ {
+ combine_aggregates = true;
+ tlist = (List *) ReplaceVarsFromTargetList((Node *) tlist,
+ root->aggregaterel, 0,
+ rt_fetch(root->aggregaterel, root->parse->rtable),
+ sub_tlist,
+ REPLACEVARS_SUBSTITUTE_NULL, 0,
+ &parse->hasSubLinks);
+ }
+ else
+ combine_aggregates = false;
+
/*
* Extract rowcount and width estimates for use below. If final_rel
* has been proven dummy, its rows estimate will be zero; clamp it to
@@ -1983,6 +1999,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
extract_grouping_ops(parse->groupClause),
NIL,
numGroups,
+ combine_aggregates,
+ finalize_aggregates,
result_plan);
/* Hashed aggregation produces randomly-ordered results */
current_pathkeys = NIL;
@@ -2294,6 +2312,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
extract_grouping_ops(parse->distinctClause),
NIL,
numDistinctRows,
+ false,
+ false,
result_plan);
/* Hashed aggregation produces randomly-ordered results */
current_pathkeys = NIL;
@@ -2527,6 +2547,8 @@ build_grouping_chain(PlannerInfo *root,
extract_grouping_ops(groupClause),
gsets,
numGroups,
+ false,
+ true,
sort_plan);
sort_plan->lefttree = NULL;
@@ -2563,6 +2585,8 @@ build_grouping_chain(PlannerInfo *root,
extract_grouping_ops(groupClause),
gsets,
numGroups,
+ false,
+ true,
result_plan);
((Agg *) result_plan)->chain = chain;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index d0bc412..9da104d 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -532,7 +532,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
*/
plan = subquery_planner(root->glob, subquery,
root,
- false, tuple_fraction,
+ false, true, tuple_fraction,
&subroot);
/* Isolate the params needed by this specific subplan */
@@ -571,7 +571,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
/* Generate the plan for the ANY subquery; we'll need all rows */
plan = subquery_planner(root->glob, subquery,
root,
- false, 0.0,
+ false, true, 0.0,
&subroot);
/* Isolate the params needed by this specific subplan */
@@ -1163,7 +1163,7 @@ SS_process_ctes(PlannerInfo *root)
*/
plan = subquery_planner(root->glob, subquery,
root,
- cte->cterecursive, 0.0,
+ cte->cterecursive, true, 0.0,
&subroot);
/*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 8884fb1..224c575 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -247,7 +247,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
*/
subplan = subquery_planner(root->glob, subquery,
root,
- false, tuple_fraction,
+ false, true, tuple_fraction,
&subroot);
/* Save subroot and subplan in RelOptInfo for setrefs.c */
@@ -775,6 +775,8 @@ make_union_unique(SetOperationStmt *op, Plan *plan,
extract_grouping_ops(groupList),
NIL,
numGroups,
+ false,
+ true,
plan);
/* Hashed aggregation produces randomly-ordered results */
*sortClauses = NIL;
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 5b0d568..1dde1a7 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -1929,6 +1929,43 @@ build_aggregate_transfn_expr(Oid *agg_input_types,
/*
* Like build_aggregate_transfn_expr, but creates an expression tree for the
+ * combine function of an aggregate, rather than the transition function.
+ */
+void
+build_aggregate_combinefn_expr(bool agg_variadic,
+ Oid agg_state_type,
+ Oid agg_input_collation,
+ Oid combinefn_oid,
+ Expr **combinefnexpr)
+{
+ Param *argp;
+ List *args;
+ FuncExpr *fexpr;
+
+ /* Build arg list to use in the combinefn FuncExpr node. */
+ argp = makeNode(Param);
+ argp->paramkind = PARAM_EXEC;
+ argp->paramid = -1;
+ argp->paramtype = agg_state_type;
+ argp->paramtypmod = -1;
+ argp->paramcollid = agg_input_collation;
+ argp->location = -1;
+
+ /* trans state type is arg 1 and 2 */
+ args = list_make2(argp, argp);
+
+ fexpr = makeFuncExpr(combinefn_oid,
+ agg_state_type,
+ args,
+ InvalidOid,
+ agg_input_collation,
+ COERCE_EXPLICIT_CALL);
+ fexpr->funcvariadic = agg_variadic;
+ *combinefnexpr = (Expr *) fexpr;
+}
+
+/*
+ * Like build_aggregate_transfn_expr, but creates an expression tree for the
* final function of an aggregate, rather than the transition function.
*/
void
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 17053af..87052f2 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -854,6 +854,15 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
{
+ { "enable_earlygrouping", PGC_USERSET, QUERY_TUNING_METHOD,
+ gettext_noop("Enables the planner's ability to perform GROUP BY before joining to unrelated tables."),
+ NULL
+ },
+ &enable_earlygrouping,
+ true,
+ NULL, NULL, NULL
+ },
+ {
{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
gettext_noop("Enables genetic query optimization."),
gettext_noop("This algorithm attempts to do planning without "
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7e75bc2..c180730 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12265,6 +12265,7 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
PGresult *res;
int i_aggtransfn;
int i_aggfinalfn;
+ int i_aggcombinefn;
int i_aggmtransfn;
int i_aggminvtransfn;
int i_aggmfinalfn;
@@ -12281,6 +12282,7 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
int i_convertok;
const char *aggtransfn;
const char *aggfinalfn;
+ const char *aggcombinefn;
const char *aggmtransfn;
const char *aggminvtransfn;
const char *aggmfinalfn;
@@ -12311,7 +12313,26 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
selectSourceSchema(fout, agginfo->aggfn.dobj.namespace->dobj.name);
/* Get aggregate-specific details */
- if (fout->remoteVersion >= 90400)
+ if (fout->remoteVersion >= 90600)
+ {
+ appendPQExpBuffer(query, "SELECT aggtransfn, "
+ "aggfinalfn, aggtranstype::pg_catalog.regtype, "
+ "aggcombinefn, aggmtransfn, aggminvtransfn, "
+ "aggmfinalfn, aggmtranstype::pg_catalog.regtype, "
+ "aggfinalextra, aggmfinalextra, "
+ "aggsortop::pg_catalog.regoperator, "
+ "(aggkind = 'h') AS hypothetical, "
+ "aggtransspace, agginitval, "
+ "aggmtransspace, aggminitval, "
+ "true AS convertok, "
+ "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs, "
+ "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs "
+ "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
+ "WHERE a.aggfnoid = p.oid "
+ "AND p.oid = '%u'::pg_catalog.oid",
+ agginfo->aggfn.dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 90400)
{
appendPQExpBuffer(query, "SELECT aggtransfn, "
"aggfinalfn, aggtranstype::pg_catalog.regtype, "
@@ -12421,6 +12442,7 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
i_aggtransfn = PQfnumber(res, "aggtransfn");
i_aggfinalfn = PQfnumber(res, "aggfinalfn");
+ i_aggcombinefn = PQfnumber(res, "aggcombinefn");
i_aggmtransfn = PQfnumber(res, "aggmtransfn");
i_aggminvtransfn = PQfnumber(res, "aggminvtransfn");
i_aggmfinalfn = PQfnumber(res, "aggmfinalfn");
@@ -12438,6 +12460,7 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
+ aggcombinefn = PQgetvalue(res, 0, i_aggcombinefn);
aggmtransfn = PQgetvalue(res, 0, i_aggmtransfn);
aggminvtransfn = PQgetvalue(res, 0, i_aggminvtransfn);
aggmfinalfn = PQgetvalue(res, 0, i_aggmfinalfn);
@@ -12526,6 +12549,11 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
appendPQExpBufferStr(details, ",\n FINALFUNC_EXTRA");
}
+ if (strcmp(aggcombinefn, "-") != 0)
+ {
+ appendPQExpBuffer(details, ",\n CFUNC = %s", aggcombinefn);
+ }
+
if (strcmp(aggmtransfn, "-") != 0)
{
appendPQExpBuffer(details, ",\n MSFUNC = %s,\n MINVFUNC = %s,\n MSTYPE = %s",
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index dd6079f..b306f9b 100644
--- a/src/include/catalog/pg_aggregate.h
+++ b/src/include/catalog/pg_aggregate.h
@@ -33,6 +33,7 @@
* aggnumdirectargs number of arguments that are "direct" arguments
* aggtransfn transition function
* aggfinalfn final function (0 if none)
+ * aggcombinefn combine function (0 if none)
* aggmtransfn forward function for moving-aggregate mode (0 if none)
* aggminvtransfn inverse function for moving-aggregate mode (0 if none)
* aggmfinalfn final function for moving-aggregate mode (0 if none)
@@ -56,6 +57,7 @@ CATALOG(pg_aggregate,2600) BKI_WITHOUT_OIDS
int16 aggnumdirectargs;
regproc aggtransfn;
regproc aggfinalfn;
+ regproc aggcombinefn;
regproc aggmtransfn;
regproc aggminvtransfn;
regproc aggmfinalfn;
@@ -85,24 +87,25 @@ typedef FormData_pg_aggregate *Form_pg_aggregate;
* ----------------
*/
-#define Natts_pg_aggregate 17
+#define Natts_pg_aggregate 18
#define Anum_pg_aggregate_aggfnoid 1
#define Anum_pg_aggregate_aggkind 2
#define Anum_pg_aggregate_aggnumdirectargs 3
#define Anum_pg_aggregate_aggtransfn 4
#define Anum_pg_aggregate_aggfinalfn 5
-#define Anum_pg_aggregate_aggmtransfn 6
-#define Anum_pg_aggregate_aggminvtransfn 7
-#define Anum_pg_aggregate_aggmfinalfn 8
-#define Anum_pg_aggregate_aggfinalextra 9
-#define Anum_pg_aggregate_aggmfinalextra 10
-#define Anum_pg_aggregate_aggsortop 11
-#define Anum_pg_aggregate_aggtranstype 12
-#define Anum_pg_aggregate_aggtransspace 13
-#define Anum_pg_aggregate_aggmtranstype 14
-#define Anum_pg_aggregate_aggmtransspace 15
-#define Anum_pg_aggregate_agginitval 16
-#define Anum_pg_aggregate_aggminitval 17
+#define Anum_pg_aggregate_aggcombinefn 6
+#define Anum_pg_aggregate_aggmtransfn 7
+#define Anum_pg_aggregate_aggminvtransfn 8
+#define Anum_pg_aggregate_aggmfinalfn 9
+#define Anum_pg_aggregate_aggfinalextra 10
+#define Anum_pg_aggregate_aggmfinalextra 11
+#define Anum_pg_aggregate_aggsortop 12
+#define Anum_pg_aggregate_aggtranstype 13
+#define Anum_pg_aggregate_aggtransspace 14
+#define Anum_pg_aggregate_aggmtranstype 15
+#define Anum_pg_aggregate_aggmtransspace 16
+#define Anum_pg_aggregate_agginitval 17
+#define Anum_pg_aggregate_aggminitval 18
/*
* Symbolic values for aggkind column. We distinguish normal aggregates
@@ -126,184 +129,184 @@ typedef FormData_pg_aggregate *Form_pg_aggregate;
*/
/* avg */
-DATA(insert ( 2100 n 0 int8_avg_accum numeric_poly_avg int8_avg_accum int8_avg_accum_inv numeric_poly_avg f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2101 n 0 int4_avg_accum int8_avg int4_avg_accum int4_avg_accum_inv int8_avg f f 0 1016 0 1016 0 "{0,0}" "{0,0}" ));
-DATA(insert ( 2102 n 0 int2_avg_accum int8_avg int2_avg_accum int2_avg_accum_inv int8_avg f f 0 1016 0 1016 0 "{0,0}" "{0,0}" ));
-DATA(insert ( 2103 n 0 numeric_avg_accum numeric_avg numeric_avg_accum numeric_accum_inv numeric_avg f f 0 2281 128 2281 128 _null_ _null_ ));
-DATA(insert ( 2104 n 0 float4_accum float8_avg - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2105 n 0 float8_accum float8_avg - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2106 n 0 interval_accum interval_avg interval_accum interval_accum_inv interval_avg f f 0 1187 0 1187 0 "{0 second,0 second}" "{0 second,0 second}" ));
+DATA(insert ( 2100 n 0 int8_avg_accum numeric_poly_avg - int8_avg_accum int8_avg_accum_inv numeric_poly_avg f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2101 n 0 int4_avg_accum int8_avg - int4_avg_accum int4_avg_accum_inv int8_avg f f 0 1016 0 1016 0 "{0,0}" "{0,0}" ));
+DATA(insert ( 2102 n 0 int2_avg_accum int8_avg - int2_avg_accum int2_avg_accum_inv int8_avg f f 0 1016 0 1016 0 "{0,0}" "{0,0}" ));
+DATA(insert ( 2103 n 0 numeric_avg_accum numeric_avg - numeric_avg_accum numeric_accum_inv numeric_avg f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2104 n 0 float4_accum float8_avg - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2105 n 0 float8_accum float8_avg - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2106 n 0 interval_accum interval_avg - interval_accum interval_accum_inv interval_avg f f 0 1187 0 1187 0 "{0 second,0 second}" "{0 second,0 second}" ));
/* sum */
-DATA(insert ( 2107 n 0 int8_avg_accum numeric_poly_sum int8_avg_accum int8_avg_accum_inv numeric_poly_sum f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2108 n 0 int4_sum - int4_avg_accum int4_avg_accum_inv int2int4_sum f f 0 20 0 1016 0 _null_ "{0,0}" ));
-DATA(insert ( 2109 n 0 int2_sum - int2_avg_accum int2_avg_accum_inv int2int4_sum f f 0 20 0 1016 0 _null_ "{0,0}" ));
-DATA(insert ( 2110 n 0 float4pl - - - - f f 0 700 0 0 0 _null_ _null_ ));
-DATA(insert ( 2111 n 0 float8pl - - - - f f 0 701 0 0 0 _null_ _null_ ));
-DATA(insert ( 2112 n 0 cash_pl - cash_pl cash_mi - f f 0 790 0 790 0 _null_ _null_ ));
-DATA(insert ( 2113 n 0 interval_pl - interval_pl interval_mi - f f 0 1186 0 1186 0 _null_ _null_ ));
-DATA(insert ( 2114 n 0 numeric_avg_accum numeric_sum numeric_avg_accum numeric_accum_inv numeric_sum f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2107 n 0 int8_avg_accum numeric_poly_sum - int8_avg_accum int8_avg_accum_inv numeric_poly_sum f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2108 n 0 int4_sum - int8pl int4_avg_accum int4_avg_accum_inv int2int4_sum f f 0 20 0 1016 0 _null_ "{0,0}" ));
+DATA(insert ( 2109 n 0 int2_sum - int8pl int2_avg_accum int2_avg_accum_inv int2int4_sum f f 0 20 0 1016 0 _null_ "{0,0}" ));
+DATA(insert ( 2110 n 0 float4pl - float4pl - - - f f 0 700 0 0 0 _null_ _null_ ));
+DATA(insert ( 2111 n 0 float8pl - float8pl - - - f f 0 701 0 0 0 _null_ _null_ ));
+DATA(insert ( 2112 n 0 cash_pl - cash_pl cash_pl cash_mi - f f 0 790 0 790 0 _null_ _null_ ));
+DATA(insert ( 2113 n 0 interval_pl - interval_pl interval_pl interval_mi - f f 0 1186 0 1186 0 _null_ _null_ ));
+DATA(insert ( 2114 n 0 numeric_avg_accum numeric_sum - numeric_avg_accum numeric_accum_inv numeric_sum f f 0 2281 128 2281 128 _null_ _null_ ));
/* max */
-DATA(insert ( 2115 n 0 int8larger - - - - f f 413 20 0 0 0 _null_ _null_ ));
-DATA(insert ( 2116 n 0 int4larger - - - - f f 521 23 0 0 0 _null_ _null_ ));
-DATA(insert ( 2117 n 0 int2larger - - - - f f 520 21 0 0 0 _null_ _null_ ));
-DATA(insert ( 2118 n 0 oidlarger - - - - f f 610 26 0 0 0 _null_ _null_ ));
-DATA(insert ( 2119 n 0 float4larger - - - - f f 623 700 0 0 0 _null_ _null_ ));
-DATA(insert ( 2120 n 0 float8larger - - - - f f 674 701 0 0 0 _null_ _null_ ));
-DATA(insert ( 2121 n 0 int4larger - - - - f f 563 702 0 0 0 _null_ _null_ ));
-DATA(insert ( 2122 n 0 date_larger - - - - f f 1097 1082 0 0 0 _null_ _null_ ));
-DATA(insert ( 2123 n 0 time_larger - - - - f f 1112 1083 0 0 0 _null_ _null_ ));
-DATA(insert ( 2124 n 0 timetz_larger - - - - f f 1554 1266 0 0 0 _null_ _null_ ));
-DATA(insert ( 2125 n 0 cashlarger - - - - f f 903 790 0 0 0 _null_ _null_ ));
-DATA(insert ( 2126 n 0 timestamp_larger - - - - f f 2064 1114 0 0 0 _null_ _null_ ));
-DATA(insert ( 2127 n 0 timestamptz_larger - - - - f f 1324 1184 0 0 0 _null_ _null_ ));
-DATA(insert ( 2128 n 0 interval_larger - - - - f f 1334 1186 0 0 0 _null_ _null_ ));
-DATA(insert ( 2129 n 0 text_larger - - - - f f 666 25 0 0 0 _null_ _null_ ));
-DATA(insert ( 2130 n 0 numeric_larger - - - - f f 1756 1700 0 0 0 _null_ _null_ ));
-DATA(insert ( 2050 n 0 array_larger - - - - f f 1073 2277 0 0 0 _null_ _null_ ));
-DATA(insert ( 2244 n 0 bpchar_larger - - - - f f 1060 1042 0 0 0 _null_ _null_ ));
-DATA(insert ( 2797 n 0 tidlarger - - - - f f 2800 27 0 0 0 _null_ _null_ ));
-DATA(insert ( 3526 n 0 enum_larger - - - - f f 3519 3500 0 0 0 _null_ _null_ ));
-DATA(insert ( 3564 n 0 network_larger - - - - f f 1205 869 0 0 0 _null_ _null_ ));
+DATA(insert ( 2115 n 0 int8larger - int8larger - - - f f 413 20 0 0 0 _null_ _null_ ));
+DATA(insert ( 2116 n 0 int4larger - int4larger - - - f f 521 23 0 0 0 _null_ _null_ ));
+DATA(insert ( 2117 n 0 int2larger - int2larger - - - f f 520 21 0 0 0 _null_ _null_ ));
+DATA(insert ( 2118 n 0 oidlarger - oidlarger - - - f f 610 26 0 0 0 _null_ _null_ ));
+DATA(insert ( 2119 n 0 float4larger - float4larger - - - f f 623 700 0 0 0 _null_ _null_ ));
+DATA(insert ( 2120 n 0 float8larger - float8larger - - - f f 674 701 0 0 0 _null_ _null_ ));
+DATA(insert ( 2121 n 0 int4larger - int4larger - - - f f 563 702 0 0 0 _null_ _null_ ));
+DATA(insert ( 2122 n 0 date_larger - date_larger - - - f f 1097 1082 0 0 0 _null_ _null_ ));
+DATA(insert ( 2123 n 0 time_larger - time_larger - - - f f 1112 1083 0 0 0 _null_ _null_ ));
+DATA(insert ( 2124 n 0 timetz_larger - timetz_larger - - - f f 1554 1266 0 0 0 _null_ _null_ ));
+DATA(insert ( 2125 n 0 cashlarger - cashlarger - - - f f 903 790 0 0 0 _null_ _null_ ));
+DATA(insert ( 2126 n 0 timestamp_larger - timestamp_larger - - - f f 2064 1114 0 0 0 _null_ _null_ ));
+DATA(insert ( 2127 n 0 timestamptz_larger - timestamptz_larger - - - f f 1324 1184 0 0 0 _null_ _null_ ));
+DATA(insert ( 2128 n 0 interval_larger - interval_larger - - - f f 1334 1186 0 0 0 _null_ _null_ ));
+DATA(insert ( 2129 n 0 text_larger - text_larger - - - f f 666 25 0 0 0 _null_ _null_ ));
+DATA(insert ( 2130 n 0 numeric_larger - numeric_larger - - - f f 1756 1700 0 0 0 _null_ _null_ ));
+DATA(insert ( 2050 n 0 array_larger - array_larger - - - f f 1073 2277 0 0 0 _null_ _null_ ));
+DATA(insert ( 2244 n 0 bpchar_larger - bpchar_larger - - - f f 1060 1042 0 0 0 _null_ _null_ ));
+DATA(insert ( 2797 n 0 tidlarger - tidlarger - - - f f 2800 27 0 0 0 _null_ _null_ ));
+DATA(insert ( 3526 n 0 enum_larger - enum_larger - - - f f 3519 3500 0 0 0 _null_ _null_ ));
+DATA(insert ( 3564 n 0 network_larger - network_larger - - - f f 1205 869 0 0 0 _null_ _null_ ));
/* min */
-DATA(insert ( 2131 n 0 int8smaller - - - - f f 412 20 0 0 0 _null_ _null_ ));
-DATA(insert ( 2132 n 0 int4smaller - - - - f f 97 23 0 0 0 _null_ _null_ ));
-DATA(insert ( 2133 n 0 int2smaller - - - - f f 95 21 0 0 0 _null_ _null_ ));
-DATA(insert ( 2134 n 0 oidsmaller - - - - f f 609 26 0 0 0 _null_ _null_ ));
-DATA(insert ( 2135 n 0 float4smaller - - - - f f 622 700 0 0 0 _null_ _null_ ));
-DATA(insert ( 2136 n 0 float8smaller - - - - f f 672 701 0 0 0 _null_ _null_ ));
-DATA(insert ( 2137 n 0 int4smaller - - - - f f 562 702 0 0 0 _null_ _null_ ));
-DATA(insert ( 2138 n 0 date_smaller - - - - f f 1095 1082 0 0 0 _null_ _null_ ));
-DATA(insert ( 2139 n 0 time_smaller - - - - f f 1110 1083 0 0 0 _null_ _null_ ));
-DATA(insert ( 2140 n 0 timetz_smaller - - - - f f 1552 1266 0 0 0 _null_ _null_ ));
-DATA(insert ( 2141 n 0 cashsmaller - - - - f f 902 790 0 0 0 _null_ _null_ ));
-DATA(insert ( 2142 n 0 timestamp_smaller - - - - f f 2062 1114 0 0 0 _null_ _null_ ));
-DATA(insert ( 2143 n 0 timestamptz_smaller - - - - f f 1322 1184 0 0 0 _null_ _null_ ));
-DATA(insert ( 2144 n 0 interval_smaller - - - - f f 1332 1186 0 0 0 _null_ _null_ ));
-DATA(insert ( 2145 n 0 text_smaller - - - - f f 664 25 0 0 0 _null_ _null_ ));
-DATA(insert ( 2146 n 0 numeric_smaller - - - - f f 1754 1700 0 0 0 _null_ _null_ ));
-DATA(insert ( 2051 n 0 array_smaller - - - - f f 1072 2277 0 0 0 _null_ _null_ ));
-DATA(insert ( 2245 n 0 bpchar_smaller - - - - f f 1058 1042 0 0 0 _null_ _null_ ));
-DATA(insert ( 2798 n 0 tidsmaller - - - - f f 2799 27 0 0 0 _null_ _null_ ));
-DATA(insert ( 3527 n 0 enum_smaller - - - - f f 3518 3500 0 0 0 _null_ _null_ ));
-DATA(insert ( 3565 n 0 network_smaller - - - - f f 1203 869 0 0 0 _null_ _null_ ));
+DATA(insert ( 2131 n 0 int8smaller - int8smaller - - - f f 412 20 0 0 0 _null_ _null_ ));
+DATA(insert ( 2132 n 0 int4smaller - int4smaller - - - f f 97 23 0 0 0 _null_ _null_ ));
+DATA(insert ( 2133 n 0 int2smaller - int2smaller - - - f f 95 21 0 0 0 _null_ _null_ ));
+DATA(insert ( 2134 n 0 oidsmaller - oidsmaller - - - f f 609 26 0 0 0 _null_ _null_ ));
+DATA(insert ( 2135 n 0 float4smaller - float4smaller - - - f f 622 700 0 0 0 _null_ _null_ ));
+DATA(insert ( 2136 n 0 float8smaller - float8smaller - - - f f 672 701 0 0 0 _null_ _null_ ));
+DATA(insert ( 2137 n 0 int4smaller - int4smaller - - - f f 562 702 0 0 0 _null_ _null_ ));
+DATA(insert ( 2138 n 0 date_smaller - date_smaller - - - f f 1095 1082 0 0 0 _null_ _null_ ));
+DATA(insert ( 2139 n 0 time_smaller - time_smaller - - - f f 1110 1083 0 0 0 _null_ _null_ ));
+DATA(insert ( 2140 n 0 timetz_smaller - timetz_smaller - - - f f 1552 1266 0 0 0 _null_ _null_ ));
+DATA(insert ( 2141 n 0 cashsmaller - cashsmaller - - - f f 902 790 0 0 0 _null_ _null_ ));
+DATA(insert ( 2142 n 0 timestamp_smaller - timestamp_smaller - - - f f 2062 1114 0 0 0 _null_ _null_ ));
+DATA(insert ( 2143 n 0 timestamptz_smaller - timestamptz_smaller - - - f f 1322 1184 0 0 0 _null_ _null_ ));
+DATA(insert ( 2144 n 0 interval_smaller - interval_smaller - - - f f 1332 1186 0 0 0 _null_ _null_ ));
+DATA(insert ( 2145 n 0 text_smaller - text_smaller - - - f f 664 25 0 0 0 _null_ _null_ ));
+DATA(insert ( 2146 n 0 numeric_smaller - numeric_smaller - - - f f 1754 1700 0 0 0 _null_ _null_ ));
+DATA(insert ( 2051 n 0 array_smaller - array_smaller - - - f f 1072 2277 0 0 0 _null_ _null_ ));
+DATA(insert ( 2245 n 0 bpchar_smaller - bpchar_smaller - - - f f 1058 1042 0 0 0 _null_ _null_ ));
+DATA(insert ( 2798 n 0 tidsmaller - tidsmaller - - - f f 2799 27 0 0 0 _null_ _null_ ));
+DATA(insert ( 3527 n 0 enum_smaller - enum_smaller - - - f f 3518 3500 0 0 0 _null_ _null_ ));
+DATA(insert ( 3565 n 0 network_smaller - network_smaller - - - f f 1203 869 0 0 0 _null_ _null_ ));
/* count */
-DATA(insert ( 2147 n 0 int8inc_any - int8inc_any int8dec_any - f f 0 20 0 20 0 "0" "0" ));
-DATA(insert ( 2803 n 0 int8inc - int8inc int8dec - f f 0 20 0 20 0 "0" "0" ));
+DATA(insert ( 2147 n 0 int8inc_any - int8pl int8inc_any int8dec_any - f f 0 20 0 20 0 "0" "0" ));
+DATA(insert ( 2803 n 0 int8inc - int8pl int8inc int8dec - f f 0 20 0 20 0 "0" "0" ));
/* var_pop */
-DATA(insert ( 2718 n 0 int8_accum numeric_var_pop int8_accum int8_accum_inv numeric_var_pop f f 0 2281 128 2281 128 _null_ _null_ ));
-DATA(insert ( 2719 n 0 int4_accum numeric_poly_var_pop int4_accum int4_accum_inv numeric_poly_var_pop f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2720 n 0 int2_accum numeric_poly_var_pop int2_accum int2_accum_inv numeric_poly_var_pop f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2721 n 0 float4_accum float8_var_pop - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2722 n 0 float8_accum float8_var_pop - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2723 n 0 numeric_accum numeric_var_pop numeric_accum numeric_accum_inv numeric_var_pop f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2718 n 0 int8_accum numeric_var_pop - int8_accum int8_accum_inv numeric_var_pop f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2719 n 0 int4_accum numeric_poly_var_pop - int4_accum int4_accum_inv numeric_poly_var_pop f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2720 n 0 int2_accum numeric_poly_var_pop - int2_accum int2_accum_inv numeric_poly_var_pop f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2721 n 0 float4_accum float8_var_pop - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2722 n 0 float8_accum float8_var_pop - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2723 n 0 numeric_accum numeric_var_pop - numeric_accum numeric_accum_inv numeric_var_pop f f 0 2281 128 2281 128 _null_ _null_ ));
/* var_samp */
-DATA(insert ( 2641 n 0 int8_accum numeric_var_samp int8_accum int8_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
-DATA(insert ( 2642 n 0 int4_accum numeric_poly_var_samp int4_accum int4_accum_inv numeric_poly_var_samp f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2643 n 0 int2_accum numeric_poly_var_samp int2_accum int2_accum_inv numeric_poly_var_samp f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2644 n 0 float4_accum float8_var_samp - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2645 n 0 float8_accum float8_var_samp - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2646 n 0 numeric_accum numeric_var_samp numeric_accum numeric_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2641 n 0 int8_accum numeric_var_samp - int8_accum int8_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2642 n 0 int4_accum numeric_poly_var_samp - int4_accum int4_accum_inv numeric_poly_var_samp f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2643 n 0 int2_accum numeric_poly_var_samp - int2_accum int2_accum_inv numeric_poly_var_samp f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2644 n 0 float4_accum float8_var_samp - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2645 n 0 float8_accum float8_var_samp - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2646 n 0 numeric_accum numeric_var_samp - numeric_accum numeric_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
/* variance: historical Postgres syntax for var_samp */
-DATA(insert ( 2148 n 0 int8_accum numeric_var_samp int8_accum int8_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
-DATA(insert ( 2149 n 0 int4_accum numeric_poly_var_samp int4_accum int4_accum_inv numeric_poly_var_samp f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2150 n 0 int2_accum numeric_poly_var_samp int2_accum int2_accum_inv numeric_poly_var_samp f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2151 n 0 float4_accum float8_var_samp - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2152 n 0 float8_accum float8_var_samp - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2153 n 0 numeric_accum numeric_var_samp numeric_accum numeric_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2148 n 0 int8_accum numeric_var_samp - int8_accum int8_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2149 n 0 int4_accum numeric_poly_var_samp - int4_accum int4_accum_inv numeric_poly_var_samp f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2150 n 0 int2_accum numeric_poly_var_samp - int2_accum int2_accum_inv numeric_poly_var_samp f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2151 n 0 float4_accum float8_var_samp - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2152 n 0 float8_accum float8_var_samp - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2153 n 0 numeric_accum numeric_var_samp - numeric_accum numeric_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
/* stddev_pop */
-DATA(insert ( 2724 n 0 int8_accum numeric_stddev_pop int8_accum int8_accum_inv numeric_stddev_pop f f 0 2281 128 2281 128 _null_ _null_ ));
-DATA(insert ( 2725 n 0 int4_accum numeric_poly_stddev_pop int4_accum int4_accum_inv numeric_poly_stddev_pop f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2726 n 0 int2_accum numeric_poly_stddev_pop int2_accum int2_accum_inv numeric_poly_stddev_pop f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2727 n 0 float4_accum float8_stddev_pop - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2728 n 0 float8_accum float8_stddev_pop - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2729 n 0 numeric_accum numeric_stddev_pop numeric_accum numeric_accum_inv numeric_stddev_pop f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2724 n 0 int8_accum numeric_stddev_pop - int8_accum int8_accum_inv numeric_stddev_pop f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2725 n 0 int4_accum numeric_poly_stddev_pop - int4_accum int4_accum_inv numeric_poly_stddev_pop f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2726 n 0 int2_accum numeric_poly_stddev_pop - int2_accum int2_accum_inv numeric_poly_stddev_pop f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2727 n 0 float4_accum float8_stddev_pop - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2728 n 0 float8_accum float8_stddev_pop - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2729 n 0 numeric_accum numeric_stddev_pop - numeric_accum numeric_accum_inv numeric_stddev_pop f f 0 2281 128 2281 128 _null_ _null_ ));
/* stddev_samp */
-DATA(insert ( 2712 n 0 int8_accum numeric_stddev_samp int8_accum int8_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
-DATA(insert ( 2713 n 0 int4_accum numeric_poly_stddev_samp int4_accum int4_accum_inv numeric_poly_stddev_samp f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2714 n 0 int2_accum numeric_poly_stddev_samp int2_accum int2_accum_inv numeric_poly_stddev_samp f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2715 n 0 float4_accum float8_stddev_samp - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2716 n 0 float8_accum float8_stddev_samp - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2717 n 0 numeric_accum numeric_stddev_samp numeric_accum numeric_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2712 n 0 int8_accum numeric_stddev_samp - int8_accum int8_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2713 n 0 int4_accum numeric_poly_stddev_samp - int4_accum int4_accum_inv numeric_poly_stddev_samp f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2714 n 0 int2_accum numeric_poly_stddev_samp - int2_accum int2_accum_inv numeric_poly_stddev_samp f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2715 n 0 float4_accum float8_stddev_samp - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2716 n 0 float8_accum float8_stddev_samp - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2717 n 0 numeric_accum numeric_stddev_samp - numeric_accum numeric_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
/* stddev: historical Postgres syntax for stddev_samp */
-DATA(insert ( 2154 n 0 int8_accum numeric_stddev_samp int8_accum int8_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
-DATA(insert ( 2155 n 0 int4_accum numeric_poly_stddev_samp int4_accum int4_accum_inv numeric_poly_stddev_samp f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2156 n 0 int2_accum numeric_poly_stddev_samp int2_accum int2_accum_inv numeric_poly_stddev_samp f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2157 n 0 float4_accum float8_stddev_samp - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2158 n 0 float8_accum float8_stddev_samp - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2159 n 0 numeric_accum numeric_stddev_samp numeric_accum numeric_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2154 n 0 int8_accum numeric_stddev_samp - int8_accum int8_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2155 n 0 int4_accum numeric_poly_stddev_samp - int4_accum int4_accum_inv numeric_poly_stddev_samp f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2156 n 0 int2_accum numeric_poly_stddev_samp - int2_accum int2_accum_inv numeric_poly_stddev_samp f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2157 n 0 float4_accum float8_stddev_samp - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2158 n 0 float8_accum float8_stddev_samp - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2159 n 0 numeric_accum numeric_stddev_samp - numeric_accum numeric_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
/* SQL2003 binary regression aggregates */
-DATA(insert ( 2818 n 0 int8inc_float8_float8 - - - - f f 0 20 0 0 0 "0" _null_ ));
-DATA(insert ( 2819 n 0 float8_regr_accum float8_regr_sxx - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2820 n 0 float8_regr_accum float8_regr_syy - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2821 n 0 float8_regr_accum float8_regr_sxy - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2822 n 0 float8_regr_accum float8_regr_avgx - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2823 n 0 float8_regr_accum float8_regr_avgy - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2824 n 0 float8_regr_accum float8_regr_r2 - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2825 n 0 float8_regr_accum float8_regr_slope - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2826 n 0 float8_regr_accum float8_regr_intercept - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2827 n 0 float8_regr_accum float8_covar_pop - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2828 n 0 float8_regr_accum float8_covar_samp - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2829 n 0 float8_regr_accum float8_corr - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2818 n 0 int8inc_float8_float8 - - - - - f f 0 20 0 0 0 "0" _null_ ));
+DATA(insert ( 2819 n 0 float8_regr_accum float8_regr_sxx - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2820 n 0 float8_regr_accum float8_regr_syy - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2821 n 0 float8_regr_accum float8_regr_sxy - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2822 n 0 float8_regr_accum float8_regr_avgx - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2823 n 0 float8_regr_accum float8_regr_avgy - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2824 n 0 float8_regr_accum float8_regr_r2 - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2825 n 0 float8_regr_accum float8_regr_slope - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2826 n 0 float8_regr_accum float8_regr_intercept - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2827 n 0 float8_regr_accum float8_covar_pop - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2828 n 0 float8_regr_accum float8_covar_samp - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2829 n 0 float8_regr_accum float8_corr - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
/* boolean-and and boolean-or */
-DATA(insert ( 2517 n 0 booland_statefunc - bool_accum bool_accum_inv bool_alltrue f f 58 16 0 2281 16 _null_ _null_ ));
-DATA(insert ( 2518 n 0 boolor_statefunc - bool_accum bool_accum_inv bool_anytrue f f 59 16 0 2281 16 _null_ _null_ ));
-DATA(insert ( 2519 n 0 booland_statefunc - bool_accum bool_accum_inv bool_alltrue f f 58 16 0 2281 16 _null_ _null_ ));
+DATA(insert ( 2517 n 0 booland_statefunc - - bool_accum bool_accum_inv bool_alltrue f f 58 16 0 2281 16 _null_ _null_ ));
+DATA(insert ( 2518 n 0 boolor_statefunc - - bool_accum bool_accum_inv bool_anytrue f f 59 16 0 2281 16 _null_ _null_ ));
+DATA(insert ( 2519 n 0 booland_statefunc - - bool_accum bool_accum_inv bool_alltrue f f 58 16 0 2281 16 _null_ _null_ ));
/* bitwise integer */
-DATA(insert ( 2236 n 0 int2and - - - - f f 0 21 0 0 0 _null_ _null_ ));
-DATA(insert ( 2237 n 0 int2or - - - - f f 0 21 0 0 0 _null_ _null_ ));
-DATA(insert ( 2238 n 0 int4and - - - - f f 0 23 0 0 0 _null_ _null_ ));
-DATA(insert ( 2239 n 0 int4or - - - - f f 0 23 0 0 0 _null_ _null_ ));
-DATA(insert ( 2240 n 0 int8and - - - - f f 0 20 0 0 0 _null_ _null_ ));
-DATA(insert ( 2241 n 0 int8or - - - - f f 0 20 0 0 0 _null_ _null_ ));
-DATA(insert ( 2242 n 0 bitand - - - - f f 0 1560 0 0 0 _null_ _null_ ));
-DATA(insert ( 2243 n 0 bitor - - - - f f 0 1560 0 0 0 _null_ _null_ ));
+DATA(insert ( 2236 n 0 int2and - int2and - - - f f 0 21 0 0 0 _null_ _null_ ));
+DATA(insert ( 2237 n 0 int2or - int2or - - - f f 0 21 0 0 0 _null_ _null_ ));
+DATA(insert ( 2238 n 0 int4and - int4and - - - f f 0 23 0 0 0 _null_ _null_ ));
+DATA(insert ( 2239 n 0 int4or - int4or - - - f f 0 23 0 0 0 _null_ _null_ ));
+DATA(insert ( 2240 n 0 int8and - int8and - - - f f 0 20 0 0 0 _null_ _null_ ));
+DATA(insert ( 2241 n 0 int8or - int8or - - - f f 0 20 0 0 0 _null_ _null_ ));
+DATA(insert ( 2242 n 0 bitand - bitand - - - f f 0 1560 0 0 0 _null_ _null_ ));
+DATA(insert ( 2243 n 0 bitor - bitor - - - f f 0 1560 0 0 0 _null_ _null_ ));
/* xml */
-DATA(insert ( 2901 n 0 xmlconcat2 - - - - f f 0 142 0 0 0 _null_ _null_ ));
+DATA(insert ( 2901 n 0 xmlconcat2 - - - - - f f 0 142 0 0 0 _null_ _null_ ));
/* array */
-DATA(insert ( 2335 n 0 array_agg_transfn array_agg_finalfn - - - t f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 4053 n 0 array_agg_array_transfn array_agg_array_finalfn - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 2335 n 0 array_agg_transfn array_agg_finalfn - - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 4053 n 0 array_agg_array_transfn array_agg_array_finalfn - - - - t f 0 2281 0 0 0 _null_ _null_ ));
/* text */
-DATA(insert ( 3538 n 0 string_agg_transfn string_agg_finalfn - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3538 n 0 string_agg_transfn string_agg_finalfn - - - - f f 0 2281 0 0 0 _null_ _null_ ));
/* bytea */
-DATA(insert ( 3545 n 0 bytea_string_agg_transfn bytea_string_agg_finalfn - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3545 n 0 bytea_string_agg_transfn bytea_string_agg_finalfn - - - - f f 0 2281 0 0 0 _null_ _null_ ));
/* json */
-DATA(insert ( 3175 n 0 json_agg_transfn json_agg_finalfn - - - f f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3197 n 0 json_object_agg_transfn json_object_agg_finalfn - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3175 n 0 json_agg_transfn json_agg_finalfn - - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3197 n 0 json_object_agg_transfn json_object_agg_finalfn - - - - f f 0 2281 0 0 0 _null_ _null_ ));
/* jsonb */
-DATA(insert ( 3267 n 0 jsonb_agg_transfn jsonb_agg_finalfn - - - f f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3270 n 0 jsonb_object_agg_transfn jsonb_object_agg_finalfn - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3267 n 0 jsonb_agg_transfn jsonb_agg_finalfn - - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3270 n 0 jsonb_object_agg_transfn jsonb_object_agg_finalfn - - - - f f 0 2281 0 0 0 _null_ _null_ ));
/* ordered-set and hypothetical-set aggregates */
-DATA(insert ( 3972 o 1 ordered_set_transition percentile_disc_final - - - t f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3974 o 1 ordered_set_transition percentile_cont_float8_final - - - f f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3976 o 1 ordered_set_transition percentile_cont_interval_final - - - f f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3978 o 1 ordered_set_transition percentile_disc_multi_final - - - t f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3980 o 1 ordered_set_transition percentile_cont_float8_multi_final - - - f f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3982 o 1 ordered_set_transition percentile_cont_interval_multi_final - - - f f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3984 o 0 ordered_set_transition mode_final - - - t f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3986 h 1 ordered_set_transition_multi rank_final - - - t f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3988 h 1 ordered_set_transition_multi percent_rank_final - - - t f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3990 h 1 ordered_set_transition_multi cume_dist_final - - - t f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3992 h 1 ordered_set_transition_multi dense_rank_final - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3972 o 1 ordered_set_transition percentile_disc_final - - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3974 o 1 ordered_set_transition percentile_cont_float8_final - - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3976 o 1 ordered_set_transition percentile_cont_interval_final - - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3978 o 1 ordered_set_transition percentile_disc_multi_final - - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3980 o 1 ordered_set_transition percentile_cont_float8_multi_final - - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3982 o 1 ordered_set_transition percentile_cont_interval_multi_final - - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3984 o 0 ordered_set_transition mode_final - - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3986 h 1 ordered_set_transition_multi rank_final - - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3988 h 1 ordered_set_transition_multi percent_rank_final - - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3990 h 1 ordered_set_transition_multi cume_dist_final - - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3992 h 1 ordered_set_transition_multi dense_rank_final - - - - t f 0 2281 0 0 0 _null_ _null_ ));
/*
@@ -322,6 +325,7 @@ extern ObjectAddress AggregateCreate(const char *aggName,
Oid variadicArgType,
List *aggtransfnName,
List *aggfinalfnName,
+ List *aggcombinefnName,
List *aggmtransfnName,
List *aggminvtransfnName,
List *aggmfinalfnName,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4ae2f3e..70e88c7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1845,6 +1845,8 @@ typedef struct AggState
AggStatePerTrans curpertrans; /* currently active trans state */
bool input_done; /* indicates end of input */
bool agg_done; /* indicates completion of Agg scan */
+ bool combineStates; /* input tuples contain transition states */
+ bool finalizeAggs; /* should we call the finalfn on agg states? */
int projected_set; /* The last projected grouping set */
int current_set; /* The current grouping set being evaluated */
Bitmapset *grouped_cols; /* grouped cols in current projection */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index cc259f1..6a1353d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -715,6 +715,8 @@ typedef struct Agg
AggStrategy aggstrategy;
int numCols; /* number of grouping columns */
AttrNumber *grpColIdx; /* their indexes in the target list */
+ bool combineStates; /* input tuples contain transition states */
+ bool finalizeAggs; /* should we call the finalfn on agg states? */
Oid *grpOperators; /* equality operators to compare with */
long numGroups; /* estimated number of groups in input */
List *groupingSets; /* grouping sets to use */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 79bed33..e8ca077 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -16,6 +16,7 @@
#include "access/sdir.h"
#include "lib/stringinfo.h"
+#include "nodes/plannodes.h"
#include "nodes/params.h"
#include "nodes/parsenodes.h"
#include "storage/block.h"
@@ -260,7 +261,14 @@ typedef struct PlannerInfo
bool hasPseudoConstantQuals; /* true if any RestrictInfo has
* pseudoconstant = true */
bool hasRecursion; /* true if planning a recursive WITH item */
-
+ Index aggregaterel; /* Index of relation to perform early
+ * aggregation on, or 0 if not enabled */
+ Index groupbyrel; /* Index of single relation to perform group by
+ * on, or 0 if not enabled. */
+ Index groupbeforejoinrel; /* Index of additional rel which was added
+ * to rewrite the group by to happen before
+ * joining to any other relations.
+ */
/* These fields are used only when hasRecursion is true: */
int wt_param_id; /* PARAM_EXEC ID for the work table */
struct Plan *non_recursive_plan; /* plan for non-recursive term */
@@ -450,7 +458,8 @@ typedef struct RelOptInfo
/* per-relation planner control flags */
bool consider_startup; /* keep cheap-startup-cost paths? */
bool consider_param_startup; /* ditto, for parameterized paths? */
-
+ bool partial_aggregate; /* true if aggregates should not be
+ * finalized. */
/* materialization information */
List *reltargetlist; /* Vars to be output by scan of relation */
List *pathlist; /* Path structures */
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index dd43e45..aaa1e08 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -61,6 +61,7 @@ extern bool enable_nestloop;
extern bool enable_material;
extern bool enable_mergejoin;
extern bool enable_hashjoin;
+extern bool enable_earlygrouping;
extern int constraint_exclusion;
extern double clamp_row_est(double nrows);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 52b077a..9540061 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -36,6 +36,8 @@ extern RelOptInfo *query_planner(PlannerInfo *root, List *tlist,
extern void preprocess_minmax_aggregates(PlannerInfo *root, List *tlist);
extern Plan *optimize_minmax_aggregates(PlannerInfo *root, List *tlist,
const AggClauseCosts *aggcosts, Path *best_path);
+extern List *setup_grouping_rel(PlannerInfo *root, List *joinlist);
+extern bool prepare_group_before_join(PlannerInfo *root, Query *parse);
/*
* prototypes for plan/createplan.c
@@ -59,9 +61,8 @@ 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);
+ List *groupingSets, long numGroups, bool combineStates,
+ bool finalizeAggs, Plan *lefttree);
extern WindowAgg *make_windowagg(PlannerInfo *root, List *tlist,
List *windowFuncs, Index winref,
int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index b10a504..cfedf1f 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -32,8 +32,8 @@ extern PlannedStmt *standard_planner(Query *parse, int cursorOptions,
extern Plan *subquery_planner(PlannerGlobal *glob, Query *parse,
PlannerInfo *parent_root,
- bool hasRecursion, double tuple_fraction,
- PlannerInfo **subroot);
+ bool hasRecursion, bool finalize_aggregates,
+ double tuple_fraction, PlannerInfo **subroot);
extern void add_tlist_costs_to_plan(PlannerInfo *root, Plan *plan,
List *tlist);
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index e2b3894..621b6b9 100644
--- a/src/include/parser/parse_agg.h
+++ b/src/include/parser/parse_agg.h
@@ -46,6 +46,12 @@ extern void build_aggregate_transfn_expr(Oid *agg_input_types,
Expr **transfnexpr,
Expr **invtransfnexpr);
+extern void build_aggregate_combinefn_expr(bool agg_variadic,
+ Oid agg_state_type,
+ Oid agg_input_collation,
+ Oid combinefn_oid,
+ Expr **combinefnexpr);
+
extern void build_aggregate_finalfn_expr(Oid *agg_input_types,
int num_finalfn_inputs,
Oid agg_state_type,
diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out
index 82a34fb..906c2b5 100644
--- a/src/test/regress/expected/create_aggregate.out
+++ b/src/test/regress/expected/create_aggregate.out
@@ -101,6 +101,13 @@ CREATE AGGREGATE sumdouble (float8)
msfunc = float8pl,
minvfunc = float8mi
);
+-- aggregate combine functions
+CREATE AGGREGATE mymax (int)
+(
+ stype = int4,
+ sfunc = int4larger,
+ cfunc = int4larger
+);
-- invalid: nonstrict inverse with strict forward function
CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
$$ SELECT $1 - $2; $$
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 00ef421..af3f9b7 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -2,6 +2,7 @@ SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
name | setting
----------------------+---------
enable_bitmapscan | on
+ enable_earlygrouping | on
enable_hashagg | on
enable_hashjoin | on
enable_indexonlyscan | on
@@ -12,7 +13,7 @@ SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
enable_seqscan | on
enable_sort | on
enable_tidscan | on
-(11 rows)
+(12 rows)
CREATE TABLE foo2(fooid int, f2 int);
INSERT INTO foo2 VALUES(1, 11);
diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql
index 0ec1572..0ebd0d9 100644
--- a/src/test/regress/sql/create_aggregate.sql
+++ b/src/test/regress/sql/create_aggregate.sql
@@ -115,6 +115,14 @@ CREATE AGGREGATE sumdouble (float8)
minvfunc = float8mi
);
+-- aggregate combine functions
+CREATE AGGREGATE mymax (int)
+(
+ stype = int4,
+ sfunc = int4larger,
+ cfunc = int4larger
+);
+
-- invalid: nonstrict inverse with strict forward function
CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
On 2015/09/28 13:31, David Rowley wrote:
I've been spending time working on allowing the planner to perform
aggregation before the final join relation is created.
...
The patch is however so far capable of giving us extremely nice performance
improvements for some (likely artificial) queries.Let's look at a quick example:
CREATE TABLE product (product_id INT NOT NULL,product_code VARCHAR(64) NOT
NULL, PRIMARY KEY(product_id));
CREATE UNIQUE INDEX product_product_code_uidx ON product (product_code);
-- create small list of products
INSERT INTO product SELECT g.id,'ABC' || CAST(g.id AS TEXT) FROM
generate_series(1,100) g(id);CREATE TABLE sale (sale_id INT NOT NULL, product_id INT NOT NULL, quantity
INT NOT NULL);INSERT INTO sale (sale_id, product_id,quantity) SELECT
x.x,x.x%100+1,CAST(random() * 1000 AS INT) FROM
generate_series(1,100000000) x(x);ALTER TABLE sale ADD CONSTRAINT sale_pkey PRIMARY KEY(sale_id);
test=# SELECT count(sale.sale_id) FROM sale, product;
count
-------------
10000000000
(1 row)
Time: 10323.053 msAnd if I disable the optimisation:
test=# set enable_earlygrouping = off;
SET
Time: 0.302 ms
test=# SELECT count(sale.sale_id) FROM sale, product;
count
-------------
10000000000
(1 row)
Time: 775790.764 msSo, in this probably rather unlikely query, we get something around a 7500%
performance increase. Of course as the ratio of groups per underlying
tuples increase, the performance increase will tail off.The explain output from the optimised version is as follows:
QUERY PLAN
------------------------------------------------------------------------------------
Finalize Aggregate (cost=1790544.37..1790544.38 rows=1 width=4)
-> Nested Loop (cost=1790541.10..1790544.12 rows=100 width=4)
-> Partial Aggregate (cost=1790541.10..1790541.11 rows=1 width=4)
-> Seq Scan on sale (cost=0.00..1540541.08 rows=100000008
width=4)
-> Seq Scan on product (cost=0.00..2.00 rows=100 width=0)
Did you perhaps attach a version of the patch you didn't intend to?
I get the following plan and hence a different result from what's shown above:
postgres=# EXPLAIN SELECT count(sale.sale_id) FROM sale, product;
QUERY PLAN
--------------------------------------------------------------------------------
Aggregate (cost=17909.27..17909.28 rows=1 width=4)
-> Nested Loop (cost=17906.00..17909.02 rows=100 width=4)
-> Aggregate (cost=17906.00..17906.01 rows=1 width=4)
-> Seq Scan on sale (cost=0.00..15406.00 rows=1000000
width=4)
-> Seq Scan on product (cost=0.00..2.00 rows=100 width=0)
postgres=# SELECT count(sale.sale_id) FROM sale, product;
count
-------
100
(1 row)
postgres=# set enable_earlygrouping = off;
SET
postgres=# EXPLAIN SELECT count(sale.sale_id) FROM sale, product;
QUERY PLAN
---------------------------------------------------------------------------
Aggregate (cost=1515408.25..1515408.26 rows=1 width=4)
-> Nested Loop (cost=0.00..1265408.25 rows=100000000 width=4)
-> Seq Scan on sale (cost=0.00..15406.00 rows=1000000 width=4)
-> Materialize (cost=0.00..2.50 rows=100 width=0)
-> Seq Scan on product (cost=0.00..2.00 rows=100 width=0)
(5 rows)
postgres=# SELECT count(sale.sale_id) FROM sale, product;
count
-----------
100000000
(1 row)
Am I missing something?
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 28 September 2015 at 20:36, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:
Did you perhaps attach a version of the patch you didn't intend to?
Oops. It seems so.
Please find the correct version attached.
Thanks for checking and letting me know.
--
David Rowley http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/>
PostgreSQL Development, 24x7 Support, Training & Services
Attachments:
group_before_join_2015-09-28_c059c53.patchapplication/octet-stream; name=group_before_join_2015-09-28_c059c53.patchDownload
diff --git a/doc/src/sgml/ref/create_aggregate.sgml b/doc/src/sgml/ref/create_aggregate.sgml
index eaa410b..f0e4407 100644
--- a/doc/src/sgml/ref/create_aggregate.sgml
+++ b/doc/src/sgml/ref/create_aggregate.sgml
@@ -27,6 +27,7 @@ CREATE AGGREGATE <replaceable class="parameter">name</replaceable> ( [ <replacea
[ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
[ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
[ , FINALFUNC_EXTRA ]
+ [ , CFUNC = <replaceable class="PARAMETER">cfunc</replaceable> ]
[ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
[ , MSFUNC = <replaceable class="PARAMETER">msfunc</replaceable> ]
[ , MINVFUNC = <replaceable class="PARAMETER">minvfunc</replaceable> ]
@@ -45,6 +46,7 @@ CREATE AGGREGATE <replaceable class="parameter">name</replaceable> ( [ [ <replac
[ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
[ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
[ , FINALFUNC_EXTRA ]
+ [ , CFUNC = <replaceable class="PARAMETER">cfunc</replaceable> ]
[ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
[ , HYPOTHETICAL ]
)
@@ -58,6 +60,7 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
[ , SSPACE = <replaceable class="PARAMETER">state_data_size</replaceable> ]
[ , FINALFUNC = <replaceable class="PARAMETER">ffunc</replaceable> ]
[ , FINALFUNC_EXTRA ]
+ [ , CFUNC = <replaceable class="PARAMETER">cfunc</replaceable> ]
[ , INITCOND = <replaceable class="PARAMETER">initial_condition</replaceable> ]
[ , MSFUNC = <replaceable class="PARAMETER">msfunc</replaceable> ]
[ , MINVFUNC = <replaceable class="PARAMETER">minvfunc</replaceable> ]
@@ -105,12 +108,15 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
functions:
a state transition function
<replaceable class="PARAMETER">sfunc</replaceable>,
- and an optional final calculation function
- <replaceable class="PARAMETER">ffunc</replaceable>.
+ an optional final calculation function
+ <replaceable class="PARAMETER">ffunc</replaceable>,
+ and an optional combine function
+ <replaceable class="PARAMETER">cfunc</replaceable>.
These are used as follows:
<programlisting>
<replaceable class="PARAMETER">sfunc</replaceable>( internal-state, next-data-values ) ---> next-internal-state
<replaceable class="PARAMETER">ffunc</replaceable>( internal-state ) ---> aggregate-value
+<replaceable class="PARAMETER">cfunc</replaceable>( internal-state, internal-state ) ---> next-internal-state
</programlisting>
</para>
@@ -128,6 +134,13 @@ CREATE AGGREGATE <replaceable class="PARAMETER">name</replaceable> (
</para>
<para>
+ An aggregate function may also supply a combining function, which allows
+ the aggregation process to be broken down into multiple steps. This
+ facilitates query optimization techniques such as parallel query,
+ pre-join aggregation and aggregation while sorting.
+ </para>
+
+ <para>
An aggregate function can provide an initial condition,
that is, an initial value for the internal state value.
This is specified and stored in the database as a value of type
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 121c27f..820d982 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -57,6 +57,7 @@ AggregateCreate(const char *aggName,
Oid variadicArgType,
List *aggtransfnName,
List *aggfinalfnName,
+ List *aggcombinefnName,
List *aggmtransfnName,
List *aggminvtransfnName,
List *aggmfinalfnName,
@@ -77,6 +78,7 @@ AggregateCreate(const char *aggName,
Form_pg_proc proc;
Oid transfn;
Oid finalfn = InvalidOid; /* can be omitted */
+ Oid combinefn = InvalidOid; /* can be omitted */
Oid mtransfn = InvalidOid; /* can be omitted */
Oid minvtransfn = InvalidOid; /* can be omitted */
Oid mfinalfn = InvalidOid; /* can be omitted */
@@ -396,6 +398,20 @@ AggregateCreate(const char *aggName,
}
Assert(OidIsValid(finaltype));
+ /* handle the combinefn, if supplied */
+ if (aggcombinefnName)
+ {
+ /*
+ * Combine function must have 2 argument, each of which is the
+ * trans type
+ */
+ fnArgs[0] = aggTransType;
+ fnArgs[1] = aggTransType;
+
+ combinefn = lookup_agg_function(aggcombinefnName, 2, fnArgs,
+ variadicArgType, &finaltype);
+ }
+
/*
* If finaltype (i.e. aggregate return type) is polymorphic, inputs must
* be polymorphic also, else parser will fail to deduce result type.
diff --git a/src/backend/commands/aggregatecmds.c b/src/backend/commands/aggregatecmds.c
index 894c89d..035882e 100644
--- a/src/backend/commands/aggregatecmds.c
+++ b/src/backend/commands/aggregatecmds.c
@@ -61,6 +61,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
char aggKind = AGGKIND_NORMAL;
List *transfuncName = NIL;
List *finalfuncName = NIL;
+ List *combinefuncName = NIL;
List *mtransfuncName = NIL;
List *minvtransfuncName = NIL;
List *mfinalfuncName = NIL;
@@ -124,6 +125,8 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
transfuncName = defGetQualifiedName(defel);
else if (pg_strcasecmp(defel->defname, "finalfunc") == 0)
finalfuncName = defGetQualifiedName(defel);
+ else if (pg_strcasecmp(defel->defname, "cfunc") == 0)
+ combinefuncName = defGetQualifiedName(defel);
else if (pg_strcasecmp(defel->defname, "msfunc") == 0)
mtransfuncName = defGetQualifiedName(defel);
else if (pg_strcasecmp(defel->defname, "minvfunc") == 0)
@@ -383,6 +386,7 @@ DefineAggregate(List *name, List *args, bool oldstyle, List *parameters,
variadicArgType,
transfuncName, /* step function name */
finalfuncName, /* final function name */
+ combinefuncName, /* combine function name */
mtransfuncName, /* fwd trans function name */
minvtransfuncName, /* inv trans function name */
mfinalfuncName, /* final function name */
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index f0d9e94..df9d2e8 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -904,25 +904,38 @@ ExplainNode(PlanState *planstate, List *ancestors,
pname = sname = "Group";
break;
case T_Agg:
- sname = "Aggregate";
- switch (((Agg *) plan)->aggstrategy)
{
- case AGG_PLAIN:
- pname = "Aggregate";
- strategy = "Plain";
- break;
- case AGG_SORTED:
- pname = "GroupAggregate";
- strategy = "Sorted";
- break;
- case AGG_HASHED:
- pname = "HashAggregate";
- strategy = "Hashed";
- break;
- default:
- pname = "Aggregate ???";
- strategy = "???";
- break;
+ char *modifier;
+ Agg *agg = (Agg *) plan;
+
+ sname = "Aggregate";
+
+ if (agg->finalizeAggs == false)
+ modifier = "Partial ";
+ else if (agg->combineStates == true)
+ modifier = "Finalize ";
+ else
+ modifier = "";
+
+ switch (agg->aggstrategy)
+ {
+ case AGG_PLAIN:
+ pname = psprintf("%sAggregate", modifier);
+ strategy = "Plain";
+ break;
+ case AGG_SORTED:
+ pname = psprintf("%sGroupAggregate", modifier);
+ strategy = "Sorted";
+ break;
+ case AGG_HASHED:
+ pname = psprintf("%sHashAggregate", modifier);
+ strategy = "Hashed";
+ break;
+ default:
+ pname = "Aggregate ???";
+ strategy = "???";
+ break;
+ }
}
break;
case T_WindowAgg:
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 2e36855..1639256 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -3,15 +3,24 @@
* nodeAgg.c
* Routines to handle aggregate nodes.
*
- * ExecAgg evaluates each aggregate in the following steps:
+ * ExecAgg normally evaluates each aggregate in the following steps:
*
* transvalue = initcond
* foreach input_tuple do
* transvalue = transfunc(transvalue, input_value(s))
* result = finalfunc(transvalue, direct_argument(s))
*
- * If a finalfunc is not supplied then the result is just the ending
- * value of transvalue.
+ * If a finalfunc is not supplied or finalizeAggs is false, then the result
+ * is just the ending value of transvalue.
+ *
+ * If combineStates is true then we assume that input values are other
+ * transition states. In this case we use the aggregate's combinefunc to
+ * 'add' the passed in trans state to the trans state being operated on.
+ * This allows aggregation to happen in multiple stages. 'combineStates'
+ * will only be true if another nodeAgg is below this one in the plan tree.
+ *
+ * 'finalizeAggs' should be false for all nodeAggs apart from the upper most
+ * one in the plan tree.
*
* If a normal aggregate call specifies DISTINCT or ORDER BY, we sort the
* input tuples and eliminate duplicates (if required) before performing
@@ -197,7 +206,7 @@ typedef struct AggStatePerTransData
*/
int numTransInputs;
- /* Oid of the state transition function */
+ /* Oid of the state transition or combine function */
Oid transfn_oid;
/* Oid of state value's datatype */
@@ -209,8 +218,8 @@ typedef struct AggStatePerTransData
List *aggdirectargs; /* states of direct-argument expressions */
/*
- * fmgr lookup data for transition function. Note in particular that the
- * fn_strict flag is kept here.
+ * fmgr lookup data for transition function or combination function. Note
+ * in particular that the fn_strict flag is kept here.
*/
FmgrInfo transfn;
@@ -421,6 +430,10 @@ static void advance_transition_function(AggState *aggstate,
AggStatePerTrans pertrans,
AggStatePerGroup pergroupstate);
static void advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup);
+static void advance_combination_function(AggState *aggstate,
+ AggStatePerTrans pertrans,
+ AggStatePerGroup pergroupstate);
+static void combine_aggregates(AggState *aggstate, AggStatePerGroup pergroup);
static void process_ordered_aggregate_single(AggState *aggstate,
AggStatePerTrans pertrans,
AggStatePerGroup pergroupstate);
@@ -796,6 +809,8 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
int numGroupingSets = Max(aggstate->phase->numsets, 1);
int numTrans = aggstate->numtrans;
+ Assert(!aggstate->combineStates);
+
for (transno = 0; transno < numTrans; transno++)
{
AggStatePerTrans pertrans = &aggstate->pertrans[transno];
@@ -879,6 +894,109 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
}
}
+static void
+combine_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
+{
+ int transno;
+ int numTrans = aggstate->numtrans;
+
+ /* combine not supported with grouping sets */
+ Assert(aggstate->phase->numsets == 0);
+ Assert(aggstate->combineStates);
+
+ for (transno = 0; transno < numTrans; transno++)
+ {
+ AggStatePerTrans pertrans = &aggstate->pertrans[transno];
+ TupleTableSlot *slot;
+ FunctionCallInfo fcinfo = &pertrans->transfn_fcinfo;
+ AggStatePerGroup pergroupstate = &pergroup[transno];
+
+ /* Evaluate the current input expressions for this aggregate */
+ slot = ExecProject(pertrans->evalproj, NULL);
+ Assert(slot->tts_nvalid >= 1);
+
+ fcinfo->arg[1] = slot->tts_values[0];
+ fcinfo->argnull[1] = slot->tts_isnull[0];
+
+ advance_combination_function(aggstate, pertrans, pergroupstate);
+ }
+}
+
+/*
+ * Perform combination of states between 2 aggregate states. Effectively this
+ * 'adds' two states together by whichever logic is defined in the aggregate
+ * function's combine function.
+ *
+ * Note that in this case transfn is set to the combination function. This
+ * perhaps should be changed to avoid confusion, but one field is ok for now
+ * as they'll never be needed at the same time.
+ */
+static void
+advance_combination_function(AggState *aggstate,
+ AggStatePerTrans pertrans,
+ AggStatePerGroup pergroupstate)
+{
+ FunctionCallInfo fcinfo = &pertrans->transfn_fcinfo;
+ MemoryContext oldContext;
+ Datum newVal;
+
+ if (pertrans->transfn.fn_strict)
+ {
+ /* if we're asked to merge to a NULL state, then do nothing */
+ if (fcinfo->argnull[1])
+ return;
+
+ if (pergroupstate->noTransValue)
+ {
+ pergroupstate->transValue = fcinfo->arg[1];
+ pergroupstate->transValueIsNull = false;
+ return;
+ }
+ }
+
+ /* We run the combine functions in per-input-tuple memory context */
+ oldContext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
+
+ /* set up aggstate->curpertrans for AggGetAggref() */
+ aggstate->curpertrans = pertrans;
+
+ /*
+ * OK to call the combine function
+ */
+ fcinfo->arg[0] = pergroupstate->transValue;
+ fcinfo->argnull[0] = pergroupstate->transValueIsNull;
+ fcinfo->isnull = false; /* just in case combine func doesn't set it */
+
+ newVal = FunctionCallInvoke(fcinfo);
+
+ aggstate->curpertrans = NULL;
+
+ /*
+ * If pass-by-ref datatype, must copy the new value into aggcontext and
+ * pfree the prior transValue. But if the combine function returned a
+ * pointer to its first input, we don't need to do anything.
+ */
+ if (!pertrans->transtypeByVal &&
+ DatumGetPointer(newVal) != DatumGetPointer(pergroupstate->transValue))
+ {
+ if (!fcinfo->isnull)
+ {
+ MemoryContextSwitchTo(aggstate->aggcontexts[aggstate->current_set]->ecxt_per_tuple_memory);
+ newVal = datumCopy(newVal,
+ pertrans->transtypeByVal,
+ pertrans->transtypeLen);
+ }
+ if (!pergroupstate->transValueIsNull)
+ pfree(DatumGetPointer(pergroupstate->transValue));
+ }
+
+ pergroupstate->transValue = newVal;
+ pergroupstate->transValueIsNull = fcinfo->isnull;
+
+ MemoryContextSwitchTo(oldContext);
+
+}
+
/*
* Run the transition function for a DISTINCT or ORDER BY aggregate
@@ -1278,8 +1396,14 @@ finalize_aggregates(AggState *aggstate,
pergroupstate);
}
- finalize_aggregate(aggstate, peragg, pergroupstate,
- &aggvalues[aggno], &aggnulls[aggno]);
+ if (aggstate->finalizeAggs)
+ finalize_aggregate(aggstate, peragg, pergroupstate,
+ &aggvalues[aggno], &aggnulls[aggno]);
+ else
+ {
+ aggvalues[aggno] = pergroupstate->transValue;
+ aggnulls[aggno] = pergroupstate->transValueIsNull;
+ }
}
}
@@ -1294,9 +1418,11 @@ project_aggregates(AggState *aggstate)
ExprContext *econtext = aggstate->ss.ps.ps_ExprContext;
/*
- * Check the qual (HAVING clause); if the group does not match, ignore it.
+ * iif performing the final aggregate stage we'll check the qual (HAVING
+ * clause); if the group does not match, ignore it.
*/
- if (ExecQual(aggstate->ss.ps.qual, econtext, false))
+ if (aggstate->finalizeAggs == false ||
+ ExecQual(aggstate->ss.ps.qual, econtext, false))
{
/*
* Form and return or store a projection tuple using the aggregate
@@ -1811,7 +1937,10 @@ agg_retrieve_direct(AggState *aggstate)
*/
for (;;)
{
- advance_aggregates(aggstate, pergroup);
+ if (!aggstate->combineStates)
+ advance_aggregates(aggstate, pergroup);
+ else
+ combine_aggregates(aggstate, pergroup);
/* Reset per-input-tuple context after each tuple */
ResetExprContext(tmpcontext);
@@ -1919,7 +2048,10 @@ agg_fill_hash_table(AggState *aggstate)
entry = lookup_hash_entry(aggstate, outerslot);
/* Advance the aggregates */
- advance_aggregates(aggstate, entry->pergroup);
+ if (!aggstate->combineStates)
+ advance_aggregates(aggstate, entry->pergroup);
+ else
+ combine_aggregates(aggstate, entry->pergroup);
/* Reset per-input-tuple context after each tuple */
ResetExprContext(tmpcontext);
@@ -2051,6 +2183,8 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
aggstate->pertrans = NULL;
aggstate->curpertrans = NULL;
aggstate->agg_done = false;
+ aggstate->combineStates = node->combineStates;
+ aggstate->finalizeAggs = node->finalizeAggs;
aggstate->input_done = false;
aggstate->pergroup = NULL;
aggstate->grp_firstTuple = NULL;
@@ -2402,7 +2536,21 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
get_func_name(aggref->aggfnoid));
InvokeFunctionExecuteHook(aggref->aggfnoid);
- transfn_oid = aggform->aggtransfn;
+ /*
+ * if this aggregation is performing state combines, then instead of
+ * using the transition function, we'll use the combine function
+ */
+ if (aggstate->combineStates)
+ {
+ transfn_oid = aggform->aggcombinefn;
+
+ /* If not set then the planner messed up */
+ if (!OidIsValid(transfn_oid))
+ elog(ERROR, "combinefn not set during aggregate state combine phase");
+ }
+ else
+ transfn_oid = aggform->aggtransfn;
+
peragg->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
/* Check that aggregate owner has permission to call component fns */
@@ -2583,44 +2731,69 @@ build_pertrans_for_aggref(AggStatePerTrans pertrans,
pertrans->numTransInputs = numArguments;
/*
- * Set up infrastructure for calling the transfn
+ * When combining states, we have no use at all for the aggregate
+ * function's transfn. Instead we use the combinefn. However we do
+ * reuse the transfnexpr for the combinefn, perhaps this should change
*/
- build_aggregate_transfn_expr(inputTypes,
- numArguments,
- numDirectArgs,
- aggref->aggvariadic,
- aggtranstype,
- aggref->inputcollid,
- aggtransfn,
- InvalidOid, /* invtrans is not needed here */
- &transfnexpr,
- NULL);
- fmgr_info(aggtransfn, &pertrans->transfn);
- fmgr_info_set_expr((Node *) transfnexpr, &pertrans->transfn);
-
- InitFunctionCallInfoData(pertrans->transfn_fcinfo,
- &pertrans->transfn,
- pertrans->numTransInputs + 1,
- pertrans->aggCollation,
- (void *) aggstate, NULL);
+ if (aggstate->combineStates)
+ {
+ build_aggregate_combinefn_expr(aggref->aggvariadic,
+ aggtranstype,
+ aggref->inputcollid,
+ aggtransfn,
+ &transfnexpr);
+ fmgr_info(aggtransfn, &pertrans->transfn);
+ fmgr_info_set_expr((Node *) transfnexpr, &pertrans->transfn);
+
+ InitFunctionCallInfoData(pertrans->transfn_fcinfo,
+ &pertrans->transfn,
+ 2,
+ pertrans->aggCollation,
+ (void *) aggstate, NULL);
- /*
- * If the transfn is strict and the initval is NULL, make sure input type
- * and transtype are the same (or at least binary-compatible), so that
- * it's OK to use the first aggregated input value as the initial
- * transValue. This should have been checked at agg definition time, but
- * we must check again in case the transfn's strictness property has been
- * changed.
- */
- if (pertrans->transfn.fn_strict && pertrans->initValueIsNull)
+ }
+ else
{
- if (numArguments <= numDirectArgs ||
- !IsBinaryCoercible(inputTypes[numDirectArgs],
- aggtranstype))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("aggregate %u needs to have compatible input type and transition type",
- aggref->aggfnoid)));
+ /*
+ * Set up infrastructure for calling the transfn
+ */
+ build_aggregate_transfn_expr(inputTypes,
+ numArguments,
+ numDirectArgs,
+ aggref->aggvariadic,
+ aggtranstype,
+ aggref->inputcollid,
+ aggtransfn,
+ InvalidOid, /* invtrans is not needed here */
+ &transfnexpr,
+ NULL);
+ fmgr_info(aggtransfn, &pertrans->transfn);
+ fmgr_info_set_expr((Node *) transfnexpr, &pertrans->transfn);
+
+ InitFunctionCallInfoData(pertrans->transfn_fcinfo,
+ &pertrans->transfn,
+ pertrans->numTransInputs + 1,
+ pertrans->aggCollation,
+ (void *) aggstate, NULL);
+
+ /*
+ * If the transfn is strict and the initval is NULL, make sure input type
+ * and transtype are the same (or at least binary-compatible), so that
+ * it's OK to use the first aggregated input value as the initial
+ * transValue. This should have been checked at agg definition time, but
+ * we must check again in case the transfn's strictness property has been
+ * changed.
+ */
+ if (pertrans->transfn.fn_strict && pertrans->initValueIsNull)
+ {
+ if (numArguments <= numDirectArgs ||
+ !IsBinaryCoercible(inputTypes[numDirectArgs],
+ aggtranstype))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("aggregate %u needs to have compatible input type and transition type",
+ aggref->aggfnoid)));
+ }
}
/* get info about the state value's datatype */
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8fc1cfd..3040ca7 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -1426,8 +1426,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Generate the plan for the subquery */
rel->subplan = subquery_planner(root->glob, subquery,
root,
- false, tuple_fraction,
- &subroot);
+ false, !rel->partial_aggregate,
+ tuple_fraction, &subroot);
rel->subroot = subroot;
/* Isolate the params needed by this specific subplan */
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index d107d76..6f0a6f6 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -118,6 +118,7 @@ bool enable_nestloop = true;
bool enable_material = true;
bool enable_mergejoin = true;
bool enable_hashjoin = true;
+bool enable_earlygrouping = true;
typedef struct
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index a35c881..c177ca5 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -16,11 +16,25 @@
#include <math.h>
+#include "access/htup_details.h"
+#include "catalog/pg_aggregate.h"
#include "executor/executor.h"
#include "foreign/fdwapi.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
+#include "optimizer/planmain.h"
+#include "optimizer/planner.h"
+#include "optimizer/tlist.h"
+#include "optimizer/var.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_oper.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteManip.h"
+#include "utils/syscache.h"
/* Hook for plugins to get control in add_paths_to_joinrel() */
set_join_pathlist_hook_type set_join_pathlist_hook = NULL;
@@ -1474,3 +1488,594 @@ select_mergejoin_clauses(PlannerInfo *root,
return result_list;
}
+
+static List *
+remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved)
+{
+ List *result = NIL;
+ ListCell *jl;
+
+ foreach(jl, joinlist)
+ {
+ Node *jlnode = (Node *) lfirst(jl);
+
+ if (IsA(jlnode, RangeTblRef))
+ {
+ int varno = ((RangeTblRef *) jlnode)->rtindex;
+
+ if (varno == relid)
+ (*nremoved)++;
+ else
+ result = lappend(result, jlnode);
+ }
+ else if (IsA(jlnode, List))
+ {
+ /* Recurse to handle subproblem */
+ List *sublist;
+
+ sublist = remove_rel_from_joinlist((List *) jlnode,
+ relid, nremoved);
+ /* Avoid including empty sub-lists in the result */
+ if (sublist)
+ result = lappend(result, sublist);
+ }
+ else
+ {
+ elog(ERROR, "unrecognized joinlist node type: %d",
+ (int) nodeTag(jlnode));
+ }
+ }
+
+ return result;
+}
+
+static Expr *
+substitute_expr_vars(Expr *expr, int srcrelid, int tarrelid)
+{
+ Expr *newexpr = (Expr *) copyObject(expr);
+ List *vars = pull_vars_of_level((Node *) newexpr, 0);
+ ListCell *l;
+
+ foreach(l, vars)
+ {
+ Var *var = lfirst(l);
+ if (var->varno == srcrelid)
+ var->varno = tarrelid;
+ }
+ return newexpr;
+}
+
+static void
+duplicate_eclass_exprs(PlannerInfo *root, int srcrelid, int tarrelid, List *tlist)
+{
+ ListCell *lc;
+
+ foreach(lc, root->eq_classes)
+ {
+ EquivalenceClass *ec = lfirst(lc);
+ ListCell *l2;
+ List *newmembers = NIL;
+
+ /* Skip classes which don't contain any members for srcrelid */
+ if (!bms_is_member(srcrelid, ec->ec_relids))
+ continue;
+
+ /*
+ * Now look at each member to find out if there's Vars for the
+ * srcrelid. All expression which have Vars belonging to the source
+ * rel must be copied, and the Vars replaced with Vars from the target
+ * relation.
+ */
+ foreach(l2, ec->ec_members)
+ {
+ EquivalenceMember *em = lfirst(l2);
+ Expr *newexpr;
+ EquivalenceMember *newem;
+ ListCell *l3;
+ List *vars;
+
+ /*
+ * No point in looking at Consts, and skip if no Vars belong to
+ * srcrelid.
+ */
+ if (em->em_is_const || !bms_is_member(srcrelid, em->em_relids))
+ continue;
+
+ newexpr = (Expr *) copyObject(em->em_expr);
+ vars = pull_vars_of_level((Node *) newexpr, 0);
+
+ foreach(l3, vars)
+ {
+ Var *var = lfirst(l3);
+ if (var->varno == srcrelid)
+ {
+ AttrNumber resno = 1;
+ ListCell *l4;
+ bool found = false;
+ foreach(l4, tlist)
+ {
+ Expr *expr = lfirst(l4);
+
+ if (equal(expr, em->em_expr))
+ {
+ var->varattno = resno;
+ found = true;
+ break;
+ }
+
+ resno++;
+ }
+
+ if (!found)
+ elog(ERROR, "missing item from tlist");
+
+ var->varno = tarrelid;
+ }
+ }
+
+ /*
+ * Ok, now we just need to generate a new member for the new
+ * expression. We can't append it to the members list of the eclass
+ * just yet, as we're looping over it. Instead we'll just generate
+ * a new list of members to append, and we'll do that after we're
+ * done looping.
+ */
+ newem = makeNode(EquivalenceMember);
+ newem->em_datatype = em->em_datatype;
+ newem->em_expr = newexpr;
+ newem->em_is_child = em->em_is_child;
+ newem->em_is_const = false;
+ newem->em_nullable_relids = bms_copy(em->em_nullable_relids);
+ newem->em_relids = pull_varnos((Node *) newexpr);
+
+ newmembers = lappend(newmembers, newem);
+ }
+
+ /* There should be new members, let's check anyway... */
+ if (newmembers != NIL)
+ {
+ ec->ec_members = list_concat(ec->ec_members, newmembers);
+ ec->ec_relids = bms_add_member(ec->ec_relids, tarrelid);
+ }
+ }
+}
+
+/*
+ * setup_grouping_rel
+ * Build a new subquery RTE which performs the aggregation required of the
+ * outer query, but without joining to any of the rels which are not
+ * required of the aggregate functions.
+ *
+ * Note: For now this removes the original RTE from the joinlist. This will
+ * be changed in a more complete version.
+ */
+List *
+setup_grouping_rel(PlannerInfo *root, List *joinlist)
+{
+ ListCell *lc;
+ int aggregaterel = root->aggregaterel;
+ int groupbyrel = root->groupbyrel;
+ int groupbeforejoinrel = root->groupbeforejoinrel;
+ List *joinconditions = NIL;
+ List *groupexprs = NIL;
+ Query *parse;
+ RangeTblEntry *rte;
+ RangeTblEntry *subrte;
+ RangeTblRef *rtr;
+ RelOptInfo *rel;
+ List *sub_tlist;
+ List *tlist;
+ List *oldtlist;
+ AttrNumber resno;
+ int nremoved;
+
+ /*
+ * If the query's GROUP BY clause does not contain columns from the
+ * aggregate relation, then we must be sure that the GROUP BY clause is
+ * functionally dependent on the join clause to that relation. The reason
+ * for this is that we must ensure that we do not perform a partial
+ * grouping which creates a more tightly grouped set than what is required
+ * of the query's main GROUP BY clause. We can test this simply by looking
+ * for a unique index in the join expressions to the grouprel. If we find
+ * a supporting unique index then we can be certain that early grouping is
+ * legal.
+ */
+ if (groupbyrel != 0 && groupbyrel != aggregaterel)
+ {
+ RelOptInfo *grouprel = find_base_rel(root, groupbyrel);
+
+ foreach(lc, joinlist)
+ {
+ RangeTblRef *rtr = (RangeTblRef *) lfirst(lc);
+ RelOptInfo *joinrel;
+ List *joininfo;
+ Relids joinrelids;
+
+ if (!IsA(rtr, RangeTblRef) || rtr->rtindex == groupbyrel)
+ continue;
+
+ joinrel = find_base_rel(root, rtr->rtindex);
+ joinrelids = bms_union(joinrel->relids, grouprel->relids);
+
+ joininfo = generate_join_implied_equalities(root, joinrelids,
+ joinrel->relids, grouprel);
+
+ if (joininfo != NIL)
+ {
+ ListCell *lc2;
+
+ foreach(lc2, joininfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc2);
+ Node *expr;
+ clause_sides_match_join(rinfo, joinrel, grouprel);
+
+ if (rinfo->outer_is_left)
+ expr = get_rightop(rinfo->clause);
+ else
+ expr = get_leftop(rinfo->clause);
+
+ groupexprs = list_append_unique(groupexprs, expr);
+
+ joinconditions = lappend(joinconditions, rinfo);
+ }
+ }
+ }
+
+ /* If no unique index is found then we cannot perform the group before join */
+ if (!relation_has_unique_index_for(root, grouprel, joinconditions, NIL, NIL))
+ return joinlist;
+
+ foreach(lc, joinconditions)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+ Node *rexpr;
+
+ if (rinfo->outer_is_left)
+ rexpr = get_rightop(rinfo->clause);
+ else
+ rexpr = get_leftop(rinfo->clause);
+
+ groupexprs = list_append_unique(groupexprs, rexpr);
+ }
+ }
+ else if (root->parse->groupClause)
+ {
+ List *tempexprs = get_sortgrouplist_exprs(root->parse->groupClause,
+ root->parse->targetList);
+
+ /*
+ * All grouping expressions come from the aggregate relation. We can
+ * simply just copy these expressions and replace the varno of each
+ * var with the first RTE in the new subquery that we're creating
+ */
+ foreach(lc, tempexprs)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+ Expr *newexpr = substitute_expr_vars(expr, aggregaterel, 1);
+ groupexprs = lappend(groupexprs, newexpr);
+ }
+ }
+
+ parse = makeNode(Query);
+ /************ The following code is still under construction! ************/
+ rte = copyObject(root->simple_rte_array[aggregaterel]);
+ rte->rtekind = RTE_SUBQUERY;
+ rte->subquery = parse;
+ root->simple_rte_array[groupbeforejoinrel] = rte;
+ list_nth_cell(root->parse->rtable, groupbeforejoinrel - 1)->data.ptr_value = rte;
+
+ rel = build_simple_rel(root, groupbeforejoinrel, RELOPT_BASEREL);
+ rel->rtekind = RTE_SUBQUERY;
+ rel->partial_aggregate = true;
+ root->simple_rel_array[groupbeforejoinrel] = rel;
+
+ subrte = copyObject(root->simple_rte_array[aggregaterel]);
+
+ parse->rtable = list_make1(subrte);
+ parse->jointree = makeNode(FromExpr);
+ parse->canSetTag = true;
+ parse->hasAggs = root->parse->hasAggs;
+
+ joinlist = remove_rel_from_joinlist(joinlist, aggregaterel, &nremoved);
+
+ rtr = makeNode(RangeTblRef);
+ rtr->rtindex = groupbeforejoinrel;
+ joinlist = lappend(joinlist, rtr);
+ /* for now don't bother considering the non group before join version */
+ root->simple_rel_array[aggregaterel]->reloptkind = RELOPT_DEADREL;
+
+ oldtlist = tlist = sub_tlist = NIL;
+ resno = 1;
+
+ foreach(lc, root->parse->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ Relids relids = pull_varnos((Node *) tle->expr);
+
+ if (bms_is_member(aggregaterel, relids))
+ {
+ TargetEntry *newtle;
+ Var *var;
+ Expr *newexpr;
+
+ /* We should have ensured this earlier */
+ Assert(bms_membership(relids) == BMS_SINGLETON);
+
+ newexpr = substitute_expr_vars(tle->expr, aggregaterel, 1);
+ newtle = makeTargetEntry(newexpr, resno, tle->resname ? pstrdup(tle->resname) : NULL, false);
+
+ sub_tlist = lappend(sub_tlist, newtle);
+ /* we need to copy this as the tle will be modified later */
+ oldtlist = lappend(oldtlist, copyObject(tle->expr));
+ var = makeVarFromTargetEntry(groupbeforejoinrel, newtle);
+ tlist = lappend(tlist, var);
+ resno++;
+ }
+ }
+
+ parse->targetList = sub_tlist;
+ add_vars_to_targetlist(root, tlist, bms_make_singleton(0), false);
+
+ pull_varattnos((Node *) sub_tlist, aggregaterel, &rte->selectedCols);
+
+ foreach(lc, groupexprs)
+ {
+ Node *node = (Node *) lfirst(lc);
+ TargetEntry *tle;
+ Oid sortop;
+ Oid eqop;
+ bool hashable;
+ SortGroupClause *sgc;
+
+ /* determine the eqop and optional sortop */
+ get_sort_group_operators(exprType(node),
+ false, true, false,
+ &sortop, &eqop, NULL,
+ &hashable);
+
+ tle = tlist_member(node, parse->targetList);
+
+ if (!tle) /* shouldn't happen */
+ elog(ERROR, "failed to find group expression in subplan tlist");
+
+ sgc = makeNode(SortGroupClause);
+ sgc->eqop = eqop;
+ sgc->hashable = hashable;
+ sgc->nulls_first = false;
+ sgc->sortop = sortop;
+ tle->ressortgroupref = sgc->tleSortGroupRef = assignSortGroupRef(tle, parse->targetList);
+
+ parse->groupClause = lappend(parse->groupClause, sgc);
+ }
+
+ rtr = makeNode(RangeTblRef);
+ rtr->rtindex = 1;
+ parse->jointree->fromlist = list_make1(rtr);
+ parse->resultRelation = 0;
+ parse->commandType = CMD_SELECT;
+
+ //root->parse = (Query *) ReplaceVarsFromTargetList((Node *) root->parse,
+ // aggregaterel, 0,
+ // rt_fetch(aggregaterel, root->parse->rtable),
+ // parse->targetList,
+ // REPLACEVARS_SUBSTITUTE_NULL, 0,
+ // &parse->hasSubLinks);
+
+ //root->parse->groupClause = NIL;
+ /*** TEMP HACKS SECTION ***/
+ {
+ List *vars = pull_vars_of_level((Node *) root->parse->targetList, 0);
+ foreach(lc, vars)
+ {
+ Var *v = lfirst(lc);
+ if (v->varno == aggregaterel)
+ v->varno = groupbeforejoinrel;
+ }
+ }
+
+ duplicate_eclass_exprs(root, aggregaterel, groupbeforejoinrel, oldtlist);
+ return joinlist;
+}
+
+/*
+ * prepare_group_before_join
+ * Phase 1 in determining if GROUP BY and/or aggregation may be possible
+ * before joining to other relations. This determination is performed in
+ * multiple phases. In this phase we perform some simple checks in order
+ * to eliminated cases where this is absolutely not possible.
+ * If we determine the optimization maybe possible we add a dummy item to
+ * the rtable so as to reserve a slot in the simple rel arrays for this
+ * alternative way of performing the query. We return false if group
+ * before join is absolutely not possible, and true if it may be possible.
+ */
+bool
+prepare_group_before_join(PlannerInfo *root, Query *parse)
+{
+ List *tlist = parse->targetList;
+ Index aggregaterel = 0;
+ Index grouprel = 0;
+ ListCell *l;
+ Relids relids;
+
+ /* bail if earlygrouping GUC is disabled */
+ if (!enable_earlygrouping)
+ return false;
+
+ /* Nothing to do for queries which don't need aggregation */
+ if (!parse->hasAggs &&
+ parse->havingQual == NULL &&
+ parse->groupClause == NIL)
+ return false;
+
+ /* No joins? It'll be hard to group before that! */
+ if (list_length(parse->rtable) == 1)
+ return false;
+
+ /* XXX do we need to disable in this case? */
+ if (expression_returns_set((Node *) parse->targetList))
+ return false;
+
+ /*
+ * For queries with aggregate functions, we're only able to perform this
+ * group before join optimization when all aggregate function parameters
+ * contain Vars from a single relation. If they do, then this relation will
+ * become the relation which we perform the grouping on.
+ */
+
+ /*
+ * We can skip performing any analysis on aggregate functions for queries
+ * which have none.
+ */
+ if (parse->hasAggs)
+ {
+ Index curaggrel;
+
+ foreach(l, tlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+ Aggref *aggref = (Aggref *) tle->expr;
+ Oid aggcombinefn;
+ HeapTuple aggTuple;
+ Form_pg_aggregate aggform;
+
+ /* Skip over anything that's not an Aggref */
+ if (!IsA(aggref, Aggref))
+ continue;
+
+ /*
+ * We're unable to apply the optimization if any of the aggregates
+ * have a DISTINCT or ORDER BY. The problem with this is that we
+ * currently have no infrastructure to ensure that the groups are
+ * not duplicated by some join that takes place after the grouping
+ * is performed.
+ */
+ if (aggref->aggdistinct != NIL || aggref->aggorder != NIL)
+ return false;
+
+ /*
+ * Now we must check if the aggregate function has a state combine
+ * function set. It's not possible to perform the final aggregate
+ * stage for aggregates which don't have one of these.
+ */
+ aggTuple = SearchSysCache1(AGGFNOID,
+ ObjectIdGetDatum(aggref->aggfnoid));
+ if (!HeapTupleIsValid(aggTuple))
+ elog(ERROR, "cache lookup failed for aggregate %u",
+ aggref->aggfnoid);
+ aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
+ aggcombinefn = aggform->aggcombinefn;
+ ReleaseSysCache(aggTuple);
+
+ if (!OidIsValid(aggcombinefn))
+ return false;
+
+ if (!aggref->aggstar)
+ {
+ relids = pull_varnos((Node *) aggref);
+
+ /*
+ * Check if the aggregate's parameters contain Vars from more
+ * than one rel, if it does then we'll need to disable the
+ * optimization, as we can only optimize cases where there's
+ * a single grouping relation.
+ */
+ if (relids && !bms_get_singleton_member(relids, &curaggrel))
+ return false;
+
+ bms_free(relids);
+
+ /*
+ * If we've found an Aggref where the parameter contains rels
+ * different from the last aggregate function call then we can't
+ * optimize this either, for the same reason as above.
+ */
+ if (aggregaterel != 0 && aggregaterel != curaggrel)
+ return false;
+
+ aggregaterel = curaggrel;
+ }
+ }
+ }
+
+ /*
+ * If the query has no aggs, or the aggs contain no vars, e.g COUNT(*),
+ * then we'll just select any relation as the one to aggregate on
+ */
+ if (aggregaterel == 0)
+ aggregaterel = 1;
+
+ /*
+ * We'll make another pass over the targetlist, this time we know which
+ * relation that we might like to try grouping on. We'll need to ensure
+ * that no targets expression exist which have Vars which belong to our
+ * aggregate relation, and also contain Vars which don't belong to our
+ * aggregate relation. We'll also ensure that any Vars belonging to the
+ * aggregate relation are also present in the GROUP BY clause. This is
+ * possible as the SQL standard does allow columns which are functionally
+ * dependant on the GROUP BY clause.
+ */
+ foreach(l, tlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+ Expr *expr = tle->expr;
+
+ /* Skip over any Aggrefs, we've already checked these above. */
+ if (IsA(expr, Aggref))
+ continue;
+
+ relids = pull_varnos((Node *) expr);
+
+ /* Does this expression require the aggregate relation? */
+ if (bms_is_member(aggregaterel, relids))
+ {
+ if (bms_membership(relids) == BMS_MULTIPLE)
+ return false;
+
+ if (tle->ressortgroupref == 0)
+ return false;
+ }
+ }
+
+ /*
+ * Now we'll have a look at the GROUP BY clause if one exists. We need to
+ * ensure that all of the grouping expressions only contain Vars from a
+ * single rel. This does not have to be the same rel as the one we may have
+ * seen in the Aggref parameters above. The reason for this is that later
+ * we need to perform some analysis to ensure that the GROUP BY columns are
+ * functionally dependant on the join clause to our grouping relation, and
+ * the only way we currently have to test functional dependency is by
+ * looking at unique indexes, and a unique index can only be on a single
+ * relation.
+ */
+ if (root->parse->groupClause != NIL)
+ {
+ List *groupExprs;
+ Relids grouprels;
+
+ groupExprs = get_sortgrouplist_exprs(parse->groupClause, tlist);
+ grouprels = pull_varnos((Node *) groupExprs);
+
+ if (!bms_get_singleton_member(grouprels, &grouprel))
+ return false;
+ }
+
+ /*
+ * At this stage we cannot be sure that performing GROUP BY early is
+ * actually possible as we still need to analyze the join condition for
+ * relations being joined to this one as we need to ensure we don't
+ * accidentally generate a smaller number of groups than is required of the
+ * main GROUP BY clause.
+ */
+ root->aggregaterel = aggregaterel;
+ root->groupbyrel = grouprel;
+ root->groupbeforejoinrel = list_length(parse->rtable) + 1;
+ /*
+ * Another thing we must also do now is add an item to the query's rtable.
+ * We'll need this so we can consider a grouped RelOptInfo during the join
+ * search. We need to do this before setup_simple_rel_arrays() is called.
+ */
+ parse->rtable = lappend(parse->rtable, makeNode(RangeTblEntry));
+
+ return true; /* Group before join may be possible */
+}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 404c6f5..3ccb5ef 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -1045,6 +1045,8 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
groupOperators,
NIL,
numGroups,
+ false,
+ true,
subplan);
}
else
@@ -4501,9 +4503,8 @@ 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)
+ List *groupingSets, long numGroups, bool combineStates,
+ bool finalizeAggs, Plan *lefttree)
{
Agg *node = makeNode(Agg);
Plan *plan = &node->plan;
@@ -4512,6 +4513,8 @@ make_agg(PlannerInfo *root, List *tlist, List *qual,
node->aggstrategy = aggstrategy;
node->numCols = numGroupCols;
+ node->combineStates = combineStates;
+ node->finalizeAggs = finalizeAggs;
node->grpColIdx = grpColIdx;
node->grpOperators = grpOperators;
node->numGroups = numGroups;
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 848df97..3651de6 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -20,11 +20,14 @@
*/
#include "postgres.h"
+#include "nodes/nodeFuncs.h"
#include "optimizer/orclauses.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/planmain.h"
+#include "optimizer/tlist.h"
+#include "optimizer/var.h"
/*
@@ -60,6 +63,7 @@ query_planner(PlannerInfo *root, List *tlist,
RelOptInfo *final_rel;
Index rti;
double total_pages;
+ bool try_group_before_join;
/*
* If the query has an empty join tree, then it's something easy like
@@ -107,6 +111,12 @@ query_planner(PlannerInfo *root, List *tlist,
root->initial_rels = NIL;
/*
+ * Determine if it might be possible to apply grouping of one relation
+ * before joining it to any other relations.
+ */
+ try_group_before_join = prepare_group_before_join(root, parse);
+
+ /*
* Make a flattened version of the rangetable for faster access (this is
* OK because the rangetable won't change any more), and set up an empty
* array for indexing base relations.
@@ -201,6 +211,10 @@ query_planner(PlannerInfo *root, List *tlist,
*/
extract_restriction_or_clauses(root);
+ /* run the final phase to determine if group before join is possible */
+ if (try_group_before_join)
+ joinlist = setup_grouping_rel(root, joinlist);
+
/*
* We should now have size estimates for every actual table involved in
* the query, and we also know which if any have been deleted from the
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 06be922..f6905e0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -80,7 +80,8 @@ typedef struct
static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
static Plan *inheritance_planner(PlannerInfo *root);
-static Plan *grouping_planner(PlannerInfo *root, double tuple_fraction);
+static Plan *grouping_planner(PlannerInfo *root, double tuple_fraction,
+ bool finalize_aggregates);
static void preprocess_rowmarks(PlannerInfo *root);
static double preprocess_limit(PlannerInfo *root,
double tuple_fraction,
@@ -133,7 +134,9 @@ static Plan *build_grouping_chain(PlannerInfo *root,
AttrNumber *groupColIdx,
AggClauseCosts *agg_costs,
long numGroups,
- Plan *result_plan);
+ Plan *result_plan,
+ bool combine_aggregates,
+ bool finalize_aggregates);
/*****************************************************************************
*
@@ -271,7 +274,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
/* primary planning entry point (may recurse for subqueries) */
top_plan = subquery_planner(glob, parse, NULL,
- false, tuple_fraction, &root);
+ false, true, tuple_fraction, &root);
/*
* If creating a plan for a scrollable cursor, make sure it can run
@@ -373,8 +376,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
Plan *
subquery_planner(PlannerGlobal *glob, Query *parse,
PlannerInfo *parent_root,
- bool hasRecursion, double tuple_fraction,
- PlannerInfo **subroot)
+ bool hasRecursion, bool finalize_aggregates,
+ double tuple_fraction, PlannerInfo **subroot)
{
PlannerInfo *root;
Plan *plan;
@@ -673,7 +676,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
plan = inheritance_planner(root);
else
{
- plan = grouping_planner(root, tuple_fraction);
+ plan = grouping_planner(root, tuple_fraction, finalize_aggregates);
/* If it's not SELECT, we need a ModifyTable node */
if (parse->commandType != CMD_SELECT)
{
@@ -1129,7 +1132,7 @@ inheritance_planner(PlannerInfo *root)
subroot.hasInheritedTarget = true;
/* Generate plan */
- subplan = grouping_planner(&subroot, 0.0 /* retrieve all tuples */ );
+ subplan = grouping_planner(&subroot, 0.0 /* retrieve all tuples */ ,true);
/*
* Planning may have modified the query result relation (if there were
@@ -1310,7 +1313,8 @@ inheritance_planner(PlannerInfo *root)
*--------------------
*/
static Plan *
-grouping_planner(PlannerInfo *root, double tuple_fraction)
+grouping_planner(PlannerInfo *root, double tuple_fraction,
+ bool finalize_aggregates)
{
Query *parse = root->parse;
List *tlist = parse->targetList;
@@ -1425,6 +1429,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
Path *cheapest_path;
Path *sorted_path;
Path *best_path;
+ bool combine_aggregates;
MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
@@ -1603,6 +1608,19 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
final_rel = query_planner(root, sub_tlist,
standard_qp_callback, &qp_extra);
+ if (bms_is_member(root->groupbeforejoinrel, final_rel->relids))
+ {
+ combine_aggregates = true;
+ tlist = (List *) ReplaceVarsFromTargetList((Node *) tlist,
+ root->aggregaterel, 0,
+ rt_fetch(root->aggregaterel, root->parse->rtable),
+ sub_tlist,
+ REPLACEVARS_SUBSTITUTE_NULL, 0,
+ &parse->hasSubLinks);
+ }
+ else
+ combine_aggregates = false;
+
/*
* Extract rowcount and width estimates for use below. If final_rel
* has been proven dummy, its rows estimate will be zero; clamp it to
@@ -1983,6 +2001,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
extract_grouping_ops(parse->groupClause),
NIL,
numGroups,
+ combine_aggregates,
+ finalize_aggregates,
result_plan);
/* Hashed aggregation produces randomly-ordered results */
current_pathkeys = NIL;
@@ -2009,7 +2029,9 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
groupColIdx,
&agg_costs,
numGroups,
- result_plan);
+ result_plan,
+ combine_aggregates,
+ finalize_aggregates);
/*
* these are destroyed by build_grouping_chain, so make sure
@@ -2294,6 +2316,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
extract_grouping_ops(parse->distinctClause),
NIL,
numDistinctRows,
+ false,
+ false,
result_plan);
/* Hashed aggregation produces randomly-ordered results */
current_pathkeys = NIL;
@@ -2461,7 +2485,9 @@ build_grouping_chain(PlannerInfo *root,
AttrNumber *groupColIdx,
AggClauseCosts *agg_costs,
long numGroups,
- Plan *result_plan)
+ Plan *result_plan,
+ bool combine_aggregates,
+ bool finalize_aggregates)
{
AttrNumber *top_grpColIdx = groupColIdx;
List *chain = NIL;
@@ -2499,6 +2525,8 @@ build_grouping_chain(PlannerInfo *root,
Assert(groupClause);
Assert(gsets);
+ Assert(finalize_aggregates);
+ Assert(!combine_aggregates);
new_grpColIdx = remap_groupColIdx(root, groupClause);
@@ -2527,6 +2555,8 @@ build_grouping_chain(PlannerInfo *root,
extract_grouping_ops(groupClause),
gsets,
numGroups,
+ false,
+ true,
sort_plan);
sort_plan->lefttree = NULL;
@@ -2563,6 +2593,8 @@ build_grouping_chain(PlannerInfo *root,
extract_grouping_ops(groupClause),
gsets,
numGroups,
+ combine_aggregates,
+ finalize_aggregates,
result_plan);
((Agg *) result_plan)->chain = chain;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index d0bc412..9da104d 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -532,7 +532,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
*/
plan = subquery_planner(root->glob, subquery,
root,
- false, tuple_fraction,
+ false, true, tuple_fraction,
&subroot);
/* Isolate the params needed by this specific subplan */
@@ -571,7 +571,7 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
/* Generate the plan for the ANY subquery; we'll need all rows */
plan = subquery_planner(root->glob, subquery,
root,
- false, 0.0,
+ false, true, 0.0,
&subroot);
/* Isolate the params needed by this specific subplan */
@@ -1163,7 +1163,7 @@ SS_process_ctes(PlannerInfo *root)
*/
plan = subquery_planner(root->glob, subquery,
root,
- cte->cterecursive, 0.0,
+ cte->cterecursive, true, 0.0,
&subroot);
/*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 8884fb1..224c575 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -247,7 +247,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
*/
subplan = subquery_planner(root->glob, subquery,
root,
- false, tuple_fraction,
+ false, true, tuple_fraction,
&subroot);
/* Save subroot and subplan in RelOptInfo for setrefs.c */
@@ -775,6 +775,8 @@ make_union_unique(SetOperationStmt *op, Plan *plan,
extract_grouping_ops(groupList),
NIL,
numGroups,
+ false,
+ true,
plan);
/* Hashed aggregation produces randomly-ordered results */
*sortClauses = NIL;
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 5b0d568..1dde1a7 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -1929,6 +1929,43 @@ build_aggregate_transfn_expr(Oid *agg_input_types,
/*
* Like build_aggregate_transfn_expr, but creates an expression tree for the
+ * combine function of an aggregate, rather than the transition function.
+ */
+void
+build_aggregate_combinefn_expr(bool agg_variadic,
+ Oid agg_state_type,
+ Oid agg_input_collation,
+ Oid combinefn_oid,
+ Expr **combinefnexpr)
+{
+ Param *argp;
+ List *args;
+ FuncExpr *fexpr;
+
+ /* Build arg list to use in the combinefn FuncExpr node. */
+ argp = makeNode(Param);
+ argp->paramkind = PARAM_EXEC;
+ argp->paramid = -1;
+ argp->paramtype = agg_state_type;
+ argp->paramtypmod = -1;
+ argp->paramcollid = agg_input_collation;
+ argp->location = -1;
+
+ /* trans state type is arg 1 and 2 */
+ args = list_make2(argp, argp);
+
+ fexpr = makeFuncExpr(combinefn_oid,
+ agg_state_type,
+ args,
+ InvalidOid,
+ agg_input_collation,
+ COERCE_EXPLICIT_CALL);
+ fexpr->funcvariadic = agg_variadic;
+ *combinefnexpr = (Expr *) fexpr;
+}
+
+/*
+ * Like build_aggregate_transfn_expr, but creates an expression tree for the
* final function of an aggregate, rather than the transition function.
*/
void
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 17053af..87052f2 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -854,6 +854,15 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
{
+ { "enable_earlygrouping", PGC_USERSET, QUERY_TUNING_METHOD,
+ gettext_noop("Enables the planner's ability to perform GROUP BY before joining to unrelated tables."),
+ NULL
+ },
+ &enable_earlygrouping,
+ true,
+ NULL, NULL, NULL
+ },
+ {
{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
gettext_noop("Enables genetic query optimization."),
gettext_noop("This algorithm attempts to do planning without "
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7e75bc2..c180730 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12265,6 +12265,7 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
PGresult *res;
int i_aggtransfn;
int i_aggfinalfn;
+ int i_aggcombinefn;
int i_aggmtransfn;
int i_aggminvtransfn;
int i_aggmfinalfn;
@@ -12281,6 +12282,7 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
int i_convertok;
const char *aggtransfn;
const char *aggfinalfn;
+ const char *aggcombinefn;
const char *aggmtransfn;
const char *aggminvtransfn;
const char *aggmfinalfn;
@@ -12311,7 +12313,26 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
selectSourceSchema(fout, agginfo->aggfn.dobj.namespace->dobj.name);
/* Get aggregate-specific details */
- if (fout->remoteVersion >= 90400)
+ if (fout->remoteVersion >= 90600)
+ {
+ appendPQExpBuffer(query, "SELECT aggtransfn, "
+ "aggfinalfn, aggtranstype::pg_catalog.regtype, "
+ "aggcombinefn, aggmtransfn, aggminvtransfn, "
+ "aggmfinalfn, aggmtranstype::pg_catalog.regtype, "
+ "aggfinalextra, aggmfinalextra, "
+ "aggsortop::pg_catalog.regoperator, "
+ "(aggkind = 'h') AS hypothetical, "
+ "aggtransspace, agginitval, "
+ "aggmtransspace, aggminitval, "
+ "true AS convertok, "
+ "pg_catalog.pg_get_function_arguments(p.oid) AS funcargs, "
+ "pg_catalog.pg_get_function_identity_arguments(p.oid) AS funciargs "
+ "FROM pg_catalog.pg_aggregate a, pg_catalog.pg_proc p "
+ "WHERE a.aggfnoid = p.oid "
+ "AND p.oid = '%u'::pg_catalog.oid",
+ agginfo->aggfn.dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 90400)
{
appendPQExpBuffer(query, "SELECT aggtransfn, "
"aggfinalfn, aggtranstype::pg_catalog.regtype, "
@@ -12421,6 +12442,7 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
i_aggtransfn = PQfnumber(res, "aggtransfn");
i_aggfinalfn = PQfnumber(res, "aggfinalfn");
+ i_aggcombinefn = PQfnumber(res, "aggcombinefn");
i_aggmtransfn = PQfnumber(res, "aggmtransfn");
i_aggminvtransfn = PQfnumber(res, "aggminvtransfn");
i_aggmfinalfn = PQfnumber(res, "aggmfinalfn");
@@ -12438,6 +12460,7 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
aggtransfn = PQgetvalue(res, 0, i_aggtransfn);
aggfinalfn = PQgetvalue(res, 0, i_aggfinalfn);
+ aggcombinefn = PQgetvalue(res, 0, i_aggcombinefn);
aggmtransfn = PQgetvalue(res, 0, i_aggmtransfn);
aggminvtransfn = PQgetvalue(res, 0, i_aggminvtransfn);
aggmfinalfn = PQgetvalue(res, 0, i_aggmfinalfn);
@@ -12526,6 +12549,11 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
appendPQExpBufferStr(details, ",\n FINALFUNC_EXTRA");
}
+ if (strcmp(aggcombinefn, "-") != 0)
+ {
+ appendPQExpBuffer(details, ",\n CFUNC = %s", aggcombinefn);
+ }
+
if (strcmp(aggmtransfn, "-") != 0)
{
appendPQExpBuffer(details, ",\n MSFUNC = %s,\n MINVFUNC = %s,\n MSTYPE = %s",
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index dd6079f..b306f9b 100644
--- a/src/include/catalog/pg_aggregate.h
+++ b/src/include/catalog/pg_aggregate.h
@@ -33,6 +33,7 @@
* aggnumdirectargs number of arguments that are "direct" arguments
* aggtransfn transition function
* aggfinalfn final function (0 if none)
+ * aggcombinefn combine function (0 if none)
* aggmtransfn forward function for moving-aggregate mode (0 if none)
* aggminvtransfn inverse function for moving-aggregate mode (0 if none)
* aggmfinalfn final function for moving-aggregate mode (0 if none)
@@ -56,6 +57,7 @@ CATALOG(pg_aggregate,2600) BKI_WITHOUT_OIDS
int16 aggnumdirectargs;
regproc aggtransfn;
regproc aggfinalfn;
+ regproc aggcombinefn;
regproc aggmtransfn;
regproc aggminvtransfn;
regproc aggmfinalfn;
@@ -85,24 +87,25 @@ typedef FormData_pg_aggregate *Form_pg_aggregate;
* ----------------
*/
-#define Natts_pg_aggregate 17
+#define Natts_pg_aggregate 18
#define Anum_pg_aggregate_aggfnoid 1
#define Anum_pg_aggregate_aggkind 2
#define Anum_pg_aggregate_aggnumdirectargs 3
#define Anum_pg_aggregate_aggtransfn 4
#define Anum_pg_aggregate_aggfinalfn 5
-#define Anum_pg_aggregate_aggmtransfn 6
-#define Anum_pg_aggregate_aggminvtransfn 7
-#define Anum_pg_aggregate_aggmfinalfn 8
-#define Anum_pg_aggregate_aggfinalextra 9
-#define Anum_pg_aggregate_aggmfinalextra 10
-#define Anum_pg_aggregate_aggsortop 11
-#define Anum_pg_aggregate_aggtranstype 12
-#define Anum_pg_aggregate_aggtransspace 13
-#define Anum_pg_aggregate_aggmtranstype 14
-#define Anum_pg_aggregate_aggmtransspace 15
-#define Anum_pg_aggregate_agginitval 16
-#define Anum_pg_aggregate_aggminitval 17
+#define Anum_pg_aggregate_aggcombinefn 6
+#define Anum_pg_aggregate_aggmtransfn 7
+#define Anum_pg_aggregate_aggminvtransfn 8
+#define Anum_pg_aggregate_aggmfinalfn 9
+#define Anum_pg_aggregate_aggfinalextra 10
+#define Anum_pg_aggregate_aggmfinalextra 11
+#define Anum_pg_aggregate_aggsortop 12
+#define Anum_pg_aggregate_aggtranstype 13
+#define Anum_pg_aggregate_aggtransspace 14
+#define Anum_pg_aggregate_aggmtranstype 15
+#define Anum_pg_aggregate_aggmtransspace 16
+#define Anum_pg_aggregate_agginitval 17
+#define Anum_pg_aggregate_aggminitval 18
/*
* Symbolic values for aggkind column. We distinguish normal aggregates
@@ -126,184 +129,184 @@ typedef FormData_pg_aggregate *Form_pg_aggregate;
*/
/* avg */
-DATA(insert ( 2100 n 0 int8_avg_accum numeric_poly_avg int8_avg_accum int8_avg_accum_inv numeric_poly_avg f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2101 n 0 int4_avg_accum int8_avg int4_avg_accum int4_avg_accum_inv int8_avg f f 0 1016 0 1016 0 "{0,0}" "{0,0}" ));
-DATA(insert ( 2102 n 0 int2_avg_accum int8_avg int2_avg_accum int2_avg_accum_inv int8_avg f f 0 1016 0 1016 0 "{0,0}" "{0,0}" ));
-DATA(insert ( 2103 n 0 numeric_avg_accum numeric_avg numeric_avg_accum numeric_accum_inv numeric_avg f f 0 2281 128 2281 128 _null_ _null_ ));
-DATA(insert ( 2104 n 0 float4_accum float8_avg - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2105 n 0 float8_accum float8_avg - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2106 n 0 interval_accum interval_avg interval_accum interval_accum_inv interval_avg f f 0 1187 0 1187 0 "{0 second,0 second}" "{0 second,0 second}" ));
+DATA(insert ( 2100 n 0 int8_avg_accum numeric_poly_avg - int8_avg_accum int8_avg_accum_inv numeric_poly_avg f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2101 n 0 int4_avg_accum int8_avg - int4_avg_accum int4_avg_accum_inv int8_avg f f 0 1016 0 1016 0 "{0,0}" "{0,0}" ));
+DATA(insert ( 2102 n 0 int2_avg_accum int8_avg - int2_avg_accum int2_avg_accum_inv int8_avg f f 0 1016 0 1016 0 "{0,0}" "{0,0}" ));
+DATA(insert ( 2103 n 0 numeric_avg_accum numeric_avg - numeric_avg_accum numeric_accum_inv numeric_avg f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2104 n 0 float4_accum float8_avg - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2105 n 0 float8_accum float8_avg - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2106 n 0 interval_accum interval_avg - interval_accum interval_accum_inv interval_avg f f 0 1187 0 1187 0 "{0 second,0 second}" "{0 second,0 second}" ));
/* sum */
-DATA(insert ( 2107 n 0 int8_avg_accum numeric_poly_sum int8_avg_accum int8_avg_accum_inv numeric_poly_sum f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2108 n 0 int4_sum - int4_avg_accum int4_avg_accum_inv int2int4_sum f f 0 20 0 1016 0 _null_ "{0,0}" ));
-DATA(insert ( 2109 n 0 int2_sum - int2_avg_accum int2_avg_accum_inv int2int4_sum f f 0 20 0 1016 0 _null_ "{0,0}" ));
-DATA(insert ( 2110 n 0 float4pl - - - - f f 0 700 0 0 0 _null_ _null_ ));
-DATA(insert ( 2111 n 0 float8pl - - - - f f 0 701 0 0 0 _null_ _null_ ));
-DATA(insert ( 2112 n 0 cash_pl - cash_pl cash_mi - f f 0 790 0 790 0 _null_ _null_ ));
-DATA(insert ( 2113 n 0 interval_pl - interval_pl interval_mi - f f 0 1186 0 1186 0 _null_ _null_ ));
-DATA(insert ( 2114 n 0 numeric_avg_accum numeric_sum numeric_avg_accum numeric_accum_inv numeric_sum f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2107 n 0 int8_avg_accum numeric_poly_sum - int8_avg_accum int8_avg_accum_inv numeric_poly_sum f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2108 n 0 int4_sum - int8pl int4_avg_accum int4_avg_accum_inv int2int4_sum f f 0 20 0 1016 0 _null_ "{0,0}" ));
+DATA(insert ( 2109 n 0 int2_sum - int8pl int2_avg_accum int2_avg_accum_inv int2int4_sum f f 0 20 0 1016 0 _null_ "{0,0}" ));
+DATA(insert ( 2110 n 0 float4pl - float4pl - - - f f 0 700 0 0 0 _null_ _null_ ));
+DATA(insert ( 2111 n 0 float8pl - float8pl - - - f f 0 701 0 0 0 _null_ _null_ ));
+DATA(insert ( 2112 n 0 cash_pl - cash_pl cash_pl cash_mi - f f 0 790 0 790 0 _null_ _null_ ));
+DATA(insert ( 2113 n 0 interval_pl - interval_pl interval_pl interval_mi - f f 0 1186 0 1186 0 _null_ _null_ ));
+DATA(insert ( 2114 n 0 numeric_avg_accum numeric_sum - numeric_avg_accum numeric_accum_inv numeric_sum f f 0 2281 128 2281 128 _null_ _null_ ));
/* max */
-DATA(insert ( 2115 n 0 int8larger - - - - f f 413 20 0 0 0 _null_ _null_ ));
-DATA(insert ( 2116 n 0 int4larger - - - - f f 521 23 0 0 0 _null_ _null_ ));
-DATA(insert ( 2117 n 0 int2larger - - - - f f 520 21 0 0 0 _null_ _null_ ));
-DATA(insert ( 2118 n 0 oidlarger - - - - f f 610 26 0 0 0 _null_ _null_ ));
-DATA(insert ( 2119 n 0 float4larger - - - - f f 623 700 0 0 0 _null_ _null_ ));
-DATA(insert ( 2120 n 0 float8larger - - - - f f 674 701 0 0 0 _null_ _null_ ));
-DATA(insert ( 2121 n 0 int4larger - - - - f f 563 702 0 0 0 _null_ _null_ ));
-DATA(insert ( 2122 n 0 date_larger - - - - f f 1097 1082 0 0 0 _null_ _null_ ));
-DATA(insert ( 2123 n 0 time_larger - - - - f f 1112 1083 0 0 0 _null_ _null_ ));
-DATA(insert ( 2124 n 0 timetz_larger - - - - f f 1554 1266 0 0 0 _null_ _null_ ));
-DATA(insert ( 2125 n 0 cashlarger - - - - f f 903 790 0 0 0 _null_ _null_ ));
-DATA(insert ( 2126 n 0 timestamp_larger - - - - f f 2064 1114 0 0 0 _null_ _null_ ));
-DATA(insert ( 2127 n 0 timestamptz_larger - - - - f f 1324 1184 0 0 0 _null_ _null_ ));
-DATA(insert ( 2128 n 0 interval_larger - - - - f f 1334 1186 0 0 0 _null_ _null_ ));
-DATA(insert ( 2129 n 0 text_larger - - - - f f 666 25 0 0 0 _null_ _null_ ));
-DATA(insert ( 2130 n 0 numeric_larger - - - - f f 1756 1700 0 0 0 _null_ _null_ ));
-DATA(insert ( 2050 n 0 array_larger - - - - f f 1073 2277 0 0 0 _null_ _null_ ));
-DATA(insert ( 2244 n 0 bpchar_larger - - - - f f 1060 1042 0 0 0 _null_ _null_ ));
-DATA(insert ( 2797 n 0 tidlarger - - - - f f 2800 27 0 0 0 _null_ _null_ ));
-DATA(insert ( 3526 n 0 enum_larger - - - - f f 3519 3500 0 0 0 _null_ _null_ ));
-DATA(insert ( 3564 n 0 network_larger - - - - f f 1205 869 0 0 0 _null_ _null_ ));
+DATA(insert ( 2115 n 0 int8larger - int8larger - - - f f 413 20 0 0 0 _null_ _null_ ));
+DATA(insert ( 2116 n 0 int4larger - int4larger - - - f f 521 23 0 0 0 _null_ _null_ ));
+DATA(insert ( 2117 n 0 int2larger - int2larger - - - f f 520 21 0 0 0 _null_ _null_ ));
+DATA(insert ( 2118 n 0 oidlarger - oidlarger - - - f f 610 26 0 0 0 _null_ _null_ ));
+DATA(insert ( 2119 n 0 float4larger - float4larger - - - f f 623 700 0 0 0 _null_ _null_ ));
+DATA(insert ( 2120 n 0 float8larger - float8larger - - - f f 674 701 0 0 0 _null_ _null_ ));
+DATA(insert ( 2121 n 0 int4larger - int4larger - - - f f 563 702 0 0 0 _null_ _null_ ));
+DATA(insert ( 2122 n 0 date_larger - date_larger - - - f f 1097 1082 0 0 0 _null_ _null_ ));
+DATA(insert ( 2123 n 0 time_larger - time_larger - - - f f 1112 1083 0 0 0 _null_ _null_ ));
+DATA(insert ( 2124 n 0 timetz_larger - timetz_larger - - - f f 1554 1266 0 0 0 _null_ _null_ ));
+DATA(insert ( 2125 n 0 cashlarger - cashlarger - - - f f 903 790 0 0 0 _null_ _null_ ));
+DATA(insert ( 2126 n 0 timestamp_larger - timestamp_larger - - - f f 2064 1114 0 0 0 _null_ _null_ ));
+DATA(insert ( 2127 n 0 timestamptz_larger - timestamptz_larger - - - f f 1324 1184 0 0 0 _null_ _null_ ));
+DATA(insert ( 2128 n 0 interval_larger - interval_larger - - - f f 1334 1186 0 0 0 _null_ _null_ ));
+DATA(insert ( 2129 n 0 text_larger - text_larger - - - f f 666 25 0 0 0 _null_ _null_ ));
+DATA(insert ( 2130 n 0 numeric_larger - numeric_larger - - - f f 1756 1700 0 0 0 _null_ _null_ ));
+DATA(insert ( 2050 n 0 array_larger - array_larger - - - f f 1073 2277 0 0 0 _null_ _null_ ));
+DATA(insert ( 2244 n 0 bpchar_larger - bpchar_larger - - - f f 1060 1042 0 0 0 _null_ _null_ ));
+DATA(insert ( 2797 n 0 tidlarger - tidlarger - - - f f 2800 27 0 0 0 _null_ _null_ ));
+DATA(insert ( 3526 n 0 enum_larger - enum_larger - - - f f 3519 3500 0 0 0 _null_ _null_ ));
+DATA(insert ( 3564 n 0 network_larger - network_larger - - - f f 1205 869 0 0 0 _null_ _null_ ));
/* min */
-DATA(insert ( 2131 n 0 int8smaller - - - - f f 412 20 0 0 0 _null_ _null_ ));
-DATA(insert ( 2132 n 0 int4smaller - - - - f f 97 23 0 0 0 _null_ _null_ ));
-DATA(insert ( 2133 n 0 int2smaller - - - - f f 95 21 0 0 0 _null_ _null_ ));
-DATA(insert ( 2134 n 0 oidsmaller - - - - f f 609 26 0 0 0 _null_ _null_ ));
-DATA(insert ( 2135 n 0 float4smaller - - - - f f 622 700 0 0 0 _null_ _null_ ));
-DATA(insert ( 2136 n 0 float8smaller - - - - f f 672 701 0 0 0 _null_ _null_ ));
-DATA(insert ( 2137 n 0 int4smaller - - - - f f 562 702 0 0 0 _null_ _null_ ));
-DATA(insert ( 2138 n 0 date_smaller - - - - f f 1095 1082 0 0 0 _null_ _null_ ));
-DATA(insert ( 2139 n 0 time_smaller - - - - f f 1110 1083 0 0 0 _null_ _null_ ));
-DATA(insert ( 2140 n 0 timetz_smaller - - - - f f 1552 1266 0 0 0 _null_ _null_ ));
-DATA(insert ( 2141 n 0 cashsmaller - - - - f f 902 790 0 0 0 _null_ _null_ ));
-DATA(insert ( 2142 n 0 timestamp_smaller - - - - f f 2062 1114 0 0 0 _null_ _null_ ));
-DATA(insert ( 2143 n 0 timestamptz_smaller - - - - f f 1322 1184 0 0 0 _null_ _null_ ));
-DATA(insert ( 2144 n 0 interval_smaller - - - - f f 1332 1186 0 0 0 _null_ _null_ ));
-DATA(insert ( 2145 n 0 text_smaller - - - - f f 664 25 0 0 0 _null_ _null_ ));
-DATA(insert ( 2146 n 0 numeric_smaller - - - - f f 1754 1700 0 0 0 _null_ _null_ ));
-DATA(insert ( 2051 n 0 array_smaller - - - - f f 1072 2277 0 0 0 _null_ _null_ ));
-DATA(insert ( 2245 n 0 bpchar_smaller - - - - f f 1058 1042 0 0 0 _null_ _null_ ));
-DATA(insert ( 2798 n 0 tidsmaller - - - - f f 2799 27 0 0 0 _null_ _null_ ));
-DATA(insert ( 3527 n 0 enum_smaller - - - - f f 3518 3500 0 0 0 _null_ _null_ ));
-DATA(insert ( 3565 n 0 network_smaller - - - - f f 1203 869 0 0 0 _null_ _null_ ));
+DATA(insert ( 2131 n 0 int8smaller - int8smaller - - - f f 412 20 0 0 0 _null_ _null_ ));
+DATA(insert ( 2132 n 0 int4smaller - int4smaller - - - f f 97 23 0 0 0 _null_ _null_ ));
+DATA(insert ( 2133 n 0 int2smaller - int2smaller - - - f f 95 21 0 0 0 _null_ _null_ ));
+DATA(insert ( 2134 n 0 oidsmaller - oidsmaller - - - f f 609 26 0 0 0 _null_ _null_ ));
+DATA(insert ( 2135 n 0 float4smaller - float4smaller - - - f f 622 700 0 0 0 _null_ _null_ ));
+DATA(insert ( 2136 n 0 float8smaller - float8smaller - - - f f 672 701 0 0 0 _null_ _null_ ));
+DATA(insert ( 2137 n 0 int4smaller - int4smaller - - - f f 562 702 0 0 0 _null_ _null_ ));
+DATA(insert ( 2138 n 0 date_smaller - date_smaller - - - f f 1095 1082 0 0 0 _null_ _null_ ));
+DATA(insert ( 2139 n 0 time_smaller - time_smaller - - - f f 1110 1083 0 0 0 _null_ _null_ ));
+DATA(insert ( 2140 n 0 timetz_smaller - timetz_smaller - - - f f 1552 1266 0 0 0 _null_ _null_ ));
+DATA(insert ( 2141 n 0 cashsmaller - cashsmaller - - - f f 902 790 0 0 0 _null_ _null_ ));
+DATA(insert ( 2142 n 0 timestamp_smaller - timestamp_smaller - - - f f 2062 1114 0 0 0 _null_ _null_ ));
+DATA(insert ( 2143 n 0 timestamptz_smaller - timestamptz_smaller - - - f f 1322 1184 0 0 0 _null_ _null_ ));
+DATA(insert ( 2144 n 0 interval_smaller - interval_smaller - - - f f 1332 1186 0 0 0 _null_ _null_ ));
+DATA(insert ( 2145 n 0 text_smaller - text_smaller - - - f f 664 25 0 0 0 _null_ _null_ ));
+DATA(insert ( 2146 n 0 numeric_smaller - numeric_smaller - - - f f 1754 1700 0 0 0 _null_ _null_ ));
+DATA(insert ( 2051 n 0 array_smaller - array_smaller - - - f f 1072 2277 0 0 0 _null_ _null_ ));
+DATA(insert ( 2245 n 0 bpchar_smaller - bpchar_smaller - - - f f 1058 1042 0 0 0 _null_ _null_ ));
+DATA(insert ( 2798 n 0 tidsmaller - tidsmaller - - - f f 2799 27 0 0 0 _null_ _null_ ));
+DATA(insert ( 3527 n 0 enum_smaller - enum_smaller - - - f f 3518 3500 0 0 0 _null_ _null_ ));
+DATA(insert ( 3565 n 0 network_smaller - network_smaller - - - f f 1203 869 0 0 0 _null_ _null_ ));
/* count */
-DATA(insert ( 2147 n 0 int8inc_any - int8inc_any int8dec_any - f f 0 20 0 20 0 "0" "0" ));
-DATA(insert ( 2803 n 0 int8inc - int8inc int8dec - f f 0 20 0 20 0 "0" "0" ));
+DATA(insert ( 2147 n 0 int8inc_any - int8pl int8inc_any int8dec_any - f f 0 20 0 20 0 "0" "0" ));
+DATA(insert ( 2803 n 0 int8inc - int8pl int8inc int8dec - f f 0 20 0 20 0 "0" "0" ));
/* var_pop */
-DATA(insert ( 2718 n 0 int8_accum numeric_var_pop int8_accum int8_accum_inv numeric_var_pop f f 0 2281 128 2281 128 _null_ _null_ ));
-DATA(insert ( 2719 n 0 int4_accum numeric_poly_var_pop int4_accum int4_accum_inv numeric_poly_var_pop f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2720 n 0 int2_accum numeric_poly_var_pop int2_accum int2_accum_inv numeric_poly_var_pop f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2721 n 0 float4_accum float8_var_pop - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2722 n 0 float8_accum float8_var_pop - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2723 n 0 numeric_accum numeric_var_pop numeric_accum numeric_accum_inv numeric_var_pop f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2718 n 0 int8_accum numeric_var_pop - int8_accum int8_accum_inv numeric_var_pop f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2719 n 0 int4_accum numeric_poly_var_pop - int4_accum int4_accum_inv numeric_poly_var_pop f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2720 n 0 int2_accum numeric_poly_var_pop - int2_accum int2_accum_inv numeric_poly_var_pop f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2721 n 0 float4_accum float8_var_pop - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2722 n 0 float8_accum float8_var_pop - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2723 n 0 numeric_accum numeric_var_pop - numeric_accum numeric_accum_inv numeric_var_pop f f 0 2281 128 2281 128 _null_ _null_ ));
/* var_samp */
-DATA(insert ( 2641 n 0 int8_accum numeric_var_samp int8_accum int8_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
-DATA(insert ( 2642 n 0 int4_accum numeric_poly_var_samp int4_accum int4_accum_inv numeric_poly_var_samp f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2643 n 0 int2_accum numeric_poly_var_samp int2_accum int2_accum_inv numeric_poly_var_samp f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2644 n 0 float4_accum float8_var_samp - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2645 n 0 float8_accum float8_var_samp - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2646 n 0 numeric_accum numeric_var_samp numeric_accum numeric_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2641 n 0 int8_accum numeric_var_samp - int8_accum int8_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2642 n 0 int4_accum numeric_poly_var_samp - int4_accum int4_accum_inv numeric_poly_var_samp f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2643 n 0 int2_accum numeric_poly_var_samp - int2_accum int2_accum_inv numeric_poly_var_samp f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2644 n 0 float4_accum float8_var_samp - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2645 n 0 float8_accum float8_var_samp - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2646 n 0 numeric_accum numeric_var_samp - numeric_accum numeric_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
/* variance: historical Postgres syntax for var_samp */
-DATA(insert ( 2148 n 0 int8_accum numeric_var_samp int8_accum int8_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
-DATA(insert ( 2149 n 0 int4_accum numeric_poly_var_samp int4_accum int4_accum_inv numeric_poly_var_samp f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2150 n 0 int2_accum numeric_poly_var_samp int2_accum int2_accum_inv numeric_poly_var_samp f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2151 n 0 float4_accum float8_var_samp - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2152 n 0 float8_accum float8_var_samp - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2153 n 0 numeric_accum numeric_var_samp numeric_accum numeric_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2148 n 0 int8_accum numeric_var_samp - int8_accum int8_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2149 n 0 int4_accum numeric_poly_var_samp - int4_accum int4_accum_inv numeric_poly_var_samp f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2150 n 0 int2_accum numeric_poly_var_samp - int2_accum int2_accum_inv numeric_poly_var_samp f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2151 n 0 float4_accum float8_var_samp - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2152 n 0 float8_accum float8_var_samp - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2153 n 0 numeric_accum numeric_var_samp - numeric_accum numeric_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
/* stddev_pop */
-DATA(insert ( 2724 n 0 int8_accum numeric_stddev_pop int8_accum int8_accum_inv numeric_stddev_pop f f 0 2281 128 2281 128 _null_ _null_ ));
-DATA(insert ( 2725 n 0 int4_accum numeric_poly_stddev_pop int4_accum int4_accum_inv numeric_poly_stddev_pop f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2726 n 0 int2_accum numeric_poly_stddev_pop int2_accum int2_accum_inv numeric_poly_stddev_pop f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2727 n 0 float4_accum float8_stddev_pop - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2728 n 0 float8_accum float8_stddev_pop - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2729 n 0 numeric_accum numeric_stddev_pop numeric_accum numeric_accum_inv numeric_stddev_pop f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2724 n 0 int8_accum numeric_stddev_pop - int8_accum int8_accum_inv numeric_stddev_pop f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2725 n 0 int4_accum numeric_poly_stddev_pop - int4_accum int4_accum_inv numeric_poly_stddev_pop f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2726 n 0 int2_accum numeric_poly_stddev_pop - int2_accum int2_accum_inv numeric_poly_stddev_pop f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2727 n 0 float4_accum float8_stddev_pop - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2728 n 0 float8_accum float8_stddev_pop - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2729 n 0 numeric_accum numeric_stddev_pop - numeric_accum numeric_accum_inv numeric_stddev_pop f f 0 2281 128 2281 128 _null_ _null_ ));
/* stddev_samp */
-DATA(insert ( 2712 n 0 int8_accum numeric_stddev_samp int8_accum int8_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
-DATA(insert ( 2713 n 0 int4_accum numeric_poly_stddev_samp int4_accum int4_accum_inv numeric_poly_stddev_samp f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2714 n 0 int2_accum numeric_poly_stddev_samp int2_accum int2_accum_inv numeric_poly_stddev_samp f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2715 n 0 float4_accum float8_stddev_samp - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2716 n 0 float8_accum float8_stddev_samp - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2717 n 0 numeric_accum numeric_stddev_samp numeric_accum numeric_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2712 n 0 int8_accum numeric_stddev_samp - int8_accum int8_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2713 n 0 int4_accum numeric_poly_stddev_samp - int4_accum int4_accum_inv numeric_poly_stddev_samp f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2714 n 0 int2_accum numeric_poly_stddev_samp - int2_accum int2_accum_inv numeric_poly_stddev_samp f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2715 n 0 float4_accum float8_stddev_samp - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2716 n 0 float8_accum float8_stddev_samp - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2717 n 0 numeric_accum numeric_stddev_samp - numeric_accum numeric_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
/* stddev: historical Postgres syntax for stddev_samp */
-DATA(insert ( 2154 n 0 int8_accum numeric_stddev_samp int8_accum int8_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
-DATA(insert ( 2155 n 0 int4_accum numeric_poly_stddev_samp int4_accum int4_accum_inv numeric_poly_stddev_samp f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2156 n 0 int2_accum numeric_poly_stddev_samp int2_accum int2_accum_inv numeric_poly_stddev_samp f f 0 2281 48 2281 48 _null_ _null_ ));
-DATA(insert ( 2157 n 0 float4_accum float8_stddev_samp - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2158 n 0 float8_accum float8_stddev_samp - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
-DATA(insert ( 2159 n 0 numeric_accum numeric_stddev_samp numeric_accum numeric_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2154 n 0 int8_accum numeric_stddev_samp - int8_accum int8_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
+DATA(insert ( 2155 n 0 int4_accum numeric_poly_stddev_samp - int4_accum int4_accum_inv numeric_poly_stddev_samp f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2156 n 0 int2_accum numeric_poly_stddev_samp - int2_accum int2_accum_inv numeric_poly_stddev_samp f f 0 2281 48 2281 48 _null_ _null_ ));
+DATA(insert ( 2157 n 0 float4_accum float8_stddev_samp - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2158 n 0 float8_accum float8_stddev_samp - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
+DATA(insert ( 2159 n 0 numeric_accum numeric_stddev_samp - numeric_accum numeric_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
/* SQL2003 binary regression aggregates */
-DATA(insert ( 2818 n 0 int8inc_float8_float8 - - - - f f 0 20 0 0 0 "0" _null_ ));
-DATA(insert ( 2819 n 0 float8_regr_accum float8_regr_sxx - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2820 n 0 float8_regr_accum float8_regr_syy - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2821 n 0 float8_regr_accum float8_regr_sxy - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2822 n 0 float8_regr_accum float8_regr_avgx - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2823 n 0 float8_regr_accum float8_regr_avgy - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2824 n 0 float8_regr_accum float8_regr_r2 - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2825 n 0 float8_regr_accum float8_regr_slope - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2826 n 0 float8_regr_accum float8_regr_intercept - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2827 n 0 float8_regr_accum float8_covar_pop - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2828 n 0 float8_regr_accum float8_covar_samp - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
-DATA(insert ( 2829 n 0 float8_regr_accum float8_corr - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2818 n 0 int8inc_float8_float8 - - - - - f f 0 20 0 0 0 "0" _null_ ));
+DATA(insert ( 2819 n 0 float8_regr_accum float8_regr_sxx - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2820 n 0 float8_regr_accum float8_regr_syy - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2821 n 0 float8_regr_accum float8_regr_sxy - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2822 n 0 float8_regr_accum float8_regr_avgx - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2823 n 0 float8_regr_accum float8_regr_avgy - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2824 n 0 float8_regr_accum float8_regr_r2 - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2825 n 0 float8_regr_accum float8_regr_slope - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2826 n 0 float8_regr_accum float8_regr_intercept - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2827 n 0 float8_regr_accum float8_covar_pop - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2828 n 0 float8_regr_accum float8_covar_samp - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
+DATA(insert ( 2829 n 0 float8_regr_accum float8_corr - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
/* boolean-and and boolean-or */
-DATA(insert ( 2517 n 0 booland_statefunc - bool_accum bool_accum_inv bool_alltrue f f 58 16 0 2281 16 _null_ _null_ ));
-DATA(insert ( 2518 n 0 boolor_statefunc - bool_accum bool_accum_inv bool_anytrue f f 59 16 0 2281 16 _null_ _null_ ));
-DATA(insert ( 2519 n 0 booland_statefunc - bool_accum bool_accum_inv bool_alltrue f f 58 16 0 2281 16 _null_ _null_ ));
+DATA(insert ( 2517 n 0 booland_statefunc - - bool_accum bool_accum_inv bool_alltrue f f 58 16 0 2281 16 _null_ _null_ ));
+DATA(insert ( 2518 n 0 boolor_statefunc - - bool_accum bool_accum_inv bool_anytrue f f 59 16 0 2281 16 _null_ _null_ ));
+DATA(insert ( 2519 n 0 booland_statefunc - - bool_accum bool_accum_inv bool_alltrue f f 58 16 0 2281 16 _null_ _null_ ));
/* bitwise integer */
-DATA(insert ( 2236 n 0 int2and - - - - f f 0 21 0 0 0 _null_ _null_ ));
-DATA(insert ( 2237 n 0 int2or - - - - f f 0 21 0 0 0 _null_ _null_ ));
-DATA(insert ( 2238 n 0 int4and - - - - f f 0 23 0 0 0 _null_ _null_ ));
-DATA(insert ( 2239 n 0 int4or - - - - f f 0 23 0 0 0 _null_ _null_ ));
-DATA(insert ( 2240 n 0 int8and - - - - f f 0 20 0 0 0 _null_ _null_ ));
-DATA(insert ( 2241 n 0 int8or - - - - f f 0 20 0 0 0 _null_ _null_ ));
-DATA(insert ( 2242 n 0 bitand - - - - f f 0 1560 0 0 0 _null_ _null_ ));
-DATA(insert ( 2243 n 0 bitor - - - - f f 0 1560 0 0 0 _null_ _null_ ));
+DATA(insert ( 2236 n 0 int2and - int2and - - - f f 0 21 0 0 0 _null_ _null_ ));
+DATA(insert ( 2237 n 0 int2or - int2or - - - f f 0 21 0 0 0 _null_ _null_ ));
+DATA(insert ( 2238 n 0 int4and - int4and - - - f f 0 23 0 0 0 _null_ _null_ ));
+DATA(insert ( 2239 n 0 int4or - int4or - - - f f 0 23 0 0 0 _null_ _null_ ));
+DATA(insert ( 2240 n 0 int8and - int8and - - - f f 0 20 0 0 0 _null_ _null_ ));
+DATA(insert ( 2241 n 0 int8or - int8or - - - f f 0 20 0 0 0 _null_ _null_ ));
+DATA(insert ( 2242 n 0 bitand - bitand - - - f f 0 1560 0 0 0 _null_ _null_ ));
+DATA(insert ( 2243 n 0 bitor - bitor - - - f f 0 1560 0 0 0 _null_ _null_ ));
/* xml */
-DATA(insert ( 2901 n 0 xmlconcat2 - - - - f f 0 142 0 0 0 _null_ _null_ ));
+DATA(insert ( 2901 n 0 xmlconcat2 - - - - - f f 0 142 0 0 0 _null_ _null_ ));
/* array */
-DATA(insert ( 2335 n 0 array_agg_transfn array_agg_finalfn - - - t f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 4053 n 0 array_agg_array_transfn array_agg_array_finalfn - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 2335 n 0 array_agg_transfn array_agg_finalfn - - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 4053 n 0 array_agg_array_transfn array_agg_array_finalfn - - - - t f 0 2281 0 0 0 _null_ _null_ ));
/* text */
-DATA(insert ( 3538 n 0 string_agg_transfn string_agg_finalfn - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3538 n 0 string_agg_transfn string_agg_finalfn - - - - f f 0 2281 0 0 0 _null_ _null_ ));
/* bytea */
-DATA(insert ( 3545 n 0 bytea_string_agg_transfn bytea_string_agg_finalfn - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3545 n 0 bytea_string_agg_transfn bytea_string_agg_finalfn - - - - f f 0 2281 0 0 0 _null_ _null_ ));
/* json */
-DATA(insert ( 3175 n 0 json_agg_transfn json_agg_finalfn - - - f f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3197 n 0 json_object_agg_transfn json_object_agg_finalfn - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3175 n 0 json_agg_transfn json_agg_finalfn - - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3197 n 0 json_object_agg_transfn json_object_agg_finalfn - - - - f f 0 2281 0 0 0 _null_ _null_ ));
/* jsonb */
-DATA(insert ( 3267 n 0 jsonb_agg_transfn jsonb_agg_finalfn - - - f f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3270 n 0 jsonb_object_agg_transfn jsonb_object_agg_finalfn - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3267 n 0 jsonb_agg_transfn jsonb_agg_finalfn - - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3270 n 0 jsonb_object_agg_transfn jsonb_object_agg_finalfn - - - - f f 0 2281 0 0 0 _null_ _null_ ));
/* ordered-set and hypothetical-set aggregates */
-DATA(insert ( 3972 o 1 ordered_set_transition percentile_disc_final - - - t f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3974 o 1 ordered_set_transition percentile_cont_float8_final - - - f f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3976 o 1 ordered_set_transition percentile_cont_interval_final - - - f f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3978 o 1 ordered_set_transition percentile_disc_multi_final - - - t f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3980 o 1 ordered_set_transition percentile_cont_float8_multi_final - - - f f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3982 o 1 ordered_set_transition percentile_cont_interval_multi_final - - - f f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3984 o 0 ordered_set_transition mode_final - - - t f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3986 h 1 ordered_set_transition_multi rank_final - - - t f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3988 h 1 ordered_set_transition_multi percent_rank_final - - - t f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3990 h 1 ordered_set_transition_multi cume_dist_final - - - t f 0 2281 0 0 0 _null_ _null_ ));
-DATA(insert ( 3992 h 1 ordered_set_transition_multi dense_rank_final - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3972 o 1 ordered_set_transition percentile_disc_final - - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3974 o 1 ordered_set_transition percentile_cont_float8_final - - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3976 o 1 ordered_set_transition percentile_cont_interval_final - - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3978 o 1 ordered_set_transition percentile_disc_multi_final - - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3980 o 1 ordered_set_transition percentile_cont_float8_multi_final - - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3982 o 1 ordered_set_transition percentile_cont_interval_multi_final - - - - f f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3984 o 0 ordered_set_transition mode_final - - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3986 h 1 ordered_set_transition_multi rank_final - - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3988 h 1 ordered_set_transition_multi percent_rank_final - - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3990 h 1 ordered_set_transition_multi cume_dist_final - - - - t f 0 2281 0 0 0 _null_ _null_ ));
+DATA(insert ( 3992 h 1 ordered_set_transition_multi dense_rank_final - - - - t f 0 2281 0 0 0 _null_ _null_ ));
/*
@@ -322,6 +325,7 @@ extern ObjectAddress AggregateCreate(const char *aggName,
Oid variadicArgType,
List *aggtransfnName,
List *aggfinalfnName,
+ List *aggcombinefnName,
List *aggmtransfnName,
List *aggminvtransfnName,
List *aggmfinalfnName,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 4ae2f3e..70e88c7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1845,6 +1845,8 @@ typedef struct AggState
AggStatePerTrans curpertrans; /* currently active trans state */
bool input_done; /* indicates end of input */
bool agg_done; /* indicates completion of Agg scan */
+ bool combineStates; /* input tuples contain transition states */
+ bool finalizeAggs; /* should we call the finalfn on agg states? */
int projected_set; /* The last projected grouping set */
int current_set; /* The current grouping set being evaluated */
Bitmapset *grouped_cols; /* grouped cols in current projection */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index cc259f1..6a1353d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -715,6 +715,8 @@ typedef struct Agg
AggStrategy aggstrategy;
int numCols; /* number of grouping columns */
AttrNumber *grpColIdx; /* their indexes in the target list */
+ bool combineStates; /* input tuples contain transition states */
+ bool finalizeAggs; /* should we call the finalfn on agg states? */
Oid *grpOperators; /* equality operators to compare with */
long numGroups; /* estimated number of groups in input */
List *groupingSets; /* grouping sets to use */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 79bed33..e8ca077 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -16,6 +16,7 @@
#include "access/sdir.h"
#include "lib/stringinfo.h"
+#include "nodes/plannodes.h"
#include "nodes/params.h"
#include "nodes/parsenodes.h"
#include "storage/block.h"
@@ -260,7 +261,14 @@ typedef struct PlannerInfo
bool hasPseudoConstantQuals; /* true if any RestrictInfo has
* pseudoconstant = true */
bool hasRecursion; /* true if planning a recursive WITH item */
-
+ Index aggregaterel; /* Index of relation to perform early
+ * aggregation on, or 0 if not enabled */
+ Index groupbyrel; /* Index of single relation to perform group by
+ * on, or 0 if not enabled. */
+ Index groupbeforejoinrel; /* Index of additional rel which was added
+ * to rewrite the group by to happen before
+ * joining to any other relations.
+ */
/* These fields are used only when hasRecursion is true: */
int wt_param_id; /* PARAM_EXEC ID for the work table */
struct Plan *non_recursive_plan; /* plan for non-recursive term */
@@ -450,7 +458,8 @@ typedef struct RelOptInfo
/* per-relation planner control flags */
bool consider_startup; /* keep cheap-startup-cost paths? */
bool consider_param_startup; /* ditto, for parameterized paths? */
-
+ bool partial_aggregate; /* true if aggregates should not be
+ * finalized. */
/* materialization information */
List *reltargetlist; /* Vars to be output by scan of relation */
List *pathlist; /* Path structures */
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index dd43e45..aaa1e08 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -61,6 +61,7 @@ extern bool enable_nestloop;
extern bool enable_material;
extern bool enable_mergejoin;
extern bool enable_hashjoin;
+extern bool enable_earlygrouping;
extern int constraint_exclusion;
extern double clamp_row_est(double nrows);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 52b077a..9540061 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -36,6 +36,8 @@ extern RelOptInfo *query_planner(PlannerInfo *root, List *tlist,
extern void preprocess_minmax_aggregates(PlannerInfo *root, List *tlist);
extern Plan *optimize_minmax_aggregates(PlannerInfo *root, List *tlist,
const AggClauseCosts *aggcosts, Path *best_path);
+extern List *setup_grouping_rel(PlannerInfo *root, List *joinlist);
+extern bool prepare_group_before_join(PlannerInfo *root, Query *parse);
/*
* prototypes for plan/createplan.c
@@ -59,9 +61,8 @@ 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);
+ List *groupingSets, long numGroups, bool combineStates,
+ bool finalizeAggs, Plan *lefttree);
extern WindowAgg *make_windowagg(PlannerInfo *root, List *tlist,
List *windowFuncs, Index winref,
int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index b10a504..cfedf1f 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -32,8 +32,8 @@ extern PlannedStmt *standard_planner(Query *parse, int cursorOptions,
extern Plan *subquery_planner(PlannerGlobal *glob, Query *parse,
PlannerInfo *parent_root,
- bool hasRecursion, double tuple_fraction,
- PlannerInfo **subroot);
+ bool hasRecursion, bool finalize_aggregates,
+ double tuple_fraction, PlannerInfo **subroot);
extern void add_tlist_costs_to_plan(PlannerInfo *root, Plan *plan,
List *tlist);
diff --git a/src/include/parser/parse_agg.h b/src/include/parser/parse_agg.h
index e2b3894..621b6b9 100644
--- a/src/include/parser/parse_agg.h
+++ b/src/include/parser/parse_agg.h
@@ -46,6 +46,12 @@ extern void build_aggregate_transfn_expr(Oid *agg_input_types,
Expr **transfnexpr,
Expr **invtransfnexpr);
+extern void build_aggregate_combinefn_expr(bool agg_variadic,
+ Oid agg_state_type,
+ Oid agg_input_collation,
+ Oid combinefn_oid,
+ Expr **combinefnexpr);
+
extern void build_aggregate_finalfn_expr(Oid *agg_input_types,
int num_finalfn_inputs,
Oid agg_state_type,
diff --git a/src/test/regress/expected/create_aggregate.out b/src/test/regress/expected/create_aggregate.out
index 82a34fb..906c2b5 100644
--- a/src/test/regress/expected/create_aggregate.out
+++ b/src/test/regress/expected/create_aggregate.out
@@ -101,6 +101,13 @@ CREATE AGGREGATE sumdouble (float8)
msfunc = float8pl,
minvfunc = float8mi
);
+-- aggregate combine functions
+CREATE AGGREGATE mymax (int)
+(
+ stype = int4,
+ sfunc = int4larger,
+ cfunc = int4larger
+);
-- invalid: nonstrict inverse with strict forward function
CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
$$ SELECT $1 - $2; $$
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 00ef421..af3f9b7 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -2,6 +2,7 @@ SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
name | setting
----------------------+---------
enable_bitmapscan | on
+ enable_earlygrouping | on
enable_hashagg | on
enable_hashjoin | on
enable_indexonlyscan | on
@@ -12,7 +13,7 @@ SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
enable_seqscan | on
enable_sort | on
enable_tidscan | on
-(11 rows)
+(12 rows)
CREATE TABLE foo2(fooid int, f2 int);
INSERT INTO foo2 VALUES(1, 11);
diff --git a/src/test/regress/sql/create_aggregate.sql b/src/test/regress/sql/create_aggregate.sql
index 0ec1572..0ebd0d9 100644
--- a/src/test/regress/sql/create_aggregate.sql
+++ b/src/test/regress/sql/create_aggregate.sql
@@ -115,6 +115,14 @@ CREATE AGGREGATE sumdouble (float8)
minvfunc = float8mi
);
+-- aggregate combine functions
+CREATE AGGREGATE mymax (int)
+(
+ stype = int4,
+ sfunc = int4larger,
+ cfunc = int4larger
+);
+
-- invalid: nonstrict inverse with strict forward function
CREATE FUNCTION float8mi_n(float8, float8) RETURNS float8 AS
On 2015/09/28 17:04, David Rowley wrote:
On 28 September 2015 at 20:36, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:Did you perhaps attach a version of the patch you didn't intend to?
Oops. It seems so.
Please find the correct version attached.
Thanks, this one works fine.
By the way, you may have noticed that the append_rel_list would be broken
if the proposed optimization is applied to a appendrel parent.
CREATE TABLE sale_1() INHERITS(sale);
CREATE TABLE sale_2() INHERITS(sale);
EXPLAIN SELECT count(*) FROM sale;
QUERY PLAN
------------------------------------------------------
Finalize Aggregate (cost=0.01..0.02 rows=1 width=0)
-> Result (cost=0.00..0.01 rows=1 width=0)
One-Time Filter: false
(3 rows)
Moreover, would partial aggregation work below Append?
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 28 September 2015 at 23:17, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:
On 2015/09/28 17:04, David Rowley wrote:
On 28 September 2015 at 20:36, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp>
wrote:
Did you perhaps attach a version of the patch you didn't intend to?
Oops. It seems so.
Please find the correct version attached.
Thanks, this one works fine.
By the way, you may have noticed that the append_rel_list would be broken
if the proposed optimization is applied to a appendrel parent.CREATE TABLE sale_1() INHERITS(sale);
CREATE TABLE sale_2() INHERITS(sale);EXPLAIN SELECT count(*) FROM sale;
QUERY PLAN
------------------------------------------------------
Finalize Aggregate (cost=0.01..0.02 rows=1 width=0)
-> Result (cost=0.00..0.01 rows=1 width=0)
One-Time Filter: false
(3 rows)
Thanks. I've changed this locally to disable the optimisation in this case.
Moreover, would partial aggregation work below Append?
Do you mean for cases like:
create table a as select x.x a from generate_series(1,1000000) x(x);
select sum(a) from (select a from a union all select a from a) a;
to allow the aggregation to happen before the append?
On testing this I do see that writing the query as:
select sum(a) from (select sum(a) a from a union all select sum(a) from a)
a;
causes it to execute marginally faster. 174.280 ms vs 153.498 ms on my
laptop.
However pushing aggregation below Append nodes is not something I'm aiming
to do for this patch.
--
David Rowley http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/>
PostgreSQL Development, 24x7 Support, Training & Services
On 2015/09/28 20:58, David Rowley wrote:
On 28 September 2015 at 23:17, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:Moreover, would partial aggregation work below Append?
Do you mean for cases like:
create table a as select x.x a from generate_series(1,1000000) x(x);
select sum(a) from (select a from a union all select a from a) a;to allow the aggregation to happen before the append?
Yes.
On testing this I do see that writing the query as:
select sum(a) from (select sum(a) a from a union all select sum(a) from a)
a;causes it to execute marginally faster. 174.280 ms vs 153.498 ms on my
laptop.
However pushing aggregation below Append nodes is not something I'm aiming
to do for this patch.
I see. I recall reading in archives that pushing aggregates below append
was not found to make much difference as your little test suggests, too.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers