Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtp (Exim 4.84_2) (envelope-from ) id 1d41Xx-0006c4-Ih for pgsql-hackers@arkaria.postgresql.org; Fri, 28 Apr 2017 08:46:38 +0000 Received: from localhost ([127.0.0.1] helo=postgresql.org) by malur.postgresql.org with smtp (Exim 4.84_2) (envelope-from ) id 1d41Xw-0000PL-Qi for pgsql-hackers@arkaria.postgresql.org; Fri, 28 Apr 2017 08:46:36 +0000 Received: from makus.postgresql.org ([2001:4800:1501:1::229]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.84_2) (envelope-from ) id 1d41Xr-0000Kq-GM for pgsql-hackers@postgresql.org; Fri, 28 Apr 2017 08:46:32 +0000 Received: from mx2.mailbox.org ([80.241.60.215]) by makus.postgresql.org with esmtps (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.84_2) (envelope-from ) id 1d41Xi-0007ZY-0A for pgsql-hackers@postgresql.org; Fri, 28 Apr 2017 08:46:30 +0000 Received: from smtp1.mailbox.org (smtp1.mailbox.org [80.241.60.240]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx2.mailbox.org (Postfix) with ESMTPS id F085544AB2 for ; Fri, 28 Apr 2017 10:46:17 +0200 (CEST) X-Virus-Scanned: amavisd-new at heinlein-support.de Received: from smtp1.mailbox.org ([80.241.60.240]) by hefe.heinlein-support.de (hefe.heinlein-support.de [91.198.250.172]) (amavisd-new, port 10030) with ESMTP id rCKRsXBxQZo0 for ; Fri, 28 Apr 2017 10:46:10 +0200 (CEST) From: Antonin Houska To: pgsql-hackers@postgresql.org Subject: Re: WIP: Aggregation push-down In-reply-to: <9666.1491295317@localhost> References: <9666.1491295317@localhost> Comments: In-reply-to Antonin Houska message dated "Tue, 04 Apr 2017 10:41:57 +0200." MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Date: Fri, 28 Apr 2017 10:46:09 +0200 Message-ID: <8160.1493369169@localhost> X-Pg-Spam-Score: -2.6 (--) List-Archive: List-Help: List-ID: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: X-Mailing-List: pgsql-hackers Precedence: bulk Sender: pgsql-hackers-owner@postgresql.org --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Antonin Houska wrote: > This is a new version of the patch I presented in [1]. Rebased. cat .git/refs/heads/master=20 b9a3ef55b253d885081c2d0e9dc45802cab71c7b --=20 Antonin Houska Cybertec Sch=C3=B6nig & Sch=C3=B6nig GmbH Gr=C3=B6hrm=C3=BChlgasse 26 A-2700 Wiener Neustadt Web: http://www.postgresql-support.de, http://www.cybertec.at --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=agg_pushdown_v2.diff 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, --=-=-= Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --=-=-=--