agg_pushdown_v2.diff
text/x-diff
Patch
Format: unified
Series: patch v2
| File | + | − |
|---|---|---|
| src/backend/executor/execExpr.c | 0 | 0 |
| src/backend/executor/nodeAgg.c | 0 | 0 |
| src/backend/nodes/copyfuncs.c | 0 | 0 |
| src/backend/nodes/equalfuncs.c | 0 | 0 |
| src/backend/nodes/nodeFuncs.c | 0 | 0 |
| src/backend/nodes/outfuncs.c | 0 | 0 |
| src/backend/nodes/readfuncs.c | 0 | 0 |
| src/backend/optimizer/geqo/geqo_eval.c | 0 | 0 |
| src/backend/optimizer/path/allpaths.c | 0 | 0 |
| src/backend/optimizer/path/indxpath.c | 0 | 0 |
| src/backend/optimizer/path/joinpath.c | 0 | 0 |
| src/backend/optimizer/path/joinrels.c | 0 | 0 |
| src/backend/optimizer/path/tidpath.c | 0 | 0 |
| src/backend/optimizer/plan/initsplan.c | 0 | 0 |
| src/backend/optimizer/plan/planagg.c | 0 | 0 |
| src/backend/optimizer/plan/planmain.c | 0 | 0 |
| src/backend/optimizer/plan/planner.c | 0 | 0 |
| src/backend/optimizer/plan/setrefs.c | 0 | 0 |
| src/backend/optimizer/prep/prepunion.c | 0 | 0 |
| src/backend/optimizer/util/pathnode.c | 0 | 0 |
| src/backend/optimizer/util/relnode.c | 0 | 0 |
| src/backend/optimizer/util/tlist.c | 0 | 0 |
| src/backend/utils/adt/ruleutils.c | 0 | 0 |
| src/backend/utils/adt/selfuncs.c | 0 | 0 |
| src/include/nodes/nodes.h | 0 | 0 |
| src/include/nodes/relation.h | 0 | 0 |
| src/include/optimizer/pathnode.h | 0 | 0 |
| src/include/optimizer/paths.h | 0 | 0 |
| src/include/optimizer/planmain.h | 0 | 0 |
| src/include/optimizer/tlist.h | 0 | 0 |
| src/include/utils/selfuncs.h | 0 | 0 |
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
new file mode 100644
index 5a34a46..717763d
*** a/src/backend/executor/execExpr.c
--- b/src/backend/executor/execExpr.c
*************** ExecInitExprRec(Expr *node, PlanState *p
*** 723,728 ****
--- 723,755 ----
break;
}
+ case T_GroupedVar:
+ /*
+ * GroupedVar is treated as an aggregate if it appears in the
+ * targetlist of Agg node, but as a normal variable elsewhere.
+ */
+ if (parent && (IsA(parent, AggState)))
+ {
+ GroupedVar *gvar = (GroupedVar *) node;
+
+ /*
+ * Currently GroupedVar can only represent partial aggregate.
+ */
+ Assert(gvar->agg_partial != NULL);
+
+ ExecInitExprRec((Expr *) gvar->agg_partial, parent, state,
+ resv, resnull);
+ break;
+ }
+ else
+ {
+ /*
+ * set_plan_refs should have replaced GroupedVar in the
+ * targetlist with an ordinary Var.
+ */
+ elog(ERROR, "parent of GroupedVar is not Agg node");
+ }
+
case T_GroupingFunc:
{
GroupingFunc *grp_node = (GroupingFunc *) node;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
new file mode 100644
index c2b8618..c4cb4c0
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** find_unaggregated_cols_walker(Node *node
*** 1829,1834 ****
--- 1829,1845 ----
/* do not descend into aggregate exprs */
return false;
}
+ if (IsA(node, GroupedVar))
+ {
+ GroupedVar *gvar = (GroupedVar *) node;
+
+ /*
+ * GroupedVar is currently used only for partial aggregation, so treat
+ * it like an Aggref above.
+ */
+ Assert(gvar->agg_partial != NULL);
+ return false;
+ }
return expression_tree_walker(node, find_unaggregated_cols_walker,
(void *) colnos);
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index 00a0fed..7d188ea
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyPlaceHolderVar(const PlaceHolderVar
*** 2206,2211 ****
--- 2206,2226 ----
}
/*
+ * _copyGroupedVar
+ */
+ static GroupedVar *
+ _copyGroupedVar(const GroupedVar *from)
+ {
+ GroupedVar *newnode = makeNode(GroupedVar);
+
+ COPY_NODE_FIELD(gvexpr);
+ COPY_NODE_FIELD(agg_partial);
+ COPY_SCALAR_FIELD(gvid);
+
+ return newnode;
+ }
+
+ /*
* _copySpecialJoinInfo
*/
static SpecialJoinInfo *
*************** copyObjectImpl(const void *from)
*** 4984,4989 ****
--- 4999,5007 ----
case T_PlaceHolderVar:
retval = _copyPlaceHolderVar(from);
break;
+ case T_GroupedVar:
+ retval = _copyGroupedVar(from);
+ break;
case T_SpecialJoinInfo:
retval = _copySpecialJoinInfo(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index 46573ae..f1dacd5
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalPlaceHolderVar(const PlaceHolderVa
*** 874,879 ****
--- 874,887 ----
}
static bool
+ _equalGroupedVar(const GroupedVar *a, const GroupedVar *b)
+ {
+ COMPARE_SCALAR_FIELD(gvid);
+
+ return true;
+ }
+
+ static bool
_equalSpecialJoinInfo(const SpecialJoinInfo *a, const SpecialJoinInfo *b)
{
COMPARE_BITMAPSET_FIELD(min_lefthand);
*************** equal(const void *a, const void *b)
*** 3148,3153 ****
--- 3156,3164 ----
case T_PlaceHolderVar:
retval = _equalPlaceHolderVar(a, b);
break;
+ case T_GroupedVar:
+ retval = _equalGroupedVar(a, b);
+ break;
case T_SpecialJoinInfo:
retval = _equalSpecialJoinInfo(a, b);
break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
new file mode 100644
index 3e8189c..5c00e55
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** exprType(const Node *expr)
*** 259,264 ****
--- 259,267 ----
case T_PlaceHolderVar:
type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
break;
+ case T_GroupedVar:
+ type = exprType((Node *) ((const GroupedVar *) expr)->agg_partial);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
*************** exprCollation(const Node *expr)
*** 931,936 ****
--- 934,942 ----
case T_PlaceHolderVar:
coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
break;
+ case T_GroupedVar:
+ coll = exprCollation((Node *) ((const GroupedVar *) expr)->gvexpr);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
coll = InvalidOid; /* keep compiler quiet */
*************** expression_tree_walker(Node *node,
*** 2198,2203 ****
--- 2204,2211 ----
break;
case T_PlaceHolderVar:
return walker(((PlaceHolderVar *) node)->phexpr, context);
+ case T_GroupedVar:
+ return walker(((GroupedVar *) node)->gvexpr, context);
case T_InferenceElem:
return walker(((InferenceElem *) node)->expr, context);
case T_AppendRelInfo:
*************** expression_tree_mutator(Node *node,
*** 2989,2994 ****
--- 2997,3012 ----
return (Node *) newnode;
}
break;
+ case T_GroupedVar:
+ {
+ GroupedVar *gv = (GroupedVar *) node;
+ GroupedVar *newnode;
+
+ FLATCOPY(newnode, gv, GroupedVar);
+ MUTATE(newnode->gvexpr, gv->gvexpr, Expr *);
+ MUTATE(newnode->agg_partial, gv->agg_partial, Aggref *);
+ return (Node *) newnode;
+ }
case T_InferenceElem:
{
InferenceElem *inferenceelemdexpr = (InferenceElem *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index 28cef85..4b6ee30
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outPlannerInfo(StringInfo str, const Pl
*** 2186,2191 ****
--- 2186,2192 ----
WRITE_NODE_FIELD(pcinfo_list);
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(placeholder_list);
+ WRITE_NODE_FIELD(grouped_var_list);
WRITE_NODE_FIELD(fkey_list);
WRITE_NODE_FIELD(query_pathkeys);
WRITE_NODE_FIELD(group_pathkeys);
*************** _outParamPathInfo(StringInfo str, const
*** 2408,2413 ****
--- 2409,2424 ----
}
static void
+ _outGroupedPathInfo(StringInfo str, const GroupedPathInfo *node)
+ {
+ WRITE_NODE_TYPE("GROUPEDPATHINFO");
+
+ WRITE_NODE_FIELD(target);
+ WRITE_NODE_FIELD(pathlist);
+ WRITE_NODE_FIELD(partial_pathlist);
+ }
+
+ static void
_outRestrictInfo(StringInfo str, const RestrictInfo *node)
{
WRITE_NODE_TYPE("RESTRICTINFO");
*************** _outPlaceHolderVar(StringInfo str, const
*** 2451,2456 ****
--- 2462,2477 ----
}
static void
+ _outGroupedVar(StringInfo str, const GroupedVar *node)
+ {
+ WRITE_NODE_TYPE("GROUPEDVAR");
+
+ WRITE_NODE_FIELD(gvexpr);
+ WRITE_NODE_FIELD(agg_partial);
+ WRITE_UINT_FIELD(gvid);
+ }
+
+ static void
_outSpecialJoinInfo(StringInfo str, const SpecialJoinInfo *node)
{
WRITE_NODE_TYPE("SPECIALJOININFO");
*************** outNode(StringInfo str, const void *obj)
*** 3996,4007 ****
--- 4017,4034 ----
case T_ParamPathInfo:
_outParamPathInfo(str, obj);
break;
+ case T_GroupedPathInfo:
+ _outGroupedPathInfo(str, obj);
+ break;
case T_RestrictInfo:
_outRestrictInfo(str, obj);
break;
case T_PlaceHolderVar:
_outPlaceHolderVar(str, obj);
break;
+ case T_GroupedVar:
+ _outGroupedVar(str, obj);
+ break;
case T_SpecialJoinInfo:
_outSpecialJoinInfo(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
new file mode 100644
index a883220..138f71c
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readVar(void)
*** 522,527 ****
--- 522,542 ----
}
/*
+ * _readGroupedVar
+ */
+ static GroupedVar *
+ _readGroupedVar(void)
+ {
+ READ_LOCALS(GroupedVar);
+
+ READ_NODE_FIELD(gvexpr);
+ READ_NODE_FIELD(agg_partial);
+ READ_UINT_FIELD(gvid);
+
+ READ_DONE();
+ }
+
+ /*
* _readConst
*/
static Const *
*************** parseNodeString(void)
*** 2440,2445 ****
--- 2455,2462 ----
return_value = _readTableFunc();
else if (MATCH("VAR", 3))
return_value = _readVar();
+ else if (MATCH("GROUPEDVAR", 10))
+ return_value = _readGroupedVar();
else if (MATCH("CONST", 5))
return_value = _readConst();
else if (MATCH("PARAM", 5))
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 b93b4fc..ad14578
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** set_rel_pathlist(PlannerInfo *root, RelO
*** 486,492 ****
* 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
--- 486,495 ----
* 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
*** 686,691 ****
--- 689,695 ----
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
*** 694,708 ****
*/
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);
/* Consider index scans */
! create_index_paths(root, rel);
/* Consider TID scans */
create_tidscan_paths(root, rel);
--- 698,725 ----
*/
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->gpi != NULL && required_outer == NULL)
! create_grouped_path(root, rel, seq_path, false, false, AGG_HASHED);
! /* If appropriate, consider parallel sequential scan (plain or grouped) */
if (rel->consider_parallel && required_outer == NULL)
create_plain_partial_paths(root, rel);
/* Consider index scans */
! create_index_paths(root, rel, false);
! if (rel->gpi != NULL)
! {
! /*
! * TODO Instead of calling the whole clause-matching machinery twice
! * (there should be no difference between plain and grouped paths from
! * this point of view), consider returning a separate list of paths
! * usable as grouped ones.
! */
! create_index_paths(root, rel, true);
! }
/* Consider TID scans */
create_tidscan_paths(root, rel);
*************** static void
*** 716,721 ****
--- 733,739 ----
create_plain_partial_paths(PlannerInfo *root, RelOptInfo *rel)
{
int parallel_workers;
+ Path *path;
parallel_workers = compute_parallel_worker(rel, rel->pages, -1);
*************** create_plain_partial_paths(PlannerInfo *
*** 724,730 ****
return;
/* Add an unordered partial path based on a parallel sequential scan. */
! add_partial_path(rel, create_seqscan_path(root, rel, NULL, parallel_workers));
}
/*
--- 742,849 ----
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->gpi != NULL)
! create_grouped_path(root, rel, path, false, true, AGG_HASHED);
! }
!
! /*
! * Apply partial aggregation to a subpath and add the AggPath to the
! * appropriate pathlist.
! *
! * "precheck" tells whether the aggregation path should first be checked using
! * add_path_precheck().
! *
! * If "partial" is true, the resulting path is considered partial in terms of
! * parallel execution.
! *
! * The path we create here shouldn't be parameterized because of supposedly
! * high startup cost of aggregation (whether due to build of hash table for
! * AGG_HASHED strategy or due to explicit sort for AGG_SORTED).
! *
! * XXX IndexPath as an input for AGG_SORTED might seem to be an exception, but
! * aggregation of its output is only beneficial if it's performed by multiple
! * workers, i.e. the resulting path is partial (Besides parallel aggregation,
! * the other use case of aggregation push-down is aggregation performed on
! * remote database, but that has nothing to do with IndexScan). And partial
! * path cannot be parameterized because it's semantically wrong to use it on
! * the inner side of NL join.
! */
! void
! create_grouped_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
! bool precheck, bool partial, AggStrategy aggstrategy)
! {
! List *group_clauses = NIL;
! List *group_exprs = NIL;
! List *agg_exprs = NIL;
! Path *agg_path;
!
! /*
! * If the AggPath should be partial, the subpath must be too, and
! * therefore the subpath is essentially parallel_safe.
! */
! Assert(subpath->parallel_safe || !partial);
!
! /*
! * Grouped path should never be parameterized, so we're not supposed to
! * receive parameterized subpath.
! */
! Assert(subpath->param_info == NULL);
!
! /*
! * Note that "partial" in the following function names refers to 2-stage
! * aggregation, not to parallel processing.
! */
! if (aggstrategy == AGG_HASHED)
! agg_path = (Path *) create_partial_agg_hashed_path(root, subpath,
! true,
! &group_clauses,
! &group_exprs,
! &agg_exprs,
! subpath->rows);
! else if (aggstrategy == AGG_SORTED)
! agg_path = (Path *) create_partial_agg_sorted_path(root, subpath,
! true,
! &group_clauses,
! &group_exprs,
! &agg_exprs,
! subpath->rows);
! else
! elog(ERROR, "unexpected strategy %d", aggstrategy);
!
! /* Add the grouped path to the list of grouped base paths. */
! if (agg_path != NULL)
! {
! if (precheck)
! {
! List *pathkeys;
!
! /* AGG_HASH is not supposed to generate sorted output. */
! pathkeys = aggstrategy == AGG_SORTED ? subpath->pathkeys : NIL;
!
! if (!partial &&
! !add_path_precheck(rel, agg_path->startup_cost,
! agg_path->total_cost, pathkeys, NULL,
! true))
! return;
!
! if (partial &&
! !add_partial_path_precheck(rel, agg_path->total_cost, pathkeys,
! true))
! return;
! }
!
! if (!partial)
! add_path(rel, (Path *) agg_path, true);
! else
! add_partial_path(rel, (Path *) agg_path, true);
! }
}
/*
*************** set_tablesample_rel_pathlist(PlannerInfo
*** 810,816 ****
path = (Path *) create_material_path(rel, path);
}
! add_path(rel, path);
/* For the moment, at least, there are no other paths to consider */
}
--- 929,935 ----
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
*** 1067,1072 ****
--- 1186,1234 ----
appinfo);
/*
+ * If grouping is applicable to the parent relation, it should be
+ * applicable to the children too. Make sure the child rel has valid
+ * sortgrouprefs.
+ *
+ * TODO Consider if this is really needed --- child rel is not joined
+ * to grouped rel itself, so it might not participate on creation of
+ * the grouped path target that upper joins will see.
+ */
+ 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.
+ */
+ if (rel->gpi != NULL)
+ {
+ PathTarget *target = rel->gpi->target;
+
+ Assert(target->sortgrouprefs != NULL);
+
+ Assert(childrel->gpi == NULL);
+ childrel->gpi = makeNode(GroupedPathInfo);
+ memcpy(childrel->gpi, rel->gpi, sizeof(GroupedPathInfo));
+
+ /*
+ * add_grouping_info_to_base_rels was not sure if grouping makes
+ * sense for the parent rel, so create a separate copy of the
+ * target now.
+ */
+ childrel->gpi->target = copy_pathtarget(childrel->gpi->target);
+
+ /* Translate vars of the grouping target. */
+ Assert(childrel->gpi->target->exprs != NIL);
+ childrel->gpi->target->exprs = (List *)
+ adjust_appendrel_attrs(root,
+ (Node *) childrel->gpi->target->exprs,
+ appinfo);
+ }
+
+ /*
* 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
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1281,1286 ****
--- 1443,1450 ----
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;
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1324,1329 ****
--- 1488,1516 ----
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->gpi != NULL && childrel->gpi->pathlist != NIL)
+ {
+ Path *path;
+
+ path = (Path *) linitial(childrel->gpi->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
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1395,1401 ****
*/
if (subpaths_valid)
add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0,
! partitioned_rels));
/*
* Consider an append of partial unordered, unparameterized partial paths.
--- 1582,1589 ----
*/
if (subpaths_valid)
add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0,
! partitioned_rels),
! false);
/*
* Consider an append of partial unordered, unparameterized partial paths.
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1422,1429 ****
/* Generate a partial append path. */
appendpath = create_append_path(rel, partial_subpaths, NULL,
! parallel_workers, partitioned_rels);
! add_partial_path(rel, (Path *) appendpath);
}
/*
--- 1610,1630 ----
/* Generate a partial append path. */
appendpath = create_append_path(rel, partial_subpaths, NULL,
! parallel_workers,
! partitioned_rels);
! add_partial_path(rel, (Path *) appendpath, false);
! }
!
! /* TODO Also partial grouped paths? */
! if (grouped_subpaths_valid)
! {
! Path *path;
!
! path = (Path *) create_append_path(rel, grouped_subpaths, NULL, 0,
! partitioned_rels);
! /* pathtarget will produce the grouped relation.. */
! path->pathtarget = rel->gpi->target;
! add_path(rel, path, true);
}
/*
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1476,1482 ****
if (subpaths_valid)
add_path(rel, (Path *)
create_append_path(rel, subpaths, required_outer, 0,
! partitioned_rels));
}
}
--- 1677,1684 ----
if (subpaths_valid)
add_path(rel, (Path *)
create_append_path(rel, subpaths, required_outer, 0,
! partitioned_rels),
! false);
}
}
*************** generate_mergeappend_paths(PlannerInfo *
*** 1572,1585 ****
startup_subpaths,
pathkeys,
NULL,
! partitioned_rels));
if (startup_neq_total)
add_path(rel, (Path *) create_merge_append_path(root,
rel,
total_subpaths,
pathkeys,
NULL,
! partitioned_rels));
}
}
--- 1774,1789 ----
startup_subpaths,
pathkeys,
NULL,
! partitioned_rels),
! false);
if (startup_neq_total)
add_path(rel, (Path *) create_merge_append_path(root,
rel,
total_subpaths,
pathkeys,
NULL,
! partitioned_rels),
! false);
}
}
*************** set_dummy_rel_pathlist(RelOptInfo *rel)
*** 1712,1718 ****
rel->pathlist = NIL;
rel->partial_pathlist = NIL;
! add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL));
/*
* We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
--- 1916,1922 ----
rel->pathlist = NIL;
rel->partial_pathlist = NIL;
! add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL), false);
/*
* We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
*************** set_subquery_pathlist(PlannerInfo *root,
*** 1926,1932 ****
/* Generate outer path using this subpath */
add_path(rel, (Path *)
create_subqueryscan_path(root, rel, subpath,
! pathkeys, required_outer));
}
}
--- 2130,2136 ----
/* 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,
*** 1995,2001 ****
/* Generate appropriate path */
add_path(rel, create_functionscan_path(root, rel,
! pathkeys, required_outer));
}
/*
--- 2199,2205 ----
/* Generate appropriate path */
add_path(rel, create_functionscan_path(root, rel,
! pathkeys, required_outer), false);
}
/*
*************** set_values_pathlist(PlannerInfo *root, R
*** 2015,2021 ****
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_valuesscan_path(root, rel, required_outer));
}
/*
--- 2219,2225 ----
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_valuesscan_path(root, rel, required_outer), false);
}
/*
*************** set_tablefunc_pathlist(PlannerInfo *root
*** 2036,2042 ****
/* Generate appropriate path */
add_path(rel, create_tablefuncscan_path(root, rel,
! required_outer));
}
/*
--- 2240,2246 ----
/* Generate appropriate path */
add_path(rel, create_tablefuncscan_path(root, rel,
! required_outer), false);
}
/*
*************** set_cte_pathlist(PlannerInfo *root, RelO
*** 2102,2108 ****
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_ctescan_path(root, rel, required_outer));
}
/*
--- 2306,2312 ----
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_ctescan_path(root, rel, required_outer), false);
}
/*
*************** set_namedtuplestore_pathlist(PlannerInfo
*** 2129,2135 ****
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_namedtuplestorescan_path(root, rel, required_outer));
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(rel);
--- 2333,2340 ----
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_namedtuplestorescan_path(root, rel, required_outer),
! false);
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(rel);
*************** set_worktable_pathlist(PlannerInfo *root
*** 2182,2188 ****
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_worktablescan_path(root, rel, required_outer));
}
/*
--- 2387,2394 ----
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_worktablescan_path(root, rel, required_outer),
! false);
}
/*
*************** set_worktable_pathlist(PlannerInfo *root
*** 2195,2208 ****
* path that some GatherPath or GatherMergePath has a reference to.)
*/
void
! generate_gather_paths(PlannerInfo *root, RelOptInfo *rel)
{
Path *cheapest_partial_path;
Path *simple_gather_path;
ListCell *lc;
/* If there are no partial paths, there's nothing to do here. */
! if (rel->partial_pathlist == NIL)
return;
/*
--- 2401,2421 ----
* path that some GatherPath or GatherMergePath has a reference to.)
*/
void
! generate_gather_paths(PlannerInfo *root, RelOptInfo *rel, bool grouped)
{
Path *cheapest_partial_path;
Path *simple_gather_path;
+ List *pathlist = NIL;
+ PathTarget *partial_target;
ListCell *lc;
+ if (!grouped)
+ pathlist = rel->partial_pathlist;
+ else if (rel->gpi != NULL)
+ pathlist = rel->gpi->partial_pathlist;
+
/* If there are no partial paths, there's nothing to do here. */
! if (pathlist == NIL)
return;
/*
*************** generate_gather_paths(PlannerInfo *root,
*** 2210,2226 ****
* path of interest: the cheapest one. That will be the one at the front
* of partial_pathlist because of the way add_partial_path works.
*/
! 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);
/*
* For each useful ordering, we can consider an order-preserving Gather
* Merge.
*/
! foreach (lc, rel->partial_pathlist)
{
Path *subpath = (Path *) lfirst(lc);
GatherMergePath *path;
--- 2423,2445 ----
* path of interest: the cheapest one. That will be the one at the front
* of partial_pathlist because of the way add_partial_path works.
*/
! cheapest_partial_path = linitial(pathlist);
!
! if (!grouped)
! partial_target = rel->reltarget;
! else if (rel->gpi != NULL)
! partial_target = rel->gpi->target;
!
simple_gather_path = (Path *)
! create_gather_path(root, rel, cheapest_partial_path, partial_target,
NULL, NULL);
! add_path(rel, simple_gather_path, grouped);
/*
* For each useful ordering, we can consider an order-preserving Gather
* Merge.
*/
! foreach (lc, pathlist)
{
Path *subpath = (Path *) lfirst(lc);
GatherMergePath *path;
*************** generate_gather_paths(PlannerInfo *root,
*** 2228,2236 ****
if (subpath->pathkeys == NIL)
continue;
! path = create_gather_merge_path(root, rel, subpath, rel->reltarget,
subpath->pathkeys, NULL, NULL);
! add_path(rel, &path->path);
}
}
--- 2447,2455 ----
if (subpath->pathkeys == NIL)
continue;
! path = create_gather_merge_path(root, rel, subpath, partial_target,
subpath->pathkeys, NULL, NULL);
! add_path(rel, &path->path, grouped);
}
}
*************** standard_join_search(PlannerInfo *root,
*** 2396,2402 ****
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);
--- 2615,2622 ----
rel = (RelOptInfo *) lfirst(lc);
/* Create GatherPaths for any useful partial paths for rel */
! generate_gather_paths(root, rel, false);
! generate_gather_paths(root, rel, true);
/* Find and save the cheapest paths for this rel */
set_cheapest(rel);
*************** create_partial_bitmap_paths(PlannerInfo
*** 3047,3053 ****
return;
add_partial_path(rel, (Path *) create_bitmap_heap_path(root, rel,
! bitmapqual, rel->lateral_relids, 1.0, parallel_workers));
}
/*
--- 3267,3273 ----
return;
add_partial_path(rel, (Path *) create_bitmap_heap_path(root, rel,
! bitmapqual, rel->lateral_relids, 1.0, parallel_workers), false);
}
/*
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
new file mode 100644
index 6e4bae8..a6fa713
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
***************
*** 32,37 ****
--- 32,38 ----
#include "optimizer/predtest.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+ #include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "utils/builtins.h"
#include "utils/bytea.h"
*************** static bool eclass_already_used(Equivale
*** 107,119 ****
static bool bms_equal_any(Relids relids, List *relids_list);
static void get_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
! List **bitindexpaths);
static List *build_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
bool useful_predicate,
ScanTypeControl scantype,
bool *skip_nonnative_saop,
! bool *skip_lower_saop);
static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
List *clauses, List *other_clauses);
static List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
--- 108,121 ----
static bool bms_equal_any(Relids relids, List *relids_list);
static void get_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
! List **bitindexpaths, bool grouped);
static List *build_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
bool useful_predicate,
ScanTypeControl scantype,
bool *skip_nonnative_saop,
! bool *skip_lower_saop,
! bool grouped);
static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
List *clauses, List *other_clauses);
static List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
*************** static Const *string_to_const(const char
*** 229,235 ****
* as meaning "unparameterized so far as the indexquals are concerned".
*/
void
! create_index_paths(PlannerInfo *root, RelOptInfo *rel)
{
List *indexpaths;
List *bitindexpaths;
--- 231,237 ----
* as meaning "unparameterized so far as the indexquals are concerned".
*/
void
! create_index_paths(PlannerInfo *root, RelOptInfo *rel, bool grouped)
{
List *indexpaths;
List *bitindexpaths;
*************** create_index_paths(PlannerInfo *root, Re
*** 274,281 ****
* non-parameterized paths. Plain paths go directly to add_path(),
* bitmap paths are added to bitindexpaths to be handled below.
*/
! get_index_paths(root, rel, index, &rclauseset,
! &bitindexpaths);
/*
* Identify the join clauses that can match the index. For the moment
--- 276,283 ----
* non-parameterized paths. Plain paths go directly to add_path(),
* bitmap paths are added to bitindexpaths to be handled below.
*/
! get_index_paths(root, rel, index, &rclauseset, &bitindexpaths,
! grouped);
/*
* Identify the join clauses that can match the index. For the moment
*************** 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, 0);
! add_path(rel, (Path *) bpath);
/* create a partial bitmap heap path */
if (rel->consider_parallel && rel->lateral_relids == NULL)
--- 340,346 ----
bitmapqual = choose_bitmap_and(root, rel, bitindexpaths);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
rel->lateral_relids, 1.0, 0);
! add_path(rel, (Path *) bpath, false);
/* create a partial bitmap heap path */
if (rel->consider_parallel && rel->lateral_relids == NULL)
*************** create_index_paths(PlannerInfo *root, Re
*** 415,421 ****
loop_count = get_loop_count(root, rel->relid, required_outer);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
required_outer, loop_count, 0);
! add_path(rel, (Path *) bpath);
}
}
}
--- 417,423 ----
loop_count = get_loop_count(root, rel->relid, required_outer);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
required_outer, loop_count, 0);
! add_path(rel, (Path *) bpath, false);
}
}
}
*************** get_join_index_paths(PlannerInfo *root,
*** 667,673 ****
Assert(clauseset.nonempty);
/* Build index path(s) using the collected set of clauses */
! get_index_paths(root, rel, index, &clauseset, bitindexpaths);
/*
* Remember we considered paths for this set of relids. We use lcons not
--- 669,675 ----
Assert(clauseset.nonempty);
/* Build index path(s) using the collected set of clauses */
! get_index_paths(root, rel, index, &clauseset, bitindexpaths, false);
/*
* Remember we considered paths for this set of relids. We use lcons not
*************** bms_equal_any(Relids relids, List *relid
*** 736,742 ****
static void
get_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
! List **bitindexpaths)
{
List *indexpaths;
bool skip_nonnative_saop = false;
--- 738,744 ----
static void
get_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
! List **bitindexpaths, bool grouped)
{
List *indexpaths;
bool skip_nonnative_saop = false;
*************** get_index_paths(PlannerInfo *root, RelOp
*** 754,760 ****
index->predOK,
ST_ANYSCAN,
&skip_nonnative_saop,
! &skip_lower_saop);
/*
* If we skipped any lower-order ScalarArrayOpExprs on an index with an AM
--- 756,762 ----
index->predOK,
ST_ANYSCAN,
&skip_nonnative_saop,
! &skip_lower_saop, grouped);
/*
* If we skipped any lower-order ScalarArrayOpExprs on an index with an AM
*************** get_index_paths(PlannerInfo *root, RelOp
*** 769,775 ****
index->predOK,
ST_ANYSCAN,
&skip_nonnative_saop,
! NULL));
}
/*
--- 771,777 ----
index->predOK,
ST_ANYSCAN,
&skip_nonnative_saop,
! NULL, grouped));
}
/*
*************** get_index_paths(PlannerInfo *root, RelOp
*** 789,797 ****
IndexPath *ipath = (IndexPath *) lfirst(lc);
if (index->amhasgettuple)
! add_path(rel, (Path *) ipath);
! if (index->amhasgetbitmap &&
(ipath->path.pathkeys == NIL ||
ipath->indexselectivity < 1.0))
*bitindexpaths = lappend(*bitindexpaths, ipath);
--- 791,799 ----
IndexPath *ipath = (IndexPath *) lfirst(lc);
if (index->amhasgettuple)
! add_path(rel, (Path *) ipath, grouped);
! if (!grouped && index->amhasgetbitmap &&
(ipath->path.pathkeys == NIL ||
ipath->indexselectivity < 1.0))
*bitindexpaths = lappend(*bitindexpaths, ipath);
*************** get_index_paths(PlannerInfo *root, RelOp
*** 802,815 ****
* natively, generate bitmap scan paths relying on executor-managed
* ScalarArrayOpExpr.
*/
! if (skip_nonnative_saop)
{
indexpaths = build_index_paths(root, rel,
index, clauses,
false,
ST_BITMAPSCAN,
NULL,
! NULL);
*bitindexpaths = list_concat(*bitindexpaths, indexpaths);
}
}
--- 804,818 ----
* natively, generate bitmap scan paths relying on executor-managed
* ScalarArrayOpExpr.
*/
! if (!grouped && skip_nonnative_saop)
{
indexpaths = build_index_paths(root, rel,
index, clauses,
false,
ST_BITMAPSCAN,
NULL,
! NULL,
! false);
*bitindexpaths = list_concat(*bitindexpaths, indexpaths);
}
}
*************** build_index_paths(PlannerInfo *root, Rel
*** 861,867 ****
bool useful_predicate,
ScanTypeControl scantype,
bool *skip_nonnative_saop,
! bool *skip_lower_saop)
{
List *result = NIL;
IndexPath *ipath;
--- 864,870 ----
bool useful_predicate,
ScanTypeControl scantype,
bool *skip_nonnative_saop,
! bool *skip_lower_saop, bool grouped)
{
List *result = NIL;
IndexPath *ipath;
*************** build_index_paths(PlannerInfo *root, Rel
*** 878,883 ****
--- 881,890 ----
bool index_is_ordered;
bool index_only_scan;
int indexcol;
+ bool can_agg_sorted;
+ List *group_clauses, *group_exprs, *agg_exprs;
+ AggPath *agg_path;
+ double agg_input_rows;
/*
* Check that index supports the desired scan type(s)
*************** build_index_paths(PlannerInfo *root, Rel
*** 891,896 ****
--- 898,906 ----
case ST_BITMAPSCAN:
if (!index->amhasgetbitmap)
return NIL;
+
+ if (grouped)
+ return NIL;
break;
case ST_ANYSCAN:
/* either or both are OK */
*************** build_index_paths(PlannerInfo *root, Rel
*** 1032,1037 ****
--- 1042,1051 ----
* later merging or final output ordering, OR the index has a useful
* predicate, OR an index-only scan is possible.
*/
+ can_agg_sorted = true;
+ group_clauses = NIL;
+ group_exprs = NIL;
+ agg_exprs = NIL;
if (index_clauses != NIL || useful_pathkeys != NIL || useful_predicate ||
index_only_scan)
{
*************** build_index_paths(PlannerInfo *root, Rel
*** 1048,1054 ****
outer_relids,
loop_count,
false);
! result = lappend(result, ipath);
/*
* If appropriate, consider parallel index scan. We don't allow
--- 1062,1086 ----
outer_relids,
loop_count,
false);
! if (!grouped)
! result = lappend(result, ipath);
! else
! {
! /* TODO Double-check if this is the correct input value. */
! agg_input_rows = rel->rows * ipath->indexselectivity;
!
! agg_path = create_partial_agg_sorted_path(root, (Path *) ipath,
! true,
! &group_clauses,
! &group_exprs,
! &agg_exprs,
! agg_input_rows);
!
! if (agg_path != NULL)
! result = lappend(result, agg_path);
! else
! can_agg_sorted = false;
! }
/*
* If appropriate, consider parallel index scan. We don't allow
*************** build_index_paths(PlannerInfo *root, Rel
*** 1077,1083 ****
* using parallel workers, just free it.
*/
if (ipath->path.parallel_workers > 0)
! add_partial_path(rel, (Path *) ipath);
else
pfree(ipath);
}
--- 1109,1139 ----
* using parallel workers, just free it.
*/
if (ipath->path.parallel_workers > 0)
! {
! if (!grouped)
! add_partial_path(rel, (Path *) ipath, grouped);
! else if (can_agg_sorted && outer_relids == NULL)
! {
! /* TODO Double-check if this is the correct input value. */
! agg_input_rows = rel->rows * ipath->indexselectivity;
!
! agg_path = create_partial_agg_sorted_path(root,
! (Path *) ipath,
! false,
! &group_clauses,
! &group_exprs,
! &agg_exprs,
! agg_input_rows);
!
! /*
! * If create_agg_sorted_path succeeded once, it should
! * always do.
! */
! Assert(agg_path != NULL);
!
! add_partial_path(rel, (Path *) agg_path, grouped);
! }
! }
else
pfree(ipath);
}
*************** build_index_paths(PlannerInfo *root, Rel
*** 1105,1111 ****
outer_relids,
loop_count,
false);
! result = lappend(result, ipath);
/* If appropriate, consider parallel index scan */
if (index->amcanparallel &&
--- 1161,1185 ----
outer_relids,
loop_count,
false);
!
! if (!grouped)
! result = lappend(result, ipath);
! else if (can_agg_sorted)
! {
! /* TODO Double-check if this is the correct input value. */
! agg_input_rows = rel->rows * ipath->indexselectivity;
!
! agg_path = create_partial_agg_sorted_path(root,
! (Path *) ipath,
! true,
! &group_clauses,
! &group_exprs,
! &agg_exprs,
! agg_input_rows);
!
! Assert(agg_path != NULL);
! result = lappend(result, agg_path);
! }
/* If appropriate, consider parallel index scan */
if (index->amcanparallel &&
*************** build_index_paths(PlannerInfo *root, Rel
*** 1129,1135 ****
* using parallel workers, just free it.
*/
if (ipath->path.parallel_workers > 0)
! add_partial_path(rel, (Path *) ipath);
else
pfree(ipath);
}
--- 1203,1227 ----
* using parallel workers, just free it.
*/
if (ipath->path.parallel_workers > 0)
! {
! if (!grouped)
! add_partial_path(rel, (Path *) ipath, grouped);
! else if (can_agg_sorted && outer_relids == NULL)
! {
! /* TODO Double-check if this is the correct input value. */
! agg_input_rows = rel->rows * ipath->indexselectivity;
!
! agg_path = create_partial_agg_sorted_path(root,
! (Path *) ipath,
! false,
! &group_clauses,
! &group_exprs,
! &agg_exprs,
! agg_input_rows);
! Assert(agg_path != NULL);
! add_partial_path(rel, (Path *) agg_path, grouped);
! }
! }
else
pfree(ipath);
}
*************** build_paths_for_OR(PlannerInfo *root, Re
*** 1244,1250 ****
useful_predicate,
ST_BITMAPSCAN,
NULL,
! NULL);
result = list_concat(result, indexpaths);
}
--- 1336,1343 ----
useful_predicate,
ST_BITMAPSCAN,
NULL,
! NULL,
! false);
result = list_concat(result, indexpaths);
}
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
new file mode 100644
index 5aedcd1..12f0356
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
***************
*** 22,27 ****
--- 22,28 ----
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
+ #include "optimizer/tlist.h"
/* Hook for plugins to get control in add_paths_to_joinrel() */
set_join_pathlist_hook_type set_join_pathlist_hook = NULL;
*************** static void try_partial_mergejoin_path(P
*** 38,66 ****
List *outersortkeys,
List *innersortkeys,
JoinType jointype,
! JoinPathExtraData *extra);
static void sort_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
! RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinType jointype, JoinPathExtraData *extra);
static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinType jointype, JoinPathExtraData *extra);
static void consider_parallel_nestloop(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra);
static void consider_parallel_mergejoin(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra,
! Path *inner_cheapest_total);
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,87 ----
List *outersortkeys,
List *innersortkeys,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped,
! bool do_aggregate);
static void sort_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
! RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinType jointype, JoinPathExtraData *extra,
! bool grouped);
! static void sort_inner_and_outer_common(PlannerInfo *root,
! RelOptInfo *joinrel,
! RelOptInfo *outerrel,
! RelOptInfo *innerrel,
! JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped_outer,
! bool grouped_inner,
! bool do_aggregate);
static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinType jointype, JoinPathExtraData *extra,
! bool grouped);
static void consider_parallel_nestloop(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped, bool do_aggregate);
static void consider_parallel_mergejoin(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra,
! Path *inner_cheapest_total,
! bool grouped);
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinType jointype, JoinPathExtraData *extra,
! bool grouped);
! static bool is_grouped_join_target_complete(PlannerInfo *root,
! PathTarget *jointarget,
! Path *outer_path,
! Path *inner_path);
static List *select_mergejoin_clauses(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
*************** static void generate_mergejoin_paths(Pla
*** 77,83 ****
bool useallclauses,
Path *inner_cheapest_total,
List *merge_pathkeys,
! bool is_partial);
/*
--- 98,107 ----
bool useallclauses,
Path *inner_cheapest_total,
List *merge_pathkeys,
! bool is_partial,
! bool grouped_outer,
! bool grouped_inner,
! bool do_aggregate);
/*
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 227,234 ****
* sorted. Skip this if we can't mergejoin.
*/
if (mergejoin_allowed)
sort_inner_and_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra);
/*
* 2. Consider paths where the outer relation need not be explicitly
--- 251,262 ----
* sorted. Skip this if we can't mergejoin.
*/
if (mergejoin_allowed)
+ {
sort_inner_and_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra, false);
! sort_inner_and_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra, true);
! }
/*
* 2. Consider paths where the outer relation need not be explicitly
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 238,245 ****
* joins at all, so it wouldn't work in the prohibited cases either.)
*/
if (mergejoin_allowed)
match_unsorted_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra);
#ifdef NOT_USED
--- 266,277 ----
* joins at all, so it wouldn't work in the prohibited cases either.)
*/
if (mergejoin_allowed)
+ {
match_unsorted_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra, false);
! match_unsorted_outer(root, joinrel, outerrel, innerrel,
! jointype, &extra, true);
! }
#ifdef NOT_USED
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 265,272 ****
* 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
--- 297,308 ----
* 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,
*** 330,339 ****
Path *inner_path,
List *pathkeys,
JoinType jointype,
! JoinPathExtraData *extra)
{
Relids required_outer;
JoinCostWorkspace workspace;
/*
* Check to see if proposed path is still parameterized, and reject if the
--- 366,385 ----
Path *inner_path,
List *pathkeys,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped,
! bool do_aggregate)
{
Relids required_outer;
JoinCostWorkspace workspace;
+ Path *join_path;
+ PathTarget *join_target;
+
+ /* Caller should not request aggregation w/o grouped output. */
+ Assert(!do_aggregate || grouped);
+
+ /* GroupedPathInfo is necessary for us to produce a grouped set. */
+ Assert(joinrel->gpi != NULL || !grouped);
/*
* Check to see if proposed path is still parameterized, and reject if the
*************** try_nestloop_path(PlannerInfo *root,
*** 341,359 ****
* says to allow it anyway. Also, we must reject if have_dangerous_phv
* doesn't like the look of it, which could only happen if the nestloop is
* still parameterized.
*/
! required_outer = calc_nestloop_required_outer(outer_path,
! inner_path);
! if (required_outer &&
! ((!bms_overlap(required_outer, extra->param_source_rels) &&
! !allow_star_schema_join(root, outer_path, inner_path)) ||
! have_dangerous_phv(root,
! outer_path->parent->relids,
! PATH_REQ_OUTER(inner_path))))
{
! /* Waste no memory when we reject a path here */
! bms_free(required_outer);
! return;
}
/*
--- 387,409 ----
* says to allow it anyway. Also, we must reject if have_dangerous_phv
* doesn't like the look of it, which could only happen if the nestloop is
* still parameterized.
+ *
+ * Grouped path should never be parameterized.
*/
! required_outer = calc_nestloop_required_outer(outer_path, inner_path);
! if (required_outer)
{
! if (grouped ||
! (!bms_overlap(required_outer, extra->param_source_rels) &&
! !allow_star_schema_join(root, outer_path, inner_path)) ||
! have_dangerous_phv(root,
! outer_path->parent->relids,
! PATH_REQ_OUTER(inner_path)))
! {
! /* Waste no memory when we reject a path here */
! bms_free(required_outer);
! return;
! }
}
/*
*************** try_nestloop_path(PlannerInfo *root,
*** 368,388 ****
initial_cost_nestloop(root, &workspace, jointype,
outer_path, inner_path, extra);
! if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer))
{
! add_path(joinrel, (Path *)
! create_nestloop_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! pathkeys,
! required_outer));
}
else
{
--- 418,453 ----
initial_cost_nestloop(root, &workspace, jointype,
outer_path, inner_path, extra);
! /*
! * Determine which target the join should produce.
! *
! * In the case of explicit aggregation, output of the join itself is
! * plain.
! */
! if (!grouped || do_aggregate)
! join_target = joinrel->reltarget;
! else
! join_target = joinrel->gpi->target;
!
! join_path = (Path *) create_nestloop_path(root, joinrel, jointype,
! &workspace, extra,
! outer_path, inner_path,
! extra->restrictlist, pathkeys,
! required_outer, join_target);
!
! /* Do partial aggregation if needed. */
! if (do_aggregate && required_outer == NULL)
! {
! create_grouped_path(root, joinrel, join_path, true, false,
! AGG_HASHED);
! create_grouped_path(root, joinrel, join_path, true, false,
! AGG_SORTED);
! }
! else if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer, grouped))
{
! add_path(joinrel, join_path, grouped);
}
else
{
*************** try_partial_nestloop_path(PlannerInfo *r
*** 403,411 ****
Path *inner_path,
List *pathkeys,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinCostWorkspace workspace;
/*
* If the inner path is parameterized, the parameterization must be fully
--- 468,484 ----
Path *inner_path,
List *pathkeys,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped,
! bool do_aggregate)
{
JoinCostWorkspace workspace;
+ Path *join_path;
+ PathTarget *join_target;
+
+ /* The same checks we do in try_nestloop_path. */
+ Assert(!do_aggregate || grouped);
+ Assert(joinrel->gpi != NULL || !grouped);
/*
* If the inner path is parameterized, the parameterization must be fully
*************** try_partial_nestloop_path(PlannerInfo *r
*** 428,448 ****
*/
initial_cost_nestloop(root, &workspace, jointype,
outer_path, inner_path, extra);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys))
return;
! /* Might be good enough to be worth trying, so let's try it. */
! add_partial_path(joinrel, (Path *)
! create_nestloop_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! pathkeys,
! NULL));
}
/*
--- 501,581 ----
*/
initial_cost_nestloop(root, &workspace, jointype,
outer_path, inner_path, extra);
!
! /*
! * Determine which target the join should produce.
! *
! * In the case of explicit aggregation, output of the join itself is
! * plain.
! */
! if (!grouped || do_aggregate)
! join_target = joinrel->reltarget;
! else
! {
! Assert(joinrel->gpi != NULL);
! join_target = joinrel->gpi->target;
! }
!
! join_path = (Path *) create_nestloop_path(root, joinrel, jointype,
! &workspace, extra,
! outer_path, inner_path,
! extra->restrictlist, pathkeys,
! NULL, join_target);
!
! if (do_aggregate)
! {
! create_grouped_path(root, joinrel, join_path, true, true, AGG_HASHED);
! create_grouped_path(root, joinrel, join_path, true, true, AGG_SORTED);
! }
! else if (add_partial_path_precheck(joinrel, workspace.total_cost,
! pathkeys, grouped))
! {
! /* Might be good enough to be worth trying, so let's try it. */
! add_partial_path(joinrel, (Path *) join_path, grouped);
! }
! }
!
! static void
! try_grouped_nestloop_path(PlannerInfo *root,
! RelOptInfo *joinrel,
! Path *outer_path,
! Path *inner_path,
! List *pathkeys,
! JoinType jointype,
! JoinPathExtraData *extra,
! bool do_aggregate,
! bool partial)
! {
! /*
! * Missing GroupedPathInfo indicates that we should not try to create a
! * grouped join.
! */
! if (joinrel->gpi == NULL)
return;
! /*
! * Reject the path if we're supposed to combine grouped and plain relation
! * but the grouped one does not evaluate all the relevant aggregates.
! */
! if (!do_aggregate &&
! !is_grouped_join_target_complete(root, joinrel->gpi->target,
! outer_path, inner_path))
! return;
!
! /*
! * As repeated aggregation doesn't seem to be attractive, make sure that
! * the resulting grouped relation is not parameterized.
! */
! if (outer_path->param_info != NULL || inner_path->param_info != NULL)
! return;
!
! if (!partial)
! try_nestloop_path(root, joinrel, outer_path, inner_path, pathkeys,
! jointype, extra, true, do_aggregate);
! else
! try_partial_nestloop_path(root, joinrel, outer_path, inner_path,
! pathkeys, jointype, extra, true,
! do_aggregate);
}
/*
*************** try_mergejoin_path(PlannerInfo *root,
*** 461,470 ****
List *innersortkeys,
JoinType jointype,
JoinPathExtraData *extra,
! bool is_partial)
{
Relids required_outer;
JoinCostWorkspace workspace;
if (is_partial)
{
--- 594,613 ----
List *innersortkeys,
JoinType jointype,
JoinPathExtraData *extra,
! bool is_partial,
! bool grouped,
! bool do_aggregate)
{
Relids required_outer;
JoinCostWorkspace workspace;
+ Path *join_path;
+ PathTarget *join_target;
+
+ /* Caller should not request aggregation w/o grouped output. */
+ Assert(!do_aggregate || grouped);
+
+ /* GroupedPathInfo is necessary for us to produce a grouped set. */
+ Assert(joinrel->gpi != NULL || !grouped);
if (is_partial)
{
*************** try_mergejoin_path(PlannerInfo *root,
*** 477,498 ****
outersortkeys,
innersortkeys,
jointype,
! extra);
return;
}
/*
! * Check to see if proposed path is still parameterized, and reject if the
! * parameterization wouldn't be sensible.
*/
! required_outer = calc_non_nestloop_required_outer(outer_path,
! inner_path);
! if (required_outer &&
! !bms_overlap(required_outer, extra->param_source_rels))
{
! /* Waste no memory when we reject a path here */
! bms_free(required_outer);
! return;
}
/*
--- 620,644 ----
outersortkeys,
innersortkeys,
jointype,
! extra,
! grouped,
! do_aggregate);
return;
}
/*
! * Check to see if proposed path is still parameterized, and reject if
! * it's grouped or if the parameterization wouldn't be sensible.
*/
! required_outer = calc_non_nestloop_required_outer(outer_path, inner_path);
! if (required_outer)
{
! if (grouped || !bms_overlap(required_outer, extra->param_source_rels))
! {
! /* Waste no memory when we reject a path here */
! bms_free(required_outer);
! return;
! }
}
/*
*************** try_mergejoin_path(PlannerInfo *root,
*** 511,537 ****
*/
initial_cost_mergejoin(root, &workspace, jointype, mergeclauses,
outer_path, inner_path,
! outersortkeys, innersortkeys,
! extra);
! if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer))
{
! add_path(joinrel, (Path *)
! create_mergejoin_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! pathkeys,
! required_outer,
! mergeclauses,
! outersortkeys,
! innersortkeys));
}
else
{
--- 657,704 ----
*/
initial_cost_mergejoin(root, &workspace, jointype, mergeclauses,
outer_path, inner_path,
! outersortkeys, innersortkeys, extra);
! /*
! * Determine which target the join should produce.
! *
! * In the case of explicit aggregation, output of the join itself is
! * plain.
! */
! if (!grouped || do_aggregate)
! join_target = joinrel->reltarget;
! else
! join_target = joinrel->gpi->target;
!
!
! join_path = (Path *) create_mergejoin_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! pathkeys,
! required_outer,
! mergeclauses,
! outersortkeys,
! innersortkeys,
! join_target);
!
! /* Do partial aggregation if needed. */
! if (do_aggregate)
! {
! create_grouped_path(root, joinrel, join_path, true, false,
! AGG_HASHED);
! create_grouped_path(root, joinrel, join_path, true, false,
! AGG_SORTED);
! }
! else if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer, grouped))
{
! add_path(joinrel, (Path *) join_path, grouped);
}
else
{
*************** try_partial_mergejoin_path(PlannerInfo *
*** 555,563 ****
List *outersortkeys,
List *innersortkeys,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinCostWorkspace workspace;
/*
* See comments in try_partial_hashjoin_path().
--- 722,738 ----
List *outersortkeys,
List *innersortkeys,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped,
! bool do_aggregate)
{
JoinCostWorkspace workspace;
+ Path *join_path;
+ PathTarget *join_target;
+
+ /* The same checks we do in try_mergejoin_path. */
+ Assert(!do_aggregate || grouped);
+ Assert(joinrel->gpi != NULL || !grouped);
/*
* See comments in try_partial_hashjoin_path().
*************** try_partial_mergejoin_path(PlannerInfo *
*** 587,613 ****
*/
initial_cost_mergejoin(root, &workspace, jointype, mergeclauses,
outer_path, inner_path,
! outersortkeys, innersortkeys,
! extra);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys))
return;
! /* Might be good enough to be worth trying, so let's try it. */
! add_partial_path(joinrel, (Path *)
! create_mergejoin_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! pathkeys,
! NULL,
! mergeclauses,
! outersortkeys,
! innersortkeys));
}
/*
--- 762,934 ----
*/
initial_cost_mergejoin(root, &workspace, jointype, mergeclauses,
outer_path, inner_path,
! outersortkeys, innersortkeys, extra);
! /*
! * Determine which target the join should produce.
! *
! * In the case of explicit aggregation, output of the join itself is
! * plain.
! */
! if (!grouped || do_aggregate)
! join_target = joinrel->reltarget;
! else
! {
! Assert(joinrel->gpi != NULL);
! join_target = joinrel->gpi->target;
! }
!
! join_path = (Path *) create_mergejoin_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! pathkeys,
! NULL,
! mergeclauses,
! outersortkeys,
! innersortkeys,
! join_target);
!
! if (do_aggregate)
! {
! create_grouped_path(root, joinrel, join_path, true, true, AGG_HASHED);
! create_grouped_path(root, joinrel, join_path, true, true, AGG_SORTED);
! }
! else if (add_partial_path_precheck(joinrel, workspace.total_cost,
! pathkeys, grouped))
! {
! /* Might be good enough to be worth trying, so let's try it. */
! add_partial_path(joinrel, (Path *) join_path, grouped);
! }
! }
!
! static void
! try_grouped_mergejoin_path(PlannerInfo *root,
! RelOptInfo *joinrel,
! Path *outer_path,
! Path *inner_path,
! List *pathkeys,
! List *mergeclauses,
! List *outersortkeys,
! List *innersortkeys,
! JoinType jointype,
! JoinPathExtraData *extra,
! bool partial,
! bool do_aggregate)
! {
! /*
! * Missing GroupedPathInfo indicates that we should not try to create a
! * grouped join.
! */
! if (joinrel->gpi == NULL)
return;
! /*
! * Reject the path if we're supposed to combine grouped and plain relation
! * but the grouped one does not evaluate all the relevant aggregates.
! */
! if (!do_aggregate &&
! !is_grouped_join_target_complete(root, joinrel->gpi->target,
! outer_path, inner_path))
! return;
!
! /*
! * As repeated aggregation doesn't seem to be attractive, make sure that
! * the resulting grouped relation is not parameterized.
! */
! if (outer_path->param_info != NULL || inner_path->param_info != NULL)
! return;
!
! if (!partial)
! try_mergejoin_path(root, joinrel, outer_path, inner_path, pathkeys,
! mergeclauses, outersortkeys, innersortkeys,
! jointype, extra, false, true, do_aggregate);
! else
! try_partial_mergejoin_path(root, joinrel, outer_path, inner_path,
! pathkeys,
! mergeclauses, outersortkeys, innersortkeys,
! jointype, extra, true, do_aggregate);
! }
!
! static void
! try_mergejoin_path_common(PlannerInfo *root,
! RelOptInfo *joinrel,
! Path *outer_path,
! Path *inner_path,
! List *pathkeys,
! List *mergeclauses,
! List *outersortkeys,
! List *innersortkeys,
! JoinType jointype,
! JoinPathExtraData *extra,
! bool partial,
! bool grouped_outer,
! bool grouped_inner,
! bool do_aggregate)
! {
! bool grouped_join;
!
! grouped_join = grouped_outer || grouped_inner || do_aggregate;
!
! /* Join of two grouped paths is not supported. */
! Assert(!(grouped_outer && grouped_inner));
!
! if (!grouped_join)
! {
! /* Only join plain paths. */
! try_mergejoin_path(root,
! joinrel,
! outer_path,
! inner_path,
! pathkeys,
! mergeclauses,
! outersortkeys,
! innersortkeys,
! jointype,
! extra,
! partial,
! false, false);
! }
! else if (grouped_outer || grouped_inner)
! {
! Assert(!do_aggregate);
!
! /*
! * Exactly one of the input paths is grouped, so create a grouped join
! * path.
! */
! try_grouped_mergejoin_path(root,
! joinrel,
! outer_path,
! inner_path,
! pathkeys,
! mergeclauses,
! outersortkeys,
! innersortkeys,
! jointype,
! extra,
! partial,
! false);
! }
! /* Preform explicit aggregation only if suitable target exists. */
! else if (joinrel->gpi != NULL)
! {
! try_grouped_mergejoin_path(root,
! joinrel,
! outer_path,
! inner_path,
! pathkeys,
! mergeclauses,
! outersortkeys,
! innersortkeys,
! jointype,
! extra,
! partial, true);
! }
}
/*
*************** try_hashjoin_path(PlannerInfo *root,
*** 622,668 ****
Path *inner_path,
List *hashclauses,
JoinType jointype,
! JoinPathExtraData *extra)
{
Relids required_outer;
JoinCostWorkspace workspace;
/*
! * Check to see if proposed path is still parameterized, and reject if the
! * parameterization wouldn't be sensible.
*/
! required_outer = calc_non_nestloop_required_outer(outer_path,
! inner_path);
! if (required_outer &&
! !bms_overlap(required_outer, extra->param_source_rels))
{
! /* Waste no memory when we reject a path here */
! bms_free(required_outer);
! return;
}
/*
* See comments in try_nestloop_path(). Also note that hashjoin paths
* never have any output pathkeys, per comments in create_hashjoin_path.
*/
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path, extra);
! if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! NIL, required_outer))
{
! add_path(joinrel, (Path *)
! create_hashjoin_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! required_outer,
! hashclauses));
}
else
{
--- 943,1017 ----
Path *inner_path,
List *hashclauses,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped,
! bool do_aggregate)
{
Relids required_outer;
JoinCostWorkspace workspace;
+ Path *join_path;
+ PathTarget *join_target;
+
+ /* Caller should not request aggregation w/o grouped output. */
+ Assert(!do_aggregate || grouped);
+
+ /* GroupedPathInfo is necessary for us to produce a grouped set. */
+ Assert(joinrel->gpi != NULL || !grouped);
/*
! * Check to see if proposed path is still parameterized, and reject if
! * it's grouped or if the parameterization wouldn't be sensible.
*/
! required_outer = calc_non_nestloop_required_outer(outer_path, inner_path);
! if (required_outer)
{
! if (grouped || !bms_overlap(required_outer, extra->param_source_rels))
! {
! /* Waste no memory when we reject a path here */
! bms_free(required_outer);
! return;
! }
}
/*
* See comments in try_nestloop_path(). Also note that hashjoin paths
* never have any output pathkeys, per comments in create_hashjoin_path.
+ *
+ * TODO Need to consider aggregation here?
*/
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path, extra);
! /*
! * Determine which target the join should produce.
! *
! * In the case of explicit aggregation, output of the join itself is
! * plain.
! */
! if (!grouped || do_aggregate)
! join_target = joinrel->reltarget;
! else
! join_target = joinrel->gpi->target;
!
! join_path = (Path *) create_hashjoin_path(root, joinrel, jointype,
! &workspace,
! extra,
! outer_path, inner_path,
! extra->restrictlist,
! required_outer, hashclauses,
! join_target);
!
! /* Do partial aggregation if needed. */
! if (do_aggregate)
! {
! create_grouped_path(root, joinrel, join_path, true, false,
! AGG_HASHED);
! }
! else if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! NIL, required_outer, grouped))
{
! add_path(joinrel, (Path *) join_path, grouped);
}
else
{
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 683,691 ****
Path *inner_path,
List *hashclauses,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinCostWorkspace workspace;
/*
* If the inner path is parameterized, the parameterization must be fully
--- 1032,1048 ----
Path *inner_path,
List *hashclauses,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped,
! bool do_aggregate)
{
JoinCostWorkspace workspace;
+ Path *join_path;
+ PathTarget *join_target;
+
+ /* The same checks we do in try_hashjoin_path. */
+ Assert(!do_aggregate || grouped);
+ Assert(joinrel->gpi != NULL || !grouped);
/*
* If the inner path is parameterized, the parameterization must be fully
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 708,728 ****
*/
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path, extra);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, NIL))
return;
! /* Might be good enough to be worth trying, so let's try it. */
! add_partial_path(joinrel, (Path *)
! create_hashjoin_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! NULL,
! hashclauses));
}
/*
--- 1065,1160 ----
*/
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path, extra);
!
! /*
! * Determine which target the join should produce.
! *
! * In the case of explicit aggregation, output of the join itself is
! * plain.
! */
! if (!grouped || do_aggregate)
! join_target = joinrel->reltarget;
! else
! {
! Assert(joinrel->gpi != NULL);
! join_target = joinrel->gpi->target;
! }
!
! join_path = (Path *) create_hashjoin_path(root, joinrel, jointype,
! &workspace,
! extra,
! outer_path, inner_path,
! extra->restrictlist, NULL,
! hashclauses, join_target);
!
! /* Do partial aggregation if needed. */
! if (do_aggregate)
! {
! create_grouped_path(root, joinrel, join_path, true, true, AGG_HASHED);
! }
! else if (add_partial_path_precheck(joinrel, workspace.total_cost,
! NIL, grouped))
! {
! add_partial_path(joinrel, (Path *) join_path , grouped);
! }
! }
!
! /*
! * Create a new grouped hash join path by joining a grouped path to plain
! * (non-grouped) one, or by joining 2 plain relations and applying grouping on
! * the result.
! *
! * Joining of 2 grouped paths is not supported. If a grouped relation A was
! * joined to grouped relation B, then the grouping of B reduces the number of
! * times each group of A is appears in the join output. This makes difference
! * for some aggregates, e.g. sum().
! *
! * If do_aggregate is true, neither input rel is grouped so we need to
! * aggregate the join result explicitly.
! *
! * partial argument tells whether the join path should be considered partial.
! */
! static void
! try_grouped_hashjoin_path(PlannerInfo *root,
! RelOptInfo *joinrel,
! Path *outer_path,
! Path *inner_path,
! List *hashclauses,
! JoinType jointype,
! JoinPathExtraData *extra,
! bool do_aggregate,
! bool partial)
! {
! /*
! * Missing GroupedPathInfo indicates that we should not try to create a
! * grouped join.
! */
! if (joinrel->gpi == NULL)
return;
! /*
! * Reject the path if we're supposed to combine grouped and plain relation
! * but the grouped one does not evaluate all the relevant aggregates.
! */
! if (!do_aggregate &&
! !is_grouped_join_target_complete(root, joinrel->gpi->target,
! outer_path, inner_path))
! return;
!
! /*
! * As repeated aggregation doesn't seem to be attractive, make sure that
! * the resulting grouped relation is not parameterized.
! */
! if (outer_path->param_info != NULL || inner_path->param_info != NULL)
! return;
!
! if (!partial)
! try_hashjoin_path(root, joinrel, outer_path, inner_path, hashclauses,
! jointype, extra, true, do_aggregate);
! else
! try_partial_hashjoin_path(root, joinrel, outer_path, inner_path,
! hashclauses, jointype, extra, true,
! do_aggregate);
}
/*
*************** sort_inner_and_outer(PlannerInfo *root,
*** 773,779 ****
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinType save_jointype = jointype;
Path *outer_path;
--- 1205,1244 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped)
! {
! if (!grouped)
! {
! sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, false, false, false);
! }
! else
! {
! /* Use all the supported strategies to generate grouped join. */
! sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, true, false, false);
! sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, false, true, false);
! sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, false, false, true);
! }
! }
!
! /*
! * TODO As merge_pathkeys shouldn't differ across execution, use a separate
! * function to derive them and pass them here in a list.
! */
! static void
! sort_inner_and_outer_common(PlannerInfo *root,
! RelOptInfo *joinrel,
! RelOptInfo *outerrel,
! RelOptInfo *innerrel,
! JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped_outer,
! bool grouped_inner,
! bool do_aggregate)
{
JoinType save_jointype = jointype;
Path *outer_path;
*************** sort_inner_and_outer(PlannerInfo *root,
*** 782,787 ****
--- 1247,1253 ----
Path *cheapest_safe_inner = NULL;
List *all_pathkeys;
ListCell *l;
+ bool grouped_result;
/*
* We only consider the cheapest-total-cost input paths, since we are
*************** sort_inner_and_outer(PlannerInfo *root,
*** 796,803 ****
* against mergejoins with parameterized inputs; see comments in
* src/backend/optimizer/README.
*/
! outer_path = outerrel->cheapest_total_path;
! inner_path = innerrel->cheapest_total_path;
/*
* If either cheapest-total path is parameterized by the other rel, we
--- 1262,1288 ----
* against mergejoins with parameterized inputs; see comments in
* src/backend/optimizer/README.
*/
! if (grouped_outer)
! {
! if (outerrel->gpi != NULL && outerrel->gpi->pathlist != NIL)
! outer_path = linitial(outerrel->gpi->pathlist);
! else
! return;
! }
! else
! outer_path = outerrel->cheapest_total_path;
!
! if (grouped_inner)
! {
! if (innerrel->gpi != NULL && innerrel->gpi->pathlist != NIL)
! inner_path = linitial(innerrel->gpi->pathlist);
! else
! return;
! }
! else
! inner_path = innerrel->cheapest_total_path;
!
! grouped_result = grouped_outer || grouped_inner || do_aggregate;
/*
* If either cheapest-total path is parameterized by the other rel, we
*************** sort_inner_and_outer(PlannerInfo *root,
*** 843,855 ****
outerrel->partial_pathlist != NIL &&
bms_is_empty(joinrel->lateral_relids))
{
! cheapest_partial_outer = (Path *) linitial(outerrel->partial_pathlist);
if (inner_path->parallel_safe)
cheapest_safe_inner = inner_path;
else if (save_jointype != JOIN_UNIQUE_INNER)
cheapest_safe_inner =
! get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
}
/*
--- 1328,1377 ----
outerrel->partial_pathlist != NIL &&
bms_is_empty(joinrel->lateral_relids))
{
! if (grouped_outer)
! {
! if (outerrel->gpi != NULL && outerrel->gpi->partial_pathlist != NIL)
! cheapest_partial_outer = (Path *)
! linitial(outerrel->gpi->partial_pathlist);
! else
! return;
! }
! else
! cheapest_partial_outer = (Path *)
! linitial(outerrel->partial_pathlist);
!
! if (grouped_inner)
! {
! if (innerrel->gpi != NULL && innerrel->gpi->pathlist != NIL)
! inner_path = linitial(innerrel->gpi->pathlist);
! else
! return;
! }
! else
! inner_path = innerrel->cheapest_total_path;
if (inner_path->parallel_safe)
cheapest_safe_inner = inner_path;
else if (save_jointype != JOIN_UNIQUE_INNER)
+ {
+ List *inner_pathlist;
+
+ if (!grouped_inner)
+ inner_pathlist = innerrel->pathlist;
+ else
+ {
+ Assert(innerrel->gpi != NULL);
+ inner_pathlist = innerrel->gpi->pathlist;
+ }
+
+ /*
+ * All the grouped paths should be unparameterized, so the
+ * function is overly stringent in the grouped_inner case, but
+ * still useful.
+ */
cheapest_safe_inner =
! get_cheapest_parallel_safe_total_inner(inner_pathlist);
! }
}
/*
*************** sort_inner_and_outer(PlannerInfo *root,
*** 925,957 ****
* properly. try_mergejoin_path will detect that case and suppress an
* explicit sort step, so we needn't do so here.
*/
! try_mergejoin_path(root,
! joinrel,
! outer_path,
! inner_path,
! merge_pathkeys,
! cur_mergeclauses,
! outerkeys,
! innerkeys,
! jointype,
! extra,
! false);
/*
* If we have partial outer and parallel safe inner path then try
* partial mergejoin path.
*/
if (cheapest_partial_outer && cheapest_safe_inner)
! try_partial_mergejoin_path(root,
! joinrel,
! cheapest_partial_outer,
! cheapest_safe_inner,
! merge_pathkeys,
! cur_mergeclauses,
! outerkeys,
! innerkeys,
! jointype,
! extra);
}
}
--- 1447,1505 ----
* properly. try_mergejoin_path will detect that case and suppress an
* explicit sort step, so we needn't do so here.
*/
! if (!grouped_result)
! try_mergejoin_path(root,
! joinrel,
! outer_path,
! inner_path,
! merge_pathkeys,
! cur_mergeclauses,
! outerkeys,
! innerkeys,
! jointype,
! extra,
! false, false, false);
! else
! {
! try_mergejoin_path_common(root, joinrel, outer_path, inner_path,
! merge_pathkeys, cur_mergeclauses,
! outerkeys, innerkeys, jointype, extra,
! false,
! grouped_outer, grouped_inner,
! do_aggregate);
! }
/*
* If we have partial outer and parallel safe inner path then try
* partial mergejoin path.
*/
if (cheapest_partial_outer && cheapest_safe_inner)
! {
! if (!grouped_result)
! {
! try_partial_mergejoin_path(root,
! joinrel,
! cheapest_partial_outer,
! cheapest_safe_inner,
! merge_pathkeys,
! cur_mergeclauses,
! outerkeys,
! innerkeys,
! jointype,
! extra, false, false);
! }
! else
! {
! try_mergejoin_path_common(root, joinrel,
! cheapest_partial_outer,
! cheapest_safe_inner,
! merge_pathkeys, cur_mergeclauses,
! outerkeys, innerkeys, jointype, extra,
! true,
! grouped_outer, grouped_inner,
! do_aggregate);
! }
! }
}
}
*************** sort_inner_and_outer(PlannerInfo *root,
*** 968,973 ****
--- 1516,1529 ----
* some sort key requirements). So, we consider truncations of the
* mergeclause list as well as the full list. (Ideally we'd consider all
* subsets of the mergeclause list, but that seems way too expensive.)
+ *
+ * grouped_outer - is outerpath grouped?
+ * grouped_inner - use grouped paths of innerrel?
+ * do_aggregate - apply (partial) aggregation to the output?
+ *
+ * TODO If subsequent calls often differ only by the 3 arguments above,
+ * consider a workspace structure to share useful info (eg merge clauses)
+ * across calls.
*/
static void
generate_mergejoin_paths(PlannerInfo *root,
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 979,985 ****
bool useallclauses,
Path *inner_cheapest_total,
List *merge_pathkeys,
! bool is_partial)
{
List *mergeclauses;
List *innersortkeys;
--- 1535,1544 ----
bool useallclauses,
Path *inner_cheapest_total,
List *merge_pathkeys,
! bool is_partial,
! bool grouped_outer,
! bool grouped_inner,
! bool do_aggregate)
{
List *mergeclauses;
List *innersortkeys;
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1030,1046 ****
* try_mergejoin_path will do the right thing if inner_cheapest_total is
* already correctly sorted.)
*/
! try_mergejoin_path(root,
! joinrel,
! outerpath,
! inner_cheapest_total,
! merge_pathkeys,
! mergeclauses,
! NIL,
! innersortkeys,
! jointype,
! extra,
! is_partial);
/* Can't do anything else if inner path needs to be unique'd */
if (save_jointype == JOIN_UNIQUE_INNER)
--- 1589,1606 ----
* try_mergejoin_path will do the right thing if inner_cheapest_total is
* already correctly sorted.)
*/
! try_mergejoin_path_common(root,
! joinrel,
! outerpath,
! inner_cheapest_total,
! merge_pathkeys,
! mergeclauses,
! NIL,
! innersortkeys,
! jointype,
! extra,
! is_partial,
! grouped_outer, grouped_inner, do_aggregate);
/* Can't do anything else if inner path needs to be unique'd */
if (save_jointype == JOIN_UNIQUE_INNER)
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1096,1111 ****
for (sortkeycnt = num_sortkeys; sortkeycnt > 0; sortkeycnt--)
{
Path *innerpath;
List *newclauses = NIL;
/*
* Look for an inner path ordered well enough for the first
* 'sortkeycnt' innersortkeys. NB: trialsortkeys list is modified
* destructively, which is why we made a copy...
*/
trialsortkeys = list_truncate(trialsortkeys, sortkeycnt);
! innerpath = get_cheapest_path_for_pathkeys(innerrel->pathlist,
trialsortkeys,
NULL,
TOTAL_COST,
--- 1656,1677 ----
for (sortkeycnt = num_sortkeys; sortkeycnt > 0; sortkeycnt--)
{
+ List *inner_pathlist = NIL;
Path *innerpath;
List *newclauses = NIL;
+ if (!grouped_inner)
+ inner_pathlist = innerrel->pathlist;
+ else if (innerrel->gpi != NULL)
+ inner_pathlist = innerrel->gpi->pathlist;
+
/*
* Look for an inner path ordered well enough for the first
* 'sortkeycnt' innersortkeys. NB: trialsortkeys list is modified
* destructively, which is why we made a copy...
*/
trialsortkeys = list_truncate(trialsortkeys, sortkeycnt);
! innerpath = get_cheapest_path_for_pathkeys(inner_pathlist,
trialsortkeys,
NULL,
TOTAL_COST,
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1128,1148 ****
}
else
newclauses = mergeclauses;
! try_mergejoin_path(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! newclauses,
! NIL,
! NIL,
! jointype,
! extra,
! is_partial);
cheapest_total_inner = innerpath;
}
/* Same on the basis of cheapest startup cost ... */
! innerpath = get_cheapest_path_for_pathkeys(innerrel->pathlist,
trialsortkeys,
NULL,
STARTUP_COST,
--- 1694,1718 ----
}
else
newclauses = mergeclauses;
!
! try_mergejoin_path_common(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! newclauses,
! NIL,
! NIL,
! jointype,
! extra,
! is_partial,
! grouped_outer, grouped_inner,
! do_aggregate);
!
cheapest_total_inner = innerpath;
}
/* Same on the basis of cheapest startup cost ... */
! innerpath = get_cheapest_path_for_pathkeys(inner_pathlist,
trialsortkeys,
NULL,
STARTUP_COST,
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1173,1189 ****
else
newclauses = mergeclauses;
}
! try_mergejoin_path(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! newclauses,
! NIL,
! NIL,
! jointype,
! extra,
! is_partial);
}
cheapest_startup_inner = innerpath;
}
--- 1743,1761 ----
else
newclauses = mergeclauses;
}
! try_mergejoin_path_common(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! newclauses,
! NIL,
! NIL,
! jointype,
! extra,
! is_partial,
! grouped_outer, grouped_inner,
! do_aggregate);
}
cheapest_startup_inner = innerpath;
}
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1218,1223 ****
--- 1790,1797 ----
* 'innerrel' is the inner join relation
* 'jointype' is the type of join to do
* 'extra' contains additional input values
+ * 'grouped' indicates that the at least one relation in the join has been
+ * aggregated.
*/
static void
match_unsorted_outer(PlannerInfo *root,
*************** match_unsorted_outer(PlannerInfo *root,
*** 1225,1231 ****
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinType save_jointype = jointype;
bool nestjoinOK;
--- 1799,1806 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped)
{
JoinType save_jointype = jointype;
bool nestjoinOK;
*************** match_unsorted_outer(PlannerInfo *root,
*** 1235,1240 ****
--- 1810,1837 ----
ListCell *lc1;
/*
+ * If grouped join path is requested, we ignore cases where either input
+ * path needs to be unique. For each side we should expect either grouped
+ * or plain relation, which differ quite a bit.
+ *
+ * XXX Although unique-ification of grouped path might result in too
+ * expensive input path (note that grouped input relation is not
+ * necessarily unique, regardless the grouping keys --- one or more plain
+ * relation could already have been joined to it), we might want to
+ * unique-ify the input relation in the future at least in the case it's a
+ * plain relation.
+ *
+ * (Materialization is not involved in grouped paths for similar reasons.)
+ */
+ if (grouped &&
+ (jointype == JOIN_UNIQUE_OUTER || jointype == JOIN_UNIQUE_INNER))
+ return;
+
+ /* No grouped join w/o grouped target. */
+ if (grouped && joinrel->gpi == NULL)
+ return;
+
+ /*
* Nestloop only supports inner, left, semi, and anti joins. Also, if we
* are doing a right or full mergejoin, we must use *all* the mergeclauses
* as join clauses, else we will not have a valid plan. (Although these
*************** match_unsorted_outer(PlannerInfo *root,
*** 1290,1296 ****
create_unique_path(root, innerrel, inner_cheapest_total, extra->sjinfo);
Assert(inner_cheapest_total);
}
! else if (nestjoinOK)
{
/*
* Consider materializing the cheapest inner path, unless
--- 1887,1893 ----
create_unique_path(root, innerrel, inner_cheapest_total, extra->sjinfo);
Assert(inner_cheapest_total);
}
! else if (nestjoinOK && !grouped)
{
/*
* Consider materializing the cheapest inner path, unless
*************** match_unsorted_outer(PlannerInfo *root,
*** 1321,1326 ****
--- 1918,1925 ----
*/
if (save_jointype == JOIN_UNIQUE_OUTER)
{
+ Assert(!grouped);
+
if (outerpath != outerrel->cheapest_total_path)
continue;
outerpath = (Path *) create_unique_path(root, outerrel,
*************** match_unsorted_outer(PlannerInfo *root,
*** 1348,1354 ****
inner_cheapest_total,
merge_pathkeys,
jointype,
! extra);
}
else if (nestjoinOK)
{
--- 1947,1954 ----
inner_cheapest_total,
merge_pathkeys,
jointype,
! extra,
! false, false);
}
else if (nestjoinOK)
{
*************** match_unsorted_outer(PlannerInfo *root,
*** 1364,1387 ****
{
Path *innerpath = (Path *) lfirst(lc2);
! try_nestloop_path(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! jointype,
! extra);
}
! /* Also consider materialized form of the cheapest inner path */
! if (matpath != NULL)
try_nestloop_path(root,
joinrel,
outerpath,
matpath,
merge_pathkeys,
jointype,
! extra);
}
/* Can't do anything else if outer path needs to be unique'd */
--- 1964,2009 ----
{
Path *innerpath = (Path *) lfirst(lc2);
! if (!grouped)
! try_nestloop_path(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! jointype,
! extra, false, false);
! else
! {
! /*
! * Since both input paths are plain, request explicit
! * aggregation.
! */
! try_grouped_nestloop_path(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! jointype,
! extra,
! true,
! false);
! }
}
! /*
! * Also consider materialized form of the cheapest inner path.
! *
! * (There's no matpath for grouped join.)
! */
! if (matpath != NULL && !grouped)
try_nestloop_path(root,
joinrel,
outerpath,
matpath,
merge_pathkeys,
jointype,
! extra,
! false, false);
}
/* Can't do anything else if outer path needs to be unique'd */
*************** match_unsorted_outer(PlannerInfo *root,
*** 1396,1402 ****
generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
save_jointype, extra, useallclauses,
inner_cheapest_total, merge_pathkeys,
! false);
}
/*
--- 2018,2094 ----
generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
save_jointype, extra, useallclauses,
inner_cheapest_total, merge_pathkeys,
! false, false, false, grouped);
!
! /* Try to join the plain outer relation to grouped inner. */
! if (grouped && nestjoinOK &&
! save_jointype != JOIN_UNIQUE_OUTER &&
! save_jointype != JOIN_UNIQUE_INNER &&
! innerrel->gpi != NULL && outerrel->gpi == NULL)
! {
! Path *inner_cheapest_grouped = (Path *) linitial(innerrel->gpi->pathlist);
!
! if (PATH_PARAM_BY_REL(inner_cheapest_grouped, outerrel))
! continue;
!
! /* XXX Shouldn't Assert() be used here instead? */
! if (PATH_PARAM_BY_REL(outerpath, innerrel))
! continue;
!
! /*
! * Only outer grouped path is interesting in this case: grouped
! * path on the inner side of NL join would imply repeated
! * aggregation somewhere in the inner path.
! */
! generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
! save_jointype, extra, useallclauses,
! inner_cheapest_grouped, merge_pathkeys,
! false, false, true, false);
! }
! }
!
! /*
! * Combine grouped outer and plain inner paths.
! */
! if (grouped && nestjoinOK &&
! save_jointype != JOIN_UNIQUE_OUTER &&
! save_jointype != JOIN_UNIQUE_INNER)
! {
! /*
! * If the inner rel had a grouped target, its plain paths should be
! * ignored. Otherwise we could create grouped paths with different
! * targets.
! */
! if (outerrel->gpi != NULL && innerrel->gpi == NULL &&
! inner_cheapest_total != NULL)
! {
! /* Nested loop paths. */
! foreach(lc1, outerrel->gpi->pathlist)
! {
! Path *outerpath = (Path *) lfirst(lc1);
! List *merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
! outerpath->pathkeys);
!
! if (PATH_PARAM_BY_REL(outerpath, innerrel))
! continue;
!
! try_grouped_nestloop_path(root,
! joinrel,
! outerpath,
! inner_cheapest_total,
! merge_pathkeys,
! jointype,
! extra,
! false,
! false);
!
! /* Merge join paths. */
! generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
! save_jointype, extra, useallclauses,
! inner_cheapest_total, merge_pathkeys,
! false, true, false, false);
! }
! }
}
/*
*************** match_unsorted_outer(PlannerInfo *root,
*** 1416,1423 ****
bms_is_empty(joinrel->lateral_relids))
{
if (nestjoinOK)
! consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! save_jointype, extra);
/*
* If inner_cheapest_total is NULL or non parallel-safe then find the
--- 2108,2128 ----
bms_is_empty(joinrel->lateral_relids))
{
if (nestjoinOK)
! {
! if (!grouped)
! /* Plain partial paths. */
! consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! save_jointype, extra, false, false);
! else
! {
! /* Grouped partial paths with explicit aggregation. */
! consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! save_jointype, extra, true, true);
! /* Grouped partial paths w/o explicit aggregation. */
! consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! save_jointype, extra, true, false);
! }
! }
/*
* If inner_cheapest_total is NULL or non parallel-safe then find the
*************** match_unsorted_outer(PlannerInfo *root,
*** 1437,1443 ****
if (inner_cheapest_total)
consider_parallel_mergejoin(root, joinrel, outerrel, innerrel,
save_jointype, extra,
! inner_cheapest_total);
}
}
--- 2142,2148 ----
if (inner_cheapest_total)
consider_parallel_mergejoin(root, joinrel, outerrel, innerrel,
save_jointype, extra,
! inner_cheapest_total, grouped);
}
}
*************** consider_parallel_mergejoin(PlannerInfo
*** 1460,1469 ****
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra,
! Path *inner_cheapest_total)
{
ListCell *lc1;
/* generate merge join path for each partial outer path */
foreach(lc1, outerrel->partial_pathlist)
{
--- 2165,2183 ----
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra,
! Path *inner_cheapest_total,
! bool grouped)
{
ListCell *lc1;
+ if (grouped)
+ {
+ /* TODO Consider if these types should be supported. */
+ if (jointype == JOIN_UNIQUE_OUTER ||
+ jointype == JOIN_UNIQUE_INNER)
+ return;
+ }
+
/* generate merge join path for each partial outer path */
foreach(lc1, outerrel->partial_pathlist)
{
*************** consider_parallel_mergejoin(PlannerInfo
*** 1476,1484 ****
merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
outerpath->pathkeys);
! generate_mergejoin_paths(root, joinrel, innerrel, outerpath, jointype,
! extra, false, inner_cheapest_total,
! merge_pathkeys, true);
}
}
--- 2190,2245 ----
merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
outerpath->pathkeys);
! if (!grouped)
! generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
! jointype, extra, false,
! inner_cheapest_total, merge_pathkeys,
! true,
! false, false, false);
! else
! {
! /*
! * Create grouped join by joining plain rels and aggregating the
! * result.
! */
! Assert(joinrel->gpi != NULL);
! generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
! jointype, extra, false,
! inner_cheapest_total, merge_pathkeys,
! true, false, false, true);
!
! /* Combine the plain outer with grouped inner one(s). */
! if (outerrel->gpi == NULL && innerrel->gpi != NULL)
! {
! Path *inner_cheapest_grouped = (Path *)
! linitial(innerrel->gpi->pathlist);
!
! if (inner_cheapest_grouped != NULL &&
! inner_cheapest_grouped->parallel_safe)
! generate_mergejoin_paths(root, joinrel, innerrel,
! outerpath, jointype, extra,
! false, inner_cheapest_grouped,
! merge_pathkeys,
! true, false, true, false);
! }
! }
! }
!
! /* In addition, try to join grouped outer to plain inner one(s). */
! if (grouped && outerrel->gpi != NULL && innerrel->gpi == NULL)
! {
! foreach(lc1, outerrel->gpi->partial_pathlist)
! {
! Path *outerpath = (Path *) lfirst(lc1);
! List *merge_pathkeys;
!
! merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
! outerpath->pathkeys);
! generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
! jointype, extra, false,
! inner_cheapest_total, merge_pathkeys,
! true, true, false, false);
! }
}
}
*************** consider_parallel_nestloop(PlannerInfo *
*** 1499,1513 ****
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;
--- 2260,2304 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped, bool do_aggregate)
{
JoinType save_jointype = jointype;
+ List *outer_pathlist;
ListCell *lc1;
+ if (grouped)
+ {
+ /* TODO Consider if these types should be supported. */
+ if (save_jointype == JOIN_UNIQUE_OUTER ||
+ save_jointype == JOIN_UNIQUE_INNER)
+ return;
+ }
+
if (jointype == JOIN_UNIQUE_INNER)
jointype = JOIN_INNER;
! if (!grouped || do_aggregate)
! {
! /*
! * If creating grouped paths by explicit aggregation, the input paths
! * must be plain.
! */
! outer_pathlist = outerrel->partial_pathlist;
! }
! else if (outerrel->gpi != NULL)
! {
! /*
! * Only the outer paths are accepted as grouped when we try to combine
! * grouped and plain ones. Grouped inner path implies repeated
! * aggregation, which doesn't sound as a good idea.
! */
! outer_pathlist = outerrel->gpi->partial_pathlist;
! }
! else
! return;
!
! foreach(lc1, outer_pathlist)
{
Path *outerpath = (Path *) lfirst(lc1);
List *pathkeys;
*************** consider_parallel_nestloop(PlannerInfo *
*** 1538,1544 ****
* inner paths, but right now create_unique_path is not on board
* with that.)
*/
! if (save_jointype == JOIN_UNIQUE_INNER)
{
if (innerpath != innerrel->cheapest_total_path)
continue;
--- 2329,2335 ----
* inner paths, but right now create_unique_path is not on board
* with that.)
*/
! if (save_jointype == JOIN_UNIQUE_INNER && !grouped)
{
if (innerpath != innerrel->cheapest_total_path)
continue;
*************** consider_parallel_nestloop(PlannerInfo *
*** 1548,1555 ****
Assert(innerpath);
}
! try_partial_nestloop_path(root, joinrel, outerpath, innerpath,
! pathkeys, jointype, extra);
}
}
}
--- 2339,2364 ----
Assert(innerpath);
}
! if (!grouped)
! try_partial_nestloop_path(root, joinrel, outerpath, innerpath,
! pathkeys, jointype, extra,
! false, false);
! else if (do_aggregate)
! {
! /* Request aggregation as both input rels are plain. */
! try_grouped_nestloop_path(root, joinrel, outerpath, innerpath,
! pathkeys, jointype, extra,
! true, true);
! }
! /*
! * Only combine the grouped outer path with the plain inner if the
! * inner relation cannot produce grouped paths. Otherwise we could
! * generate grouped paths with different targets.
! */
! else if (innerrel->gpi == NULL)
! try_grouped_nestloop_path(root, joinrel, outerpath, innerpath,
! pathkeys, jointype, extra,
! false, true);
}
}
}
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1571,1583 ****
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinType save_jointype = jointype;
bool isouterjoin = IS_OUTER_JOIN(jointype);
List *hashclauses;
ListCell *l;
/*
* We need to build only one hashclauses list for any given pair of outer
* and inner relations; all of the hashable clauses will be used as keys.
--- 2380,2397 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped)
{
JoinType save_jointype = jointype;
bool isouterjoin = IS_OUTER_JOIN(jointype);
List *hashclauses;
ListCell *l;
+ /* No grouped join w/o grouped target. */
+ if (grouped && joinrel->gpi == NULL)
+ return;
+
/*
* We need to build only one hashclauses list for any given pair of outer
* and inner relations; all of the hashable clauses will be used as keys.
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1627,1632 ****
--- 2441,2449 ----
* 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))
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1646,1652 ****
cheapest_total_inner,
hashclauses,
jointype,
! extra);
/* no possibility of cheap startup here */
}
else if (jointype == JOIN_UNIQUE_INNER)
--- 2463,2470 ----
cheapest_total_inner,
hashclauses,
jointype,
! extra,
! false, false);
/* no possibility of cheap startup here */
}
else if (jointype == JOIN_UNIQUE_INNER)
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1662,1668 ****
cheapest_total_inner,
hashclauses,
jointype,
! extra);
if (cheapest_startup_outer != NULL &&
cheapest_startup_outer != cheapest_total_outer)
try_hashjoin_path(root,
--- 2480,2487 ----
cheapest_total_inner,
hashclauses,
jointype,
! extra,
! false, false);
if (cheapest_startup_outer != NULL &&
cheapest_startup_outer != cheapest_total_outer)
try_hashjoin_path(root,
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1671,1733 ****
cheapest_total_inner,
hashclauses,
jointype,
! extra);
}
else
{
! /*
! * For other jointypes, we consider the cheapest startup outer
! * together with the cheapest total inner, and then consider
! * pairings of cheapest-total paths including parameterized ones.
! * There is no use in generating parameterized paths on the basis
! * of possibly cheap startup cost, so this is sufficient.
! */
! ListCell *lc1;
! ListCell *lc2;
!
! if (cheapest_startup_outer != NULL)
! try_hashjoin_path(root,
! joinrel,
! cheapest_startup_outer,
! cheapest_total_inner,
! hashclauses,
! jointype,
! extra);
!
! foreach(lc1, outerrel->cheapest_parameterized_paths)
{
- Path *outerpath = (Path *) lfirst(lc1);
-
/*
! * We cannot use an outer path that is parameterized by the
! * inner rel.
*/
! if (PATH_PARAM_BY_REL(outerpath, innerrel))
! continue;
! foreach(lc2, innerrel->cheapest_parameterized_paths)
{
! Path *innerpath = (Path *) lfirst(lc2);
/*
! * We cannot use an inner path that is parameterized by
! * the outer rel, either.
*/
! if (PATH_PARAM_BY_REL(innerpath, outerrel))
continue;
! if (outerpath == cheapest_startup_outer &&
! innerpath == cheapest_total_inner)
! continue; /* already tried it */
! try_hashjoin_path(root,
! joinrel,
! outerpath,
! innerpath,
! hashclauses,
! jointype,
! extra);
}
}
}
--- 2490,2643 ----
cheapest_total_inner,
hashclauses,
jointype,
! extra,
! false, false);
}
else
{
! if (!grouped)
{
/*
! * For other jointypes, we consider the cheapest startup outer
! * together with the cheapest total inner, and then consider
! * pairings of cheapest-total paths including parameterized
! * ones. There is no use in generating parameterized paths on
! * the basis of possibly cheap startup cost, so this is
! * sufficient.
*/
! ListCell *lc1;
! if (cheapest_startup_outer != NULL)
! try_hashjoin_path(root,
! joinrel,
! cheapest_startup_outer,
! cheapest_total_inner,
! hashclauses,
! jointype,
! extra,
! false, false);
!
! foreach(lc1, outerrel->cheapest_parameterized_paths)
{
! Path *outerpath = (Path *) lfirst(lc1);
! ListCell *lc2;
/*
! * We cannot use an outer path that is parameterized by the
! * inner rel.
*/
! if (PATH_PARAM_BY_REL(outerpath, innerrel))
continue;
! foreach(lc2, innerrel->cheapest_parameterized_paths)
! {
! Path *innerpath = (Path *) lfirst(lc2);
! /*
! * We cannot use an inner path that is parameterized by
! * the outer rel, either.
! */
! if (PATH_PARAM_BY_REL(innerpath, outerrel))
! continue;
!
! if (outerpath == cheapest_startup_outer &&
! innerpath == cheapest_total_inner)
! continue; /* already tried it */
!
! try_hashjoin_path(root,
! joinrel,
! outerpath,
! innerpath,
! hashclauses,
! jointype,
! extra,
! false, false);
! }
! }
! }
! else
! {
! /* Create grouped paths if possible. */
! /*
! * TODO
! *
! * Consider processing JOIN_UNIQUE_INNER and JOIN_UNIQUE_OUTER
! * join types, ie perform grouping of the inner / outer rel if
! * it's not unique yet and if the grouping is legal.
! */
! if (jointype == JOIN_UNIQUE_OUTER ||
! jointype == JOIN_UNIQUE_INNER)
! return;
!
! /*
! * Join grouped relation to non-grouped one.
! *
! * Do not use plain path of the input rel whose target does
! * have GroupedPahtInfo. For example (assuming that join of
! * two grouped rels is not supported), the only way to
! * evaluate SELECT sum(a.x), sum(b.y) ... is to join "a" and
! * "b" and aggregate the result. Otherwise the path target
! * wouldn't match joinrel->gpi->target. TODO Move this comment
! * elsewhere as it seems common to all join kinds.
! */
! /*
! * TODO Allow outer join if the grouped rel is on the
! * non-nullable side.
! */
! if (jointype == JOIN_INNER)
! {
! Path *grouped_path, *plain_path;
!
! if (outerrel->gpi != NULL &&
! outerrel->gpi->pathlist != NIL &&
! innerrel->gpi == NULL)
! {
! grouped_path = (Path *)
! linitial(outerrel->gpi->pathlist);
! plain_path = cheapest_total_inner;
! try_grouped_hashjoin_path(root, joinrel,
! grouped_path, plain_path,
! hashclauses, jointype,
! extra, false, false);
! }
! else if (innerrel->gpi != NULL &&
! innerrel->gpi->pathlist != NIL &&
! outerrel->gpi == NULL)
! {
! grouped_path = (Path *)
! linitial(innerrel->gpi->pathlist);
! plain_path = cheapest_total_outer;
! try_grouped_hashjoin_path(root, joinrel, plain_path,
! grouped_path, hashclauses,
! jointype, extra,
! false, false);
!
! if (cheapest_startup_outer != NULL &&
! cheapest_startup_outer != cheapest_total_outer)
! {
! plain_path = cheapest_startup_outer;
! try_grouped_hashjoin_path(root, joinrel,
! plain_path,
! grouped_path,
! hashclauses,
! jointype, extra,
! false, false);
! }
! }
}
+
+ /*
+ * Try to join plain relations and make a grouped rel out of
+ * the join.
+ *
+ * Since aggregation needs the whole relation, we are only
+ * interested in total costs.
+ */
+ try_grouped_hashjoin_path(root, joinrel,
+ cheapest_total_outer,
+ cheapest_total_inner,
+ hashclauses,
+ jointype, extra, true, false);
}
}
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1765,1777 ****
cheapest_safe_inner =
get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
! if (cheapest_safe_inner != NULL)
! try_partial_hashjoin_path(root, joinrel,
! cheapest_partial_outer,
! cheapest_safe_inner,
! hashclauses, jointype, extra);
}
}
}
/*
--- 2675,2898 ----
cheapest_safe_inner =
get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
! if (!grouped)
! {
! if (cheapest_safe_inner != NULL)
! try_partial_hashjoin_path(root, joinrel,
! cheapest_partial_outer,
! cheapest_safe_inner,
! hashclauses, jointype, extra,
! false, false);
! }
! else if (joinrel->gpi != NULL)
! {
! /*
! * Grouped partial path.
! *
! * 1. Apply aggregation to the plain partial join path.
! */
! if (cheapest_safe_inner != NULL)
! try_grouped_hashjoin_path(root, joinrel,
! cheapest_partial_outer,
! cheapest_safe_inner,
! hashclauses,
! jointype, extra, true, true);
!
! /*
! * 2. Join the cheapest partial grouped outer path (if one
! * exists) to cheapest_safe_inner (there's no reason to look
! * for another inner path than what we used for non-grouped
! * partial join path).
! */
! if (outerrel->gpi != NULL &&
! outerrel->gpi->partial_pathlist != NIL &&
! innerrel->gpi == NULL &&
! cheapest_safe_inner != NULL)
! {
! Path *outer_path;
!
! outer_path = (Path *)
! linitial(outerrel->gpi->partial_pathlist);
!
! try_grouped_hashjoin_path(root, joinrel, outer_path,
! cheapest_safe_inner,
! hashclauses,
! jointype, extra, false, true);
! }
!
! /*
! * 3. Join the cheapest_partial_outer path (again, no reason
! * to use different outer path than the one we used for plain
! * partial join) to the cheapest grouped inner path if the
! * latter exists and is parallel-safe.
! */
! if (innerrel->gpi != NULL &&
! innerrel->gpi->pathlist != NIL &&
! outerrel->gpi == NULL)
! {
! Path *inner_path;
!
! inner_path = (Path *) linitial(innerrel->gpi->pathlist);
!
! if (inner_path->parallel_safe)
! try_grouped_hashjoin_path(root, joinrel,
! cheapest_partial_outer,
! inner_path,
! hashclauses,
! jointype, extra,
! false, true);
! }
!
! /*
! * Other combinations seem impossible because: 1. At most 1
! * input relation of the join can be grouped, 2. the inner
! * path must not be partial.
! */
! }
! }
! }
! }
!
! /*
! * Do the input paths emit all the aggregates contained in the grouped target
! * of the join?
! *
! * The point is that one input relation might be unable to evaluate some
! * aggregate(s), so it'll only generate plain paths. It's wrong to combine
! * such plain paths with grouped ones that the other input rel might be able
! * to generate because the result would miss the aggregate(s) the first
! * relation failed to evaluate.
! *
! * TODO For better efficiency, consider storing Bitmapset of
! * GroupedVarInfo.gvid in GroupedPathInfo.
! */
! static bool
! is_grouped_join_target_complete(PlannerInfo *root, PathTarget *jointarget,
! Path *outer_path, Path *inner_path)
! {
! RelOptInfo *outer_rel = outer_path->parent;
! RelOptInfo *inner_rel = inner_path->parent;
! ListCell *l1;
!
! /*
! * Join of two grouped relations is not supported.
! *
! * This actually isn't check of target completeness --- can it be located
! * elsewhere?
! */
! if (outer_rel->gpi != NULL && inner_rel->gpi != NULL)
! return false;
!
! foreach(l1, jointarget->exprs)
! {
! Expr *expr = (Expr *) lfirst(l1);
! GroupedVar *gvar;
! GroupedVarInfo *gvi = NULL;
! ListCell *l2;
! bool found = false;
!
! /* Only interested in aggregates. */
! if (!IsA(expr, GroupedVar))
! continue;
!
! gvar = castNode(GroupedVar, expr);
!
! /* Find the corresponding GroupedVarInfo. */
! foreach(l2, root->grouped_var_list)
! {
! GroupedVarInfo *gvi_tmp = castNode(GroupedVarInfo, lfirst(l2));
!
! if (gvi_tmp->gvid == gvar->gvid)
! {
! gvi = gvi_tmp;
! break;
! }
! }
! Assert(gvi != NULL);
!
! /*
! * If any aggregate references both input relations, something went
! * wrong during construction of one of the input targets: one input
! * rel is grouped, but no grouping target should have been created for
! * it if some aggregate required more than that input rel.
! */
! Assert(gvi->gv_eval_at == NULL ||
! !(bms_overlap(gvi->gv_eval_at, outer_rel->relids) &&
! bms_overlap(gvi->gv_eval_at, inner_rel->relids)));
!
! /*
! * If the aggregate belongs to the plain relation, it probably
! * means that non-grouping expression made aggregation of that
! * input relation impossible. Since that expression is not
! * necessarily emitted by the current join, aggregation might be
! * possible here. On the other hand, aggregation of a join which
! * already contains a grouped relation does not seem too
! * beneficial.
! *
! * XXX The condition below is also met if the query contains both
! * "star aggregate" and a normal one. Since the earlier can be
! * added to any base relation, and since we don't support join of
! * 2 grouped relations, join of arbitrary 2 relations will always
! * result in a plain relation.
! *
! * XXX If we conclude that aggregation is worth, only consider
! * this test failed if target usable for aggregation cannot be
! * created (i.e. the non-grouping expression is in the output of
! * the current join).
! */
! if ((outer_rel->gpi == NULL &&
! bms_overlap(gvi->gv_eval_at, outer_rel->relids))
! || (inner_rel->gpi == NULL &&
! bms_overlap(gvi->gv_eval_at, inner_rel->relids)))
! return false;
!
! /* Look for the aggregate in the input targets. */
! if (outer_rel->gpi != NULL)
! {
! /* No more than one input path should be grouped. */
! Assert(inner_rel->gpi == NULL);
!
! foreach(l2, outer_path->pathtarget->exprs)
! {
! expr = (Expr *) lfirst(l2);
!
! if (!IsA(expr, GroupedVar))
! continue;
!
! gvar = castNode(GroupedVar, expr);
! if (gvar->gvid == gvi->gvid)
! {
! found = true;
! break;
! }
! }
! }
! else if (!found && inner_rel->gpi != NULL)
! {
! Assert(outer_rel->gpi == NULL);
!
! foreach(l2, inner_path->pathtarget->exprs)
! {
! expr = (Expr *) lfirst(l2);
!
! if (!IsA(expr, GroupedVar))
! continue;
!
! gvar = castNode(GroupedVar, expr);
! if (gvar->gvid == gvi->gvid)
! {
! found = true;
! break;
! }
! }
}
+
+ /* Even a single missing aggregate causes the whole test to fail. */
+ if (!found)
+ return false;
}
+
+ return true;
}
/*
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
new file mode 100644
index 5a68de3..eadf3ee
*** a/src/backend/optimizer/path/joinrels.c
--- b/src/backend/optimizer/path/joinrels.c
*************** mark_dummy_rel(RelOptInfo *rel)
*** 1217,1223 ****
rel->partial_pathlist = NIL;
/* Set up the dummy path */
! add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL));
/* Set or update cheapest_total_path and related fields */
set_cheapest(rel);
--- 1217,1223 ----
rel->partial_pathlist = NIL;
/* Set up the dummy path */
! add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL), 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 a2fe661..91d855c
*** a/src/backend/optimizer/path/tidpath.c
--- b/src/backend/optimizer/path/tidpath.c
*************** create_tidscan_paths(PlannerInfo *root,
*** 266,270 ****
if (tidquals)
add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
! required_outer));
}
--- 266,270 ----
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 ebd442a..a7be1b2
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
***************
*** 14,19 ****
--- 14,20 ----
*/
#include "postgres.h"
+ #include "access/sysattr.h"
#include "catalog/pg_type.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
***************
*** 26,31 ****
--- 27,33 ----
#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"
*************** typedef struct PostponedQual
*** 45,50 ****
--- 47,53 ----
} PostponedQual;
+ static void create_grouped_var_infos(PlannerInfo *root);
static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
Index rtindex);
static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
*************** add_vars_to_targetlist(PlannerInfo *root
*** 240,245 ****
--- 243,532 ----
}
}
+ /*
+ * Add GroupedVarInfo to grouped_var_list for each aggregate and setup
+ * GroupedPathInfo for each base relation that can product grouped paths.
+ *
+ * XXX In the future we might want to create GroupedVarInfo for grouping
+ * expressions too, so that grouping key is not limited to plain Var if the
+ * grouping takes place below the top-level join.
+ *
+ * root->group_pathkeys must be setup before this function is called.
+ */
+ extern void
+ add_grouping_info_to_base_rels(PlannerInfo *root)
+ {
+ 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;
+
+ /* Create GroupedVarInfo per (distinct) aggregate. */
+ create_grouped_var_infos(root);
+
+ /* Is no grouping is possible below the top-level join? */
+ if (root->grouped_var_list == NIL)
+ return;
+
+ /* Process the individual base relations. */
+ 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 we
+ * wanted to prepare the child rels here, we'd need another iteration
+ * of simple_rel_array_size.
+ */
+ if (rel != NULL && rel->reloptkind == RELOPT_BASEREL)
+ prepare_rel_for_grouping(root, rel);
+ }
+ }
+
+ /*
+ * Create GroupedVarInfo for each distinct aggregate.
+ *
+ * If any aggregate is not suitable, set root->grouped_var_list to NIL and
+ * return.
+ *
+ * TODO Include aggregates from HAVING clause.
+ */
+ static void
+ create_grouped_var_infos(PlannerInfo *root)
+ {
+ List *tlist_exprs;
+ ListCell *lc;
+
+ Assert(root->grouped_var_list == NIL);
+
+ /*
+ * TODO Check if processed_tlist contains the HAVING aggregates. If not,
+ * get them elsewhere.
+ */
+ tlist_exprs = pull_var_clause((Node *) root->processed_tlist,
+ PVC_INCLUDE_AGGREGATES);
+ if (tlist_exprs == NIL)
+ return;
+
+ /* tlist_exprs may also contain Vars, but we only need Aggrefs. */
+ foreach(lc, tlist_exprs)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+ Aggref *aggref;
+ ListCell *lc2;
+ GroupedVarInfo *gvi;
+ bool exists;
+
+ if (IsA(expr, Var))
+ continue;
+
+ aggref = castNode(Aggref, expr);
+
+ /* TODO Think if (some of) these can be handled. */
+ if (aggref->aggvariadic ||
+ aggref->aggdirectargs || aggref->aggorder ||
+ aggref->aggdistinct || aggref->aggfilter)
+ {
+ /*
+ * Partial aggregation is not useful if at least one aggregate
+ * cannot be evaluated below the top-level join.
+ *
+ * XXX Is it worth freeing the GroupedVarInfos and their subtrees?
+ */
+ root->grouped_var_list = NIL;
+ break;
+ }
+
+ /* Does GroupedVarInfo for this aggregate already exist? */
+ exists = false;
+ foreach(lc2, root->grouped_var_list)
+ {
+ Expr *expr = (Expr *) lfirst(lc2);
+
+ gvi = castNode(GroupedVarInfo, expr);
+
+ if (equal(expr, gvi->gvexpr))
+ {
+ exists = true;
+ break;
+ }
+ }
+
+ /* Construct a new GroupedVarInfo if does not exist yet. */
+ if (!exists)
+ {
+ Relids relids;
+
+ /* TODO Initialize gv_width. */
+ gvi = makeNode(GroupedVarInfo);
+
+ gvi->gvid = list_length(root->grouped_var_list);
+ gvi->gvexpr = (Expr *) copyObject(aggref);
+ gvi->agg_partial = copyObject(aggref);
+ mark_partial_aggref(gvi->agg_partial, AGGSPLIT_INITIAL_SERIAL);
+
+ /* Find out where the aggregate should be evaluated. */
+ relids = pull_varnos((Node *) aggref);
+ if (!bms_is_empty(relids))
+ gvi->gv_eval_at = relids;
+ else
+ {
+ Assert(aggref->aggstar);
+ gvi->gv_eval_at = NULL;
+ }
+
+ root->grouped_var_list = lappend(root->grouped_var_list, gvi);
+ }
+ }
+
+ list_free(tlist_exprs);
+ }
+
+ /*
+ * Check if all the expressions of rel->reltarget can be used as grouping
+ * expressions and create target for grouped paths.
+ *
+ * If we succeed to create the grouping target, also replace rel->reltarget
+ * with a new one that has sortgrouprefs initialized -- this is necessary for
+ * create_agg_plan to match the grouping clauses against the input target
+ * expressions.
+ *
+ * rel_agg_attrs is a set attributes of the relation referenced by aggregate
+ * arguments. These can exist in the (plain) target without being grouping
+ * expressions.
+ *
+ * rel_agg_vars should be passed instead if rel is a join.
+ *
+ * TODO How about PHVs?
+ *
+ * TODO Make sure cost / width of both "result" and "plain" are correct.
+ */
+ PathTarget *
+ create_grouped_target(PlannerInfo *root, RelOptInfo *rel,
+ Relids rel_agg_attrs, List *rel_agg_vars)
+ {
+ PathTarget *result, *plain;
+ ListCell *lc;
+
+ /* The plan to be returned. */
+ result = create_empty_pathtarget();
+ /* The one to replace rel->reltarget. */
+ plain = create_empty_pathtarget();
+
+ foreach(lc, rel->reltarget->exprs)
+ {
+ Expr *texpr;
+ Index sortgroupref;
+ bool agg_arg_only = false;
+
+ texpr = (Expr *) lfirst(lc);
+
+ sortgroupref = get_expr_sortgroupref(root, texpr);
+ if (sortgroupref > 0)
+ {
+ /* It's o.k. to use the target expression for grouping. */
+ add_column_to_pathtarget(result, texpr, sortgroupref);
+
+ /*
+ * As for the plain target, add the original expression but set
+ * sortgroupref in addition.
+ */
+ add_column_to_pathtarget(plain, texpr, sortgroupref);
+
+ /* Process the next expression. */
+ continue;
+ }
+
+ /*
+ * 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))
+ {
+ Var *arg_var = castNode(Var, texpr);
+
+ if (rel->relid > 0)
+ {
+ AttrNumber varattno;
+
+ /*
+ * For a single relation we only need to check attribute
+ * number.
+ *
+ * Apply the same offset that pull_varattnos() did.
+ */
+ varattno = arg_var->varattno - FirstLowInvalidHeapAttributeNumber;
+
+ if (bms_is_member(varattno, rel_agg_attrs))
+ agg_arg_only = true;
+ }
+ else
+ {
+ ListCell *lc2;
+
+ /* Join case. */
+ foreach(lc2, rel_agg_vars)
+ {
+ Var *var = castNode(Var, lfirst(lc2));
+
+ if (var->varno == arg_var->varno &&
+ var->varattno == arg_var->varattno)
+ {
+ agg_arg_only = true;
+ break;
+ }
+ }
+ }
+
+ if (agg_arg_only)
+ {
+ /*
+ * This expression is not suitable for grouping, but the
+ * aggregation input target ought to stay complete.
+ */
+ add_column_to_pathtarget(plain, texpr, 0);
+ }
+ }
+
+ /*
+ * A single mismatched expression makes the whole relation useless
+ * for grouping.
+ */
+ if (!agg_arg_only)
+ {
+ /*
+ * TODO This seems possible to happen multiple times per relation,
+ * so result might be worth freeing. Implement free_pathtarget()?
+ * Or mark the relation as inappropriate for grouping?
+ */
+ /* TODO Free both result and plain. */
+ return NULL;
+ }
+ }
+
+ if (list_length(result->exprs) == 0)
+ {
+ /* TODO free_pathtarget(result); free_pathtarget(plain) */
+ result = NULL;
+ }
+
+ /* Apply the adjusted input target as the replacement is complete now.q */
+ rel->reltarget = plain;
+
+ return result;
+ }
+
/*****************************************************************************
*
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
new file mode 100644
index 5565736..058af2c
*** 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 ef0de3f..f70b445
*** 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
*** 177,182 ****
--- 178,191 ----
(*qp_callback) (root, qp_extra);
/*
+ * If the query result can be grouped, check if any grouping can be
+ * performed below the top-level join. If so, Initialize GroupedPathInfo
+ * of base relations capable to do the grouping and setup
+ * root->grouped_var_list.
+ */
+ add_grouping_info_to_base_rels(root);
+
+ /*
* Examine any "placeholder" expressions generated during subquery pullup.
* Make sure that the Vars they need are marked as needed at the relevant
* join level. This must be done before join removal because it might
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 649a233..2c44f42
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** static void standard_qp_callback(Planner
*** 130,138 ****
static double get_number_of_groups(PlannerInfo *root,
double path_rows,
grouping_sets_data *gd);
- 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,
--- 130,135 ----
*************** inheritance_planner(PlannerInfo *root)
*** 1419,1425 ****
returningLists,
rowMarks,
NULL,
! SS_assign_special_param(root)));
}
/*--------------------
--- 1416,1422 ----
returningLists,
rowMarks,
NULL,
! SS_assign_special_param(root)), false);
}
/*--------------------
*************** grouping_planner(PlannerInfo *root, bool
*** 2040,2046 ****
}
/* And shove it into final_rel */
! add_path(final_rel, path);
}
/*
--- 2037,2043 ----
}
/* And shove it into final_rel */
! add_path(final_rel, path, false);
}
/*
*************** get_number_of_groups(PlannerInfo *root,
*** 3446,3485 ****
}
/*
- * estimate_hashagg_tablesize
- * estimate the number of bytes that a hash aggregate hashtable will
- * require based on the agg_costs, path width and dNumGroups.
- *
- * XXX this may be over-estimating the size now that hashagg knows to omit
- * unneeded columns from the hashtable. Also for mixed-mode grouping sets,
- * grouping columns not in the hashed set are counted here even though hashagg
- * won't store them. Is this a problem?
- */
- 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.
--- 3443,3448 ----
*************** create_grouping_paths(PlannerInfo *root,
*** 3600,3606 ****
(List *) parse->havingQual);
}
! add_path(grouped_rel, path);
/* No need to consider any other alternatives. */
set_cheapest(grouped_rel);
--- 3563,3569 ----
(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,
*** 3777,3783 ****
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups));
else
add_partial_path(grouped_rel, (Path *)
create_group_path(root,
--- 3740,3747 ----
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups),
! false);
else
add_partial_path(grouped_rel, (Path *)
create_group_path(root,
*************** create_grouping_paths(PlannerInfo *root,
*** 3786,3792 ****
partial_grouping_target,
parse->groupClause,
NIL,
! dNumPartialGroups));
}
}
}
--- 3750,3757 ----
partial_grouping_target,
parse->groupClause,
NIL,
! dNumPartialGroups),
! false);
}
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 3817,3823 ****
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups));
}
}
}
--- 3782,3789 ----
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups),
! false);
}
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 3869,3875 ****
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups));
}
else if (parse->groupClause)
{
--- 3835,3841 ----
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups), false);
}
else if (parse->groupClause)
{
*************** create_grouping_paths(PlannerInfo *root,
*** 3884,3890 ****
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups));
}
else
{
--- 3850,3856 ----
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups), false);
}
else
{
*************** create_grouping_paths(PlannerInfo *root,
*** 3933,3939 ****
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups));
else
add_path(grouped_rel, (Path *)
create_group_path(root,
--- 3899,3905 ----
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,
*** 3942,3948 ****
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups));
/*
* The point of using Gather Merge rather than Gather is that it
--- 3908,3914 ----
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups), false);
/*
* The point of using Gather Merge rather than Gather is that it
*************** create_grouping_paths(PlannerInfo *root,
*** 3995,4001 ****
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups));
else
add_path(grouped_rel, (Path *)
create_group_path(root,
--- 3961,3967 ----
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,
*** 4004,4010 ****
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups));
}
}
}
--- 3970,3976 ----
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups), false);
}
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 4049,4055 ****
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups));
}
}
--- 4015,4021 ----
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups), false);
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 4087,4095 ****
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups));
}
}
}
/* Give a helpful error if we failed to find any implementation */
--- 4053,4129 ----
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups), false);
}
}
+
+ /*
+ * If input_rel has partially aggregated partial paths, gather them
+ * and perform the final aggregation.
+ *
+ * TODO Allow havingQual - currently not supported at base relation
+ * level.
+ */
+ if (input_rel->gpi != NULL &&
+ input_rel->gpi->partial_pathlist != NIL &&
+ !parse->havingQual)
+ {
+ Path *path = (Path *) linitial(input_rel->gpi->partial_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.
+ *
+ * The top-level grouped_rel needs to receive the path into
+ * regular pathlist, as opposed grouped_rel->gpi->pathlist.
+ */
+
+ add_path(input_rel, path, false);
+ }
+
+ /*
+ * If input_rel has partially aggregated paths, perform the final
+ * aggregation.
+ *
+ * TODO Allow havingQual - currently not supported at base relation
+ * level.
+ */
+ if (input_rel->gpi != NULL && input_rel->gpi->pathlist != NIL &&
+ !parse->havingQual)
+ {
+ Path *pre_agg = (Path *) linitial(input_rel->gpi->pathlist);
+
+ dNumGroups = get_number_of_groups(root, pre_agg->rows, gd);
+
+ 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 */
*************** consider_groupingsets_paths(PlannerInfo
*** 4289,4295 ****
strat,
new_rollups,
agg_costs,
! dNumGroups));
return;
}
--- 4323,4329 ----
strat,
new_rollups,
agg_costs,
! dNumGroups), false);
return;
}
*************** consider_groupingsets_paths(PlannerInfo
*** 4447,4453 ****
AGG_MIXED,
rollups,
agg_costs,
! dNumGroups));
}
}
--- 4481,4487 ----
AGG_MIXED,
rollups,
agg_costs,
! dNumGroups), false);
}
}
*************** consider_groupingsets_paths(PlannerInfo
*** 4464,4470 ****
AGG_SORTED,
gd->rollups,
agg_costs,
! dNumGroups));
}
/*
--- 4498,4504 ----
AGG_SORTED,
gd->rollups,
agg_costs,
! dNumGroups), false);
}
/*
*************** create_one_window_path(PlannerInfo *root
*** 4649,4655 ****
window_pathkeys);
}
! add_path(window_rel, path);
}
/*
--- 4683,4689 ----
window_pathkeys);
}
! add_path(window_rel, path, false);
}
/*
*************** create_distinct_paths(PlannerInfo *root,
*** 4755,4761 ****
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows));
}
}
--- 4789,4795 ----
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows), false);
}
}
*************** create_distinct_paths(PlannerInfo *root,
*** 4782,4788 ****
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows));
}
/*
--- 4816,4822 ----
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows), false);
}
/*
*************** create_distinct_paths(PlannerInfo *root,
*** 4829,4835 ****
parse->distinctClause,
NIL,
NULL,
! numDistinctRows));
}
/* Give a helpful error if we failed to find any implementation */
--- 4863,4869 ----
parse->distinctClause,
NIL,
NULL,
! numDistinctRows), false);
}
/* Give a helpful error if we failed to find any implementation */
*************** create_ordered_paths(PlannerInfo *root,
*** 4927,4933 ****
path = apply_projection_to_path(root, ordered_rel,
path, target);
! add_path(ordered_rel, path);
}
}
--- 4961,4967 ----
path = apply_projection_to_path(root, ordered_rel,
path, target);
! add_path(ordered_rel, path, false);
}
}
*************** create_ordered_paths(PlannerInfo *root,
*** 4977,4983 ****
path = apply_projection_to_path(root, ordered_rel,
path, target);
! add_path(ordered_rel, path);
}
}
--- 5011,5017 ----
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 1278371..548b372
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
*************** typedef struct
*** 40,45 ****
--- 40,46 ----
List *tlist; /* underlying target list */
int num_vars; /* number of plain Var tlist entries */
bool has_ph_vars; /* are there PlaceHolderVar entries? */
+ bool has_grp_vars; /* are there GroupedVar entries? */
bool has_non_vars; /* are there other entries? */
tlist_vinfo vars[FLEXIBLE_ARRAY_MEMBER]; /* has num_vars entries */
} indexed_tlist;
*************** set_upper_references(PlannerInfo *root,
*** 1725,1733 ****
--- 1726,1777 ----
indexed_tlist *subplan_itlist;
List *output_targetlist;
ListCell *l;
+ List *sub_tlist_save = NIL;
+
+ 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 eventually need to restore the original list.
+ */
+ if (!IsA(subplan, Agg))
+ sub_tlist_save = 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.
+ */
+ subplan->targetlist =
+ restore_grouping_expressions(root, subplan->targetlist);
+ }
+ }
+ }
subplan_itlist = build_tlist_index(subplan->targetlist);
+ /*
+ * The replacement of GroupVars by Aggrefs was only needed for the index
+ * build.
+ */
+ if (sub_tlist_save != NIL)
+ subplan->targetlist = sub_tlist_save;
+
output_targetlist = NIL;
foreach(l, plan->targetlist)
{
*************** build_tlist_index(List *tlist)
*** 1937,1942 ****
--- 1981,1987 ----
itlist->tlist = tlist;
itlist->has_ph_vars = false;
+ itlist->has_grp_vars = false;
itlist->has_non_vars = false;
/* Find the Vars and fill in the index array */
*************** build_tlist_index(List *tlist)
*** 1956,1961 ****
--- 2001,2008 ----
}
else if (tle->expr && IsA(tle->expr, PlaceHolderVar))
itlist->has_ph_vars = true;
+ else if (tle->expr && IsA(tle->expr, GroupedVar))
+ itlist->has_grp_vars = true;
else
itlist->has_non_vars = true;
}
*************** fix_join_expr_mutator(Node *node, fix_jo
*** 2233,2238 ****
--- 2280,2310 ----
/* No referent found for Var */
elog(ERROR, "variable not found in subplan target lists");
}
+ if (IsA(node, GroupedVar))
+ {
+ GroupedVar *gvar = (GroupedVar *) node;
+
+ /* See if the GroupedVar has bubbled up from a lower plan node */
+ if (context->outer_itlist && context->outer_itlist->has_grp_vars)
+ {
+ newvar = search_indexed_tlist_for_non_var((Expr *) gvar,
+ context->outer_itlist,
+ OUTER_VAR);
+ if (newvar)
+ return (Node *) newvar;
+ }
+ if (context->inner_itlist && context->inner_itlist->has_grp_vars)
+ {
+ newvar = search_indexed_tlist_for_non_var((Expr *) gvar,
+ context->inner_itlist,
+ INNER_VAR);
+ if (newvar)
+ return (Node *) newvar;
+ }
+
+ /* No referent found for GroupedVar */
+ elog(ERROR, "grouped variable not found in subplan target lists");
+ }
if (IsA(node, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
*************** fix_upper_expr_mutator(Node *node, fix_u
*** 2389,2395 ****
/* If no match, just fall through to process it normally */
}
/* Try matching more complex expressions too, if tlist has any */
! if (context->subplan_itlist->has_non_vars)
{
newvar = search_indexed_tlist_for_non_var((Expr *) node,
context->subplan_itlist,
--- 2461,2468 ----
/* If no match, just fall through to process it normally */
}
/* Try matching more complex expressions too, if tlist has any */
! if (context->subplan_itlist->has_grp_vars ||
! context->subplan_itlist->has_non_vars)
{
newvar = search_indexed_tlist_for_non_var((Expr *) node,
context->subplan_itlist,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
new file mode 100644
index a1be858..87a74be
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
*************** plan_set_operations(PlannerInfo *root)
*** 207,213 ****
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)
--- 207,213 ----
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 2d5caae..98e5d6a
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
***************
*** 24,29 ****
--- 24,31 ----
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
#include "optimizer/restrictinfo.h"
+ /* TODO Remove this if get_grouping_expressions ends up in another module. */
+ #include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
*************** 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;
--- 411,419 ----
* 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 ****
--- 430,443 ----
/* Pretend parameterized paths have no pathkeys, per comment above */
new_path_pathkeys = new_path->param_info ? NIL : new_path->pathkeys;
+ if (!grouped)
+ pathlist = parent_rel->pathlist;
+ else
+ {
+ Assert(parent_rel->gpi != NULL);
+ pathlist = parent_rel->gpi->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 */
--- 447,453 ----
* 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
--- 593,599 ----
*/
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
{
--- 624,637 ----
{
/* 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);
!
! if (!grouped)
! parent_rel->pathlist = pathlist;
! else
! parent_rel->gpi->pathlist = pathlist;
}
else
{
*************** 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;
--- 661,669 ----
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
*** 656,664 ****
new_path_pathkeys = required_outer ? NIL : pathkeys;
/* 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;
--- 672,689 ----
new_path_pathkeys = required_outer ? NIL : pathkeys;
/* Decide whether new path's startup cost is interesting */
! consider_startup = required_outer ? parent_rel->consider_param_startup :
! parent_rel->consider_startup;
! if (!grouped)
! pathlist = parent_rel->pathlist;
! else
! {
! Assert(parent_rel->gpi != NULL);
! pathlist = parent_rel->gpi->pathlist;
! }
!
! foreach(p1, pathlist)
{
Path *old_path = (Path *) lfirst(p1);
PathKeysComparison keyscmp;
*************** add_path_precheck(RelOptInfo *parent_rel
*** 749,771 ****
* referenced by partial BitmapHeapPaths.
*/
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);
--- 774,805 ----
* referenced by partial BitmapHeapPaths.
*/
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();
+ if (!grouped)
+ pathlist = parent_rel->partial_pathlist;
+ else
+ {
+ Assert(parent_rel->gpi != NULL);
+ pathlist = parent_rel->gpi->partial_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,
*** 819,830 ****
}
/*
! * 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);
pfree(old_path);
/* p1_prev does not advance */
}
--- 853,863 ----
}
/*
! * Remove current element from pathlist if dominated by new.
*/
if (remove_old)
{
! pathlist = list_delete_cell(pathlist, p1, p1_prev);
pfree(old_path);
/* p1_prev does not advance */
}
*************** add_partial_path(RelOptInfo *parent_rel,
*** 839,845 ****
/*
* 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)
--- 872,878 ----
/*
* 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,
*** 850,859 ****
{
/* 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
{
--- 883,896 ----
{
/* 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);
!
! if (!grouped)
! parent_rel->partial_pathlist = pathlist;
! else
! parent_rel->gpi->partial_pathlist = pathlist;
}
else
{
*************** add_partial_path(RelOptInfo *parent_rel,
*** 874,882 ****
*/
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
--- 911,928 ----
*/
bool
add_partial_path_precheck(RelOptInfo *parent_rel, Cost total_cost,
! List *pathkeys, bool grouped)
{
ListCell *p1;
+ List *pathlist;
+
+ if (!grouped)
+ pathlist = parent_rel->partial_pathlist;
+ else
+ {
+ Assert(parent_rel->gpi != NULL);
+ pathlist = parent_rel->gpi->partial_pathlist;
+ }
/*
* Our goal here is twofold. First, we want to find out whether this path
*************** add_partial_path_precheck(RelOptInfo *pa
*** 886,895 ****
* 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;
--- 932,942 ----
* 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
*** 918,924 ****
* completion.
*/
if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! NULL))
return false;
return true;
--- 965,971 ----
* completion.
*/
if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! NULL, grouped))
return false;
return true;
*************** calc_non_nestloop_required_outer(Path *o
*** 2055,2060 ****
--- 2102,2108 ----
* 'restrict_clauses' are the RestrictInfo nodes to apply at the join
* 'pathkeys' are the path keys of the new join path
* 'required_outer' is the set of required outer rels
+ * 'target' can be passed to override that of joinrel.
*
* Returns the resulting path node.
*/
*************** create_nestloop_path(PlannerInfo *root,
*** 2068,2074 ****
Path *inner_path,
List *restrict_clauses,
List *pathkeys,
! Relids required_outer)
{
NestPath *pathnode = makeNode(NestPath);
Relids inner_req_outer = PATH_REQ_OUTER(inner_path);
--- 2116,2123 ----
Path *inner_path,
List *restrict_clauses,
List *pathkeys,
! Relids required_outer,
! PathTarget *target)
{
NestPath *pathnode = makeNode(NestPath);
Relids inner_req_outer = PATH_REQ_OUTER(inner_path);
*************** create_nestloop_path(PlannerInfo *root,
*** 2101,2107 ****
pathnode->path.pathtype = T_NestLoop;
pathnode->path.parent = joinrel;
! pathnode->path.pathtarget = joinrel->reltarget;
pathnode->path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
--- 2150,2156 ----
pathnode->path.pathtype = T_NestLoop;
pathnode->path.parent = joinrel;
! pathnode->path.pathtarget = target == NULL ? joinrel->reltarget : target;
pathnode->path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
*************** create_mergejoin_path(PlannerInfo *root,
*** 2159,2171 ****
Relids required_outer,
List *mergeclauses,
List *outersortkeys,
! List *innersortkeys)
{
MergePath *pathnode = makeNode(MergePath);
pathnode->jpath.path.pathtype = T_MergeJoin;
pathnode->jpath.path.parent = joinrel;
! pathnode->jpath.path.pathtarget = joinrel->reltarget;
pathnode->jpath.path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
--- 2208,2222 ----
Relids required_outer,
List *mergeclauses,
List *outersortkeys,
! List *innersortkeys,
! PathTarget *target)
{
MergePath *pathnode = makeNode(MergePath);
pathnode->jpath.path.pathtype = T_MergeJoin;
pathnode->jpath.path.parent = joinrel;
! pathnode->jpath.path.pathtarget = target == NULL ? joinrel->reltarget :
! target;
pathnode->jpath.path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
*************** create_mergejoin_path(PlannerInfo *root,
*** 2210,2215 ****
--- 2261,2267 ----
* 'required_outer' is the set of required outer rels
* 'hashclauses' are the RestrictInfo nodes to use as hash clauses
* (this should be a subset of the restrict_clauses list)
+ * 'target' can be passed to override that of joinrel.
*/
HashPath *
create_hashjoin_path(PlannerInfo *root,
*************** create_hashjoin_path(PlannerInfo *root,
*** 2221,2233 ****
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,
--- 2273,2287 ----
Path *inner_path,
List *restrict_clauses,
Relids required_outer,
! List *hashclauses,
! PathTarget *target)
{
HashPath *pathnode = makeNode(HashPath);
pathnode->jpath.path.pathtype = T_HashJoin;
pathnode->jpath.path.parent = joinrel;
! pathnode->jpath.path.pathtarget = target == NULL ? joinrel->reltarget :
! target;
pathnode->jpath.path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
*************** create_agg_path(PlannerInfo *root,
*** 2713,2718 ****
--- 2767,2942 ----
}
/*
+ * Apply partial AGG_SORTED aggregation path to subpath if it's suitably
+ * sorted.
+ *
+ * first_call indicates whether the function is being called first time for
+ * given index --- since the target should not change, we can skip the check
+ * of sorting during subsequent calls.
+ *
+ * group_clauses, group_exprs and agg_exprs are pointers to lists we populate
+ * when called first time for particular index, and that user passes for
+ * subsequent calls.
+ *
+ * NULL is returned if sorting of subpath output is not suitable.
+ */
+ AggPath *
+ create_partial_agg_sorted_path(PlannerInfo *root, Path *subpath,
+ bool first_call,
+ List **group_clauses, List **group_exprs,
+ List **agg_exprs, double input_rows)
+ {
+ RelOptInfo *rel;
+ AggClauseCosts agg_costs;
+ double dNumGroups;
+ AggPath *result = NULL;
+
+ rel = subpath->parent;
+ Assert(rel->gpi != NULL);
+
+ if (subpath->pathkeys == NIL)
+ return NULL;
+
+ if (!grouping_is_sortable(root->parse->groupClause))
+ return NULL;
+
+ if (first_call)
+ {
+ ListCell *lc1;
+ List *key_subset = NIL;
+
+ /*
+ * Find all query pathkeys that our relation does affect.
+ */
+ foreach(lc1, root->group_pathkeys)
+ {
+ PathKey *gkey = castNode(PathKey, lfirst(lc1));
+ ListCell *lc2;
+
+ foreach(lc2, subpath->pathkeys)
+ {
+ PathKey *skey = castNode(PathKey, lfirst(lc2));
+
+ if (skey == gkey)
+ {
+ key_subset = lappend(key_subset, gkey);
+ break;
+ }
+ }
+ }
+
+ if (key_subset == NIL)
+ return NULL;
+
+ /* Check if AGG_SORTED is useful for the whole query. */
+ if (!pathkeys_contained_in(key_subset, subpath->pathkeys))
+ return NULL;
+ }
+
+ if (first_call)
+ get_grouping_expressions(root, rel->gpi->target, group_clauses,
+ group_exprs, agg_exprs);
+
+ MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
+ Assert(*agg_exprs != NIL);
+ get_agg_clause_costs(root, (Node *) *agg_exprs, AGGSPLIT_INITIAL_SERIAL,
+ &agg_costs);
+
+ Assert(*group_exprs != NIL);
+ dNumGroups = estimate_num_groups(root, *group_exprs, input_rows, NULL);
+
+ /* TODO HAVING qual. */
+ Assert(*group_clauses != NIL);
+ result = create_agg_path(root, rel, subpath, rel->gpi->target, AGG_SORTED,
+ AGGSPLIT_INITIAL_SERIAL, *group_clauses, NIL,
+ &agg_costs, dNumGroups);
+
+ return result;
+ }
+
+ /*
+ * Appy partial AGG_HASHED aggregation to subpath.
+ *
+ * Arguments have the same meaning as those of create_agg_sorted_path.
+ *
+ */
+ AggPath *
+ create_partial_agg_hashed_path(PlannerInfo *root, Path *subpath,
+ bool first_call,
+ List **group_clauses, List **group_exprs,
+ List **agg_exprs, double input_rows)
+ {
+ RelOptInfo *rel;
+ bool can_hash;
+ AggClauseCosts agg_costs;
+ double dNumGroups;
+ Size hashaggtablesize;
+ Query *parse = root->parse;
+ AggPath *result = NULL;
+
+ rel = subpath->parent;
+ Assert(rel->gpi != NULL);
+
+ if (first_call)
+ {
+ /*
+ * Find one grouping clause per grouping column.
+ *
+ * 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.
+ */
+ get_grouping_expressions(root, rel->gpi->target, group_clauses,
+ group_exprs, agg_exprs);
+ }
+
+ MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
+ Assert(*agg_exprs != NIL);
+ 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));
+
+ if (can_hash)
+ {
+ Assert(*group_exprs != NIL);
+ dNumGroups = estimate_num_groups(root, *group_exprs, input_rows,
+ NULL);
+
+ hashaggtablesize = estimate_hashagg_tablesize(subpath, &agg_costs,
+ dNumGroups);
+
+ if (hashaggtablesize < work_mem * 1024L)
+ {
+ /*
+ * Create the partial aggregation path.
+ */
+ Assert(*group_clauses != NIL);
+
+ result = create_agg_path(root, rel, subpath,
+ rel->gpi->target,
+ AGG_HASHED,
+ AGGSPLIT_INITIAL_SERIAL,
+ *group_clauses, NIL,
+ &agg_costs,
+ dNumGroups);
+
+ /*
+ * The agg path should require no fewer parameters than the plain
+ * one.
+ */
+ result->path.param_info = subpath->param_info;
+ }
+ }
+
+ return result;
+ }
+
+ /*
* create_groupingsets_path
* Creates a pathnode that represents performing GROUPING SETS aggregation
*
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
new file mode 100644
index 342d884..3cd7093
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
***************
*** 25,30 ****
--- 25,31 ----
#include "optimizer/plancat.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h"
+ #include "optimizer/var.h"
#include "utils/hsearch.h"
*************** typedef struct JoinHashEntry
*** 35,41 ****
} 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,
--- 36,42 ----
} 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
*** 120,125 ****
--- 121,127 ----
rel->cheapest_parameterized_paths = NIL;
rel->direct_lateral_relids = NULL;
rel->lateral_relids = NULL;
+ rel->gpi = NULL;
rel->relid = relid;
rel->rtekind = rte->rtekind;
/* min_attr, max_attr, attr_needed, attr_widths are set below */
*************** build_join_rel(PlannerInfo *root,
*** 497,502 ****
--- 499,505 ----
inner_rel->direct_lateral_relids);
joinrel->lateral_relids = min_join_parameterization(root, joinrel->relids,
outer_rel, inner_rel);
+ joinrel->gpi = NULL;
joinrel->relid = 0; /* indicates not a baserel */
joinrel->rtekind = RTE_JOIN;
joinrel->min_attr = 0;
*************** build_join_rel(PlannerInfo *root,
*** 539,548 ****
* 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
--- 542,558 ----
* 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);
+ /* Try to build grouped target. */
+ /*
+ * TODO Consider if placeholders make sense here. If not, also make the
+ * related code below conditional.
+ */
+ prepare_rel_for_grouping(root, joinrel);
+
/*
* 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
*** 670,686 ****
*/
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
* decisions about whether to copy them.
--- 680,722 ----
*/
static void
build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! RelOptInfo *input_rel, bool grouped)
{
Relids relids = joinrel->relids;
+ PathTarget *input_target, *result;
ListCell *vars;
+ int i = -1;
! if (!grouped)
! {
! input_target = input_rel->reltarget;
! result = joinrel->reltarget;
! }
! else
! {
! if (input_rel->gpi != NULL)
! {
! input_target = input_rel->gpi->target;
! Assert(input_target != NULL);
! }
! else
! input_target = input_rel->reltarget;
!
! /* Caller should have initialized this. */
! Assert(joinrel->gpi != NULL);
!
! /* Default to the plain target. */
! result = joinrel->gpi->target;
! }
!
! foreach(vars, input_target->exprs)
{
Var *var = (Var *) lfirst(vars);
RelOptInfo *baserel;
int ndx;
+ i++;
+
/*
* Ignore PlaceHolderVars in the input tlists; we'll make our own
* decisions about whether to copy them.
*************** build_joinrel_tlist(PlannerInfo *root, R
*** 704,713 ****
ndx = var->varattno - baserel->min_attr;
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];
}
}
}
--- 740,763 ----
ndx = var->varattno - baserel->min_attr;
if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
{
+ Index sortgroupref = 0;
+
/* Yup, add it to the output */
! if (input_target->sortgrouprefs)
! sortgroupref = input_target->sortgrouprefs[i];
!
! /*
! * Even if not used for grouping in the input path (the input path
! * is not necessarily grouped), it might be useful for grouping
! * higher in the join tree.
! */
! if (sortgroupref == 0)
! sortgroupref = get_expr_sortgroupref(root, (Expr *) var);
!
! add_column_to_pathtarget(result, (Expr *) var, sortgroupref);
!
/* Vars have cost zero, so no need to adjust reltarget->cost */
! result->width += baserel->attr_widths[ndx];
}
}
}
*************** get_appendrel_parampathinfo(RelOptInfo *
*** 1359,1361 ****
--- 1409,1560 ----
return ppi;
}
+
+ /*
+ * If the relation can produce grouped paths, create GroupedPathInfo for it
+ * and create target for the grouped paths.
+ */
+ void
+ prepare_rel_for_grouping(PlannerInfo *root, RelOptInfo *rel)
+ {
+ List *rel_aggregates;
+ Relids rel_agg_attrs = NULL;
+ List *rel_agg_vars = NIL;
+ bool found_higher;
+ ListCell *lc;
+ PathTarget *target_grouped;
+
+ if (rel->relid > 0)
+ {
+ RangeTblEntry *rte = root->simple_rte_array[rel->relid];;
+
+ /*
+ * rtekind != RTE_RELATION case is not supported yet.
+ */
+ if (rte->rtekind != RTE_RELATION)
+ return;
+ }
+
+ /* Caller should only pass base relations or joins. */
+ Assert(rel->reloptkind == RELOPT_BASEREL ||
+ rel->reloptkind == RELOPT_JOINREL);
+
+ /*
+ * If any outer join can set the attribute value to NULL, the aggregate
+ * would receive different input at the base rel level.
+ *
+ * TODO For RELOPT_JOINREL, do not return if all the joins that can set
+ * any entry of the grouped target (do we need to postpone this check
+ * until the grouped target is available, and should create_grouped_target
+ * take care?) of this rel to NULL are provably below rel. (It's ok if rel
+ * is one of these joins.)
+ */
+ if (bms_overlap(rel->relids, root->nullable_baserels))
+ return;
+
+ /*
+ * Check if some aggregates can be evaluated in this relation's target,
+ * and collect all vars referenced by these aggregates.
+ */
+ rel_aggregates = NIL;
+ found_higher = false;
+ foreach(lc, root->grouped_var_list)
+ {
+ GroupedVarInfo *gvi = castNode(GroupedVarInfo, lfirst(lc));
+
+ /*
+ * The subset includes gv_eval_at uninitialized, which typically means
+ * Aggref.aggstar.
+ */
+ if (bms_is_subset(gvi->gv_eval_at, rel->relids))
+ {
+ Aggref *aggref = castNode(Aggref, gvi->gvexpr);
+
+ /*
+ * Accept the aggregate.
+ *
+ * GroupedVarInfo is more convenient for the next processing than
+ * Aggref, see add_aggregates_to_grouped_target.
+ */
+ rel_aggregates = lappend(rel_aggregates, gvi);
+
+ if (rel->relid > 0)
+ {
+ /*
+ * Simple relation. Collect attributes referenced by the
+ * aggregate arguments.
+ */
+ pull_varattnos((Node *) aggref, rel->relid, &rel_agg_attrs);
+ }
+ else
+ {
+ List *agg_vars;
+
+ /*
+ * Join. Collect vars referenced by the aggregate
+ * arguments.
+ */
+ /*
+ * TODO Can any argument contain PHVs? And if so, does it matter?
+ * Consider PVC_INCLUDE_PLACEHOLDERS | PVC_RECURSE_PLACEHOLDERS.
+ */
+ agg_vars = pull_var_clause((Node *) aggref,
+ PVC_RECURSE_AGGREGATES);
+ rel_agg_vars = list_concat(rel_agg_vars, agg_vars);
+ }
+ }
+ else if (bms_overlap(gvi->gv_eval_at, rel->relids))
+ {
+ /*
+ * Remember that there is at least one aggregate that needs more
+ * than this rel.
+ */
+ found_higher = true;
+ }
+ }
+
+ /*
+ * Grouping makes little sense w/o aggregate function.
+ */
+ if (rel_aggregates == NIL)
+ {
+ bms_free(rel_agg_attrs);
+ return;
+ }
+
+ if (found_higher)
+ {
+ /*
+ * If some aggregate(s) need only this rel but some other need
+ * multiple relations including the the current one, grouping of the
+ * current rel could steal some input variables from the "higher
+ * aggregate" (besides decreasing the number of input rows).
+ */
+ list_free(rel_aggregates);
+ bms_free(rel_agg_attrs);
+ return;
+ }
+
+ /*
+ * If rel->reltarget can be used for aggregation, mark the relation as
+ * capable of grouping.
+ */
+ Assert(rel->gpi == NULL);
+ target_grouped = create_grouped_target(root, rel, rel_agg_attrs,
+ rel_agg_vars);
+ if (target_grouped != NULL)
+ {
+ GroupedPathInfo *gpi;
+
+ gpi = makeNode(GroupedPathInfo);
+ gpi->target = copy_pathtarget(target_grouped);
+ gpi->pathlist = NIL;
+ gpi->partial_pathlist = NIL;
+ rel->gpi = gpi;
+
+ /*
+ * Add aggregates (in the form of GroupedVar) to the target.
+ */
+ add_aggregates_to_target(root, gpi->target, rel_aggregates, rel);
+ }
+ }
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
new file mode 100644
index 0952385..dd962b7
*** a/src/backend/optimizer/util/tlist.c
--- b/src/backend/optimizer/util/tlist.c
*************** get_sortgrouplist_exprs(List *sgClauses,
*** 408,413 ****
--- 408,487 ----
return result;
}
+ /*
+ * get_sortgrouplist_clauses
+ *
+ * Given a "grouped target" (i.e. target where each non-GroupedVar
+ * element must have sortgroupref set), build a list of the referencing
+ * SortGroupClauses, a list of the corresponding grouping expressions and
+ * a list of aggregate expressions.
+ */
+ /* Refine the function name. */
+ void
+ get_grouping_expressions(PlannerInfo *root, PathTarget *target,
+ List **grouping_clauses, List **grouping_exprs,
+ List **agg_exprs)
+ {
+ ListCell *l;
+ int i = 0;
+
+ foreach(l, target->exprs)
+ {
+ Index sortgroupref = 0;
+ SortGroupClause *cl;
+ Expr *texpr;
+
+ texpr = (Expr *) lfirst(l);
+
+ /* The target should contain at least one grouping column. */
+ Assert(target->sortgrouprefs != NULL);
+
+ if (IsA(texpr, GroupedVar))
+ {
+ /*
+ * texpr should represent the first aggregate in the targetlist.
+ */
+ break;
+ }
+
+ /*
+ * Find the clause by sortgroupref.
+ */
+ sortgroupref = target->sortgrouprefs[i++];
+
+ /*
+ * Besides aggregates, the target should contain no expressions w/o
+ * sortgroupref. Plain relation being joined to grouped can have
+ * sortgroupref equal to zero for expressions contained neither in
+ * grouping expression nor in aggregate arguments, but if the target
+ * contains such an expression, it shouldn't be used for aggregation
+ * --- see can_aggregate field of GroupedPathInfo.
+ */
+ Assert(sortgroupref > 0);
+
+ cl = get_sortgroupref_clause(sortgroupref, root->parse->groupClause);
+ *grouping_clauses = list_append_unique(*grouping_clauses, cl);
+
+ /*
+ * Add only unique clauses because of joins (both sides of a join can
+ * point at the same grouping clause). XXX Is it worth adding a bool
+ * argument indicating that we're dealing with join right now?
+ */
+ *grouping_exprs = list_append_unique(*grouping_exprs, texpr);
+ }
+
+ /* Now collect the aggregates. */
+ while (l != NULL)
+ {
+ GroupedVar *gvar = castNode(GroupedVar, lfirst(l));
+
+ /* Currently, GroupedVarInfo can only represent aggregate. */
+ Assert(gvar->agg_partial != NULL);
+ *agg_exprs = lappend(*agg_exprs, gvar->agg_partial);
+ l = lnext(l);
+ }
+ }
+
/*****************************************************************************
* Functions to extract data from a list of SortGroupClauses
*************** apply_pathtarget_labeling_to_tlist(List
*** 783,788 ****
--- 857,1081 ----
}
/*
+ * Replace each "grouped var" in the source targetlist with the original
+ * expression.
+ *
+ * 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;
+ Aggref *expr_new = NULL;
+
+ te = castNode(TargetEntry, lfirst(l));
+
+ if (IsA(te->expr, GroupedVar))
+ {
+ GroupedVar *gvar;
+
+ gvar = castNode(GroupedVar, te->expr);
+ expr_new = gvar->agg_partial;
+ }
+
+ if (expr_new != NULL)
+ {
+ te_new = flatCopyTargetEntry(te);
+ te_new->expr = (Expr *) expr_new;
+ }
+ else
+ te_new = te;
+ result = lappend(result, te_new);
+ }
+
+ return result;
+ }
+
+ /*
+ * For each aggregate add GroupedVar to target if "vars" is true, or the
+ * Aggref (marked as partial) if "vars" is false.
+ *
+ * If caller passes the aggregates, he must do so in the form of
+ * GroupedVarInfos so that we don't have to look for gvid. If NULL is passed,
+ * the function retrieves the suitable aggregates itself.
+ *
+ * List of the aggregates added is returned. This is only useful if the
+ * function had to retrieve the aggregates itself (i.e. NIL was passed for
+ * aggregates) -- caller is expected to do extra checks in that case (and to
+ * also free the list).
+ */
+ List *
+ add_aggregates_to_target(PlannerInfo *root, PathTarget *target,
+ List *aggregates, RelOptInfo *rel)
+ {
+ ListCell *lc;
+ GroupedVarInfo *gvi;
+
+ if (aggregates == NIL)
+ {
+ /* Caller should pass the aggregates for base relation. */
+ Assert(rel->reloptkind != RELOPT_BASEREL);
+
+ /* Collect all aggregates that this rel can evaluate. */
+ foreach(lc, root->grouped_var_list)
+ {
+ gvi = castNode(GroupedVarInfo, lfirst(lc));
+
+ /*
+ * Overlap is not guarantee of correctness alone, but caller needs
+ * to do additional checks, so we're optimistic here.
+ *
+ * If gv_eval_at is NULL, the underlying Aggref should have
+ * aggstar set.
+ */
+ if (bms_overlap(gvi->gv_eval_at, rel->relids) ||
+ gvi->gv_eval_at == NULL)
+ aggregates = lappend(aggregates, gvi);
+ }
+
+ if (aggregates == NIL)
+ return NIL;
+ }
+
+ /* Create the vars and add them to the target. */
+ foreach(lc, aggregates)
+ {
+ GroupedVar *gvar;
+
+ gvi = castNode(GroupedVarInfo, lfirst(lc));
+ gvar = makeNode(GroupedVar);
+ gvar->gvid = gvi->gvid;
+ gvar->gvexpr = gvi->gvexpr;
+ gvar->agg_partial = gvi->agg_partial;
+ add_new_column_to_pathtarget(target, (Expr *) gvar);
+ }
+
+ return aggregates;
+ }
+
+ /*
+ * Return ressortgroupref of the target entry that is either equal to the
+ * expression or exists in the same equivalence class.
+ */
+ Index
+ get_expr_sortgroupref(PlannerInfo *root, Expr *expr)
+ {
+ ListCell *lc;
+ Index sortgroupref;
+
+ /*
+ * First, check if the query group clause contains exactly this
+ * expression.
+ */
+ foreach(lc, root->processed_tlist)
+ {
+ TargetEntry *te = castNode(TargetEntry, lfirst(lc));
+
+ if (equal(expr, te->expr) && te->ressortgroupref > 0)
+ return te->ressortgroupref;
+ }
+
+ /*
+ * If exactly this expression is not there, check if a grouping clause
+ * exists that belongs to the same equivalence class as the expression.
+ */
+ foreach(lc, root->group_pathkeys)
+ {
+ PathKey *pk = castNode(PathKey, lfirst(lc));
+ EquivalenceClass *ec = pk->pk_eclass;
+ ListCell *lm;
+ EquivalenceMember *em;
+ Expr *em_expr = NULL;
+ Query *query = root->parse;
+
+ /*
+ * 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, expr))
+ {
+ 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 *expr;
+
+ sgc = (SortGroupClause *) lfirst(lsg);
+ expr = (Expr *) get_sortgroupclause_expr(sgc,
+ query->targetList);
+ if (equal(em->em_expr, expr))
+ {
+ Assert(sgc->tleSortGroupRef > 0);
+ sortgroupref = sgc->tleSortGroupRef;
+ break;
+ }
+ }
+
+ if (sortgroupref > 0)
+ break;
+ }
+
+ /*
+ * Since we searched in group_pathkeys, at least one EM of this EC
+ * should correspond to a SortGroupClause, otherwise the EC could
+ * not exist at all.
+ */
+ Assert(sortgroupref > 0);
+
+ return sortgroupref;
+ }
+
+ /* No EC found in group_pathkeys. */
+ return 0;
+ }
+
+ /*
* split_pathtarget_at_srfs
* Split given PathTarget into multiple levels to position SRFs safely
*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
new file mode 100644
index cbde1ff..49f87ac
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** get_rule_expr(Node *node, deparse_contex
*** 7567,7572 ****
--- 7567,7580 ----
get_agg_expr((Aggref *) node, context, (Aggref *) node);
break;
+ case T_GroupedVar:
+ {
+ GroupedVar *gvar = castNode(GroupedVar, node);
+
+ get_agg_expr(gvar->agg_partial, context, (Aggref *) gvar->gvexpr);
+ break;
+ }
+
case T_GroupingFunc:
{
GroupingFunc *gexpr = (GroupingFunc *) node;
*************** get_agg_combine_expr(Node *node, deparse
*** 9001,9010 ****
Aggref *aggref;
Aggref *original_aggref = private;
! if (!IsA(node, Aggref))
elog(ERROR, "combining Aggref does not point to an Aggref");
- aggref = (Aggref *) node;
get_agg_expr(aggref, context, original_aggref);
}
--- 9009,9026 ----
Aggref *aggref;
Aggref *original_aggref = private;
! if (IsA(node, Aggref))
! aggref = (Aggref *) node;
! else if (IsA(node, GroupedVar))
! {
! GroupedVar *gvar = castNode(GroupedVar, node);
!
! aggref = gvar->agg_partial;
! original_aggref = castNode(Aggref, gvar->gvexpr);
! }
! else
elog(ERROR, "combining Aggref does not point to an Aggref");
get_agg_expr(aggref, context, original_aggref);
}
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
new file mode 100644
index a35b93b..78e24ea
*** a/src/backend/utils/adt/selfuncs.c
--- b/src/backend/utils/adt/selfuncs.c
***************
*** 114,119 ****
--- 114,120 ----
#include "catalog/pg_statistic_ext.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
*** 3705,3710 ****
--- 3706,3744 ----
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.
+ *
+ * XXX this may be over-estimating the size now that hashagg knows to omit
+ * unneeded columns from the hashtable. Also for mixed-mode grouping sets,
+ * grouping columns not in the hashed set are counted here even though hashagg
+ * won't store them. Is this a problem?
+ */
+ 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/nodes/nodes.h b/src/include/nodes/nodes.h
new file mode 100644
index f59d719..ba1eac8
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 218,223 ****
--- 218,224 ----
T_IndexOptInfo,
T_ForeignKeyOptInfo,
T_ParamPathInfo,
+ T_GroupedPathInfo,
T_Path,
T_IndexPath,
T_BitmapHeapPath,
*************** typedef enum NodeTag
*** 258,267 ****
--- 259,270 ----
T_PathTarget,
T_RestrictInfo,
T_PlaceHolderVar,
+ T_GroupedVar,
T_SpecialJoinInfo,
T_AppendRelInfo,
T_PartitionedChildRelInfo,
T_PlaceHolderInfo,
+ T_GroupedVarInfo,
T_MinMaxAggInfo,
T_PlannerParamItem,
T_RollupData,
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
new file mode 100644
index 7a8e2fd..103ed14
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct PlannerInfo
*** 256,261 ****
--- 256,263 ----
List *placeholder_list; /* list of PlaceHolderInfos */
+ List *grouped_var_list; /* List of GroupedVarInfos. */
+
List *fkey_list; /* list of ForeignKeyOptInfos */
List *query_pathkeys; /* desired pathkeys for query_planner() */
*************** typedef struct PlannerInfo
*** 401,406 ****
--- 403,410 ----
* direct_lateral_relids - rels this rel has direct LATERAL references to
* lateral_relids - required outer rels for LATERAL, as a Relids set
* (includes both direct and indirect lateral references)
+ * gpi - GroupedPathInfo if the relation can produce grouped paths, NULL
+ * otherwise.
*
* If the relation is a base relation it will have these fields set:
*
*************** typedef struct RelOptInfo
*** 548,553 ****
--- 552,560 ----
Relids direct_lateral_relids; /* rels directly laterally referenced */
Relids lateral_relids; /* minimum parameterization of rel */
+ /* Information needed to produce grouped paths. */
+ struct GroupedPathInfo *gpi;
+
/* information about a base rel (not set for join rels!) */
Index relid;
Oid reltablespace; /* containing tablespace */
*************** typedef struct ParamPathInfo
*** 913,918 ****
--- 920,947 ----
List *ppi_clauses; /* join clauses available from outer rels */
} ParamPathInfo;
+ /*
+ * GroupedPathInfo
+ *
+ * If RelOptInfo points to this structure, grouped paths can be created for
+ * it.
+ *
+ * "target" will be used as pathtarget of grouped paths produced by this
+ * relation. Grouped path is either a result of aggregation of the relation
+ * that owns this structure or, if the owning relation is a join, a join path
+ * whose one side is a grouped path and the other is a plain (i.e. not
+ * grouped) one. (Two grouped paths cannot be joined in general because
+ * grouping of one side of the join essentially reduces occurrence of groups
+ * of the other side in the input of the final aggregation.)
+ */
+ typedef struct GroupedPathInfo
+ {
+ NodeTag type;
+
+ PathTarget *target; /* output of grouped paths. */
+ List *pathlist; /* List of grouped paths. */
+ List *partial_pathlist; /* List of partial grouped paths. */
+ } GroupedPathInfo;
/*
* Type "Path" is used as-is for sequential-scan paths, as well as some other
*************** typedef struct PlaceHolderVar
*** 1852,1857 ****
--- 1881,1919 ----
Index phlevelsup; /* > 0 if PHV belongs to outer query */
} PlaceHolderVar;
+
+ /*
+ * Similar to the concept of PlaceHolderVar, we treat aggregates and grouping
+ * columns as special variables if grouping is possible below the top-level
+ * join. The reason is that aggregates having start as the argument can be
+ * evaluated at various places in the join tree (i.e. cannot be assigned to
+ * target list of exactly one relation). Also this concept seems to be less
+ * invasive than adding the grouped vars to reltarget (in which case
+ * attr_needed and attr_widths arrays of RelOptInfo) would also need
+ * additional changes.
+ *
+ * gvexpr is a pointer to gvexpr field of the corresponding instance
+ * GroupedVarInfo. It's there for the sake of exprType(), exprCollation(),
+ * etc.
+ *
+ * agg_partial also points to the corresponding field of GroupedVarInfo if the
+ * GroupedVar is in the target of a parent relation (RELOPT_BASEREL). However
+ * within a child relation's (RELOPT_OTHER_MEMBER_REL) target it points to a
+ * copy which has argument expressions translated, so they no longer reference
+ * the parent.
+ *
+ * XXX Currently we only create GroupedVar for aggregates, but sometime we can
+ * do it for grouping keys as well. That would allow grouping below the
+ * top-level join by keys other than plain Var.
+ */
+ typedef struct GroupedVar
+ {
+ Expr xpr;
+ Expr *gvexpr; /* the represented expression */
+ Aggref *agg_partial; /* partial aggregate if gvexpr is aggregate */
+ Index gvid; /* GroupedVarInfo */
+ } GroupedVar;
+
/*
* "Special join" info.
*
*************** typedef struct PlaceHolderInfo
*** 2067,2072 ****
--- 2129,2150 ----
} PlaceHolderInfo;
/*
+ * Likewise, GroupedVarInfo exists for each distinct GroupedVar.
+ */
+ typedef struct GroupedVarInfo
+ {
+ NodeTag type;
+
+ Index gvid; /* GroupedVar.gvid */
+ Expr *gvexpr; /* the represented expression. */
+ Aggref *agg_partial; /* if gvexpr is aggregate, agg_partial is
+ * the corresponding partial aggregate */
+ Relids gv_eval_at; /* lowest level we can evaluate the expression
+ * at or NULL if it can happen anywhere. */
+ int32 gv_width; /* estimated width of the expression */
+ } 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 77bc770..abc2ac1
*** 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 NestPath *create_nestloop_path(Pl
*** 124,130 ****
Path *inner_path,
List *restrict_clauses,
List *pathkeys,
! Relids required_outer);
extern MergePath *create_mergejoin_path(PlannerInfo *root,
RelOptInfo *joinrel,
--- 126,133 ----
Path *inner_path,
List *restrict_clauses,
List *pathkeys,
! Relids required_outer,
! PathTarget *target);
extern MergePath *create_mergejoin_path(PlannerInfo *root,
RelOptInfo *joinrel,
*************** extern MergePath *create_mergejoin_path(
*** 138,144 ****
Relids required_outer,
List *mergeclauses,
List *outersortkeys,
! List *innersortkeys);
extern HashPath *create_hashjoin_path(PlannerInfo *root,
RelOptInfo *joinrel,
--- 141,148 ----
Relids required_outer,
List *mergeclauses,
List *outersortkeys,
! List *innersortkeys,
! PathTarget *target);
extern HashPath *create_hashjoin_path(PlannerInfo *root,
RelOptInfo *joinrel,
*************** extern HashPath *create_hashjoin_path(Pl
*** 149,155 ****
Path *inner_path,
List *restrict_clauses,
Relids required_outer,
! List *hashclauses);
extern ProjectionPath *create_projection_path(PlannerInfo *root,
RelOptInfo *rel,
--- 153,160 ----
Path *inner_path,
List *restrict_clauses,
Relids required_outer,
! List *hashclauses,
! PathTarget *target);
extern ProjectionPath *create_projection_path(PlannerInfo *root,
RelOptInfo *rel,
*************** extern AggPath *create_agg_path(PlannerI
*** 190,195 ****
--- 195,214 ----
List *qual,
const AggClauseCosts *aggcosts,
double numGroups);
+ extern AggPath *create_partial_agg_sorted_path(PlannerInfo *root,
+ Path *subpath,
+ bool first_call,
+ List **group_clauses,
+ List **group_exprs,
+ List **agg_exprs,
+ double input_rows);
+ extern AggPath *create_partial_agg_hashed_path(PlannerInfo *root,
+ Path *subpath,
+ bool first_call,
+ List **group_clauses,
+ List **group_exprs,
+ List **agg_exprs,
+ double input_rows);
extern GroupingSetsPath *create_groupingsets_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
*************** extern ParamPathInfo *get_joinrel_paramp
*** 285,289 ****
--- 304,309 ----
List **restrict_clauses);
extern ParamPathInfo *get_appendrel_parampathinfo(RelOptInfo *appendrel,
Relids required_outer);
+ extern void prepare_rel_for_grouping(PlannerInfo *root, RelOptInfo *rel);
#endif /* PATHNODE_H */
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
new file mode 100644
index 25fe78c..38967da
*** a/src/include/optimizer/paths.h
--- b/src/include/optimizer/paths.h
*************** extern void set_dummy_rel_pathlist(RelOp
*** 53,59 ****
extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,
List *initial_rels);
! extern void generate_gather_paths(PlannerInfo *root, RelOptInfo *rel);
extern int compute_parallel_worker(RelOptInfo *rel, double heap_pages,
double index_pages);
extern void create_partial_bitmap_paths(PlannerInfo *root, RelOptInfo *rel,
--- 53,64 ----
extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,
List *initial_rels);
! extern void generate_gather_paths(PlannerInfo *root, RelOptInfo *rel,
! bool grouped);
!
! extern void create_grouped_path(PlannerInfo *root, RelOptInfo *rel,
! Path *subpath, bool precheck, bool partial,
! AggStrategy aggstrategy);
extern int compute_parallel_worker(RelOptInfo *rel, double heap_pages,
double index_pages);
extern void create_partial_bitmap_paths(PlannerInfo *root, RelOptInfo *rel,
*************** extern void debug_print_rel(PlannerInfo
*** 67,73 ****
* indxpath.c
* routines to generate index paths
*/
! extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel);
extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
List *restrictlist,
List *exprlist, List *oprlist);
--- 72,79 ----
* indxpath.c
* routines to generate index paths
*/
! extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel,
! bool grouped);
extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
List *restrictlist,
List *exprlist, List *oprlist);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
new file mode 100644
index 5df68a2..07bc4c0
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
*************** extern int join_collapse_limit;
*** 74,80 ****
extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
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 find_lateral_references(PlannerInfo *root);
extern void create_lateral_join_info(PlannerInfo *root);
extern List *deconstruct_jointree(PlannerInfo *root);
--- 74,82 ----
extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
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 add_grouping_info_to_base_rels(PlannerInfo *root);
! extern void add_grouped_vars_to_rels(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 ccb93d8..ddea03c
*** a/src/include/optimizer/tlist.h
--- b/src/include/optimizer/tlist.h
*************** extern Node *get_sortgroupclause_expr(So
*** 41,46 ****
--- 41,49 ----
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
List *targetList);
+ extern void get_grouping_expressions(PlannerInfo *root, PathTarget *target,
+ List **grouping_clauses,
+ List **grouping_exprs, List **agg_exprs);
extern SortGroupClause *get_sortgroupref_clause(Index sortref,
List *clauses);
*************** extern void split_pathtarget_at_srfs(Pla
*** 65,70 ****
--- 68,84 ----
PathTarget *target, PathTarget *input_target,
List **targets, List **targets_contain_srfs);
+ /* TODO Find the best location (position and in some cases even file) for the
+ * following ones. */
+ extern List *restore_grouping_expressions(PlannerInfo *root, List *src);
+ extern List *add_aggregates_to_target(PlannerInfo *root, PathTarget *target,
+ List *aggregates, RelOptInfo *rel);
+ extern Index get_expr_sortgroupref(PlannerInfo *root, Expr *expr);
+ /* TODO Move definition from initsplan.c to tlist.c. */
+ extern PathTarget *create_grouped_target(PlannerInfo *root, RelOptInfo *rel,
+ Relids rel_agg_attrs,
+ List *rel_agg_vars);
+
/* 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/selfuncs.h b/src/include/utils/selfuncs.h
new file mode 100644
index 9f9d2dc..e05e6f6
*** a/src/include/utils/selfuncs.h
--- b/src/include/utils/selfuncs.h
*************** extern double estimate_num_groups(Planne
*** 206,211 ****
--- 206,214 ----
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,