PoC: Grouped base relation
Attached is a draft patch that lets partial aggregation happen at base
relation level. If the relations contain relatively small number of groups,
the number of input rows of the aggregation at the query level can be reduced
this way. Also, if append relation and postgres_fdw planning is enhanced
accordingly, patch like this can let us aggregate individual tables on remote
servers (e.g. shard nodes) and thus reduce the amount of rows subject to the
final aggregation.
For example, consider query
SELECT b.j, sum(a.x) FROM a, b WHERE a.i = b.j GROUP BY b.j;
and tables "a"
i | x
-------
1 | 3
1 | 4
and "b"
j
---
1
1
The base relations grouped look like
i | sum(a.x)| count(*)
-----------------------
1 | 7 | 2
and
j | count(*)
-------------
1 | 2
A few "footnotes":
* Equivalence class {a.i, b.j} tells that "a" can be grouped by "i", besides
the grouping of "b" which is explicitly required by GROUP BY b.j clause.)
* To transfer the aggregate results to upper nodes, I introduced a concept of
"grouped variable". Base relation has special target which the planner uses to
generate "grouped paths". The grouped target contains one grouped variable per
aggregate that the relation computes. During final processing of the plan
(setrefs.c), the corresponding (partial) aggregate is restored in the query
target if needed - typically this happens to ensure that the final aggregate
references the output of the partial aggregate.
* So far the grouped variable is only used for aggregates, but it might be
useful for grouping expressions in the future as well. Currently the base
relation can only be grouped by a plain Var, but it might be worth grouping it
by generic grouping expressions of the GROUP BY clause, and using the grouped
var mechanism to propagate the expression value to the query target.
As for the example, the processing continues by joining the partially grouped
sets:
i | sum(x)| count(i.*) | j | count(j.*)
----------------------------------------
1 | 7 | 2 | 1 | 3
Before performing the final aggregation, we need to multiply sum(a.x) by
count(j.*) because w/o the aggregation at base relation level the input
of the query-level aggregation would look like
a.i | a.x | b.j
----------------
1 | 3 | 1
1 | 4 | 1
1 | 3 | 1
1 | 4 | 1
In other words, grouping of the base relation "b" below the join prevents the
join from bringing per-group input set to the aggregate input multiple
times. To compensate for this effect, I've added a new field "aggtransmultifn"
to pg_aggregate catalog. It "multiplies" the aggregate state w/o repeated
processing of the same input set many times. sum() is an example of an
aggregate that needs such processing, avg() is one that does not.
The example query can eventually produce plans like
QUERY PLAN
------------------------------------------------------
Finalize HashAggregate
Group Key: b.j
-> Gather
Workers Planned: 2
-> Hash Join
Hash Cond: (a.i = b.j)
-> Partial HashAggregate
Group Key: a.i
-> Parallel Seq Scan on a
-> Hash
-> Partial HashAggregate
Group Key: b.j
-> Parallel Seq Scan on b
or
QUERY PLAN
------------------------------------------------------
Finalize HashAggregate
Group Key: b.j
-> Hash Join
Hash Cond: (a.i = b.j)
-> Gather
Workers Planned: 2
-> Partial HashAggregate
Group Key: a.i
-> Parallel Seq Scan on a
-> Hash
-> Gather
Workers Planned: 1
-> Partial HashAggregate
Group Key: b.j
-> Parallel Seq Scan on b
An obvious limitation is that neither grouping expression nor aggregate
argument can be below the nullable side of outer join. In such a case the
aggregate at the base relation level wouldn't receive the NULL values that it
does receive at the query level. Also, no aggregate can reference multiple
tables.
Does this concept seem worth to continue coding?
BTW, if anyone wants to play with the current version:
1. Don't forget to initialize a new cluster (initdb) first. I decided not to
bump CATALOG_VERSION_NO so far because it'd probably make the patch
incompatible with master branch quite soon.
2. Only hash aggregation is implemented so far at the base relation level.
3. As for sum() aggregate, only sum(float4) is supposed to work correctly so
far - this is related to the pg_aggregate changes mentioned above. avg()
should work in general, and I didn't care about the other ones so far.
4. As for joins, only hash join is able to process the grouped relations. I
didn't want to do too much coding until there's a consensus on the design.
--
Antonin Houska
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt
Web: http://www.postgresql-support.de, http://www.cybertec.at
Attachments:
grouped_base_rel.difftext/x-diffDownload
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index 930f2f1..59836ba
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyAggref(const Aggref *from)
*** 1245,1250 ****
--- 1245,1251 ----
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(aggvariadic);
COPY_SCALAR_FIELD(aggkind);
+ COPY_SCALAR_FIELD(aggtransmultifn);
COPY_SCALAR_FIELD(agglevelsup);
COPY_SCALAR_FIELD(aggsplit);
COPY_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index 806d0a9..8e789fe
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outPlannerInfo(StringInfo str, const Pl
*** 2046,2051 ****
--- 2046,2053 ----
WRITE_NODE_FIELD(append_rel_list);
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(placeholder_list);
+ WRITE_NODE_FIELD(grouped_var_list);
+ WRITE_BOOL_FIELD(all_baserels_grouped);
WRITE_NODE_FIELD(fkey_list);
WRITE_NODE_FIELD(query_pathkeys);
WRITE_NODE_FIELD(group_pathkeys);
diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c
new file mode 100644
index b5cab0c..f89406d
*** a/src/backend/optimizer/geqo/geqo_eval.c
--- b/src/backend/optimizer/geqo/geqo_eval.c
*************** merge_clump(PlannerInfo *root, List *clu
*** 265,271 ****
if (joinrel)
{
/* Create GatherPaths for any useful partial paths for rel */
! generate_gather_paths(root, joinrel);
/* Find and save the cheapest paths for this joinrel */
set_cheapest(joinrel);
--- 265,271 ----
if (joinrel)
{
/* Create GatherPaths for any useful partial paths for rel */
! generate_gather_paths(root, joinrel, false);
/* Find and save the cheapest paths for this joinrel */
set_cheapest(joinrel);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
new file mode 100644
index 46d7d06..0ae0a1b
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
***************
*** 24,29 ****
--- 24,30 ----
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "foreign/fdwapi.h"
+ #include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#ifdef OPTIMIZER_DEBUG
***************
*** 44,49 ****
--- 45,51 ----
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
+ #include "utils/selfuncs.h"
/* results of subquery_is_pushdown_safe */
*************** static void set_rel_pathlist(PlannerInfo
*** 76,81 ****
--- 78,85 ----
static void set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static void create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel);
+ static void create_plain_grouped_path(PlannerInfo *root, RelOptInfo *rel,
+ Path *subpath);
static void set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
*************** set_rel_pathlist(PlannerInfo *root, RelO
*** 457,463 ****
* we'll consider gathering partial paths for the parent appendrel.)
*/
if (rel->reloptkind == RELOPT_BASEREL)
! generate_gather_paths(root, rel);
/*
* Allow a plugin to editorialize on the set of Paths for this base
--- 461,470 ----
* we'll consider gathering partial paths for the parent appendrel.)
*/
if (rel->reloptkind == RELOPT_BASEREL)
! {
! generate_gather_paths(root, rel, false);
! generate_gather_paths(root, rel, true);
! }
/*
* Allow a plugin to editorialize on the set of Paths for this base
*************** set_plain_rel_pathlist(PlannerInfo *root
*** 656,662 ****
required_outer = rel->lateral_relids;
/* Consider sequential scan */
! add_path(rel, create_seqscan_path(root, rel, required_outer, 0));
/* If appropriate, consider parallel sequential scan */
if (rel->consider_parallel && required_outer == NULL)
--- 663,669 ----
required_outer = rel->lateral_relids;
/* Consider sequential scan */
! add_path(rel, create_seqscan_path(root, rel, required_outer, 0), false);
/* If appropriate, consider parallel sequential scan */
if (rel->consider_parallel && required_outer == NULL)
*************** static void
*** 677,682 ****
--- 684,690 ----
create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel)
{
int parallel_workers;
+ Path *path;
/*
* If the user has set the parallel_workers reloption, use that; otherwise
*************** create_plain_partial_paths(PlannerInfo *
*** 727,733 ****
return;
/* Add an unordered partial path based on a parallel sequential scan. */
! add_partial_path(rel, create_seqscan_path(root, rel, NULL, parallel_workers));
}
/*
--- 735,888 ----
return;
/* Add an unordered partial path based on a parallel sequential scan. */
! path = create_seqscan_path(root, rel, NULL, parallel_workers);
! add_partial_path(rel, path, false);
!
! /*
! * Do partial aggregation at base relation level if the relation is
! * eligible for it.
! */
! if (rel->reltarget_grouped)
! create_plain_grouped_path(root, rel, path);
! }
!
! /*
! * Apply (partial) aggregation to partial subpath.
! *
! * As we modify the subpath here, copy is taken before we adjust it.
! */
! static void
! create_plain_grouped_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath)
! {
! Query *parse = root->parse;
! ListCell *lc;
! Expr *texpr;
! AggClauseCosts agg_costs;
! AggPath *agg_path;
! List *group_clause = NIL;
! List *group_exprs = NIL;
! List *agg_exprs = NIL;
! bool can_hash;
! Size hashaggtablesize;
! int i;
! double dNumGroups;
! Path *subpath_tmp;
! PathTarget *target = rel->reltarget_grouped;
!
! /* Copy the subpath before doing changes. */
! subpath_tmp = makeNode(Path);
! memcpy(subpath_tmp, subpath, sizeof(Path));
! subpath = subpath_tmp;
!
! /* Apply the specific target. */
! subpath->pathtarget = target;
!
! /*
! * Find one grouping clause per grouping column.
! *
! * The "grouped target" should contain grouping expressions, and these
! * should have non-zero sortgroupref.
! */
! Assert(target->sortgrouprefs != NULL);
!
! i = 0;
! foreach(lc, target->exprs)
! {
! Index sortgroupref;
! SortGroupClause *cl;
!
! texpr = (Expr *) lfirst(lc);
! sortgroupref = target->sortgrouprefs[i++];
!
! if (sortgroupref == 0)
! {
! /*
! * Looks like "grouped var" representing Aggref - these should
! * appear at the end of the list.
! */
! break;
! }
!
! /*
! * Find the clause by sortgroupref.
! *
! * All that create_agg_plan eventually needs of the clause is
! * tleSortGroupRef, so we don't have to care that the clause
! * expression might differ from texpr, in case texpr was derived from
! * EC.
! */
! cl = get_sortgroupref_clause(sortgroupref, root->parse->groupClause);
! group_clause = lappend(group_clause, cl);
! group_exprs = lappend(group_exprs, texpr);
! }
!
! /* Now collect the aggregates. */
! while (lc != NULL)
! {
! texpr = (Expr *) lfirst(lc);
!
! /*
! * texpr still contains the replacement var, so restore the actual
! * Aggref expression.
! */
! Assert(IsA(texpr, Var));
! agg_exprs = lappend(agg_exprs, find_grouped_var_expr(root,
! (Var *) texpr));
! lc = lnext(lc);
! }
!
! Assert(agg_exprs != NIL);
! MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
! get_agg_clause_costs(root, (Node *) agg_exprs, AGGSPLIT_INITIAL_SERIAL,
! &agg_costs);
!
! can_hash = (parse->groupClause != NIL &&
! parse->groupingSets == NIL &&
! agg_costs.numOrderedAggs == 0 &&
! grouping_is_hashable(parse->groupClause));
!
! /* TODO Consider other kinds of aggregation. */
! if (!can_hash)
! return;
!
! Assert(group_exprs != NIL);
! dNumGroups = estimate_num_groups(root, group_exprs, subpath->rows, NULL);
!
! hashaggtablesize = estimate_hashagg_tablesize(subpath, &agg_costs,
! dNumGroups);
!
! if (hashaggtablesize < work_mem * 1024L)
! {
! /*
! * Create the partial aggregation path.
! *
! * Note that target contains "grouped vars" (see GroupedVarInfo)
! * instead of Aggref expressions so far. Thus set_upper_references can
! * easily link TLEs of the upper node (which is not necessarily the
! * final Agg node) to the output of the patial Agg plan. Once
! * descended to this partial Agg node, set_upper_references will
! * install the Aggref expressions.
! */
! Assert(group_clause != NIL);
! agg_path = create_agg_path(root,
! rel,
! subpath,
! target,
! AGG_HASHED,
! AGGSPLIT_INITIAL_SERIAL,
! group_clause,
! NIL,
! &agg_costs,
! dNumGroups);
!
! /*
! * The agg path should require no fewer parameters than the plain one.
! */
! agg_path->path.param_info = subpath->param_info;
!
! /* Finally add the grouped path to the list of grouped base paths. */
! add_partial_path(rel, (Path *) agg_path, true);
! }
}
/*
*************** set_tablesample_rel_pathlist(PlannerInfo
*** 813,819 ****
path = (Path *) create_material_path(rel, path);
}
! add_path(rel, path);
/* For the moment, at least, there are no other paths to consider */
}
--- 968,974 ----
path = (Path *) create_material_path(rel, path);
}
! add_path(rel, path, false);
/* For the moment, at least, there are no other paths to consider */
}
*************** set_append_rel_pathlist(PlannerInfo *roo
*** 1267,1273 ****
* if we have zero or one live subpath due to constraint exclusion.)
*/
if (subpaths_valid)
! add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0));
/*
* Consider an append of partial unordered, unparameterized partial paths.
--- 1422,1429 ----
* if we have zero or one live subpath due to constraint exclusion.)
*/
if (subpaths_valid)
! add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0),
! false);
/*
* Consider an append of partial unordered, unparameterized partial paths.
*************** set_append_rel_pathlist(PlannerInfo *roo
*** 1295,1301 ****
/* Generate a partial append path. */
appendpath = create_append_path(rel, partial_subpaths, NULL,
parallel_workers);
! add_partial_path(rel, (Path *) appendpath);
}
/*
--- 1451,1457 ----
/* Generate a partial append path. */
appendpath = create_append_path(rel, partial_subpaths, NULL,
parallel_workers);
! add_partial_path(rel, (Path *) appendpath, false);
}
/*
*************** set_append_rel_pathlist(PlannerInfo *roo
*** 1346,1352 ****
if (subpaths_valid)
add_path(rel, (Path *)
! create_append_path(rel, subpaths, required_outer, 0));
}
}
--- 1502,1509 ----
if (subpaths_valid)
add_path(rel, (Path *)
! create_append_path(rel, subpaths, required_outer, 0),
! false);
}
}
*************** generate_mergeappend_paths(PlannerInfo *
*** 1438,1450 ****
rel,
startup_subpaths,
pathkeys,
! NULL));
if (startup_neq_total)
add_path(rel, (Path *) create_merge_append_path(root,
rel,
total_subpaths,
pathkeys,
! NULL));
}
}
--- 1595,1607 ----
rel,
startup_subpaths,
pathkeys,
! NULL), false);
if (startup_neq_total)
add_path(rel, (Path *) create_merge_append_path(root,
rel,
total_subpaths,
pathkeys,
! NULL), false);
}
}
*************** set_dummy_rel_pathlist(RelOptInfo *rel)
*** 1576,1582 ****
rel->pathlist = NIL;
rel->partial_pathlist = NIL;
! add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0));
/*
* We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
--- 1733,1739 ----
rel->pathlist = NIL;
rel->partial_pathlist = NIL;
! add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0), false);
/*
* We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
*************** set_subquery_pathlist(PlannerInfo *root,
*** 1789,1795 ****
/* Generate outer path using this subpath */
add_path(rel, (Path *)
create_subqueryscan_path(root, rel, subpath,
! pathkeys, required_outer));
}
}
--- 1946,1952 ----
/* Generate outer path using this subpath */
add_path(rel, (Path *)
create_subqueryscan_path(root, rel, subpath,
! pathkeys, required_outer), false);
}
}
*************** set_function_pathlist(PlannerInfo *root,
*** 1858,1864 ****
/* Generate appropriate path */
add_path(rel, create_functionscan_path(root, rel,
! pathkeys, required_outer));
}
/*
--- 2015,2021 ----
/* Generate appropriate path */
add_path(rel, create_functionscan_path(root, rel,
! pathkeys, required_outer), false);
}
/*
*************** set_values_pathlist(PlannerInfo *root, R
*** 1878,1884 ****
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_valuesscan_path(root, rel, required_outer));
}
/*
--- 2035,2041 ----
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_valuesscan_path(root, rel, required_outer), false);
}
/*
*************** set_cte_pathlist(PlannerInfo *root, RelO
*** 1944,1950 ****
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_ctescan_path(root, rel, required_outer));
}
/*
--- 2101,2107 ----
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_ctescan_path(root, rel, required_outer), false);
}
/*
*************** set_worktable_pathlist(PlannerInfo *root
*** 1994,2000 ****
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_worktablescan_path(root, rel, required_outer));
}
/*
--- 2151,2158 ----
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_worktablescan_path(root, rel, required_outer),
! false);
}
/*
*************** set_worktable_pathlist(PlannerInfo *root
*** 2007,2019 ****
* path that some GatherPath has a reference to.)
*/
void
! generate_gather_paths(PlannerInfo *root, RelOptInfo *rel)
{
Path *cheapest_partial_path;
Path *simple_gather_path;
/* If there are no partial paths, there's nothing to do here. */
! if (rel->partial_pathlist == NIL)
return;
/*
--- 2165,2182 ----
* path that some GatherPath has a reference to.)
*/
void
! generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool grouped)
{
Path *cheapest_partial_path;
Path *simple_gather_path;
+ List *pathlist;
+ PathTarget *partial_target;
+
+ pathlist = !grouped ? rel->partial_pathlist :
+ rel->partial_grouped_pathlist;
/* If there are no partial paths, there's nothing to do here. */
! if (pathlist == NIL)
return;
/*
*************** generate_gather_paths(PlannerInfo *root,
*** 2027,2037 ****
* could usefully generate such a path from each partial path that has
* non-NIL pathkeys.
*/
! cheapest_partial_path = linitial(rel->partial_pathlist);
simple_gather_path = (Path *)
! create_gather_path(root, rel, cheapest_partial_path, rel->reltarget,
NULL, NULL);
! add_path(rel, simple_gather_path);
}
/*
--- 2190,2203 ----
* could usefully generate such a path from each partial path that has
* non-NIL pathkeys.
*/
! cheapest_partial_path = linitial(pathlist);
!
! partial_target = !grouped ? rel->reltarget : rel->reltarget_grouped;
!
simple_gather_path = (Path *)
! create_gather_path(root, rel, cheapest_partial_path, partial_target,
NULL, NULL);
! add_path(rel, simple_gather_path, grouped);
}
/*
*************** standard_join_search(PlannerInfo *root,
*** 2196,2202 ****
rel = (RelOptInfo *) lfirst(lc);
/* Create GatherPaths for any useful partial paths for rel */
! generate_gather_paths(root, rel);
/* Find and save the cheapest paths for this rel */
set_cheapest(rel);
--- 2362,2368 ----
rel = (RelOptInfo *) lfirst(lc);
/* Create GatherPaths for any useful partial paths for rel */
! generate_gather_paths(root, rel, false);
/* Find and save the cheapest paths for this rel */
set_cheapest(rel);
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
new file mode 100644
index 7b43c4a..7c44938
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
*************** create_index_paths(PlannerInfo *root, Re
*** 338,344 ****
bitmapqual = choose_bitmap_and(root, rel, bitindexpaths);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
rel->lateral_relids, 1.0);
! add_path(rel, (Path *) bpath);
}
/*
--- 338,344 ----
bitmapqual = choose_bitmap_and(root, rel, bitindexpaths);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
rel->lateral_relids, 1.0);
! add_path(rel, (Path *) bpath, false);
}
/*
*************** create_index_paths(PlannerInfo *root, Re
*** 411,417 ****
loop_count = get_loop_count(root, rel->relid, required_outer);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
required_outer, loop_count);
! add_path(rel, (Path *) bpath);
}
}
}
--- 411,417 ----
loop_count = get_loop_count(root, rel->relid, required_outer);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
required_outer, loop_count);
! add_path(rel, (Path *) bpath, false);
}
}
}
*************** get_index_paths(PlannerInfo *root, RelOp
*** 785,791 ****
IndexPath *ipath = (IndexPath *) lfirst(lc);
if (index->amhasgettuple)
! add_path(rel, (Path *) ipath);
if (index->amhasgetbitmap &&
(ipath->path.pathkeys == NIL ||
--- 785,791 ----
IndexPath *ipath = (IndexPath *) lfirst(lc);
if (index->amhasgettuple)
! add_path(rel, (Path *) ipath, false);
if (index->amhasgetbitmap &&
(ipath->path.pathkeys == NIL ||
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
new file mode 100644
index 7c30ec6..45318c8
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
*************** static void consider_parallel_nestloop(P
*** 39,48 ****
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra);
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinType jointype, JoinPathExtraData *extra);
static List *select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
--- 39,49 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped);
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinType jointype, JoinPathExtraData *extra, bool grouped);
static List *select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 217,224 ****
* joins, because there may be no other alternative.
*/
if (enable_hashjoin || jointype == JOIN_FULL)
hash_inner_and_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra);
/*
* 5. If inner and outer relations are foreign tables (or joins) belonging
--- 218,229 ----
* joins, because there may be no other alternative.
*/
if (enable_hashjoin || jointype == JOIN_FULL)
+ {
hash_inner_and_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra, false);
! hash_inner_and_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra, true);
! }
/*
* 5. If inner and outer relations are foreign tables (or joins) belonging
*************** try_nestloop_path(PlannerInfo *root,
*** 323,329 ****
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer))
{
add_path(joinrel, (Path *)
create_nestloop_path(root,
--- 328,334 ----
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer, false))
{
add_path(joinrel, (Path *)
create_nestloop_path(root,
*************** try_nestloop_path(PlannerInfo *root,
*** 336,342 ****
inner_path,
extra->restrictlist,
pathkeys,
! required_outer));
}
else
{
--- 341,348 ----
inner_path,
extra->restrictlist,
pathkeys,
! required_outer),
! false);
}
else
{
*************** try_partial_nestloop_path(PlannerInfo *r
*** 357,363 ****
Path *inner_path,
List *pathkeys,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinCostWorkspace workspace;
--- 363,370 ----
Path *inner_path,
List *pathkeys,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped)
{
JoinCostWorkspace workspace;
*************** try_partial_nestloop_path(PlannerInfo *r
*** 383,389 ****
initial_cost_nestloop(root, &workspace, jointype,
outer_path, inner_path,
extra->sjinfo, &extra->semifactors);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys))
return;
/* Might be good enough to be worth trying, so let's try it. */
--- 390,397 ----
initial_cost_nestloop(root, &workspace, jointype,
outer_path, inner_path,
extra->sjinfo, &extra->semifactors);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys,
! false))
return;
/* Might be good enough to be worth trying, so let's try it. */
*************** try_partial_nestloop_path(PlannerInfo *r
*** 398,404 ****
inner_path,
extra->restrictlist,
pathkeys,
! NULL));
}
/*
--- 406,413 ----
inner_path,
extra->restrictlist,
pathkeys,
! NULL),
! grouped);
}
/*
*************** try_mergejoin_path(PlannerInfo *root,
*** 456,462 ****
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer))
{
add_path(joinrel, (Path *)
create_mergejoin_path(root,
--- 465,471 ----
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer, false))
{
add_path(joinrel, (Path *)
create_mergejoin_path(root,
*************** try_mergejoin_path(PlannerInfo *root,
*** 471,477 ****
required_outer,
mergeclauses,
outersortkeys,
! innersortkeys));
}
else
{
--- 480,486 ----
required_outer,
mergeclauses,
outersortkeys,
! innersortkeys), false);
}
else
{
*************** try_hashjoin_path(PlannerInfo *root,
*** 492,498 ****
Path *inner_path,
List *hashclauses,
JoinType jointype,
! JoinPathExtraData *extra)
{
Relids required_outer;
JoinCostWorkspace workspace;
--- 501,508 ----
Path *inner_path,
List *hashclauses,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped)
{
Relids required_outer;
JoinCostWorkspace workspace;
*************** try_hashjoin_path(PlannerInfo *root,
*** 521,527 ****
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! NIL, required_outer))
{
add_path(joinrel, (Path *)
create_hashjoin_path(root,
--- 531,537 ----
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! NIL, required_outer, grouped))
{
add_path(joinrel, (Path *)
create_hashjoin_path(root,
*************** try_hashjoin_path(PlannerInfo *root,
*** 534,540 ****
inner_path,
extra->restrictlist,
required_outer,
! hashclauses));
}
else
{
--- 544,551 ----
inner_path,
extra->restrictlist,
required_outer,
! hashclauses,
! grouped), grouped);
}
else
{
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 555,561 ****
Path *inner_path,
List *hashclauses,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinCostWorkspace workspace;
--- 566,573 ----
Path *inner_path,
List *hashclauses,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped)
{
JoinCostWorkspace workspace;
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 581,587 ****
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path,
extra->sjinfo, &extra->semifactors);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, NIL))
return;
/* Might be good enough to be worth trying, so let's try it. */
--- 593,599 ----
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path,
extra->sjinfo, &extra->semifactors);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, NIL, false))
return;
/* Might be good enough to be worth trying, so let's try it. */
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 596,602 ****
inner_path,
extra->restrictlist,
NULL,
! hashclauses));
}
/*
--- 608,616 ----
inner_path,
extra->restrictlist,
NULL,
! hashclauses,
! grouped),
! grouped);
}
/*
*************** match_unsorted_outer(PlannerInfo *root,
*** 1233,1240 ****
if (joinrel->consider_parallel && nestjoinOK &&
save_jointype != JOIN_UNIQUE_OUTER &&
bms_is_empty(joinrel->lateral_relids))
consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! save_jointype, extra);
}
/*
--- 1247,1258 ----
if (joinrel->consider_parallel && nestjoinOK &&
save_jointype != JOIN_UNIQUE_OUTER &&
bms_is_empty(joinrel->lateral_relids))
+ {
consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! save_jointype, extra, false);
! consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! save_jointype, extra, true);
! }
}
/*
*************** consider_parallel_nestloop(PlannerInfo *
*** 1254,1268 ****
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinType save_jointype = jointype;
ListCell *lc1;
if (jointype == JOIN_UNIQUE_INNER)
jointype = JOIN_INNER;
! foreach(lc1, outerrel->partial_pathlist)
{
Path *outerpath = (Path *) lfirst(lc1);
List *pathkeys;
--- 1272,1291 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped)
{
JoinType save_jointype = jointype;
+ List *outerrel_pathlist;
ListCell *lc1;
if (jointype == JOIN_UNIQUE_INNER)
jointype = JOIN_INNER;
! outerrel_pathlist = !grouped ? outerrel->partial_pathlist :
! outerrel->partial_grouped_pathlist;
!
! foreach(lc1, outerrel_pathlist)
{
Path *outerpath = (Path *) lfirst(lc1);
List *pathkeys;
*************** consider_parallel_nestloop(PlannerInfo *
*** 1304,1310 ****
}
try_partial_nestloop_path(root, joinrel, outerpath, innerpath,
! pathkeys, jointype, extra);
}
}
}
--- 1327,1333 ----
}
try_partial_nestloop_path(root, joinrel, outerpath, innerpath,
! pathkeys, jointype, extra, grouped);
}
}
}
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1326,1332 ****
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinType save_jointype = jointype;
bool isouterjoin = IS_OUTER_JOIN(jointype);
--- 1349,1356 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped)
{
JoinType save_jointype = jointype;
bool isouterjoin = IS_OUTER_JOIN(jointype);
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1382,1394 ****
* can't use a hashjoin. (There's no use looking for alternative
* input paths, since these should already be the least-parameterized
* available paths.)
*/
if (PATH_PARAM_BY_REL(cheapest_total_outer, innerrel) ||
PATH_PARAM_BY_REL(cheapest_total_inner, outerrel))
return;
/* Unique-ify if need be; we ignore parameterized possibilities */
! if (jointype == JOIN_UNIQUE_OUTER)
{
cheapest_total_outer = (Path *)
create_unique_path(root, outerrel,
--- 1406,1445 ----
* can't use a hashjoin. (There's no use looking for alternative
* input paths, since these should already be the least-parameterized
* available paths.)
+ *
+ * (The same check should work for grouped paths, as these don't
+ * differ in parameterization.)
*/
if (PATH_PARAM_BY_REL(cheapest_total_outer, innerrel) ||
PATH_PARAM_BY_REL(cheapest_total_inner, outerrel))
return;
+ if (grouped)
+ {
+ /*
+ * The result of grouped path should be quite different from that
+ * of the non-grouped one as such, so don't bother combining
+ * multiple kinds of grouped paths.
+ *
+ * TODO As for JOIN_UNIQUE_OUTER and JOIN_UNIQUE_INNER, consider
+ * if the unique-ificiation is worth the effort.
+ */
+ if (jointype != JOIN_UNIQUE_OUTER &&
+ jointype != JOIN_UNIQUE_INNER &&
+ outerrel->grouped_pathlist && innerrel->grouped_pathlist)
+ {
+ try_hashjoin_path(root,
+ joinrel,
+ (Path *) linitial(outerrel->grouped_pathlist),
+ (Path *) linitial(innerrel->grouped_pathlist),
+ hashclauses,
+ jointype,
+ extra,
+ true);
+ }
+ }
/* Unique-ify if need be; we ignore parameterized possibilities */
! else if (jointype == JOIN_UNIQUE_OUTER)
{
cheapest_total_outer = (Path *)
create_unique_path(root, outerrel,
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1401,1407 ****
cheapest_total_inner,
hashclauses,
jointype,
! extra);
/* no possibility of cheap startup here */
}
else if (jointype == JOIN_UNIQUE_INNER)
--- 1452,1459 ----
cheapest_total_inner,
hashclauses,
jointype,
! extra,
! false);
/* no possibility of cheap startup here */
}
else if (jointype == JOIN_UNIQUE_INNER)
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1417,1423 ****
cheapest_total_inner,
hashclauses,
jointype,
! extra);
if (cheapest_startup_outer != NULL &&
cheapest_startup_outer != cheapest_total_outer)
try_hashjoin_path(root,
--- 1469,1476 ----
cheapest_total_inner,
hashclauses,
jointype,
! extra,
! false);
if (cheapest_startup_outer != NULL &&
cheapest_startup_outer != cheapest_total_outer)
try_hashjoin_path(root,
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1426,1432 ****
cheapest_total_inner,
hashclauses,
jointype,
! extra);
}
else
{
--- 1479,1486 ----
cheapest_total_inner,
hashclauses,
jointype,
! extra,
! false);
}
else
{
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1447,1453 ****
cheapest_total_inner,
hashclauses,
jointype,
! extra);
foreach(lc1, outerrel->cheapest_parameterized_paths)
{
--- 1501,1508 ----
cheapest_total_inner,
hashclauses,
jointype,
! extra,
! false);
foreach(lc1, outerrel->cheapest_parameterized_paths)
{
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1481,1487 ****
innerpath,
hashclauses,
jointype,
! extra);
}
}
}
--- 1536,1543 ----
innerpath,
hashclauses,
jointype,
! extra,
! false);
}
}
}
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1537,1543 ****
try_partial_hashjoin_path(root, joinrel,
cheapest_partial_outer,
cheapest_safe_inner,
! hashclauses, jointype, extra);
}
}
}
--- 1593,1613 ----
try_partial_hashjoin_path(root, joinrel,
cheapest_partial_outer,
cheapest_safe_inner,
! hashclauses, jointype, extra,
! false);
!
! /*
! * If partial grouped path exists for either side, join the
! * cheapest ones into a new grouped path.
! */
! if (outerrel->partial_grouped_pathlist &&
! innerrel->partial_grouped_pathlist)
! try_partial_hashjoin_path(root,
! joinrel,
! (Path *) linitial(outerrel->partial_grouped_pathlist),
! (Path *) linitial(innerrel->partial_grouped_pathlist),
! hashclauses, jointype, extra,
! true);
}
}
}
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
new file mode 100644
index 6f3c20b..51e01d7
*** a/src/backend/optimizer/path/joinrels.c
--- b/src/backend/optimizer/path/joinrels.c
*************** mark_dummy_rel(RelOptInfo *rel)
*** 1197,1203 ****
rel->partial_pathlist = NIL;
/* Set up the dummy path */
! add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0));
/* Set or update cheapest_total_path and related fields */
set_cheapest(rel);
--- 1197,1203 ----
rel->partial_pathlist = NIL;
/* Set up the dummy path */
! add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0), false);
/* Set or update cheapest_total_path and related fields */
set_cheapest(rel);
diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c
new file mode 100644
index 240ade6..6c3a77a
*** a/src/backend/optimizer/path/tidpath.c
--- b/src/backend/optimizer/path/tidpath.c
*************** create_tidscan_paths(PlannerInfo *root,
*** 263,267 ****
if (tidquals)
add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
! required_outer));
}
--- 263,267 ----
if (tidquals)
add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
! required_outer), false);
}
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
new file mode 100644
index 6ceb801..6712f8f
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
***************
*** 14,20 ****
--- 14,25 ----
*/
#include "postgres.h"
+ #include "access/htup_details.h"
+ #include "access/sysattr.h"
+ #include "catalog/pg_aggregate.h"
+ #include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
+ #include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
***************
*** 26,35 ****
--- 31,42 ----
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+ #include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
+ #include "utils/syscache.h"
/* These parameters are set by GUC */
*************** static List *deconstruct_recurse(Planner
*** 51,56 ****
--- 58,67 ----
bool below_outer_join,
Relids *qualscope, Relids *inner_join_rels,
List **postponed_qual_list);
+ static void build_base_rel_tlist_grouped(PlannerInfo *root, RelOptInfo *rel,
+ List *aggregates, Aggref *countagg,
+ List **countagg_vars);
+ static void finalize_grouped_vars(PlannerInfo *root, List *countagg_vars);
static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root,
Relids left_rels, Relids right_rels,
Relids inner_join_rels,
*************** add_vars_to_targetlist(PlannerInfo *root
*** 236,241 ****
--- 247,861 ----
}
}
+ /*
+ * Initialize rel->reltarget_grouped where possible.
+ *
+ * root->group_pathkeys must be setup before this function is called.
+ */
+ extern void
+ build_base_rel_tlists_grouped(PlannerInfo *root)
+ {
+ List *tlist_vars;
+ List *aggregates = NIL;
+ ListCell *lc;
+ bool aggs_acceptable = true;
+ Aggref *countagg = NULL;
+ List *countagg_vars = NIL;
+ int nbaserels = 0;
+ int i;
+
+ /* No grouping in the query? */
+ if (!root->parse->groupClause || root->group_pathkeys == NIL)
+ return;
+
+ /* TODO This is just for PoC. Relax the limitation later. */
+ if (root->parse->havingQual)
+ return;
+
+ /*
+ * If no join is expected, aggregation at base relation level makes no
+ * sense. XXX Is there simpler way to find out? (We're not interested in
+ * RELOPT_OTHER_MEMBER_REL, so simple_rel_array_size does not help.)
+ */
+ for (i = 1; i < root->simple_rel_array_size; i++)
+ {
+ RelOptInfo *rel;
+
+ rel = find_base_rel(root, i);
+ if (rel->reloptkind == RELOPT_BASEREL)
+ {
+ nbaserels++;
+ /*
+ * We only want to know whether the number of relations is greater
+ * than one.
+ */
+ if (nbaserels > 1)
+ break;
+ }
+ }
+ if (nbaserels <= 1)
+ return;
+
+ tlist_vars = pull_var_clause((Node *) root->processed_tlist,
+ PVC_INCLUDE_AGGREGATES);
+ if (tlist_vars == NIL)
+ return;
+
+ /* tlist_vars may also contain Vars, but we only need Aggrefs. */
+ foreach(lc, tlist_vars)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+
+ if (!IsA(expr, Var))
+ {
+ Aggref *aggref = (Aggref *) expr;
+
+ Assert(IsA(aggref, Aggref));
+
+ /* TODO Think if (some of) these can be handled. */
+ if (aggref->aggstar || aggref->aggvariadic || aggref->aggdirectargs ||
+ aggref->aggorder || aggref->aggdistinct || aggref->aggfilter)
+ {
+ aggs_acceptable = false;
+ break;
+ }
+
+ aggregates = lappend(aggregates, expr);
+ }
+ }
+
+ if (aggregates != NIL && aggs_acceptable)
+ {
+ int i;
+
+ if (countagg == NULL)
+ {
+ /* count(*) aggregate for base relation grouping. */
+ countagg = makeNode(Aggref);
+ countagg->aggfnoid = COUNTFNOID;
+ countagg->aggkind = AGGKIND_NORMAL;
+ countagg->aggtype = INT8OID;
+ countagg->aggtranstype = INT8OID;
+ countagg->aggstar = true;
+ }
+ /* Process the individual base relations. */
+ root->all_baserels_grouped = true;
+ for (i = 1; i < root->simple_rel_array_size; i++)
+ {
+ RelOptInfo *rel;
+
+ rel = root->simple_rel_array[i];
+ if (rel != NULL)
+ build_base_rel_tlist_grouped(root, rel, aggregates, countagg,
+ &countagg_vars);
+
+ /*
+ * Don't bother processing the rest if the current rel prevents us
+ * from forming the final "grouped join".
+ */
+ if (!root->all_baserels_grouped)
+ break;
+ }
+
+ if (root->all_baserels_grouped)
+ finalize_grouped_vars(root, countagg_vars);
+ }
+ else
+ root->all_baserels_grouped = false;
+
+ if (aggregates != NIL)
+ list_free(aggregates);
+ list_free(tlist_vars);
+ }
+
+ /*
+ * Construct target of grouped relation - that can only contain grouping
+ * expressions and aggregates. Such a target can't be created if anything else
+ * is required on the output.
+ *
+ * countagg represents count(*) aggregate that each grouped rel must have to
+ * collect information of per-group row count. This information is used to
+ * adjust the transient state in the output of the final join.
+ *
+ * Variable representing the result of countagg is added to *countagg_vars
+ * list (besides being added to root->grouped_var_list as well, so that it the
+ * actual count(*) expression be restored by set_upper_references.)
+ */
+ static void
+ build_base_rel_tlist_grouped(PlannerInfo *root, RelOptInfo *rel,
+ List *aggregates, Aggref *countagg,
+ List **countagg_vars)
+ {
+ RangeTblEntry *rte;
+ List *rel_aggregates;
+ ListCell *lc;
+ Relids agg_arg_rels_all, agg_arg_attrs_all;
+ PathTarget *reltarget_grouped;
+ bool first;
+
+ rte = root->simple_rte_array[rel->relid];
+
+ /*
+ * XXX Append relation, as well as rtekind != RTE_RELATION seem to deserve
+ * separate patches.
+ */
+ if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL ||
+ rte->rtekind != RTE_RELATION)
+ {
+ root->all_baserels_grouped = false;
+ return;
+ }
+
+ /* Caller should only pass elements of root->simple_rel_array. */
+ Assert(rel->reloptkind == RELOPT_BASEREL);
+ Assert(bms_membership(rel->relids) == BMS_SINGLETON);
+
+ /*
+ * If any outer join can set the attribute value to NULL, the aggregate
+ * would receive different input at the base rel level.
+ *
+ * TODO This as well as some other limitations below should only apply to
+ * the PoC version of the patch. In the future we should handle
+ * non-grouped relations by joining them to the grouped ones and applying
+ * additional partial aggregation to the final join.
+ */
+ if (bms_overlap(rel->relids, root->nullable_baserels))
+ {
+ root->all_baserels_grouped = false;
+ return;
+ }
+
+ /* Collect aggregates applicable to the current relation. */
+ rel_aggregates = NIL;
+ agg_arg_rels_all = NULL;
+ agg_arg_attrs_all = NULL;
+ foreach(lc, aggregates)
+ {
+ Aggref *aggref = (Aggref *) lfirst(lc);
+ Relids agg_arg_rels, agg_arg_attrs = NULL;
+
+ /* TODO Does it matter if any argument contains PHV ? */
+ agg_arg_rels = pull_varnos((Node *) aggref->args);
+
+ /* Skip aggregates which don't reference rel at all. */
+ if (!bms_overlap(rel->relids, agg_arg_rels))
+ continue;
+
+ /*
+ * If the aggregate references multiple rels, it must be processed
+ * higher in the join tree.
+ */
+ if (bms_membership(agg_arg_rels) != BMS_SINGLETON)
+ {
+ /*
+ * TODO Consider relaxing this limitation by adding one extra
+ * partial aggregation below the final one.
+ */
+ root->all_baserels_grouped = false;
+
+ return;
+ }
+
+ /* Accept this aggregate. */
+ rel_aggregates = lappend(rel_aggregates, aggref);
+ agg_arg_rels_all = bms_union(agg_arg_rels_all, agg_arg_rels);
+
+ /* Collect attnos while being here. */
+ pull_varattnos((Node *) aggref->args, rel->relid, &agg_arg_attrs);
+ agg_arg_attrs_all = bms_union(agg_arg_attrs_all, agg_arg_attrs);
+ }
+
+ Assert(rel->reltarget_grouped == NULL);
+
+ /*
+ * Create a separate target which represents the actual output of grouped
+ * relation. This target will have aggregates replaced with special vars
+ * which will ensure propagation of the result of the partial aggregation
+ * of the base relation to the final Agg node.
+ *
+ * This target won't contain expressions that the grouped relation cannot
+ * emit. Another difference from rel->reltarget is that sortgroupref is
+ * set for each grouping expression. This is because the same target will
+ * be used for the partial aggregation.
+ *
+ * (Do not set rel->reltarget_grouped yet, as we might return
+ * prematurely.)
+ */
+ reltarget_grouped = create_empty_pathtarget();
+
+ /*
+ * Check if all the output columns can be used as grouping expressions.
+ *
+ * TODO
+ *
+ * 1. How about PHVs?
+ *
+ * 2. Consider additional grouping expressions, just to be able to emit
+ * the columns that the query group clause doesn't mention. (These
+ * additional expressions wouldn't be used during the final aggregation.)
+ * Does this also mean that we shouldn't check existence of
+ * parse->groupClause earlier in this function?
+ */
+ foreach(lc, rel->reltarget->exprs)
+ {
+ ListCell *lc2;
+ Expr *texpr = (Expr *) lfirst(lc);
+ bool is_grouping = false;
+ bool ec_found = false;
+ bool agg_arg_only = false;
+
+ /*
+ * First, check if the query group clause contains exactly this
+ * expression.
+ */
+ foreach(lc2, root->processed_tlist)
+ {
+ TargetEntry *te = (TargetEntry *) lfirst(lc2);
+
+ Assert(IsA(te, TargetEntry));
+
+ if (equal(texpr, te->expr) && te->ressortgroupref > 0)
+ {
+ add_column_to_pathtarget(reltarget_grouped, texpr,
+ te->ressortgroupref);
+ is_grouping = true;
+ break;
+ }
+ }
+
+ /* Go for the next expression if matched the current one. */
+ if (is_grouping)
+ continue;
+
+ /*
+ * If exactly this expression is not there, check if a grouping clause
+ * exists that belongs to the same equivalence class as the
+ * expression.
+ */
+ foreach(lc2, root->group_pathkeys)
+ {
+ PathKey *pk = (PathKey *) lfirst(lc2);
+ EquivalenceClass *ec = pk->pk_eclass;
+ ListCell *lm;
+ EquivalenceMember *em;
+ Expr *em_expr = NULL;
+ Query *query = root->parse;
+ Index sortgroupref;
+
+ /*
+ * Single-member EC cannot provide us with additional
+ * expression.
+ */
+ if (list_length(ec->ec_members) < 2)
+ continue;
+
+ /* We need equality anywhere in the join tree. */
+ if (ec->ec_below_outer_join)
+ continue;
+
+ /*
+ * TODO Reconsider this restriction. As the grouping expression is
+ * only evaluated at the relation level (and only the result will
+ * be propagated to the final targetlist), volatile function might
+ * be o.k. Need to think what volatile EC exactly means.
+ */
+ if (ec->ec_has_volatile)
+ continue;
+
+ foreach(lm, ec->ec_members)
+ {
+ em = (EquivalenceMember *) lfirst(lm);
+
+ /* The EC has !ec_below_outer_join. */
+ Assert(!em->em_nullable_relids);
+ if (equal(em->em_expr, texpr))
+ {
+ em_expr = (Expr *) em->em_expr;
+ break;
+ }
+ }
+
+ if (em_expr == NULL)
+ /* Go for the next EC. */
+ continue;
+
+ /*
+ * Find the corresponding SortGroupClause, which provides us
+ * with sortgroupref. (It can belong to any EC member.)
+ */
+ sortgroupref = 0;
+ foreach(lm, ec->ec_members)
+ {
+ ListCell *lsg;
+
+ em = (EquivalenceMember *) lfirst(lm);
+ foreach(lsg, query->groupClause)
+ {
+ SortGroupClause *sgc;
+ Expr *texpr;
+
+ sgc = (SortGroupClause *) lfirst(lsg);
+ texpr = (Expr *) get_sortgroupclause_expr(sgc,
+ query->targetList);
+ if (equal(em->em_expr, texpr))
+ {
+ Assert(sgc->tleSortGroupRef > 0);
+ sortgroupref = sgc->tleSortGroupRef;
+ break;
+ }
+ }
+
+ if (sortgroupref > 0)
+ break;
+ }
+
+ /*
+ * At least one EM of this EC should have correspond to a
+ * SortGroupClause, otherwise the EC could hardly exist.
+ */
+ if (sortgroupref == 0)
+ elog(ERROR, "Grouping EC does not match any grouping clause.");
+
+ /* It's o.k. to use the target expression for grouping. */
+ add_column_to_pathtarget(reltarget_grouped, texpr, sortgroupref);
+ ec_found = true;
+ break;
+ }
+
+ /*
+ * It may still be o.k. if the expression is only contained in Aggref
+ * - then it's not expected in the grouped output.
+ *
+ * TODO Try to handle generic expression, not only Var. That might
+ * require us to create rel->reltarget of the grouping rel in
+ * parallel to that of the plain rel, and adding whole expressions
+ * instead of individual vars.
+ */
+ if (IsA(texpr, Var) &&
+ bms_is_member(((Var *) texpr)->varattno -
+ FirstLowInvalidHeapAttributeNumber, agg_arg_attrs_all))
+ agg_arg_only = true;
+
+ /*
+ * A single mismatched expression makes the whole relation useless
+ * for grouping at base level.
+ */
+ if (!ec_found && !agg_arg_only)
+ return;
+ }
+
+ /*
+ * Add the count(*) aggregate, as its processing is nearly identical to
+ * that of other aggregates. It should be located in front of the other
+ * vars so they can reference it.
+ *
+ * TODO Avoid this if no aggregate in the query has aggtransmultifn.
+ */
+ rel_aggregates = lcons(countagg, rel_aggregates);
+
+ /*
+ * Add each aggregate to reltarget_grouped in the form of special
+ * variable, for which GroupedVarInfo will also be added to
+ * root->grouped_var_list.
+ *
+ * It doesn't matter that the relation contains no such variables - they
+ * will be replaced with the partially-aggregated Aggref before execution
+ * starts - see restore_grouped_var_expr.
+ */
+ first = true;
+ foreach(lc, rel_aggregates)
+ {
+ Aggref *aggref = (Aggref *) lfirst(lc);
+ AttrNumber varattno;
+ Var *var;
+ GroupedVarInfo *gvi;
+
+ rel->max_attr++;
+ varattno = rel->max_attr;
+
+ var = makeVar(rel->relid, varattno, InvalidOid, 0, InvalidOid, 0);
+ add_new_column_to_pathtarget(reltarget_grouped, (Expr *) var);
+
+ /*
+ * Create writable copy so that it can be marked as partial, and
+ * eventually be associated with the final aggregate - see
+ * set_upper_references. (XXX Is copy necessary for the count(*)
+ * aggregate as well?)
+ */
+ aggref = copyObject(aggref);
+
+ if (first)
+ {
+ /* count(*) should be the first item of the list. */
+ Assert(aggref->aggfnoid == COUNTFNOID && aggref->aggstar);
+
+ /*
+ * The variable representing the auxiliary count(*) is not subject
+ * to the final aggregation (in fact the result is used as
+ * argument of aggtransmultifn, which prepares input value for the
+ * final aggregationq), so there's never reason to restore the
+ * count(*) expression. Moreover, restoration of the count(*)
+ * expression outside Aggref (e.g. in the targetlist of the final
+ * join) would cause ERROR during plan initialization.
+ *
+ * So instead of creating GroupedVarInfo we only add the var to a
+ * list of vars that GroupedVarInfo.expr_intermediate can
+ * reference.
+ */
+ *countagg_vars = lappend(*countagg_vars, var);
+
+ first = false;
+ }
+
+ /*
+ * Associate the expression with the corresponding variable, so that
+ * set_upper_references can do the changes explained above.
+ */
+ gvi = (GroupedVarInfo *) palloc0(sizeof(GroupedVarInfo));
+ gvi->var = var;
+
+ /* The variable represents result of the partial aggregation. */
+ mark_partial_aggref(aggref, AGGSPLIT_INITIAL_SERIAL);
+ /* The variable type info should reflect the transient type. */
+ var->vartype = exprType((Node *) aggref);
+ var->vartypmod = exprTypmod((Node *) aggref);
+ var->varcollid = exprCollation((Node *) aggref);
+
+ gvi->expr = (Expr *) aggref;
+ gvi->sortgroupref = 0;
+ root->grouped_var_list = lappend(root->grouped_var_list, gvi);
+
+ /*
+ * Add the variable to rel->attr_needed to ensure propagation to the
+ * top-level target list. (Non-grouped path are not aware of the
+ * variable, and they should already be generated by now.)
+ */
+ rel->attr_needed = (Relids *)
+ repalloc(rel->attr_needed, (rel->max_attr - rel->min_attr + 1) *
+ sizeof(Relids));
+ rel->attr_needed[varattno - rel->min_attr] = bms_make_singleton(0);
+
+ /*
+ * As set_rel_width should already have completed, it makes no sense
+ * to add the variable to attr_widths. Instead we only adjust
+ * rel->reltarget->width.
+ */
+ /* TODO Adjust rel->reltarget->width */
+ }
+
+ rel->reltarget_grouped = reltarget_grouped;
+ }
+
+ /*
+ * Initialize expr_intermediate of each GroupedVarInfo that requires it.
+ * This expression will adjust partial state of an aggregate prior to final
+ * aggregation, so it reflects existence of joins.
+ *
+ * If a single relation is aggregated, only the table determines how many
+ * times each value of aggregate argument appears in the relevant group. But
+ * if relation is joined to another one, the aggregate executed on the final
+ * join (i.e. w/o the relation-level aggregation) can actually receive the
+ * whole input set multiple times: some values of the grouping key present in
+ * the relation can match multiple values in the other table(s).
+ *
+ * sum() is an example of an aggregate where join matters, avg() is one where
+ * it does not.
+ *
+ * We simulate the effect of joins by applying aggtransmultifn (see
+ * pg_aggregate) to the result of relation-level aggregation.
+ */
+ static void
+ finalize_grouped_vars(PlannerInfo *root, List *countagg_vars)
+ {
+ ListCell *lc;
+ Oid op_int8mul = InvalidOid;
+
+ foreach(lc, root->grouped_var_list)
+ {
+ GroupedVarInfo *gvi = (GroupedVarInfo *) lfirst(lc);
+ Aggref *aggref = (Aggref *) gvi->expr;
+ ListCell *l;
+ Expr *factor = NULL;
+
+ Assert(gvi->expr_intermediate == NULL);
+ Assert(IsA(aggref, Aggref));
+
+ /* Is transient state multiplication needed for this aggregate? */
+ if (aggref->aggtransmultifn == InvalidOid)
+ continue;
+
+ /*
+ * If there was no relation-level aggregation, each table joined to
+ * the one gvi->varno points at would increase the frequency of a
+ * value within a group by the factor which equals to the frequency of
+ * the corresponding grouping key in the table joined.
+ *
+ * To prepare the correct input for the final aggregation, the
+ * relation-level per-group state must be "multiplied" by the number
+ * of grouping key values that join of the *other* tables (i.e. all
+ * but the one referenced by the current aggregate) generates.
+ *
+ * This is why count(*) was added to each base relation.
+ */
+ foreach(l, countagg_vars)
+ {
+ Var *var = (Var *) lfirst(l);
+
+ /* Only the other tables do matter. */
+ if (var->varno == gvi->var->varno)
+ continue;
+
+ if (factor == NULL)
+ /* The first value. */
+ factor = (Expr *) var;
+ else
+ {
+ OpExpr *op = makeNode(OpExpr);
+
+ /*
+ * Multiply the intermediate result by the result of the next
+ * count(*) aggregate.
+ */
+ op->opno = OPERATOR_INT8MUL;
+ if (op_int8mul == InvalidOid)
+ {
+ set_opfuncid(op);
+ op_int8mul = op->opfuncid;
+ }
+ else
+ op->opfuncid = op_int8mul;
+
+ op->opresulttype = INT8OID;
+ op->opretset = false;
+ op->opcollid = InvalidOid;
+ op->inputcollid = InvalidOid;
+ op->args = list_make2(factor, var);
+ op->location = -1;
+
+ factor = (Expr *) op;
+ }
+ }
+
+ /* Construct the function call if needed. */
+ if (factor != NULL)
+ {
+ FuncExpr *func = makeNode(FuncExpr);
+
+ func->funcid = aggref->aggtransmultifn;
+ func->funcresulttype = aggref->aggtranstype;
+ func->funcretset = false;
+ func->funcvariadic = false;
+ func->funccollid = InvalidOid;
+ func->inputcollid = InvalidOid;
+ Assert(gvi->var != NULL);
+ func->args = list_make2(gvi->var, factor);
+ func->location = -1;
+
+ gvi->expr_intermediate = (Expr *) func;
+ }
+ }
+ }
+
/*****************************************************************************
*
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
new file mode 100644
index c3fbf3c..5b3bc71
*** a/src/backend/optimizer/plan/planagg.c
--- b/src/backend/optimizer/plan/planagg.c
*************** preprocess_minmax_aggregates(PlannerInfo
*** 223,229 ****
create_minmaxagg_path(root, grouped_rel,
create_pathtarget(root, tlist),
aggs_list,
! (List *) parse->havingQual));
}
/*
--- 223,229 ----
create_minmaxagg_path(root, grouped_rel,
create_pathtarget(root, tlist),
aggs_list,
! (List *) parse->havingQual), false);
}
/*
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
new file mode 100644
index e880759..c7d98ca
*** a/src/backend/optimizer/plan/planmain.c
--- b/src/backend/optimizer/plan/planmain.c
*************** query_planner(PlannerInfo *root, List *t
*** 83,89 ****
add_path(final_rel, (Path *)
create_result_path(root, final_rel,
final_rel->reltarget,
! (List *) parse->jointree->quals));
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(final_rel);
--- 83,89 ----
add_path(final_rel, (Path *)
create_result_path(root, final_rel,
final_rel->reltarget,
! (List *) parse->jointree->quals), false);
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(final_rel);
*************** query_planner(PlannerInfo *root, List *t
*** 176,181 ****
--- 176,183 ----
*/
(*qp_callback) (root, qp_extra);
+ build_base_rel_tlists_grouped(root);
+
/*
* Examine any "placeholder" expressions generated during subquery pullup.
* Make sure that the Vars they need are marked as needed at the relevant
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 207290f..eeac4ac
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** static double get_number_of_groups(Plann
*** 109,117 ****
double path_rows,
List *rollup_lists,
List *rollup_groupclauses);
- static Size estimate_hashagg_tablesize(Path *path,
- const AggClauseCosts *agg_costs,
- double dNumGroups);
static RelOptInfo *create_grouping_paths(PlannerInfo *root,
RelOptInfo *input_rel,
PathTarget *target,
--- 109,114 ----
*************** inheritance_planner(PlannerInfo *root)
*** 1396,1402 ****
returningLists,
rowMarks,
NULL,
! SS_assign_special_param(root)));
}
/*--------------------
--- 1393,1399 ----
returningLists,
rowMarks,
NULL,
! SS_assign_special_param(root)), false);
}
/*--------------------
*************** grouping_planner(PlannerInfo *root, bool
*** 2061,2067 ****
}
/* And shove it into final_rel */
! add_path(final_rel, path);
}
/*
--- 2058,2064 ----
}
/* And shove it into final_rel */
! add_path(final_rel, path, false);
}
/*
*************** get_number_of_groups(PlannerInfo *root,
*** 3270,3304 ****
}
/*
- * estimate_hashagg_tablesize
- * estimate the number of bytes that a hash aggregate hashtable will
- * require based on the agg_costs, path width and dNumGroups.
- */
- static Size
- estimate_hashagg_tablesize(Path *path, const AggClauseCosts *agg_costs,
- double dNumGroups)
- {
- Size hashentrysize;
-
- /* Estimate per-hash-entry space at tuple width... */
- hashentrysize = MAXALIGN(path->pathtarget->width) +
- MAXALIGN(SizeofMinimalTupleHeader);
-
- /* plus space for pass-by-ref transition values... */
- hashentrysize += agg_costs->transitionSpace;
- /* plus the per-hash-entry overhead */
- hashentrysize += hash_agg_entry_size(agg_costs->numAggs);
-
- /*
- * Note that this disregards the effect of fill-factor and growth policy
- * of the hash-table. That's probably ok, given default the default
- * fill-factor is relatively high. It'd be hard to meaningfully factor in
- * "double-in-size" growth policies here.
- */
- return hashentrysize * dNumGroups;
- }
-
- /*
* create_grouping_paths
*
* Build a new upperrel containing Paths for grouping and/or aggregation.
--- 3267,3272 ----
*************** create_grouping_paths(PlannerInfo *root,
*** 3419,3425 ****
(List *) parse->havingQual);
}
! add_path(grouped_rel, path);
/* No need to consider any other alternatives. */
set_cheapest(grouped_rel);
--- 3387,3393 ----
(List *) parse->havingQual);
}
! add_path(grouped_rel, path, false);
/* No need to consider any other alternatives. */
set_cheapest(grouped_rel);
*************** create_grouping_paths(PlannerInfo *root,
*** 3592,3598 ****
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups));
else
add_partial_path(grouped_rel, (Path *)
create_group_path(root,
--- 3560,3567 ----
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups),
! false);
else
add_partial_path(grouped_rel, (Path *)
create_group_path(root,
*************** create_grouping_paths(PlannerInfo *root,
*** 3601,3607 ****
partial_grouping_target,
parse->groupClause,
NIL,
! dNumPartialGroups));
}
}
}
--- 3570,3577 ----
partial_grouping_target,
parse->groupClause,
NIL,
! dNumPartialGroups),
! false);
}
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 3632,3638 ****
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups));
}
}
}
--- 3602,3609 ----
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups),
! false);
}
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 3677,3683 ****
rollup_lists,
rollup_groupclauses,
agg_costs,
! dNumGroups));
}
else if (parse->hasAggs)
{
--- 3648,3654 ----
rollup_lists,
rollup_groupclauses,
agg_costs,
! dNumGroups), false);
}
else if (parse->hasAggs)
{
*************** create_grouping_paths(PlannerInfo *root,
*** 3695,3701 ****
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups));
}
else if (parse->groupClause)
{
--- 3666,3672 ----
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups), false);
}
else if (parse->groupClause)
{
*************** create_grouping_paths(PlannerInfo *root,
*** 3710,3716 ****
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups));
}
else
{
--- 3681,3687 ----
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups), false);
}
else
{
*************** create_grouping_paths(PlannerInfo *root,
*** 3760,3766 ****
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups));
else
add_path(grouped_rel, (Path *)
create_group_path(root,
--- 3731,3737 ----
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups), false);
else
add_path(grouped_rel, (Path *)
create_group_path(root,
*************** create_grouping_paths(PlannerInfo *root,
*** 3769,3775 ****
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups));
}
}
--- 3740,3746 ----
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups), false);
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 3801,3807 ****
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups));
}
/*
--- 3772,3778 ----
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups), false);
}
/*
*************** create_grouping_paths(PlannerInfo *root,
*** 3838,3846 ****
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups));
}
}
}
/* Give a helpful error if we failed to find any implementation */
--- 3809,3895 ----
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups), false);
}
}
+
+ /*
+ * If input_rel has partially aggregated partial paths, perform the
+ * final aggregation.
+ *
+ * TODO Allow havingQual - currently not supported at base relation
+ * level.
+ */
+ if (input_rel->partial_grouped_pathlist != NIL &&
+ !parse->havingQual)
+ {
+ Path *path = (Path *) linitial(input_rel->partial_grouped_pathlist);
+ double total_groups = path->rows * path->parallel_workers;
+
+ path = (Path *) create_gather_path(root,
+ input_rel,
+ path,
+ path->pathtarget,
+ NULL,
+ &total_groups);
+
+ /*
+ * The input path is partially aggregated and the final
+ * aggregation - if the path wins - will be done below. So we're
+ * done with it for now.
+ */
+ add_path(input_rel, path, true);
+ }
+
+ /*
+ * If input_rel has partially aggregated paths, perform the final
+ * aggregation.
+ *
+ * TODO Allow havingQual - currently not supported at base relation
+ * level.
+ */
+ if (input_rel->grouped_pathlist != NIL &&
+ !parse->havingQual)
+ {
+ Path *pre_agg = (Path *) linitial(input_rel->grouped_pathlist);
+ PathTarget *proj_target;
+
+ /*
+ * For each grouped variable in pre_agg->pathtarget ensure
+ * evaluation of expr_intermediate (see GroupedVarInfo).
+ */
+ proj_target =
+ create_intermediate_grouping_target(root,
+ pre_agg->pathtarget);
+ pre_agg = (Path *) create_projection_path(root, input_rel,
+ pre_agg,
+ proj_target);
+
+ dNumGroups = get_number_of_groups(root,
+ pre_agg->rows,
+ rollup_lists,
+ rollup_groupclauses);
+
+ MemSet(&agg_final_costs, 0, sizeof(AggClauseCosts));
+ get_agg_clause_costs(root, (Node *) target->exprs,
+ AGGSPLIT_FINAL_DESERIAL,
+ &agg_final_costs);
+ /* get_agg_clause_costs(root, parse->havingQual, */
+ /* AGGSPLIT_FINAL_DESERIAL, */
+ /* &agg_final_costs); */
+
+ add_path(grouped_rel,
+ (Path *) create_agg_path(root, grouped_rel,
+ pre_agg,
+ target,
+ AGG_HASHED,
+ AGGSPLIT_FINAL_DESERIAL,
+ parse->groupClause,
+ (List *) parse->havingQual,
+ &agg_final_costs,
+ dNumGroups),
+ false);
+ }
}
/* Give a helpful error if we failed to find any implementation */
*************** create_one_window_path(PlannerInfo *root
*** 4053,4059 ****
window_pathkeys);
}
! add_path(window_rel, path);
}
/*
--- 4102,4108 ----
window_pathkeys);
}
! add_path(window_rel, path, false);
}
/*
*************** create_distinct_paths(PlannerInfo *root,
*** 4159,4165 ****
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows));
}
}
--- 4208,4214 ----
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows), false);
}
}
*************** create_distinct_paths(PlannerInfo *root,
*** 4186,4192 ****
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows));
}
/*
--- 4235,4241 ----
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows), false);
}
/*
*************** create_distinct_paths(PlannerInfo *root,
*** 4233,4239 ****
parse->distinctClause,
NIL,
NULL,
! numDistinctRows));
}
/* Give a helpful error if we failed to find any implementation */
--- 4282,4288 ----
parse->distinctClause,
NIL,
NULL,
! numDistinctRows), false);
}
/* Give a helpful error if we failed to find any implementation */
*************** create_ordered_paths(PlannerInfo *root,
*** 4331,4337 ****
path = apply_projection_to_path(root, ordered_rel,
path, target);
! add_path(ordered_rel, path);
}
}
--- 4380,4386 ----
path = apply_projection_to_path(root, ordered_rel,
path, target);
! add_path(ordered_rel, path, false);
}
}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
new file mode 100644
index 413a0d9..632f4bb
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
*************** set_upper_references(PlannerInfo *root,
*** 1677,1686 ****
indexed_tlist *subplan_itlist;
List *output_targetlist;
ListCell *l;
! subplan_itlist = build_tlist_index(subplan->targetlist);
output_targetlist = NIL;
foreach(l, plan->targetlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
--- 1677,1744 ----
indexed_tlist *subplan_itlist;
List *output_targetlist;
ListCell *l;
+ List *sub_tlist_save = NIL;
+ bool install_partial_aggrefs = false;
! if (root->grouped_var_list != NIL)
! {
! if (IsA(plan, Agg))
! {
! Agg *agg = (Agg *) plan;
!
! if (agg->aggsplit == AGGSPLIT_FINAL_DESERIAL)
! {
! /*
! * convert_combining_aggrefs could have replaced some vars
! * with Aggref expressions representing the partial
! * aggregation. We need to restore the same Aggrefs in the
! * subplan targetlist, but this would break the subplan if
! * it's something else than the partial aggregation (i.e. the
! * partial aggregation takes place lower in the plan tree). So
! * we'll need restore the original list when done with the
! * references.
! */
! if (!IsA(subplan, Agg))
! sub_tlist_save = copyObject(subplan->targetlist);
! #ifdef USE_ASSERT_CHECKING
! else
! Assert(((Agg *) subplan)->aggsplit == AGGSPLIT_INITIAL_SERIAL);
! #endif /* USE_ASSERT_CHECKING */
!
! /*
! * Restore the aggregate expressions that we might have
! * removed when planning for aggregation at base relation
! * level.
! *
! * TODO Optimize restore_grouping_expressions using a new
! * parameter indicating whether the targetlist can contain
! * "intermediate expression". Only Result (or also Gather if
! * we use its target list to evaluate "intermediate
! * expressions"?) node should contain those.
! */
! subplan->targetlist =
! restore_grouping_expressions(root, subplan->targetlist);
! }
! else if (agg->aggsplit == AGGSPLIT_INITIAL_SERIAL)
! install_partial_aggrefs = true;
! }
! }
+ /*
+ * AGGSPLIT_INITIAL_SERIAL might be there just to implement grouped base
+ * relation. Since the parent node does not necessarily call
+ * set_upper_references() (note that there might be various nodes between
+ * the initial and the final aggregation), special effort is needed to
+ * ensure that "grouped vars" are replaced with the corresponding
+ * expressions (see GroupedVarInfo).
+ */
+ if (install_partial_aggrefs)
+ plan->targetlist = restore_grouping_expressions(root,
+ plan->targetlist);
+
+ subplan_itlist = build_tlist_index(subplan->targetlist);
output_targetlist = NIL;
+
foreach(l, plan->targetlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
*************** set_upper_references(PlannerInfo *root,
*** 1720,1725 ****
--- 1778,1787 ----
OUTER_VAR,
rtoffset);
+ /* Restore the original list if appropriate. */
+ if (sub_tlist_save != NIL)
+ subplan->targetlist = sub_tlist_save;
+
pfree(subplan_itlist);
}
*************** convert_combining_aggrefs(Node *node, vo
*** 1796,1801 ****
--- 1858,1864 ----
(void *) context);
}
+
/*
* set_dummy_tlist_references
* Replace the targetlist of an upper-level plan node with a simple
*************** fix_join_expr_mutator(Node *node, fix_jo
*** 2235,2240 ****
--- 2298,2304 ----
(void *) context);
}
+
/*
* fix_upper_expr
* Modifies an expression tree so that all Var nodes reference outputs
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
new file mode 100644
index 1bbbc29..e0b31c3
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
*************** plan_set_operations(PlannerInfo *root)
*** 208,214 ****
root->processed_tlist = top_tlist;
/* Add only the final path to the SETOP upperrel. */
! add_path(setop_rel, path);
/* Let extensions possibly add some more paths */
if (create_upper_paths_hook)
--- 208,214 ----
root->processed_tlist = top_tlist;
/* Add only the final path to the SETOP upperrel. */
! add_path(setop_rel, path, false);
/* Let extensions possibly add some more paths */
if (create_upper_paths_hook)
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
new file mode 100644
index 3b7c56d..aefb79e
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
*************** set_cheapest(RelOptInfo *parent_rel)
*** 409,416 ****
* Returns nothing, but modifies parent_rel->pathlist.
*/
void
! add_path(RelOptInfo *parent_rel, Path *new_path)
{
bool accept_new = true; /* unless we find a superior old path */
ListCell *insert_after = NULL; /* where to insert new item */
List *new_path_pathkeys;
--- 409,417 ----
* Returns nothing, but modifies parent_rel->pathlist.
*/
void
! add_path(RelOptInfo *parent_rel, Path *new_path, bool grouped)
{
+ List *pathlist;
bool accept_new = true; /* unless we find a superior old path */
ListCell *insert_after = NULL; /* where to insert new item */
List *new_path_pathkeys;
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 427,432 ****
--- 428,435 ----
/* Pretend parameterized paths have no pathkeys, per comment above */
new_path_pathkeys = new_path->param_info ? NIL : new_path->pathkeys;
+ pathlist = !grouped ? parent_rel->pathlist : parent_rel->grouped_pathlist;
+
/*
* Loop to check proposed new path against old paths. Note it is possible
* for more than one old path to be tossed out because new_path dominates
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 436,442 ****
* list cell.
*/
p1_prev = NULL;
! for (p1 = list_head(parent_rel->pathlist); p1 != NULL; p1 = p1_next)
{
Path *old_path = (Path *) lfirst(p1);
bool remove_old = false; /* unless new proves superior */
--- 439,445 ----
* list cell.
*/
p1_prev = NULL;
! for (p1 = list_head(pathlist); p1 != NULL; p1 = p1_next)
{
Path *old_path = (Path *) lfirst(p1);
bool remove_old = false; /* unless new proves superior */
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 582,589 ****
*/
if (remove_old)
{
! parent_rel->pathlist = list_delete_cell(parent_rel->pathlist,
! p1, p1_prev);
/*
* Delete the data pointed-to by the deleted cell, if possible
--- 585,591 ----
*/
if (remove_old)
{
! pathlist = list_delete_cell(pathlist, p1, p1_prev);
/*
* Delete the data pointed-to by the deleted cell, if possible
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 614,622 ****
{
/* Accept the new path: insert it at proper place in pathlist */
if (insert_after)
! lappend_cell(parent_rel->pathlist, insert_after, new_path);
else
! parent_rel->pathlist = lcons(new_path, parent_rel->pathlist);
}
else
{
--- 616,624 ----
{
/* Accept the new path: insert it at proper place in pathlist */
if (insert_after)
! lappend_cell(pathlist, insert_after, new_path);
else
! pathlist = lcons(new_path, pathlist);
}
else
{
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 624,629 ****
--- 626,636 ----
if (!IsA(new_path, IndexPath))
pfree(new_path);
}
+
+ if (!grouped)
+ parent_rel->pathlist = pathlist;
+ else
+ parent_rel->grouped_pathlist = pathlist;
}
/*
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 646,653 ****
bool
add_path_precheck(RelOptInfo *parent_rel,
Cost startup_cost, Cost total_cost,
! List *pathkeys, Relids required_outer)
{
List *new_path_pathkeys;
bool consider_startup;
ListCell *p1;
--- 653,661 ----
bool
add_path_precheck(RelOptInfo *parent_rel,
Cost startup_cost, Cost total_cost,
! List *pathkeys, Relids required_outer, bool grouped)
{
+ List *pathlist;
List *new_path_pathkeys;
bool consider_startup;
ListCell *p1;
*************** add_path_precheck(RelOptInfo *parent_rel
*** 658,664 ****
/* Decide whether new path's startup cost is interesting */
consider_startup = required_outer ? parent_rel->consider_param_startup : parent_rel->consider_startup;
! foreach(p1, parent_rel->pathlist)
{
Path *old_path = (Path *) lfirst(p1);
PathKeysComparison keyscmp;
--- 666,674 ----
/* Decide whether new path's startup cost is interesting */
consider_startup = required_outer ? parent_rel->consider_param_startup : parent_rel->consider_startup;
! pathlist = !grouped ? parent_rel->pathlist : parent_rel->grouped_pathlist;
!
! foreach(p1, pathlist)
{
Path *old_path = (Path *) lfirst(p1);
PathKeysComparison keyscmp;
*************** add_path_precheck(RelOptInfo *parent_rel
*** 750,772 ****
* isn't an IndexPath.
*/
void
! add_partial_path(RelOptInfo *parent_rel, Path *new_path)
{
bool accept_new = true; /* unless we find a superior old path */
ListCell *insert_after = NULL; /* where to insert new item */
ListCell *p1;
ListCell *p1_prev;
ListCell *p1_next;
/* Check for query cancel. */
CHECK_FOR_INTERRUPTS();
/*
* As in add_path, throw out any paths which are dominated by the new
* path, but throw out the new path if some existing path dominates it.
*/
p1_prev = NULL;
! for (p1 = list_head(parent_rel->partial_pathlist); p1 != NULL;
p1 = p1_next)
{
Path *old_path = (Path *) lfirst(p1);
--- 760,786 ----
* isn't an IndexPath.
*/
void
! add_partial_path(RelOptInfo *parent_rel, Path *new_path, bool grouped)
{
bool accept_new = true; /* unless we find a superior old path */
ListCell *insert_after = NULL; /* where to insert new item */
ListCell *p1;
ListCell *p1_prev;
ListCell *p1_next;
+ List *pathlist;
/* Check for query cancel. */
CHECK_FOR_INTERRUPTS();
+ pathlist = !grouped ? parent_rel->partial_pathlist :
+ parent_rel->partial_grouped_pathlist;
+
/*
* As in add_path, throw out any paths which are dominated by the new
* path, but throw out the new path if some existing path dominates it.
*/
p1_prev = NULL;
! for (p1 = list_head(pathlist); p1 != NULL;
p1 = p1_next)
{
Path *old_path = (Path *) lfirst(p1);
*************** add_partial_path(RelOptInfo *parent_rel,
*** 820,831 ****
}
/*
! * Remove current element from partial_pathlist if dominated by new.
*/
if (remove_old)
{
! parent_rel->partial_pathlist =
! list_delete_cell(parent_rel->partial_pathlist, p1, p1_prev);
/* we should not see IndexPaths here, so always safe to delete */
Assert(!IsA(old_path, IndexPath));
pfree(old_path);
--- 834,844 ----
}
/*
! * Remove current element from pathlist if dominated by new.
*/
if (remove_old)
{
! pathlist = list_delete_cell(pathlist, p1, p1_prev);
/* we should not see IndexPaths here, so always safe to delete */
Assert(!IsA(old_path, IndexPath));
pfree(old_path);
*************** add_partial_path(RelOptInfo *parent_rel,
*** 842,848 ****
/*
* If we found an old path that dominates new_path, we can quit
! * scanning the partial_pathlist; we will not add new_path, and we
* assume new_path cannot dominate any later path.
*/
if (!accept_new)
--- 855,861 ----
/*
* If we found an old path that dominates new_path, we can quit
! * scanning the pathlist; we will not add new_path, and we
* assume new_path cannot dominate any later path.
*/
if (!accept_new)
*************** add_partial_path(RelOptInfo *parent_rel,
*** 853,862 ****
{
/* Accept the new path: insert it at proper place */
if (insert_after)
! lappend_cell(parent_rel->partial_pathlist, insert_after, new_path);
else
! parent_rel->partial_pathlist =
! lcons(new_path, parent_rel->partial_pathlist);
}
else
{
--- 866,874 ----
{
/* Accept the new path: insert it at proper place */
if (insert_after)
! lappend_cell(pathlist, insert_after, new_path);
else
! pathlist = lcons(new_path, pathlist);
}
else
{
*************** add_partial_path(RelOptInfo *parent_rel,
*** 865,870 ****
--- 877,887 ----
/* Reject and recycle the new path */
pfree(new_path);
}
+
+ if (!grouped)
+ parent_rel->partial_pathlist = pathlist;
+ else
+ parent_rel->partial_grouped_pathlist = pathlist;
}
/*
*************** add_partial_path(RelOptInfo *parent_rel,
*** 879,887 ****
*/
bool
add_partial_path_precheck(RelOptInfo *parent_rel, Cost total_cost,
! List *pathkeys)
{
ListCell *p1;
/*
* Our goal here is twofold. First, we want to find out whether this path
--- 896,906 ----
*/
bool
add_partial_path_precheck(RelOptInfo *parent_rel, Cost total_cost,
! List *pathkeys, bool grouped)
{
ListCell *p1;
+ List *pathlist = !grouped ? parent_rel->partial_pathlist :
+ parent_rel->partial_grouped_pathlist;
/*
* Our goal here is twofold. First, we want to find out whether this path
*************** add_partial_path_precheck(RelOptInfo *pa
*** 891,900 ****
* final cost computations. If so, we definitely want to consider it.
*
* Unlike add_path(), we always compare pathkeys here. This is because we
! * expect partial_pathlist to be very short, and getting a definitive
! * answer at this stage avoids the need to call add_path_precheck.
*/
! foreach(p1, parent_rel->partial_pathlist)
{
Path *old_path = (Path *) lfirst(p1);
PathKeysComparison keyscmp;
--- 910,920 ----
* final cost computations. If so, we definitely want to consider it.
*
* Unlike add_path(), we always compare pathkeys here. This is because we
! * expect partial_pathlist / grouped_pathlist to be very short, and
! * getting a definitive answer at this stage avoids the need to call
! * add_path_precheck.
*/
! foreach(p1, pathlist)
{
Path *old_path = (Path *) lfirst(p1);
PathKeysComparison keyscmp;
*************** add_partial_path_precheck(RelOptInfo *pa
*** 923,929 ****
* completion.
*/
if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! NULL))
return false;
return true;
--- 943,949 ----
* completion.
*/
if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! NULL, false))
return false;
return true;
*************** create_hashjoin_path(PlannerInfo *root,
*** 2108,2120 ****
Path *inner_path,
List *restrict_clauses,
Relids required_outer,
! List *hashclauses)
{
HashPath *pathnode = makeNode(HashPath);
pathnode->jpath.path.pathtype = T_HashJoin;
pathnode->jpath.path.parent = joinrel;
! pathnode->jpath.path.pathtarget = joinrel->reltarget;
pathnode->jpath.path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
--- 2128,2142 ----
Path *inner_path,
List *restrict_clauses,
Relids required_outer,
! List *hashclauses,
! bool grouped)
{
HashPath *pathnode = makeNode(HashPath);
pathnode->jpath.path.pathtype = T_HashJoin;
pathnode->jpath.path.parent = joinrel;
! pathnode->jpath.path.pathtarget = !grouped ?
! joinrel->reltarget : joinrel->reltarget_grouped;
pathnode->jpath.path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
new file mode 100644
index 7a8674d..126120f
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
*************** typedef struct JoinHashEntry
*** 33,39 ****
} JoinHashEntry;
static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! RelOptInfo *input_rel);
static List *build_joinrel_restrictlist(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outer_rel,
--- 33,39 ----
} JoinHashEntry;
static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! RelOptInfo *input_rel, bool grouped);
static List *build_joinrel_restrictlist(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outer_rel,
*************** build_simple_rel(PlannerInfo *root, int
*** 106,114 ****
--- 106,117 ----
rel->consider_param_startup = false; /* might get changed later */
rel->consider_parallel = false; /* might get changed later */
rel->reltarget = create_empty_pathtarget();
+ /* Not all relations have this target. */
+ rel->reltarget_grouped = NULL;
rel->pathlist = NIL;
rel->ppilist = NIL;
rel->partial_pathlist = NIL;
+ rel->partial_grouped_pathlist = NIL;
rel->cheapest_startup_path = NULL;
rel->cheapest_total_path = NULL;
rel->cheapest_unique_path = NULL;
*************** build_join_rel(PlannerInfo *root,
*** 371,379 ****
--- 374,385 ----
joinrel->consider_param_startup = false;
joinrel->consider_parallel = false;
joinrel->reltarget = create_empty_pathtarget();
+ /* Not all joins have this target. */
+ joinrel->reltarget_grouped = NULL;
joinrel->pathlist = NIL;
joinrel->ppilist = NIL;
joinrel->partial_pathlist = NIL;
+ joinrel->partial_grouped_pathlist = NIL;
joinrel->cheapest_startup_path = NULL;
joinrel->cheapest_total_path = NULL;
joinrel->cheapest_unique_path = NULL;
*************** build_join_rel(PlannerInfo *root,
*** 459,468 ****
* and inner rels we first try to build it from. But the contents should
* be the same regardless.
*/
! build_joinrel_tlist(root, joinrel, outer_rel);
! build_joinrel_tlist(root, joinrel, inner_rel);
add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel);
/*
* add_placeholders_to_joinrel also took care of adding the ph_lateral
* sets of any PlaceHolderVars computed here to direct_lateral_relids, so
--- 465,488 ----
* and inner rels we first try to build it from. But the contents should
* be the same regardless.
*/
! build_joinrel_tlist(root, joinrel, outer_rel, false);
! build_joinrel_tlist(root, joinrel, inner_rel, false);
add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel);
+ /* Build reltarget_grouped if appropriate. */
+ if (outer_rel->reltarget_grouped && inner_rel->reltarget_grouped &&
+ root->all_baserels_grouped)
+ {
+ build_joinrel_tlist(root, joinrel, outer_rel, true);
+ build_joinrel_tlist(root, joinrel, inner_rel, true);
+
+ /*
+ * No need to add PHVs - if there were some, it'd mean that the join
+ * is below nullable side of outer join, in which case no
+ * pre-aggregation should take place.
+ */
+ }
+
/*
* add_placeholders_to_joinrel also took care of adding the ph_lateral
* sets of any PlaceHolderVars computed here to direct_lateral_relids, so
*************** min_join_parameterization(PlannerInfo *r
*** 607,622 ****
*/
static void
build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! RelOptInfo *input_rel)
{
Relids relids = joinrel->relids;
ListCell *vars;
! foreach(vars, input_rel->reltarget->exprs)
{
Var *var = (Var *) lfirst(vars);
RelOptInfo *baserel;
int ndx;
/*
* Ignore PlaceHolderVars in the input tlists; we'll make our own
--- 627,665 ----
*/
static void
build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! RelOptInfo *input_rel, bool grouped)
{
Relids relids = joinrel->relids;
+ PathTarget *reltarget_input, *reltarget_result;
ListCell *vars;
+ int i = -1;
! if (!grouped)
! {
! reltarget_input = input_rel->reltarget;
! reltarget_result = joinrel->reltarget;
! }
! else
! {
! reltarget_input = input_rel->reltarget_grouped;
! /* Shouldn't be called otherwise. */
! Assert(reltarget_input != NULL);
!
! /* Called first time for this joinrel? */
! if (joinrel->reltarget_grouped == NULL)
! joinrel->reltarget_grouped = create_empty_pathtarget();
!
! reltarget_result = joinrel->reltarget_grouped;
! }
!
! foreach(vars, reltarget_input->exprs)
{
Var *var = (Var *) lfirst(vars);
RelOptInfo *baserel;
int ndx;
+ Index sortgroupref = 0;
+
+ i++;
/*
* Ignore PlaceHolderVars in the input tlists; we'll make our own
*************** build_joinrel_tlist(PlannerInfo *root, R
*** 642,650 ****
if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
{
/* Yup, add it to the output */
! joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs, var);
/* Vars have cost zero, so no need to adjust reltarget->cost */
! joinrel->reltarget->width += baserel->attr_widths[ndx];
}
}
}
--- 685,697 ----
if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
{
/* Yup, add it to the output */
! if (reltarget_input->sortgrouprefs)
! sortgroupref = reltarget_input->sortgrouprefs[i];
! add_column_to_pathtarget(reltarget_result, (Expr *) var,
! sortgroupref);
!
/* Vars have cost zero, so no need to adjust reltarget->cost */
! reltarget_result->width += baserel->attr_widths[ndx];
}
}
}
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
new file mode 100644
index 45205a8..423a39e
*** a/src/backend/optimizer/util/tlist.c
--- b/src/backend/optimizer/util/tlist.c
*************** apply_pathtarget_labeling_to_tlist(List
*** 759,761 ****
--- 759,896 ----
i++;
}
}
+
+ /*
+ * Replace each "grouped var" in the source target with its "intermediate
+ * expression" if one exists, i.e. apply the effect of joins to the result of
+ * partial aggregation emitted by base relation.
+ */
+ PathTarget *
+ create_intermediate_grouping_target(PlannerInfo *root, PathTarget *src)
+ {
+ PathTarget *result = create_empty_pathtarget();
+ int i = 0;
+ ListCell *l;
+
+ foreach(l, src->exprs)
+ {
+ Expr *expr, *expr_new;
+ Index sortgroupref = 0;
+
+ if (src->sortgrouprefs)
+ sortgroupref = src->sortgrouprefs[i];
+
+ expr_new = expr = (Expr *) lfirst(l);
+ if (IsA(expr, Var))
+ {
+ Var *var = (Var *) expr;
+ ListCell *lc;
+
+ foreach(lc, root->grouped_var_list)
+ {
+ GroupedVarInfo *gvi = (GroupedVarInfo *) lfirst(lc);
+
+ if (gvi->expr_intermediate &&
+ gvi->var->varno == var->varno &&
+ gvi->var->varattno == var->varattno)
+ {
+ /* XXX Need a copy? */
+ expr_new = gvi->expr_intermediate;
+ break;
+ }
+ }
+ }
+ add_column_to_pathtarget(result, expr_new, sortgroupref);
+
+ i++;
+ }
+
+ return result;
+ }
+
+ /*
+ * Replace each "grouped var" in the source targetlist with the original
+ * expression. This includes the items already replaced by "intermediate
+ * expression", see create_intermediate_grouping_target.
+ *
+ * TODO Think of more suitable name. Although "grouped var" may substitute for
+ * grouping expressions in the future, currently Aggref is the only outcome of
+ * the replacement. undo_grouped_var_substitutions?
+ */
+ List *
+ restore_grouping_expressions(PlannerInfo *root, List *src)
+ {
+ List *result = NIL;
+ ListCell *l;
+
+ foreach(l, src)
+ {
+ TargetEntry *te, *te_new;
+ Expr *expr_new = NULL;
+
+ te = (TargetEntry *) lfirst(l);
+
+ if (IsA(te->expr, Var))
+ {
+ Var *var = (Var *) te->expr;
+
+ expr_new = find_grouped_var_expr(root, var);
+ }
+ else
+ {
+ /* Is this the "intermediate expression"? */
+ ListCell *lc;
+
+ foreach(lc, root->grouped_var_list)
+ {
+ GroupedVarInfo *gvi = (GroupedVarInfo *) lfirst(lc);
+
+ /*
+ * If expr_intermediate was valid, the var should already have
+ * been replaced by the "intermediate expression" (which
+ * should be non-Var).
+ */
+ if (gvi->expr_intermediate != NULL &&
+ equal(te->expr, gvi->expr_intermediate))
+ {
+ /* XXX Need a copy? */
+ expr_new = gvi->expr;
+ break;
+ }
+ }
+ }
+ if (expr_new != NULL)
+ {
+ te_new = flatCopyTargetEntry(te);
+ te_new->expr = expr_new;
+ }
+ else
+ te_new = te;
+ result = lappend(result, te_new);
+ }
+
+ return result;
+ }
+
+ /*
+ * Find the expression that we've replaced with grouped var earlier.
+ */
+ Expr *
+ find_grouped_var_expr(PlannerInfo *root, Var *var)
+ {
+ ListCell *lc;
+
+ foreach(lc, root->grouped_var_list)
+ {
+ GroupedVarInfo *gvi = (GroupedVarInfo *) lfirst(lc);
+
+ if (gvi->var->varno == var->varno &&
+ gvi->var->varattno == var->varattno)
+ {
+ /* XXX Need a copy? */
+ return gvi->expr;
+ }
+ }
+
+ return NULL;
+ }
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
new file mode 100644
index 1297960..ed521bb
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 95,100 ****
--- 95,101 ----
FuncDetailCode fdresult;
char aggkind = 0;
ParseCallbackState pcbstate;
+ Oid aggtransmultifn = InvalidOid;
/*
* If there's an aggregate filter, transform it using transformWhereClause
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 318,323 ****
--- 319,325 ----
classForm = (Form_pg_aggregate) GETSTRUCT(tup);
aggkind = classForm->aggkind;
catDirectArgs = classForm->aggnumdirectargs;
+ aggtransmultifn = classForm->aggtransmultifn;
ReleaseSysCache(tup);
/* Now check various disallowed cases. */
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 662,667 ****
--- 664,670 ----
aggref->aggstar = agg_star;
aggref->aggvariadic = func_variadic;
aggref->aggkind = aggkind;
+ aggref->aggtransmultifn = aggtransmultifn;
/* agglevelsup will be set by transformAggregateCall */
aggref->aggsplit = AGGSPLIT_SIMPLE; /* planner might change this */
aggref->location = location;
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
new file mode 100644
index 86b46de..be7ef22
*** a/src/backend/utils/adt/float.c
--- b/src/backend/utils/adt/float.c
*************** float4_accum(PG_FUNCTION_ARGS)
*** 2630,2635 ****
--- 2630,2663 ----
}
}
+ /*
+ * State multiplication function for sum(float4) aggregate.
+ */
+ Datum
+ float4_sum_mul(PG_FUNCTION_ARGS)
+ {
+ float4 state = PG_GETARG_FLOAT4(0);
+ int64 factor;
+
+ #ifndef USE_FLOAT8_BYVAL /* controls int8 too */
+ {
+ int64 *factor_p = (int64 *) PG_GETARG_POINTER(1);
+
+ factor = *factor_p;
+ }
+ #else
+ factor = PG_GETARG_INT64(1);
+ #endif
+
+ /*
+ * Sum of a single group is added each time the same input set is
+ * aggregated again.
+ */
+ state *= (float8) factor;
+
+ PG_RETURN_FLOAT4(state);
+ }
+
Datum
float8_avg(PG_FUNCTION_ARGS)
{
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
new file mode 100644
index 301dffa..063adbb
*** a/src/backend/utils/adt/selfuncs.c
--- b/src/backend/utils/adt/selfuncs.c
***************
*** 111,116 ****
--- 111,117 ----
#include "catalog/pg_statistic.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
+ #include "executor/nodeAgg.h"
#include "mb/pg_wchar.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
*************** estimate_hash_bucketsize(PlannerInfo *ro
*** 3657,3662 ****
--- 3658,3691 ----
return (Selectivity) estfract;
}
+ /*
+ * estimate_hashagg_tablesize
+ * estimate the number of bytes that a hash aggregate hashtable will
+ * require based on the agg_costs, path width and dNumGroups.
+ */
+ Size
+ estimate_hashagg_tablesize(Path *path, const AggClauseCosts *agg_costs,
+ double dNumGroups)
+ {
+ Size hashentrysize;
+
+ /* Estimate per-hash-entry space at tuple width... */
+ hashentrysize = MAXALIGN(path->pathtarget->width) +
+ MAXALIGN(SizeofMinimalTupleHeader);
+
+ /* plus space for pass-by-ref transition values... */
+ hashentrysize += agg_costs->transitionSpace;
+ /* plus the per-hash-entry overhead */
+ hashentrysize += hash_agg_entry_size(agg_costs->numAggs);
+
+ /*
+ * Note that this disregards the effect of fill-factor and growth policy
+ * of the hash-table. That's probably ok, given default the default
+ * fill-factor is relatively high. It'd be hard to meaningfully factor in
+ * "double-in-size" growth policies here.
+ */
+ return hashentrysize * dNumGroups;
+ }
/*-------------------------------------------------------------------------
*
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
new file mode 100644
index 1ffde6c..f0b5498
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
***************
*** 32,37 ****
--- 32,38 ----
* aggkind aggregate kind, see AGGKIND_ categories below
* aggnumdirectargs number of arguments that are "direct" arguments
* aggtransfn transition function
+ * aggtransmultifn transition state multiplication function
* aggfinalfn final function (0 if none)
* aggcombinefn combine function (0 if none)
* aggserialfn function to convert transtype to bytea (0 if none)
*************** CATALOG(pg_aggregate,2600) BKI_WITHOUT_O
*** 58,63 ****
--- 59,65 ----
char aggkind;
int16 aggnumdirectargs;
regproc aggtransfn;
+ regproc aggtransmultifn;
regproc aggfinalfn;
regproc aggcombinefn;
regproc aggserialfn;
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 91,117 ****
* ----------------
*/
! #define Natts_pg_aggregate 20
#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_aggcombinefn 6
! #define Anum_pg_aggregate_aggserialfn 7
! #define Anum_pg_aggregate_aggdeserialfn 8
! #define Anum_pg_aggregate_aggmtransfn 9
! #define Anum_pg_aggregate_aggminvtransfn 10
! #define Anum_pg_aggregate_aggmfinalfn 11
! #define Anum_pg_aggregate_aggfinalextra 12
! #define Anum_pg_aggregate_aggmfinalextra 13
! #define Anum_pg_aggregate_aggsortop 14
! #define Anum_pg_aggregate_aggtranstype 15
! #define Anum_pg_aggregate_aggtransspace 16
! #define Anum_pg_aggregate_aggmtranstype 17
! #define Anum_pg_aggregate_aggmtransspace 18
! #define Anum_pg_aggregate_agginitval 19
! #define Anum_pg_aggregate_aggminitval 20
/*
* Symbolic values for aggkind column. We distinguish normal aggregates
--- 93,120 ----
* ----------------
*/
! #define Natts_pg_aggregate 21
#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_aggtransmultifn 5
! #define Anum_pg_aggregate_aggfinalfn 6
! #define Anum_pg_aggregate_aggcombinefn 7
! #define Anum_pg_aggregate_aggserialfn 8
! #define Anum_pg_aggregate_aggdeserialfn 9
! #define Anum_pg_aggregate_aggmtransfn 10
! #define Anum_pg_aggregate_aggminvtransfn 11
! #define Anum_pg_aggregate_aggmfinalfn 12
! #define Anum_pg_aggregate_aggfinalextra 13
! #define Anum_pg_aggregate_aggmfinalextra 14
! #define Anum_pg_aggregate_aggsortop 15
! #define Anum_pg_aggregate_aggtranstype 16
! #define Anum_pg_aggregate_aggtransspace 17
! #define Anum_pg_aggregate_aggmtranstype 18
! #define Anum_pg_aggregate_aggmtransspace 19
! #define Anum_pg_aggregate_agginitval 20
! #define Anum_pg_aggregate_aggminitval 21
/*
* Symbolic values for aggkind column. We distinguish normal aggregates
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 135,318 ****
*/
/* avg */
! DATA(insert ( 2100 n 0 int8_avg_accum numeric_poly_avg int8_avg_combine int8_avg_serialize int8_avg_deserialize 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_combine - - 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 int4_avg_combine - - 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_combine numeric_avg_serialize numeric_avg_deserialize 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 float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2105 n 0 float8_accum float8_avg float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2106 n 0 interval_accum interval_avg interval_combine - - 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_combine int8_avg_serialize int8_avg_deserialize 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_combine numeric_avg_serialize numeric_avg_deserialize numeric_avg_accum numeric_accum_inv numeric_sum f f 0 2281 128 2281 128 _null_ _null_ ));
/* max */
! 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 - 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 - 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 numeric_combine numeric_serialize numeric_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2722 n 0 float8_accum float8_var_pop float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2723 n 0 numeric_accum numeric_var_pop numeric_combine numeric_serialize numeric_deserialize 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 numeric_combine numeric_serialize numeric_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2645 n 0 float8_accum float8_var_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2646 n 0 numeric_accum numeric_var_samp numeric_combine numeric_serialize numeric_deserialize 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 numeric_combine numeric_serialize numeric_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2152 n 0 float8_accum float8_var_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2153 n 0 numeric_accum numeric_var_samp numeric_combine numeric_serialize numeric_deserialize 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 numeric_combine numeric_serialize numeric_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2728 n 0 float8_accum float8_stddev_pop float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2729 n 0 numeric_accum numeric_stddev_pop numeric_combine numeric_serialize numeric_deserialize 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 numeric_combine numeric_serialize numeric_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2716 n 0 float8_accum float8_stddev_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2717 n 0 numeric_accum numeric_stddev_samp numeric_combine numeric_serialize numeric_deserialize 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 numeric_combine numeric_serialize numeric_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2158 n 0 float8_accum float8_stddev_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2159 n 0 numeric_accum numeric_stddev_samp numeric_combine numeric_serialize numeric_deserialize 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 - int8pl - - - - - f f 0 20 0 0 0 "0" _null_ ));
! DATA(insert ( 2819 n 0 float8_regr_accum float8_regr_sxx float8_regr_combine - - - - - 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 float8_regr_combine - - - - - 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 float8_regr_combine - - - - - 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 float8_regr_combine - - - - - 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 float8_regr_combine - - - - - 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 float8_regr_combine - - - - - 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 float8_regr_combine - - - - - 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 float8_regr_combine - - - - - 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 float8_regr_combine - - - - - 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 float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2829 n 0 float8_regr_accum float8_corr float8_regr_combine - - - - - 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 - 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 - 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 - 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 - 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_ ));
/* 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_ ));
/* text */
! 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_ ));
/* 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_ ));
/* 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_ ));
/* 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_ ));
/*
--- 138,322 ----
*/
/* avg */
! DATA(insert ( 2100 n 0 int8_avg_accum 0 numeric_poly_avg int8_avg_combine int8_avg_serialize int8_avg_deserialize 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 0 int8_avg int4_avg_combine - - 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 0 int8_avg int4_avg_combine - - 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 0 numeric_avg numeric_avg_combine numeric_avg_serialize numeric_avg_deserialize numeric_avg_accum numeric_accum_inv numeric_avg f f 0 2281 128 2281 128 _null_ _null_ ));
! DATA(insert ( 2104 n 0 float4_accum 0 float8_avg float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2105 n 0 float8_accum 0 float8_avg float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2106 n 0 interval_accum 0 interval_avg interval_combine - - 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 0 numeric_poly_sum int8_avg_combine int8_avg_serialize int8_avg_deserialize 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 0 - 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 0 - 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 4002 - float4pl - - - - - f f 0 700 0 0 0 _null_ _null_ ));
! DATA(insert ( 2111 n 0 float8pl 0 - float8pl - - - - - f f 0 701 0 0 0 _null_ _null_ ));
! DATA(insert ( 2112 n 0 cash_pl 0 - cash_pl - - cash_pl cash_mi - f f 0 790 0 790 0 _null_ _null_ ));
! DATA(insert ( 2113 n 0 interval_pl 0 - interval_pl - - interval_pl interval_mi - f f 0 1186 0 1186 0 _null_ _null_ ));
! DATA(insert ( 2114 n 0 numeric_avg_accum 0 numeric_sum numeric_avg_combine numeric_avg_serialize numeric_avg_deserialize numeric_avg_accum numeric_accum_inv numeric_sum f f 0 2281 128 2281 128 _null_ _null_ ));
/* max */
! DATA(insert ( 2115 n 0 int8larger 0 - int8larger - - - - - f f 413 20 0 0 0 _null_ _null_ ));
! DATA(insert ( 2116 n 0 int4larger 0 - int4larger - - - - - f f 521 23 0 0 0 _null_ _null_ ));
! DATA(insert ( 2117 n 0 int2larger 0 - int2larger - - - - - f f 520 21 0 0 0 _null_ _null_ ));
! DATA(insert ( 2118 n 0 oidlarger 0 - oidlarger - - - - - f f 610 26 0 0 0 _null_ _null_ ));
! DATA(insert ( 2119 n 0 float4larger 0 - float4larger - - - - - f f 623 700 0 0 0 _null_ _null_ ));
! DATA(insert ( 2120 n 0 float8larger 0 - float8larger - - - - - f f 674 701 0 0 0 _null_ _null_ ));
! DATA(insert ( 2121 n 0 int4larger 0 - int4larger - - - - - f f 563 702 0 0 0 _null_ _null_ ));
! DATA(insert ( 2122 n 0 date_larger 0 - date_larger - - - - - f f 1097 1082 0 0 0 _null_ _null_ ));
! DATA(insert ( 2123 n 0 time_larger 0 - time_larger - - - - - f f 1112 1083 0 0 0 _null_ _null_ ));
! DATA(insert ( 2124 n 0 timetz_larger 0 - timetz_larger - - - - - f f 1554 1266 0 0 0 _null_ _null_ ));
! DATA(insert ( 2125 n 0 cashlarger 0 - cashlarger - - - - - f f 903 790 0 0 0 _null_ _null_ ));
! DATA(insert ( 2126 n 0 timestamp_larger 0 - timestamp_larger - - - - - f f 2064 1114 0 0 0 _null_ _null_ ));
! DATA(insert ( 2127 n 0 timestamptz_larger 0 - timestamptz_larger - - - - - f f 1324 1184 0 0 0 _null_ _null_ ));
! DATA(insert ( 2128 n 0 interval_larger 0 - interval_larger - - - - - f f 1334 1186 0 0 0 _null_ _null_ ));
! DATA(insert ( 2129 n 0 text_larger 0 - text_larger - - - - - f f 666 25 0 0 0 _null_ _null_ ));
! DATA(insert ( 2130 n 0 numeric_larger 0 - numeric_larger - - - - - f f 1756 1700 0 0 0 _null_ _null_ ));
! DATA(insert ( 2050 n 0 array_larger 0 - array_larger - - - - - f f 1073 2277 0 0 0 _null_ _null_ ));
! DATA(insert ( 2244 n 0 bpchar_larger 0 - bpchar_larger - - - - - f f 1060 1042 0 0 0 _null_ _null_ ));
! DATA(insert ( 2797 n 0 tidlarger 0 - tidlarger - - - - - f f 2800 27 0 0 0 _null_ _null_ ));
! DATA(insert ( 3526 n 0 enum_larger 0 - enum_larger - - - - - f f 3519 3500 0 0 0 _null_ _null_ ));
! DATA(insert ( 3564 n 0 network_larger 0 - network_larger - - - - - f f 1205 869 0 0 0 _null_ _null_ ));
/* min */
! DATA(insert ( 2131 n 0 int8smaller 0 - int8smaller - - - - - f f 412 20 0 0 0 _null_ _null_ ));
! DATA(insert ( 2132 n 0 int4smaller 0 - int4smaller - - - - - f f 97 23 0 0 0 _null_ _null_ ));
! DATA(insert ( 2133 n 0 int2smaller 0 - int2smaller - - - - - f f 95 21 0 0 0 _null_ _null_ ));
! DATA(insert ( 2134 n 0 oidsmaller 0 - oidsmaller - - - - - f f 609 26 0 0 0 _null_ _null_ ));
! DATA(insert ( 2135 n 0 float4smaller 0 - float4smaller - - - - - f f 622 700 0 0 0 _null_ _null_ ));
! DATA(insert ( 2136 n 0 float8smaller 0 - float8smaller - - - - - f f 672 701 0 0 0 _null_ _null_ ));
! DATA(insert ( 2137 n 0 int4smaller 0 - int4smaller - - - - - f f 562 702 0 0 0 _null_ _null_ ));
! DATA(insert ( 2138 n 0 date_smaller 0 - date_smaller - - - - - f f 1095 1082 0 0 0 _null_ _null_ ));
! DATA(insert ( 2139 n 0 time_smaller 0 - time_smaller - - - - - f f 1110 1083 0 0 0 _null_ _null_ ));
! DATA(insert ( 2140 n 0 timetz_smaller 0 - timetz_smaller - - - - - f f 1552 1266 0 0 0 _null_ _null_ ));
! DATA(insert ( 2141 n 0 cashsmaller 0 - cashsmaller - - - - - f f 902 790 0 0 0 _null_ _null_ ));
! DATA(insert ( 2142 n 0 timestamp_smaller 0 - timestamp_smaller - - - - - f f 2062 1114 0 0 0 _null_ _null_ ));
! DATA(insert ( 2143 n 0 timestamptz_smaller 0 - timestamptz_smaller - - - - - f f 1322 1184 0 0 0 _null_ _null_ ));
! DATA(insert ( 2144 n 0 interval_smaller 0 - interval_smaller - - - - - f f 1332 1186 0 0 0 _null_ _null_ ));
! DATA(insert ( 2145 n 0 text_smaller 0 - text_smaller - - - - - f f 664 25 0 0 0 _null_ _null_ ));
! DATA(insert ( 2146 n 0 numeric_smaller 0 - numeric_smaller - - - - - f f 1754 1700 0 0 0 _null_ _null_ ));
! DATA(insert ( 2051 n 0 array_smaller 0 - array_smaller - - - - - f f 1072 2277 0 0 0 _null_ _null_ ));
! DATA(insert ( 2245 n 0 bpchar_smaller 0 - bpchar_smaller - - - - - f f 1058 1042 0 0 0 _null_ _null_ ));
! DATA(insert ( 2798 n 0 tidsmaller 0 - tidsmaller - - - - - f f 2799 27 0 0 0 _null_ _null_ ));
! DATA(insert ( 3527 n 0 enum_smaller 0 - enum_smaller - - - - - f f 3518 3500 0 0 0 _null_ _null_ ));
! DATA(insert ( 3565 n 0 network_smaller 0 - network_smaller - - - - - f f 1203 869 0 0 0 _null_ _null_ ));
/* count */
! DATA(insert ( 2147 n 0 int8inc_any 0 - int8pl - - int8inc_any int8dec_any - f f 0 20 0 20 0 "0" "0" ));
! DATA(insert ( 2803 n 0 int8inc 0 - int8pl - - int8inc int8dec - f f 0 20 0 20 0 "0" "0" ));
! #define COUNTFNOID 2803
/* var_pop */
! DATA(insert ( 2718 n 0 int8_accum 0 numeric_var_pop numeric_combine numeric_serialize numeric_deserialize int8_accum int8_accum_inv numeric_var_pop f f 0 2281 128 2281 128 _null_ _null_ ));
! DATA(insert ( 2719 n 0 int4_accum 0 numeric_poly_var_pop numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 numeric_poly_var_pop numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 float8_var_pop float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2722 n 0 float8_accum 0 float8_var_pop float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2723 n 0 numeric_accum 0 numeric_var_pop numeric_combine numeric_serialize numeric_deserialize 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 0 numeric_var_samp numeric_combine numeric_serialize numeric_deserialize int8_accum int8_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
! DATA(insert ( 2642 n 0 int4_accum 0 numeric_poly_var_samp numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 numeric_poly_var_samp numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 float8_var_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2645 n 0 float8_accum 0 float8_var_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2646 n 0 numeric_accum 0 numeric_var_samp numeric_combine numeric_serialize numeric_deserialize 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 0 numeric_var_samp numeric_combine numeric_serialize numeric_deserialize int8_accum int8_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
! DATA(insert ( 2149 n 0 int4_accum 0 numeric_poly_var_samp numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 numeric_poly_var_samp numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 float8_var_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2152 n 0 float8_accum 0 float8_var_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2153 n 0 numeric_accum 0 numeric_var_samp numeric_combine numeric_serialize numeric_deserialize 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 0 numeric_stddev_pop numeric_combine numeric_serialize numeric_deserialize int8_accum int8_accum_inv numeric_stddev_pop f f 0 2281 128 2281 128 _null_ _null_ ));
! DATA(insert ( 2725 n 0 int4_accum 0 numeric_poly_stddev_pop numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 numeric_poly_stddev_pop numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 float8_stddev_pop float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2728 n 0 float8_accum 0 float8_stddev_pop float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2729 n 0 numeric_accum 0 numeric_stddev_pop numeric_combine numeric_serialize numeric_deserialize 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 0 numeric_stddev_samp numeric_combine numeric_serialize numeric_deserialize int8_accum int8_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
! DATA(insert ( 2713 n 0 int4_accum 0 numeric_poly_stddev_samp numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 numeric_poly_stddev_samp numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 float8_stddev_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2716 n 0 float8_accum 0 float8_stddev_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2717 n 0 numeric_accum 0 numeric_stddev_samp numeric_combine numeric_serialize numeric_deserialize 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 0 numeric_stddev_samp numeric_combine numeric_serialize numeric_deserialize int8_accum int8_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
! DATA(insert ( 2155 n 0 int4_accum 0 numeric_poly_stddev_samp numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 numeric_poly_stddev_samp numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 float8_stddev_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2158 n 0 float8_accum 0 float8_stddev_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2159 n 0 numeric_accum 0 numeric_stddev_samp numeric_combine numeric_serialize numeric_deserialize 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 0 - int8pl - - - - - f f 0 20 0 0 0 "0" _null_ ));
! DATA(insert ( 2819 n 0 float8_regr_accum 0 float8_regr_sxx float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2820 n 0 float8_regr_accum 0 float8_regr_syy float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2821 n 0 float8_regr_accum 0 float8_regr_sxy float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2822 n 0 float8_regr_accum 0 float8_regr_avgx float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2823 n 0 float8_regr_accum 0 float8_regr_avgy float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2824 n 0 float8_regr_accum 0 float8_regr_r2 float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2825 n 0 float8_regr_accum 0 float8_regr_slope float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2826 n 0 float8_regr_accum 0 float8_regr_intercept float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2827 n 0 float8_regr_accum 0 float8_covar_pop float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2828 n 0 float8_regr_accum 0 float8_covar_samp float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2829 n 0 float8_regr_accum 0 float8_corr float8_regr_combine - - - - - 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 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 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 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 0 - int2and - - - - - f f 0 21 0 0 0 _null_ _null_ ));
! DATA(insert ( 2237 n 0 int2or 0 - int2or - - - - - f f 0 21 0 0 0 _null_ _null_ ));
! DATA(insert ( 2238 n 0 int4and 0 - int4and - - - - - f f 0 23 0 0 0 _null_ _null_ ));
! DATA(insert ( 2239 n 0 int4or 0 - int4or - - - - - f f 0 23 0 0 0 _null_ _null_ ));
! DATA(insert ( 2240 n 0 int8and 0 - int8and - - - - - f f 0 20 0 0 0 _null_ _null_ ));
! DATA(insert ( 2241 n 0 int8or 0 - int8or - - - - - f f 0 20 0 0 0 _null_ _null_ ));
! DATA(insert ( 2242 n 0 bitand 0 - bitand - - - - - f f 0 1560 0 0 0 _null_ _null_ ));
! DATA(insert ( 2243 n 0 bitor 0 - bitor - - - - - f f 0 1560 0 0 0 _null_ _null_ ));
/* xml */
! DATA(insert ( 2901 n 0 xmlconcat2 0 - - - - - - - f f 0 142 0 0 0 _null_ _null_ ));
/* array */
! DATA(insert ( 2335 n 0 array_agg_transfn 0 array_agg_finalfn - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 4053 n 0 array_agg_array_transfn 0 array_agg_array_finalfn - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
/* text */
! DATA(insert ( 3538 n 0 string_agg_transfn 0 string_agg_finalfn - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
/* bytea */
! DATA(insert ( 3545 n 0 bytea_string_agg_transfn 0 bytea_string_agg_finalfn - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
/* json */
! DATA(insert ( 3175 n 0 json_agg_transfn 0 json_agg_finalfn - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3197 n 0 json_object_agg_transfn 0 json_object_agg_finalfn - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
/* jsonb */
! DATA(insert ( 3267 n 0 jsonb_agg_transfn 0 jsonb_agg_finalfn - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3270 n 0 jsonb_object_agg_transfn 0 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 0 percentile_disc_final - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3974 o 1 ordered_set_transition 0 percentile_cont_float8_final - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3976 o 1 ordered_set_transition 0 percentile_cont_interval_final - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3978 o 1 ordered_set_transition 0 percentile_disc_multi_final - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3980 o 1 ordered_set_transition 0 percentile_cont_float8_multi_final - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3982 o 1 ordered_set_transition 0 percentile_cont_interval_multi_final - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3984 o 0 ordered_set_transition 0 mode_final - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3986 h 1 ordered_set_transition_multi 0 rank_final - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3988 h 1 ordered_set_transition_multi 0 percent_rank_final - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3990 h 1 ordered_set_transition_multi 0 cume_dist_final - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3992 h 1 ordered_set_transition_multi 0 dense_rank_final - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
/*
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
new file mode 100644
index aeb7927..3095380
*** a/src/include/catalog/pg_operator.h
--- b/src/include/catalog/pg_operator.h
*************** DATA(insert OID = 685 ( "-" PGNSP PG
*** 547,552 ****
--- 547,553 ----
DESCR("subtract");
DATA(insert OID = 686 ( "*" PGNSP PGUID b f f 20 20 20 686 0 int8mul - - ));
DESCR("multiply");
+ #define OPERATOR_INT8MUL 686
DATA(insert OID = 687 ( "/" PGNSP PGUID b f f 20 20 20 0 0 int8div - - ));
DESCR("divide");
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
new file mode 100644
index 37e022d..51d1b74
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("pg_controldata recovery state inf
*** 5345,5350 ****
--- 5345,5353 ----
DATA(insert OID = 3444 ( pg_control_init PGNSP PGUID 12 1 0 0 0 f f f f t f v s 0 0 2249 "" "{23,23,23,23,23,23,23,23,23,16,16,16,23}" "{o,o,o,o,o,o,o,o,o,o,o,o,o}" "{max_data_alignment,database_block_size,blocks_per_segment,wal_block_size,bytes_per_wal_segment,max_identifier_length,max_index_columns,max_toast_chunk_size,large_object_chunk_size,bigint_timestamps,float4_pass_by_value,float8_pass_by_value,data_page_checksum_version}" _null_ _null_ pg_control_init _null_ _null_ _null_ ));
DESCR("pg_controldata init state information as a function");
+ DATA(insert OID = 4002 ( float4_sum_mul PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 1022 "1022 20" _null_ _null_ _null_ _null_ _null_ float4_sum_mul _null_ _null_ _null_ ));
+ DESCR("aggregate state multiplication function");
+
/*
* Symbolic values for provolatile column: these indicate whether the result
* of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
new file mode 100644
index f72ec24..dd76d9b
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
*************** typedef struct Aggref
*** 284,289 ****
--- 284,290 ----
bool aggvariadic; /* true if variadic arguments have been
* combined into an array last argument */
char aggkind; /* aggregate kind (see pg_aggregate.h) */
+ Oid aggtransmultifn;/* pg_aggregate(aggtransmultifn) */
Index agglevelsup; /* > 0 if agg belongs to outer query */
AggSplit aggsplit; /* expected agg-splitting mode of parent Agg */
int location; /* token location, or -1 if unknown */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
new file mode 100644
index e1d31c7..1e8ef3b
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct PlannerInfo
*** 252,257 ****
--- 252,262 ----
List *placeholder_list; /* list of PlaceHolderInfos */
+ /* TODO Consider hashing. */
+ List *grouped_var_list; /* List of GroupedVarInfos. */
+
+ bool all_baserels_grouped; /* Should grouped rels be joined? */
+
List *fkey_list; /* list of ForeignKeyOptInfos */
List *query_pathkeys; /* desired pathkeys for query_planner() */
*************** typedef struct RelOptInfo
*** 495,504 ****
--- 500,516 ----
/* default result targetlist for Paths scanning this relation */
struct PathTarget *reltarget; /* list of Vars/Exprs, cost, width */
+ /* result targetlist if the base relation is grouped */
+ struct PathTarget *reltarget_grouped;
+
/* materialization information */
List *pathlist; /* Path structures */
+ List *grouped_pathlist; /* paths with partial aggregation already
+ * done. */
List *ppilist; /* ParamPathInfos used in pathlist */
List *partial_pathlist; /* partial Paths */
+ List *partial_grouped_pathlist; /* partial Paths with partial
+ * aggregation already done. */
struct Path *cheapest_startup_path;
struct Path *cheapest_total_path;
struct Path *cheapest_unique_path;
*************** typedef struct PlaceHolderInfo
*** 1905,1910 ****
--- 1917,1951 ----
} PlaceHolderInfo;
/*
+ * Variable of a "grouped relation" represents an aggregate or grouping
+ * expression that it evaluates. Input values of such expressions are not
+ * available above this relation, so the values of such expressions are passed
+ * as variables. Each variable-to-expression association is stored as an item
+ * of root->grouped_var_list and used by set_upper_references.
+ */
+ typedef struct GroupedVarInfo
+ {
+ /* The variable added to base relation instead of aggregate. */
+ Var *var;
+
+ /*
+ * The expression whose value this "grouped var" represents in the
+ * target list of the (partially) grouped relation.
+ */
+ Expr *expr;
+
+ /*
+ * If aggtransmultifn function (see Aggref) must be applied before the
+ * final aggregation, expr_intermediate contains the function call. It'll
+ * replace the variable in targetlist of the Result node that prepares
+ * input for the final aggregation.
+ */
+ Expr *expr_intermediate;
+
+ Index sortgroupref;
+ } GroupedVarInfo;
+
+ /*
* This struct describes one potentially index-optimizable MIN/MAX aggregate
* function. MinMaxAggPath contains a list of these, and if we accept that
* path, the list is stored into root->minmax_aggs for use during setrefs.c.
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
new file mode 100644
index d16f879..54b84c7
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern int compare_path_costs(Path *path
*** 25,37 ****
extern int compare_fractional_path_costs(Path *path1, Path *path2,
double fraction);
extern void set_cheapest(RelOptInfo *parent_rel);
! extern void add_path(RelOptInfo *parent_rel, Path *new_path);
extern bool add_path_precheck(RelOptInfo *parent_rel,
Cost startup_cost, Cost total_cost,
! List *pathkeys, Relids required_outer);
! extern void add_partial_path(RelOptInfo *parent_rel, Path *new_path);
extern bool add_partial_path_precheck(RelOptInfo *parent_rel,
! Cost total_cost, List *pathkeys);
extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer, int parallel_workers);
--- 25,39 ----
extern int compare_fractional_path_costs(Path *path1, Path *path2,
double fraction);
extern void set_cheapest(RelOptInfo *parent_rel);
! extern void add_path(RelOptInfo *parent_rel, Path *new_path, bool grouped);
extern bool add_path_precheck(RelOptInfo *parent_rel,
Cost startup_cost, Cost total_cost,
! List *pathkeys, Relids required_outer, bool grouped);
! extern void add_partial_path(RelOptInfo *parent_rel, Path *new_path,
! bool grouped);
extern bool add_partial_path_precheck(RelOptInfo *parent_rel,
! Cost total_cost, List *pathkeys,
! bool grouped);
extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer, int parallel_workers);
*************** extern HashPath *create_hashjoin_path(Pl
*** 134,140 ****
Path *inner_path,
List *restrict_clauses,
Relids required_outer,
! List *hashclauses);
extern ProjectionPath *create_projection_path(PlannerInfo *root,
RelOptInfo *rel,
--- 136,143 ----
Path *inner_path,
List *restrict_clauses,
Relids required_outer,
! List *hashclauses,
! bool grouped);
extern ProjectionPath *create_projection_path(PlannerInfo *root,
RelOptInfo *rel,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
new file mode 100644
index 480f25f..0d29bec
*** a/src/include/optimizer/paths.h
--- b/src/include/optimizer/paths.h
*************** extern void set_dummy_rel_pathlist(RelOp
*** 52,58 ****
extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,
List *initial_rels);
! extern void generate_gather_paths(PlannerInfo *root, RelOptInfo *rel);
#ifdef OPTIMIZER_DEBUG
extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel);
--- 52,59 ----
extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,
List *initial_rels);
! extern void generate_gather_paths(PlannerInfo *root, RelOptInfo *rel,
! bool grouped);
#ifdef OPTIMIZER_DEBUG
extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
new file mode 100644
index 8468b0c..dd2529b
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
*************** extern void add_base_rels_to_query(Plann
*** 75,80 ****
--- 75,81 ----
extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
Relids where_needed, bool create_new_ph);
+ extern void build_base_rel_tlists_grouped(PlannerInfo *root);
extern void find_lateral_references(PlannerInfo *root);
extern void create_lateral_join_info(PlannerInfo *root);
extern List *deconstruct_jointree(PlannerInfo *root);
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
new file mode 100644
index f80b31a..cb9d0c0
*** a/src/include/optimizer/tlist.h
--- b/src/include/optimizer/tlist.h
*************** extern void add_column_to_pathtarget(Pat
*** 61,67 ****
extern void add_new_column_to_pathtarget(PathTarget *target, Expr *expr);
extern void add_new_columns_to_pathtarget(PathTarget *target, List *exprs);
extern void apply_pathtarget_labeling_to_tlist(List *tlist, PathTarget *target);
!
/* Convenience macro to get a PathTarget with valid cost/width fields */
#define create_pathtarget(root, tlist) \
set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
--- 61,70 ----
extern void add_new_column_to_pathtarget(PathTarget *target, Expr *expr);
extern void add_new_columns_to_pathtarget(PathTarget *target, List *exprs);
extern void apply_pathtarget_labeling_to_tlist(List *tlist, PathTarget *target);
! extern PathTarget *create_intermediate_grouping_target(PlannerInfo *root,
! PathTarget *src);
! extern List *restore_grouping_expressions(PlannerInfo *root, List *src);
! extern Expr *find_grouped_var_expr(PlannerInfo *root, Var *var);
/* Convenience macro to get a PathTarget with valid cost/width fields */
#define create_pathtarget(root, tlist) \
set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
new file mode 100644
index e1bb344..628b142
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum float84le(PG_FUNCTION_ARGS)
*** 475,480 ****
--- 475,481 ----
extern Datum float84gt(PG_FUNCTION_ARGS);
extern Datum float84ge(PG_FUNCTION_ARGS);
extern Datum width_bucket_float8(PG_FUNCTION_ARGS);
+ extern Datum float4_sum_mul(PG_FUNCTION_ARGS);
/* dbsize.c */
extern Datum pg_tablespace_size_oid(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
new file mode 100644
index b43dae7..39f5dee
*** a/src/include/utils/selfuncs.h
--- b/src/include/utils/selfuncs.h
*************** extern double estimate_num_groups(Planne
*** 232,237 ****
--- 232,240 ----
extern Selectivity estimate_hash_bucketsize(PlannerInfo *root, Node *hashkey,
double nbuckets);
+ extern Size estimate_hashagg_tablesize(Path *path,
+ const AggClauseCosts *agg_costs,
+ double dNumGroups);
extern List *deconstruct_indexquals(IndexPath *path);
extern void genericcostestimate(PlannerInfo *root, IndexPath *path,
On Mon, Jan 9, 2017 at 12:56 PM, Antonin Houska <ah@cybertec.at> wrote:
Attached is a draft patch that lets partial aggregation happen at base
relation level. If the relations contain relatively small number of groups,
the number of input rows of the aggregation at the query level can be reduced
this way. Also, if append relation and postgres_fdw planning is enhanced
accordingly, patch like this can let us aggregate individual tables on remote
servers (e.g. shard nodes) and thus reduce the amount of rows subject to the
final aggregation.
Very interesting. I don't have time to study this in detail right
now, but as a concept it seems worthwhile. I think the trick is
figuring out at which levels of the path tree it makes sense to
consider partial aggregation.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Jan 9, 2017 at 5:56 PM, Antonin Houska <ah@cybertec.at> wrote:
Attached is a draft patch that lets partial aggregation happen at base
relation level. If the relations contain relatively small number of groups,
the number of input rows of the aggregation at the query level can be
reduced
this way. Also, if append relation and postgres_fdw planning is enhanced
accordingly, patch like this can let us aggregate individual tables on
remote
servers (e.g. shard nodes) and thus reduce the amount of rows subject to
the
final aggregation.For example, consider query
SELECT b.j, sum(a.x) FROM a, b WHERE a.i = b.j GROUP BY b.j;
and tables "a"
i | x
-------
1 | 3
1 | 4and "b"
j
---
1
1
The example should have j= 1,2 , right?
and "b"
j
---
1
2
The base relations grouped look like
i | sum(a.x)| count(*)
-----------------------
1 | 7 | 2
Otherwise, the sum and count would be 14 and 4.
and
j | count(*)
-------------
1 | 2
Pantelis
On Tue, Jan 10, 2017 at 6:52 PM, Pantelis Theodosiou <ypercube@gmail.com>
wrote:
On Mon, Jan 9, 2017 at 5:56 PM, Antonin Houska <ah@cybertec.at> wrote:
Attached is a draft patch that lets partial aggregation happen at base
relation level. If the relations contain relatively small number of
groups,
the number of input rows of the aggregation at the query level can be
reduced
this way. Also, if append relation and postgres_fdw planning is enhanced
accordingly, patch like this can let us aggregate individual tables on
remote
servers (e.g. shard nodes) and thus reduce the amount of rows subject to
the
final aggregation.For example, consider query
SELECT b.j, sum(a.x) FROM a, b WHERE a.i = b.j GROUP BY b.j;
and tables "a"
i | x
-------
1 | 3
1 | 4and "b"
j
---
1
1The example should have j= 1,2 , right?
and "b"
j
---
1
2The base relations grouped look like
i | sum(a.x)| count(*)
-----------------------
1 | 7 | 2Otherwise, the sum and count would be 14 and 4.
and
j | count(*)
-------------
1 | 2Pantelis
Or perhaps I should be reading more carefully the whole mail before
posting. Ignore the previous.
On Mon, Jan 9, 2017 at 11:26 PM, Antonin Houska <ah@cybertec.at> wrote:
Attached is a draft patch that lets partial aggregation happen at base
relation level. If the relations contain relatively small number of groups,
the number of input rows of the aggregation at the query level can be reduced
this way. Also, if append relation and postgres_fdw planning is enhanced
accordingly, patch like this can let us aggregate individual tables on remote
servers (e.g. shard nodes) and thus reduce the amount of rows subject to the
final aggregation.
For an appendrel probably you need an ability to switch group->append
into append->group. For postgres_fdw, we already support aggregate
pushdown. But we don't support fetching partial aggregates from
foreign server. What other enhancements do you need?
For example, consider query
SELECT b.j, sum(a.x) FROM a, b WHERE a.i = b.j GROUP BY b.j;
and tables "a"
i | x
-------
1 | 3
1 | 4and "b"
j
---
1
1The base relations grouped look like
i | sum(a.x)| count(*)
-----------------------
1 | 7 | 2and
j | count(*)
-------------
1 | 2
Looks like an interesting technique.
A few "footnotes":
* Equivalence class {a.i, b.j} tells that "a" can be grouped by "i", besides
the grouping of "b" which is explicitly required by GROUP BY b.j clause.)* To transfer the aggregate results to upper nodes, I introduced a concept of
"grouped variable". Base relation has special target which the planner uses to
generate "grouped paths". The grouped target contains one grouped variable per
aggregate that the relation computes. During final processing of the plan
(setrefs.c), the corresponding (partial) aggregate is restored in the query
target if needed - typically this happens to ensure that the final aggregate
references the output of the partial aggregate.* So far the grouped variable is only used for aggregates, but it might be
useful for grouping expressions in the future as well. Currently the base
relation can only be grouped by a plain Var, but it might be worth grouping it
by generic grouping expressions of the GROUP BY clause, and using the grouped
var mechanism to propagate the expression value to the query target.As for the example, the processing continues by joining the partially grouped
sets:i | sum(x)| count(i.*) | j | count(j.*)
----------------------------------------
1 | 7 | 2 | 1 | 3Before performing the final aggregation, we need to multiply sum(a.x) by
count(j.*) because w/o the aggregation at base relation level the input
of the query-level aggregation would look likea.i | a.x | b.j
----------------
1 | 3 | 1
1 | 4 | 1
1 | 3 | 1
1 | 4 | 1In other words, grouping of the base relation "b" below the join prevents the
join from bringing per-group input set to the aggregate input multiple
times. To compensate for this effect, I've added a new field "aggtransmultifn"
to pg_aggregate catalog. It "multiplies" the aggregate state w/o repeated
processing of the same input set many times. sum() is an example of an
aggregate that needs such processing, avg() is one that does not.
For something like product aggregation, where the product (or higher
order operations) across rows is accumulated instead of sum, mere
multiplication wouldn't help. We will need some higher order operation
to "extrapolate"
the result based on count(j.*). In fact, the multiplication factor
will depend upon the number of relations being joined E.g.
select b.j, sum(a.x) where a, b, c where a.i = b.j and a.i = c.k group by b.j
The example query can eventually produce plans like
QUERY PLAN
------------------------------------------------------
Finalize HashAggregate
Group Key: b.j
-> Gather
Workers Planned: 2
-> Hash Join
Hash Cond: (a.i = b.j)
-> Partial HashAggregate
Group Key: a.i
-> Parallel Seq Scan on a
-> Hash
-> Partial HashAggregate
Group Key: b.j
-> Parallel Seq Scan on bor
QUERY PLAN
------------------------------------------------------
Finalize HashAggregate
Group Key: b.j
-> Hash Join
Hash Cond: (a.i = b.j)
-> Gather
Workers Planned: 2
-> Partial HashAggregate
Group Key: a.i
-> Parallel Seq Scan on a
-> Hash
-> Gather
Workers Planned: 1
-> Partial HashAggregate
Group Key: b.j
-> Parallel Seq Scan on bAn obvious limitation is that neither grouping expression nor aggregate
argument can be below the nullable side of outer join. In such a case the
aggregate at the base relation level wouldn't receive the NULL values that it
does receive at the query level. Also, no aggregate can reference multiple
tables.Does this concept seem worth to continue coding?
May be we want to implement this technique without partial aggregation
first i.e. push down aggregation and grouping down the join tree and
then add partial aggregation steps. That might make it easy to review.
Looking at the changes in create_plain_partial_paths(), it looks like
we use this technique only in case of parallel query. I think the
technique is useful otherwise as well.
Also, if we could generalize this technique to push
aggregation/grouping upto any relation (not just base but join as
well) where it can be calculated that may be better. Trying that might
lead us to a better design; which right now is focused only on base
relations.
BTW, if anyone wants to play with the current version:
1. Don't forget to initialize a new cluster (initdb) first. I decided not to
bump CATALOG_VERSION_NO so far because it'd probably make the patch
incompatible with master branch quite soon.2. Only hash aggregation is implemented so far at the base relation level.
3. As for sum() aggregate, only sum(float4) is supposed to work correctly so
far - this is related to the pg_aggregate changes mentioned above. avg()
should work in general, and I didn't care about the other ones so far.4. As for joins, only hash join is able to process the grouped relations. I
didn't want to do too much coding until there's a consensus on the design.
Probably it's too early to review code, but ...
+ /*
+ * If no join is expected, aggregation at base relation level makes no
+ * sense. XXX Is there simpler way to find out? (We're not interested in
+ * RELOPT_OTHER_MEMBER_REL, so simple_rel_array_size does not help.)
+ */
+ for (i = 1; i < root->simple_rel_array_size; i++)
+ {
+ RelOptInfo *rel;
+
+ rel = find_base_rel(root, i);
+ if (rel->reloptkind == RELOPT_BASEREL)
+ {
+ nbaserels++;
+ /*
+ * We only want to know whether the number of relations is greater
+ * than one.
+ */
+ if (nbaserels > 1)
+ break;
+ }
+ }
You might want to check bms_membership(root->all_baserels), instead of
this loop.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote:
On Mon, Jan 9, 2017 at 11:26 PM, Antonin Houska <ah@cybertec.at> wrote:
Attached is a draft patch that lets partial aggregation happen at base
relation level. If the relations contain relatively small number of groups,
the number of input rows of the aggregation at the query level can be reduced
this way. Also, if append relation and postgres_fdw planning is enhanced
accordingly, patch like this can let us aggregate individual tables on remote
servers (e.g. shard nodes) and thus reduce the amount of rows subject to the
final aggregation.
For an appendrel probably you need an ability to switch group->append into
append->group
Yes, like the new patch version (see attachment) does:
postgres=# EXPLAIN (COSTS false) SELECT b.j, sum(a.x) FROM a JOIN b ON a.i = b.j GROUP BY b.j;
QUERY PLAN
------------------------------------------------------
Finalize HashAggregate
Group Key: b.j
-> Hash Join
Hash Cond: (a.i = b.j)
-> Append
-> Partial HashAggregate
Group Key: a.i
-> Seq Scan on a
-> Partial HashAggregate
Group Key: a_1.i
-> Seq Scan on a_1
-> Partial HashAggregate
Group Key: a_2.i
-> Seq Scan on a_2
-> Hash
-> Gather
Workers Planned: 1
-> Partial HashAggregate
Group Key: b.j
-> Parallel Seq Scan on b
For postgres_fdw, we already support aggregate pushdown.
My understanding is that currently it only works if the whole query can be
evaluated by the FDW. What I try to do is to push down aggregation of
individual table, and join the partially-aggregated set with other tables,
which are not necessarily remote or reside on different remote server.
But we don't support fetching partial aggregates from foreign server. What
other enhancements do you need?
Here I try to introduce infrastructure for aggregation pushdown and
propagation of the transient aggregate state values from base relations to the
final join. postgres_fdw can benefit from it but it's not the only use case,
so I'd prefer adjusting it in a separate patch.
Yes, an outstanding problem is that the remote nodes need to return transient
state values - probably using bytea type. I think this functionality should
even be separate from postgres_fdw (e.g. a new contrib module?), because the
remote nodes do not need postgres_fdw.
A few "footnotes":
As for the example, the processing continues by joining the partially grouped
sets:i | sum(x)| count(i.*) | j | count(j.*)
----------------------------------------
1 | 7 | 2 | 1 | 2
[ Sorry, count(j.*) should be 2, not 3 as I wrote in the initial email. ]
Before performing the final aggregation, we need to multiply sum(a.x) by
count(j.*) because w/o the aggregation at base relation level the input
of the query-level aggregation would look likea.i | a.x | b.j
----------------
1 | 3 | 1
1 | 4 | 1
1 | 3 | 1
1 | 4 | 1In other words, grouping of the base relation "b" below the join prevents the
join from bringing per-group input set to the aggregate input multiple
times. To compensate for this effect, I've added a new field "aggtransmultifn"
to pg_aggregate catalog. It "multiplies" the aggregate state w/o repeated
processing of the same input set many times. sum() is an example of an
aggregate that needs such processing, avg() is one that does not.For something like product aggregation, where the product (or higher order
operations) across rows is accumulated instead of sum, mere multiplication
wouldn't help. We will need some higher order operation to "extrapolate" the
result based on count(j.*). In fact, the multiplication factor will depend
upon the number of relations being joined E.g. select b.j, sum(a.x) where
a, b, c where a.i = b.j and a.i = c.k group by b.j
Maybe you're saying what I already try to do. Let me modify the example
accordingly (unlike the initial example, each table produces a group of
different size so the the count() values are harder to confuse):
Table "a":
i | x
-------
1 | 3
1 | 4
Table "b":
j | y
-------
1 | 10
1 | 20
1 | 30
Table "c":
k | z
---------
1 | 100
1 | 200
1 | 300
1 | 400
Your query:
SELECT b.j, sum(a.x)
FROM a, b, c
WHERE a.i = b.j AND a.i = c.k
GROUP BY b.j
The tables grouped:
i | sum(a.x) | count(a.*)
---+-----------------------
1 | 7 | 2
j | sum(b.y) | count(b.*)
---+-----------------------
1 | 60 | 3
j | sum(c.z) | count(c.*)
---+-----------------------
1 | 1000 | 4
Grouped (partially) and joined (table names omitted somewhere so that the
table width does not exceed 80 characters):
i | sum(x) | count(a.*) | j | sum(y) | count(b.*) | k | sum(z) | count(c.*)
---+-------------------------------------------------------------------------
1 | 7 | 2 | 1 | 60 | 3 | k | 1000 | 4
For sum(a.x), the input for final aggregation is the partial sum(x) multiplied
by the count(b.*) and also by count(c.*), because grouping of both "b" and "c"
reduced the number of times the input values of 3 and 4 arrived to the
aggregate node. Thus 7 * 3 * 4 = 84.
Likewise, sum(y) * count(a.*) * count(c.*) = 60 * 2 * 4 = 480
and finally, sum(z) * count(a.*) * count(b.*) = 1000 * 2 * 3 = 6000
I get exactly these values when I run your query on master branch w/o my
patch, so my theory could be correct :-)
May be we want to implement this technique without partial aggregation first
i.e. push down aggregation and grouping down the join tree and then add
partial aggregation steps. That might make it easy to review. Looking at
the changes in create_plain_partial_paths(), it looks like we use this
technique only in case of parallel query. I think the technique is useful
otherwise as well.
The term "partial" is actually ambiguous. Sometimes it refers to "partial
path", i.e. path that can be executed by multiple workers, sometimes "partial
aggregate", i.e. aggregate that produces the transient state instead of the
final values.
The fact that partial (i.e. parallel) path was necessary to push aggregation
down was rather a limitation of the initial version of the patch. The current
version can push the aggregation down even w/o parallelism - see
create_plain_partial_paths() -> create_plain_grouped_path().
Also, if we could generalize this technique to push aggregation/grouping
upto any relation (not just base but join as well) where it can be
calculated that may be better. Trying that might lead us to a better design;
which right now is focused only on base relations.
Yes, that's true. Currently this patch is unable to process queries where an
aggregate references multiple tables. It should be possible to apply the
"grouped path" to a join.
BTW, if anyone wants to play with the current version:
1. Don't forget to initialize a new cluster (initdb) first. I decided not to
bump CATALOG_VERSION_NO so far because it'd probably make the patch
incompatible with master branch quite soon.2. Only hash aggregation is implemented so far at the base relation level.
3. As for sum() aggregate, only sum(float4) is supposed to work correctly so
far - this is related to the pg_aggregate changes mentioned above. avg()
should work in general, and I didn't care about the other ones so far.4. As for joins, only hash join is able to process the grouped relations. I
didn't want to do too much coding until there's a consensus on the design.
Probably it's too early to review code, but ...
Thank's for doing so anyway!
+ /* + * If no join is expected, aggregation at base relation level makes no + * sense. XXX Is there simpler way to find out? (We're not interested in + * RELOPT_OTHER_MEMBER_REL, so simple_rel_array_size does not help.) + */ + for (i = 1; i < root->simple_rel_array_size; i++) + { + RelOptInfo *rel; + + rel = find_base_rel(root, i); + if (rel->reloptkind == RELOPT_BASEREL) + { + nbaserels++; + /* + * We only want to know whether the number of relations is greater + * than one. + */ + if (nbaserels > 1) + break; + } + }
You might want to check bms_membership(root->all_baserels), instead of
this loop.
I liked this idea, but when tried it, I found out that all_baserels gets
initialized much later. And when moved the initialization to
add_base_rels_to_query I found out that the result is not the same
(RELOPT_DEADREL relations) make difference. Thanks for your suggestion anyway.
--
Antonin Houska
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt
Web: http://www.postgresql-support.de, http://www.cybertec.at
Attachments:
grouped_base_rel_v2.difftext/x-diffDownload
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index 930f2f1..59836ba
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyAggref(const Aggref *from)
*** 1245,1250 ****
--- 1245,1251 ----
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(aggvariadic);
COPY_SCALAR_FIELD(aggkind);
+ COPY_SCALAR_FIELD(aggtransmultifn);
COPY_SCALAR_FIELD(agglevelsup);
COPY_SCALAR_FIELD(aggsplit);
COPY_LOCATION_FIELD(location);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index 806d0a9..8e789fe
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outPlannerInfo(StringInfo str, const Pl
*** 2046,2051 ****
--- 2046,2053 ----
WRITE_NODE_FIELD(append_rel_list);
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(placeholder_list);
+ WRITE_NODE_FIELD(grouped_var_list);
+ WRITE_BOOL_FIELD(all_baserels_grouped);
WRITE_NODE_FIELD(fkey_list);
WRITE_NODE_FIELD(query_pathkeys);
WRITE_NODE_FIELD(group_pathkeys);
diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c
new file mode 100644
index b5cab0c..f89406d
*** a/src/backend/optimizer/geqo/geqo_eval.c
--- b/src/backend/optimizer/geqo/geqo_eval.c
*************** merge_clump(PlannerInfo *root, List *clu
*** 265,271 ****
if (joinrel)
{
/* Create GatherPaths for any useful partial paths for rel */
! generate_gather_paths(root, joinrel);
/* Find and save the cheapest paths for this joinrel */
set_cheapest(joinrel);
--- 265,271 ----
if (joinrel)
{
/* Create GatherPaths for any useful partial paths for rel */
! generate_gather_paths(root, joinrel, false);
/* Find and save the cheapest paths for this joinrel */
set_cheapest(joinrel);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
new file mode 100644
index 46d7d06..46a6d18
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
***************
*** 20,29 ****
--- 20,35 ----
#include "access/sysattr.h"
#include "access/tsmapi.h"
+ /*
+ * TODO Consider moving COUNTFNOID away from pg_aggregate.h so it doesn't
+ * have to be included.
+ */
+ #include "catalog/pg_aggregate.h"
#include "catalog/pg_class.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "foreign/fdwapi.h"
+ #include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#ifdef OPTIMIZER_DEBUG
***************
*** 44,50 ****
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
!
/* results of subquery_is_pushdown_safe */
typedef struct pushdown_safety_info
--- 50,56 ----
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
! #include "utils/selfuncs.h"
/* results of subquery_is_pushdown_safe */
typedef struct pushdown_safety_info
*************** static void set_rel_pathlist(PlannerInfo
*** 76,81 ****
--- 82,89 ----
static void set_plain_rel_size(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static void create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel);
+ static void create_plain_grouped_path(PlannerInfo *root, RelOptInfo *rel,
+ Path *subpath, bool partial);
static void set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static void set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
*************** RelOptInfo *
*** 137,143 ****
make_one_rel(PlannerInfo *root, List *joinlist)
{
RelOptInfo *rel;
! Index rti;
/*
* Construct the all_baserels Relids set.
--- 145,151 ----
make_one_rel(PlannerInfo *root, List *joinlist)
{
RelOptInfo *rel;
! Index rti;
/*
* Construct the all_baserels Relids set.
*************** make_one_rel(PlannerInfo *root, List *jo
*** 151,157 ****
if (brel == NULL)
continue;
! Assert(brel->relid == rti); /* sanity check on array */
/* ignore RTEs that are "other rels" */
if (brel->reloptkind != RELOPT_BASEREL)
--- 159,165 ----
if (brel == NULL)
continue;
! Assert(brel->relid == rti); /* sanity check on array */
/* ignore RTEs that are "other rels" */
if (brel->reloptkind != RELOPT_BASEREL)
*************** set_rel_pathlist(PlannerInfo *root, RelO
*** 457,463 ****
* we'll consider gathering partial paths for the parent appendrel.)
*/
if (rel->reloptkind == RELOPT_BASEREL)
! generate_gather_paths(root, rel);
/*
* Allow a plugin to editorialize on the set of Paths for this base
--- 465,474 ----
* we'll consider gathering partial paths for the parent appendrel.)
*/
if (rel->reloptkind == RELOPT_BASEREL)
! {
! generate_gather_paths(root, rel, false);
! generate_gather_paths(root, rel, true);
! }
/*
* Allow a plugin to editorialize on the set of Paths for this base
*************** static void
*** 647,652 ****
--- 658,664 ----
set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
Relids required_outer;
+ Path *seq_path;
/*
* We don't support pushing join clauses into the quals of a seqscan, but
*************** set_plain_rel_pathlist(PlannerInfo *root
*** 655,664 ****
*/
required_outer = rel->lateral_relids;
! /* Consider sequential scan */
! add_path(rel, create_seqscan_path(root, rel, required_outer, 0));
! /* If appropriate, consider parallel sequential scan */
if (rel->consider_parallel && required_outer == NULL)
create_plain_partial_paths(root, rel);
--- 667,679 ----
*/
required_outer = rel->lateral_relids;
! /* Consider sequential scan, both plain and grouped. */
! seq_path = create_seqscan_path(root, rel, required_outer, 0);
! add_path(rel, seq_path, false);
! if (rel->reltarget_grouped != NULL && required_outer == NULL)
! create_plain_grouped_path(root, rel, seq_path, false);
! /* If appropriate, consider parallel sequential scan (plain or grouped) */
if (rel->consider_parallel && required_outer == NULL)
create_plain_partial_paths(root, rel);
*************** static void
*** 677,682 ****
--- 692,698 ----
create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel)
{
int parallel_workers;
+ Path *path;
/*
* If the user has set the parallel_workers reloption, use that; otherwise
*************** create_plain_partial_paths(PlannerInfo *
*** 727,733 ****
return;
/* Add an unordered partial path based on a parallel sequential scan. */
! add_partial_path(rel, create_seqscan_path(root, rel, NULL, parallel_workers));
}
/*
--- 743,902 ----
return;
/* Add an unordered partial path based on a parallel sequential scan. */
! path = create_seqscan_path(root, rel, NULL, parallel_workers);
! add_partial_path(rel, path, false);
!
! /*
! * Do partial aggregation at base relation level if the relation is
! * eligible for it. (And if we can expect that the final join can be built
! * out of grouped rels.)
! */
! if (root->all_baserels_grouped && rel->reltarget_grouped != NULL)
! create_plain_grouped_path(root, rel, path, true);
! }
!
! /*
! * Apply (partial) aggregation to a subpath.
! *
! * "partial" argument tells whether both subpath and the resulting path are
! * partial or not.
! *
! * As we modify the subpath here, copy is taken before we adjust it.
! */
! static void
! create_plain_grouped_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
! bool partial)
! {
! Query *parse = root->parse;
! ListCell *lc;
! Expr *texpr;
! AggClauseCosts agg_costs;
! AggPath *agg_path;
! List *group_clause = NIL;
! List *group_exprs = NIL;
! List *agg_exprs = NIL;
! bool can_hash;
! Size hashaggtablesize;
! int i;
! double dNumGroups;
! Path *subpath_tmp;
! PathTarget *target_grouped = rel->reltarget_grouped;
!
! /* Copy the subpath before doing changes. */
! subpath_tmp = makeNode(Path);
! memcpy(subpath_tmp, subpath, sizeof(Path));
! subpath = subpath_tmp;
!
! /* The "grouped target" should contain grouping expressions, and these
! * should have non-zero sortgroupref.
! */
! Assert(target_grouped->sortgrouprefs != NULL);
!
! /*
! * Find one grouping clause per grouping column and make sure
! * subpath->pathtarget entries have sortgroupref initialized.
! */
! i = 0;
! foreach(lc, target_grouped->exprs)
! {
! Index sortgroupref;
! SortGroupClause *cl;
!
! texpr = (Expr *) lfirst(lc);
! sortgroupref = target_grouped->sortgrouprefs[i++];
!
! if (sortgroupref == 0)
! {
! /*
! * Looks like "grouped var" representing Aggref - these should
! * appear at the end of the list.
! */
! break;
! }
!
! /*
! * Find the clause by sortgroupref.
! *
! * All that create_agg_plan eventually needs of the clause is
! * tleSortGroupRef, so we don't have to care that the clause
! * expression might differ from texpr, in case texpr was derived from
! * EC.
! */
! cl = get_sortgroupref_clause(sortgroupref, root->parse->groupClause);
! group_clause = lappend(group_clause, cl);
! group_exprs = lappend(group_exprs, texpr);
! }
!
! /* Now collect the aggregates. */
! while (lc != NULL)
! {
! texpr = (Expr *) lfirst(lc);
!
! /*
! * texpr still contains the replacement var, so restore the actual
! * Aggref expression.
! */
! Assert(IsA(texpr, Var));
! agg_exprs = lappend(agg_exprs, find_grouped_var_expr(root,
! (Var *) texpr));
! lc = lnext(lc);
! }
!
! Assert(agg_exprs != NIL);
! MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
! get_agg_clause_costs(root, (Node *) agg_exprs, AGGSPLIT_INITIAL_SERIAL,
! &agg_costs);
!
! can_hash = (parse->groupClause != NIL &&
! parse->groupingSets == NIL &&
! agg_costs.numOrderedAggs == 0 &&
! grouping_is_hashable(parse->groupClause));
!
! /* TODO Consider other kinds of aggregation. */
! if (!can_hash)
! return;
!
! Assert(group_exprs != NIL);
! dNumGroups = estimate_num_groups(root, group_exprs, subpath->rows, NULL);
!
! hashaggtablesize = estimate_hashagg_tablesize(subpath, &agg_costs,
! dNumGroups);
!
! if (hashaggtablesize < work_mem * 1024L)
! {
! /*
! * Create the partial aggregation path.
! *
! * Note that target contains "grouped vars" (see GroupedVarInfo)
! * instead of Aggref expressions so far. Thus set_upper_references can
! * easily link TLEs of the upper node (which is not necessarily the
! * final Agg node) to the output of the patial Agg plan. Once
! * descended to this partial Agg node, set_upper_references will
! * install the Aggref expressions.
! */
! Assert(group_clause != NIL);
! agg_path = create_agg_path(root,
! rel,
! subpath,
! target_grouped,
! AGG_HASHED,
! AGGSPLIT_INITIAL_SERIAL,
! group_clause,
! NIL,
! &agg_costs,
! dNumGroups);
!
! /*
! * The agg path should require no fewer parameters than the plain one.
! */
! agg_path->path.param_info = subpath->param_info;
!
! /* Finally add the grouped path to the list of grouped base paths. */
! if (!partial)
! add_path(rel, (Path *) agg_path, true);
! else
! add_partial_path(rel, (Path *) agg_path, true);
! }
}
/*
*************** set_tablesample_rel_pathlist(PlannerInfo
*** 813,819 ****
path = (Path *) create_material_path(rel, path);
}
! add_path(rel, path);
/* For the moment, at least, there are no other paths to consider */
}
--- 982,988 ----
path = (Path *) create_material_path(rel, path);
}
! add_path(rel, path, false);
/* For the moment, at least, there are no other paths to consider */
}
*************** set_append_rel_size(PlannerInfo *root, R
*** 980,985 ****
--- 1149,1279 ----
appinfo);
/*
+ * If grouping is applicable to the parent relation, it's applicable
+ * to the children too. Make sure it has valid sortgrouprefs.
+ */
+ if (rel->reltarget->sortgrouprefs)
+ {
+ Assert(childrel->reltarget->sortgrouprefs == NULL);
+ childrel->reltarget->sortgrouprefs = rel->reltarget->sortgrouprefs;
+ }
+
+ /*
+ * Also the grouped target needs to be adjusted, if one exists.
+ *
+ * TODO Move this to separate function?
+ */
+ if (root->all_baserels_grouped && rel->reltarget_grouped != NULL)
+ {
+ PathTarget *target;
+ ListCell *l2;
+ int i;
+ List *aggregates = NIL;
+ Aggref *countagg = NULL;
+
+ target = rel->reltarget_grouped;
+ Assert(target->sortgrouprefs != NULL);
+ childrel->reltarget_grouped = create_empty_pathtarget();
+
+ /*
+ * It makes sense to treat the grouping expressions separate from
+ * the aggregates.
+ *
+ * TODO As we do similar thing in create_plain_grouped_path,
+ * consider storing 2 separate lists, or moving the repeated logic
+ * into a function.
+ */
+ i = 0;
+ foreach(l2, rel->reltarget_grouped->exprs)
+ {
+ Index sortgroupref;
+ Expr *texpr, *texpr_translated;
+
+ sortgroupref = target->sortgrouprefs[i++];
+ if (sortgroupref == 0)
+ /* The first aggregate in the list. */
+ break;
+
+ /*
+ * Adjust vars so the expression references the child relation
+ * and add it to the child rel's grouping target.
+ */
+ texpr = (Expr *) lfirst(l2);
+ texpr_translated = (Expr *)
+ adjust_appendrel_attrs(root, (Node *) texpr, appinfo);
+ add_column_to_pathtarget(childrel->reltarget_grouped,
+ texpr_translated, sortgroupref);
+ }
+ /* At least one grouping expression must be there. */
+ Assert(list_length(childrel->reltarget_grouped->exprs) > 0);
+
+ /*
+ * The aggregates should follow, but already in the form of
+ * "grouped vars".
+ */
+ while (l2 != NULL)
+ {
+ Var *gvar;
+ Aggref *aggref;
+ GroupedVarInfo *gvi_parent = NULL;
+ ListCell *l3;
+
+ gvar = (Var *) lfirst(l2);
+ Assert(IsA(gvar, Var));
+
+ /* Retrieve the parent's grouped var. */
+ foreach(l3, root->grouped_var_list)
+ {
+ GroupedVarInfo *gvi = (GroupedVarInfo *) lfirst(l3);
+
+ if (gvi->var->varno == gvar->varno &&
+ gvi->var->varattno == gvar->varattno)
+ {
+ gvi_parent = gvi;
+ break;
+ }
+ }
+ Assert(gvi_parent != NULL);
+
+ /* Translate the aggregate so it references the child rel. */
+ aggref = (Aggref *) gvi_parent->expr;
+ Assert(IsA(aggref, Aggref));
+
+ if (aggregates == NIL)
+ {
+ /*
+ * The count(*) added by build_base_rel_tlist_grouped
+ * should be the first aggregate in the targetlist. No
+ * translation is applicable here.
+ */
+ Assert(aggref->aggfnoid == COUNTFNOID && aggref->aggstar);
+ countagg = aggref;
+ }
+ else
+ aggref = (Aggref *)
+ adjust_appendrel_attrs(root, (Node *) aggref,
+ appinfo);
+ aggregates = lappend(aggregates, aggref);
+
+ l2 = lnext(l2);
+ }
+
+ /* At least the count(*) aggregate should be there. */
+ Assert(list_length(aggregates) > 0);
+
+ Assert(countagg != NULL);
+ /*
+ * The count(*) aggregate needs to be evaluated for child
+ * relation, but the result will be referenced via the parent
+ * rel. So don't pass countagg_vars here.
+ *
+ * Aggregates are already marked as partial.
+ */
+ add_aggregates_to_grouped_target(root, aggregates, childrel,
+ countagg, NULL, false);
+ }
+
+ /*
* We have to make child entries in the EquivalenceClass data
* structures as well. This is needed either if the parent
* participates in some eclass joins (because we will want to consider
*************** set_append_rel_pathlist(PlannerInfo *roo
*** 1126,1131 ****
--- 1420,1427 ----
bool subpaths_valid = true;
List *partial_subpaths = NIL;
bool partial_subpaths_valid = true;
+ List *grouped_subpaths = NIL;
+ bool grouped_subpaths_valid = true;
List *all_child_pathkeys = NIL;
List *all_child_outers = NIL;
ListCell *l;
*************** set_append_rel_pathlist(PlannerInfo *roo
*** 1197,1202 ****
--- 1493,1521 ----
partial_subpaths_valid = false;
/*
+ * For grouped paths, use only the unparameterized subpaths.
+ *
+ * XXX Consider if the parameterized subpaths should be processed
+ * below. It's probably not useful for sequential scans (due to
+ * repeated aggregation), but might be worthwhile for other child
+ * nodes.
+ */
+ if (childrel->grouped_pathlist != NIL)
+ {
+ Path *path;
+
+ path = (Path *) linitial(childrel->grouped_pathlist);
+ if (path->param_info == NULL)
+ grouped_subpaths = accumulate_append_subpath(grouped_subpaths,
+ path);
+ else
+ grouped_subpaths_valid = false;
+ }
+ else
+ grouped_subpaths_valid = false;
+
+
+ /*
* Collect lists of all the available path orderings and
* parameterizations for all the children. We use these as a
* heuristic to indicate which sort orderings and parameterizations we
*************** set_append_rel_pathlist(PlannerInfo *roo
*** 1267,1273 ****
* if we have zero or one live subpath due to constraint exclusion.)
*/
if (subpaths_valid)
! add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0));
/*
* Consider an append of partial unordered, unparameterized partial paths.
--- 1586,1593 ----
* if we have zero or one live subpath due to constraint exclusion.)
*/
if (subpaths_valid)
! add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0),
! false);
/*
* Consider an append of partial unordered, unparameterized partial paths.
*************** set_append_rel_pathlist(PlannerInfo *roo
*** 1295,1301 ****
/* Generate a partial append path. */
appendpath = create_append_path(rel, partial_subpaths, NULL,
parallel_workers);
! add_partial_path(rel, (Path *) appendpath);
}
/*
--- 1615,1631 ----
/* Generate a partial append path. */
appendpath = create_append_path(rel, partial_subpaths, NULL,
parallel_workers);
! add_partial_path(rel, (Path *) appendpath, false);
! }
!
! if (grouped_subpaths_valid)
! {
! Path *path;
!
! path = (Path *) create_append_path(rel, grouped_subpaths, NULL, 0);
! /* pathtarget will produce the grouped relation.. */
! path->pathtarget = rel->reltarget_grouped;
! add_path(rel, path, true);
}
/*
*************** set_append_rel_pathlist(PlannerInfo *roo
*** 1346,1352 ****
if (subpaths_valid)
add_path(rel, (Path *)
! create_append_path(rel, subpaths, required_outer, 0));
}
}
--- 1676,1683 ----
if (subpaths_valid)
add_path(rel, (Path *)
! create_append_path(rel, subpaths, required_outer, 0),
! false);
}
}
*************** generate_mergeappend_paths(PlannerInfo *
*** 1438,1450 ****
rel,
startup_subpaths,
pathkeys,
! NULL));
if (startup_neq_total)
add_path(rel, (Path *) create_merge_append_path(root,
rel,
total_subpaths,
pathkeys,
! NULL));
}
}
--- 1769,1781 ----
rel,
startup_subpaths,
pathkeys,
! NULL), false);
if (startup_neq_total)
add_path(rel, (Path *) create_merge_append_path(root,
rel,
total_subpaths,
pathkeys,
! NULL), false);
}
}
*************** set_dummy_rel_pathlist(RelOptInfo *rel)
*** 1576,1582 ****
rel->pathlist = NIL;
rel->partial_pathlist = NIL;
! add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0));
/*
* We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
--- 1907,1913 ----
rel->pathlist = NIL;
rel->partial_pathlist = NIL;
! add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0), false);
/*
* We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
*************** set_subquery_pathlist(PlannerInfo *root,
*** 1789,1795 ****
/* Generate outer path using this subpath */
add_path(rel, (Path *)
create_subqueryscan_path(root, rel, subpath,
! pathkeys, required_outer));
}
}
--- 2120,2126 ----
/* Generate outer path using this subpath */
add_path(rel, (Path *)
create_subqueryscan_path(root, rel, subpath,
! pathkeys, required_outer), false);
}
}
*************** set_function_pathlist(PlannerInfo *root,
*** 1858,1864 ****
/* Generate appropriate path */
add_path(rel, create_functionscan_path(root, rel,
! pathkeys, required_outer));
}
/*
--- 2189,2195 ----
/* Generate appropriate path */
add_path(rel, create_functionscan_path(root, rel,
! pathkeys, required_outer), false);
}
/*
*************** set_values_pathlist(PlannerInfo *root, R
*** 1878,1884 ****
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_valuesscan_path(root, rel, required_outer));
}
/*
--- 2209,2215 ----
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_valuesscan_path(root, rel, required_outer), false);
}
/*
*************** set_cte_pathlist(PlannerInfo *root, RelO
*** 1944,1950 ****
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_ctescan_path(root, rel, required_outer));
}
/*
--- 2275,2281 ----
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_ctescan_path(root, rel, required_outer), false);
}
/*
*************** set_worktable_pathlist(PlannerInfo *root
*** 1994,2000 ****
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_worktablescan_path(root, rel, required_outer));
}
/*
--- 2325,2332 ----
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_worktablescan_path(root, rel, required_outer),
! false);
}
/*
*************** set_worktable_pathlist(PlannerInfo *root
*** 2007,2019 ****
* path that some GatherPath has a reference to.)
*/
void
! generate_gather_paths(PlannerInfo *root, RelOptInfo *rel)
{
Path *cheapest_partial_path;
Path *simple_gather_path;
/* If there are no partial paths, there's nothing to do here. */
! if (rel->partial_pathlist == NIL)
return;
/*
--- 2339,2356 ----
* path that some GatherPath has a reference to.)
*/
void
! generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool grouped)
{
Path *cheapest_partial_path;
Path *simple_gather_path;
+ List *pathlist;
+ PathTarget *partial_target;
+
+ pathlist = !grouped ? rel->partial_pathlist :
+ rel->partial_grouped_pathlist;
/* If there are no partial paths, there's nothing to do here. */
! if (pathlist == NIL)
return;
/*
*************** generate_gather_paths(PlannerInfo *root,
*** 2027,2037 ****
* could usefully generate such a path from each partial path that has
* non-NIL pathkeys.
*/
! cheapest_partial_path = linitial(rel->partial_pathlist);
simple_gather_path = (Path *)
! create_gather_path(root, rel, cheapest_partial_path, rel->reltarget,
NULL, NULL);
! add_path(rel, simple_gather_path);
}
/*
--- 2364,2377 ----
* could usefully generate such a path from each partial path that has
* non-NIL pathkeys.
*/
! cheapest_partial_path = linitial(pathlist);
!
! partial_target = !grouped ? rel->reltarget : rel->reltarget_grouped;
!
simple_gather_path = (Path *)
! create_gather_path(root, rel, cheapest_partial_path, partial_target,
NULL, NULL);
! add_path(rel, simple_gather_path, grouped);
}
/*
*************** standard_join_search(PlannerInfo *root,
*** 2196,2202 ****
rel = (RelOptInfo *) lfirst(lc);
/* Create GatherPaths for any useful partial paths for rel */
! generate_gather_paths(root, rel);
/* Find and save the cheapest paths for this rel */
set_cheapest(rel);
--- 2536,2542 ----
rel = (RelOptInfo *) lfirst(lc);
/* Create GatherPaths for any useful partial paths for rel */
! generate_gather_paths(root, rel, false);
/* Find and save the cheapest paths for this rel */
set_cheapest(rel);
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
new file mode 100644
index 7b43c4a..7c44938
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
*************** create_index_paths(PlannerInfo *root, Re
*** 338,344 ****
bitmapqual = choose_bitmap_and(root, rel, bitindexpaths);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
rel->lateral_relids, 1.0);
! add_path(rel, (Path *) bpath);
}
/*
--- 338,344 ----
bitmapqual = choose_bitmap_and(root, rel, bitindexpaths);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
rel->lateral_relids, 1.0);
! add_path(rel, (Path *) bpath, false);
}
/*
*************** create_index_paths(PlannerInfo *root, Re
*** 411,417 ****
loop_count = get_loop_count(root, rel->relid, required_outer);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
required_outer, loop_count);
! add_path(rel, (Path *) bpath);
}
}
}
--- 411,417 ----
loop_count = get_loop_count(root, rel->relid, required_outer);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
required_outer, loop_count);
! add_path(rel, (Path *) bpath, false);
}
}
}
*************** get_index_paths(PlannerInfo *root, RelOp
*** 785,791 ****
IndexPath *ipath = (IndexPath *) lfirst(lc);
if (index->amhasgettuple)
! add_path(rel, (Path *) ipath);
if (index->amhasgetbitmap &&
(ipath->path.pathkeys == NIL ||
--- 785,791 ----
IndexPath *ipath = (IndexPath *) lfirst(lc);
if (index->amhasgettuple)
! add_path(rel, (Path *) ipath, false);
if (index->amhasgetbitmap &&
(ipath->path.pathkeys == NIL ||
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
new file mode 100644
index 7c30ec6..45318c8
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
*************** static void consider_parallel_nestloop(P
*** 39,48 ****
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra);
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinType jointype, JoinPathExtraData *extra);
static List *select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
--- 39,49 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped);
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinType jointype, JoinPathExtraData *extra, bool grouped);
static List *select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 217,224 ****
* joins, because there may be no other alternative.
*/
if (enable_hashjoin || jointype == JOIN_FULL)
hash_inner_and_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra);
/*
* 5. If inner and outer relations are foreign tables (or joins) belonging
--- 218,229 ----
* joins, because there may be no other alternative.
*/
if (enable_hashjoin || jointype == JOIN_FULL)
+ {
hash_inner_and_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra, false);
! hash_inner_and_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra, true);
! }
/*
* 5. If inner and outer relations are foreign tables (or joins) belonging
*************** try_nestloop_path(PlannerInfo *root,
*** 323,329 ****
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer))
{
add_path(joinrel, (Path *)
create_nestloop_path(root,
--- 328,334 ----
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer, false))
{
add_path(joinrel, (Path *)
create_nestloop_path(root,
*************** try_nestloop_path(PlannerInfo *root,
*** 336,342 ****
inner_path,
extra->restrictlist,
pathkeys,
! required_outer));
}
else
{
--- 341,348 ----
inner_path,
extra->restrictlist,
pathkeys,
! required_outer),
! false);
}
else
{
*************** try_partial_nestloop_path(PlannerInfo *r
*** 357,363 ****
Path *inner_path,
List *pathkeys,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinCostWorkspace workspace;
--- 363,370 ----
Path *inner_path,
List *pathkeys,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped)
{
JoinCostWorkspace workspace;
*************** try_partial_nestloop_path(PlannerInfo *r
*** 383,389 ****
initial_cost_nestloop(root, &workspace, jointype,
outer_path, inner_path,
extra->sjinfo, &extra->semifactors);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys))
return;
/* Might be good enough to be worth trying, so let's try it. */
--- 390,397 ----
initial_cost_nestloop(root, &workspace, jointype,
outer_path, inner_path,
extra->sjinfo, &extra->semifactors);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys,
! false))
return;
/* Might be good enough to be worth trying, so let's try it. */
*************** try_partial_nestloop_path(PlannerInfo *r
*** 398,404 ****
inner_path,
extra->restrictlist,
pathkeys,
! NULL));
}
/*
--- 406,413 ----
inner_path,
extra->restrictlist,
pathkeys,
! NULL),
! grouped);
}
/*
*************** try_mergejoin_path(PlannerInfo *root,
*** 456,462 ****
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer))
{
add_path(joinrel, (Path *)
create_mergejoin_path(root,
--- 465,471 ----
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer, false))
{
add_path(joinrel, (Path *)
create_mergejoin_path(root,
*************** try_mergejoin_path(PlannerInfo *root,
*** 471,477 ****
required_outer,
mergeclauses,
outersortkeys,
! innersortkeys));
}
else
{
--- 480,486 ----
required_outer,
mergeclauses,
outersortkeys,
! innersortkeys), false);
}
else
{
*************** try_hashjoin_path(PlannerInfo *root,
*** 492,498 ****
Path *inner_path,
List *hashclauses,
JoinType jointype,
! JoinPathExtraData *extra)
{
Relids required_outer;
JoinCostWorkspace workspace;
--- 501,508 ----
Path *inner_path,
List *hashclauses,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped)
{
Relids required_outer;
JoinCostWorkspace workspace;
*************** try_hashjoin_path(PlannerInfo *root,
*** 521,527 ****
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! NIL, required_outer))
{
add_path(joinrel, (Path *)
create_hashjoin_path(root,
--- 531,537 ----
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! NIL, required_outer, grouped))
{
add_path(joinrel, (Path *)
create_hashjoin_path(root,
*************** try_hashjoin_path(PlannerInfo *root,
*** 534,540 ****
inner_path,
extra->restrictlist,
required_outer,
! hashclauses));
}
else
{
--- 544,551 ----
inner_path,
extra->restrictlist,
required_outer,
! hashclauses,
! grouped), grouped);
}
else
{
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 555,561 ****
Path *inner_path,
List *hashclauses,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinCostWorkspace workspace;
--- 566,573 ----
Path *inner_path,
List *hashclauses,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped)
{
JoinCostWorkspace workspace;
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 581,587 ****
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path,
extra->sjinfo, &extra->semifactors);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, NIL))
return;
/* Might be good enough to be worth trying, so let's try it. */
--- 593,599 ----
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path,
extra->sjinfo, &extra->semifactors);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, NIL, false))
return;
/* Might be good enough to be worth trying, so let's try it. */
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 596,602 ****
inner_path,
extra->restrictlist,
NULL,
! hashclauses));
}
/*
--- 608,616 ----
inner_path,
extra->restrictlist,
NULL,
! hashclauses,
! grouped),
! grouped);
}
/*
*************** match_unsorted_outer(PlannerInfo *root,
*** 1233,1240 ****
if (joinrel->consider_parallel && nestjoinOK &&
save_jointype != JOIN_UNIQUE_OUTER &&
bms_is_empty(joinrel->lateral_relids))
consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! save_jointype, extra);
}
/*
--- 1247,1258 ----
if (joinrel->consider_parallel && nestjoinOK &&
save_jointype != JOIN_UNIQUE_OUTER &&
bms_is_empty(joinrel->lateral_relids))
+ {
consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! save_jointype, extra, false);
! consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! save_jointype, extra, true);
! }
}
/*
*************** consider_parallel_nestloop(PlannerInfo *
*** 1254,1268 ****
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinType save_jointype = jointype;
ListCell *lc1;
if (jointype == JOIN_UNIQUE_INNER)
jointype = JOIN_INNER;
! foreach(lc1, outerrel->partial_pathlist)
{
Path *outerpath = (Path *) lfirst(lc1);
List *pathkeys;
--- 1272,1291 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped)
{
JoinType save_jointype = jointype;
+ List *outerrel_pathlist;
ListCell *lc1;
if (jointype == JOIN_UNIQUE_INNER)
jointype = JOIN_INNER;
! outerrel_pathlist = !grouped ? outerrel->partial_pathlist :
! outerrel->partial_grouped_pathlist;
!
! foreach(lc1, outerrel_pathlist)
{
Path *outerpath = (Path *) lfirst(lc1);
List *pathkeys;
*************** consider_parallel_nestloop(PlannerInfo *
*** 1304,1310 ****
}
try_partial_nestloop_path(root, joinrel, outerpath, innerpath,
! pathkeys, jointype, extra);
}
}
}
--- 1327,1333 ----
}
try_partial_nestloop_path(root, joinrel, outerpath, innerpath,
! pathkeys, jointype, extra, grouped);
}
}
}
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1326,1332 ****
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinType save_jointype = jointype;
bool isouterjoin = IS_OUTER_JOIN(jointype);
--- 1349,1356 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped)
{
JoinType save_jointype = jointype;
bool isouterjoin = IS_OUTER_JOIN(jointype);
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1382,1394 ****
* can't use a hashjoin. (There's no use looking for alternative
* input paths, since these should already be the least-parameterized
* available paths.)
*/
if (PATH_PARAM_BY_REL(cheapest_total_outer, innerrel) ||
PATH_PARAM_BY_REL(cheapest_total_inner, outerrel))
return;
/* Unique-ify if need be; we ignore parameterized possibilities */
! if (jointype == JOIN_UNIQUE_OUTER)
{
cheapest_total_outer = (Path *)
create_unique_path(root, outerrel,
--- 1406,1445 ----
* can't use a hashjoin. (There's no use looking for alternative
* input paths, since these should already be the least-parameterized
* available paths.)
+ *
+ * (The same check should work for grouped paths, as these don't
+ * differ in parameterization.)
*/
if (PATH_PARAM_BY_REL(cheapest_total_outer, innerrel) ||
PATH_PARAM_BY_REL(cheapest_total_inner, outerrel))
return;
+ if (grouped)
+ {
+ /*
+ * The result of grouped path should be quite different from that
+ * of the non-grouped one as such, so don't bother combining
+ * multiple kinds of grouped paths.
+ *
+ * TODO As for JOIN_UNIQUE_OUTER and JOIN_UNIQUE_INNER, consider
+ * if the unique-ificiation is worth the effort.
+ */
+ if (jointype != JOIN_UNIQUE_OUTER &&
+ jointype != JOIN_UNIQUE_INNER &&
+ outerrel->grouped_pathlist && innerrel->grouped_pathlist)
+ {
+ try_hashjoin_path(root,
+ joinrel,
+ (Path *) linitial(outerrel->grouped_pathlist),
+ (Path *) linitial(innerrel->grouped_pathlist),
+ hashclauses,
+ jointype,
+ extra,
+ true);
+ }
+ }
/* Unique-ify if need be; we ignore parameterized possibilities */
! else if (jointype == JOIN_UNIQUE_OUTER)
{
cheapest_total_outer = (Path *)
create_unique_path(root, outerrel,
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1401,1407 ****
cheapest_total_inner,
hashclauses,
jointype,
! extra);
/* no possibility of cheap startup here */
}
else if (jointype == JOIN_UNIQUE_INNER)
--- 1452,1459 ----
cheapest_total_inner,
hashclauses,
jointype,
! extra,
! false);
/* no possibility of cheap startup here */
}
else if (jointype == JOIN_UNIQUE_INNER)
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1417,1423 ****
cheapest_total_inner,
hashclauses,
jointype,
! extra);
if (cheapest_startup_outer != NULL &&
cheapest_startup_outer != cheapest_total_outer)
try_hashjoin_path(root,
--- 1469,1476 ----
cheapest_total_inner,
hashclauses,
jointype,
! extra,
! false);
if (cheapest_startup_outer != NULL &&
cheapest_startup_outer != cheapest_total_outer)
try_hashjoin_path(root,
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1426,1432 ****
cheapest_total_inner,
hashclauses,
jointype,
! extra);
}
else
{
--- 1479,1486 ----
cheapest_total_inner,
hashclauses,
jointype,
! extra,
! false);
}
else
{
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1447,1453 ****
cheapest_total_inner,
hashclauses,
jointype,
! extra);
foreach(lc1, outerrel->cheapest_parameterized_paths)
{
--- 1501,1508 ----
cheapest_total_inner,
hashclauses,
jointype,
! extra,
! false);
foreach(lc1, outerrel->cheapest_parameterized_paths)
{
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1481,1487 ****
innerpath,
hashclauses,
jointype,
! extra);
}
}
}
--- 1536,1543 ----
innerpath,
hashclauses,
jointype,
! extra,
! false);
}
}
}
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1537,1543 ****
try_partial_hashjoin_path(root, joinrel,
cheapest_partial_outer,
cheapest_safe_inner,
! hashclauses, jointype, extra);
}
}
}
--- 1593,1613 ----
try_partial_hashjoin_path(root, joinrel,
cheapest_partial_outer,
cheapest_safe_inner,
! hashclauses, jointype, extra,
! false);
!
! /*
! * If partial grouped path exists for either side, join the
! * cheapest ones into a new grouped path.
! */
! if (outerrel->partial_grouped_pathlist &&
! innerrel->partial_grouped_pathlist)
! try_partial_hashjoin_path(root,
! joinrel,
! (Path *) linitial(outerrel->partial_grouped_pathlist),
! (Path *) linitial(innerrel->partial_grouped_pathlist),
! hashclauses, jointype, extra,
! true);
}
}
}
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
new file mode 100644
index 6f3c20b..51e01d7
*** a/src/backend/optimizer/path/joinrels.c
--- b/src/backend/optimizer/path/joinrels.c
*************** mark_dummy_rel(RelOptInfo *rel)
*** 1197,1203 ****
rel->partial_pathlist = NIL;
/* Set up the dummy path */
! add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0));
/* Set or update cheapest_total_path and related fields */
set_cheapest(rel);
--- 1197,1203 ----
rel->partial_pathlist = NIL;
/* Set up the dummy path */
! add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0), false);
/* Set or update cheapest_total_path and related fields */
set_cheapest(rel);
diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c
new file mode 100644
index 240ade6..6c3a77a
*** a/src/backend/optimizer/path/tidpath.c
--- b/src/backend/optimizer/path/tidpath.c
*************** create_tidscan_paths(PlannerInfo *root,
*** 263,267 ****
if (tidquals)
add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
! required_outer));
}
--- 263,267 ----
if (tidquals)
add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
! required_outer), false);
}
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
new file mode 100644
index 6ceb801..7a8b3c4
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
***************
*** 14,20 ****
--- 14,25 ----
*/
#include "postgres.h"
+ #include "access/htup_details.h"
+ #include "access/sysattr.h"
+ #include "catalog/pg_aggregate.h"
+ #include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
+ #include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
***************
*** 26,36 ****
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
!
/* These parameters are set by GUC */
int from_collapse_limit;
--- 31,42 ----
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+ #include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
! #include "utils/syscache.h"
/* These parameters are set by GUC */
int from_collapse_limit;
*************** static List *deconstruct_recurse(Planner
*** 51,56 ****
--- 57,67 ----
bool below_outer_join,
Relids *qualscope, Relids *inner_join_rels,
List **postponed_qual_list);
+ static bool build_base_rel_tlist_grouped(PlannerInfo *root, RelOptInfo *rel,
+ List *aggregates, Aggref *countagg,
+ List **countagg_vars);
+ static void finalize_grouped_vars(PlannerInfo *root, List *countagg_vars);
+ static void add_sortgrouprefs_to_reltarget(RelOptInfo *rel);
static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root,
Relids left_rels, Relids right_rels,
Relids inner_join_rels,
*************** add_vars_to_targetlist(PlannerInfo *root
*** 236,241 ****
--- 247,820 ----
}
}
+ /*
+ * Initialize rel->reltarget_grouped where possible.
+ *
+ * root->group_pathkeys must be setup before this function is called.
+ */
+ extern void
+ build_base_rel_tlists_grouped(PlannerInfo *root)
+ {
+ List *tlist_vars;
+ List *aggregates = NIL;
+ ListCell *lc;
+ bool aggs_acceptable = true;
+ Aggref *countagg = NULL;
+ List *countagg_vars = NIL;
+ int nbaserels = 0;
+ int i;
+
+ /* No grouping in the query? */
+ if (!root->parse->groupClause || root->group_pathkeys == NIL)
+ return;
+
+ /* TODO This is just for PoC. Relax the limitation later. */
+ if (root->parse->havingQual)
+ return;
+
+ /*
+ * If no join is expected, aggregation at base relation level makes no
+ * sense. XXX Is there simpler way to find out? (We're not interested in
+ * RELOPT_OTHER_MEMBER_REL, so simple_rel_array_size does not help.)
+ */
+ for (i = 1; i < root->simple_rel_array_size; i++)
+ {
+ RelOptInfo *rel;
+
+ rel = find_base_rel(root, i);
+ if (rel->reloptkind == RELOPT_BASEREL)
+ {
+ nbaserels++;
+ /*
+ * We only want to know whether the number of relations is greater
+ * than one.
+ */
+ if (nbaserels > 1)
+ break;
+ }
+ }
+ if (nbaserels <= 1)
+ return;
+
+ tlist_vars = pull_var_clause((Node *) root->processed_tlist,
+ PVC_INCLUDE_AGGREGATES);
+ if (tlist_vars == NIL)
+ return;
+
+ /* tlist_vars may also contain Vars, but we only need Aggrefs. */
+ foreach(lc, tlist_vars)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+
+ if (!IsA(expr, Var))
+ {
+ Aggref *aggref = (Aggref *) expr;
+
+ Assert(IsA(aggref, Aggref));
+
+ /* TODO Think if (some of) these can be handled. */
+ if (aggref->aggstar || aggref->aggvariadic || aggref->aggdirectargs ||
+ aggref->aggorder || aggref->aggdistinct || aggref->aggfilter)
+ {
+ aggs_acceptable = false;
+ break;
+ }
+
+ aggregates = lappend(aggregates, expr);
+ }
+ }
+
+ root->all_baserels_grouped = false;
+ if (aggregates != NIL && aggs_acceptable)
+ {
+ int i;
+ bool success = true;
+
+ if (countagg == NULL)
+ {
+ /* count(*) aggregate for base relation grouping. */
+ countagg = makeNode(Aggref);
+ countagg->aggfnoid = COUNTFNOID;
+ countagg->aggkind = AGGKIND_NORMAL;
+ countagg->aggtype = INT8OID;
+ countagg->aggtranstype = INT8OID;
+ countagg->aggstar = true;
+ }
+ /* Process the individual base relations. */
+ root->all_baserels_grouped = true;
+ for (i = 1; i < root->simple_rel_array_size; i++)
+ {
+ RelOptInfo *rel = root->simple_rel_array[i];
+
+ /*
+ * "other rels" will have their targets built later, by
+ * translation of the target of the parent rel - see
+ * set_append_rel_size.
+ */
+ if (rel != NULL && rel->reloptkind == RELOPT_BASEREL)
+ {
+ if (build_base_rel_tlist_grouped(root, rel, aggregates, countagg,
+ &countagg_vars))
+ {
+ /*
+ * The paths generated for this relation may be
+ * immediately grouped, so the target entries need valid
+ * sortgroupref.
+ */
+ add_sortgrouprefs_to_reltarget(rel);
+ }
+ else
+ {
+ /*
+ * Don't bother processing the rest if the current rel
+ * prevents us from forming the final "grouped join".
+ */
+ success = false;
+ break;
+ }
+ }
+ }
+
+ if (success)
+ {
+ finalize_grouped_vars(root, countagg_vars);
+ root->all_baserels_grouped = true;
+ }
+ }
+
+ if (aggregates != NIL)
+ list_free(aggregates);
+ list_free(tlist_vars);
+ }
+
+ /*
+ * Construct target of grouped relation - that can only contain grouping
+ * expressions and aggregates. Such a target can't be created if anything else
+ * is required on the output.
+ *
+ * Return true iff succeeded.
+ */
+ static bool
+ build_base_rel_tlist_grouped(PlannerInfo *root, RelOptInfo *rel,
+ List *aggregates, Aggref *countagg,
+ List **countagg_vars)
+ {
+ RangeTblEntry *rte;
+ List *rel_aggregates;
+ ListCell *lc;
+ Relids agg_arg_rels_all, agg_arg_attrs_all;
+ PathTarget *reltarget_grouped;
+
+ rte = root->simple_rte_array[rel->relid];
+
+ /*
+ * XXX rtekind != RTE_RELATION seem to deserve separate patch.
+ */
+ if ( rte->rtekind != RTE_RELATION)
+ return false;
+
+ /* Caller should only pass base relations. */
+ Assert(rel->reloptkind == RELOPT_BASEREL);
+ Assert(bms_membership(rel->relids) == BMS_SINGLETON);
+
+ /*
+ * If any outer join can set the attribute value to NULL, the aggregate
+ * would receive different input at the base rel level.
+ *
+ * TODO This as well as some other limitations below should only apply to
+ * the PoC version of the patch. In the future we should handle
+ * non-grouped relations by joining them to the grouped ones and applying
+ * additional partial aggregation to the final join.
+ */
+ if (bms_overlap(rel->relids, root->nullable_baserels))
+ return false;
+
+ /* Collect aggregates applicable to the current relation. */
+ rel_aggregates = NIL;
+ agg_arg_rels_all = NULL;
+ agg_arg_attrs_all = NULL;
+ foreach(lc, aggregates)
+ {
+ Aggref *aggref = (Aggref *) lfirst(lc);
+ Relids agg_arg_rels, agg_arg_attrs = NULL;
+
+ /* TODO Does it matter if any argument contains PHV ? */
+ agg_arg_rels = pull_varnos((Node *) aggref->args);
+
+ /* Skip aggregates which don't reference rel at all. */
+ if (!bms_overlap(rel->relids, agg_arg_rels))
+ continue;
+
+ /*
+ * If the aggregate references multiple rels, it must be processed
+ * higher in the join tree.
+ */
+ if (bms_membership(agg_arg_rels) != BMS_SINGLETON)
+ {
+ /*
+ * TODO Consider relaxing this limitation by adding one extra
+ * partial aggregation below the final one.
+ */
+ return false;
+ }
+
+ /* Accept this aggregate. */
+ rel_aggregates = lappend(rel_aggregates, aggref);
+ agg_arg_rels_all = bms_union(agg_arg_rels_all, agg_arg_rels);
+
+ /* Collect attnos while being here. */
+ pull_varattnos((Node *) aggref->args, rel->relid, &agg_arg_attrs);
+ agg_arg_attrs_all = bms_union(agg_arg_attrs_all, agg_arg_attrs);
+ }
+
+ Assert(rel->reltarget_grouped == NULL);
+
+ /*
+ * Create a separate target which represents the actual output of grouped
+ * relation. This target will have aggregates replaced with special vars
+ * which will ensure propagation of the result of the partial aggregation
+ * of the base relation to the final Agg node.
+ *
+ * This target won't contain expressions that the grouped relation cannot
+ * emit. Another difference from rel->reltarget is that sortgroupref is
+ * set for each grouping expression. This is because the same target will
+ * be used for the partial aggregation.
+ *
+ * (Do not set rel->reltarget_grouped yet, as we might return
+ * prematurely.)
+ */
+ reltarget_grouped = create_empty_pathtarget();
+
+ /*
+ * Check if all the output columns can be used as grouping expressions.
+ *
+ * TODO
+ *
+ * 1. How about PHVs?
+ *
+ * 2. Consider additional grouping expressions, just to be able to emit
+ * the columns that the query group clause doesn't mention. (These
+ * additional expressions wouldn't be used during the final aggregation.)
+ * Does this also mean that we shouldn't check existence of
+ * parse->groupClause earlier in this function?
+ */
+ foreach(lc, rel->reltarget->exprs)
+ {
+ ListCell *lc2;
+ Expr *texpr = (Expr *) lfirst(lc);
+ bool is_grouping = false;
+ bool ec_found = false;
+ bool agg_arg_only = false;
+
+ /*
+ * First, check if the query group clause contains exactly this
+ * expression.
+ */
+ foreach(lc2, root->processed_tlist)
+ {
+ TargetEntry *te = (TargetEntry *) lfirst(lc2);
+
+ Assert(IsA(te, TargetEntry));
+
+ if (equal(texpr, te->expr) && te->ressortgroupref > 0)
+ {
+ add_column_to_pathtarget(reltarget_grouped, texpr,
+ te->ressortgroupref);
+ is_grouping = true;
+ break;
+ }
+ }
+
+ /* Go for the next expression if matched the current one. */
+ if (is_grouping)
+ continue;
+
+ /*
+ * If exactly this expression is not there, check if a grouping clause
+ * exists that belongs to the same equivalence class as the
+ * expression.
+ */
+ foreach(lc2, root->group_pathkeys)
+ {
+ PathKey *pk = (PathKey *) lfirst(lc2);
+ EquivalenceClass *ec = pk->pk_eclass;
+ ListCell *lm;
+ EquivalenceMember *em;
+ Expr *em_expr = NULL;
+ Query *query = root->parse;
+ Index sortgroupref;
+
+ /*
+ * Single-member EC cannot provide us with additional
+ * expression.
+ */
+ if (list_length(ec->ec_members) < 2)
+ continue;
+
+ /* We need equality anywhere in the join tree. */
+ if (ec->ec_below_outer_join)
+ continue;
+
+ /*
+ * TODO Reconsider this restriction. As the grouping expression is
+ * only evaluated at the relation level (and only the result will
+ * be propagated to the final targetlist), volatile function might
+ * be o.k. Need to think what volatile EC exactly means.
+ */
+ if (ec->ec_has_volatile)
+ continue;
+
+ foreach(lm, ec->ec_members)
+ {
+ em = (EquivalenceMember *) lfirst(lm);
+
+ /* The EC has !ec_below_outer_join. */
+ Assert(!em->em_nullable_relids);
+ if (equal(em->em_expr, texpr))
+ {
+ em_expr = (Expr *) em->em_expr;
+ break;
+ }
+ }
+
+ if (em_expr == NULL)
+ /* Go for the next EC. */
+ continue;
+
+ /*
+ * Find the corresponding SortGroupClause, which provides us
+ * with sortgroupref. (It can belong to any EC member.)
+ */
+ sortgroupref = 0;
+ foreach(lm, ec->ec_members)
+ {
+ ListCell *lsg;
+
+ em = (EquivalenceMember *) lfirst(lm);
+ foreach(lsg, query->groupClause)
+ {
+ SortGroupClause *sgc;
+ Expr *texpr;
+
+ sgc = (SortGroupClause *) lfirst(lsg);
+ texpr = (Expr *) get_sortgroupclause_expr(sgc,
+ query->targetList);
+ if (equal(em->em_expr, texpr))
+ {
+ Assert(sgc->tleSortGroupRef > 0);
+ sortgroupref = sgc->tleSortGroupRef;
+ break;
+ }
+ }
+
+ if (sortgroupref > 0)
+ break;
+ }
+
+ /*
+ * At least one EM of this EC should have correspond to a
+ * SortGroupClause, otherwise the EC could hardly exist.
+ */
+ if (sortgroupref == 0)
+ elog(ERROR, "Grouping EC does not match any grouping clause.");
+
+ /* It's o.k. to use the target expression for grouping. */
+ add_column_to_pathtarget(reltarget_grouped, texpr, sortgroupref);
+ ec_found = true;
+ break;
+ }
+
+ /*
+ * It may still be o.k. if the expression is only contained in Aggref
+ * - then it's not expected in the grouped output.
+ *
+ * TODO Try to handle generic expression, not only Var. That might
+ * require us to create rel->reltarget of the grouping rel in
+ * parallel to that of the plain rel, and adding whole expressions
+ * instead of individual vars.
+ */
+ if (IsA(texpr, Var) &&
+ bms_is_member(((Var *) texpr)->varattno -
+ FirstLowInvalidHeapAttributeNumber, agg_arg_attrs_all))
+ agg_arg_only = true;
+
+ /*
+ * A single mismatched expression makes the whole relation useless
+ * for grouping at base level.
+ */
+ if (!ec_found && !agg_arg_only)
+ return false;
+ }
+
+ /*
+ * Add the count(*) aggregate, as its processing is nearly identical to
+ * that of other aggregates. For convenience, add it in front of the other
+ * vars.
+ *
+ * TODO Avoid this if no aggregate in the query has aggtransmultifn.
+ */
+ rel_aggregates = lcons(countagg, rel_aggregates);
+
+ /* Process all the aggregates at once. */
+ rel->reltarget_grouped = reltarget_grouped;
+ add_aggregates_to_grouped_target(root, rel_aggregates, rel, countagg,
+ countagg_vars, true);
+ return true;
+ }
+
+ /*
+ * Initialize expr_intermediate of each GroupedVarInfo that requires it.
+ * This expression will adjust partial state of an aggregate prior to final
+ * aggregation, so it reflects existence of joins.
+ *
+ * If a single relation is aggregated, only the table determines how many
+ * times each value of aggregate argument appears in the relevant group. But
+ * if relation is joined to another one, the aggregate executed on the final
+ * join (i.e. w/o the relation-level aggregation) can actually receive the
+ * whole input set multiple times: some values of the grouping key present in
+ * the relation can match multiple values in the other table(s).
+ *
+ * sum() is an example of an aggregate where join matters, avg() is one where
+ * it does not.
+ *
+ * We simulate the effect of joins by applying aggtransmultifn (see
+ * pg_aggregate) to the result of relation-level aggregation.
+ */
+ static void
+ finalize_grouped_vars(PlannerInfo *root, List *countagg_vars)
+ {
+ ListCell *lc;
+ Oid op_int8mul = InvalidOid;
+
+ foreach(lc, root->grouped_var_list)
+ {
+ GroupedVarInfo *gvi = (GroupedVarInfo *) lfirst(lc);
+ Aggref *aggref = (Aggref *) gvi->expr;
+ ListCell *l;
+ Expr *factor = NULL;
+
+ Assert(gvi->expr_intermediate == NULL);
+ Assert(IsA(aggref, Aggref));
+
+ /* Is transient state multiplication needed for this aggregate? */
+ if (aggref->aggtransmultifn == InvalidOid)
+ continue;
+
+ /*
+ * If there was no relation-level aggregation, each table joined to
+ * the one gvi->varno points at would increase the frequency of a
+ * value within a group by the factor which equals to the frequency of
+ * the corresponding grouping key in the table joined.
+ *
+ * To prepare the correct input for the final aggregation, the
+ * relation-level per-group state must be "multiplied" by the number
+ * of grouping key values that join of the *other* tables (i.e. all
+ * but the one referenced by the current aggregate) generates.
+ *
+ * This is why count(*) was added to each base relation.
+ */
+ foreach(l, countagg_vars)
+ {
+ Var *var = (Var *) lfirst(l);
+
+ /* Only the other tables do matter. */
+ if (var->varno == gvi->var->varno)
+ continue;
+
+ if (factor == NULL)
+ /* The first value. */
+ factor = (Expr *) var;
+ else
+ {
+ OpExpr *op = makeNode(OpExpr);
+
+ /*
+ * Multiply the intermediate result by the result of the next
+ * count(*) aggregate.
+ */
+ op->opno = OPERATOR_INT8MUL;
+ if (op_int8mul == InvalidOid)
+ {
+ set_opfuncid(op);
+ op_int8mul = op->opfuncid;
+ }
+ else
+ op->opfuncid = op_int8mul;
+
+ op->opresulttype = INT8OID;
+ op->opretset = false;
+ op->opcollid = InvalidOid;
+ op->inputcollid = InvalidOid;
+ op->args = list_make2(factor, var);
+ op->location = -1;
+
+ factor = (Expr *) op;
+ }
+ }
+
+ /* Construct the function call if needed. */
+ if (factor != NULL)
+ {
+ FuncExpr *func = makeNode(FuncExpr);
+
+ func->funcid = aggref->aggtransmultifn;
+ func->funcresulttype = aggref->aggtranstype;
+ func->funcretset = false;
+ func->funcvariadic = false;
+ func->funccollid = InvalidOid;
+ func->inputcollid = InvalidOid;
+ Assert(gvi->var != NULL);
+ func->args = list_make2(gvi->var, factor);
+ func->location = -1;
+
+ gvi->expr_intermediate = (Expr *) func;
+ }
+ }
+ }
+
+ /*
+ * Create a new reltarget, in which the expressions have sortgroupref
+ * initialized.
+ */
+ static void
+ add_sortgrouprefs_to_reltarget(RelOptInfo *rel)
+ {
+ PathTarget *target_new = create_empty_pathtarget();
+ ListCell *lc1;
+
+ /* reltarget_grouped should have sortgrouprefs valid by now. */
+ Assert(rel->reltarget_grouped->sortgrouprefs != NULL);
+
+ foreach(lc1, rel->reltarget->exprs)
+ {
+ ListCell *lc2;
+ int i = 0;
+ Index sortgroupref = 0;
+ Expr *texpr = (Expr *) lfirst(lc1);
+
+ /*
+ * Find the expression in the grouped target. (If not found, it should
+ * be a variable used only as aggregate argument, which is not itself
+ * emitted by the relation.)
+ */
+ foreach(lc2, rel->reltarget_grouped->exprs)
+ {
+ Expr *gexpr = (Expr *) lfirst(lc2);
+
+ if (equal(texpr, gexpr))
+ {
+ sortgroupref = rel->reltarget_grouped->sortgrouprefs[i++];
+ break;
+ }
+
+ i++;
+ }
+
+ add_column_to_pathtarget(target_new, texpr, sortgroupref);
+ }
+
+ rel->reltarget = target_new;
+ }
/*****************************************************************************
*
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
new file mode 100644
index c3fbf3c..5b3bc71
*** a/src/backend/optimizer/plan/planagg.c
--- b/src/backend/optimizer/plan/planagg.c
*************** preprocess_minmax_aggregates(PlannerInfo
*** 223,229 ****
create_minmaxagg_path(root, grouped_rel,
create_pathtarget(root, tlist),
aggs_list,
! (List *) parse->havingQual));
}
/*
--- 223,229 ----
create_minmaxagg_path(root, grouped_rel,
create_pathtarget(root, tlist),
aggs_list,
! (List *) parse->havingQual), false);
}
/*
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
new file mode 100644
index e880759..f81e125
*** a/src/backend/optimizer/plan/planmain.c
--- b/src/backend/optimizer/plan/planmain.c
*************** query_planner(PlannerInfo *root, List *t
*** 83,89 ****
add_path(final_rel, (Path *)
create_result_path(root, final_rel,
final_rel->reltarget,
! (List *) parse->jointree->quals));
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(final_rel);
--- 83,89 ----
add_path(final_rel, (Path *)
create_result_path(root, final_rel,
final_rel->reltarget,
! (List *) parse->jointree->quals), false);
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(final_rel);
*************** query_planner(PlannerInfo *root, List *t
*** 114,119 ****
--- 114,120 ----
root->full_join_clauses = NIL;
root->join_info_list = NIL;
root->placeholder_list = NIL;
+ root->grouped_var_list = NIL;
root->fkey_list = NIL;
root->initial_rels = NIL;
*************** query_planner(PlannerInfo *root, List *t
*** 176,181 ****
--- 177,184 ----
*/
(*qp_callback) (root, qp_extra);
+ build_base_rel_tlists_grouped(root);
+
/*
* Examine any "placeholder" expressions generated during subquery pullup.
* Make sure that the Vars they need are marked as needed at the relevant
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 207290f..eeac4ac
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** static double get_number_of_groups(Plann
*** 109,117 ****
double path_rows,
List *rollup_lists,
List *rollup_groupclauses);
- static Size estimate_hashagg_tablesize(Path *path,
- const AggClauseCosts *agg_costs,
- double dNumGroups);
static RelOptInfo *create_grouping_paths(PlannerInfo *root,
RelOptInfo *input_rel,
PathTarget *target,
--- 109,114 ----
*************** inheritance_planner(PlannerInfo *root)
*** 1396,1402 ****
returningLists,
rowMarks,
NULL,
! SS_assign_special_param(root)));
}
/*--------------------
--- 1393,1399 ----
returningLists,
rowMarks,
NULL,
! SS_assign_special_param(root)), false);
}
/*--------------------
*************** grouping_planner(PlannerInfo *root, bool
*** 2061,2067 ****
}
/* And shove it into final_rel */
! add_path(final_rel, path);
}
/*
--- 2058,2064 ----
}
/* And shove it into final_rel */
! add_path(final_rel, path, false);
}
/*
*************** get_number_of_groups(PlannerInfo *root,
*** 3270,3304 ****
}
/*
- * estimate_hashagg_tablesize
- * estimate the number of bytes that a hash aggregate hashtable will
- * require based on the agg_costs, path width and dNumGroups.
- */
- static Size
- estimate_hashagg_tablesize(Path *path, const AggClauseCosts *agg_costs,
- double dNumGroups)
- {
- Size hashentrysize;
-
- /* Estimate per-hash-entry space at tuple width... */
- hashentrysize = MAXALIGN(path->pathtarget->width) +
- MAXALIGN(SizeofMinimalTupleHeader);
-
- /* plus space for pass-by-ref transition values... */
- hashentrysize += agg_costs->transitionSpace;
- /* plus the per-hash-entry overhead */
- hashentrysize += hash_agg_entry_size(agg_costs->numAggs);
-
- /*
- * Note that this disregards the effect of fill-factor and growth policy
- * of the hash-table. That's probably ok, given default the default
- * fill-factor is relatively high. It'd be hard to meaningfully factor in
- * "double-in-size" growth policies here.
- */
- return hashentrysize * dNumGroups;
- }
-
- /*
* create_grouping_paths
*
* Build a new upperrel containing Paths for grouping and/or aggregation.
--- 3267,3272 ----
*************** create_grouping_paths(PlannerInfo *root,
*** 3419,3425 ****
(List *) parse->havingQual);
}
! add_path(grouped_rel, path);
/* No need to consider any other alternatives. */
set_cheapest(grouped_rel);
--- 3387,3393 ----
(List *) parse->havingQual);
}
! add_path(grouped_rel, path, false);
/* No need to consider any other alternatives. */
set_cheapest(grouped_rel);
*************** create_grouping_paths(PlannerInfo *root,
*** 3592,3598 ****
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups));
else
add_partial_path(grouped_rel, (Path *)
create_group_path(root,
--- 3560,3567 ----
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups),
! false);
else
add_partial_path(grouped_rel, (Path *)
create_group_path(root,
*************** create_grouping_paths(PlannerInfo *root,
*** 3601,3607 ****
partial_grouping_target,
parse->groupClause,
NIL,
! dNumPartialGroups));
}
}
}
--- 3570,3577 ----
partial_grouping_target,
parse->groupClause,
NIL,
! dNumPartialGroups),
! false);
}
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 3632,3638 ****
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups));
}
}
}
--- 3602,3609 ----
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups),
! false);
}
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 3677,3683 ****
rollup_lists,
rollup_groupclauses,
agg_costs,
! dNumGroups));
}
else if (parse->hasAggs)
{
--- 3648,3654 ----
rollup_lists,
rollup_groupclauses,
agg_costs,
! dNumGroups), false);
}
else if (parse->hasAggs)
{
*************** create_grouping_paths(PlannerInfo *root,
*** 3695,3701 ****
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups));
}
else if (parse->groupClause)
{
--- 3666,3672 ----
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups), false);
}
else if (parse->groupClause)
{
*************** create_grouping_paths(PlannerInfo *root,
*** 3710,3716 ****
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups));
}
else
{
--- 3681,3687 ----
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups), false);
}
else
{
*************** create_grouping_paths(PlannerInfo *root,
*** 3760,3766 ****
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups));
else
add_path(grouped_rel, (Path *)
create_group_path(root,
--- 3731,3737 ----
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups), false);
else
add_path(grouped_rel, (Path *)
create_group_path(root,
*************** create_grouping_paths(PlannerInfo *root,
*** 3769,3775 ****
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups));
}
}
--- 3740,3746 ----
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups), false);
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 3801,3807 ****
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups));
}
/*
--- 3772,3778 ----
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups), false);
}
/*
*************** create_grouping_paths(PlannerInfo *root,
*** 3838,3846 ****
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups));
}
}
}
/* Give a helpful error if we failed to find any implementation */
--- 3809,3895 ----
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups), false);
}
}
+
+ /*
+ * If input_rel has partially aggregated partial paths, perform the
+ * final aggregation.
+ *
+ * TODO Allow havingQual - currently not supported at base relation
+ * level.
+ */
+ if (input_rel->partial_grouped_pathlist != NIL &&
+ !parse->havingQual)
+ {
+ Path *path = (Path *) linitial(input_rel->partial_grouped_pathlist);
+ double total_groups = path->rows * path->parallel_workers;
+
+ path = (Path *) create_gather_path(root,
+ input_rel,
+ path,
+ path->pathtarget,
+ NULL,
+ &total_groups);
+
+ /*
+ * The input path is partially aggregated and the final
+ * aggregation - if the path wins - will be done below. So we're
+ * done with it for now.
+ */
+ add_path(input_rel, path, true);
+ }
+
+ /*
+ * If input_rel has partially aggregated paths, perform the final
+ * aggregation.
+ *
+ * TODO Allow havingQual - currently not supported at base relation
+ * level.
+ */
+ if (input_rel->grouped_pathlist != NIL &&
+ !parse->havingQual)
+ {
+ Path *pre_agg = (Path *) linitial(input_rel->grouped_pathlist);
+ PathTarget *proj_target;
+
+ /*
+ * For each grouped variable in pre_agg->pathtarget ensure
+ * evaluation of expr_intermediate (see GroupedVarInfo).
+ */
+ proj_target =
+ create_intermediate_grouping_target(root,
+ pre_agg->pathtarget);
+ pre_agg = (Path *) create_projection_path(root, input_rel,
+ pre_agg,
+ proj_target);
+
+ dNumGroups = get_number_of_groups(root,
+ pre_agg->rows,
+ rollup_lists,
+ rollup_groupclauses);
+
+ MemSet(&agg_final_costs, 0, sizeof(AggClauseCosts));
+ get_agg_clause_costs(root, (Node *) target->exprs,
+ AGGSPLIT_FINAL_DESERIAL,
+ &agg_final_costs);
+ /* get_agg_clause_costs(root, parse->havingQual, */
+ /* AGGSPLIT_FINAL_DESERIAL, */
+ /* &agg_final_costs); */
+
+ add_path(grouped_rel,
+ (Path *) create_agg_path(root, grouped_rel,
+ pre_agg,
+ target,
+ AGG_HASHED,
+ AGGSPLIT_FINAL_DESERIAL,
+ parse->groupClause,
+ (List *) parse->havingQual,
+ &agg_final_costs,
+ dNumGroups),
+ false);
+ }
}
/* Give a helpful error if we failed to find any implementation */
*************** create_one_window_path(PlannerInfo *root
*** 4053,4059 ****
window_pathkeys);
}
! add_path(window_rel, path);
}
/*
--- 4102,4108 ----
window_pathkeys);
}
! add_path(window_rel, path, false);
}
/*
*************** create_distinct_paths(PlannerInfo *root,
*** 4159,4165 ****
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows));
}
}
--- 4208,4214 ----
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows), false);
}
}
*************** create_distinct_paths(PlannerInfo *root,
*** 4186,4192 ****
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows));
}
/*
--- 4235,4241 ----
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows), false);
}
/*
*************** create_distinct_paths(PlannerInfo *root,
*** 4233,4239 ****
parse->distinctClause,
NIL,
NULL,
! numDistinctRows));
}
/* Give a helpful error if we failed to find any implementation */
--- 4282,4288 ----
parse->distinctClause,
NIL,
NULL,
! numDistinctRows), false);
}
/* Give a helpful error if we failed to find any implementation */
*************** create_ordered_paths(PlannerInfo *root,
*** 4331,4337 ****
path = apply_projection_to_path(root, ordered_rel,
path, target);
! add_path(ordered_rel, path);
}
}
--- 4380,4386 ----
path = apply_projection_to_path(root, ordered_rel,
path, target);
! add_path(ordered_rel, path, false);
}
}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
new file mode 100644
index 413a0d9..632f4bb
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
*************** set_upper_references(PlannerInfo *root,
*** 1677,1686 ****
indexed_tlist *subplan_itlist;
List *output_targetlist;
ListCell *l;
! subplan_itlist = build_tlist_index(subplan->targetlist);
output_targetlist = NIL;
foreach(l, plan->targetlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
--- 1677,1744 ----
indexed_tlist *subplan_itlist;
List *output_targetlist;
ListCell *l;
+ List *sub_tlist_save = NIL;
+ bool install_partial_aggrefs = false;
! if (root->grouped_var_list != NIL)
! {
! if (IsA(plan, Agg))
! {
! Agg *agg = (Agg *) plan;
!
! if (agg->aggsplit == AGGSPLIT_FINAL_DESERIAL)
! {
! /*
! * convert_combining_aggrefs could have replaced some vars
! * with Aggref expressions representing the partial
! * aggregation. We need to restore the same Aggrefs in the
! * subplan targetlist, but this would break the subplan if
! * it's something else than the partial aggregation (i.e. the
! * partial aggregation takes place lower in the plan tree). So
! * we'll need restore the original list when done with the
! * references.
! */
! if (!IsA(subplan, Agg))
! sub_tlist_save = copyObject(subplan->targetlist);
! #ifdef USE_ASSERT_CHECKING
! else
! Assert(((Agg *) subplan)->aggsplit == AGGSPLIT_INITIAL_SERIAL);
! #endif /* USE_ASSERT_CHECKING */
!
! /*
! * Restore the aggregate expressions that we might have
! * removed when planning for aggregation at base relation
! * level.
! *
! * TODO Optimize restore_grouping_expressions using a new
! * parameter indicating whether the targetlist can contain
! * "intermediate expression". Only Result (or also Gather if
! * we use its target list to evaluate "intermediate
! * expressions"?) node should contain those.
! */
! subplan->targetlist =
! restore_grouping_expressions(root, subplan->targetlist);
! }
! else if (agg->aggsplit == AGGSPLIT_INITIAL_SERIAL)
! install_partial_aggrefs = true;
! }
! }
+ /*
+ * AGGSPLIT_INITIAL_SERIAL might be there just to implement grouped base
+ * relation. Since the parent node does not necessarily call
+ * set_upper_references() (note that there might be various nodes between
+ * the initial and the final aggregation), special effort is needed to
+ * ensure that "grouped vars" are replaced with the corresponding
+ * expressions (see GroupedVarInfo).
+ */
+ if (install_partial_aggrefs)
+ plan->targetlist = restore_grouping_expressions(root,
+ plan->targetlist);
+
+ subplan_itlist = build_tlist_index(subplan->targetlist);
output_targetlist = NIL;
+
foreach(l, plan->targetlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
*************** set_upper_references(PlannerInfo *root,
*** 1720,1725 ****
--- 1778,1787 ----
OUTER_VAR,
rtoffset);
+ /* Restore the original list if appropriate. */
+ if (sub_tlist_save != NIL)
+ subplan->targetlist = sub_tlist_save;
+
pfree(subplan_itlist);
}
*************** convert_combining_aggrefs(Node *node, vo
*** 1796,1801 ****
--- 1858,1864 ----
(void *) context);
}
+
/*
* set_dummy_tlist_references
* Replace the targetlist of an upper-level plan node with a simple
*************** fix_join_expr_mutator(Node *node, fix_jo
*** 2235,2240 ****
--- 2298,2304 ----
(void *) context);
}
+
/*
* fix_upper_expr
* Modifies an expression tree so that all Var nodes reference outputs
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
new file mode 100644
index 1bbbc29..e0b31c3
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
*************** plan_set_operations(PlannerInfo *root)
*** 208,214 ****
root->processed_tlist = top_tlist;
/* Add only the final path to the SETOP upperrel. */
! add_path(setop_rel, path);
/* Let extensions possibly add some more paths */
if (create_upper_paths_hook)
--- 208,214 ----
root->processed_tlist = top_tlist;
/* Add only the final path to the SETOP upperrel. */
! add_path(setop_rel, path, false);
/* Let extensions possibly add some more paths */
if (create_upper_paths_hook)
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
new file mode 100644
index 3b7c56d..aefb79e
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
*************** set_cheapest(RelOptInfo *parent_rel)
*** 409,416 ****
* Returns nothing, but modifies parent_rel->pathlist.
*/
void
! add_path(RelOptInfo *parent_rel, Path *new_path)
{
bool accept_new = true; /* unless we find a superior old path */
ListCell *insert_after = NULL; /* where to insert new item */
List *new_path_pathkeys;
--- 409,417 ----
* Returns nothing, but modifies parent_rel->pathlist.
*/
void
! add_path(RelOptInfo *parent_rel, Path *new_path, bool grouped)
{
+ List *pathlist;
bool accept_new = true; /* unless we find a superior old path */
ListCell *insert_after = NULL; /* where to insert new item */
List *new_path_pathkeys;
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 427,432 ****
--- 428,435 ----
/* Pretend parameterized paths have no pathkeys, per comment above */
new_path_pathkeys = new_path->param_info ? NIL : new_path->pathkeys;
+ pathlist = !grouped ? parent_rel->pathlist : parent_rel->grouped_pathlist;
+
/*
* Loop to check proposed new path against old paths. Note it is possible
* for more than one old path to be tossed out because new_path dominates
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 436,442 ****
* list cell.
*/
p1_prev = NULL;
! for (p1 = list_head(parent_rel->pathlist); p1 != NULL; p1 = p1_next)
{
Path *old_path = (Path *) lfirst(p1);
bool remove_old = false; /* unless new proves superior */
--- 439,445 ----
* list cell.
*/
p1_prev = NULL;
! for (p1 = list_head(pathlist); p1 != NULL; p1 = p1_next)
{
Path *old_path = (Path *) lfirst(p1);
bool remove_old = false; /* unless new proves superior */
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 582,589 ****
*/
if (remove_old)
{
! parent_rel->pathlist = list_delete_cell(parent_rel->pathlist,
! p1, p1_prev);
/*
* Delete the data pointed-to by the deleted cell, if possible
--- 585,591 ----
*/
if (remove_old)
{
! pathlist = list_delete_cell(pathlist, p1, p1_prev);
/*
* Delete the data pointed-to by the deleted cell, if possible
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 614,622 ****
{
/* Accept the new path: insert it at proper place in pathlist */
if (insert_after)
! lappend_cell(parent_rel->pathlist, insert_after, new_path);
else
! parent_rel->pathlist = lcons(new_path, parent_rel->pathlist);
}
else
{
--- 616,624 ----
{
/* Accept the new path: insert it at proper place in pathlist */
if (insert_after)
! lappend_cell(pathlist, insert_after, new_path);
else
! pathlist = lcons(new_path, pathlist);
}
else
{
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 624,629 ****
--- 626,636 ----
if (!IsA(new_path, IndexPath))
pfree(new_path);
}
+
+ if (!grouped)
+ parent_rel->pathlist = pathlist;
+ else
+ parent_rel->grouped_pathlist = pathlist;
}
/*
*************** add_path(RelOptInfo *parent_rel, Path *n
*** 646,653 ****
bool
add_path_precheck(RelOptInfo *parent_rel,
Cost startup_cost, Cost total_cost,
! List *pathkeys, Relids required_outer)
{
List *new_path_pathkeys;
bool consider_startup;
ListCell *p1;
--- 653,661 ----
bool
add_path_precheck(RelOptInfo *parent_rel,
Cost startup_cost, Cost total_cost,
! List *pathkeys, Relids required_outer, bool grouped)
{
+ List *pathlist;
List *new_path_pathkeys;
bool consider_startup;
ListCell *p1;
*************** add_path_precheck(RelOptInfo *parent_rel
*** 658,664 ****
/* Decide whether new path's startup cost is interesting */
consider_startup = required_outer ? parent_rel->consider_param_startup : parent_rel->consider_startup;
! foreach(p1, parent_rel->pathlist)
{
Path *old_path = (Path *) lfirst(p1);
PathKeysComparison keyscmp;
--- 666,674 ----
/* Decide whether new path's startup cost is interesting */
consider_startup = required_outer ? parent_rel->consider_param_startup : parent_rel->consider_startup;
! pathlist = !grouped ? parent_rel->pathlist : parent_rel->grouped_pathlist;
!
! foreach(p1, pathlist)
{
Path *old_path = (Path *) lfirst(p1);
PathKeysComparison keyscmp;
*************** add_path_precheck(RelOptInfo *parent_rel
*** 750,772 ****
* isn't an IndexPath.
*/
void
! add_partial_path(RelOptInfo *parent_rel, Path *new_path)
{
bool accept_new = true; /* unless we find a superior old path */
ListCell *insert_after = NULL; /* where to insert new item */
ListCell *p1;
ListCell *p1_prev;
ListCell *p1_next;
/* Check for query cancel. */
CHECK_FOR_INTERRUPTS();
/*
* As in add_path, throw out any paths which are dominated by the new
* path, but throw out the new path if some existing path dominates it.
*/
p1_prev = NULL;
! for (p1 = list_head(parent_rel->partial_pathlist); p1 != NULL;
p1 = p1_next)
{
Path *old_path = (Path *) lfirst(p1);
--- 760,786 ----
* isn't an IndexPath.
*/
void
! add_partial_path(RelOptInfo *parent_rel, Path *new_path, bool grouped)
{
bool accept_new = true; /* unless we find a superior old path */
ListCell *insert_after = NULL; /* where to insert new item */
ListCell *p1;
ListCell *p1_prev;
ListCell *p1_next;
+ List *pathlist;
/* Check for query cancel. */
CHECK_FOR_INTERRUPTS();
+ pathlist = !grouped ? parent_rel->partial_pathlist :
+ parent_rel->partial_grouped_pathlist;
+
/*
* As in add_path, throw out any paths which are dominated by the new
* path, but throw out the new path if some existing path dominates it.
*/
p1_prev = NULL;
! for (p1 = list_head(pathlist); p1 != NULL;
p1 = p1_next)
{
Path *old_path = (Path *) lfirst(p1);
*************** add_partial_path(RelOptInfo *parent_rel,
*** 820,831 ****
}
/*
! * Remove current element from partial_pathlist if dominated by new.
*/
if (remove_old)
{
! parent_rel->partial_pathlist =
! list_delete_cell(parent_rel->partial_pathlist, p1, p1_prev);
/* we should not see IndexPaths here, so always safe to delete */
Assert(!IsA(old_path, IndexPath));
pfree(old_path);
--- 834,844 ----
}
/*
! * Remove current element from pathlist if dominated by new.
*/
if (remove_old)
{
! pathlist = list_delete_cell(pathlist, p1, p1_prev);
/* we should not see IndexPaths here, so always safe to delete */
Assert(!IsA(old_path, IndexPath));
pfree(old_path);
*************** add_partial_path(RelOptInfo *parent_rel,
*** 842,848 ****
/*
* If we found an old path that dominates new_path, we can quit
! * scanning the partial_pathlist; we will not add new_path, and we
* assume new_path cannot dominate any later path.
*/
if (!accept_new)
--- 855,861 ----
/*
* If we found an old path that dominates new_path, we can quit
! * scanning the pathlist; we will not add new_path, and we
* assume new_path cannot dominate any later path.
*/
if (!accept_new)
*************** add_partial_path(RelOptInfo *parent_rel,
*** 853,862 ****
{
/* Accept the new path: insert it at proper place */
if (insert_after)
! lappend_cell(parent_rel->partial_pathlist, insert_after, new_path);
else
! parent_rel->partial_pathlist =
! lcons(new_path, parent_rel->partial_pathlist);
}
else
{
--- 866,874 ----
{
/* Accept the new path: insert it at proper place */
if (insert_after)
! lappend_cell(pathlist, insert_after, new_path);
else
! pathlist = lcons(new_path, pathlist);
}
else
{
*************** add_partial_path(RelOptInfo *parent_rel,
*** 865,870 ****
--- 877,887 ----
/* Reject and recycle the new path */
pfree(new_path);
}
+
+ if (!grouped)
+ parent_rel->partial_pathlist = pathlist;
+ else
+ parent_rel->partial_grouped_pathlist = pathlist;
}
/*
*************** add_partial_path(RelOptInfo *parent_rel,
*** 879,887 ****
*/
bool
add_partial_path_precheck(RelOptInfo *parent_rel, Cost total_cost,
! List *pathkeys)
{
ListCell *p1;
/*
* Our goal here is twofold. First, we want to find out whether this path
--- 896,906 ----
*/
bool
add_partial_path_precheck(RelOptInfo *parent_rel, Cost total_cost,
! List *pathkeys, bool grouped)
{
ListCell *p1;
+ List *pathlist = !grouped ? parent_rel->partial_pathlist :
+ parent_rel->partial_grouped_pathlist;
/*
* Our goal here is twofold. First, we want to find out whether this path
*************** add_partial_path_precheck(RelOptInfo *pa
*** 891,900 ****
* final cost computations. If so, we definitely want to consider it.
*
* Unlike add_path(), we always compare pathkeys here. This is because we
! * expect partial_pathlist to be very short, and getting a definitive
! * answer at this stage avoids the need to call add_path_precheck.
*/
! foreach(p1, parent_rel->partial_pathlist)
{
Path *old_path = (Path *) lfirst(p1);
PathKeysComparison keyscmp;
--- 910,920 ----
* final cost computations. If so, we definitely want to consider it.
*
* Unlike add_path(), we always compare pathkeys here. This is because we
! * expect partial_pathlist / grouped_pathlist to be very short, and
! * getting a definitive answer at this stage avoids the need to call
! * add_path_precheck.
*/
! foreach(p1, pathlist)
{
Path *old_path = (Path *) lfirst(p1);
PathKeysComparison keyscmp;
*************** add_partial_path_precheck(RelOptInfo *pa
*** 923,929 ****
* completion.
*/
if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! NULL))
return false;
return true;
--- 943,949 ----
* completion.
*/
if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! NULL, false))
return false;
return true;
*************** create_hashjoin_path(PlannerInfo *root,
*** 2108,2120 ****
Path *inner_path,
List *restrict_clauses,
Relids required_outer,
! List *hashclauses)
{
HashPath *pathnode = makeNode(HashPath);
pathnode->jpath.path.pathtype = T_HashJoin;
pathnode->jpath.path.parent = joinrel;
! pathnode->jpath.path.pathtarget = joinrel->reltarget;
pathnode->jpath.path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
--- 2128,2142 ----
Path *inner_path,
List *restrict_clauses,
Relids required_outer,
! List *hashclauses,
! bool grouped)
{
HashPath *pathnode = makeNode(HashPath);
pathnode->jpath.path.pathtype = T_HashJoin;
pathnode->jpath.path.parent = joinrel;
! pathnode->jpath.path.pathtarget = !grouped ?
! joinrel->reltarget : joinrel->reltarget_grouped;
pathnode->jpath.path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
new file mode 100644
index 7a8674d..126120f
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
*************** typedef struct JoinHashEntry
*** 33,39 ****
} JoinHashEntry;
static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! RelOptInfo *input_rel);
static List *build_joinrel_restrictlist(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outer_rel,
--- 33,39 ----
} JoinHashEntry;
static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! RelOptInfo *input_rel, bool grouped);
static List *build_joinrel_restrictlist(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outer_rel,
*************** build_simple_rel(PlannerInfo *root, int
*** 106,114 ****
--- 106,117 ----
rel->consider_param_startup = false; /* might get changed later */
rel->consider_parallel = false; /* might get changed later */
rel->reltarget = create_empty_pathtarget();
+ /* Not all relations have this target. */
+ rel->reltarget_grouped = NULL;
rel->pathlist = NIL;
rel->ppilist = NIL;
rel->partial_pathlist = NIL;
+ rel->partial_grouped_pathlist = NIL;
rel->cheapest_startup_path = NULL;
rel->cheapest_total_path = NULL;
rel->cheapest_unique_path = NULL;
*************** build_join_rel(PlannerInfo *root,
*** 371,379 ****
--- 374,385 ----
joinrel->consider_param_startup = false;
joinrel->consider_parallel = false;
joinrel->reltarget = create_empty_pathtarget();
+ /* Not all joins have this target. */
+ joinrel->reltarget_grouped = NULL;
joinrel->pathlist = NIL;
joinrel->ppilist = NIL;
joinrel->partial_pathlist = NIL;
+ joinrel->partial_grouped_pathlist = NIL;
joinrel->cheapest_startup_path = NULL;
joinrel->cheapest_total_path = NULL;
joinrel->cheapest_unique_path = NULL;
*************** build_join_rel(PlannerInfo *root,
*** 459,468 ****
* and inner rels we first try to build it from. But the contents should
* be the same regardless.
*/
! build_joinrel_tlist(root, joinrel, outer_rel);
! build_joinrel_tlist(root, joinrel, inner_rel);
add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel);
/*
* add_placeholders_to_joinrel also took care of adding the ph_lateral
* sets of any PlaceHolderVars computed here to direct_lateral_relids, so
--- 465,488 ----
* and inner rels we first try to build it from. But the contents should
* be the same regardless.
*/
! build_joinrel_tlist(root, joinrel, outer_rel, false);
! build_joinrel_tlist(root, joinrel, inner_rel, false);
add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel);
+ /* Build reltarget_grouped if appropriate. */
+ if (outer_rel->reltarget_grouped && inner_rel->reltarget_grouped &&
+ root->all_baserels_grouped)
+ {
+ build_joinrel_tlist(root, joinrel, outer_rel, true);
+ build_joinrel_tlist(root, joinrel, inner_rel, true);
+
+ /*
+ * No need to add PHVs - if there were some, it'd mean that the join
+ * is below nullable side of outer join, in which case no
+ * pre-aggregation should take place.
+ */
+ }
+
/*
* add_placeholders_to_joinrel also took care of adding the ph_lateral
* sets of any PlaceHolderVars computed here to direct_lateral_relids, so
*************** min_join_parameterization(PlannerInfo *r
*** 607,622 ****
*/
static void
build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! RelOptInfo *input_rel)
{
Relids relids = joinrel->relids;
ListCell *vars;
! foreach(vars, input_rel->reltarget->exprs)
{
Var *var = (Var *) lfirst(vars);
RelOptInfo *baserel;
int ndx;
/*
* Ignore PlaceHolderVars in the input tlists; we'll make our own
--- 627,665 ----
*/
static void
build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! RelOptInfo *input_rel, bool grouped)
{
Relids relids = joinrel->relids;
+ PathTarget *reltarget_input, *reltarget_result;
ListCell *vars;
+ int i = -1;
! if (!grouped)
! {
! reltarget_input = input_rel->reltarget;
! reltarget_result = joinrel->reltarget;
! }
! else
! {
! reltarget_input = input_rel->reltarget_grouped;
! /* Shouldn't be called otherwise. */
! Assert(reltarget_input != NULL);
!
! /* Called first time for this joinrel? */
! if (joinrel->reltarget_grouped == NULL)
! joinrel->reltarget_grouped = create_empty_pathtarget();
!
! reltarget_result = joinrel->reltarget_grouped;
! }
!
! foreach(vars, reltarget_input->exprs)
{
Var *var = (Var *) lfirst(vars);
RelOptInfo *baserel;
int ndx;
+ Index sortgroupref = 0;
+
+ i++;
/*
* Ignore PlaceHolderVars in the input tlists; we'll make our own
*************** build_joinrel_tlist(PlannerInfo *root, R
*** 642,650 ****
if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
{
/* Yup, add it to the output */
! joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs, var);
/* Vars have cost zero, so no need to adjust reltarget->cost */
! joinrel->reltarget->width += baserel->attr_widths[ndx];
}
}
}
--- 685,697 ----
if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
{
/* Yup, add it to the output */
! if (reltarget_input->sortgrouprefs)
! sortgroupref = reltarget_input->sortgrouprefs[i];
! add_column_to_pathtarget(reltarget_result, (Expr *) var,
! sortgroupref);
!
/* Vars have cost zero, so no need to adjust reltarget->cost */
! reltarget_result->width += baserel->attr_widths[ndx];
}
}
}
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
new file mode 100644
index 45205a8..af1856c
*** a/src/backend/optimizer/util/tlist.c
--- b/src/backend/optimizer/util/tlist.c
***************
*** 14,22 ****
--- 14,28 ----
*/
#include "postgres.h"
+ /*
+ * TODO Consider moving COUNTFNOID away from pg_aggregate.h so it doesn't
+ * have to be included.
+ */
+ #include "catalog/pg_aggregate.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/tlist.h"
+ #include "optimizer/planner.h"
/*****************************************************************************
*************** apply_pathtarget_labeling_to_tlist(List
*** 759,761 ****
--- 765,1012 ----
i++;
}
}
+
+ /*
+ * Replace each "grouped var" in the source target with its "intermediate
+ * expression" if one exists, i.e. apply the effect of joins to the result of
+ * partial aggregation emitted by base relation.
+ */
+ PathTarget *
+ create_intermediate_grouping_target(PlannerInfo *root, PathTarget *src)
+ {
+ PathTarget *result = create_empty_pathtarget();
+ int i = 0;
+ ListCell *l;
+
+ foreach(l, src->exprs)
+ {
+ Expr *expr, *expr_new;
+ Index sortgroupref = 0;
+
+ if (src->sortgrouprefs)
+ sortgroupref = src->sortgrouprefs[i];
+
+ expr_new = expr = (Expr *) lfirst(l);
+ if (IsA(expr, Var))
+ {
+ Var *var = (Var *) expr;
+ ListCell *lc;
+
+ foreach(lc, root->grouped_var_list)
+ {
+ GroupedVarInfo *gvi = (GroupedVarInfo *) lfirst(lc);
+
+ if (gvi->expr_intermediate &&
+ gvi->var->varno == var->varno &&
+ gvi->var->varattno == var->varattno)
+ {
+ /* XXX Need a copy? */
+ expr_new = gvi->expr_intermediate;
+ break;
+ }
+ }
+ }
+ add_column_to_pathtarget(result, expr_new, sortgroupref);
+
+ i++;
+ }
+
+ return result;
+ }
+
+ /*
+ * Replace each "grouped var" in the source targetlist with the original
+ * expression. This includes the items already replaced by "intermediate
+ * expression", see create_intermediate_grouping_target.
+ *
+ * TODO Think of more suitable name. Although "grouped var" may substitute for
+ * grouping expressions in the future, currently Aggref is the only outcome of
+ * the replacement. undo_grouped_var_substitutions?
+ */
+ List *
+ restore_grouping_expressions(PlannerInfo *root, List *src)
+ {
+ List *result = NIL;
+ ListCell *l;
+
+ foreach(l, src)
+ {
+ TargetEntry *te, *te_new;
+ Expr *expr_new = NULL;
+
+ te = (TargetEntry *) lfirst(l);
+
+ if (IsA(te->expr, Var))
+ {
+ Var *var = (Var *) te->expr;
+
+ expr_new = find_grouped_var_expr(root, var);
+ }
+ else
+ {
+ /* Is this the "intermediate expression"? */
+ ListCell *lc;
+
+ foreach(lc, root->grouped_var_list)
+ {
+ GroupedVarInfo *gvi = (GroupedVarInfo *) lfirst(lc);
+
+ /*
+ * If expr_intermediate was valid, the var should already have
+ * been replaced by the "intermediate expression" (which
+ * should be non-Var).
+ */
+ if (gvi->expr_intermediate != NULL &&
+ equal(te->expr, gvi->expr_intermediate))
+ {
+ /* XXX Need a copy? */
+ expr_new = gvi->expr;
+ break;
+ }
+ }
+ }
+ if (expr_new != NULL)
+ {
+ te_new = flatCopyTargetEntry(te);
+ te_new->expr = expr_new;
+ }
+ else
+ te_new = te;
+ result = lappend(result, te_new);
+ }
+
+ return result;
+ }
+
+ /*
+ * Find the expression that we've replaced with grouped var earlier.
+ */
+ Expr *
+ find_grouped_var_expr(PlannerInfo *root, Var *var)
+ {
+ ListCell *lc;
+
+ foreach(lc, root->grouped_var_list)
+ {
+ GroupedVarInfo *gvi = (GroupedVarInfo *) lfirst(lc);
+
+ if (gvi->var->varno == var->varno &&
+ gvi->var->varattno == var->varattno)
+ {
+ /* XXX Need a copy? */
+ return gvi->expr;
+ }
+ }
+
+ return NULL;
+ }
+
+ /*
+ * Add each aggregate to "grouped target" of a relation in the form of special
+ * variable, for which GroupedVarInfo will also be added to
+ * root->grouped_var_list.
+ *
+ * It doesn't matter that the relation contains no such variables - they will
+ * be replaced with the partially-aggregated Aggref before execution starts -
+ * see restore_grouped_var_expr.
+ *
+ * countagg represents count(*) aggregate that each grouped rel must have to
+ * collect information of per-group row count. This information is used to
+ * adjust the transient state in the output of the final join.
+ *
+ * Variable representing the result of countagg is added to *countagg_vars
+ * list (besides being added to root->grouped_var_list as well, so that it the
+ * actual count(*) expression be restored by set_upper_references.)
+ */
+ void
+ add_aggregates_to_grouped_target(PlannerInfo *root, List *aggregates,
+ RelOptInfo *rel, Aggref *countagg,
+ List **countagg_vars, bool mark_partial)
+ {
+ ListCell *lc;
+ bool first = true;
+
+ foreach(lc, aggregates)
+ {
+ Aggref *aggref = (Aggref *) lfirst(lc);
+ AttrNumber varattno;
+ Var *var;
+ GroupedVarInfo *gvi;
+
+ rel->max_attr++;
+ varattno = rel->max_attr;
+
+ var = makeVar(rel->relid, varattno, InvalidOid, 0, InvalidOid, 0);
+ add_new_column_to_pathtarget(rel->reltarget_grouped, (Expr *) var);
+
+ /*
+ * Create writable copy so that it can be marked as partial, and
+ * eventually be associated with the final aggregate - see
+ * set_upper_references. (XXX Is copy necessary for the count(*)
+ * aggregate as well?)
+ */
+ aggref = copyObject(aggref);
+
+ if (first)
+ {
+ /* count(*) should be the first item of the list. */
+ Assert(aggref->aggfnoid == COUNTFNOID && aggref->aggstar);
+
+ /*
+ * The variable representing the auxiliary count(*) is not subject
+ * to the final aggregation (in fact the result is used as
+ * argument of aggtransmultifn, which prepares input value for the
+ * final aggregationq), so there's never reason to restore the
+ * count(*) expression. Moreover, restoration of the count(*)
+ * expression outside Aggref (e.g. in the targetlist of the final
+ * join) would cause ERROR during plan initialization.
+ *
+ * So instead of creating GroupedVarInfo we only add the var to a
+ * list of vars that GroupedVarInfo.expr_intermediate can
+ * reference.
+ */
+ if (countagg_vars)
+ *countagg_vars = lappend(*countagg_vars, var);
+
+ first = false;
+ }
+
+ /*
+ * Associate the expression with the corresponding variable, so that
+ * set_upper_references can do the changes explained above.
+ */
+ gvi = (GroupedVarInfo *) palloc0(sizeof(GroupedVarInfo));
+ gvi->var = var;
+
+ /* The variable represents result of the partial aggregation. */
+ if (mark_partial)
+ mark_partial_aggref(aggref, AGGSPLIT_INITIAL_SERIAL);
+ /* The variable type info should reflect the transient type. */
+ var->vartype = exprType((Node *) aggref);
+ var->vartypmod = exprTypmod((Node *) aggref);
+ var->varcollid = exprCollation((Node *) aggref);
+
+ gvi->expr = (Expr *) aggref;
+ gvi->sortgroupref = 0;
+ root->grouped_var_list = lappend(root->grouped_var_list, gvi);
+
+ /*
+ * Add the variable to rel->attr_needed to ensure propagation to the
+ * top-level target list. (Non-grouped path are not aware of the
+ * variable, and they should already be generated by now.)
+ */
+ rel->attr_needed = (Relids *)
+ repalloc(rel->attr_needed, (rel->max_attr - rel->min_attr + 1) *
+ sizeof(Relids));
+ rel->attr_needed[varattno - rel->min_attr] = bms_make_singleton(0);
+
+ rel->attr_widths = (int32 *)
+ repalloc(rel->attr_widths, (rel->max_attr - rel->min_attr + 1) *
+ sizeof(int32));
+ /*
+ * TODO Determine the width and make sure it's used in the grouped
+ * target.
+ */
+ rel->attr_widths[varattno - rel->min_attr] = 0;
+ }
+ }
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
new file mode 100644
index 1297960..ed521bb
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 95,100 ****
--- 95,101 ----
FuncDetailCode fdresult;
char aggkind = 0;
ParseCallbackState pcbstate;
+ Oid aggtransmultifn = InvalidOid;
/*
* If there's an aggregate filter, transform it using transformWhereClause
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 318,323 ****
--- 319,325 ----
classForm = (Form_pg_aggregate) GETSTRUCT(tup);
aggkind = classForm->aggkind;
catDirectArgs = classForm->aggnumdirectargs;
+ aggtransmultifn = classForm->aggtransmultifn;
ReleaseSysCache(tup);
/* Now check various disallowed cases. */
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 662,667 ****
--- 664,670 ----
aggref->aggstar = agg_star;
aggref->aggvariadic = func_variadic;
aggref->aggkind = aggkind;
+ aggref->aggtransmultifn = aggtransmultifn;
/* agglevelsup will be set by transformAggregateCall */
aggref->aggsplit = AGGSPLIT_SIMPLE; /* planner might change this */
aggref->location = location;
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
new file mode 100644
index 86b46de..be7ef22
*** a/src/backend/utils/adt/float.c
--- b/src/backend/utils/adt/float.c
*************** float4_accum(PG_FUNCTION_ARGS)
*** 2630,2635 ****
--- 2630,2663 ----
}
}
+ /*
+ * State multiplication function for sum(float4) aggregate.
+ */
+ Datum
+ float4_sum_mul(PG_FUNCTION_ARGS)
+ {
+ float4 state = PG_GETARG_FLOAT4(0);
+ int64 factor;
+
+ #ifndef USE_FLOAT8_BYVAL /* controls int8 too */
+ {
+ int64 *factor_p = (int64 *) PG_GETARG_POINTER(1);
+
+ factor = *factor_p;
+ }
+ #else
+ factor = PG_GETARG_INT64(1);
+ #endif
+
+ /*
+ * Sum of a single group is added each time the same input set is
+ * aggregated again.
+ */
+ state *= (float8) factor;
+
+ PG_RETURN_FLOAT4(state);
+ }
+
Datum
float8_avg(PG_FUNCTION_ARGS)
{
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
new file mode 100644
index 301dffa..944a23e
*** a/src/backend/utils/adt/selfuncs.c
--- b/src/backend/utils/adt/selfuncs.c
***************
*** 111,116 ****
--- 111,117 ----
#include "catalog/pg_statistic.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
+ #include "executor/nodeAgg.h"
#include "mb/pg_wchar.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
*************** estimate_num_groups(PlannerInfo *root, L
*** 3426,3432 ****
/*
* Sanity check --- don't divide by zero if empty relation.
*/
! Assert(rel->reloptkind == RELOPT_BASEREL);
if (rel->tuples > 0)
{
/*
--- 3427,3434 ----
/*
* Sanity check --- don't divide by zero if empty relation.
*/
! Assert(rel->reloptkind == RELOPT_BASEREL ||
! rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
if (rel->tuples > 0)
{
/*
*************** estimate_hash_bucketsize(PlannerInfo *ro
*** 3657,3662 ****
--- 3659,3692 ----
return (Selectivity) estfract;
}
+ /*
+ * estimate_hashagg_tablesize
+ * estimate the number of bytes that a hash aggregate hashtable will
+ * require based on the agg_costs, path width and dNumGroups.
+ */
+ Size
+ estimate_hashagg_tablesize(Path *path, const AggClauseCosts *agg_costs,
+ double dNumGroups)
+ {
+ Size hashentrysize;
+
+ /* Estimate per-hash-entry space at tuple width... */
+ hashentrysize = MAXALIGN(path->pathtarget->width) +
+ MAXALIGN(SizeofMinimalTupleHeader);
+
+ /* plus space for pass-by-ref transition values... */
+ hashentrysize += agg_costs->transitionSpace;
+ /* plus the per-hash-entry overhead */
+ hashentrysize += hash_agg_entry_size(agg_costs->numAggs);
+
+ /*
+ * Note that this disregards the effect of fill-factor and growth policy
+ * of the hash-table. That's probably ok, given default the default
+ * fill-factor is relatively high. It'd be hard to meaningfully factor in
+ * "double-in-size" growth policies here.
+ */
+ return hashentrysize * dNumGroups;
+ }
/*-------------------------------------------------------------------------
*
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
new file mode 100644
index 1ffde6c..f0b5498
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
***************
*** 32,37 ****
--- 32,38 ----
* aggkind aggregate kind, see AGGKIND_ categories below
* aggnumdirectargs number of arguments that are "direct" arguments
* aggtransfn transition function
+ * aggtransmultifn transition state multiplication function
* aggfinalfn final function (0 if none)
* aggcombinefn combine function (0 if none)
* aggserialfn function to convert transtype to bytea (0 if none)
*************** CATALOG(pg_aggregate,2600) BKI_WITHOUT_O
*** 58,63 ****
--- 59,65 ----
char aggkind;
int16 aggnumdirectargs;
regproc aggtransfn;
+ regproc aggtransmultifn;
regproc aggfinalfn;
regproc aggcombinefn;
regproc aggserialfn;
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 91,117 ****
* ----------------
*/
! #define Natts_pg_aggregate 20
#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_aggcombinefn 6
! #define Anum_pg_aggregate_aggserialfn 7
! #define Anum_pg_aggregate_aggdeserialfn 8
! #define Anum_pg_aggregate_aggmtransfn 9
! #define Anum_pg_aggregate_aggminvtransfn 10
! #define Anum_pg_aggregate_aggmfinalfn 11
! #define Anum_pg_aggregate_aggfinalextra 12
! #define Anum_pg_aggregate_aggmfinalextra 13
! #define Anum_pg_aggregate_aggsortop 14
! #define Anum_pg_aggregate_aggtranstype 15
! #define Anum_pg_aggregate_aggtransspace 16
! #define Anum_pg_aggregate_aggmtranstype 17
! #define Anum_pg_aggregate_aggmtransspace 18
! #define Anum_pg_aggregate_agginitval 19
! #define Anum_pg_aggregate_aggminitval 20
/*
* Symbolic values for aggkind column. We distinguish normal aggregates
--- 93,120 ----
* ----------------
*/
! #define Natts_pg_aggregate 21
#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_aggtransmultifn 5
! #define Anum_pg_aggregate_aggfinalfn 6
! #define Anum_pg_aggregate_aggcombinefn 7
! #define Anum_pg_aggregate_aggserialfn 8
! #define Anum_pg_aggregate_aggdeserialfn 9
! #define Anum_pg_aggregate_aggmtransfn 10
! #define Anum_pg_aggregate_aggminvtransfn 11
! #define Anum_pg_aggregate_aggmfinalfn 12
! #define Anum_pg_aggregate_aggfinalextra 13
! #define Anum_pg_aggregate_aggmfinalextra 14
! #define Anum_pg_aggregate_aggsortop 15
! #define Anum_pg_aggregate_aggtranstype 16
! #define Anum_pg_aggregate_aggtransspace 17
! #define Anum_pg_aggregate_aggmtranstype 18
! #define Anum_pg_aggregate_aggmtransspace 19
! #define Anum_pg_aggregate_agginitval 20
! #define Anum_pg_aggregate_aggminitval 21
/*
* Symbolic values for aggkind column. We distinguish normal aggregates
*************** typedef FormData_pg_aggregate *Form_pg_a
*** 135,318 ****
*/
/* avg */
! DATA(insert ( 2100 n 0 int8_avg_accum numeric_poly_avg int8_avg_combine int8_avg_serialize int8_avg_deserialize 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_combine - - 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 int4_avg_combine - - 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_combine numeric_avg_serialize numeric_avg_deserialize 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 float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2105 n 0 float8_accum float8_avg float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2106 n 0 interval_accum interval_avg interval_combine - - 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_combine int8_avg_serialize int8_avg_deserialize 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_combine numeric_avg_serialize numeric_avg_deserialize numeric_avg_accum numeric_accum_inv numeric_sum f f 0 2281 128 2281 128 _null_ _null_ ));
/* max */
! 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 - 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 - 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 numeric_combine numeric_serialize numeric_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2722 n 0 float8_accum float8_var_pop float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2723 n 0 numeric_accum numeric_var_pop numeric_combine numeric_serialize numeric_deserialize 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 numeric_combine numeric_serialize numeric_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2645 n 0 float8_accum float8_var_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2646 n 0 numeric_accum numeric_var_samp numeric_combine numeric_serialize numeric_deserialize 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 numeric_combine numeric_serialize numeric_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2152 n 0 float8_accum float8_var_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2153 n 0 numeric_accum numeric_var_samp numeric_combine numeric_serialize numeric_deserialize 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 numeric_combine numeric_serialize numeric_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2728 n 0 float8_accum float8_stddev_pop float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2729 n 0 numeric_accum numeric_stddev_pop numeric_combine numeric_serialize numeric_deserialize 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 numeric_combine numeric_serialize numeric_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2716 n 0 float8_accum float8_stddev_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2717 n 0 numeric_accum numeric_stddev_samp numeric_combine numeric_serialize numeric_deserialize 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 numeric_combine numeric_serialize numeric_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2158 n 0 float8_accum float8_stddev_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2159 n 0 numeric_accum numeric_stddev_samp numeric_combine numeric_serialize numeric_deserialize 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 - int8pl - - - - - f f 0 20 0 0 0 "0" _null_ ));
! DATA(insert ( 2819 n 0 float8_regr_accum float8_regr_sxx float8_regr_combine - - - - - 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 float8_regr_combine - - - - - 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 float8_regr_combine - - - - - 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 float8_regr_combine - - - - - 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 float8_regr_combine - - - - - 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 float8_regr_combine - - - - - 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 float8_regr_combine - - - - - 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 float8_regr_combine - - - - - 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 float8_regr_combine - - - - - 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 float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2829 n 0 float8_regr_accum float8_corr float8_regr_combine - - - - - 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 - 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 - 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 - 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 - 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_ ));
/* 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_ ));
/* text */
! 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_ ));
/* 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_ ));
/* 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_ ));
/* 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_ ));
/*
--- 138,322 ----
*/
/* avg */
! DATA(insert ( 2100 n 0 int8_avg_accum 0 numeric_poly_avg int8_avg_combine int8_avg_serialize int8_avg_deserialize 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 0 int8_avg int4_avg_combine - - 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 0 int8_avg int4_avg_combine - - 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 0 numeric_avg numeric_avg_combine numeric_avg_serialize numeric_avg_deserialize numeric_avg_accum numeric_accum_inv numeric_avg f f 0 2281 128 2281 128 _null_ _null_ ));
! DATA(insert ( 2104 n 0 float4_accum 0 float8_avg float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2105 n 0 float8_accum 0 float8_avg float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2106 n 0 interval_accum 0 interval_avg interval_combine - - 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 0 numeric_poly_sum int8_avg_combine int8_avg_serialize int8_avg_deserialize 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 0 - 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 0 - 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 4002 - float4pl - - - - - f f 0 700 0 0 0 _null_ _null_ ));
! DATA(insert ( 2111 n 0 float8pl 0 - float8pl - - - - - f f 0 701 0 0 0 _null_ _null_ ));
! DATA(insert ( 2112 n 0 cash_pl 0 - cash_pl - - cash_pl cash_mi - f f 0 790 0 790 0 _null_ _null_ ));
! DATA(insert ( 2113 n 0 interval_pl 0 - interval_pl - - interval_pl interval_mi - f f 0 1186 0 1186 0 _null_ _null_ ));
! DATA(insert ( 2114 n 0 numeric_avg_accum 0 numeric_sum numeric_avg_combine numeric_avg_serialize numeric_avg_deserialize numeric_avg_accum numeric_accum_inv numeric_sum f f 0 2281 128 2281 128 _null_ _null_ ));
/* max */
! DATA(insert ( 2115 n 0 int8larger 0 - int8larger - - - - - f f 413 20 0 0 0 _null_ _null_ ));
! DATA(insert ( 2116 n 0 int4larger 0 - int4larger - - - - - f f 521 23 0 0 0 _null_ _null_ ));
! DATA(insert ( 2117 n 0 int2larger 0 - int2larger - - - - - f f 520 21 0 0 0 _null_ _null_ ));
! DATA(insert ( 2118 n 0 oidlarger 0 - oidlarger - - - - - f f 610 26 0 0 0 _null_ _null_ ));
! DATA(insert ( 2119 n 0 float4larger 0 - float4larger - - - - - f f 623 700 0 0 0 _null_ _null_ ));
! DATA(insert ( 2120 n 0 float8larger 0 - float8larger - - - - - f f 674 701 0 0 0 _null_ _null_ ));
! DATA(insert ( 2121 n 0 int4larger 0 - int4larger - - - - - f f 563 702 0 0 0 _null_ _null_ ));
! DATA(insert ( 2122 n 0 date_larger 0 - date_larger - - - - - f f 1097 1082 0 0 0 _null_ _null_ ));
! DATA(insert ( 2123 n 0 time_larger 0 - time_larger - - - - - f f 1112 1083 0 0 0 _null_ _null_ ));
! DATA(insert ( 2124 n 0 timetz_larger 0 - timetz_larger - - - - - f f 1554 1266 0 0 0 _null_ _null_ ));
! DATA(insert ( 2125 n 0 cashlarger 0 - cashlarger - - - - - f f 903 790 0 0 0 _null_ _null_ ));
! DATA(insert ( 2126 n 0 timestamp_larger 0 - timestamp_larger - - - - - f f 2064 1114 0 0 0 _null_ _null_ ));
! DATA(insert ( 2127 n 0 timestamptz_larger 0 - timestamptz_larger - - - - - f f 1324 1184 0 0 0 _null_ _null_ ));
! DATA(insert ( 2128 n 0 interval_larger 0 - interval_larger - - - - - f f 1334 1186 0 0 0 _null_ _null_ ));
! DATA(insert ( 2129 n 0 text_larger 0 - text_larger - - - - - f f 666 25 0 0 0 _null_ _null_ ));
! DATA(insert ( 2130 n 0 numeric_larger 0 - numeric_larger - - - - - f f 1756 1700 0 0 0 _null_ _null_ ));
! DATA(insert ( 2050 n 0 array_larger 0 - array_larger - - - - - f f 1073 2277 0 0 0 _null_ _null_ ));
! DATA(insert ( 2244 n 0 bpchar_larger 0 - bpchar_larger - - - - - f f 1060 1042 0 0 0 _null_ _null_ ));
! DATA(insert ( 2797 n 0 tidlarger 0 - tidlarger - - - - - f f 2800 27 0 0 0 _null_ _null_ ));
! DATA(insert ( 3526 n 0 enum_larger 0 - enum_larger - - - - - f f 3519 3500 0 0 0 _null_ _null_ ));
! DATA(insert ( 3564 n 0 network_larger 0 - network_larger - - - - - f f 1205 869 0 0 0 _null_ _null_ ));
/* min */
! DATA(insert ( 2131 n 0 int8smaller 0 - int8smaller - - - - - f f 412 20 0 0 0 _null_ _null_ ));
! DATA(insert ( 2132 n 0 int4smaller 0 - int4smaller - - - - - f f 97 23 0 0 0 _null_ _null_ ));
! DATA(insert ( 2133 n 0 int2smaller 0 - int2smaller - - - - - f f 95 21 0 0 0 _null_ _null_ ));
! DATA(insert ( 2134 n 0 oidsmaller 0 - oidsmaller - - - - - f f 609 26 0 0 0 _null_ _null_ ));
! DATA(insert ( 2135 n 0 float4smaller 0 - float4smaller - - - - - f f 622 700 0 0 0 _null_ _null_ ));
! DATA(insert ( 2136 n 0 float8smaller 0 - float8smaller - - - - - f f 672 701 0 0 0 _null_ _null_ ));
! DATA(insert ( 2137 n 0 int4smaller 0 - int4smaller - - - - - f f 562 702 0 0 0 _null_ _null_ ));
! DATA(insert ( 2138 n 0 date_smaller 0 - date_smaller - - - - - f f 1095 1082 0 0 0 _null_ _null_ ));
! DATA(insert ( 2139 n 0 time_smaller 0 - time_smaller - - - - - f f 1110 1083 0 0 0 _null_ _null_ ));
! DATA(insert ( 2140 n 0 timetz_smaller 0 - timetz_smaller - - - - - f f 1552 1266 0 0 0 _null_ _null_ ));
! DATA(insert ( 2141 n 0 cashsmaller 0 - cashsmaller - - - - - f f 902 790 0 0 0 _null_ _null_ ));
! DATA(insert ( 2142 n 0 timestamp_smaller 0 - timestamp_smaller - - - - - f f 2062 1114 0 0 0 _null_ _null_ ));
! DATA(insert ( 2143 n 0 timestamptz_smaller 0 - timestamptz_smaller - - - - - f f 1322 1184 0 0 0 _null_ _null_ ));
! DATA(insert ( 2144 n 0 interval_smaller 0 - interval_smaller - - - - - f f 1332 1186 0 0 0 _null_ _null_ ));
! DATA(insert ( 2145 n 0 text_smaller 0 - text_smaller - - - - - f f 664 25 0 0 0 _null_ _null_ ));
! DATA(insert ( 2146 n 0 numeric_smaller 0 - numeric_smaller - - - - - f f 1754 1700 0 0 0 _null_ _null_ ));
! DATA(insert ( 2051 n 0 array_smaller 0 - array_smaller - - - - - f f 1072 2277 0 0 0 _null_ _null_ ));
! DATA(insert ( 2245 n 0 bpchar_smaller 0 - bpchar_smaller - - - - - f f 1058 1042 0 0 0 _null_ _null_ ));
! DATA(insert ( 2798 n 0 tidsmaller 0 - tidsmaller - - - - - f f 2799 27 0 0 0 _null_ _null_ ));
! DATA(insert ( 3527 n 0 enum_smaller 0 - enum_smaller - - - - - f f 3518 3500 0 0 0 _null_ _null_ ));
! DATA(insert ( 3565 n 0 network_smaller 0 - network_smaller - - - - - f f 1203 869 0 0 0 _null_ _null_ ));
/* count */
! DATA(insert ( 2147 n 0 int8inc_any 0 - int8pl - - int8inc_any int8dec_any - f f 0 20 0 20 0 "0" "0" ));
! DATA(insert ( 2803 n 0 int8inc 0 - int8pl - - int8inc int8dec - f f 0 20 0 20 0 "0" "0" ));
! #define COUNTFNOID 2803
/* var_pop */
! DATA(insert ( 2718 n 0 int8_accum 0 numeric_var_pop numeric_combine numeric_serialize numeric_deserialize int8_accum int8_accum_inv numeric_var_pop f f 0 2281 128 2281 128 _null_ _null_ ));
! DATA(insert ( 2719 n 0 int4_accum 0 numeric_poly_var_pop numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 numeric_poly_var_pop numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 float8_var_pop float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2722 n 0 float8_accum 0 float8_var_pop float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2723 n 0 numeric_accum 0 numeric_var_pop numeric_combine numeric_serialize numeric_deserialize 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 0 numeric_var_samp numeric_combine numeric_serialize numeric_deserialize int8_accum int8_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
! DATA(insert ( 2642 n 0 int4_accum 0 numeric_poly_var_samp numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 numeric_poly_var_samp numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 float8_var_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2645 n 0 float8_accum 0 float8_var_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2646 n 0 numeric_accum 0 numeric_var_samp numeric_combine numeric_serialize numeric_deserialize 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 0 numeric_var_samp numeric_combine numeric_serialize numeric_deserialize int8_accum int8_accum_inv numeric_var_samp f f 0 2281 128 2281 128 _null_ _null_ ));
! DATA(insert ( 2149 n 0 int4_accum 0 numeric_poly_var_samp numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 numeric_poly_var_samp numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 float8_var_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2152 n 0 float8_accum 0 float8_var_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2153 n 0 numeric_accum 0 numeric_var_samp numeric_combine numeric_serialize numeric_deserialize 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 0 numeric_stddev_pop numeric_combine numeric_serialize numeric_deserialize int8_accum int8_accum_inv numeric_stddev_pop f f 0 2281 128 2281 128 _null_ _null_ ));
! DATA(insert ( 2725 n 0 int4_accum 0 numeric_poly_stddev_pop numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 numeric_poly_stddev_pop numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 float8_stddev_pop float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2728 n 0 float8_accum 0 float8_stddev_pop float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2729 n 0 numeric_accum 0 numeric_stddev_pop numeric_combine numeric_serialize numeric_deserialize 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 0 numeric_stddev_samp numeric_combine numeric_serialize numeric_deserialize int8_accum int8_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
! DATA(insert ( 2713 n 0 int4_accum 0 numeric_poly_stddev_samp numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 numeric_poly_stddev_samp numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 float8_stddev_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2716 n 0 float8_accum 0 float8_stddev_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2717 n 0 numeric_accum 0 numeric_stddev_samp numeric_combine numeric_serialize numeric_deserialize 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 0 numeric_stddev_samp numeric_combine numeric_serialize numeric_deserialize int8_accum int8_accum_inv numeric_stddev_samp f f 0 2281 128 2281 128 _null_ _null_ ));
! DATA(insert ( 2155 n 0 int4_accum 0 numeric_poly_stddev_samp numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 numeric_poly_stddev_samp numeric_poly_combine numeric_poly_serialize numeric_poly_deserialize 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 0 float8_stddev_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2158 n 0 float8_accum 0 float8_stddev_samp float8_combine - - - - - f f 0 1022 0 0 0 "{0,0,0}" _null_ ));
! DATA(insert ( 2159 n 0 numeric_accum 0 numeric_stddev_samp numeric_combine numeric_serialize numeric_deserialize 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 0 - int8pl - - - - - f f 0 20 0 0 0 "0" _null_ ));
! DATA(insert ( 2819 n 0 float8_regr_accum 0 float8_regr_sxx float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2820 n 0 float8_regr_accum 0 float8_regr_syy float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2821 n 0 float8_regr_accum 0 float8_regr_sxy float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2822 n 0 float8_regr_accum 0 float8_regr_avgx float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2823 n 0 float8_regr_accum 0 float8_regr_avgy float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2824 n 0 float8_regr_accum 0 float8_regr_r2 float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2825 n 0 float8_regr_accum 0 float8_regr_slope float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2826 n 0 float8_regr_accum 0 float8_regr_intercept float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2827 n 0 float8_regr_accum 0 float8_covar_pop float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2828 n 0 float8_regr_accum 0 float8_covar_samp float8_regr_combine - - - - - f f 0 1022 0 0 0 "{0,0,0,0,0,0}" _null_ ));
! DATA(insert ( 2829 n 0 float8_regr_accum 0 float8_corr float8_regr_combine - - - - - 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 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 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 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 0 - int2and - - - - - f f 0 21 0 0 0 _null_ _null_ ));
! DATA(insert ( 2237 n 0 int2or 0 - int2or - - - - - f f 0 21 0 0 0 _null_ _null_ ));
! DATA(insert ( 2238 n 0 int4and 0 - int4and - - - - - f f 0 23 0 0 0 _null_ _null_ ));
! DATA(insert ( 2239 n 0 int4or 0 - int4or - - - - - f f 0 23 0 0 0 _null_ _null_ ));
! DATA(insert ( 2240 n 0 int8and 0 - int8and - - - - - f f 0 20 0 0 0 _null_ _null_ ));
! DATA(insert ( 2241 n 0 int8or 0 - int8or - - - - - f f 0 20 0 0 0 _null_ _null_ ));
! DATA(insert ( 2242 n 0 bitand 0 - bitand - - - - - f f 0 1560 0 0 0 _null_ _null_ ));
! DATA(insert ( 2243 n 0 bitor 0 - bitor - - - - - f f 0 1560 0 0 0 _null_ _null_ ));
/* xml */
! DATA(insert ( 2901 n 0 xmlconcat2 0 - - - - - - - f f 0 142 0 0 0 _null_ _null_ ));
/* array */
! DATA(insert ( 2335 n 0 array_agg_transfn 0 array_agg_finalfn - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 4053 n 0 array_agg_array_transfn 0 array_agg_array_finalfn - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
/* text */
! DATA(insert ( 3538 n 0 string_agg_transfn 0 string_agg_finalfn - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
/* bytea */
! DATA(insert ( 3545 n 0 bytea_string_agg_transfn 0 bytea_string_agg_finalfn - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
/* json */
! DATA(insert ( 3175 n 0 json_agg_transfn 0 json_agg_finalfn - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3197 n 0 json_object_agg_transfn 0 json_object_agg_finalfn - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
/* jsonb */
! DATA(insert ( 3267 n 0 jsonb_agg_transfn 0 jsonb_agg_finalfn - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3270 n 0 jsonb_object_agg_transfn 0 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 0 percentile_disc_final - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3974 o 1 ordered_set_transition 0 percentile_cont_float8_final - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3976 o 1 ordered_set_transition 0 percentile_cont_interval_final - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3978 o 1 ordered_set_transition 0 percentile_disc_multi_final - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3980 o 1 ordered_set_transition 0 percentile_cont_float8_multi_final - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3982 o 1 ordered_set_transition 0 percentile_cont_interval_multi_final - - - - - - f f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3984 o 0 ordered_set_transition 0 mode_final - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3986 h 1 ordered_set_transition_multi 0 rank_final - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3988 h 1 ordered_set_transition_multi 0 percent_rank_final - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3990 h 1 ordered_set_transition_multi 0 cume_dist_final - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
! DATA(insert ( 3992 h 1 ordered_set_transition_multi 0 dense_rank_final - - - - - - t f 0 2281 0 0 0 _null_ _null_ ));
/*
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
new file mode 100644
index aeb7927..3095380
*** a/src/include/catalog/pg_operator.h
--- b/src/include/catalog/pg_operator.h
*************** DATA(insert OID = 685 ( "-" PGNSP PG
*** 547,552 ****
--- 547,553 ----
DESCR("subtract");
DATA(insert OID = 686 ( "*" PGNSP PGUID b f f 20 20 20 686 0 int8mul - - ));
DESCR("multiply");
+ #define OPERATOR_INT8MUL 686
DATA(insert OID = 687 ( "/" PGNSP PGUID b f f 20 20 20 0 0 int8div - - ));
DESCR("divide");
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
new file mode 100644
index 37e022d..51d1b74
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DESCR("pg_controldata recovery state inf
*** 5345,5350 ****
--- 5345,5353 ----
DATA(insert OID = 3444 ( pg_control_init PGNSP PGUID 12 1 0 0 0 f f f f t f v s 0 0 2249 "" "{23,23,23,23,23,23,23,23,23,16,16,16,23}" "{o,o,o,o,o,o,o,o,o,o,o,o,o}" "{max_data_alignment,database_block_size,blocks_per_segment,wal_block_size,bytes_per_wal_segment,max_identifier_length,max_index_columns,max_toast_chunk_size,large_object_chunk_size,bigint_timestamps,float4_pass_by_value,float8_pass_by_value,data_page_checksum_version}" _null_ _null_ pg_control_init _null_ _null_ _null_ ));
DESCR("pg_controldata init state information as a function");
+ DATA(insert OID = 4002 ( float4_sum_mul PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 1022 "1022 20" _null_ _null_ _null_ _null_ _null_ float4_sum_mul _null_ _null_ _null_ ));
+ DESCR("aggregate state multiplication function");
+
/*
* Symbolic values for provolatile column: these indicate whether the result
* of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
new file mode 100644
index f72ec24..dd76d9b
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
*************** typedef struct Aggref
*** 284,289 ****
--- 284,290 ----
bool aggvariadic; /* true if variadic arguments have been
* combined into an array last argument */
char aggkind; /* aggregate kind (see pg_aggregate.h) */
+ Oid aggtransmultifn;/* pg_aggregate(aggtransmultifn) */
Index agglevelsup; /* > 0 if agg belongs to outer query */
AggSplit aggsplit; /* expected agg-splitting mode of parent Agg */
int location; /* token location, or -1 if unknown */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
new file mode 100644
index e1d31c7..1e8ef3b
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct PlannerInfo
*** 252,257 ****
--- 252,262 ----
List *placeholder_list; /* list of PlaceHolderInfos */
+ /* TODO Consider hashing. */
+ List *grouped_var_list; /* List of GroupedVarInfos. */
+
+ bool all_baserels_grouped; /* Should grouped rels be joined? */
+
List *fkey_list; /* list of ForeignKeyOptInfos */
List *query_pathkeys; /* desired pathkeys for query_planner() */
*************** typedef struct RelOptInfo
*** 495,504 ****
--- 500,516 ----
/* default result targetlist for Paths scanning this relation */
struct PathTarget *reltarget; /* list of Vars/Exprs, cost, width */
+ /* result targetlist if the base relation is grouped */
+ struct PathTarget *reltarget_grouped;
+
/* materialization information */
List *pathlist; /* Path structures */
+ List *grouped_pathlist; /* paths with partial aggregation already
+ * done. */
List *ppilist; /* ParamPathInfos used in pathlist */
List *partial_pathlist; /* partial Paths */
+ List *partial_grouped_pathlist; /* partial Paths with partial
+ * aggregation already done. */
struct Path *cheapest_startup_path;
struct Path *cheapest_total_path;
struct Path *cheapest_unique_path;
*************** typedef struct PlaceHolderInfo
*** 1905,1910 ****
--- 1917,1951 ----
} PlaceHolderInfo;
/*
+ * Variable of a "grouped relation" represents an aggregate or grouping
+ * expression that it evaluates. Input values of such expressions are not
+ * available above this relation, so the values of such expressions are passed
+ * as variables. Each variable-to-expression association is stored as an item
+ * of root->grouped_var_list and used by set_upper_references.
+ */
+ typedef struct GroupedVarInfo
+ {
+ /* The variable added to base relation instead of aggregate. */
+ Var *var;
+
+ /*
+ * The expression whose value this "grouped var" represents in the
+ * target list of the (partially) grouped relation.
+ */
+ Expr *expr;
+
+ /*
+ * If aggtransmultifn function (see Aggref) must be applied before the
+ * final aggregation, expr_intermediate contains the function call. It'll
+ * replace the variable in targetlist of the Result node that prepares
+ * input for the final aggregation.
+ */
+ Expr *expr_intermediate;
+
+ Index sortgroupref;
+ } GroupedVarInfo;
+
+ /*
* This struct describes one potentially index-optimizable MIN/MAX aggregate
* function. MinMaxAggPath contains a list of these, and if we accept that
* path, the list is stored into root->minmax_aggs for use during setrefs.c.
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
new file mode 100644
index d16f879..54b84c7
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern int compare_path_costs(Path *path
*** 25,37 ****
extern int compare_fractional_path_costs(Path *path1, Path *path2,
double fraction);
extern void set_cheapest(RelOptInfo *parent_rel);
! extern void add_path(RelOptInfo *parent_rel, Path *new_path);
extern bool add_path_precheck(RelOptInfo *parent_rel,
Cost startup_cost, Cost total_cost,
! List *pathkeys, Relids required_outer);
! extern void add_partial_path(RelOptInfo *parent_rel, Path *new_path);
extern bool add_partial_path_precheck(RelOptInfo *parent_rel,
! Cost total_cost, List *pathkeys);
extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer, int parallel_workers);
--- 25,39 ----
extern int compare_fractional_path_costs(Path *path1, Path *path2,
double fraction);
extern void set_cheapest(RelOptInfo *parent_rel);
! extern void add_path(RelOptInfo *parent_rel, Path *new_path, bool grouped);
extern bool add_path_precheck(RelOptInfo *parent_rel,
Cost startup_cost, Cost total_cost,
! List *pathkeys, Relids required_outer, bool grouped);
! extern void add_partial_path(RelOptInfo *parent_rel, Path *new_path,
! bool grouped);
extern bool add_partial_path_precheck(RelOptInfo *parent_rel,
! Cost total_cost, List *pathkeys,
! bool grouped);
extern Path *create_seqscan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer, int parallel_workers);
*************** extern HashPath *create_hashjoin_path(Pl
*** 134,140 ****
Path *inner_path,
List *restrict_clauses,
Relids required_outer,
! List *hashclauses);
extern ProjectionPath *create_projection_path(PlannerInfo *root,
RelOptInfo *rel,
--- 136,143 ----
Path *inner_path,
List *restrict_clauses,
Relids required_outer,
! List *hashclauses,
! bool grouped);
extern ProjectionPath *create_projection_path(PlannerInfo *root,
RelOptInfo *rel,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
new file mode 100644
index 480f25f..0d29bec
*** a/src/include/optimizer/paths.h
--- b/src/include/optimizer/paths.h
*************** extern void set_dummy_rel_pathlist(RelOp
*** 52,58 ****
extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,
List *initial_rels);
! extern void generate_gather_paths(PlannerInfo *root, RelOptInfo *rel);
#ifdef OPTIMIZER_DEBUG
extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel);
--- 52,59 ----
extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,
List *initial_rels);
! extern void generate_gather_paths(PlannerInfo *root, RelOptInfo *rel,
! bool grouped);
#ifdef OPTIMIZER_DEBUG
extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
new file mode 100644
index 8468b0c..dd2529b
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
*************** extern void add_base_rels_to_query(Plann
*** 75,80 ****
--- 75,81 ----
extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
Relids where_needed, bool create_new_ph);
+ extern void build_base_rel_tlists_grouped(PlannerInfo *root);
extern void find_lateral_references(PlannerInfo *root);
extern void create_lateral_join_info(PlannerInfo *root);
extern List *deconstruct_jointree(PlannerInfo *root);
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
new file mode 100644
index f80b31a..9d2c097
*** a/src/include/optimizer/tlist.h
--- b/src/include/optimizer/tlist.h
*************** extern void add_column_to_pathtarget(Pat
*** 61,67 ****
extern void add_new_column_to_pathtarget(PathTarget *target, Expr *expr);
extern void add_new_columns_to_pathtarget(PathTarget *target, List *exprs);
extern void apply_pathtarget_labeling_to_tlist(List *tlist, PathTarget *target);
!
/* Convenience macro to get a PathTarget with valid cost/width fields */
#define create_pathtarget(root, tlist) \
set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
--- 61,76 ----
extern void add_new_column_to_pathtarget(PathTarget *target, Expr *expr);
extern void add_new_columns_to_pathtarget(PathTarget *target, List *exprs);
extern void apply_pathtarget_labeling_to_tlist(List *tlist, PathTarget *target);
! extern PathTarget *create_intermediate_grouping_target(PlannerInfo *root,
! PathTarget *src);
! extern List *restore_grouping_expressions(PlannerInfo *root, List *src);
! extern Expr *find_grouped_var_expr(PlannerInfo *root, Var *var);
! extern void add_aggregates_to_grouped_target(PlannerInfo *root,
! List *aggregates,
! RelOptInfo *rel,
! Aggref *countagg,
! List **countagg_vars,
! bool mark_partial);
/* Convenience macro to get a PathTarget with valid cost/width fields */
#define create_pathtarget(root, tlist) \
set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
new file mode 100644
index e1bb344..628b142
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum float84le(PG_FUNCTION_ARGS)
*** 475,480 ****
--- 475,481 ----
extern Datum float84gt(PG_FUNCTION_ARGS);
extern Datum float84ge(PG_FUNCTION_ARGS);
extern Datum width_bucket_float8(PG_FUNCTION_ARGS);
+ extern Datum float4_sum_mul(PG_FUNCTION_ARGS);
/* dbsize.c */
extern Datum pg_tablespace_size_oid(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
new file mode 100644
index b43dae7..39f5dee
*** a/src/include/utils/selfuncs.h
--- b/src/include/utils/selfuncs.h
*************** extern double estimate_num_groups(Planne
*** 232,237 ****
--- 232,240 ----
extern Selectivity estimate_hash_bucketsize(PlannerInfo *root, Node *hashkey,
double nbuckets);
+ extern Size estimate_hashagg_tablesize(Path *path,
+ const AggClauseCosts *agg_costs,
+ double dNumGroups);
extern List *deconstruct_indexquals(IndexPath *path);
extern void genericcostestimate(PlannerInfo *root, IndexPath *path,
On Fri, Jan 13, 2017 at 10:12 PM, Antonin Houska <ah@cybertec.at> wrote:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote:
On Mon, Jan 9, 2017 at 11:26 PM, Antonin Houska <ah@cybertec.at> wrote:
Attached is a draft patch that lets partial aggregation happen at base
relation level. If the relations contain relatively small number of groups,
the number of input rows of the aggregation at the query level can be reduced
this way. Also, if append relation and postgres_fdw planning is enhanced
accordingly, patch like this can let us aggregate individual tables on remote
servers (e.g. shard nodes) and thus reduce the amount of rows subject to the
final aggregation.For an appendrel probably you need an ability to switch group->append into
append->groupYes, like the new patch version (see attachment) does:
postgres=# EXPLAIN (COSTS false) SELECT b.j, sum(a.x) FROM a JOIN b ON a.i = b.j GROUP BY b.j;
QUERY PLAN
------------------------------------------------------
Finalize HashAggregate
Group Key: b.j
-> Hash Join
Hash Cond: (a.i = b.j)
-> Append
-> Partial HashAggregate
Group Key: a.i
-> Seq Scan on a
-> Partial HashAggregate
Group Key: a_1.i
-> Seq Scan on a_1
-> Partial HashAggregate
Group Key: a_2.i
-> Seq Scan on a_2
-> Hash
-> Gather
Workers Planned: 1
-> Partial HashAggregate
Group Key: b.j
-> Parallel Seq Scan on bFor postgres_fdw, we already support aggregate pushdown.
My understanding is that currently it only works if the whole query can be
evaluated by the FDW. What I try to do is to push down aggregation of
individual table, and join the partially-aggregated set with other tables,
which are not necessarily remote or reside on different remote server.
You will need to invoke FDW's hook for aggregate pushdown for the base
relations. It would work as long as we don't ask it transient results.
But I guess, that can come later.
But we don't support fetching partial aggregates from foreign server. What
other enhancements do you need?Here I try to introduce infrastructure for aggregation pushdown and
propagation of the transient aggregate state values from base relations to the
final join. postgres_fdw can benefit from it but it's not the only use case,
so I'd prefer adjusting it in a separate patch.Yes, an outstanding problem is that the remote nodes need to return transient
state values - probably using bytea type. I think this functionality should
even be separate from postgres_fdw (e.g. a new contrib module?), because the
remote nodes do not need postgres_fdw.
Hmm, that's a missing piece. We need to work on it separately.
A few "footnotes":
As for the example, the processing continues by joining the partially grouped
sets:i | sum(x)| count(i.*) | j | count(j.*)
----------------------------------------
1 | 7 | 2 | 1 | 2[ Sorry, count(j.*) should be 2, not 3 as I wrote in the initial email. ]
Before performing the final aggregation, we need to multiply sum(a.x) by
count(j.*) because w/o the aggregation at base relation level the input
of the query-level aggregation would look likea.i | a.x | b.j
----------------
1 | 3 | 1
1 | 4 | 1
1 | 3 | 1
1 | 4 | 1In other words, grouping of the base relation "b" below the join prevents the
join from bringing per-group input set to the aggregate input multiple
times. To compensate for this effect, I've added a new field "aggtransmultifn"
to pg_aggregate catalog. It "multiplies" the aggregate state w/o repeated
processing of the same input set many times. sum() is an example of an
aggregate that needs such processing, avg() is one that does not.For something like product aggregation, where the product (or higher order
operations) across rows is accumulated instead of sum, mere multiplication
wouldn't help. We will need some higher order operation to "extrapolate" the
result based on count(j.*). In fact, the multiplication factor will depend
upon the number of relations being joined E.g. select b.j, sum(a.x) where
a, b, c where a.i = b.j and a.i = c.k group by b.jMaybe you're saying what I already try to do. Let me modify the example
accordingly (unlike the initial example, each table produces a group of
different size so the the count() values are harder to confuse):
[... snip ]]
This all works well, as long as the aggregate is "summing" something
across rows. The method doesn't work when aggregation is say
"multiplying" across the rows or "concatenating" across the rows like
array_agg() or string_agg(). They need a different strategy to combine
aggregates across relations.
I get exactly these values when I run your query on master branch w/o my
patch, so my theory could be correct :-)May be we want to implement this technique without partial aggregation first
i.e. push down aggregation and grouping down the join tree and then add
partial aggregation steps. That might make it easy to review. Looking at
the changes in create_plain_partial_paths(), it looks like we use this
technique only in case of parallel query. I think the technique is useful
otherwise as well.
IIUC, we are trying to solve multiple problems here:
1. Pushing down aggregates/groups down join tree, so that the number
of rows to be joined decreases.
This might be a good optimization to have. However there are problems
in the current patch. Every path built for a relation (join or base)
returns the same result expressed by the relation or its subset
restricted by parameterization or unification. But this patch changes
that. It creates paths which represent grouping in the base relation.
I think, we need a separate relation to represent that result and hold
paths which produce that result. That itself would be a sizable patch.
2. Try to push down aggregates based on the equivalence classes, where
grouping properties can be transferred from one relation to the other
using EC mechanism. This seems to require solving the problem of
combining aggregates across the relations. But there might be some
usecases which could benefit without solving this problem.
3. If the relation to which we push the aggregate is an append
relation, push (partial) aggregation/grouping down into the child
relations. - We don't do that right now even for grouping aggregation
on a single append table. Parallel partial aggregation does that, but
not exactly per relation. That may be a sizable project in itself.
Even without this piece the rest of the optimizations proposed by this
patch are important.
4. Additional goal: push down the aggregation to any relation
(join/base) where it can be computed.
If we break the problem down into smaller problems as above, 1. the
resulting patches will be easier to review 2. Since those problems
themselves produce some usable feature, there is a chance that more
people will be interested in reviewing/testing/coding on those.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 10 January 2017 at 06:56, Antonin Houska <ah@cybertec.at> wrote:
Before performing the final aggregation, we need to multiply sum(a.x) by
count(j.*) because w/o the aggregation at base relation level the input
of the query-level aggregation would look likea.i | a.x | b.j
----------------
1 | 3 | 1
1 | 4 | 1
1 | 3 | 1
1 | 4 | 1In other words, grouping of the base relation "b" below the join prevents the
join from bringing per-group input set to the aggregate input multiple
times. To compensate for this effect, I've added a new field "aggtransmultifn"
to pg_aggregate catalog. It "multiplies" the aggregate state w/o repeated
processing of the same input set many times. sum() is an example of an
aggregate that needs such processing, avg() is one that does not.
First off, I'd like to say that making improvements in this area
sounds like a great thing. I'm very keen to see progress here.
I've been thinking about this aggtransmultifn and I'm not sure if it's
really needed. Adding a whole series of new transition functions is
quite a pain. At least I think so, and I have a feeling Robert might
agree with me.
Let's imagine some worst case (and somewhat silly) aggregate query:
SELECT count(*)
FROM million_row_table
CROSS JOIN another_million_row_table;
Today that's going to cause 1 TRILLION transitions! Performance will
be terrible.
If we pushed the aggregate down into one of those tables and performed
a Partial Aggregate on that, then a Finalize Aggregate on that single
row result (after the join), then that's 1 million transfn calls, and
1 million combinefn calls, one for each row produced by the join.
If we did it your way (providing I understand your proposal correctly)
there's 1 million transfn calls on one relation, then 1 million on the
other and then 1 multiplyfn call. which does 1000000 * 1000000
What did we save vs. using the existing aggcombinefn infrastructure
which went into 9.6? Using this actually costs us 1 extra function
call, right? I'd imagine the size of the patch to use aggcombinefn
instead would be a fraction of the size of the one which included all
the new aggmultiplyfns and pg_aggregate.h changes.
There's already a lot of infrastructure in there to help you detect
when this optimisation can be applied. For example,
AggClauseCosts.hasNonPartial will be true if any aggregates don't have
a combinefn, or if there's any DISTINCT or ORDER BY aggregates,
which'll also mean you can't apply the optimisation.
It sounds like a much more manageable project by using aggcombinefn
instead. Then maybe one day when we can detect if a join did not cause
any result duplication (i.e Unique Joins), we could finalise the
aggregates on the first call, and completely skip the combine state
altogether.
Thanks for your work on this.
Regards
David Rowley
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 01/17/2017 12:42 AM, David Rowley wrote:
On 10 January 2017 at 06:56, Antonin Houska <ah@cybertec.at> wrote:
Before performing the final aggregation, we need to multiply sum(a.x) by
count(j.*) because w/o the aggregation at base relation level the input
of the query-level aggregation would look likea.i | a.x | b.j
----------------
1 | 3 | 1
1 | 4 | 1
1 | 3 | 1
1 | 4 | 1In other words, grouping of the base relation "b" below the join prevents the
join from bringing per-group input set to the aggregate input multiple
times. To compensate for this effect, I've added a new field "aggtransmultifn"
to pg_aggregate catalog. It "multiplies" the aggregate state w/o repeated
processing of the same input set many times. sum() is an example of an
aggregate that needs such processing, avg() is one that does not.First off, I'd like to say that making improvements in this area
sounds like a great thing. I'm very keen to see progress here.
+1
Pushing down aggregates would be very useful for analytical queries.
I've been thinking about this aggtransmultifn and I'm not sure if it's
really needed. Adding a whole series of new transition functions is
quite a pain. At least I think so, and I have a feeling Robert might
agree with me.Let's imagine some worst case (and somewhat silly) aggregate query:
SELECT count(*)
FROM million_row_table
CROSS JOIN another_million_row_table;Today that's going to cause 1 TRILLION transitions! Performance will
be terrible.If we pushed the aggregate down into one of those tables and performed
a Partial Aggregate on that, then a Finalize Aggregate on that single
row result (after the join), then that's 1 million transfn calls, and
1 million combinefn calls, one for each row produced by the join.If we did it your way (providing I understand your proposal correctly)
there's 1 million transfn calls on one relation, then 1 million on the
other and then 1 multiplyfn call. which does 1000000 * 1000000What did we save vs. using the existing aggcombinefn infrastructure
which went into 9.6? Using this actually costs us 1 extra function
call, right? I'd imagine the size of the patch to use aggcombinefn
instead would be a fraction of the size of the one which included all
the new aggmultiplyfns and pg_aggregate.h changes.
I think the patch relies on the assumption that the grouping reduces
cardinality, so a CROSS JOIN without a GROUP BY clause may not be the
best counterexample.
Let's instead use an example similar to what Antonin mentioned in the
initial post - two tables, with two columns each.
CREATE TABLE t1 (a INT, b INT);
CREATE TABLE t2 (c INT, d INT);
And let's assume each table has 100.000 rows, but only 100 groups in the
first column, with 1000 rows per group. Something like
INSERT INTO t1
SELECT mod(i,100), i FROM generate_series(1, 1e5) s(i);
INSERT INTO t2
SELECT mod(i,100), i FROM generate_series(1, 1e5) s(i);
And let's assume this query
SELECT t1.a, count(t2.d) FROM t1 JOIN t2 ON (t1.a = t2.c)
GROUP BY t1.a;
On master, EXPLAIN (COSTS OFF, TIMING OFF, ANALYZE) looks like this:
QUERY PLAN
-----------------------------------------------------------------
HashAggregate (actual rows=100 loops=1)
Group Key: t1.a
-> Hash Join (actual rows=100000000 loops=1)
Hash Cond: (t2.c = t1.a)
-> Seq Scan on t2 (actual rows=100000 loops=1)
-> Hash (actual rows=100000 loops=1)
Buckets: 131072 Batches: 2 Memory Usage: 2716kB
-> Seq Scan on t1 (actual rows=100000 loops=1)
Planning time: 0.167 ms
Execution time: 17005.300 ms
(10 rows)
while with the patch it looks like this
QUERY PLAN
---------------------------------------------------------------------
Finalize HashAggregate (actual rows=100 loops=1)
Group Key: t1.a
-> Hash Join (actual rows=100 loops=1)
Hash Cond: (t1.a = t2.c)
-> Partial HashAggregate (actual rows=100 loops=1)
Group Key: t1.a
-> Seq Scan on t1 (actual rows=100000 loops=1)
-> Hash (actual rows=100 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 14kB
-> Partial HashAggregate (actual rows=100 loops=1)
Group Key: t2.c
-> Seq Scan on t2 (actual rows=100000 loops=1)
Planning time: 0.105 ms
Execution time: 31.609 ms
(14 rows)
This of course happens because with the patch we run the transition
function 200k-times (on each side of the join) and aggtransmultifn on
the 100 rows produced by the join, while on master the join produces
10.000.000 rows (which already takes much more time), and then have to
run the transition function on all those rows.
The performance difference is pretty obvious, I guess.
There's already a lot of infrastructure in there to help you detect
when this optimisation can be applied. For example,
AggClauseCosts.hasNonPartial will be true if any aggregates don't have
a combinefn, or if there's any DISTINCT or ORDER BY aggregates,
which'll also mean you can't apply the optimisation.It sounds like a much more manageable project by using aggcombinefn
instead. Then maybe one day when we can detect if a join did not cause
any result duplication (i.e Unique Joins), we could finalise the
aggregates on the first call, and completely skip the combine state
altogether.
I don't quite see how the patch could use aggcombinefn without
sacrificing a lot of the benefits. Sure, it's possible to run the
aggcombinefn in a loop (with number of iterations determined by the
group size on the other side of the join), but that sounds pretty
expensive and eliminates the reduction of transition function calls. The
join cardinality would still be reduced, though.
I do have other question about the patch, however. It seems to rely on
the fact that the grouping and joins both reference the same columns. I
wonder how uncommon such queries are.
To give a reasonable example, imagine the typical start schema, which is
pretty standard for large analytical databases. A dimension table is
"products" and the fact table is "sales", and the schema might look like
this:
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT,
category_id INT,
producer_id INT
);
CREATE TABLE sales (
product_id REFERENCES products (id),
nitems INT,
price NUMERIC
);
A typical query then looks like this:
SELECT category_id, SUM(nitems), SUM(price)
FROM products p JOIN sales s ON (p.id = s.product_id)
GROUP BY p.category_id;
which obviously uses different columns for the grouping and join, and so
the patch won't help with that. Of course, a query grouping by
product_id would allow the patch to work
SELECT category_id, SUM(nitems), SUM(price)
FROM products p JOIN sales s ON (p.id = s.product_id)
GROUP BY p.product_id;
Another thing is that in my experience most queries do joins on foreign
keys (so the PK side is unique by definition), so the benefit on
practical examples is likely much smaller.
But I guess my main question is if there are actual examples of queries
the patch is trying to improve, or whether the general benefit is
allowing parallel plans for queries where it would not be possible
otherwise.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 17 January 2017 at 16:30, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:
Let's instead use an example similar to what Antonin mentioned in the
initial post - two tables, with two columns each.CREATE TABLE t1 (a INT, b INT);
CREATE TABLE t2 (c INT, d INT);And let's assume each table has 100.000 rows, but only 100 groups in the
first column, with 1000 rows per group. Something likeINSERT INTO t1
SELECT mod(i,100), i FROM generate_series(1, 1e5) s(i);INSERT INTO t2
SELECT mod(i,100), i FROM generate_series(1, 1e5) s(i);And let's assume this query
SELECT t1.a, count(t2.d) FROM t1 JOIN t2 ON (t1.a = t2.c)
GROUP BY t1.a;On master, EXPLAIN (COSTS OFF, TIMING OFF, ANALYZE) looks like this:
QUERY PLAN
-----------------------------------------------------------------
HashAggregate (actual rows=100 loops=1)
Group Key: t1.a
-> Hash Join (actual rows=100000000 loops=1)
Hash Cond: (t2.c = t1.a)
-> Seq Scan on t2 (actual rows=100000 loops=1)
-> Hash (actual rows=100000 loops=1)
Buckets: 131072 Batches: 2 Memory Usage: 2716kB
-> Seq Scan on t1 (actual rows=100000 loops=1)
Planning time: 0.167 ms
Execution time: 17005.300 ms
(10 rows)while with the patch it looks like this
QUERY PLAN
---------------------------------------------------------------------
Finalize HashAggregate (actual rows=100 loops=1)
Group Key: t1.a
-> Hash Join (actual rows=100 loops=1)
Hash Cond: (t1.a = t2.c)
-> Partial HashAggregate (actual rows=100 loops=1)
Group Key: t1.a
-> Seq Scan on t1 (actual rows=100000 loops=1)
-> Hash (actual rows=100 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 14kB
-> Partial HashAggregate (actual rows=100 loops=1)
Group Key: t2.c
-> Seq Scan on t2 (actual rows=100000 loops=1)
Planning time: 0.105 ms
Execution time: 31.609 ms
(14 rows)This of course happens because with the patch we run the transition function
200k-times (on each side of the join) and aggtransmultifn on the 100 rows
produced by the join, while on master the join produces 10.000.000 rows
(which already takes much more time), and then have to run the transition
function on all those rows.The performance difference is pretty obvious, I guess.
An exceptional improvement.
For the combine aggregate example of this query, since no patch exists
yet, we could simply mock what the planner would do by rewriting the
query. I'll use SUM() in-place of the combinefn for COUNT():
explain analyze SELECT t1.a, sum(t2.d) FROM t1 join (SELECT c,count(d)
d from t2 group by c) t2 on t1.a = t2.c group by t1.a;
this seems to be 100,000 aggtransfn calls (for t2), then 100,000
aggcombinefn calls (for t1) (total = 200,000), where as the patch
would perform 100,000 aggtransfn calls (for t2), then 100,000
aggtransfn calls (for t1), then 100 aggtransmultifn calls (total =
200,100)
Is my maths ok?
I don't quite see how the patch could use aggcombinefn without sacrificing a
lot of the benefits. Sure, it's possible to run the aggcombinefn in a loop
(with number of iterations determined by the group size on the other side of
the join), but that sounds pretty expensive and eliminates the reduction of
transition function calls. The join cardinality would still be reduced,
though.
I'd be interested in seeing the run time of my example query above. I
can't quite see a reason for it to be slower, but please let me know.
I do have other question about the patch, however. It seems to rely on the
fact that the grouping and joins both reference the same columns. I wonder
how uncommon such queries are.To give a reasonable example, imagine the typical start schema, which is
pretty standard for large analytical databases. A dimension table is
"products" and the fact table is "sales", and the schema might look like
this:CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT,
category_id INT,
producer_id INT
);CREATE TABLE sales (
product_id REFERENCES products (id),
nitems INT,
price NUMERIC
);A typical query then looks like this:
SELECT category_id, SUM(nitems), SUM(price)
FROM products p JOIN sales s ON (p.id = s.product_id)
GROUP BY p.category_id;which obviously uses different columns for the grouping and join, and so the
patch won't help with that. Of course, a query grouping by product_id would
allow the patch to workSELECT category_id, SUM(nitems), SUM(price)
FROM products p JOIN sales s ON (p.id = s.product_id)
GROUP BY p.product_id;Another thing is that in my experience most queries do joins on foreign keys
(so the PK side is unique by definition), so the benefit on practical
examples is likely much smaller.But I guess my main question is if there are actual examples of queries the
patch is trying to improve, or whether the general benefit is allowing
parallel plans for queries where it would not be possible otherwise.
Using the combine function technique the planner could have performed
this query the same as if the query had been written as:
SELECT p.category_id, SUM(sum_nitems), SUM(sum_price) FROM products p
JOIN (SELECT product_id,SUM(nitems) sum_nitems,SUM(price) sum_price
FROM sales GROUP BY product_id) s ON p.product_id = s.product_id GROUP
BY p.category_id;
The outer SUM() would be the combine function for SUM() in the
finalize aggregate node.
Why's that less efficient?
I don't deny that there's cases that this multiple aggregate function
won't be able to optimise better than the combine function, but I'm
just not that convinced yet it'll be worth the trouble when combine
functions, which are already in core could do most of what would be
useful with a fraction of the code.
--
David Rowley http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 01/17/2017 06:39 AM, David Rowley wrote:
On 17 January 2017 at 16:30, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:
Let's instead use an example similar to what Antonin mentioned in the
initial post - two tables, with two columns each.CREATE TABLE t1 (a INT, b INT);
CREATE TABLE t2 (c INT, d INT);And let's assume each table has 100.000 rows, but only 100 groups in the
first column, with 1000 rows per group. Something likeINSERT INTO t1
SELECT mod(i,100), i FROM generate_series(1, 1e5) s(i);INSERT INTO t2
SELECT mod(i,100), i FROM generate_series(1, 1e5) s(i);And let's assume this query
SELECT t1.a, count(t2.d) FROM t1 JOIN t2 ON (t1.a = t2.c)
GROUP BY t1.a;On master, EXPLAIN (COSTS OFF, TIMING OFF, ANALYZE) looks like this:
QUERY PLAN
-----------------------------------------------------------------
HashAggregate (actual rows=100 loops=1)
Group Key: t1.a
-> Hash Join (actual rows=100000000 loops=1)
Hash Cond: (t2.c = t1.a)
-> Seq Scan on t2 (actual rows=100000 loops=1)
-> Hash (actual rows=100000 loops=1)
Buckets: 131072 Batches: 2 Memory Usage: 2716kB
-> Seq Scan on t1 (actual rows=100000 loops=1)
Planning time: 0.167 ms
Execution time: 17005.300 ms
(10 rows)while with the patch it looks like this
QUERY PLAN
---------------------------------------------------------------------
Finalize HashAggregate (actual rows=100 loops=1)
Group Key: t1.a
-> Hash Join (actual rows=100 loops=1)
Hash Cond: (t1.a = t2.c)
-> Partial HashAggregate (actual rows=100 loops=1)
Group Key: t1.a
-> Seq Scan on t1 (actual rows=100000 loops=1)
-> Hash (actual rows=100 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 14kB
-> Partial HashAggregate (actual rows=100 loops=1)
Group Key: t2.c
-> Seq Scan on t2 (actual rows=100000 loops=1)
Planning time: 0.105 ms
Execution time: 31.609 ms
(14 rows)This of course happens because with the patch we run the transition function
200k-times (on each side of the join) and aggtransmultifn on the 100 rows
produced by the join, while on master the join produces 10.000.000 rows
(which already takes much more time), and then have to run the transition
function on all those rows.The performance difference is pretty obvious, I guess.
An exceptional improvement.
I'm not sure if you're using "exceptional" in the "excellent" sense, or
"rare to happen in practice". But I guess both meanings apply here,
actually ;-)
For the combine aggregate example of this query, since no patch exists
yet, we could simply mock what the planner would do by rewriting the
query. I'll use SUM() in-place of the combinefn for COUNT():explain analyze SELECT t1.a, sum(t2.d) FROM t1 join (SELECT c,count(d)
d from t2 group by c) t2 on t1.a = t2.c group by t1.a;
QUERY PLAN
--------------------------------------------------------------------------------
HashAggregate (actual rows=100 loops=1)
Group Key: t1.a
-> Hash Join (actual rows=100000 loops=1)
Hash Cond: (t1.a = t2.c)
-> Seq Scan on t1 (actual rows=100000 loops=1)
-> Hash (actual rows=100 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 13kB
-> Subquery Scan on t2 (actual rows=100 loops=1)
-> HashAggregate (actual rows=100 loops=1)
Group Key: t2_1.c
-> Seq Scan on t2 t2_1 (actual rows=100000
loops=1)
Planning time: 0.271 ms
Execution time: 60.226 ms
(13 rows)
this seems to be 100,000 aggtransfn calls (for t2), then 100,000
aggcombinefn calls (for t1) (total = 200,000), where as the patch
would perform 100,000 aggtransfn calls (for t2), then 100,000
aggtransfn calls (for t1), then 100 aggtransmultifn calls (total =
200,100)Is my maths ok?
Yes, I believe the math for agg function calls is correct.
I don't quite see how the patch could use aggcombinefn without sacrificing a
lot of the benefits. Sure, it's possible to run the aggcombinefn in a loop
(with number of iterations determined by the group size on the other side of
the join), but that sounds pretty expensive and eliminates the reduction of
transition function calls. The join cardinality would still be reduced,
though.I'd be interested in seeing the run time of my example query above.
I can't quite see a reason for it to be slower, but please let me
know.
It's a bit slower of course, because the join needs to process more rows
from t1. The patch reduces cardinalities on both sides of the join, if
possible - the example schema is constructed to benefit from this, of
course.
I do have other question about the patch, however. It seems to rely on the
fact that the grouping and joins both reference the same columns. I wonder
how uncommon such queries are.To give a reasonable example, imagine the typical start schema, which is
pretty standard for large analytical databases. A dimension table is
"products" and the fact table is "sales", and the schema might look like
this:CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT,
category_id INT,
producer_id INT
);CREATE TABLE sales (
product_id REFERENCES products (id),
nitems INT,
price NUMERIC
);A typical query then looks like this:
SELECT category_id, SUM(nitems), SUM(price)
FROM products p JOIN sales s ON (p.id = s.product_id)
GROUP BY p.category_id;which obviously uses different columns for the grouping and join, and so the
patch won't help with that. Of course, a query grouping by product_id would
allow the patch to workSELECT category_id, SUM(nitems), SUM(price)
FROM products p JOIN sales s ON (p.id = s.product_id)
GROUP BY p.product_id;Another thing is that in my experience most queries do joins on foreign keys
(so the PK side is unique by definition), so the benefit on practical
examples is likely much smaller.But I guess my main question is if there are actual examples of queries the
patch is trying to improve, or whether the general benefit is allowing
parallel plans for queries where it would not be possible otherwise.Using the combine function technique the planner could have performed
this query the same as if the query had been written as:SELECT p.category_id, SUM(sum_nitems), SUM(sum_price) FROM products p
JOIN (SELECT product_id,SUM(nitems) sum_nitems,SUM(price) sum_price
FROM sales GROUP BY product_id) s ON p.product_id = s.product_id GROUP
BY p.category_id;The outer SUM() would be the combine function for SUM() in the
finalize aggregate node.Why's that less efficient?
I'm a bit confused. I wasn't talking about efficiency at all, but rather
about which cases the patch currently optimizes, and whether it can be
extended.
The patch currently does nothing for the "group by category_id" query,
because it only changes the case when the grouping and join happen on
the same columns. So my question is if this is inherent limitation, or
if the patch can be extended to such queries. Perhaps it's just a
limitation of the WIP patch version?
You're right the rewritten query performs better compared to master:
1) master
QUERY PLAN
----------------------------------------------------------------------
HashAggregate (actual rows=100 loops=1)
Group Key: p.category_id
-> Hash Join (actual rows=10000000 loops=1)
Hash Cond: (s.product_id = p.id)
-> Seq Scan on sales s (actual rows=10000000 loops=1)
-> Hash (actual rows=10000 loops=1)
Buckets: 16384 Batches: 1 Memory Usage: 519kB
-> Seq Scan on products p (actual rows=10000 loops=1)
Planning time: 0.410 ms
Execution time: 3577.070 ms
(10 rows)
2) rewritten
QUERY PLAN
----------------------------------------------------------------------
HashAggregate (actual rows=100 loops=1)
Group Key: p.category_id
-> Hash Join (actual rows=10000 loops=1)
Hash Cond: (sales.product_id = p.id)
-> HashAggregate (actual rows=10000 loops=1)
Group Key: sales.product_id
-> Seq Scan on sales (actual rows=10000000 loops=1)
-> Hash (actual rows=10000 loops=1)
Buckets: 16384 Batches: 1 Memory Usage: 519kB
-> Seq Scan on products p (actual rows=10000 loops=1)
Planning time: 0.555 ms
Execution time: 2585.287 ms
(12 rows)
I can't really compare it to the patch, because that simply does the
same thing as master.
I don't deny that there's cases that this multiple aggregate function
won't be able to optimise better than the combine function, but I'm
just not that convinced yet it'll be worth the trouble when combine
functions, which are already in core could do most of what would be
useful with a fraction of the code.
Sure, no problem with that. It's essentially a cost/benefit analysis,
and we're still trying to understand what the patch does/can do.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote:
[... snip ]]
This all works well, as long as the aggregate is "summing" something
across rows. The method doesn't work when aggregation is say
"multiplying" across the rows or "concatenating" across the rows like
array_agg() or string_agg(). They need a different strategy to combine
aggregates across relations.
Good point. The common characteristic of these seems to be that thay don't
have aggcombinefn defined.
IIUC, we are trying to solve multiple problems here:
1. Pushing down aggregates/groups down join tree, so that the number of rows
to be joined decreases. This might be a good optimization to have. However
there are problems in the current patch. Every path built for a relation
(join or base) returns the same result expressed by the relation or its
subset restricted by parameterization or unification. But this patch changes
that. It creates paths which represent grouping in the base relation. I
think, we need a separate relation to represent that result and hold paths
which produce that result. That itself would be a sizable patch.
Whether a separate relation (RelOptInfo) should be created for grouped
relation is an important design decision indeed. More important than your
argument about the same result ("partial path", used to implement parallel
nodes actually does not fit this criterion perfectly - it only returns part of
the set) is the fact that the data type (target) differs.
I even spent some time coding a prototype where separate RelOptInfo is created
for the grouped relation but it was much more invasive. In particular, if only
some relations are grouped, it's hard to join them with non-grouped ones w/o
changing make_rel_from_joinlist and subroutines substantially. (Decision
whether the plain or the grouped relation should be involved in joining makes
little sense at the leaf level of the join tree.)
So I took the approach that resembles the partial paths - separate pathlists
within the same RelOptInfo.
2. Try to push down aggregates based on the equivalence classes, where
grouping properties can be transferred from one relation to the other using
EC mechanism.
I don't think the EC part should increase the patch complexity a lot. Unless I
missed something, it's rather isolated to the part where target of the grouped
paths is assembled. And I think it's important even for initial version of the
patch.
This seems to require solving the problem of combining aggregates across the
relations. But there might be some usecases which could benefit without
solving this problem.
If "combining aggregates ..." refers to joining grouped relations, then I
insist on doing this in the initial version of the new feature too. Otherwise
it'd only work if exactly one base relation of the query is grouped.
3. If the relation to which we push the aggregate is an append relation,
push (partial) aggregation/grouping down into the child relations. - We
don't do that right now even for grouping aggregation on a single append
table. Parallel partial aggregation does that, but not exactly per
relation. That may be a sizable project in itself. Even without this piece
the rest of the optimizations proposed by this patch are important.
Yes, this can be done in a separate patch. I'll consider it.
4. Additional goal: push down the aggregation to any relation (join/base)
where it can be computed.
I think this can be achieved by adding extra aggregation nodes to the join
tree. As I still anticipate more important design changes, this part is not at
the top of my TODO list.
--
Antonin Houska
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt
Web: http://www.postgresql-support.de, http://www.cybertec.at
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
[ Trying to respond to both Tomas and David. I'll check tomorrow if anything
else of the thread needs my comment. ]
Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:
On 01/17/2017 12:42 AM, David Rowley wrote:
On 10 January 2017 at 06:56, Antonin Houska <ah@cybertec.at> wrote:
I've been thinking about this aggtransmultifn and I'm not sure if it's
really needed. Adding a whole series of new transition functions is
quite a pain. At least I think so, and I have a feeling Robert might
agree with me.Let's imagine some worst case (and somewhat silly) aggregate query:
SELECT count(*)
FROM million_row_table
CROSS JOIN another_million_row_table;Today that's going to cause 1 TRILLION transitions! Performance will
be terrible.If we pushed the aggregate down into one of those tables and performed
a Partial Aggregate on that, then a Finalize Aggregate on that single
row result (after the join), then that's 1 million transfn calls, and
1 million combinefn calls, one for each row produced by the join.If we did it your way (providing I understand your proposal correctly)
there's 1 million transfn calls on one relation, then 1 million on the
other and then 1 multiplyfn call. which does 1000000 * 1000000What did we save vs. using the existing aggcombinefn infrastructure
which went into 9.6? Using this actually costs us 1 extra function
call, right? I'd imagine the size of the patch to use aggcombinefn
instead would be a fraction of the size of the one which included all
the new aggmultiplyfns and pg_aggregate.h changes.
I think the patch relies on the assumption that the grouping reduces
cardinality,
Yes.
so a CROSS JOIN without a GROUP BY clause may not be the best
counterexample.
Yet it tells me that my approach is not ideal in some cases ...
It sounds like a much more manageable project by using aggcombinefn
instead. Then maybe one day when we can detect if a join did not cause
any result duplication (i.e Unique Joins), we could finalise the
aggregates on the first call, and completely skip the combine state
altogether.
I don't quite see how the patch could use aggcombinefn without sacrificing a
lot of the benefits. Sure, it's possible to run the aggcombinefn in a loop
(with number of iterations determined by the group size on the other side of
the join), but that sounds pretty expensive and eliminates the reduction of
transition function calls. The join cardinality would still be reduced,
though.
That's what I think. The generic case is that neither side of the join is
unique. If it appears that both relations should be aggregated below the join,
aggcombinefn would have to be called multiple times on each output row of the
join to achieve the same result as the calling aggmultiplyfn.
I do have other question about the patch, however. It seems to rely on the
fact that the grouping and joins both reference the same columns. I wonder how
uncommon such queries are.To give a reasonable example, imagine the typical start schema, which is
pretty standard for large analytical databases. A dimension table is
"products" and the fact table is "sales", and the schema might look like this:CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT,
category_id INT,
producer_id INT
);CREATE TABLE sales (
product_id REFERENCES products (id),
nitems INT,
price NUMERIC
);A typical query then looks like this:
SELECT category_id, SUM(nitems), SUM(price)
FROM products p JOIN sales s ON (p.id = s.product_id)
GROUP BY p.category_id;which obviously uses different columns for the grouping and join, and so the
patch won't help with that. Of course, a query grouping by product_id would
allow the patch to work
Right, the current version does not handle this. Thanks for suggestion.
Another thing is that in my experience most queries do joins on foreign keys
(so the PK side is unique by definition), so the benefit on practical examples
is likely much smaller.
ok. So in some cases the David's approach might be better.
However I think the ability to join 2 grouped (originally non-unique)
relations is still important. Consider a query involving "sales" as well as
another table which also has many-to-one relationship to "products".
But I guess my main question is if there are actual examples of queries the
patch is trying to improve, or whether the general benefit is allowing
parallel plans for queries where it would not be possible otherwise.
In fact I did all this with postgres_fdw in mind.
From this perspective, David's approach can be slightly more efficient if all
the tables are local, but aggregation of multiple base relations below the
join can save a lot of effort if the tables are remote (as it reduces the
amount of data transferred over network).
I'm not terribly happy about changing the system catalog, but adding something
like pg_aggregate(aggtransmultifn) is currently the best idea I have.
--
Antonin Houska
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt
Web: http://www.postgresql-support.de, http://www.cybertec.at
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 01/17/2017 08:05 PM, Antonin Houska wrote:
[ Trying to respond to both Tomas and David. I'll check tomorrow if anything
else of the thread needs my comment. ]Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:
On 01/17/2017 12:42 AM, David Rowley wrote:
On 10 January 2017 at 06:56, Antonin Houska <ah@cybertec.at> wrote:
I've been thinking about this aggtransmultifn and I'm not sure if it's
really needed. Adding a whole series of new transition functions is
quite a pain. At least I think so, and I have a feeling Robert might
agree with me.Let's imagine some worst case (and somewhat silly) aggregate query:
SELECT count(*)
FROM million_row_table
CROSS JOIN another_million_row_table;Today that's going to cause 1 TRILLION transitions! Performance will
be terrible.If we pushed the aggregate down into one of those tables and performed
a Partial Aggregate on that, then a Finalize Aggregate on that single
row result (after the join), then that's 1 million transfn calls, and
1 million combinefn calls, one for each row produced by the join.If we did it your way (providing I understand your proposal correctly)
there's 1 million transfn calls on one relation, then 1 million on the
other and then 1 multiplyfn call. which does 1000000 * 1000000What did we save vs. using the existing aggcombinefn infrastructure
which went into 9.6? Using this actually costs us 1 extra function
call, right? I'd imagine the size of the patch to use aggcombinefn
instead would be a fraction of the size of the one which included all
the new aggmultiplyfns and pg_aggregate.h changes.I think the patch relies on the assumption that the grouping reduces
cardinality,Yes.
so a CROSS JOIN without a GROUP BY clause may not be the best
counterexample.Yet it tells me that my approach is not ideal in some cases ...
It sounds like a much more manageable project by using aggcombinefn
instead. Then maybe one day when we can detect if a join did not cause
any result duplication (i.e Unique Joins), we could finalise the
aggregates on the first call, and completely skip the combine state
altogether.I don't quite see how the patch could use aggcombinefn without sacrificing a
lot of the benefits. Sure, it's possible to run the aggcombinefn in a loop
(with number of iterations determined by the group size on the other side of
the join), but that sounds pretty expensive and eliminates the reduction of
transition function calls. The join cardinality would still be reduced,
though.That's what I think. The generic case is that neither side of the join is
unique. If it appears that both relations should be aggregated below the join,
aggcombinefn would have to be called multiple times on each output row of the
join to achieve the same result as the calling aggmultiplyfn.I do have other question about the patch, however. It seems to rely on the
fact that the grouping and joins both reference the same columns. I wonder how
uncommon such queries are.To give a reasonable example, imagine the typical start schema, which is
pretty standard for large analytical databases. A dimension table is
"products" and the fact table is "sales", and the schema might look like this:CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT,
category_id INT,
producer_id INT
);CREATE TABLE sales (
product_id REFERENCES products (id),
nitems INT,
price NUMERIC
);A typical query then looks like this:
SELECT category_id, SUM(nitems), SUM(price)
FROM products p JOIN sales s ON (p.id = s.product_id)
GROUP BY p.category_id;which obviously uses different columns for the grouping and join, and so the
patch won't help with that. Of course, a query grouping by product_id would
allow the patch to workRight, the current version does not handle this. Thanks for suggestion.
So you're saying it's merely a limitation of the initial patch version
and not an inherent limitation?
Another thing is that in my experience most queries do joins on foreign keys
(so the PK side is unique by definition), so the benefit on practical examples
is likely much smaller.ok. So in some cases the David's approach might be better.
In which cases would David's approach be more efficient? But even if
there are such cases, I assume we could generate both paths and decide
based on cost, just like with all other alternative paths.
However I think the ability to join 2 grouped (originally non-unique)
relations is still important. Consider a query involving "sales" as well as
another table which also has many-to-one relationship to "products".
Well, can you give a practical example? What you describe seems like a
combination of two fact tables + a dimension, something like this:
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT,
category_id INT,
producer_id INT
);
CREATE TABLE sales (
product_id REFERENCES products (id),
nitems INT,
price NUMERIC
);
CREATE TABLE reviews (
product_id REFERENCES products (id),
stars INT
);
But how exactly do you join that together? Because
SELECT * FROM products p JOIN sales s ON (p.id = s.product_id)
JOIN reviews r ON (p.id = r.product_id)
is clearly wrong - it's essentially M:N join between the two fact
tables, increasing the number of rows.
It'd helpful to have an example of a practical query optimized by the
patch. I'm not claiming it does not exist, but I've been unable to come
up with something reasonable at the moment.
But I guess my main question is if there are actual examples of queries the
patch is trying to improve, or whether the general benefit is allowing
parallel plans for queries where it would not be possible otherwise.In fact I did all this with postgres_fdw in mind.
I assume there's not much difference between pushing down aggregates to
local workers and to remote nodes. There'll be costing differences, but
are there any other differences?
From this perspective, David's approach can be slightly more efficient if all
the tables are local, but aggregation of multiple base relations below the
join can save a lot of effort if the tables are remote (as it reduces the
amount of data transferred over network).I'm not terribly happy about changing the system catalog, but adding something
like pg_aggregate(aggtransmultifn) is currently the best idea I have.
I personally don't see that as a major problem, my impression it can be
mostly copied from the partial aggregate patch - it's not trivial, but
manageable. Propagating it to the optimizer will be less trivial, but
well, if it's necessary ...
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jan 17, 2017 at 10:07 PM, Antonin Houska <ah@cybertec.at> wrote:
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote:
[... snip ]]
This all works well, as long as the aggregate is "summing" something
across rows. The method doesn't work when aggregation is say
"multiplying" across the rows or "concatenating" across the rows like
array_agg() or string_agg(). They need a different strategy to combine
aggregates across relations.Good point. The common characteristic of these seems to be that thay don't
have aggcombinefn defined.
I don't think aggcombinefn isn't there because we couldn't write it
for array_agg() or string_agg(). I guess, it won't be efficient to
have those aggregates combined across parallel workers.
Also, the point is naming that kind of function as aggtransmultifn
would mean that it's always supposed to multiply, which isn't true for
all aggregates.
IIUC, we are trying to solve multiple problems here:
1. Pushing down aggregates/groups down join tree, so that the number of rows
to be joined decreases. This might be a good optimization to have. However
there are problems in the current patch. Every path built for a relation
(join or base) returns the same result expressed by the relation or its
subset restricted by parameterization or unification. But this patch changes
that. It creates paths which represent grouping in the base relation. I
think, we need a separate relation to represent that result and hold paths
which produce that result. That itself would be a sizable patch.Whether a separate relation (RelOptInfo) should be created for grouped
relation is an important design decision indeed. More important than your
argument about the same result ("partial path", used to implement parallel
nodes actually does not fit this criterion perfectly - it only returns part of
the set) is the fact that the data type (target) differs.
Right!
I even spent some time coding a prototype where separate RelOptInfo is created
for the grouped relation but it was much more invasive. In particular, if only
some relations are grouped, it's hard to join them with non-grouped ones w/o
changing make_rel_from_joinlist and subroutines substantially. (Decision
whether the plain or the grouped relation should be involved in joining makes
little sense at the leaf level of the join tree.)So I took the approach that resembles the partial paths - separate pathlists
within the same RelOptInfo.
Yes, it's hard, but I think without having a separate RelOptInfo the
design won't be complete. Is there a subset of problem that can be
solved by using a separate RelOptInfo e.g. pushing aggregates down
child relations or anything else.
2. Try to push down aggregates based on the equivalence classes, where
grouping properties can be transferred from one relation to the other using
EC mechanism.I don't think the EC part should increase the patch complexity a lot. Unless I
missed something, it's rather isolated to the part where target of the grouped
paths is assembled. And I think it's important even for initial version of the
patch.This seems to require solving the problem of combining aggregates across the
relations. But there might be some usecases which could benefit without
solving this problem.If "combining aggregates ..." refers to joining grouped relations, then I
insist on doing this in the initial version of the new feature too. Otherwise
it'd only work if exactly one base relation of the query is grouped.
No. "combining aggregates" refers to what aggtransmultifn does. But,
possibly that problem needs to be solved in the first step itself.
3. If the relation to which we push the aggregate is an append relation,
push (partial) aggregation/grouping down into the child relations. - We
don't do that right now even for grouping aggregation on a single append
table. Parallel partial aggregation does that, but not exactly per
relation. That may be a sizable project in itself. Even without this piece
the rest of the optimizations proposed by this patch are important.Yes, this can be done in a separate patch. I'll consider it.
4. Additional goal: push down the aggregation to any relation (join/base)
where it can be computed.I think this can be achieved by adding extra aggregation nodes to the join
tree. As I still anticipate more important design changes, this part is not at
the top of my TODO list.
I guess, attempting this will reveal some more design changes
required; it may actually simplify the design.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jan 17, 2017 at 11:33 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:
I don't think aggcombinefn isn't there because we couldn't write it
for array_agg() or string_agg(). I guess, it won't be efficient to
have those aggregates combined across parallel workers.
I think there are many cases where it would work fine. I assume that
David just didn't make it a priority to write those functions because
it wasn't important to the queries he wanted to optimize. But
somebody can submit a patch for it any time and I bet it will have
practical use cases. There might be some performance problems shoving
large numbers of lengthy values through a shm_mq, but we won't know
that until somebody tries it.
Also, the point is naming that kind of function as aggtransmultifn
would mean that it's always supposed to multiply, which isn't true for
all aggregates.
TransValue * integer = newTransValue is well-defined for any
aggregate. It's the result of aggregating that TransValue with itself
a number of times defined by the integer. And that might well be
significantly faster than using aggcombinefn many times. On the other
hand, how many queries just sit there are re-aggregate the same
TransValues over and over again? I am having trouble wrapping my head
around that part of this.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 19 January 2017 at 07:32, Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Jan 17, 2017 at 11:33 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:I don't think aggcombinefn isn't there because we couldn't write it
for array_agg() or string_agg(). I guess, it won't be efficient to
have those aggregates combined across parallel workers.I think there are many cases where it would work fine. I assume that
David just didn't make it a priority to write those functions because
it wasn't important to the queries he wanted to optimize. But
somebody can submit a patch for it any time and I bet it will have
practical use cases. There might be some performance problems shoving
large numbers of lengthy values through a shm_mq, but we won't know
that until somebody tries it.
I had assumed that the combine function which combines a large array
or a large string would not be any cheaper than doing that
incrementally with the transfn. Of course some of this would happen in
parallel, but it still doubles up some of the memcpy()ing, so perhaps
it would be slower? ... I didn't ever get a chance to test it.
--
David Rowley http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jan 18, 2017 at 5:14 PM, David Rowley
<david.rowley@2ndquadrant.com> wrote:
On 19 January 2017 at 07:32, Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Jan 17, 2017 at 11:33 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:I don't think aggcombinefn isn't there because we couldn't write it
for array_agg() or string_agg(). I guess, it won't be efficient to
have those aggregates combined across parallel workers.I think there are many cases where it would work fine. I assume that
David just didn't make it a priority to write those functions because
it wasn't important to the queries he wanted to optimize. But
somebody can submit a patch for it any time and I bet it will have
practical use cases. There might be some performance problems shoving
large numbers of lengthy values through a shm_mq, but we won't know
that until somebody tries it.I had assumed that the combine function which combines a large array
or a large string would not be any cheaper than doing that
incrementally with the transfn. Of course some of this would happen in
parallel, but it still doubles up some of the memcpy()ing, so perhaps
it would be slower? ... I didn't ever get a chance to test it.
Even if that particular bit is not very much faster, it might have the
advantage of letting other parts of the plan be parallelized, and you
can still win that way. In the internal-to-EnterpriseDB experiments
we've been doing over the last few months, we've seen that kind of
thing a lot, and it informs a lot of the patches that my colleagues
have been submitting. But I also wouldn't be surprised if there are
cases where it wins big even without that. For example, if you're
doing an aggregate with lots of groups and good physical-to-logical
correlation, the normal case might be for all the rows in a group to
be on the same page. So you parallel seq scan the table and have
hardly any need to run the combine function in the leader (but of
course you have to have it available just in case you do need it).
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Jan 19, 2017 at 12:02 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Jan 17, 2017 at 11:33 PM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:I don't think aggcombinefn isn't there because we couldn't write it
for array_agg() or string_agg(). I guess, it won't be efficient to
have those aggregates combined across parallel workers.I think there are many cases where it would work fine. I assume that
David just didn't make it a priority to write those functions because
it wasn't important to the queries he wanted to optimize. But
somebody can submit a patch for it any time and I bet it will have
practical use cases. There might be some performance problems shoving
large numbers of lengthy values through a shm_mq, but we won't know
that until somebody tries it.Also, the point is naming that kind of function as aggtransmultifn
would mean that it's always supposed to multiply, which isn't true for
all aggregates.TransValue * integer = newTransValue is well-defined for any
aggregate. It's the result of aggregating that TransValue with itself
a number of times defined by the integer. And that might well be
significantly faster than using aggcombinefn many times. On the other
hand, how many queries just sit there are re-aggregate the same
TransValues over and over again? I am having trouble wrapping my head
around that part of this.
Not all aggregates have TransValue * integer = newTransValue
behaviour. Example is array_agg() or string_agg() has "TransValue
concatenated integer time" behaviour. Or an aggregate "multiplying"
values across rows will have TransValue (raised to) integer behaviour.
Labelling all of those as "multi" doesn't look good.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 01/19/2017 04:09 AM, Ashutosh Bapat wrote:
On Thu, Jan 19, 2017 at 12:02 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Jan 17, 2017 at 11:33 PM, Ashutosh Bapat
Also, the point is naming that kind of function as aggtransmultifn
would mean that it's always supposed to multiply, which isn't true for
all aggregates.TransValue * integer = newTransValue is well-defined for any
aggregate. It's the result of aggregating that TransValue with itself
a number of times defined by the integer. And that might well be
significantly faster than using aggcombinefn many times. On the other
hand, how many queries just sit there are re-aggregate the same
TransValues over and over again? I am having trouble wrapping my head
around that part of this.Not all aggregates have TransValue * integer = newTransValue
behaviour. Example is array_agg() or string_agg() has "TransValue
concatenated integer time" behaviour. Or an aggregate "multiplying"
values across rows will have TransValue (raised to) integer behaviour.
Labelling all of those as "multi" doesn't look good.
All aggregates that have (or can have) a combine function have it,
because in the worst case you can simply implement it by calling the
combine function repeatedly.
Also, if you treat the combine function as "+" then the "multiply"
function is exactly what "*" is expected to do. So I find the naming
quite appropriate, actually.
But I think naming of the function is not the most important aspect of
the patch, I believe. In the worst case, we can do s/multi/whatever/
sometime later.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:
On 01/17/2017 08:05 PM, Antonin Houska wrote:
Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:
Another thing is that in my experience most queries do joins on foreign keys
(so the PK side is unique by definition), so the benefit on practical examples
is likely much smaller.ok. So in some cases the David's approach might be better.
In which cases would David's approach be more efficient? But even if there are
such cases, I assume we could generate both paths and decide based on cost,
just like with all other alternative paths.
Sorry, it was my thinko - I somehow confused David's CROSS JOIN example with
this one. If one side of the join clause is unique and the other becomes
unique due to aggregation (and if parallel processing is not engaged) then
neither combinefn nor multiplyfn should be necessary before the finalfn.
However I think the ability to join 2 grouped (originally non-unique)
relations is still important. Consider a query involving "sales" as well as
another table which also has many-to-one relationship to "products".Well, can you give a practical example? What you describe seems like a
combination of two fact tables + a dimension, something like this:CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT,
category_id INT,
producer_id INT
);CREATE TABLE sales (
product_id REFERENCES products (id),
nitems INT,
price NUMERIC
);CREATE TABLE reviews (
product_id REFERENCES products (id),
stars INT
);But how exactly do you join that together? Because
SELECT * FROM products p JOIN sales s ON (p.id = s.product_id)
JOIN reviews r ON (p.id = r.product_id)is clearly wrong - it's essentially M:N join between the two fact tables,
increasing the number of rows.
Without elaborating details I imagined join condition between the 2 fact
tables which references their non-unique columns, but that does not make sense
here. Sorry.
It'd helpful to have an example of a practical query optimized by the
patch. I'm not claiming it does not exist, but I've been unable to come up
with something reasonable at the moment.
As I mentioned elsewhere, remote aggregation is the primary use case.
Besides that, I can imagine another one - join of partitioned tables (costs
are not displayed just to make the plan easier to read).
For this query
SELECT
p.id, sum(price)
FROM
products AS p
JOIN sales AS s ON s.product_id = p.id
GROUP BY
p.id
I get this plan at "normal circumstances"
HashAggregate
Group Key: p.id
-> Hash Join
Hash Cond: (s.product_id = p.id)
-> Gather
Workers Planned: 2
-> Append
-> Parallel Seq Scan on sales s
-> Parallel Seq Scan on sales_2015 s_1
-> Parallel Seq Scan on sales_2016 s_2
-> Parallel Seq Scan on sales_2017 s_3
-> Hash
-> Gather
Workers Planned: 2
-> Append
-> Parallel Seq Scan on products p
-> Parallel Seq Scan on products_01 p_1
-> Parallel Seq Scan on products_02 p_2
-> Parallel Seq Scan on products_03 p_3
-> Parallel Seq Scan on products_04 p_4
but if work_mem is sufficiently low for the hash join to be efficient, the
aggregation can be moved to individual partitions.
Gather
Workers Planned: 1
Single Copy: true
-> Finalize HashAggregate
Group Key: p.id
-> Hash Join
Hash Cond: (p.id = s.product_id)
-> Append
-> Partial HashAggregate
Group Key: p.id
-> Seq Scan on products p
-> Partial HashAggregate
Group Key: p_1.id
-> Seq Scan on products_01 p_1
-> Partial HashAggregate
Group Key: p_2.id
-> Seq Scan on products_02 p_2
-> Partial HashAggregate
Group Key: p_3.id
-> Seq Scan on products_03 p_3
-> Partial HashAggregate
Group Key: p_4.id
-> Seq Scan on products_04 p_4
-> Hash
-> Append
-> Partial HashAggregate
Group Key: s.product_id
-> Seq Scan on sales s
-> Partial HashAggregate
Group Key: s_1.product_id
-> Seq Scan on sales_2015 s_1
-> Partial HashAggregate
Group Key: s_2.product_id
-> Seq Scan on sales_2016 s_2
-> Partial HashAggregate
Group Key: s_3.product_id
-> Seq Scan on sales_2017 s_3
Finally, the patch should help if the aggregation argument is rather expensive
to evaluate. I refer to this part of the example plan that I showed in the
initial message of this thread (in which case the argument was actually
simple ...):
-> Gather
Workers Planned: 2
-> Partial HashAggregate
Group Key: a.i
-> Parallel Seq Scan on a
The first 2 cases show where the base relation grouping can help alone, the
last one combines the base relation grouping with parallel query processing.
But I guess my main question is if there are actual examples of queries the
patch is trying to improve, or whether the general benefit is allowing
parallel plans for queries where it would not be possible otherwise.In fact I did all this with postgres_fdw in mind.
I assume there's not much difference between pushing down aggregates to local
workers and to remote nodes. There'll be costing differences, but are there
any other differences?
It's easier for me to see what these 2 things have in common than to point out
differences. One common thing is that aggregation takes place in multiple
stages. They can also be similar in terms of implementation (e.g. by keeping
paths in separate lists), although there are still some choices to be done.
As for doing aggregation in parallel, I personally don't think that "pushing
the aggregate down to worker" is the exact description. If the parallel worker
aggregates the base relation and if the nearest Gather node is at the top of
the plan, then the worker actually performs the whole plan, except for the
Gather node.
And yes, costing needs to be considered. Besides estimating the inputs (table
width, but especially row count), some "policy decisions" might be necessary,
similar to those that planner applies to parameterized paths - see the
comments of add_path(). The grouped path should not be used where relatively
smally error in estimates of the base relation aggregation can lead to
significant error in the total plan costs.
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT,
category_id INT,
producer_id INT
);CREATE TABLE sales (
product_id REFERENCES products (id),
nitems INT,
price NUMERIC
);A typical query then looks like this:
SELECT category_id, SUM(nitems), SUM(price)
FROM products p JOIN sales s ON (p.id = s.product_id)
GROUP BY p.category_id;which obviously uses different columns for the grouping and join, and so the
patch won't help with that. Of course, a query grouping by product_id would
allow the patch to workRight, the current version does not handle this. Thanks for suggestion.
So you're saying it's merely a limitation of the initial patch version and not
an inherent limitation?
I just haven't thought about this so far, but now that I try, I seem to miss
something. p.category_id as grouping column gives the patch no chance to do
anything useful because only one table of the join has that
column. "product_id" does help, but gives a different result ...
--
Antonin Houska
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt
Web: http://www.postgresql-support.de, http://www.cybertec.at
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Antonin Houska <ah@cybertec.at> wrote:
Well, the following one does not seem to be a typical example. I could
generate the plan, but now I think that the aggregation push down does not in
general decrease the number of groups the final aggregation has to
process. Maybe I just hit planner limitation to estimate the number of groups
within append relation.
For this query
SELECT
p.id, sum(price)
FROM
products AS p
JOIN sales AS s ON s.product_id = p.id
GROUP BY
p.idI get this plan at "normal circumstances"
HashAggregate
Group Key: p.id
-> Hash Join
Hash Cond: (s.product_id = p.id)
-> Gather
Workers Planned: 2
-> Append
-> Parallel Seq Scan on sales s
-> Parallel Seq Scan on sales_2015 s_1
-> Parallel Seq Scan on sales_2016 s_2
-> Parallel Seq Scan on sales_2017 s_3
-> Hash
-> Gather
Workers Planned: 2
-> Append
-> Parallel Seq Scan on products p
-> Parallel Seq Scan on products_01 p_1
-> Parallel Seq Scan on products_02 p_2
-> Parallel Seq Scan on products_03 p_3
-> Parallel Seq Scan on products_04 p_4but if work_mem is sufficiently low for the hash join to be efficient, the
aggregation can be moved to individual partitions.Gather
Workers Planned: 1
Single Copy: true
-> Finalize HashAggregate
Group Key: p.id
-> Hash Join
Hash Cond: (p.id = s.product_id)
-> Append
-> Partial HashAggregate
Group Key: p.id
-> Seq Scan on products p
-> Partial HashAggregate
Group Key: p_1.id
-> Seq Scan on products_01 p_1
-> Partial HashAggregate
Group Key: p_2.id
-> Seq Scan on products_02 p_2
-> Partial HashAggregate
Group Key: p_3.id
-> Seq Scan on products_03 p_3
-> Partial HashAggregate
Group Key: p_4.id
-> Seq Scan on products_04 p_4
-> Hash
-> Append
-> Partial HashAggregate
Group Key: s.product_id
-> Seq Scan on sales s
-> Partial HashAggregate
Group Key: s_1.product_id
-> Seq Scan on sales_2015 s_1
-> Partial HashAggregate
Group Key: s_2.product_id
-> Seq Scan on sales_2016 s_2
-> Partial HashAggregate
Group Key: s_3.product_id
-> Seq Scan on sales_2017 s_3
--
Antonin Houska
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt
Web: http://www.postgresql-support.de, http://www.cybertec.at
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote:
1. Pushing down aggregates/groups down join tree, so that the number of rows
to be joined decreases. This might be a good optimization to have. However
there are problems in the current patch. Every path built for a relation
(join or base) returns the same result expressed by the relation or its
subset restricted by parameterization or unification. But this patch changes
that. It creates paths which represent grouping in the base relation. I
think, we need a separate relation to represent that result and hold paths
which produce that result. That itself would be a sizable patch.Whether a separate relation (RelOptInfo) should be created for grouped
relation is an important design decision indeed. More important than your
argument about the same result ("partial path", used to implement parallel
nodes actually does not fit this criterion perfectly - it only returns part of
the set) is the fact that the data type (target) differs.
I even spent some time coding a prototype where separate RelOptInfo is created
for the grouped relation but it was much more invasive. In particular, if only
some relations are grouped, it's hard to join them with non-grouped ones w/o
changing make_rel_from_joinlist and subroutines substantially. (Decision
whether the plain or the grouped relation should be involved in joining makes
little sense at the leaf level of the join tree.)So I took the approach that resembles the partial paths - separate pathlists
within the same RelOptInfo.
Yes, it's hard, but I think without having a separate RelOptInfo the
design won't be complete. Is there a subset of problem that can be
solved by using a separate RelOptInfo e.g. pushing aggregates down
child relations or anything else.
I'm still not convinced that all the fields of RelOptInfo (typically relids)
need to be duplicated. If the current concept should be improved, I'd move all
the grouping related fields to a separate structure, e.g. GroupPathInfo, and
let RelOptInfo point to it. Similar to ParamPathInfo, which contains
parameterization-specific information, GroupPathInfo would conain the
grouping-specific information: target, row count, width, maybe path lists too.
2. Try to push down aggregates based on the equivalence classes, where
grouping properties can be transferred from one relation to the other using
EC mechanism.I don't think the EC part should increase the patch complexity a lot. Unless I
missed something, it's rather isolated to the part where target of the grouped
paths is assembled. And I think it's important even for initial version of the
patch.This seems to require solving the problem of combining aggregates across the
relations. But there might be some usecases which could benefit without
solving this problem.If "combining aggregates ..." refers to joining grouped relations, then I
insist on doing this in the initial version of the new feature too. Otherwise
it'd only work if exactly one base relation of the query is grouped.No. "combining aggregates" refers to what aggtransmultifn does. But,
possibly that problem needs to be solved in the first step itself.
ok. As the discussion goes on, I see that this part could be more useful than
I originally thought. I'll consider it.
--
Antonin Houska
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt
Web: http://www.postgresql-support.de, http://www.cybertec.at
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Yes, it's hard, but I think without having a separate RelOptInfo the
design won't be complete. Is there a subset of problem that can be
solved by using a separate RelOptInfo e.g. pushing aggregates down
child relations or anything else.I'm still not convinced that all the fields of RelOptInfo (typically relids)
need to be duplicated. If the current concept should be improved, I'd move all
the grouping related fields to a separate structure, e.g. GroupPathInfo, and
let RelOptInfo point to it. Similar to ParamPathInfo, which contains
parameterization-specific information, GroupPathInfo would conain the
grouping-specific information: target, row count, width, maybe path lists too.
I didn't think about this option. Still not very clean, but may be acceptable.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Jan 19, 2017 at 2:19 AM, Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:
Not all aggregates have TransValue * integer = newTransValue
behaviour. Example is array_agg() or string_agg() has "TransValue
concatenated integer time" behaviour. Or an aggregate "multiplying"
values across rows will have TransValue (raised to) integer behaviour.
Labelling all of those as "multi" doesn't look good.All aggregates that have (or can have) a combine function have it, because
in the worst case you can simply implement it by calling the combine
function repeatedly.
+1.
Also, if you treat the combine function as "+" then the "multiply" function
is exactly what "*" is expected to do. So I find the naming quite
appropriate, actually.
+1.
But I think naming of the function is not the most important aspect of the
patch, I believe. In the worst case, we can do s/multi/whatever/ sometime
later.
Yeah.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 20 January 2017 at 00:22, Antonin Houska <ah@cybertec.at> wrote:
Sorry, it was my thinko - I somehow confused David's CROSS JOIN example with
this one. If one side of the join clause is unique and the other becomes
unique due to aggregation (and if parallel processing is not engaged) then
neither combinefn nor multiplyfn should be necessary before the finalfn.
Yes, if the join can be detected not to duplicate the groups then a
normal aggregate node can be pushed below the join. No need for
Partial Aggregate, or Finalize Aggregate nodes.
I've a pending patch in the commitfest named "Unique Joins", which
aims teach the planner about the unique properties of joins. So you
should just have both stages of aggregation occur for now, and that
can be improved on once the planner is a bit smart and knows about
unique joins.
--
David Rowley http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
David Rowley <david.rowley@2ndquadrant.com> wrote:
On 20 January 2017 at 00:22, Antonin Houska <ah@cybertec.at> wrote:
Sorry, it was my thinko - I somehow confused David's CROSS JOIN example with
this one. If one side of the join clause is unique and the other becomes
unique due to aggregation (and if parallel processing is not engaged) then
neither combinefn nor multiplyfn should be necessary before the finalfn.Yes, if the join can be detected not to duplicate the groups then a
normal aggregate node can be pushed below the join. No need for
Partial Aggregate, or Finalize Aggregate nodes.I've a pending patch in the commitfest named "Unique Joins", which
aims teach the planner about the unique properties of joins. So you
should just have both stages of aggregation occur for now, and that
can be improved on once the planner is a bit smart and knows about
unique joins.
Thanks for a hint. I haven't paid attention to the "Unique Joins" patch until
today. Yes, that's definitely useful.
Given the progress of your patch, I don't worry to make the next version of my
patch depend on it. Implementing temporary solution for the aggregation
push-down seems to me like wasted effort.
--
Antonin Houska
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt
Web: http://www.postgresql-support.de, http://www.cybertec.at
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers