agora inbox for [email protected]
help / color / mirror / Atom feedFrom: Antonin Houska <[email protected]>
To: [email protected]
Subject: Re: [HACKERS] WIP: Aggregation push-down
Date: Fri, 22 Dec 2017 16:43:57 +0100
Message-ID: <18007.1513957437@localhost> (raw)
In-Reply-To: <CAB7nPqQgB=3ZpuvSm8hVXDcPz79kn2ZGsyyoDrmQsv-B3LDMyQ@mail.gmail.com>
References: <9666.1491295317@localhost>
<8160.1493369169@localhost>
<29613.1502983342@localhost>
<14577.1509723225@localhost>
<CAB7nPqQgB=3ZpuvSm8hVXDcPz79kn2ZGsyyoDrmQsv-B3LDMyQ@mail.gmail.com>
Michael Paquier <[email protected]> wrote:
> On Sat, Nov 4, 2017 at 12:33 AM, Antonin Houska <[email protected]> wrote:
> > I'm not about to add any other features now. Implementation of the missing
> > parts (see the TODO comments in the code) is the next step. But what I'd
> > appreciate most is a feedback on the design. Thanks.
>
> I am getting a conflict after applying patch 5 but this did not get
> any reviews so moved to next CF with waiting on author as status.
Attached is the next version.
--
Antonin Houska
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt
Web: http://www.postgresql-support.de, http://www.cybertec.at
Attachments:
[application/x-gzip] agg_pushdown_v5.tgz (118.4K, ../18007.1513957437@localhost/2-agg_pushdown_v5.tgz)
download | 11 patch file(s) in archive:
agg_pushdown_v5/01_new_types.diff
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index 96d1fc3..85e8ea3
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyAggref(const Aggref *from)
*** 1361,1366 ****
--- 1361,1367 ----
COPY_SCALAR_FIELD(aggcollid);
COPY_SCALAR_FIELD(inputcollid);
COPY_SCALAR_FIELD(aggtranstype);
+ COPY_SCALAR_FIELD(aggcombinefn);
COPY_NODE_FIELD(aggargtypes);
COPY_NODE_FIELD(aggdirectargs);
COPY_NODE_FIELD(args);
*************** _copyPlaceHolderVar(const PlaceHolderVar
*** 2213,2218 ****
--- 2214,2235 ----
}
/*
+ * _copyGroupedVar
+ */
+ static GroupedVar *
+ _copyGroupedVar(const GroupedVar *from)
+ {
+ GroupedVar *newnode = makeNode(GroupedVar);
+
+ COPY_NODE_FIELD(gvexpr);
+ COPY_NODE_FIELD(agg_partial);
+ COPY_SCALAR_FIELD(sortgroupref);
+ COPY_SCALAR_FIELD(gvid);
+
+ return newnode;
+ }
+
+ /*
* _copySpecialJoinInfo
*/
static SpecialJoinInfo *
*************** _copyPlaceHolderInfo(const PlaceHolderIn
*** 2285,2290 ****
--- 2302,2322 ----
return newnode;
}
+ static GroupedVarInfo *
+ _copyGroupedVarInfo(const GroupedVarInfo *from)
+ {
+ GroupedVarInfo *newnode = makeNode(GroupedVarInfo);
+
+ COPY_SCALAR_FIELD(gvid);
+ COPY_NODE_FIELD(gvexpr);
+ COPY_NODE_FIELD(agg_partial);
+ COPY_SCALAR_FIELD(sortgroupref);
+ COPY_SCALAR_FIELD(gv_eval_at);
+ COPY_SCALAR_FIELD(gv_width);
+
+ return newnode;
+ }
+
/* ****************************************************************
* parsenodes.h copy functions
* ****************************************************************
*************** copyObjectImpl(const void *from)
*** 5020,5025 ****
--- 5052,5060 ----
case T_PlaceHolderVar:
retval = _copyPlaceHolderVar(from);
break;
+ case T_GroupedVar:
+ retval = _copyGroupedVar(from);
+ break;
case T_SpecialJoinInfo:
retval = _copySpecialJoinInfo(from);
break;
*************** copyObjectImpl(const void *from)
*** 5032,5037 ****
--- 5067,5075 ----
case T_PlaceHolderInfo:
retval = _copyPlaceHolderInfo(from);
break;
+ case T_GroupedVarInfo:
+ retval = _copyGroupedVarInfo(from);
+ break;
/*
* VALUE NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index 2e869a9..3d735a2
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalPlaceHolderVar(const PlaceHolderVa
*** 873,878 ****
--- 873,886 ----
}
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)
*** 3171,3176 ****
--- 3179,3187 ----
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 c2a93b2..9a53aa7
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** exprType(const Node *expr)
*** 259,264 ****
--- 259,270 ----
case T_PlaceHolderVar:
type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr);
break;
+ case T_GroupedVar:
+ if (IsA(((const GroupedVar *) expr)->gvexpr, Aggref))
+ type = exprType((Node *) ((const GroupedVar *) expr)->agg_partial);
+ else
+ type = exprType((Node *) ((const GroupedVar *) expr)->gvexpr);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
*************** exprTypmod(const Node *expr)
*** 492,497 ****
--- 498,508 ----
return ((const SetToDefault *) expr)->typeMod;
case T_PlaceHolderVar:
return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr);
+ case T_GroupedVar:
+ if (IsA(((const GroupedVar *) expr)->gvexpr, Aggref))
+ return exprTypmod((Node *) ((const GroupedVar *) expr)->agg_partial);
+ else
+ return exprTypmod((Node *) ((const GroupedVar *) expr)->gvexpr);
default:
break;
}
*************** exprCollation(const Node *expr)
*** 903,908 ****
--- 914,925 ----
case T_PlaceHolderVar:
coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr);
break;
+ case T_GroupedVar:
+ if (IsA(((const GroupedVar *) expr)->gvexpr, Aggref))
+ coll = exprCollation((Node *) ((const GroupedVar *) expr)->agg_partial);
+ else
+ 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,
*** 2176,2181 ****
--- 2193,2200 ----
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,
*** 2968,2973 ****
--- 2987,3002 ----
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 e468d7c..d70d210
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outAggref(StringInfo str, const Aggref
*** 1136,1141 ****
--- 1136,1142 ----
WRITE_OID_FIELD(aggcollid);
WRITE_OID_FIELD(inputcollid);
WRITE_OID_FIELD(aggtranstype);
+ WRITE_OID_FIELD(aggcombinefn);
WRITE_NODE_FIELD(aggargtypes);
WRITE_NODE_FIELD(aggdirectargs);
WRITE_NODE_FIELD(args);
*************** _outPlannerInfo(StringInfo str, const Pl
*** 2225,2230 ****
--- 2226,2232 ----
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);
*************** _outPlannerInfo(StringInfo str, const Pl
*** 2232,2237 ****
--- 2234,2240 ----
WRITE_NODE_FIELD(distinct_pathkeys);
WRITE_NODE_FIELD(sort_pathkeys);
WRITE_NODE_FIELD(processed_tlist);
+ WRITE_INT_FIELD(max_sortgroupref);
WRITE_NODE_FIELD(minmax_aggs);
WRITE_FLOAT_FIELD(total_table_pages, "%.0f");
WRITE_FLOAT_FIELD(tuple_fraction, "%.4f");
*************** _outRelOptInfo(StringInfo str, const Rel
*** 2271,2276 ****
--- 2274,2280 ----
WRITE_NODE_FIELD(cheapest_parameterized_paths);
WRITE_BITMAPSET_FIELD(direct_lateral_relids);
WRITE_BITMAPSET_FIELD(lateral_relids);
+ WRITE_NODE_FIELD(gpi);
WRITE_UINT_FIELD(relid);
WRITE_OID_FIELD(reltablespace);
WRITE_ENUM_FIELD(rtekind, RTEKind);
*************** _outParamPathInfo(StringInfo str, const
*** 2447,2452 ****
--- 2451,2468 ----
}
static void
+ _outGroupedPathInfo(StringInfo str, const GroupedPathInfo *node)
+ {
+ WRITE_NODE_TYPE("GROUPEDPATHINFO");
+
+ WRITE_NODE_FIELD(target);
+ WRITE_NODE_FIELD(group_exprs);
+ WRITE_NODE_FIELD(pathlist);
+ WRITE_NODE_FIELD(partial_pathlist);
+ WRITE_NODE_FIELD(sortgroupclauses);
+ }
+
+ static void
_outRestrictInfo(StringInfo str, const RestrictInfo *node)
{
WRITE_NODE_TYPE("RESTRICTINFO");
*************** _outPlaceHolderVar(StringInfo str, const
*** 2490,2495 ****
--- 2506,2522 ----
}
static void
+ _outGroupedVar(StringInfo str, const GroupedVar *node)
+ {
+ WRITE_NODE_TYPE("GROUPEDVAR");
+
+ WRITE_NODE_FIELD(gvexpr);
+ WRITE_NODE_FIELD(agg_partial);
+ WRITE_UINT_FIELD(sortgroupref);
+ WRITE_UINT_FIELD(gvid);
+ }
+
+ static void
_outSpecialJoinInfo(StringInfo str, const SpecialJoinInfo *node)
{
WRITE_NODE_TYPE("SPECIALJOININFO");
*************** _outPlaceHolderInfo(StringInfo str, cons
*** 2543,2548 ****
--- 2570,2588 ----
}
static void
+ _outGroupedVarInfo(StringInfo str, const GroupedVarInfo *node)
+ {
+ WRITE_NODE_TYPE("GROUPEDVARINFO");
+
+ WRITE_UINT_FIELD(gvid);
+ WRITE_NODE_FIELD(gvexpr);
+ WRITE_NODE_FIELD(agg_partial);
+ WRITE_UINT_FIELD(sortgroupref);
+ WRITE_BITMAPSET_FIELD(gv_eval_at);
+ WRITE_INT_FIELD(gv_width);
+ }
+
+ static void
_outMinMaxAggInfo(StringInfo str, const MinMaxAggInfo *node)
{
WRITE_NODE_TYPE("MINMAXAGGINFO");
*************** outNode(StringInfo str, const void *obj)
*** 4045,4056 ****
--- 4085,4102 ----
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;
*************** outNode(StringInfo str, const void *obj)
*** 4063,4068 ****
--- 4109,4117 ----
case T_PlaceHolderInfo:
_outPlaceHolderInfo(str, obj);
break;
+ case T_GroupedVarInfo:
+ _outGroupedVarInfo(str, obj);
+ break;
case T_MinMaxAggInfo:
_outMinMaxAggInfo(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
new file mode 100644
index 1133c70..f02a963
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readVar(void)
*** 529,534 ****
--- 529,550 ----
}
/*
+ * _readGroupedVar
+ */
+ static GroupedVar *
+ _readGroupedVar(void)
+ {
+ READ_LOCALS(GroupedVar);
+
+ READ_NODE_FIELD(gvexpr);
+ READ_NODE_FIELD(agg_partial);
+ READ_UINT_FIELD(sortgroupref);
+ READ_UINT_FIELD(gvid);
+
+ READ_DONE();
+ }
+
+ /*
* _readConst
*/
static Const *
*************** _readAggref(void)
*** 584,589 ****
--- 600,606 ----
READ_OID_FIELD(aggcollid);
READ_OID_FIELD(inputcollid);
READ_OID_FIELD(aggtranstype);
+ READ_OID_FIELD(aggcombinefn);
READ_NODE_FIELD(aggargtypes);
READ_NODE_FIELD(aggdirectargs);
READ_NODE_FIELD(args);
*************** parseNodeString(void)
*** 2472,2477 ****
--- 2489,2496 ----
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/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
new file mode 100644
index f4e0a6e..2e6a60e
*** a/src/backend/optimizer/plan/planmain.c
--- b/src/backend/optimizer/plan/planmain.c
*************** 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;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 382791f..5a3efbd
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** subquery_planner(PlannerGlobal *glob, Qu
*** 544,549 ****
--- 544,550 ----
memset(root->upper_rels, 0, sizeof(root->upper_rels));
memset(root->upper_targets, 0, sizeof(root->upper_targets));
root->processed_tlist = NIL;
+ root->max_sortgroupref = 0;
root->grouping_map = NULL;
root->minmax_aggs = NIL;
root->qual_security_level = 0;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
new file mode 100644
index 1d7e499..710f028
*** a/src/backend/optimizer/prep/prepjointree.c
--- b/src/backend/optimizer/prep/prepjointree.c
*************** pull_up_simple_subquery(PlannerInfo *roo
*** 911,916 ****
--- 911,917 ----
memset(subroot->upper_rels, 0, sizeof(subroot->upper_rels));
memset(subroot->upper_targets, 0, sizeof(subroot->upper_targets));
subroot->processed_tlist = NIL;
+ subroot->max_sortgroupref = 0;
subroot->grouping_map = NULL;
subroot->minmax_aggs = NIL;
subroot->qual_security_level = 0;
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
new file mode 100644
index e6b0856..7a98753
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 98,103 ****
--- 98,104 ----
Oid vatype;
FuncDetailCode fdresult;
char aggkind = 0;
+ Oid aggcombinefn = InvalidOid;
ParseCallbackState pcbstate;
/*
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 350,355 ****
--- 351,357 ----
elog(ERROR, "cache lookup failed for aggregate %u", funcid);
classForm = (Form_pg_aggregate) GETSTRUCT(tup);
aggkind = classForm->aggkind;
+ aggcombinefn = classForm->aggcombinefn;
catDirectArgs = classForm->aggnumdirectargs;
ReleaseSysCache(tup);
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 695,700 ****
--- 697,703 ----
aggref->aggstar = agg_star;
aggref->aggvariadic = func_variadic;
aggref->aggkind = aggkind;
+ aggref->aggcombinefn = aggcombinefn;
/* agglevelsup will be set by transformAggregateCall */
aggref->aggsplit = AGGSPLIT_SIMPLE; /* planner might change this */
aggref->location = location;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
new file mode 100644
index c5b5115..86693d1
*** 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/primnodes.h b/src/include/nodes/primnodes.h
new file mode 100644
index 074ae0a..97fd887
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
*************** typedef struct Aggref
*** 296,301 ****
--- 296,302 ----
Oid aggcollid; /* OID of collation of result */
Oid inputcollid; /* OID of collation that function should use */
Oid aggtranstype; /* type Oid of aggregate's transition value */
+ Oid aggcombinefn; /* combine function (see pg_aggregate.h) */
List *aggargtypes; /* type Oids of direct and aggregated args */
List *aggdirectargs; /* direct arguments, if an ordered-set agg */
List *args; /* aggregated arguments and sort expressions */
*************** typedef struct Aggref
*** 306,311 ****
--- 307,313 ----
bool aggvariadic; /* true if variadic arguments have been
* combined into an array last argument */
char aggkind; /* aggregate kind (see pg_aggregate.h) */
+
Index agglevelsup; /* > 0 if agg belongs to outer query */
AggSplit aggsplit; /* expected agg-splitting mode of parent Agg */
int location; /* token location, or -1 if unknown */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
new file mode 100644
index 3b9d303..1c93688
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct PlannerInfo
*** 257,262 ****
--- 257,264 ----
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
*** 283,288 ****
--- 285,296 ----
*/
List *processed_tlist;
+ /*
+ * The maximum ressortgroupref among target entries in processed_list.
+ * Useful when adding extra grouping expressions for partial aggregation.
+ */
+ int max_sortgroupref;
+
/* Fields filled during create_plan() for use in setrefs.c */
AttrNumber *grouping_map; /* for GroupingFunc fixup */
List *minmax_aggs; /* List of MinMaxAggInfos */
*************** typedef struct PartitionSchemeData *Part
*** 438,443 ****
--- 446,453 ----
* 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
*** 609,614 ****
--- 619,627 ----
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
*** 1003,1008 ****
--- 1016,1065 ----
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 either by
+ * "explicit aggregation" of the relation that owns this structure, or --- if
+ * the relation is a join --- by joining grouped path to a non-grouped
+ * one.
+ *
+ * The target contains plain-Var grouping expressions, generic grouping
+ * expressions wrapped in GroupedVar structure, or Aggrefs which are also
+ * wrapped in GroupedVar. Once GroupedVar is evaluated, its value is passed to
+ * the upper paths w/o being evaluated again. If final aggregation appears to
+ * be necessary above the final join, the contained Aggrefs are supposed to
+ * provide the final aggregation plan with input values, i.e. the aggregate
+ * transient state.
+ *
+ * Note: There's a convention that GroupedVars that contain Aggref expressions
+ * are supposed to follow the other expressions of the target. Iterations of
+ * target->exprs may rely on this arrangement.
+ *
+ * "group_exprs" contains grouping expressions which are not plain vars. These
+ * are only added to path target of a path which should generate input for
+ * partial aggregation.
+ *
+ * "sortgroupclauses" is a list of grouping clauses that the relation does
+ * have in its targetlist but the query does not.
+ *
+ * (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; /* target of grouped paths aggregation. */
+ PathTarget *group_exprs; /* non-Var grouping expressions. */
+ List *pathlist; /* List of grouped paths. */
+ List *partial_pathlist; /* List of partial grouped paths. */
+ List *sortgroupclauses; /* Relation-specific grouping clauses. */
+ } GroupedPathInfo;
/*
* Type "Path" is used as-is for sequential-scan paths, as well as some other
*************** typedef struct PlaceHolderVar
*** 1950,1955 ****
--- 2007,2047 ----
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 sortgroupref; /* SortGroupClause.tleSortGroupRef if gvexpr
+ * is grouping expression. */
+ Index gvid; /* GroupedVarInfo */
+ } GroupedVar;
+
/*
* "Special join" info.
*
*************** typedef struct PlaceHolderInfo
*** 2165,2170 ****
--- 2257,2281 ----
} 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 */
+ Index sortgroupref; /* If gvexpr is a grouping expression, this is
+ * the tleSortGroupRef of the corresponding
+ * SortGroupClause. */
+ 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.
agg_pushdown_v5/02_preprocess.diff
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
new file mode 100644
index 45a6889..bd22449
*** a/src/backend/optimizer/path/equivclass.c
--- b/src/backend/optimizer/path/equivclass.c
*************** static bool reconsider_outer_join_clause
*** 65,70 ****
--- 65,83 ----
static bool reconsider_full_join_clause(PlannerInfo *root,
RestrictInfo *rinfo);
+ typedef struct translate_expr_context
+ {
+ Var **keys; /* Dictionary keys. */
+ Var **values; /* Dictionary values */
+ int nitems; /* Number of dictionary items. */
+ Relids *gv_eval_at_p; /* See GroupedVarInfo. */
+ Relids relids; /* Translate into these relids. */
+ } translate_expr_context;
+
+ static Node *translate_expression_to_rels_mutator(Node *node,
+ translate_expr_context *context);
+ static int var_dictionary_comparator(const void *a, const void *b);
+
/*
* process_equivalence
*************** is_redundant_derived_clause(RestrictInfo
*** 2510,2512 ****
--- 2523,2844 ----
return false;
}
+
+ /*
+ * translate_expression_to_rels
+ *
+ * If the appropriate equivalence classes exist, replace vars in
+ * gvi->gvexpr with vars whose varno is contained in relids.
+ */
+ GroupedVarInfo *
+ translate_expression_to_rels(PlannerInfo *root, GroupedVarInfo *gvi,
+ Relids relids)
+ {
+ List *vars;
+ ListCell *l1;
+ int i,
+ j;
+ int nkeys,
+ nkeys_resolved;
+ Var **keys,
+ **values,
+ **keys_tmp;
+ Var *key,
+ *key_prev;
+ translate_expr_context context;
+ GroupedVarInfo *result;
+
+ /* Can't do anything w/o equivalence classes. */
+ if (root->eq_classes == NIL)
+ return NULL;
+
+ /*
+ * Before actually trying to modify the expression tree, find out if all
+ * vars can be translated.
+ */
+ vars = pull_var_clause((Node *) gvi->gvexpr, PVC_RECURSE_AGGREGATES);
+
+ /* No vars to translate? */
+ if (vars == NIL)
+ return NULL;
+
+ /*
+ * Search for individual replacement vars as well as the actual expression
+ * translation will be more efficient if we use a dictionary with the keys
+ * (i.e. the "source vars") unique and sorted.
+ */
+ nkeys = list_length(vars);
+ keys = (Var **) palloc(nkeys * sizeof(Var *));
+ i = 0;
+ foreach(l1, vars)
+ {
+ key = lfirst_node(Var, l1);
+ keys[i++] = key;
+ }
+
+ /*
+ * Sort the keys by varno. varattno decides where varnos are equal.
+ */
+ pg_qsort(keys, nkeys, sizeof(Var *), var_dictionary_comparator);
+
+ /*
+ * Pick unique values and get rid of the vars that need no translation.
+ */
+ keys_tmp = (Var **) palloc(nkeys * sizeof(Var *));
+ key_prev = NULL;
+ j = 0;
+ for (i = 0; i < nkeys; i++)
+ {
+ key = keys[i];
+
+ if ((key_prev == NULL || (key->varno != key_prev->varno &&
+ key->varattno != key_prev->varattno)) &&
+ !bms_is_member(key->varno, relids))
+ keys_tmp[j++] = key;
+
+ key_prev = key;
+ }
+ pfree(keys);
+ keys = keys_tmp;
+ nkeys = j;
+
+ /*
+ * Is there actually nothing to be translated?
+ */
+ if (nkeys == 0)
+ {
+ pfree(keys);
+ return NULL;
+ }
+
+ nkeys_resolved = 0;
+
+ /*
+ * Find the replacement vars.
+ */
+ values = (Var **) palloc0(nkeys * sizeof(Var *));
+ foreach(l1, root->eq_classes)
+ {
+ EquivalenceClass *ec = lfirst_node(EquivalenceClass, l1);
+ Relids ec_var_relids;
+ Var **ec_vars;
+ int ec_nvars;
+ ListCell *l2;
+
+ /* TODO Re-check if any other EC kind should be ignored. */
+ if (ec->ec_has_volatile || ec->ec_below_outer_join || ec->ec_broken)
+ continue;
+
+ /* Single-element EC can hardly help in translations. */
+ if (list_length(ec->ec_members) == 1)
+ continue;
+
+ /*
+ * Collect all vars of this EC and their varnos.
+ *
+ * ec->ec_relids does not help because we're only interested in a
+ * subset of EC members.
+ */
+ ec_vars = (Var **) palloc(list_length(ec->ec_members) * sizeof(Var *));
+ ec_nvars = 0;
+ ec_var_relids = NULL;
+ foreach(l2, ec->ec_members)
+ {
+ EquivalenceMember *em = lfirst_node(EquivalenceMember, l2);
+ Var *ec_var;
+
+ if (!IsA(em->em_expr, Var))
+ continue;
+
+ ec_var = castNode(Var, em->em_expr);
+ ec_vars[ec_nvars++] = ec_var;
+ ec_var_relids = bms_add_member(ec_var_relids, ec_var->varno);
+ }
+
+ /*
+ * At least two vars are needed so that the EC is usable for
+ * translation.
+ */
+ if (ec_nvars <= 1)
+ {
+ pfree(ec_vars);
+ bms_free(ec_var_relids);
+ continue;
+ }
+
+ /*
+ * Now check where this EC can help.
+ */
+ for (i = 0; i < nkeys; i++)
+ {
+ Relids ec_rest;
+ bool relids_ok,
+ key_found;
+ Var *key = keys[i];
+ Var *value = values[i];
+
+ /* Skip this item if it's already resolved. */
+ if (value != NULL)
+ continue;
+
+ /*
+ * Can't translate if the EC does not mention key->varno.
+ */
+ if (!bms_is_member(key->varno, ec_var_relids))
+ continue;
+
+ /*
+ * Besides key, at least one EC member must belong to the relation
+ * we're translating our expression to.
+ */
+ ec_rest = bms_copy(ec_var_relids);
+ ec_rest = bms_del_member(ec_rest, key->varno);
+ relids_ok = bms_overlap(ec_rest, relids);
+ bms_free(ec_rest);
+ if (!relids_ok)
+ continue;
+
+ /*
+ * The preliminary checks passed, so try to find the exact vars.
+ */
+ key_found = false;
+ for (j = 0; j < ec_nvars; j++)
+ {
+ Var *ec_var = ec_vars[j];
+
+ if (!key_found && key->varno == ec_var->varno &&
+ key->varattno == ec_var->varattno)
+ key_found = true;
+
+ /*
+ * If relids contains multiple members, it shouldn't matter to
+ * which one we translate our key. Simply use the first one.
+ *
+ * XXX Shouldn't ec_var be copied?
+ */
+ if (value == NULL && bms_is_member(ec_var->varno, relids))
+ value = ec_var;
+
+ if (key_found && value != NULL)
+ break;
+ }
+
+ if (key_found && value != NULL)
+ {
+ values[i] = value;
+ nkeys_resolved++;
+
+ if (nkeys_resolved == nkeys)
+ break;
+ }
+ }
+
+ pfree(ec_vars);
+ bms_free(ec_var_relids);
+
+ /* Don't need to check the remaining ECs? */
+ if (nkeys_resolved == nkeys)
+ break;
+ }
+
+ /* Couldn't compose usable dictionary? */
+ if (nkeys_resolved < nkeys)
+ {
+ pfree(keys);
+ pfree(values);
+ return NULL;
+ }
+
+ result = makeNode(GroupedVarInfo);
+ memcpy(result, gvi, sizeof(GroupedVarInfo));
+
+ /*
+ * translate_expression_to_rels_mutator updates gv_eval_at.
+ */
+ result->gv_eval_at = bms_copy(result->gv_eval_at);
+
+ /* The dictionary is ready, so perform the translation. */
+ context.keys = keys;
+ context.values = values;
+ context.nitems = nkeys;
+ context.gv_eval_at_p = &result->gv_eval_at;
+ context.relids = relids;
+ result->gvexpr = (Expr *)
+ translate_expression_to_rels_mutator((Node *) gvi->gvexpr, &context);
+
+ pfree(keys);
+ pfree(values);
+ return result;
+ }
+
+ static Node *
+ translate_expression_to_rels_mutator(Node *node,
+ translate_expr_context *context)
+ {
+ if (node == NULL)
+ return NULL;
+
+ if (IsA(node, Var))
+ {
+ Var *var = castNode(Var, node);
+ Var **key_p;
+ Var *value;
+ int index;
+
+ /*
+ * Simply return the existing variable if already belongs to the
+ * relation we're adjusting the expression to.
+ */
+ if (bms_is_member(var->varno, context->relids))
+ return (Node *) var;
+
+ key_p = bsearch(&var, context->keys, context->nitems, sizeof(Var *),
+ var_dictionary_comparator);
+
+ /* We shouldn't have omitted any var from the dictionary. */
+ Assert(key_p != NULL);
+
+ index = key_p - context->keys;
+ Assert(index >= 0 && index < context->nitems);
+ value = context->values[index];
+
+ /* All values should be present in the dictionary. */
+ Assert(value != NULL);
+
+ /* Update gv_eval_at accordingly. */
+ bms_del_member(*context->gv_eval_at_p, var->varno);
+ *context->gv_eval_at_p = bms_add_member(*context->gv_eval_at_p,
+ value->varno);
+
+ return (Node *) value;
+ }
+
+ return expression_tree_mutator(node, translate_expression_to_rels_mutator,
+ (void *) context);
+ }
+
+ static int
+ var_dictionary_comparator(const void *a, const void *b)
+ {
+ Var **var1_p,
+ **var2_p;
+ Var *var1,
+ *var2;
+
+ var1_p = (Var **) a;
+ var1 = castNode(Var, *var1_p);
+ var2_p = (Var **) b;
+ var2 = castNode(Var, *var2_p);
+
+ if (var1->varno < var2->varno)
+ return -1;
+ else if (var1->varno > var2->varno)
+ return 1;
+
+ if (var1->varattno < var2->varattno)
+ return -1;
+ else if (var1->varattno > var2->varattno)
+ return 1;
+
+ return 0;
+ }
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
new file mode 100644
index 448cb73..8de59fc
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
***************
*** 14,21 ****
--- 14,23 ----
*/
#include "postgres.h"
+ #include "access/sysattr.h"
#include "catalog/pg_type.h"
#include "catalog/pg_class.h"
+ #include "catalog/pg_constraint_fn.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
***************
*** 27,32 ****
--- 29,35 ----
#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
*** 46,51 ****
--- 49,56 ----
} PostponedQual;
+ static void create_aggregate_grouped_var_infos(PlannerInfo *root);
+ static void create_grouping_expr_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
*** 241,246 ****
--- 246,839 ----
}
}
+ /*
+ * Add GroupedVarInfo to grouped_var_list for each aggregate as well as for
+ * each possible grouping expression and setup GroupedPathInfo for each base
+ * relation that can product grouped paths.
+ *
+ * root->group_pathkeys must be setup before this function is called.
+ */
+ extern void
+ add_grouping_info_to_base_rels(PlannerInfo *root)
+ {
+ int i;
+ ListCell *lc;
+
+ /* No grouping in the query? */
+ if (!root->parse->groupClause)
+ return;
+
+ /*
+ * Grouping sets require multiple different groupings but the base
+ * relation can only generate one.
+ */
+ if (root->parse->groupingSets)
+ return;
+
+ /*
+ * TODO Consider if this is a real limitation.
+ */
+ if (root->parse->hasWindowFuncs)
+ return;
+
+ /* Create GroupedVarInfo per (distinct) aggregate. */
+ create_aggregate_grouped_var_infos(root);
+
+ /* Is no grouping is possible below the top-level join? */
+ if (root->grouped_var_list == NIL)
+ return;
+
+ /* Create GroupedVarInfo per grouping expression. */
+ create_grouping_expr_grouped_var_infos(root);
+
+ /* 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);
+ }
+
+ /*
+ * Now that we know that grouping can be pushed down, search for the
+ * maximum sortgroupref. The base relations may need it if extra grouping
+ * expressions get added to them.
+ */
+ Assert(root->max_sortgroupref == 0);
+ foreach(lc, root->processed_tlist)
+ {
+ TargetEntry *te = lfirst_node(TargetEntry, lc);
+
+ if (te->ressortgroupref > root->max_sortgroupref)
+ root->max_sortgroupref = te->ressortgroupref;
+ }
+ }
+
+ /*
+ * Create GroupedVarInfo for each distinct aggregate.
+ *
+ * If any aggregate is not suitable, set root->grouped_var_list to NIL and
+ * return.
+ */
+ static void
+ create_aggregate_grouped_var_infos(PlannerInfo *root)
+ {
+ List *tlist_exprs;
+ ListCell *lc;
+
+ Assert(root->grouped_var_list == NIL);
+
+ tlist_exprs = pull_var_clause((Node *) root->processed_tlist,
+ PVC_INCLUDE_AGGREGATES);
+
+ /*
+ * Although GroupingFunc is related to root->parse->groupingSets, this
+ * field does not necessarily reflect its presence.
+ */
+ foreach(lc, tlist_exprs)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+
+ if (IsA(expr, GroupingFunc))
+ return;
+ }
+
+ /*
+ * Aggregates within the HAVING clause need to be processed in the same
+ * way as those in the main targetlist.
+ */
+ if (root->parse->havingQual != NULL)
+ {
+ List *having_exprs;
+
+ having_exprs = pull_var_clause((Node *) root->parse->havingQual,
+ PVC_INCLUDE_AGGREGATES);
+ if (having_exprs != NIL)
+ tlist_exprs = list_concat(tlist_exprs, having_exprs);
+ }
+
+ 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;
+ }
+
+ /*
+ * Aggregation push-down does not work w/o aggcombinefn. This field is
+ * not mandatory, so check if this particular aggregate can handle
+ * partial aggregation.
+ */
+ if (!OidIsValid(aggref->aggcombinefn))
+ {
+ root->grouped_var_list = NIL;
+ break;
+ }
+
+ /* Does GroupedVarInfo for this aggregate already exist? */
+ exists = false;
+ foreach(lc2, root->grouped_var_list)
+ {
+ gvi = lfirst_node(GroupedVarInfo, lc2);
+
+ if (equal(expr, gvi->gvexpr))
+ {
+ exists = true;
+ break;
+ }
+ }
+
+ /* Construct a new GroupedVarInfo if does not exist yet. */
+ if (!exists)
+ {
+ Relids relids;
+
+ 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
+ gvi->gv_eval_at = NULL;
+
+ /*
+ * The transient state is what appears in the target.
+ */
+ gvi->gv_width =
+ get_typavgwidth(exprType((Node *) gvi->agg_partial),
+ exprTypmod((Node *) gvi->agg_partial));
+
+ root->grouped_var_list = lappend(root->grouped_var_list, gvi);
+ }
+ }
+
+ list_free(tlist_exprs);
+ }
+
+ /*
+ * Create GroupedVarInfo for each expression usable as grouping key.
+ *
+ * In addition to the expressions of the query targetlist, group_pathkeys is
+ * also considered the source of grouping expressions. That increases the
+ * chance to get the relation output grouped.
+ */
+ static void
+ create_grouping_expr_grouped_var_infos(PlannerInfo *root)
+ {
+ ListCell *l1,
+ *l2;
+ List *exprs = NIL;
+ List *sortgrouprefs = NIL;
+
+ /*
+ * Make sure GroupedVar exists for each expression usable as grouping key.
+ */
+ foreach(l1, root->parse->groupClause)
+ {
+ SortGroupClause *sgClause;
+ TargetEntry *te;
+ Index sortgroupref;
+
+ sgClause = lfirst_node(SortGroupClause, l1);
+ te = get_sortgroupclause_tle(sgClause, root->processed_tlist);
+ sortgroupref = te->ressortgroupref;
+
+ if (sortgroupref == 0)
+ continue;
+
+ /*
+ * Non-zero sortgroupref does not necessarily imply grouping
+ * expression: data can also be sorted by aggregate.
+ */
+ if (IsA(te->expr, Aggref))
+ continue;
+
+ exprs = lappend(exprs, te->expr);
+ sortgrouprefs = lappend_int(sortgrouprefs, sortgroupref);
+ }
+
+ /*
+ * Try to derive additional grouping expressions from group_pathkeys. This
+ * increases the chance that relation can be grouped even if its columns
+ * are not mentioned in the grouping clause.
+ *
+ * It's important that we have processed the explicit grouping columns
+ * first. If the grouping clause contains multiple expressions belonging
+ * to the same EC, the original (i.e. not derived) one should be preferred
+ * when we build grouping target for a relation. Otherwise we have a
+ * problem when trying to match target entries to grouping clauses during
+ * plan creation, see extract_grouping_cols.
+ */
+ foreach(l1, root->group_pathkeys)
+ {
+ PathKey *pk = lfirst_node(PathKey, l1);
+ EquivalenceClass *ec = pk->pk_eclass;
+ EquivalenceMember *em;
+ Index sortgroupref = 0;
+
+ /* 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(l2, ec->ec_members)
+ {
+ ListCell *l3;
+
+ em = lfirst_node(EquivalenceMember, l2);
+
+ /*
+ * We search for grouping expressions now.
+ */
+ if (IsA(em->em_expr, Aggref))
+ continue;
+
+ if (em->em_nullable_relids)
+ continue;
+
+ /*
+ * If targetlist contains this expression, it should provide us
+ * with the sortgroupref.
+ */
+ foreach(l3, root->processed_tlist)
+ {
+ TargetEntry *te = lfirst_node(TargetEntry, l3);
+
+ if (equal(em->em_expr, te->expr))
+ {
+ /*
+ * XXX Not sure if the same expression exist in the
+ * targetlist multiple times and if some occurrences can
+ * miss the ressortgroupref. Maybe we just need to
+ * Assert() here that ressortgroupref is non-zero.
+ */
+ if (te->ressortgroupref > 0)
+ {
+ sortgroupref = te->ressortgroupref;
+ break;
+ }
+ }
+ }
+
+ /*
+ * If a single EC member matches, no need to check the rest of the
+ * EC.
+ */
+ if (sortgroupref > 0)
+ break;
+ }
+
+ if (sortgroupref == 0)
+ /* Go for the next EC. */
+ continue;
+
+ /*
+ * The EC contains a grouping expression, so each member of that EC
+ * should be usable as grouping key. Remember all the expressions of
+ * the EC as well as their (supposedly common) sortgroupref for the
+ * final processing.
+ */
+ foreach(l2, ec->ec_members)
+ {
+ ListCell *l3;
+
+ em = lfirst_node(EquivalenceMember, l2);
+
+ /*
+ * Do not add the expression if it's explicitly mentioned by the
+ * targetlist. We should already have it in that case.
+ */
+ foreach(l3, exprs)
+ {
+ if (equal(lfirst(l3), em->em_expr))
+ break;
+ }
+ if (l3 != NULL)
+ continue;
+
+ exprs = lappend(exprs, em->em_expr);
+ sortgrouprefs = lappend_int(sortgrouprefs, sortgroupref);
+ }
+ }
+
+ /*
+ * Finally construct GroupedVarInfo for each expression.
+ */
+ forboth(l1, exprs, l2, sortgrouprefs)
+ {
+ Expr *expr = (Expr *) lfirst(l1);
+ int sortgroupref = lfirst_int(l2);
+ GroupedVarInfo *gvi = makeNode(GroupedVarInfo);
+
+ gvi->gvid = list_length(root->grouped_var_list);
+ gvi->gvexpr = (Expr *) copyObject(expr);
+ gvi->sortgroupref = sortgroupref;
+
+ /* Find out where the expression should be evaluated. */
+ gvi->gv_eval_at = pull_varnos((Node *) expr);
+
+ /*
+ * As a special case, Var can be present in the non-grouped target, so
+ * set_rel_widths() could take care of the width computation. However
+ * the Var might not be propagated high enough in the join tree, so
+ * set_rel_width() might miss it. Handle it manually.
+ */
+ if (IsA(expr, Var))
+ {
+ Var *var = castNode(Var, expr);
+ RelOptInfo *rel = root->simple_rel_array[var->varno];
+ Oid reloid = rel->relid;
+
+ if (reloid != InvalidOid && var->varattno > 0)
+ {
+ int32 width = get_attavgwidth(reloid, var->varattno);
+
+ if (width > 0)
+ {
+ gvi->gv_width += width;
+ continue;
+ }
+ }
+ }
+
+ /*
+ * In general we should not expect any statistics to exist for an
+ * expression just because it's a grouping expression. So use the type
+ * information to get the width estimate.
+ */
+ gvi->gv_width = get_typavgwidth(exprType((Node *) gvi->gvexpr),
+ exprTypmod((Node *) gvi->gvexpr));
+
+ root->grouped_var_list = lappend(root->grouped_var_list, gvi);
+ }
+ }
+
+ /*
+ * Check if rel->reltarget allows the relation to be grouped and initialize
+ * the grouping target(s).
+ *
+ * target_agg is a target that we'll eventually used to aggregate the output
+ * of relation 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. (Thus the scan / join that provides the AggPath with input
+ * does not have to care whether the source relation does have grouped target
+ * or not.)
+ *
+ * The *group_exprs_extra_p list may receive additional grouping expressions
+ * that the query does not have. These can make the aggregation of base
+ * relation / join less efficient, but can allow for join of the grouped
+ * relation that wouldn't be possible otherwise.
+ */
+ void
+ initialize_grouped_target(PlannerInfo *root, RelOptInfo *rel,
+ PathTarget *target_agg,
+ List **group_exprs_extra_p)
+ {
+ PathTarget *target_plain;
+ ListCell *lc1;
+ List *vars_unresolved = NIL;
+
+ /* The target to replace rel->reltarget. */
+ target_plain = create_empty_pathtarget();
+
+ foreach(lc1, rel->reltarget->exprs)
+ {
+ Var *tvar;
+ GroupedVar *gvar;
+
+ /*
+ * Given that PlaceHolderVar currently prevents us from doing
+ * aggregation push-down, the source target cannot contain anything
+ * more complex than a Var. (As for generic grouping expressions,
+ * add_grouped_vars_to_target will retrieve them from the query
+ * targetlist and add them to target_agg outside this function.)
+ */
+ tvar = lfirst_node(Var, lc1);
+
+ gvar = get_grouping_expression(root, (Expr *) tvar);
+ if (gvar != NULL)
+ {
+ /*
+ * It's o.k. to use the target expression for grouping.
+ *
+ * The actual Var is added to the target. If we used the
+ * containing GroupedVar, references from various clauses (e.g.
+ * join quals) wouldn't work.
+ */
+ add_column_to_pathtarget(target_agg, (Expr *) gvar->gvexpr,
+ gvar->sortgroupref);
+
+ /*
+ * As for the plain target, add the original expression but set
+ * sortgroupref in addition.
+ */
+ add_column_to_pathtarget(target_plain, gvar->gvexpr,
+ gvar->sortgroupref);
+
+ /* Process the next expression. */
+ continue;
+ }
+
+ /*
+ * Further investigation involves dependency check, for which we need
+ * to have all the plain-var grouping expressions gathered. So far
+ * only store the var in a list.
+ */
+ vars_unresolved = lappend(vars_unresolved, tvar);
+ }
+
+ /*
+ * Check for other possible reasons for the var to be in the plain target.
+ */
+ foreach(lc1, vars_unresolved)
+ {
+ Var *var;
+ RangeTblEntry *rte;
+ List *deps = NIL;
+ Relids relids_subtract;
+ int ndx;
+ RelOptInfo *baserel;
+
+ var = lfirst_node(Var, lc1);
+ rte = root->simple_rte_array[var->varno];
+
+ /*
+ * Dependent var is almost the same as one that has sortgroupref.
+ */
+ if (check_functional_grouping(rte->relid, var->varno,
+ var->varlevelsup,
+ target_agg->exprs, &deps))
+ {
+
+ Index sortgroupref = 0;
+
+ add_column_to_pathtarget(target_agg, (Expr *) var, sortgroupref);
+
+ /*
+ * The var shouldn't be actually used as a grouping key (instead,
+ * the one this depends on will be), so sortgroupref should not be
+ * important. But once we have it ...
+ */
+ add_column_to_pathtarget(target_plain, (Expr *) var,
+ sortgroupref);
+
+ /*
+ * The var may or may not be present in generic grouping
+ * expression(s) or aggregate arguments, but we already have it in
+ * the targets, so don't care.
+ */
+ continue;
+ }
+
+ /*
+ * Isn't the expression needed by joins above the current rel?
+ *
+ * The relids we're not interested in do include 0, which is the
+ * top-level targetlist. The only reason for relids to contain 0
+ * should be that arg_var is referenced either by aggregate or by
+ * grouping expression, but right now we're interested in the *other*
+ * reasons. (As soon as GroupedVars are installed, the top level
+ * aggregates / grouping expressions no longer need direct reference
+ * to arg_var anyway.)
+ */
+ relids_subtract = bms_copy(rel->relids);
+ bms_add_member(relids_subtract, 0);
+
+ baserel = find_base_rel(root, var->varno);
+ ndx = var->varattno - baserel->min_attr;
+ if (bms_nonempty_difference(baserel->attr_needed[ndx],
+ relids_subtract))
+ {
+ /*
+ * The variable is needed by upper join. This includes one that is
+ * referenced by a generic grouping expression but couldn't be
+ * recognized as grouping expression on its own at the top of the
+ * loop.
+ *
+ * The only way to bring this var to the aggregation output is to
+ * add it to the grouping expressions too.
+ *
+ * Since root->parse->groupClause is not supposed to contain this
+ * expression, we need to construct special SortGroupClause. Its
+ * tleSortGroupRef needs to be unique within target_agg, so
+ * postpone creation of the SortGroupRefs until we're done with
+ * the iteration of rel->reltarget->exprs.
+ */
+ *group_exprs_extra_p = lappend(*group_exprs_extra_p, var);
+ }
+ else
+ {
+ /*
+ * As long as the query is semantically correct, arriving here
+ * means that the var is referenced either by aggregate argument
+ * or by generic grouping expression. The per-relation aggregation
+ * target should not contain it, as it only provides input for the
+ * final aggregation.
+ */
+ }
+
+ /*
+ * The var is not suitable for grouping, but the plain target ought to
+ * stay complete.
+ */
+ add_column_to_pathtarget(target_plain, (Expr *) var, 0);
+ }
+
+ /*
+ * Apply the adjusted input target as the replacement is complete now.
+ */
+ /* TODO Free the old one. */
+ rel->reltarget = target_plain;
+ }
+
/*****************************************************************************
*
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
new file mode 100644
index 2e6a60e..9a15e5a
*** a/src/backend/optimizer/plan/planmain.c
--- b/src/backend/optimizer/plan/planmain.c
*************** query_planner(PlannerInfo *root, List *t
*** 178,183 ****
--- 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/util/relnode.c b/src/backend/optimizer/util/relnode.c
new file mode 100644
index 674cfc6..08686f6
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
***************
*** 27,32 ****
--- 27,33 ----
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h"
+ #include "parser/parse_oper.h"
#include "utils/hsearch.h"
*************** build_joinrel_partition_info(RelOptInfo
*** 1748,1750 ****
--- 1749,2083 ----
joinrel->nullable_partexprs[cnt] = nullable_partexpr;
}
}
+
+ /*
+ * If the relation can produce grouped paths, create GroupedPathInfo for it
+ * and create target for aggregation of the relation output.
+ */
+ void
+ prepare_rel_for_grouping(PlannerInfo *root, RelOptInfo *rel)
+ {
+ List *gvis;
+ List *aggregates = NIL;
+ List *grp_exprs = NIL;
+ bool found_higher_agg;
+ ListCell *lc;
+ GroupedPathInfo *gpi;
+ PathTarget *target;
+ List *grp_exprs_extra = NIL;
+
+ /*
+ * The current implementation of aggregation push-down cannot handle
+ * PlaceHolderVar (PHV).
+ *
+ * If we knew that the PHV should be evaluated in this target (and of
+ * course, if its expression matched some grouping expression or Aggref
+ * argument), we'd just let initialize_grouped_targets create GroupedVar
+ * for the corresponding expression (phexpr). On the other hand, if we
+ * knew that the PHV is evaluated below the current rel, we'd ignore it
+ * because the referencing GroupedVar would take care of propagation of
+ * the value to upper joins. (PHV whose ph_eval_at is above the current
+ * rel make the aggregation push-down impossible in any case because the
+ * partial aggregation would receive wrong input if we ignored the
+ * ph_eval_at.)
+ *
+ * The problem is that the same PHV can be evaluated in the target of the
+ * current rel or in that of lower rel --- depending on the input paths.
+ * For example, consider rel->relids = {A, B, C} and if ph_eval_at = {B,
+ * C}. Path "A JOIN (B JOIN C)" implies that the PHV is evaluated by the
+ * "(B JOIN C)", while path "(A JOIN B) JOIN C" evaluates the PHV itself.
+ */
+ foreach(lc, rel->reltarget->exprs)
+ {
+ Expr *expr = lfirst(lc);
+
+ if (IsA(expr, PlaceHolderVar))
+ return;
+ }
+
+ /*
+ * target_agg will be used to aggregate the output of the current
+ * relation's paths.
+ */
+ target = create_empty_pathtarget();
+
+ if (IS_SIMPLE_REL(rel))
+ {
+ 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 Agg plan
+ * would receive different input at the base rel level.
+ *
+ * XXX 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 initialize_grouped_targets_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;
+
+ /*
+ * Use equivalence classes to generate additional grouping expressions for
+ * the current rel. Without these we might not be able to apply
+ * aggregation to the relation result set.
+ *
+ * While create_grouping_expr_grouped_var_infos generates equivalence
+ * class implied plain-Var grouping expressions at once, doing so for
+ * generic expressions would be complex and might result in very long list
+ * to search in. So generate the EC-related grouping expressions for each
+ * particular set of relids.
+ */
+ gvis = list_copy(root->grouped_var_list);
+ foreach(lc, root->grouped_var_list)
+ {
+ GroupedVarInfo *gvi = lfirst_node(GroupedVarInfo, lc);
+
+ /* Only interested in grouping expressions. */
+ if (IsA(gvi->gvexpr, Aggref))
+ continue;
+
+ /*
+ * Should have been processed by
+ * create_grouping_expr_grouped_var_infos.
+ */
+ if (IsA(gvi->gvexpr, Var))
+ continue;
+
+ gvi = translate_expression_to_rels(root, gvi, rel->relids);
+ if (gvi != NULL)
+ gvis = lappend(gvis, gvi);
+ }
+
+ /*
+ * Check if some aggregates or grouping expressions can be evaluated in
+ * this relation's target, and collect all vars referenced by these
+ * aggregates / grouping expressions;
+ */
+ found_higher_agg = false;
+ foreach(lc, gvis)
+ {
+ GroupedVarInfo *gvi = lfirst_node(GroupedVarInfo, lc);
+
+ /*
+ * The subset includes gv_eval_at uninitialized, which includes
+ * Aggref.aggstar.
+ */
+ if (bms_is_subset(gvi->gv_eval_at, rel->relids))
+ {
+ /*
+ * initialize_grouped_targets will handle plain Var grouping
+ * expressions because it needs to look them up in
+ * grouped_var_list anyway.
+ *
+ * XXX A plain Var could actually be handled w/o GroupedVar, but
+ * thus initialize_grouped_targets would have to spend extra
+ * effort looking for the EC-related vars, instead of relying on
+ * create_grouping_expr_grouped_var_infos. (Processing of
+ * particular expression would look different, so we could hardly
+ * reuse the same piece of code.)
+ */
+ if (IsA(gvi->gvexpr, Var))
+ continue;
+
+ /*
+ * Accept the aggregate / grouping expression.
+ *
+ * (GroupedVarInfo is more convenient for the next processing than
+ * Aggref, see add_aggregates_to_grouped_target.)
+ */
+ if (IsA(gvi->gvexpr, Aggref))
+ aggregates = lappend(aggregates, gvi);
+ else
+ grp_exprs = lappend(grp_exprs, gvi);
+ }
+ else if (bms_overlap(gvi->gv_eval_at, rel->relids) &&
+ IsA(gvi->gvexpr, Aggref))
+ {
+ /*
+ * Remember that there is at least one aggregate / grouping
+ * expression that needs more than this rel.
+ */
+ found_higher_agg = true;
+ }
+ }
+ list_free(gvis);
+
+ /*
+ * Give up if some other aggregate(s) need multiple relations including
+ * the current one. The problem is that grouping of the current relation
+ * could make some input variables unavailable for the "higher aggregate",
+ * and it'd also decrease the number of input rows the "higher aggregate"
+ * receives.
+ *
+ * In contrast, grp_exprs is only supposed to contain generic grouping
+ * expression, so it can be NIL so far. If all the grouping keys are just
+ * plain Vars, initialize_grouped_targets will take care of them.
+ */
+ if (found_higher_agg)
+ return;
+
+ /*
+ * Add plain-var grouping expressions to the target.
+ */
+ initialize_grouped_target(root, rel, target, &grp_exprs_extra);
+
+ /*
+ * Grouping makes little sense w/o aggregate function and w/o grouping
+ * expressions.
+ *
+ * This test was postponed because initialize_grouped_targets initializes
+ * sortgrouprefs of rel->reltarget. This information is useful to avoid
+ * final aggregation. In particular, even non-grouped relation (e.g.
+ * unique index scan) can generate unique values of grouping keys. See
+ * check_group_key_uniqueness for details.
+ */
+ if (aggregates == NIL)
+ return;
+
+ /*
+ * Add (non-Var) grouping expressions (in the form of GroupedVar) to
+ * target_agg.
+ *
+ * Follow the convention that the grouping expressions should precede the
+ * aggregates.
+ */
+ add_grouped_vars_to_target(root, target, grp_exprs);
+
+ /*
+ * Initialize GroupedPathInfo.
+ */
+ Assert(rel->gpi == NULL);
+ gpi = makeNode(GroupedPathInfo);
+ gpi->pathlist = NIL;
+ gpi->partial_pathlist = NIL;
+ rel->gpi = gpi;
+
+ /*
+ * Partial aggregation makes no sense w/o grouping expressions.
+ */
+ if (list_length(target->exprs) == 0)
+ {
+ pfree(rel->gpi);
+ rel->gpi = NULL;
+ return;
+ }
+
+ /*
+ * If the aggregation target should have extra grouping expressions, add
+ * them now. This step includes assignment of tleSortGroupRef's which we
+ * can generate now (the "ordinary" grouping expression are present in the
+ * target by now).
+ */
+ if (list_length(grp_exprs_extra) > 0)
+ {
+ Index sortgroupref;
+
+ /*
+ * Always start at root->max_sortgroupref. The extra grouping
+ * expressions aren't used during the final aggregation, so the
+ * sortgroupref values don't need to be unique across the query. Thus
+ * we don't have to increase root->max_sortgroupref, which makes
+ * recognition of the extra grouping expressions pretty easy.
+ */
+ sortgroupref = root->max_sortgroupref;
+
+ /*
+ * Generate the SortGroupClause's and add the expressions to the
+ * target.
+ */
+ foreach(lc, grp_exprs_extra)
+ {
+ Var *var = lfirst_node(Var, lc);
+ SortGroupClause *cl = makeNode(SortGroupClause);
+ PathTarget *target_plain;
+ int i = 0;
+ ListCell *lc2;
+
+ /*
+ * TODO Verify that these fields are sufficient for this special
+ * SortGroupClause.
+ */
+ cl->tleSortGroupRef = ++sortgroupref;
+ get_sort_group_operators(var->vartype,
+ false, true, false,
+ NULL, &cl->eqop, NULL,
+ &cl->hashable);
+ gpi->sortgroupclauses = lappend(gpi->sortgroupclauses, cl);
+ add_column_to_pathtarget(target, (Expr *) var,
+ cl->tleSortGroupRef);
+
+ /*
+ * The aggregation input target must emit this var too. It can
+ * already be there, so avoid adding it again.
+ */
+ target_plain = rel->reltarget;
+ foreach(lc2, target_plain->exprs)
+ {
+ Expr *expr = (Expr *) lfirst(lc2);
+
+ if (equal(expr, var))
+ {
+ /*
+ * The fact that the var is there does not imply that it
+ * has sortgroupref set. For example, the reason that it's
+ * there can be that a generic grouping expression
+ * references it, so grouping by the var alone hasn't been
+ * considered so far.
+ */
+ if (target_plain->sortgrouprefs == NULL)
+ {
+ target_plain->sortgrouprefs = (Index *)
+ palloc0(list_length(target_plain->exprs) *
+ sizeof(Index));
+ }
+ if (target_plain->sortgrouprefs[i] == 0)
+ target_plain->sortgrouprefs[i] = cl->tleSortGroupRef;
+
+ break;
+ }
+
+ i++;
+ }
+ if (lc2 != NULL)
+ continue;
+
+ /*
+ * Add the var if it's not in the target yet.
+ */
+ add_column_to_pathtarget(target_plain, (Expr *) var,
+ cl->tleSortGroupRef);
+ }
+ }
+
+ /*
+ * Add aggregates (in the form of GroupedVar) to the target(s).
+ */
+ add_grouped_vars_to_target(root, target, aggregates);
+
+ /* TODO Check if copy is needed here. */
+ gpi->target = copy_pathtarget(target);
+
+ /*
+ * Add the non-Var group expressions to a separate target. If
+ * rel->reltarget should be used to generate input for partial
+ * aggregation, the corresponding path should include these expressions.
+ */
+ if (list_length(grp_exprs) > 0)
+ {
+ rel->gpi->group_exprs = create_empty_pathtarget();
+ add_grouped_vars_to_target(root, rel->gpi->group_exprs, grp_exprs);
+ }
+ }
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
new file mode 100644
index 9345891..abdc5d9
*** a/src/backend/optimizer/util/tlist.c
--- b/src/backend/optimizer/util/tlist.c
*************** apply_pathtarget_labeling_to_tlist(List
*** 783,788 ****
--- 783,853 ----
}
/*
+ * For each aggregate add GroupedVar to the grouped target.
+ *
+ * Caller passes the aggregates in the form of GroupedVarInfos so that we
+ * don't have to look for gvid.
+ */
+ void
+ add_grouped_vars_to_target(PlannerInfo *root, PathTarget *target,
+ List *expressions)
+ {
+ ListCell *lc;
+
+ /* Create the vars and add them to the target. */
+ foreach(lc, expressions)
+ {
+ GroupedVarInfo *gvi;
+ GroupedVar *gvar;
+
+ gvi = lfirst_node(GroupedVarInfo, lc);
+ gvar = makeNode(GroupedVar);
+ gvar->gvid = gvi->gvid;
+ gvar->gvexpr = gvi->gvexpr;
+ gvar->agg_partial = gvi->agg_partial;
+ add_column_to_pathtarget(target, (Expr *) gvar, gvi->sortgroupref);
+ }
+ }
+
+ /*
+ * Return GroupedVar containing the passed-in expression if one exists, or
+ * NULL if the expression cannot be used as grouping key.
+ *
+ * create_grouping_expr_grouped_var_infos should have been called before this
+ * function gets called the first time.
+ */
+ GroupedVar *
+ get_grouping_expression(PlannerInfo *root, Expr *expr)
+ {
+ ListCell *lc;
+
+ /* Caller should have noticed the empty list much earlier. */
+ Assert(root->grouped_var_list != NIL);
+
+ foreach(lc, root->grouped_var_list)
+ {
+ GroupedVarInfo *gvi = lfirst_node(GroupedVarInfo, lc);
+
+ if (IsA(gvi->gvexpr, Aggref))
+ continue;
+
+ if (equal(gvi->gvexpr, expr))
+ {
+ GroupedVar *result = makeNode(GroupedVar);
+
+ Assert(gvi->sortgroupref > 0);
+ result->gvexpr = gvi->gvexpr;
+ result->gvid = gvi->gvid;
+ result->sortgroupref = gvi->sortgroupref;
+ return result;
+ }
+ }
+
+ /* The expression cannot be used as grouping key. */
+ return NULL;
+ }
+
+ /*
* split_pathtarget_at_srfs
* Split given PathTarget into multiple levels to position SRFs safely
*
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
new file mode 100644
index e367221..7090d02
*** a/src/include/optimizer/clauses.h
--- b/src/include/optimizer/clauses.h
*************** extern Node *estimate_expression_value(P
*** 84,88 ****
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
!
#endif /* CLAUSES_H */
--- 84,90 ----
extern Query *inline_set_returning_function(PlannerInfo *root,
RangeTblEntry *rte);
! extern GroupedVarInfo *translate_expression_to_rels(PlannerInfo *root,
! GroupedVarInfo *gvi,
! Relids relids);
#endif /* CLAUSES_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
new file mode 100644
index 3ef12b3..08111d2
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern RelOptInfo *build_child_join_rel(
*** 300,304 ****
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
RelOptInfo *parent_joinrel, List *restrictlist,
SpecialJoinInfo *sjinfo, JoinType jointype);
!
#endif /* PATHNODE_H */
--- 300,304 ----
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
RelOptInfo *parent_joinrel, List *restrictlist,
SpecialJoinInfo *sjinfo, JoinType jointype);
! extern void prepare_rel_for_grouping(PlannerInfo *root, RelOptInfo *rel);
#endif /* PATHNODE_H */
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
new file mode 100644
index d613322..a4c6f5d
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
*************** extern void add_base_rels_to_query(Plann
*** 76,81 ****
--- 76,83 ----
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 0d3ec92..a725818
*** a/src/include/optimizer/tlist.h
--- b/src/include/optimizer/tlist.h
*************** extern Node *get_sortgroupclause_expr(So
*** 41,46 ****
--- 41,50 ----
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
List *targetList);
+ extern void get_grouping_expressions(PlannerInfo *root, PathTarget *target,
+ List *target_group_clauses,
+ 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 ****
--- 69,82 ----
PathTarget *target, PathTarget *input_target,
List **targets, List **targets_contain_srfs);
+ extern void add_grouped_vars_to_target(PlannerInfo *root, PathTarget *target,
+ List *expressions);
+ extern GroupedVar *get_grouping_expression(PlannerInfo *root, Expr *expr);
+ extern void initialize_grouped_target(PlannerInfo *root,
+ RelOptInfo *rel,
+ PathTarget *target_agg,
+ List **group_exprs_extra_p);
+
/* 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))
agg_pushdown_v5/03_add_path.diff
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
new file mode 100644
index 370cc36..0690bd4
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
*************** fileGetForeignPaths(PlannerInfo *root,
*** 551,557 ****
NIL, /* no pathkeys */
NULL, /* no outer rel either */
NULL, /* no extra plan */
! coptions));
/*
* If data file was sorted, and we knew it somehow, we could insert
--- 551,557 ----
NIL, /* no pathkeys */
NULL, /* no outer rel either */
NULL, /* no extra plan */
! coptions), false);
/*
* If data file was sorted, and we knew it somehow, we could insert
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
new file mode 100644
index fb65e2e..459c6b9
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
*************** postgresGetForeignPaths(PlannerInfo *roo
*** 907,913 ****
NULL, /* no outer rel either */
NULL, /* no extra plan */
NIL); /* no fdw_private list */
! add_path(baserel, (Path *) path);
/* Add paths with pathkeys */
add_paths_with_pathkeys_for_rel(root, baserel, NULL);
--- 907,913 ----
NULL, /* no outer rel either */
NULL, /* no extra plan */
NIL); /* no fdw_private list */
! add_path(baserel, (Path *) path, false);
/* Add paths with pathkeys */
add_paths_with_pathkeys_for_rel(root, baserel, NULL);
*************** postgresGetForeignPaths(PlannerInfo *roo
*** 1079,1085 ****
param_info->ppi_req_outer,
NULL,
NIL); /* no fdw_private list */
! add_path(baserel, (Path *) path);
}
}
--- 1079,1085 ----
param_info->ppi_req_outer,
NULL,
NIL); /* no fdw_private list */
! add_path(baserel, (Path *) path, false);
}
}
*************** add_paths_with_pathkeys_for_rel(PlannerI
*** 4343,4349 ****
useful_pathkeys,
NULL,
epq_path,
! NIL));
}
}
--- 4343,4350 ----
useful_pathkeys,
NULL,
epq_path,
! NIL),
! false);
}
}
*************** postgresGetForeignJoinPaths(PlannerInfo
*** 4574,4580 ****
NIL); /* no fdw_private */
/* Add generated path into joinrel by add_path(). */
! add_path(joinrel, (Path *) joinpath);
/* Consider pathkeys for the join relation */
add_paths_with_pathkeys_for_rel(root, joinrel, epq_path);
--- 4575,4581 ----
NIL); /* no fdw_private */
/* Add generated path into joinrel by add_path(). */
! add_path(joinrel, (Path *) joinpath, false);
/* Consider pathkeys for the join relation */
add_paths_with_pathkeys_for_rel(root, joinrel, epq_path);
*************** add_foreign_grouping_paths(PlannerInfo *
*** 4898,4904 ****
NIL); /* no fdw_private */
/* Add generated path into grouped_rel by add_path(). */
! add_path(grouped_rel, (Path *) grouppath);
}
/*
--- 4899,4905 ----
NIL); /* no fdw_private */
/* Add generated path into grouped_rel by add_path(). */
! add_path(grouped_rel, (Path *) grouppath, false);
}
/*
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
new file mode 100644
index 0e8463e..6872e14
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** set_plain_rel_pathlist(PlannerInfo *root
*** 698,704 ****
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)
--- 698,704 ----
required_outer = rel->lateral_relids;
/* Consider sequential scan */
! add_path(rel, create_seqscan_path(root, rel, required_outer, 0), false);
/* If appropriate, consider parallel sequential scan */
if (rel->consider_parallel && required_outer == NULL)
*************** create_plain_partial_paths(PlannerInfo *
*** 727,733 ****
return;
/* Add an unordered partial path based on a parallel sequential scan. */
! add_partial_path(rel, create_seqscan_path(root, rel, NULL, parallel_workers));
}
/*
--- 727,734 ----
return;
/* Add an unordered partial path based on a parallel sequential scan. */
! add_partial_path(rel, create_seqscan_path(root, rel, NULL, parallel_workers),
! false);
}
/*
*************** set_tablesample_rel_pathlist(PlannerInfo
*** 813,819 ****
path = (Path *) create_material_path(rel, path);
}
! add_path(rel, path);
/* For the moment, at least, there are no other paths to consider */
}
--- 814,820 ----
path = (Path *) create_material_path(rel, path);
}
! add_path(rel, path, false);
/* For the moment, at least, there are no other paths to consider */
}
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1550,1556 ****
if (subpaths_valid)
add_path(rel, (Path *) create_append_path(rel, subpaths, NIL,
NULL, 0, false,
! partitioned_rels, -1));
/*
* Consider an append of unordered, unparameterized partial paths. Make
--- 1551,1558 ----
if (subpaths_valid)
add_path(rel, (Path *) create_append_path(rel, subpaths, NIL,
NULL, 0, false,
! partitioned_rels, -1),
! false);
/*
* Consider an append of unordered, unparameterized partial paths. Make
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1602,1608 ****
partial_rows = appendpath->path.rows;
/* Add the path. */
! add_partial_path(rel, (Path *) appendpath);
}
/*
--- 1604,1610 ----
partial_rows = appendpath->path.rows;
/* Add the path. */
! add_partial_path(rel, (Path *) appendpath, false);
}
/*
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1643,1649 ****
pa_partial_subpaths,
NULL, parallel_workers, true,
partitioned_rels, partial_rows);
! add_partial_path(rel, (Path *) appendpath);
}
/*
--- 1645,1651 ----
pa_partial_subpaths,
NULL, parallel_workers, true,
partitioned_rels, partial_rows);
! add_partial_path(rel, (Path *) appendpath, false);
}
/*
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1697,1703 ****
add_path(rel, (Path *)
create_append_path(rel, subpaths, NIL,
required_outer, 0, false,
! partitioned_rels, -1));
}
}
--- 1699,1705 ----
add_path(rel, (Path *)
create_append_path(rel, subpaths, NIL,
required_outer, 0, false,
! partitioned_rels, -1), false);
}
}
*************** generate_mergeappend_paths(PlannerInfo *
*** 1793,1806 ****
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));
}
}
--- 1795,1810 ----
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)
*** 1961,1967 ****
rel->partial_pathlist = NIL;
add_path(rel, (Path *) create_append_path(rel, NIL, NIL, NULL,
! 0, false, NIL, -1));
/*
* We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
--- 1965,1972 ----
rel->partial_pathlist = NIL;
add_path(rel, (Path *) create_append_path(rel, NIL, NIL, NULL,
! 0, false, NIL, -1),
! false);
/*
* We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
*************** set_subquery_pathlist(PlannerInfo *root,
*** 2175,2181 ****
/* Generate outer path using this subpath */
add_path(rel, (Path *)
create_subqueryscan_path(root, rel, subpath,
! pathkeys, required_outer));
}
}
--- 2180,2187 ----
/* 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,
*** 2244,2250 ****
/* Generate appropriate path */
add_path(rel, create_functionscan_path(root, rel,
! pathkeys, required_outer));
}
/*
--- 2250,2256 ----
/* Generate appropriate path */
add_path(rel, create_functionscan_path(root, rel,
! pathkeys, required_outer), false);
}
/*
*************** set_values_pathlist(PlannerInfo *root, R
*** 2264,2270 ****
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_valuesscan_path(root, rel, required_outer));
}
/*
--- 2270,2276 ----
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_valuesscan_path(root, rel, required_outer), false);
}
/*
*************** set_tablefunc_pathlist(PlannerInfo *root
*** 2285,2291 ****
/* Generate appropriate path */
add_path(rel, create_tablefuncscan_path(root, rel,
! required_outer));
}
/*
--- 2291,2297 ----
/* Generate appropriate path */
add_path(rel, create_tablefuncscan_path(root, rel,
! required_outer), false);
}
/*
*************** set_cte_pathlist(PlannerInfo *root, RelO
*** 2351,2357 ****
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_ctescan_path(root, rel, required_outer));
}
/*
--- 2357,2363 ----
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_ctescan_path(root, rel, required_outer), false);
}
/*
*************** set_namedtuplestore_pathlist(PlannerInfo
*** 2378,2384 ****
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);
--- 2384,2391 ----
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
*** 2431,2437 ****
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_worktablescan_path(root, rel, required_outer));
}
/*
--- 2438,2445 ----
required_outer = rel->lateral_relids;
/* Generate appropriate path */
! add_path(rel, create_worktablescan_path(root, rel, required_outer),
! false);
}
/*
*************** generate_gather_paths(PlannerInfo *root,
*** 2463,2469 ****
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
--- 2471,2477 ----
simple_gather_path = (Path *)
create_gather_path(root, rel, cheapest_partial_path, rel->reltarget,
NULL, NULL);
! add_path(rel, simple_gather_path, false);
/*
* For each useful ordering, we can consider an order-preserving Gather
*************** generate_gather_paths(PlannerInfo *root,
*** 2479,2485 ****
path = create_gather_merge_path(root, rel, subpath, rel->reltarget,
subpath->pathkeys, NULL, NULL);
! add_path(rel, &path->path);
}
}
--- 2487,2493 ----
path = create_gather_merge_path(root, rel, subpath, rel->reltarget,
subpath->pathkeys, NULL, NULL);
! add_path(rel, &path->path, false);
}
}
*************** create_partial_bitmap_paths(PlannerInfo
*** 3304,3310 ****
return;
add_partial_path(rel, (Path *) create_bitmap_heap_path(root, rel,
! bitmapqual, rel->lateral_relids, 1.0, parallel_workers));
}
/*
--- 3312,3319 ----
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/costsize.c b/src/backend/optimizer/path/costsize.c
new file mode 100644
index c3daacd..b0c7a0c
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
***************
*** 91,96 ****
--- 91,97 ----
#include "optimizer/plancat.h"
#include "optimizer/planmain.h"
#include "optimizer/restrictinfo.h"
+ #include "optimizer/var.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
#include "utils/selfuncs.h"
*************** static Selectivity get_foreign_key_join_
*** 164,170 ****
List **restrictlist);
static Cost append_nonpartial_cost(List *subpaths, int numpaths,
int parallel_workers);
! static void set_rel_width(PlannerInfo *root, RelOptInfo *rel);
static double relation_byte_size(double tuples, int width);
static double page_size(double tuples, int width);
static double get_parallel_divisor(Path *path);
--- 165,172 ----
List **restrictlist);
static Cost append_nonpartial_cost(List *subpaths, int numpaths,
int parallel_workers);
! static void set_rel_width(PlannerInfo *root, RelOptInfo *rel,
! PathTarget *reltarget, bool grouped);
static double relation_byte_size(double tuples, int width);
static double page_size(double tuples, int width);
static double get_parallel_divisor(Path *path);
*************** set_baserel_size_estimates(PlannerInfo *
*** 4285,4291 ****
cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
! set_rel_width(root, rel);
}
/*
--- 4287,4300 ----
cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
! set_rel_width(root, rel, rel->reltarget, false);
!
! /*
! * If the relation can produce grouped path, estimate width and costs of
! * the corresponding target.
! */
! if (rel->gpi)
! set_rel_width(root, rel, rel->gpi->target, true);
}
/*
*************** set_foreign_size_estimates(PlannerInfo *
*** 5036,5042 ****
cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
! set_rel_width(root, rel);
}
--- 5045,5051 ----
cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
! set_rel_width(root, rel, rel->reltarget, false);
}
*************** set_foreign_size_estimates(PlannerInfo *
*** 5060,5068 ****
*
* The per-attribute width estimates are cached for possible re-use while
* building join relations or post-scan/join pathtargets.
*/
static void
! set_rel_width(PlannerInfo *root, RelOptInfo *rel)
{
Oid reloid = planner_rt_fetch(rel->relid, root)->relid;
int32 tuple_width = 0;
--- 5069,5081 ----
*
* The per-attribute width estimates are cached for possible re-use while
* building join relations or post-scan/join pathtargets.
+ *
+ * If called for grouped target, it's assumed that it the function was already
+ * called for the non-grouped one.
*/
static void
! set_rel_width(PlannerInfo *root, RelOptInfo *rel, PathTarget *reltarget,
! bool grouped)
{
Oid reloid = planner_rt_fetch(rel->relid, root)->relid;
int32 tuple_width = 0;
*************** set_rel_width(PlannerInfo *root, RelOptI
*** 5070,5079 ****
ListCell *lc;
/* Vars are assumed to have cost zero, but other exprs do not */
! rel->reltarget->cost.startup = 0;
! rel->reltarget->cost.per_tuple = 0;
! foreach(lc, rel->reltarget->exprs)
{
Node *node = (Node *) lfirst(lc);
--- 5083,5092 ----
ListCell *lc;
/* Vars are assumed to have cost zero, but other exprs do not */
! reltarget->cost.startup = 0;
! reltarget->cost.per_tuple = 0;
! foreach(lc, reltarget->exprs)
{
Node *node = (Node *) lfirst(lc);
*************** set_rel_width(PlannerInfo *root, RelOptI
*** 5115,5120 ****
--- 5128,5140 ----
continue;
}
+ /*
+ * The non-grouped target of this rel should already have been
+ * processed, so do not waste any effort to get zero width again.
+ */
+ if (grouped && rel->attr_widths[ndx] == 0)
+ continue;
+
/* Try to get column width from statistics */
if (reloid != InvalidOid && var->varattno > 0)
{
*************** set_rel_width(PlannerInfo *root, RelOptI
*** 5148,5155 ****
tuple_width += phinfo->ph_width;
cost_qual_eval_node(&cost, (Node *) phv->phexpr, root);
! rel->reltarget->cost.startup += cost.startup;
! rel->reltarget->cost.per_tuple += cost.per_tuple;
}
else
{
--- 5168,5195 ----
tuple_width += phinfo->ph_width;
cost_qual_eval_node(&cost, (Node *) phv->phexpr, root);
! reltarget->cost.startup += cost.startup;
! reltarget->cost.per_tuple += cost.per_tuple;
! }
! else if (IsA(node, GroupedVar))
! {
! GroupedVar *gvar = (GroupedVar *) node;
! GroupedVarInfo *gvinfo;
!
! /*
! * GroupedVar should not appear in the "ordinary" target.
! */
! Assert(grouped);
!
! gvinfo = find_grouped_var_info(root, gvar);
! tuple_width += gvinfo->gv_width;
!
! /*
! * Only AggPath can evaluate GroupedVar, whether it's an aggregate
! * or generic grouping expression. In the other cases the
! * GroupedVar we see here only bubbled up from a lower AggPath, so
! * it does not add any cost to the path that owns this target.
! */
}
else
{
*************** set_rel_width(PlannerInfo *root, RelOptI
*** 5166,5173 ****
tuple_width += item_width;
/* Not entirely clear if we need to account for cost, but do so */
cost_qual_eval_node(&cost, node, root);
! rel->reltarget->cost.startup += cost.startup;
! rel->reltarget->cost.per_tuple += cost.per_tuple;
}
}
--- 5206,5213 ----
tuple_width += item_width;
/* Not entirely clear if we need to account for cost, but do so */
cost_qual_eval_node(&cost, node, root);
! reltarget->cost.startup += cost.startup;
! reltarget->cost.per_tuple += cost.per_tuple;
}
}
*************** set_rel_width(PlannerInfo *root, RelOptI
*** 5204,5210 ****
}
Assert(tuple_width >= 0);
! rel->reltarget->width = tuple_width;
}
/*
--- 5244,5250 ----
}
Assert(tuple_width >= 0);
! reltarget->width = tuple_width;
}
/*
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
new file mode 100644
index 18f6baf..c8bd820
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
*************** create_index_paths(PlannerInfo *root, Re
*** 338,344 ****
bitmapqual = choose_bitmap_and(root, rel, bitindexpaths);
bpath = create_bitmap_heap_path(root, rel, bitmapqual,
rel->lateral_relids, 1.0, 0);
! add_path(rel, (Path *) bpath);
/* create a partial bitmap heap path */
if (rel->consider_parallel && rel->lateral_relids == NULL)
--- 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, 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);
}
}
}
--- 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, false);
}
}
}
*************** get_index_paths(PlannerInfo *root, RelOp
*** 789,795 ****
IndexPath *ipath = (IndexPath *) lfirst(lc);
if (index->amhasgettuple)
! add_path(rel, (Path *) ipath);
if (index->amhasgetbitmap &&
(ipath->path.pathkeys == NIL ||
--- 789,795 ----
IndexPath *ipath = (IndexPath *) lfirst(lc);
if (index->amhasgettuple)
! add_path(rel, (Path *) ipath, false);
if (index->amhasgetbitmap &&
(ipath->path.pathkeys == NIL ||
*************** build_index_paths(PlannerInfo *root, Rel
*** 1077,1083 ****
* parallel workers, just free it.
*/
if (ipath->path.parallel_workers > 0)
! add_partial_path(rel, (Path *) ipath);
else
pfree(ipath);
}
--- 1077,1083 ----
* parallel workers, just free it.
*/
if (ipath->path.parallel_workers > 0)
! add_partial_path(rel, (Path *) ipath, false);
else
pfree(ipath);
}
*************** 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);
}
--- 1129,1135 ----
* using parallel workers, just free it.
*/
if (ipath->path.parallel_workers > 0)
! add_partial_path(rel, (Path *) ipath, false);
else
pfree(ipath);
}
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
new file mode 100644
index e774130..2a931af
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
*************** try_nestloop_path(PlannerInfo *root,
*** 422,428 ****
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer))
{
/*
* If the inner path is parameterized, it is parameterized by the
--- 422,428 ----
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer, false))
{
/*
* If the inner path is parameterized, it is parameterized by the
*************** try_nestloop_path(PlannerInfo *root,
*** 455,461 ****
inner_path,
extra->restrictlist,
pathkeys,
! required_outer));
}
else
{
--- 455,461 ----
inner_path,
extra->restrictlist,
pathkeys,
! required_outer), false);
}
else
{
*************** try_partial_nestloop_path(PlannerInfo *r
*** 513,519 ****
*/
initial_cost_nestloop(root, &workspace, jointype,
outer_path, inner_path, extra);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys))
return;
/*
--- 513,520 ----
*/
initial_cost_nestloop(root, &workspace, jointype,
outer_path, inner_path, extra);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys,
! false))
return;
/*
*************** try_partial_nestloop_path(PlannerInfo *r
*** 543,549 ****
inner_path,
extra->restrictlist,
pathkeys,
! NULL));
}
/*
--- 544,550 ----
inner_path,
extra->restrictlist,
pathkeys,
! NULL), false);
}
/*
*************** try_mergejoin_path(PlannerInfo *root,
*** 617,623 ****
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer))
{
add_path(joinrel, (Path *)
create_mergejoin_path(root,
--- 618,624 ----
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! pathkeys, required_outer, false))
{
add_path(joinrel, (Path *)
create_mergejoin_path(root,
*************** try_mergejoin_path(PlannerInfo *root,
*** 632,638 ****
required_outer,
mergeclauses,
outersortkeys,
! innersortkeys));
}
else
{
--- 633,639 ----
required_outer,
mergeclauses,
outersortkeys,
! innersortkeys), false);
}
else
{
*************** try_partial_mergejoin_path(PlannerInfo *
*** 691,697 ****
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. */
--- 692,699 ----
outersortkeys, innersortkeys,
extra);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys,
! false))
return;
/* Might be good enough to be worth trying, so let's try it. */
*************** try_partial_mergejoin_path(PlannerInfo *
*** 708,714 ****
NULL,
mergeclauses,
outersortkeys,
! innersortkeys));
}
/*
--- 710,716 ----
NULL,
mergeclauses,
outersortkeys,
! innersortkeys), false);
}
/*
*************** try_hashjoin_path(PlannerInfo *root,
*** 751,757 ****
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! NIL, required_outer))
{
add_path(joinrel, (Path *)
create_hashjoin_path(root,
--- 753,759 ----
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! NIL, required_outer, false))
{
add_path(joinrel, (Path *)
create_hashjoin_path(root,
*************** try_hashjoin_path(PlannerInfo *root,
*** 764,770 ****
false, /* parallel_hash */
extra->restrictlist,
required_outer,
! hashclauses));
}
else
{
--- 766,772 ----
false, /* parallel_hash */
extra->restrictlist,
required_outer,
! hashclauses), false);
}
else
{
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 815,821 ****
*/
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path, extra, true);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, NIL))
return;
/* Might be good enough to be worth trying, so let's try it. */
--- 817,823 ----
*/
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path, extra, true);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, NIL, false))
return;
/* Might be good enough to be worth trying, so let's try it. */
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 830,836 ****
parallel_hash,
extra->restrictlist,
NULL,
! hashclauses));
}
/*
--- 832,838 ----
parallel_hash,
extra->restrictlist,
NULL,
! hashclauses), false);
}
/*
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
new file mode 100644
index 5e03f8b..285f269
*** a/src/backend/optimizer/path/joinrels.c
--- b/src/backend/optimizer/path/joinrels.c
*************** mark_dummy_rel(RelOptInfo *rel)
*** 1233,1239 ****
/* Set up the dummy path */
add_path(rel, (Path *) create_append_path(rel, NIL, NIL, NULL,
! 0, false, NIL, -1));
/* Set or update cheapest_total_path and related fields */
set_cheapest(rel);
--- 1233,1240 ----
/* Set up the dummy path */
add_path(rel, (Path *) create_append_path(rel, NIL, NIL, NULL,
! 0, false, NIL, -1),
! 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 8de59fc..faa654b
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
*************** create_grouping_expr_grouped_var_infos(P
*** 653,658 ****
--- 653,659 ----
}
}
+
/*
* Check if rel->reltarget allows the relation to be grouped and initialize
* the grouping target(s).
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
new file mode 100644
index 889e8af..351fb93
*** 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,230 ----
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 9a15e5a..c1939a1
*** 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,90 ----
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);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 5a3efbd..8c524ac
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** inheritance_planner(PlannerInfo *root)
*** 1520,1526 ****
returningLists,
rowMarks,
NULL,
! SS_assign_special_param(root)));
}
/*--------------------
--- 1520,1527 ----
returningLists,
rowMarks,
NULL,
! SS_assign_special_param(root)),
! false);
}
/*--------------------
*************** grouping_planner(PlannerInfo *root, bool
*** 2135,2141 ****
}
/* And shove it into final_rel */
! add_path(final_rel, path);
}
/*
--- 2136,2142 ----
}
/* And shove it into final_rel */
! add_path(final_rel, path, false);
}
/*
*************** create_grouping_paths(PlannerInfo *root,
*** 3698,3704 ****
(List *) parse->havingQual);
}
! add_path(grouped_rel, path);
/* No need to consider any other alternatives. */
set_cheapest(grouped_rel);
--- 3699,3705 ----
(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,
*** 3875,3881 ****
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups));
else
add_partial_path(grouped_rel, (Path *)
create_group_path(root,
--- 3876,3883 ----
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups),
! false);
else
add_partial_path(grouped_rel, (Path *)
create_group_path(root,
*************** create_grouping_paths(PlannerInfo *root,
*** 3884,3890 ****
partial_grouping_target,
parse->groupClause,
NIL,
! dNumPartialGroups));
}
}
}
--- 3886,3893 ----
partial_grouping_target,
parse->groupClause,
NIL,
! dNumPartialGroups),
! false);
}
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 3915,3921 ****
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups));
}
}
}
--- 3918,3924 ----
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups), false);
}
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 3967,3973 ****
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups));
}
else if (parse->groupClause)
{
--- 3970,3976 ----
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups), false);
}
else if (parse->groupClause)
{
*************** create_grouping_paths(PlannerInfo *root,
*** 3982,3988 ****
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups));
}
else
{
--- 3985,3991 ----
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups), false);
}
else
{
*************** create_grouping_paths(PlannerInfo *root,
*** 4031,4037 ****
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups));
else
add_path(grouped_rel, (Path *)
create_group_path(root,
--- 4034,4040 ----
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,
*** 4040,4046 ****
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups));
/*
* The point of using Gather Merge rather than Gather is that it
--- 4043,4049 ----
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,
*** 4093,4099 ****
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups));
else
add_path(grouped_rel, (Path *)
create_group_path(root,
--- 4096,4102 ----
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,
*** 4102,4108 ****
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups));
}
}
}
--- 4105,4111 ----
target,
parse->groupClause,
(List *) parse->havingQual,
! dNumGroups), false);
}
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 4147,4153 ****
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups));
}
}
--- 4150,4156 ----
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
! dNumGroups), false);
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 4185,4191 ****
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups));
}
}
}
--- 4188,4194 ----
parse->groupClause,
(List *) parse->havingQual,
&agg_final_costs,
! dNumGroups), false);
}
}
}
*************** consider_groupingsets_paths(PlannerInfo
*** 4387,4393 ****
strat,
new_rollups,
agg_costs,
! dNumGroups));
return;
}
--- 4390,4396 ----
strat,
new_rollups,
agg_costs,
! dNumGroups), false);
return;
}
*************** consider_groupingsets_paths(PlannerInfo
*** 4545,4551 ****
AGG_MIXED,
rollups,
agg_costs,
! dNumGroups));
}
}
--- 4548,4554 ----
AGG_MIXED,
rollups,
agg_costs,
! dNumGroups), false);
}
}
*************** consider_groupingsets_paths(PlannerInfo
*** 4562,4568 ****
AGG_SORTED,
gd->rollups,
agg_costs,
! dNumGroups));
}
/*
--- 4565,4571 ----
AGG_SORTED,
gd->rollups,
agg_costs,
! dNumGroups), false);
}
/*
*************** create_one_window_path(PlannerInfo *root
*** 4747,4753 ****
window_pathkeys);
}
! add_path(window_rel, path);
}
/*
--- 4750,4756 ----
window_pathkeys);
}
! add_path(window_rel, path, false);
}
/*
*************** create_distinct_paths(PlannerInfo *root,
*** 4853,4859 ****
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows));
}
}
--- 4856,4862 ----
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows), false);
}
}
*************** create_distinct_paths(PlannerInfo *root,
*** 4880,4886 ****
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows));
}
/*
--- 4883,4889 ----
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows), false);
}
/*
*************** create_distinct_paths(PlannerInfo *root,
*** 4927,4933 ****
parse->distinctClause,
NIL,
NULL,
! numDistinctRows));
}
/* Give a helpful error if we failed to find any implementation */
--- 4930,4936 ----
parse->distinctClause,
NIL,
NULL,
! numDistinctRows), false);
}
/* Give a helpful error if we failed to find any implementation */
*************** create_ordered_paths(PlannerInfo *root,
*** 5025,5031 ****
path = apply_projection_to_path(root, ordered_rel,
path, target);
! add_path(ordered_rel, path);
}
}
--- 5028,5034 ----
path = apply_projection_to_path(root, ordered_rel,
path, target);
! add_path(ordered_rel, path, false);
}
}
*************** create_ordered_paths(PlannerInfo *root,
*** 5076,5082 ****
path = apply_projection_to_path(root, ordered_rel,
path, target);
! add_path(ordered_rel, path);
}
}
--- 5079,5085 ----
path = apply_projection_to_path(root, ordered_rel,
path, target);
! add_path(ordered_rel, path, false);
}
}
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
new file mode 100644
index a24e8ac..eb8050f
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
*************** plan_set_operations(PlannerInfo *root)
*** 221,227 ****
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)
--- 221,227 ----
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 2aee156..a1bea53
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
*************** set_cheapest(RelOptInfo *parent_rel)
*** 419,426 ****
* 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;
--- 419,427 ----
* 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
*** 437,442 ****
--- 438,451 ----
/* 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
*** 446,452 ****
* 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 */
--- 455,461 ----
* 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
*** 592,599 ****
*/
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
--- 601,607 ----
*/
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
*** 624,632 ****
{
/* 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
{
--- 632,645 ----
{
/* 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
*** 656,663 ****
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;
--- 669,677 ----
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
*** 666,674 ****
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;
--- 680,697 ----
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
*** 759,781 ****
* 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);
--- 782,813 ----
* 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,
*** 829,840 ****
}
/*
! * 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 */
}
--- 861,871 ----
}
/*
! * 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,
*** 849,856 ****
/*
* 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)
break;
--- 880,887 ----
/*
* 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)
break;
*************** add_partial_path(RelOptInfo *parent_rel,
*** 860,869 ****
{
/* 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
{
--- 891,904 ----
{
/* 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,
*** 884,892 ****
*/
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
--- 919,936 ----
*/
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
*** 896,905 ****
* 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;
--- 940,950 ----
* 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
*** 928,934 ****
* completion.
*/
if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! NULL))
return false;
return true;
--- 973,979 ----
* completion.
*/
if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! NULL, false))
return false;
return true;
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
new file mode 100644
index 81c60dc..8ecb21d
*** a/src/backend/optimizer/util/var.c
--- b/src/backend/optimizer/util/var.c
*************** alias_relid_set(PlannerInfo *root, Relid
*** 840,842 ****
--- 840,864 ----
}
return result;
}
+
+ /*
+ * Return GroupedVarInfo for given GroupedVar.
+ *
+ * XXX Consider better location of this routine.
+ */
+ GroupedVarInfo *
+ find_grouped_var_info(PlannerInfo *root, GroupedVar *gvar)
+ {
+ ListCell *l;
+
+ foreach(l, root->grouped_var_list)
+ {
+ GroupedVarInfo *gvi = lfirst_node(GroupedVarInfo, l);
+
+ if (gvi->gvid == gvar->gvid)
+ return gvi;
+ }
+
+ elog(ERROR, "GroupedVarInfo not found");
+ return NULL; /* keep compiler quiet */
+ }
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
new file mode 100644
index 08111d2..9c05492
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern int compare_path_costs(Path *path
*** 26,38 ****
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);
--- 26,40 ----
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);
diff --git a/src/include/optimizer/var.h b/src/include/optimizer/var.h
new file mode 100644
index 6186152..22816db
*** a/src/include/optimizer/var.h
--- b/src/include/optimizer/var.h
*************** extern bool contain_vars_of_level(Node *
*** 36,40 ****
--- 36,42 ----
extern int locate_var_of_level(Node *node, int levelsup);
extern List *pull_var_clause(Node *node, int flags);
extern Node *flatten_join_alias_vars(PlannerInfo *root, Node *node);
+ extern GroupedVarInfo *find_grouped_var_info(PlannerInfo *root,
+ GroupedVar *gvar);
#endif /* VAR_H */
agg_pushdown_v5/04_planner_prepare.diff
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 8c524ac..6371c34
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** static void standard_qp_callback(Planner
*** 131,139 ****
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,
--- 131,136 ----
*************** static void consider_groupingsets_paths(
*** 148,153 ****
--- 145,162 ----
grouping_sets_data *gd,
const AggClauseCosts *agg_costs,
double dNumGroups);
+ static void sort_agg_partial_grouped_paths(PlannerInfo *root, List *pathlist,
+ AggClauseCosts *agg_final_costs,
+ double dNumGroups,
+ RelOptInfo *grouped_rel,
+ PathTarget *gather_target,
+ PathTarget *group_target);
+ static void hash_agg_partial_grouped_path(PlannerInfo *root, Path *path,
+ AggClauseCosts *agg_final_costs,
+ double dNumGroups,
+ RelOptInfo *grouped_rel,
+ PathTarget *gather_target,
+ PathTarget *group_target);
static RelOptInfo *create_window_paths(PlannerInfo *root,
RelOptInfo *input_rel,
PathTarget *input_target,
*************** get_number_of_groups(PlannerInfo *root,
*** 3542,3581 ****
}
/*
- * 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.
--- 3551,3556 ----
*************** create_grouping_paths(PlannerInfo *root,
*** 4000,4114 ****
* path. We can do this using either Gather or Gather Merge.
*/
if (grouped_rel->partial_pathlist)
! {
! Path *path = (Path *) linitial(grouped_rel->partial_pathlist);
! double total_groups = path->rows * path->parallel_workers;
!
! path = (Path *) create_gather_path(root,
! grouped_rel,
! path,
! partial_grouping_target,
! NULL,
! &total_groups);
!
! /*
! * Since Gather's output is always unsorted, we'll need to sort,
! * unless there's no GROUP BY clause or a degenerate (constant)
! * one, in which case there will only be a single group.
! */
! if (root->group_pathkeys)
! path = (Path *) create_sort_path(root,
! grouped_rel,
! path,
! root->group_pathkeys,
! -1.0);
!
! if (parse->hasAggs)
! add_path(grouped_rel, (Path *)
! create_agg_path(root,
! grouped_rel,
! path,
! target,
! parse->groupClause ? AGG_SORTED : AGG_PLAIN,
! AGGSPLIT_FINAL_DESERIAL,
! parse->groupClause,
! (List *) parse->havingQual,
! &agg_final_costs,
! dNumGroups), false);
! else
! add_path(grouped_rel, (Path *)
! create_group_path(root,
! grouped_rel,
! path,
! target,
! parse->groupClause,
! (List *) parse->havingQual,
! dNumGroups), false);
!
! /*
! * The point of using Gather Merge rather than Gather is that it
! * can preserve the ordering of the input path, so there's no
! * reason to try it unless (1) it's possible to produce more than
! * one output row and (2) we want the output path to be ordered.
! */
! if (parse->groupClause != NIL && root->group_pathkeys != NIL)
! {
! foreach(lc, grouped_rel->partial_pathlist)
! {
! Path *subpath = (Path *) lfirst(lc);
! Path *gmpath;
! double total_groups;
!
! /*
! * It's useful to consider paths that are already properly
! * ordered for Gather Merge, because those don't need a
! * sort. It's also useful to consider the cheapest path,
! * because sorting it in parallel and then doing Gather
! * Merge may be better than doing an unordered Gather
! * followed by a sort. But there's no point in
! * considering non-cheapest paths that aren't already
! * sorted correctly.
! */
! if (path != subpath &&
! !pathkeys_contained_in(root->group_pathkeys,
! subpath->pathkeys))
! continue;
!
! total_groups = subpath->rows * subpath->parallel_workers;
!
! gmpath = (Path *)
! create_gather_merge_path(root,
! grouped_rel,
! subpath,
! partial_grouping_target,
! root->group_pathkeys,
! NULL,
! &total_groups);
!
! if (parse->hasAggs)
! add_path(grouped_rel, (Path *)
! create_agg_path(root,
! grouped_rel,
! gmpath,
! target,
! parse->groupClause ? AGG_SORTED : AGG_PLAIN,
! AGGSPLIT_FINAL_DESERIAL,
! parse->groupClause,
! (List *) parse->havingQual,
! &agg_final_costs,
! dNumGroups), false);
! else
! add_path(grouped_rel, (Path *)
! create_group_path(root,
! grouped_rel,
! gmpath,
! target,
! parse->groupClause,
! (List *) parse->havingQual,
! dNumGroups), false);
! }
! }
! }
}
if (can_hash)
--- 3975,3985 ----
* path. We can do this using either Gather or Gather Merge.
*/
if (grouped_rel->partial_pathlist)
! sort_agg_partial_grouped_paths(root,
! grouped_rel->partial_pathlist,
! &agg_final_costs,
! dNumGroups, grouped_rel,
! partial_grouping_target, target);
}
if (can_hash)
*************** create_grouping_paths(PlannerInfo *root,
*** 4163,4195 ****
{
Path *path = (Path *) linitial(grouped_rel->partial_pathlist);
! hashaggtablesize = estimate_hashagg_tablesize(path,
! &agg_final_costs,
! dNumGroups);
!
! if (hashaggtablesize < work_mem * 1024L)
! {
! double total_groups = path->rows * path->parallel_workers;
!
! path = (Path *) create_gather_path(root,
! grouped_rel,
! path,
! partial_grouping_target,
! NULL,
! &total_groups);
!
! add_path(grouped_rel, (Path *)
! create_agg_path(root,
! grouped_rel,
! path,
! target,
! AGG_HASHED,
! AGGSPLIT_FINAL_DESERIAL,
! parse->groupClause,
! (List *) parse->havingQual,
! &agg_final_costs,
! dNumGroups), false);
! }
}
}
--- 4034,4042 ----
{
Path *path = (Path *) linitial(grouped_rel->partial_pathlist);
! hash_agg_partial_grouped_path(root, path, &agg_final_costs,
! dNumGroups, grouped_rel,
! partial_grouping_target, target);
}
}
*************** consider_groupingsets_paths(PlannerInfo
*** 4569,4574 ****
--- 4416,4588 ----
}
/*
+ * Subroutine of create_grouping_paths() to apply GatherPath or
+ * GatherMergePath and AGG_SORTED AggPath to cases which only differ in the
+ * GatherPath / GatherMergePath target.
+ */
+ static void
+ sort_agg_partial_grouped_paths(PlannerInfo *root, List *pathlist,
+ AggClauseCosts *agg_final_costs,
+ double dNumGroups, RelOptInfo *grouped_rel,
+ PathTarget *gather_target,
+ PathTarget *group_target)
+ {
+ Path *path = (Path *) linitial(pathlist);
+ double total_groups = path->rows * path->parallel_workers;
+ Query *parse = root->parse;
+ ListCell *lc;
+
+ /*
+ * Generate a complete GroupAgg Path atop of the cheapest partial path. We
+ * can do this using either Gather or Gather Merge.
+ */
+ path = (Path *) create_gather_path(root,
+ grouped_rel,
+ path,
+ gather_target,
+ NULL,
+ &total_groups);
+
+ /*
+ * Since Gather's output is always unsorted, we'll need to sort, unless
+ * there's no GROUP BY clause or a degenerate (constant) one, in which
+ * case there will only be a single group.
+ */
+ if (root->group_pathkeys)
+ path = (Path *) create_sort_path(root,
+ grouped_rel,
+ path,
+ root->group_pathkeys,
+ -1.0);
+
+ if (parse->hasAggs)
+ add_path(grouped_rel, (Path *)
+ create_agg_path(root,
+ grouped_rel,
+ path,
+ group_target,
+ parse->groupClause ? AGG_SORTED : AGG_PLAIN,
+ AGGSPLIT_FINAL_DESERIAL,
+ parse->groupClause,
+ (List *) parse->havingQual,
+ agg_final_costs,
+ dNumGroups), false);
+ else
+ add_path(grouped_rel, (Path *)
+ create_group_path(root,
+ grouped_rel,
+ path,
+ group_target,
+ parse->groupClause,
+ (List *) parse->havingQual,
+ dNumGroups), false);
+
+ /*
+ * The point of using Gather Merge rather than Gather is that it can
+ * preserve the ordering of the input path, so there's no reason to try it
+ * unless (1) it's possible to produce more than one output row and (2) we
+ * want the output path to be ordered.
+ */
+ if (parse->groupClause != NIL && root->group_pathkeys != NIL)
+ {
+ foreach(lc, pathlist)
+ {
+ Path *subpath = (Path *) lfirst(lc);
+ Path *gmpath;
+ double total_groups;
+
+ /*
+ * It's useful to consider paths that are already properly ordered
+ * for Gather Merge, because those don't need a sort. It's also
+ * useful to consider the cheapest path, because sorting it in
+ * parallel and then doing Gather Merge may be better than doing
+ * an unordered Gather followed by a sort. But there's no point
+ * in considering non-cheapest paths that aren't already sorted
+ * correctly.
+ */
+ if (path != subpath &&
+ !pathkeys_contained_in(root->group_pathkeys,
+ subpath->pathkeys))
+ continue;
+
+ total_groups = subpath->rows * subpath->parallel_workers;
+
+ gmpath = (Path *)
+ create_gather_merge_path(root,
+ grouped_rel,
+ subpath,
+ gather_target,
+ root->group_pathkeys,
+ NULL,
+ &total_groups);
+
+ if (parse->hasAggs)
+ add_path(grouped_rel, (Path *)
+ create_agg_path(root,
+ grouped_rel,
+ gmpath,
+ group_target,
+ parse->groupClause ? AGG_SORTED : AGG_PLAIN,
+ AGGSPLIT_FINAL_DESERIAL,
+ parse->groupClause,
+ (List *) parse->havingQual,
+ agg_final_costs,
+ dNumGroups), false);
+ else
+ add_path(grouped_rel, (Path *)
+ create_group_path(root,
+ grouped_rel,
+ gmpath,
+ group_target,
+ parse->groupClause,
+ (List *) parse->havingQual,
+ dNumGroups), false);
+ }
+ }
+ }
+
+ /*
+ * Subroutine of create_grouping_paths() to apply GatherPath and AGG_HASHED
+ * AggPath to cases which only differ in the GatherPath target.
+ */
+ static void
+ hash_agg_partial_grouped_path(PlannerInfo *root, Path *path,
+ AggClauseCosts *agg_final_costs,
+ double dNumGroups, RelOptInfo *grouped_rel,
+ PathTarget *gather_target,
+ PathTarget *group_target)
+ {
+ Size hashaggtablesize = estimate_hashagg_tablesize(path, agg_final_costs,
+ dNumGroups);
+ Query *parse = root->parse;
+ double total_groups;
+
+ if (hashaggtablesize >= work_mem * 1024L)
+ return;
+
+ total_groups = path->rows * path->parallel_workers;
+
+ path = (Path *) create_gather_path(root,
+ grouped_rel,
+ path,
+ gather_target,
+ NULL,
+ &total_groups);
+
+ add_path(grouped_rel, (Path *)
+ create_agg_path(root,
+ grouped_rel,
+ path,
+ group_target,
+ AGG_HASHED,
+ AGGSPLIT_FINAL_DESERIAL,
+ parse->groupClause,
+ (List *) parse->havingQual,
+ agg_final_costs,
+ dNumGroups), false);
+ }
+
+ /*
* create_window_paths
*
* Build a new upperrel containing Paths for window-function evaluation.
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
new file mode 100644
index ea95b80..87dec17
*** 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 "miscadmin.h"
#include "nodes/makefuncs.h"
*************** estimate_hash_bucket_stats(PlannerInfo *
*** 3863,3868 ****
--- 3864,3902 ----
ReleaseVariableStats(vardata);
}
+ /*
+ * 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/utils/selfuncs.h b/src/include/utils/selfuncs.h
new file mode 100644
index 199a631..e2435b6
*** a/src/include/utils/selfuncs.h
--- b/src/include/utils/selfuncs.h
*************** extern void estimate_hash_bucket_stats(P
*** 210,215 ****
--- 210,219 ----
Node *hashkey, double nbuckets,
Selectivity *mcv_freq,
Selectivity *bucketsize_frac);
+ 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,
agg_pushdown_v5/05_base_rel.diff
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
new file mode 100644
index 55bb925..f09647e
*** a/src/backend/executor/execExpr.c
--- b/src/backend/executor/execExpr.c
*************** ExecInitExprRec(Expr *node, ExprState *s
*** 789,794 ****
--- 789,831 ----
break;
}
+ case T_GroupedVar:
+
+ /*
+ * If GroupedVar appears in targetlist of Agg node, it can
+ * represent either Aggref or grouping expression.
+ */
+ if (state->parent && (IsA(state->parent, AggState)))
+ {
+ GroupedVar *gvar = (GroupedVar *) node;
+
+ /*
+ * The only reason to execute GroupedVar is to generate either
+ * aggregate transient state or grouping expression value. So
+ * any contained Aggref must be partial.
+ *
+ * (The purpose of propagating GroupedVars to upper plan nodes
+ * is just to transfer the value, no execution takes place
+ * there.)
+ */
+ if (IsA(gvar->gvexpr, Aggref))
+
+ ExecInitExprRec((Expr *) gvar->agg_partial, state,
+ resv, resnull);
+ else
+ ExecInitExprRec((Expr *) gvar->gvexpr, 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 da6ef1a..2dabe4c
*** a/src/backend/executor/nodeAgg.c
--- b/src/backend/executor/nodeAgg.c
*************** find_unaggregated_cols_walker(Node *node
*** 1875,1880 ****
--- 1875,1887 ----
/* do not descend into aggregate exprs */
return false;
}
+ if (IsA(node, GroupedVar))
+ {
+ GroupedVar *gvar = (GroupedVar *) node;
+
+ if (IsA(gvar->gvexpr, Aggref))
+ return false;
+ }
return expression_tree_walker(node, find_unaggregated_cols_walker,
(void *) colnos);
}
diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c
new file mode 100644
index 3cf268c..e0dc8d9
*** a/src/backend/optimizer/geqo/geqo_eval.c
--- b/src/backend/optimizer/geqo/geqo_eval.c
*************** merge_clump(PlannerInfo *root, List *clu
*** 268,274 ****
generate_partition_wise_join_paths(root, 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);
--- 268,274 ----
generate_partition_wise_join_paths(root, 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 6872e14..1ccb834
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** set_rel_pathlist(PlannerInfo *root, RelO
*** 488,494 ****
* 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
--- 488,497 ----
* 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
*** 689,694 ****
--- 692,699 ----
set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
Relids required_outer;
+ Path *seq_path;
+ PartialAggInfo *agg_info = NULL;
/*
* We don't support pushing join clauses into the quals of a seqscan, but
*************** set_plain_rel_pathlist(PlannerInfo *root
*** 697,710 ****
*/
required_outer = rel->lateral_relids;
! /* Consider sequential scan */
! add_path(rel, create_seqscan_path(root, rel, required_outer, 0), false);
! /* If appropriate, consider parallel sequential scan */
if (rel->consider_parallel && required_outer == NULL)
create_plain_partial_paths(root, rel);
! /* Consider index scans */
create_index_paths(root, rel);
/* Consider TID scans */
--- 702,742 ----
*/
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 && rel->gpi->target != NULL &&
! required_outer == NULL)
! {
! /*
! * Retrieve the necessary info for the grouping expressions and
! * aggregates.
! *
! * 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.
! */
! agg_info = get_partial_agg_info(root, rel->gpi->target,
! rel->gpi->sortgroupclauses);
!
! /*
! * Only AGG_HASHED is suitable here as it does not expect the input +
! * set to be sorted.
! */
! create_grouped_path(root, rel, seq_path, false, false, AGG_HASHED,
! agg_info);
! }
!
! /* 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);
/* Consider TID scans */
*************** static void
*** 719,724 ****
--- 751,757 ----
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 *
*** 727,734 ****
return;
/* Add an unordered partial path based on a parallel sequential scan. */
! add_partial_path(rel, create_seqscan_path(root, rel, NULL, parallel_workers),
! false);
}
/*
--- 760,887 ----
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. Only AGG_HASHED is suitable here as it does not expect
! * the input set to be sorted.
! */
! if (rel->gpi != NULL && rel->gpi->target != NULL)
! {
! PartialAggInfo *agg_info = get_partial_agg_info(root,
! rel->gpi->target,
! rel->gpi->sortgroupclauses);
!
! create_grouped_path(root, rel, path, false, true, AGG_HASHED,
! agg_info);
! }
! }
!
! /*
! * 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 seems to be an exception ---
! * consider implementing parameterized AGG_SORTED unless the IndexPath is
! * partial.
! */
! void
! create_grouped_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
! bool precheck, bool partial, AggStrategy aggstrategy,
! PartialAggInfo *agg_info)
! {
! 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 || aggstrategy != AGG_HASHED);
!
! /*
! * Non-var grouping expressions will eventually require projection using
! * Result plan, but that does not work with SRFs.
! */
! Assert(rel->gpi != NULL);
! if (rel->gpi->group_exprs != NULL)
! {
! ListCell *lc;
!
! foreach(lc, rel->gpi->group_exprs->exprs)
! {
! GroupedVar *gvar = lfirst_node(GroupedVar, lc);
!
! if (IsA(gvar->gvexpr, FuncExpr))
! {
! FuncExpr *fexpr = (FuncExpr *) gvar->gvexpr;
!
! if (fexpr->funcretset)
! return;
! }
! }
! }
!
! /*
! * 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,
! agg_info,
! subpath->rows);
! else if (aggstrategy == AGG_SORTED)
! agg_path = (Path *) create_partial_agg_sorted_path(root, subpath,
! true,
! agg_info,
! 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_append_rel_size(PlannerInfo *root, R
*** 1138,1143 ****
--- 1291,1327 ----
adjust_appendrel_attrs(root,
(Node *) rel->joininfo,
1, &appinfo);
+ childrel->reltarget->exprs = (List *)
+ adjust_appendrel_attrs(root,
+ (Node *) rel->reltarget->exprs,
+ 1, &appinfo);
+
+ /*
+ * Setup GroupedPathInfo for the child relation if the parent has
+ * some.
+ */
+ if (rel->gpi != NULL)
+ build_child_rel_gpi(root, childrel, rel, 1, &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
+ * inner-indexscan joins on the individual children) or if the parent
+ * has useful pathkeys (because we should try to build MergeAppend
+ * paths that produce those sort orderings).
+ */
+ if (rel->has_eclass_joins || has_useful_pathkeys(root, rel))
+ add_child_rel_equivalences(root, appinfo, rel, childrel);
+ childrel->has_eclass_joins = rel->has_eclass_joins;
+
+ /*
+ * Note: we could compute appropriate attr_needed data for the child's
+ * variables, by transforming the parent's attr_needed through the
+ * translated_vars mapping. However, currently there's no need
+ * because attr_needed is only examined for base relations not
+ * otherrels. So we just leave the child's attr_needed empty.
+ */
/*
* If parallelism is allowable for this query in general, see whether
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1336,1341 ****
--- 1520,1527 ----
List *pa_partial_subpaths = NIL;
List *pa_nonpartial_subpaths = NIL;
bool partial_subpaths_valid = true;
+ List *grouped_subpaths = NIL;
+ bool grouped_subpaths_valid = true;
bool pa_subpaths_valid = enable_parallel_append;
List *all_child_pathkeys = NIL;
List *all_child_outers = NIL;
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1479,1484 ****
--- 1665,1691 ----
}
/*
+ * 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)
+ accumulate_append_subpath(path, &grouped_subpaths, NULL);
+ 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
*** 1648,1653 ****
--- 1855,1871 ----
add_partial_path(rel, (Path *) appendpath, false);
}
+ if (grouped_subpaths_valid)
+ {
+ Path *path;
+
+ path = (Path *) create_append_path(rel, grouped_subpaths, NIL, NULL, 0,
+ false, partitioned_rels, -1);
+ /* pathtarget will produce the grouped relation.. */
+ path->pathtarget = rel->gpi->target;
+ add_path(rel, path, true);
+ }
+
/*
* Also build unparameterized MergeAppend paths based on the collected
* list of child pathkeys.
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1699,1705 ****
add_path(rel, (Path *)
create_append_path(rel, subpaths, NIL,
required_outer, 0, false,
! partitioned_rels, -1), false);
}
}
--- 1917,1924 ----
add_path(rel, (Path *)
create_append_path(rel, subpaths, NIL,
required_outer, 0, false,
! partitioned_rels, -1),
! false);
}
}
*************** set_subquery_pathlist(PlannerInfo *root,
*** 2180,2187 ****
/* Generate outer path using this subpath */
add_path(rel, (Path *)
create_subqueryscan_path(root, rel, subpath,
! pathkeys, required_outer),
! false);
}
}
--- 2399,2405 ----
/* Generate outer path using this subpath */
add_path(rel, (Path *)
create_subqueryscan_path(root, rel, subpath,
! pathkeys, required_outer), false);
}
}
*************** set_worktable_pathlist(PlannerInfo *root
*** 2452,2465 ****
* 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;
/*
--- 2670,2690 ----
* 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,
*** 2467,2483 ****
* 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, false);
/*
* For each useful ordering, we can consider an order-preserving Gather
* Merge.
*/
! foreach(lc, rel->partial_pathlist)
{
Path *subpath = (Path *) lfirst(lc);
GatherMergePath *path;
--- 2692,2714 ----
* 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 && rel->gpi->target != 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,
*** 2485,2493 ****
if (subpath->pathkeys == NIL)
continue;
! path = create_gather_merge_path(root, rel, subpath, rel->reltarget,
subpath->pathkeys, NULL, NULL);
! add_path(rel, &path->path, false);
}
}
--- 2716,2724 ----
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,
*** 2659,2665 ****
generate_partition_wise_join_paths(root, rel);
/* 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);
--- 2890,2897 ----
generate_partition_wise_join_paths(root, rel);
/* 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);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
new file mode 100644
index b0c7a0c..9f489c3
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
*************** bool enable_gathermerge = true;
*** 131,136 ****
--- 131,137 ----
bool enable_partition_wise_join = false;
bool enable_parallel_append = true;
bool enable_parallel_hash = true;
+ bool enable_agg_pushdown = false;
typedef struct
{
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
new file mode 100644
index faa654b..79de6cf
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
*************** add_grouping_info_to_base_rels(PlannerIn
*** 259,264 ****
--- 259,270 ----
int i;
ListCell *lc;
+ /*
+ * Isn't user interested in the aggregate push-down feature?
+ */
+ if (!enable_agg_pushdown)
+ return;
+
/* No grouping in the query? */
if (!root->parse->groupClause)
return;
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
new file mode 100644
index 351fb93..73bccee
*** a/src/backend/optimizer/plan/planagg.c
--- b/src/backend/optimizer/plan/planagg.c
*************** preprocess_minmax_aggregates(PlannerInfo
*** 223,230 ****
create_minmaxagg_path(root, grouped_rel,
create_pathtarget(root, tlist),
aggs_list,
! (List *) parse->havingQual),
! false);
}
/*
--- 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 c1939a1..2bf4c8b
*** a/src/backend/optimizer/plan/planmain.c
--- b/src/backend/optimizer/plan/planmain.c
*************** query_planner(PlannerInfo *root, List *t
*** 83,90 ****
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);
--- 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);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 6371c34..94b1f8e
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** inheritance_planner(PlannerInfo *root)
*** 1529,1536 ****
returningLists,
rowMarks,
NULL,
! SS_assign_special_param(root)),
! false);
}
/*--------------------
--- 1529,1535 ----
returningLists,
rowMarks,
NULL,
! SS_assign_special_param(root)), false);
}
/*--------------------
*************** grouping_planner(PlannerInfo *root, bool
*** 1933,1939 ****
newpath = (Path *) create_projection_path(root,
current_rel,
subpath,
! scanjoin_target);
lfirst(lc) = newpath;
}
}
--- 1932,1939 ----
newpath = (Path *) create_projection_path(root,
current_rel,
subpath,
! scanjoin_target,
! false);
lfirst(lc) = newpath;
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 3581,3588 ****
Path *cheapest_path = input_rel->cheapest_total_path;
RelOptInfo *grouped_rel;
PathTarget *partial_grouping_target = NULL;
! AggClauseCosts agg_partial_costs; /* parallel only */
! AggClauseCosts agg_final_costs; /* parallel only */
Size hashaggtablesize;
double dNumGroups;
double dNumPartialGroups = 0;
--- 3581,3588 ----
Path *cheapest_path = input_rel->cheapest_total_path;
RelOptInfo *grouped_rel;
PathTarget *partial_grouping_target = NULL;
! AggClauseCosts agg_final_costs;
! bool agg_final_costs_known = false;
Size hashaggtablesize;
double dNumGroups;
double dNumPartialGroups = 0;
*************** create_grouping_paths(PlannerInfo *root,
*** 3767,3772 ****
--- 3767,3779 ----
}
/*
+ * Collect statistics about aggregates for estimating costs of performing
+ * aggregation in parallel.
+ */
+ MemSet(&agg_final_costs, 0, sizeof(AggClauseCosts));
+ agg_final_costs_known = false;
+
+ /*
* Before generating paths for grouped_rel, we first generate any possible
* partial paths; that way, later code can easily consider both parallel
* and non-parallel approaches to grouping. Note that the partial paths
*************** create_grouping_paths(PlannerInfo *root,
*** 3777,3782 ****
--- 3784,3792 ----
if (try_parallel_aggregation)
{
Path *cheapest_partial_path = linitial(input_rel->partial_pathlist);
+ AggClauseCosts agg_partial_costs;
+
+ MemSet(&agg_partial_costs, 0, sizeof(AggClauseCosts));
/*
* Build target list for partial aggregate paths. These paths cannot
*************** create_grouping_paths(PlannerInfo *root,
*** 3792,3817 ****
cheapest_partial_path->rows,
gd);
- /*
- * Collect statistics about aggregates for estimating costs of
- * performing aggregation in parallel.
- */
- MemSet(&agg_partial_costs, 0, sizeof(AggClauseCosts));
- MemSet(&agg_final_costs, 0, sizeof(AggClauseCosts));
if (parse->hasAggs)
{
/* partial phase */
get_agg_clause_costs(root, (Node *) partial_grouping_target->exprs,
AGGSPLIT_INITIAL_SERIAL,
&agg_partial_costs);
-
- /* final phase */
- get_agg_clause_costs(root, (Node *) target->exprs,
- AGGSPLIT_FINAL_DESERIAL,
- &agg_final_costs);
- get_agg_clause_costs(root, parse->havingQual,
- AGGSPLIT_FINAL_DESERIAL,
- &agg_final_costs);
}
if (can_sort)
--- 3802,3813 ----
*************** create_grouping_paths(PlannerInfo *root,
*** 3893,3899 ****
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups), false);
}
}
}
--- 3889,3896 ----
parse->groupClause,
NIL,
&agg_partial_costs,
! dNumPartialGroups),
! false);
}
}
}
*************** create_grouping_paths(PlannerInfo *root,
*** 3901,3917 ****
/* Build final grouping paths */
if (can_sort)
{
/*
* Use any available suitably-sorted path as input, and also consider
* sorting the cheapest-total path.
*/
! foreach(lc, input_rel->pathlist)
{
Path *path = (Path *) lfirst(lc);
bool is_sorted;
is_sorted = pathkeys_contained_in(root->group_pathkeys,
path->pathkeys);
if (path == cheapest_path || is_sorted)
{
/* Sort the cheapest-total path if it isn't already sorted */
--- 3898,3933 ----
/* Build final grouping paths */
if (can_sort)
{
+ List *pathlist = input_rel->pathlist;
+ int nplain = list_length(input_rel->pathlist);
+ int i;
+
+ /*
+ * The grouped paths created out of grouped base relations or joins
+ * can be treated almost identically here (the major difference is
+ * that their targetlists contain GroupedVars instead aggregate input
+ * vars, but this is handled by using the appropriate value of
+ * AggSplit), so process them in the same loop.
+ */
+ if (input_rel->gpi != NULL && input_rel->gpi->pathlist != NIL)
+ pathlist = list_concat(list_copy(pathlist),
+ input_rel->gpi->pathlist);
+
/*
* Use any available suitably-sorted path as input, and also consider
* sorting the cheapest-total path.
*/
! i = 0;
! foreach(lc, pathlist)
{
Path *path = (Path *) lfirst(lc);
bool is_sorted;
+ bool is_grouped;
is_sorted = pathkeys_contained_in(root->group_pathkeys,
path->pathkeys);
+ is_grouped = i >= nplain;
+
if (path == cheapest_path || is_sorted)
{
/* Sort the cheapest-total path if it isn't already sorted */
*************** create_grouping_paths(PlannerInfo *root,
*** 3925,3936 ****
--- 3941,3979 ----
/* Now decide what to stick atop it */
if (parse->groupingSets)
{
+ /*
+ * No grouping should have taken place at base relation /
+ * join level, i.e. pathlist should be equal to
+ * input_rel->pathlist.
+ */
+ Assert(!is_grouped);
+
consider_groupingsets_paths(root, grouped_rel,
path, true, can_hash, target,
gd, agg_costs, dNumGroups);
}
else if (parse->hasAggs)
{
+ AggStrategy aggstrategy;
+ AggSplit aggsplit;
+
+ if (!is_grouped)
+ {
+ aggstrategy = parse->groupClause ? AGG_SORTED : AGG_PLAIN;
+ aggsplit = AGGSPLIT_SIMPLE;
+ }
+ else
+ {
+ /*
+ * Like above, no grouping of base relation is not
+ * possible w/o this.
+ */
+ Assert(parse->groupClause);
+
+ aggstrategy = AGG_SORTED;
+ aggsplit = AGGSPLIT_FINAL_DESERIAL;
+ }
+
/*
* We have aggregation, possibly with plain GROUP BY. Make
* an AggPath.
*************** create_grouping_paths(PlannerInfo *root,
*** 3940,3947 ****
grouped_rel,
path,
target,
! parse->groupClause ? AGG_SORTED : AGG_PLAIN,
! AGGSPLIT_SIMPLE,
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
--- 3983,3990 ----
grouped_rel,
path,
target,
! aggstrategy,
! aggsplit,
parse->groupClause,
(List *) parse->havingQual,
agg_costs,
*************** create_grouping_paths(PlannerInfo *root,
*** 3968,3978 ****
Assert(false);
}
}
}
/*
! * Now generate a complete GroupAgg Path atop of the cheapest partial
! * path. We can do this using either Gather or Gather Merge.
*/
if (grouped_rel->partial_pathlist)
sort_agg_partial_grouped_paths(root,
--- 4011,4040 ----
Assert(false);
}
}
+
+ i++;
}
/*
! * Compute agg_final_costs iff the aggregation should take place in 2
! * steps.
! */
! if (parse->hasAggs &&
! (grouped_rel->partial_pathlist ||
! (input_rel->gpi != NULL && input_rel->gpi->partial_pathlist)))
! {
! 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);
!
! agg_final_costs_known = true;
! }
!
! /*
! * Gather grouped partial paths and apply AGG_SORTED to them.
*/
if (grouped_rel->partial_pathlist)
sort_agg_partial_grouped_paths(root,
*************** create_grouping_paths(PlannerInfo *root,
*** 3980,3985 ****
--- 4042,4060 ----
&agg_final_costs,
dNumGroups, grouped_rel,
partial_grouping_target, target);
+
+ /*
+ * The same for the grouped partial paths involving base relation /
+ * join grouping.
+ */
+ if (input_rel->gpi != NULL && input_rel->gpi->target != NULL &&
+ input_rel->gpi->partial_pathlist)
+ sort_agg_partial_grouped_paths(root,
+ input_rel->gpi->partial_pathlist,
+ &agg_final_costs, dNumGroups,
+ grouped_rel,
+ input_rel->gpi->target,
+ target);
}
if (can_hash)
*************** create_grouping_paths(PlannerInfo *root,
*** 4026,4031 ****
--- 4101,4122 ----
}
/*
+ * Compute agg_final_costs if needed below and if not done above.
+ */
+ if (parse->hasAggs && !agg_final_costs_known &&
+ (grouped_rel->partial_pathlist ||
+ (input_rel->gpi != NULL &&
+ (input_rel->gpi->pathlist || input_rel->gpi->partial_pathlist))))
+ {
+ 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);
+ }
+
+ /*
* Generate a HashAgg Path atop of the cheapest partial path. Once
* again, we'll only do this if it looks as though the hash table
* won't exceed work_mem.
*************** create_grouping_paths(PlannerInfo *root,
*** 4038,4043 ****
--- 4129,4181 ----
dNumGroups, grouped_rel,
partial_grouping_target, target);
}
+
+ /*
+ * If input_rel has partially aggregated paths, perform the final
+ * aggregation.
+ */
+ if (input_rel->gpi != NULL && input_rel->gpi->pathlist != NIL)
+ {
+ Path *path = (Path *) linitial(input_rel->gpi->pathlist);
+
+ hashaggtablesize = estimate_hashagg_tablesize(path,
+ &agg_final_costs,
+ dNumGroups);
+
+ if (hashaggtablesize < work_mem * 1024L)
+ {
+ /*
+ * The top-level grouped_rel needs to receive the path into
+ * regular pathlist, as opposed grouped_rel->gpi->pathlist. So
+ * pass FALSE for grouped.
+ */
+ add_path(grouped_rel,
+ (Path *) create_agg_path(root, grouped_rel,
+ path,
+ target,
+ AGG_HASHED,
+ AGGSPLIT_FINAL_DESERIAL,
+ 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.
+ */
+ if (input_rel->gpi != NULL && input_rel->gpi->target != NULL &&
+ input_rel->gpi->partial_pathlist != NIL)
+ {
+ Path *path = (Path *) linitial(input_rel->gpi->partial_pathlist);
+
+ hash_agg_partial_grouped_path(root, path, &agg_final_costs, dNumGroups,
+ grouped_rel, input_rel->gpi->target,
+ target);
+ }
}
/* Give a helpful error if we failed to find any implementation */
*************** create_distinct_paths(PlannerInfo *root,
*** 4870,4876 ****
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows), false);
}
}
--- 5008,5015 ----
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows),
! false);
}
}
*************** create_distinct_paths(PlannerInfo *root,
*** 4897,4903 ****
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows), false);
}
/*
--- 5036,5043 ----
create_upper_unique_path(root, distinct_rel,
path,
list_length(root->distinct_pathkeys),
! numDistinctRows),
! false);
}
/*
*************** adjust_paths_for_srfs(PlannerInfo *root,
*** 6011,6017 ****
newpath = (Path *) create_projection_path(root,
rel,
newpath,
! thistarget);
}
}
lfirst(lc) = newpath;
--- 6151,6158 ----
newpath = (Path *) create_projection_path(root,
rel,
newpath,
! thistarget,
! false);
}
}
lfirst(lc) = newpath;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
new file mode 100644
index b5c4124..b5be81c
*** 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? */
bool has_conv_whole_rows; /* are there ConvertRowtypeExpr
* entries encapsulating a whole-row
*************** set_upper_references(PlannerInfo *root,
*** 1739,1747 ****
--- 1740,1802 ----
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 current
+ * subplan->targetlist.
+ */
+ 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 =
+ replace_grouped_vars_with_aggrefs(root, subplan->targetlist);
+ }
+ else if (agg->aggsplit == AGGSPLIT_INITIAL_SERIAL)
+ {
+ /*
+ * Partial aggregation node can have GroupedVar's on the input
+ * if those represent generic (non-Var) grouping expressions.
+ * Unlike above, the restored expressions should stay there.
+ */
+ subplan->targetlist =
+ replace_grouped_vars_with_aggrefs(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)
*** 1996,2001 ****
--- 2051,2057 ----
itlist->tlist = tlist;
itlist->has_ph_vars = false;
+ itlist->has_grp_vars = false;
itlist->has_non_vars = false;
itlist->has_conv_whole_rows = false;
*************** build_tlist_index(List *tlist)
*** 2016,2021 ****
--- 2072,2079 ----
}
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 if (is_converted_whole_row_reference((Node *) tle->expr))
itlist->has_conv_whole_rows = true;
else
*************** fix_join_expr_mutator(Node *node, fix_jo
*** 2299,2304 ****
--- 2357,2387 ----
/* 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
*** 2461,2467 ****
/* 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 ||
(context->subplan_itlist->has_conv_whole_rows &&
is_converted_whole_row_reference(node)))
{
--- 2544,2551 ----
/* 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 ||
(context->subplan_itlist->has_conv_whole_rows &&
is_converted_whole_row_reference(node)))
{
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
new file mode 100644
index a1bea53..0aaac38
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
***************
*** 26,31 ****
--- 26,32 ----
#include "optimizer/planmain.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
+ /* TODO Remove this if create_grouped_path ends up in another module. */
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parsetree.h"
*************** static int append_startup_cost_compare(c
*** 56,62 ****
static List *reparameterize_pathlist_by_child(PlannerInfo *root,
List *pathlist,
RelOptInfo *child_rel);
!
/*****************************************************************************
* MISC. PATH UTILITIES
--- 57,65 ----
static List *reparameterize_pathlist_by_child(PlannerInfo *root,
List *pathlist,
RelOptInfo *child_rel);
! static Path *add_grouping_expressions_to_subpath(PlannerInfo *root,
! Path *subpath,
! PathTarget *group_exprs);
/*****************************************************************************
* MISC. PATH UTILITIES
*************** add_partial_path_precheck(RelOptInfo *pa
*** 973,979 ****
* completion.
*/
if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! NULL, false))
return false;
return true;
--- 976,982 ----
* completion.
*/
if (!add_path_precheck(parent_rel, total_cost, total_cost, pathkeys,
! NULL, grouped))
return false;
return true;
*************** create_unique_path(PlannerInfo *root, Re
*** 1543,1550 ****
MemoryContext oldcontext;
int numCols;
! /* Caller made a mistake if subpath isn't cheapest_total ... */
! Assert(subpath == rel->cheapest_total_path);
Assert(subpath->parent == rel);
/* ... or if SpecialJoinInfo is the wrong one */
Assert(sjinfo->jointype == JOIN_SEMI);
--- 1546,1557 ----
MemoryContext oldcontext;
int numCols;
! /*
! * Caller made a mistake if subpath isn't cheapest_total (or the cheapest
! * grouped).
! */
! Assert(subpath == rel->cheapest_total_path ||
! (rel->gpi != NULL && subpath == linitial(rel->gpi->pathlist)));
Assert(subpath->parent == rel);
/* ... or if SpecialJoinInfo is the wrong one */
Assert(sjinfo->jointype == JOIN_SEMI);
*************** create_hashjoin_path(PlannerInfo *root,
*** 2394,2405 ****
* 'rel' is the parent relation associated with the result
* 'subpath' is the path representing the source of data
* 'target' is the PathTarget to be computed
*/
ProjectionPath *
create_projection_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
! PathTarget *target)
{
ProjectionPath *pathnode = makeNode(ProjectionPath);
PathTarget *oldtarget = subpath->pathtarget;
--- 2401,2414 ----
* 'rel' is the parent relation associated with the result
* 'subpath' is the path representing the source of data
* 'target' is the PathTarget to be computed
+ * 'force_result' enforces implementation using Result plan.
*/
ProjectionPath *
create_projection_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
! PathTarget *target,
! bool force_result)
{
ProjectionPath *pathnode = makeNode(ProjectionPath);
PathTarget *oldtarget = subpath->pathtarget;
*************** create_projection_path(PlannerInfo *root
*** 2418,2423 ****
--- 2427,2433 ----
pathnode->path.pathkeys = subpath->pathkeys;
pathnode->subpath = subpath;
+ pathnode->force_result = force_result;
/*
* We might not need a separate Result node. If the input plan node type
*************** create_projection_path(PlannerInfo *root
*** 2428,2435 ****
* Note: in the latter case, create_projection_plan has to recheck our
* conclusion; see comments therein.
*/
! if (is_projection_capable_path(subpath) ||
! equal(oldtarget->exprs, target->exprs))
{
/* No separate Result node needed */
pathnode->dummypp = true;
--- 2438,2446 ----
* Note: in the latter case, create_projection_plan has to recheck our
* conclusion; see comments therein.
*/
! if (!force_result &&
! (is_projection_capable_path(subpath) ||
! equal(oldtarget->exprs, target->exprs)))
{
/* No separate Result node needed */
pathnode->dummypp = true;
*************** apply_projection_to_path(PlannerInfo *ro
*** 2499,2505 ****
* separate ProjectionPath.
*/
if (!is_projection_capable_path(path))
! return (Path *) create_projection_path(root, rel, path, target);
/*
* We can just jam the desired tlist into the existing path, being sure to
--- 2510,2517 ----
* separate ProjectionPath.
*/
if (!is_projection_capable_path(path))
! return (Path *) create_projection_path(root, rel, path, target,
! false);
/*
* We can just jam the desired tlist into the existing path, being sure to
*************** apply_projection_to_path(PlannerInfo *ro
*** 2539,2545 ****
create_projection_path(root,
gpath->subpath->parent,
gpath->subpath,
! target);
}
else
{
--- 2551,2558 ----
create_projection_path(root,
gpath->subpath->parent,
gpath->subpath,
! target,
! false);
}
else
{
*************** apply_projection_to_path(PlannerInfo *ro
*** 2549,2555 ****
create_projection_path(root,
gmpath->subpath->parent,
gmpath->subpath,
! target);
}
}
else if (path->parallel_safe &&
--- 2562,2569 ----
create_projection_path(root,
gmpath->subpath->parent,
gmpath->subpath,
! target,
! false);
}
}
else if (path->parallel_safe &&
*************** create_agg_path(PlannerInfo *root,
*** 2848,2853 ****
--- 2862,3123 ----
}
/*
+ * get_partial_agg_data
+ *
+ * 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.
+ *
+ * target_group_clauses can contain a list of target-specific
+ * SortGroupClause's, which correspond to grouping expressions possibly
+ * added to the target by initialize_grouped_target. These are not
+ * necessarily present in root->query->groupClause.
+ */
+ PartialAggInfo *
+ get_partial_agg_info(PlannerInfo *root, PathTarget *target,
+ List *target_group_clauses)
+ {
+ ListCell *l;
+ int i = 0;
+ PartialAggInfo *result;
+
+ /* The target should contain at least one grouping column. */
+ Assert(target->sortgrouprefs != NULL);
+
+ result = palloc0(sizeof(PartialAggInfo));
+
+ foreach(l, target->exprs)
+ {
+ Index sortgroupref = 0;
+ SortGroupClause *cl;
+ Expr *texpr;
+
+ texpr = (Expr *) lfirst(l);
+
+ if (IsA(texpr, GroupedVar) &&
+ IsA(((GroupedVar *) texpr)->gvexpr, Aggref))
+ {
+ /*
+ * texpr should represent the first aggregate in the targetlist.
+ */
+ break;
+ }
+
+ /*
+ * Find the clause by sortgroupref.
+ */
+ sortgroupref = target->sortgrouprefs[i++];
+
+ /*
+ * Besides being an aggregate, the target expression should have no
+ * other reason then being a column of a relation functionally
+ * dependent on the GROUP BY clause. So it's not actually a grouping
+ * column.
+ */
+ if (sortgroupref == 0)
+ continue;
+
+ cl = get_sortgroupref_clause_noerr(sortgroupref, root->parse->groupClause);
+
+ /*
+ * If query does not have this clause, it must be target-specific.
+ */
+ if (cl == NULL)
+ cl = get_sortgroupref_clause(sortgroupref, target_group_clauses);
+
+ result->group_clauses = list_append_unique(result->group_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?
+ */
+ result->group_exprs = list_append_unique(result->group_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);
+ result->agg_exprs = lappend(result->agg_exprs, gvar->agg_partial);
+ l = lnext(l);
+ }
+
+ return result;
+ }
+
+ /*
+ * Apply partial AGG_SORTED aggregation path to subpath if it's suitably
+ * sorted.
+ *
+ * check_pathkeys can be passed FALSE if the function was already called for
+ * given index --- since the target should not change, we can skip the check
+ * of sorting during subsequent calls.
+ *
+ * agg_info contains both aggregate and grouping expressions.
+ *
+ * NULL is returned if sorting of subpath output is not suitable.
+ */
+ AggPath *
+ create_partial_agg_sorted_path(PlannerInfo *root, Path *subpath,
+ bool check_pathkeys,
+ PartialAggInfo *agg_info,
+ double input_rows)
+ {
+ RelOptInfo *rel;
+ AggClauseCosts agg_costs;
+ double dNumGroups;
+ AggPath *result = NULL;
+
+ rel = subpath->parent;
+ Assert(rel->gpi != NULL);
+ Assert(rel->gpi->target != NULL);
+
+ if (subpath->pathkeys == NIL)
+ return NULL;
+
+ if (!grouping_is_sortable(root->parse->groupClause))
+ return NULL;
+
+ /*
+ * Add generic grouping expressions to the subpath if there are some, and
+ * adjust the costs accordingly.
+ */
+ if (rel->gpi->group_exprs != NULL)
+ subpath = add_grouping_expressions_to_subpath(root, subpath,
+ rel->gpi->group_exprs);
+
+ if (check_pathkeys)
+ {
+ 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;
+ }
+
+ MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
+ Assert(agg_info->agg_exprs != NIL);
+ get_agg_clause_costs(root, (Node *) agg_info->agg_exprs,
+ AGGSPLIT_INITIAL_SERIAL, &agg_costs);
+
+ Assert(agg_info->group_exprs != NIL);
+ dNumGroups = estimate_num_groups(root, agg_info->group_exprs,
+ input_rows, NULL);
+
+ Assert(agg_info->group_clauses != NIL);
+ result = create_agg_path(root, rel, subpath, rel->gpi->target,
+ AGG_SORTED, AGGSPLIT_INITIAL_SERIAL,
+ agg_info->group_clauses, NIL, &agg_costs, dNumGroups);
+
+ return result;
+ }
+
+ /*
+ * Apply 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,
+ PartialAggInfo *agg_info, 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);
+ Assert(rel->gpi->target != NULL);
+
+ /*
+ * Add generic grouping expressions to the subpath if there are some, and
+ * adjust the costs accordingly.
+ */
+ if (rel->gpi->group_exprs != NULL)
+ subpath = add_grouping_expressions_to_subpath(root, subpath,
+ rel->gpi->group_exprs);
+
+ MemSet(&agg_costs, 0, sizeof(AggClauseCosts));
+ Assert(agg_info->agg_exprs != NIL);
+ get_agg_clause_costs(root, (Node *) agg_info->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(agg_info->group_exprs != NIL);
+ dNumGroups = estimate_num_groups(root, agg_info->group_exprs,
+ input_rows, NULL);
+
+ hashaggtablesize = estimate_hashagg_tablesize(subpath, &agg_costs,
+ dNumGroups);
+
+ if (hashaggtablesize < work_mem * 1024L)
+ {
+ /*
+ * Create the partial aggregation path.
+ */
+ Assert(agg_info->group_clauses != NIL);
+
+ result = create_agg_path(root, rel, subpath,
+ rel->gpi->target,
+ AGG_HASHED,
+ AGGSPLIT_INITIAL_SERIAL,
+ agg_info->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
*
*************** reparameterize_pathlist_by_child(Planner
*** 3936,3938 ****
--- 4206,4261 ----
return result;
}
+
+ /*
+ * Add (non-Var) grouping expressions contained in group_exprs target to a
+ * subpath. The passed in subpath should not be touched so that it can still
+ * be used as a subpath for non-grouped paths. Therefore we wrap it into
+ * ResultPath, to which we actually add those expressions.
+ *
+ * Costs of the grouping expression evaluation is added to the ResultPath
+ * because the evaluation will take place here, rather than at the AggPath
+ * we're preparing data for.
+ */
+ static Path *
+ add_grouping_expressions_to_subpath(PlannerInfo *root, Path *subpath,
+ PathTarget *group_exprs)
+ {
+ PathTarget *target;
+ ListCell *lc;
+ int i;
+
+ /*
+ * The subpath must stay intact because, besides producing input data for
+ * aggregation, it can participate in a plan that does not use aggregation
+ * push-down. For the same reason we pass force_result=true to
+ * create_projection_path below.
+ */
+ target = copy_pathtarget(subpath->pathtarget);
+
+ Assert(group_exprs->sortgrouprefs != NULL);
+ i = 0;
+ foreach(lc, group_exprs->exprs)
+ {
+ Index sortgroupref;
+ GroupedVar *gvar;
+ QualCost cost;
+
+ sortgroupref = group_exprs->sortgrouprefs[i++];
+ gvar = lfirst_node(GroupedVar, lc);
+ add_column_to_pathtarget(target, (Expr *) gvar, sortgroupref);
+
+ /*
+ * Account for the cost to evaluate the grouping expressions.
+ */
+ cost_qual_eval_node(&cost, (Node *) gvar->gvexpr, root);
+ target->cost.startup += cost.startup;
+ target->cost.per_tuple += cost.per_tuple;
+ }
+
+ return (Path *) create_projection_path(root,
+ subpath->parent,
+ subpath,
+ target,
+ true);
+ }
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
new file mode 100644
index 08686f6..e4c2313
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
***************
*** 27,32 ****
--- 27,33 ----
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h"
+ #include "optimizer/var.h"
#include "parser/parse_oper.h"
#include "utils/hsearch.h"
*************** build_simple_rel(PlannerInfo *root, int
*** 126,131 ****
--- 127,133 ----
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_child_join_rel(PlannerInfo *root,
*** 797,802 ****
--- 799,857 ----
}
/*
+ * Initialize GroupedPathInfo of a child relation according to that of the
+ * parent.
+ */
+ void
+ build_child_rel_gpi(PlannerInfo *root, RelOptInfo *child, RelOptInfo *parent,
+ int nappinfos, AppendRelInfo **appinfos)
+ {
+ GroupedPathInfo *gpi,
+ *gpi_parent;
+
+ Assert(child->gpi == NULL);
+
+ gpi_parent = parent->gpi;
+ child->gpi = gpi = makeNode(GroupedPathInfo);
+
+ /*
+ * Create grouping target translated to the child varnos.
+ *
+ * We need a copy of the target so that the translation does not affect
+ * the parent.
+ */
+ gpi->target = copy_pathtarget(gpi_parent->target);
+ gpi->target->exprs = (List *)
+ adjust_appendrel_attrs(root,
+ (Node *) gpi->target->exprs,
+ nappinfos, appinfos);
+
+ /*
+ * Non-var grouping expressions need to be translated as well.
+ */
+ if (gpi_parent->group_exprs != NULL)
+ {
+ gpi->group_exprs = copy_pathtarget(gpi_parent->group_exprs);
+ gpi->group_exprs->exprs = (List *)
+ adjust_appendrel_attrs(root,
+ (Node *) gpi->group_exprs->exprs,
+ nappinfos, appinfos);
+ }
+
+ /*
+ * sortgroupclauses need no kind of translation, so just copy the pointer.
+ */
+ gpi->sortgroupclauses = gpi_parent->sortgroupclauses;
+
+ /*
+ * If the output of the child rel's paths should be aggregated,
+ * sortgrouprefs are also needed. (initialize_grouped_targets should have
+ * initialized sortgrouprefs of the parent rel.)
+ */
+ child->reltarget->sortgrouprefs = parent->reltarget->sortgrouprefs;
+ }
+
+ /*
* min_join_parameterization
*
* Determine the minimum possible parameterization of a joinrel, that is, the
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
new file mode 100644
index abdc5d9..b9c2d13
*** a/src/backend/optimizer/util/tlist.c
--- b/src/backend/optimizer/util/tlist.c
*************** get_sortgrouplist_exprs(List *sgClauses,
*** 408,414 ****
return result;
}
-
/*****************************************************************************
* Functions to extract data from a list of SortGroupClauses
*
--- 408,413 ----
*************** apply_pathtarget_labeling_to_tlist(List
*** 783,788 ****
--- 782,841 ----
}
/*
+ * Replace each GroupedVar in the source targetlist with the original
+ * expression --- either Aggref or a non-Var grouping expression.
+ *
+ * Even if the query targetlist has the Aggref wrapped in a generic
+ * expression, any subplan should emit the corresponding GroupedVar
+ * alone. (Aggregate finalization is needed before the aggregate result can be
+ * used for any purposes and that happens at the top level of the query.)
+ * Therefore we do not have to recurse into the target expressions here.
+ */
+ List *
+ replace_grouped_vars_with_aggrefs(PlannerInfo *root, List *src)
+ {
+ List *result = NIL;
+ ListCell *l;
+
+ foreach(l, src)
+ {
+ TargetEntry *te,
+ *te_new;
+ Expr *expr_new = NULL;
+
+ te = lfirst_node(TargetEntry, l);
+
+ if (IsA(te->expr, GroupedVar))
+ {
+ GroupedVar *gvar;
+
+ gvar = castNode(GroupedVar, te->expr);
+ if (IsA(gvar->gvexpr, Aggref))
+ {
+ /*
+ * Partial aggregate should appear in the targetlist so that
+ * it looks as if convert_combining_aggrefs arranged it.
+ */
+ expr_new = (Expr *) gvar->agg_partial;
+ }
+ else
+ expr_new = gvar->gvexpr;
+ }
+
+ 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 the grouped target.
*
* Caller passes the aggregates in the form of GroupedVarInfos so that we
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
new file mode 100644
index 8514c21..ad8a0b9
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** get_rule_expr(Node *node, deparse_contex
*** 7647,7652 ****
--- 7647,7669 ----
get_agg_expr((Aggref *) node, context, (Aggref *) node);
break;
+ case T_GroupedVar:
+ {
+ GroupedVar *gvar = castNode(GroupedVar, node);
+ Expr *expr = gvar->gvexpr;
+
+ if (IsA(expr, Aggref))
+ get_agg_expr(gvar->agg_partial, context, (Aggref *) gvar->gvexpr);
+ else if (IsA(expr, Var))
+ (void) get_variable((Var *) expr, 0, false, context);
+ else
+ {
+ Assert(IsA(gvar->gvexpr, OpExpr));
+ get_oper_expr((OpExpr *) expr, context);
+ }
+ break;
+ }
+
case T_GroupingFunc:
{
GroupingFunc *gexpr = (GroupingFunc *) node;
*************** get_agg_combine_expr(Node *node, deparse
*** 9132,9141 ****
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);
}
--- 9149,9166 ----
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/misc/guc.c b/src/backend/utils/misc/guc.c
new file mode 100644
index e32901d..90703c6
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
*************** static struct config_bool ConfigureNames
*** 921,926 ****
--- 921,935 ----
NULL, NULL, NULL
},
{
+ {"enable_agg_pushdown", PGC_USERSET, QUERY_TUNING_METHOD,
+ gettext_noop("Enables aggregation push-down."),
+ NULL
+ },
+ &enable_agg_pushdown,
+ false,
+ NULL, NULL, NULL
+ },
+ {
{"enable_parallel_append", PGC_USERSET, QUERY_TUNING_METHOD,
gettext_noop("Enables the planner's use of parallel append plans."),
NULL
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
new file mode 100644
index 1c93688..00e73ff
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct HashPath
*** 1537,1548 ****
--- 1537,1552 ----
* ProjectionPath node, which is marked dummy to indicate that we intend to
* assign the work to the input plan node. The estimated cost for the
* ProjectionPath node will account for whether a Result will be used or not.
+ *
+ * force_result field tells that the Result node must be used for some reason
+ * even though the subpath could normally handle the projection.
*/
typedef struct ProjectionPath
{
Path path;
Path *subpath; /* path representing input source */
bool dummypp; /* true if no separate Result is needed */
+ bool force_result; /* Is Result node required? */
} ProjectionPath;
/*
*************** typedef struct AggPath
*** 1618,1623 ****
--- 1622,1638 ----
} AggPath;
/*
+ * Information needed to create grouped paths out of base relation scan or
+ * join.
+ */
+ typedef struct PartialAggInfo
+ {
+ List *group_clauses;
+ List *group_exprs;
+ List *agg_exprs;
+ } PartialAggInfo;
+
+ /*
* Various annotations used for grouping sets in the planner.
*/
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
new file mode 100644
index 27afc2e..12d8134
*** a/src/include/optimizer/cost.h
--- b/src/include/optimizer/cost.h
*************** extern bool enable_mergejoin;
*** 68,73 ****
--- 68,74 ----
extern bool enable_hashjoin;
extern bool enable_gathermerge;
extern bool enable_partition_wise_join;
+ extern bool enable_agg_pushdown;
extern bool enable_parallel_append;
extern bool enable_parallel_hash;
extern int constraint_exclusion;
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
new file mode 100644
index 9c05492..b11bd47
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern HashPath *create_hashjoin_path(Pl
*** 163,169 ****
extern ProjectionPath *create_projection_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
! PathTarget *target);
extern Path *apply_projection_to_path(PlannerInfo *root,
RelOptInfo *rel,
Path *path,
--- 163,170 ----
extern ProjectionPath *create_projection_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
! PathTarget *target,
! bool force_result);
extern Path *apply_projection_to_path(PlannerInfo *root,
RelOptInfo *rel,
Path *path,
*************** extern AggPath *create_agg_path(PlannerI
*** 199,204 ****
--- 200,217 ----
List *qual,
const AggClauseCosts *aggcosts,
double numGroups);
+ extern PartialAggInfo *get_partial_agg_info(PlannerInfo *root,
+ PathTarget *target,
+ List *target_group_clauses);
+ extern AggPath *create_partial_agg_sorted_path(PlannerInfo *root,
+ Path *subpath,
+ bool check_pathkeys,
+ PartialAggInfo *agg_info,
+ double input_rows);
+ extern AggPath *create_partial_agg_hashed_path(PlannerInfo *root,
+ Path *subpath,
+ PartialAggInfo *agg_info,
+ double input_rows);
extern GroupingSetsPath *create_groupingsets_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
*************** extern RelOptInfo *build_child_join_rel(
*** 303,306 ****
--- 316,322 ----
RelOptInfo *parent_joinrel, List *restrictlist,
SpecialJoinInfo *sjinfo, JoinType jointype);
extern void prepare_rel_for_grouping(PlannerInfo *root, RelOptInfo *rel);
+ extern void build_child_rel_gpi(PlannerInfo *root, RelOptInfo *child,
+ RelOptInfo *parent, int nappinfos,
+ AppendRelInfo **appinfos);
#endif /* PATHNODE_H */
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
new file mode 100644
index ea886b6..7815138
*** 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,65 ----
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,
! PartialAggInfo *agg_info);
extern int compute_parallel_worker(RelOptInfo *rel, double heap_pages,
double index_pages);
extern void create_partial_bitmap_paths(PlannerInfo *root, RelOptInfo *rel,
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
new file mode 100644
index a725818..dfc9352
*** a/src/include/optimizer/tlist.h
--- b/src/include/optimizer/tlist.h
***************
*** 16,22 ****
#include "nodes/relation.h"
-
extern TargetEntry *tlist_member(Expr *node, List *targetlist);
extern TargetEntry *tlist_member_ignore_relabel(Expr *node, List *targetlist);
--- 16,21 ----
*************** extern Node *get_sortgroupclause_expr(So
*** 41,51 ****
List *targetList);
extern List *get_sortgrouplist_exprs(List *sgClauses,
List *targetList);
- extern void get_grouping_expressions(PlannerInfo *root, PathTarget *target,
- List *target_group_clauses,
- List **grouping_clauses,
- List **grouping_exprs, List **agg_exprs);
-
extern SortGroupClause *get_sortgroupref_clause(Index sortref,
List *clauses);
extern SortGroupClause *get_sortgroupref_clause_noerr(Index sortref,
--- 40,45 ----
*************** extern void split_pathtarget_at_srfs(Pla
*** 69,74 ****
--- 63,71 ----
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 *replace_grouped_vars_with_aggrefs(PlannerInfo *root, List *src);
extern void add_grouped_vars_to_target(PlannerInfo *root, PathTarget *target,
List *expressions);
extern GroupedVar *get_grouping_expression(PlannerInfo *root, Expr *expr);
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
new file mode 100644
index c9c8f51..e8ebafe
*** a/src/test/regress/expected/sysviews.out
--- b/src/test/regress/expected/sysviews.out
*************** select count(*) >= 0 as ok from pg_prepa
*** 72,77 ****
--- 72,78 ----
select name, setting from pg_settings where name like 'enable%';
name | setting
----------------------------+---------
+ enable_agg_pushdown | off
enable_bitmapscan | on
enable_gathermerge | on
enable_hashagg | on
*************** select name, setting from pg_settings wh
*** 87,93 ****
enable_seqscan | on
enable_sort | on
enable_tidscan | on
! (15 rows)
-- Test that the pg_timezone_names and pg_timezone_abbrevs views are
-- more-or-less working. We can't test their contents in any great detail
--- 88,94 ----
enable_seqscan | on
enable_sort | on
enable_tidscan | on
! (16 rows)
-- Test that the pg_timezone_names and pg_timezone_abbrevs views are
-- more-or-less working. We can't test their contents in any great detail
agg_pushdown_v5/06_join_prepare.diff
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
new file mode 100644
index 2a931af..ab739db
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
*************** set_join_pathlist_hook_type set_join_pat
*** 39,57 ****
#define PATH_PARAM_BY_REL(path, rel) \
(PATH_PARAM_BY_REL_SELF(path, rel) || PATH_PARAM_BY_PARENT(path, rel))
static void try_partial_mergejoin_path(PlannerInfo *root,
RelOptInfo *joinrel,
Path *outer_path,
Path *inner_path,
! List *pathkeys,
! List *mergeclauses,
! 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);
--- 39,72 ----
#define PATH_PARAM_BY_REL(path, rel) \
(PATH_PARAM_BY_REL_SELF(path, rel) || PATH_PARAM_BY_PARENT(path, rel))
+ /*
+ * The sort-specific input for try_mergejoin_... functions.
+ */
+ typedef struct MergeJoinSortInfo
+ {
+ List *outerkeys;
+ List *innerkeys;
+ List *merge_pathkeys;
+ List *merge_clauses;
+ } MergeJoinSortInfo;
+
static void try_partial_mergejoin_path(PlannerInfo *root,
RelOptInfo *joinrel,
Path *outer_path,
Path *inner_path,
! MergeJoinSortInfo *si,
JoinType jointype,
JoinPathExtraData *extra);
static void sort_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
+ static void sort_inner_and_outer_common(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ JoinType jointype,
+ JoinPathExtraData *extra,
+ List *sort_infos);
static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
*************** try_partial_nestloop_path(PlannerInfo *r
*** 547,552 ****
--- 562,680 ----
NULL), false);
}
+ static void
+ try_nestloop_path_common(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ Path *outer_path,
+ Path *inner_path,
+ List *pathkeys,
+ JoinType jointype,
+ JoinPathExtraData *extra,
+ bool partial)
+ {
+
+ /* Only join plain paths. */
+ if (!partial)
+ try_nestloop_path(root,
+ joinrel,
+ outer_path,
+ inner_path,
+ pathkeys,
+ jointype,
+ extra);
+ else
+ try_partial_nestloop_path(root,
+ joinrel,
+ outer_path,
+ inner_path,
+ pathkeys,
+ jointype,
+ extra);
+ }
+
+
+ /*
+ * Create one MergeJoinSortInfo for each possible use of merge clauses.
+ */
+ static List *
+ get_merge_join_sort_info(PlannerInfo *root, JoinPathExtraData *extra,
+ RelOptInfo *joinrel, JoinType jointype)
+ {
+ List *all_pathkeys;
+ ListCell *l;
+ List *result = NIL;
+
+ /*
+ * Each possible ordering of the available mergejoin clauses will generate
+ * a differently-sorted result path at essentially the same cost. We have
+ * no basis for choosing one over another at this level of joining, but
+ * some sort orders may be more useful than others for higher-level
+ * mergejoins, so it's worth considering multiple orderings.
+ *
+ * Actually, it's not quite true that every mergeclause ordering will
+ * generate a different path order, because some of the clauses may be
+ * partially redundant (refer to the same EquivalenceClasses). Therefore,
+ * what we do is convert the mergeclause list to a list of canonical
+ * pathkeys, and then consider different orderings of the pathkeys.
+ *
+ * Generating a path for *every* permutation of the pathkeys doesn't seem
+ * like a winning strategy; the cost in planning time is too high. For
+ * now, we generate one path for each pathkey, listing that pathkey first
+ * and the rest in random order. This should allow at least a one-clause
+ * mergejoin without re-sorting against any other possible mergejoin
+ * partner path. But if we've not guessed the right ordering of secondary
+ * keys, we may end up evaluating clauses as qpquals when they could have
+ * been done as mergeclauses. (In practice, it's rare that there's more
+ * than two or three mergeclauses, so expending a huge amount of thought
+ * on that is probably not worth it.)
+ *
+ * The pathkey order returned by select_outer_pathkeys_for_merge() has
+ * some heuristics behind it (see that function), so be sure to try it
+ * exactly as-is as well as making variants.
+ */
+ all_pathkeys = select_outer_pathkeys_for_merge(root,
+ extra->mergeclause_list,
+ joinrel);
+
+ foreach(l, all_pathkeys)
+ {
+ List *front_pathkey = (List *) lfirst(l);
+ MergeJoinSortInfo *si;
+
+ si = (MergeJoinSortInfo *) palloc(sizeof(MergeJoinSortInfo));
+
+ /* Make a pathkey list with this guy first */
+ if (l != list_head(all_pathkeys))
+ si->outerkeys = lcons(front_pathkey,
+ list_delete_ptr(list_copy(all_pathkeys),
+ front_pathkey));
+ else
+ si->outerkeys = all_pathkeys; /* no work at first one... */
+
+ /* Sort the mergeclauses into the corresponding ordering */
+ si->merge_clauses = find_mergeclauses_for_pathkeys(root,
+ si->outerkeys,
+ true,
+ extra->mergeclause_list);
+
+ /* Should have used them all... */
+ Assert(list_length(si->merge_clauses) == list_length(extra->mergeclause_list));
+
+ /* Build sort pathkeys for the inner side */
+ si->innerkeys = make_inner_pathkeys_for_merge(root,
+ si->merge_clauses,
+ si->outerkeys);
+
+ /* Build pathkeys representing output sort order */
+ si->merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
+ si->outerkeys);
+
+ result = lappend(result, si);
+ }
+
+ return result;
+ }
+
/*
* try_mergejoin_path
* Consider a merge join path; if it appears useful, push it into
*************** try_mergejoin_path(PlannerInfo *root,
*** 557,587 ****
RelOptInfo *joinrel,
Path *outer_path,
Path *inner_path,
! List *pathkeys,
! List *mergeclauses,
! List *outersortkeys,
! List *innersortkeys,
JoinType jointype,
! JoinPathExtraData *extra,
! bool is_partial)
{
Relids required_outer;
JoinCostWorkspace workspace;
!
! if (is_partial)
! {
! try_partial_mergejoin_path(root,
! joinrel,
! outer_path,
! inner_path,
! pathkeys,
! mergeclauses,
! outersortkeys,
! innersortkeys,
! jointype,
! extra);
! return;
! }
/*
* Check to see if proposed path is still parameterized, and reject if the
--- 685,698 ----
RelOptInfo *joinrel,
Path *outer_path,
Path *inner_path,
! MergeJoinSortInfo *si,
JoinType jointype,
! JoinPathExtraData *extra)
{
Relids required_outer;
JoinCostWorkspace workspace;
! List *outersortkeys = si->outerkeys;
! List *innersortkeys = si->innerkeys;
/*
* Check to see if proposed path is still parameterized, and reject if the
*************** try_mergejoin_path(PlannerInfo *root,
*** 611,624 ****
/*
* See comments in try_nestloop_path().
*/
! 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, false))
{
add_path(joinrel, (Path *)
create_mergejoin_path(root,
--- 722,734 ----
/*
* See comments in try_nestloop_path().
*/
! initial_cost_mergejoin(root, &workspace, jointype, si->merge_clauses,
outer_path, inner_path,
! outersortkeys, innersortkeys, extra);
if (add_path_precheck(joinrel,
workspace.startup_cost, workspace.total_cost,
! si->merge_pathkeys, required_outer, false))
{
add_path(joinrel, (Path *)
create_mergejoin_path(root,
*************** try_mergejoin_path(PlannerInfo *root,
*** 629,637 ****
outer_path,
inner_path,
extra->restrictlist,
! pathkeys,
required_outer,
! mergeclauses,
outersortkeys,
innersortkeys), false);
}
--- 739,747 ----
outer_path,
inner_path,
extra->restrictlist,
! si->merge_pathkeys,
required_outer,
! si->merge_clauses,
outersortkeys,
innersortkeys), false);
}
*************** try_partial_mergejoin_path(PlannerInfo *
*** 652,665 ****
RelOptInfo *joinrel,
Path *outer_path,
Path *inner_path,
! List *pathkeys,
! List *mergeclauses,
! List *outersortkeys,
! List *innersortkeys,
JoinType jointype,
JoinPathExtraData *extra)
{
JoinCostWorkspace workspace;
/*
* See comments in try_partial_hashjoin_path().
--- 762,774 ----
RelOptInfo *joinrel,
Path *outer_path,
Path *inner_path,
! MergeJoinSortInfo *si,
JoinType jointype,
JoinPathExtraData *extra)
{
JoinCostWorkspace workspace;
+ List *outersortkeys = si->outerkeys;
+ List *innersortkeys = si->innerkeys;
/*
* See comments in try_partial_hashjoin_path().
*************** try_partial_mergejoin_path(PlannerInfo *
*** 687,698 ****
/*
* See comments in try_partial_nestloop_path().
*/
! initial_cost_mergejoin(root, &workspace, jointype, mergeclauses,
outer_path, inner_path,
! outersortkeys, innersortkeys,
! extra);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys,
false))
return;
--- 796,807 ----
/*
* See comments in try_partial_nestloop_path().
*/
! initial_cost_mergejoin(root, &workspace, jointype, si->merge_clauses,
outer_path, inner_path,
! outersortkeys, innersortkeys, extra);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost,
! si->merge_pathkeys,
false))
return;
*************** try_partial_mergejoin_path(PlannerInfo *
*** 706,719 ****
outer_path,
inner_path,
extra->restrictlist,
! pathkeys,
NULL,
! mergeclauses,
outersortkeys,
innersortkeys), false);
}
/*
* try_hashjoin_path
* Consider a hash join path; if it appears useful, push it into
* the joinrel's pathlist via add_path().
--- 815,861 ----
outer_path,
inner_path,
extra->restrictlist,
! si->merge_pathkeys,
NULL,
! si->merge_clauses,
outersortkeys,
innersortkeys), false);
}
/*
+ * Generic front-end to create combinations of full/partial and grouped/plain
+ * merge joins.
+ */
+ static void
+ try_mergejoin_path_common(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ Path *outer_path,
+ Path *inner_path,
+ MergeJoinSortInfo *si,
+ JoinType jointype,
+ JoinPathExtraData *extra,
+ bool partial)
+ {
+ /* Only join plain paths. */
+ if (!partial)
+ try_mergejoin_path(root,
+ joinrel,
+ outer_path,
+ inner_path,
+ si,
+ jointype,
+ extra);
+ else
+ try_partial_mergejoin_path(root,
+ joinrel,
+ outer_path,
+ inner_path,
+ si,
+ jointype,
+ extra);
+ }
+
+ /*
* try_hashjoin_path
* Consider a hash join path; if it appears useful, push it into
* the joinrel's pathlist via add_path().
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 836,841 ****
--- 978,1021 ----
}
/*
+ * Generic front-end to create combinations of full/partial and grouped/plain
+ * hash joins.
+ */
+ static void
+ try_hashjoin_path_common(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ Path *outer_path,
+ Path *inner_path,
+ List *hashclauses,
+ JoinType jointype,
+ JoinPathExtraData *extra,
+ bool partial,
+ bool parallel_hash)
+ {
+ /* parallel_hash can only be set if partial is too. */
+ Assert(!parallel_hash || partial);
+
+ /* Only join plain paths. */
+ if (!partial)
+ try_hashjoin_path(root,
+ joinrel,
+ outer_path,
+ inner_path,
+ hashclauses,
+ jointype,
+ extra);
+ else
+ try_partial_hashjoin_path(root,
+ joinrel,
+ outer_path,
+ inner_path,
+ hashclauses,
+ jointype,
+ extra,
+ parallel_hash);
+ }
+
+ /*
* clause_sides_match_join
* Determine whether a join clause is of the right form to use in this join.
*
*************** sort_inner_and_outer(PlannerInfo *root,
*** 885,896 ****
JoinType jointype,
JoinPathExtraData *extra)
{
JoinType save_jointype = jointype;
Path *outer_path;
Path *inner_path;
Path *cheapest_partial_outer = NULL;
Path *cheapest_safe_inner = NULL;
- List *all_pathkeys;
ListCell *l;
/*
--- 1065,1098 ----
JoinType jointype,
JoinPathExtraData *extra)
{
+ List *sort_infos = get_merge_join_sort_info(root, extra, joinrel,
+ jointype);
+
+
+ sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
+ jointype, extra, sort_infos);
+ }
+
+ /*
+ * The workhorse of sort_inner_and_outer().
+ *
+ * sort_infos contains one MergeJoinSortInfo per possible sorting of the input
+ * paths that we should consider.
+ */
+ static void
+ sort_inner_and_outer_common(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ JoinType jointype,
+ JoinPathExtraData *extra,
+ List *sort_infos)
+ {
JoinType save_jointype = jointype;
Path *outer_path;
Path *inner_path;
Path *cheapest_partial_outer = NULL;
Path *cheapest_safe_inner = NULL;
ListCell *l;
/*
*************** sort_inner_and_outer(PlannerInfo *root,
*** 962,1032 ****
get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
}
! /*
! * Each possible ordering of the available mergejoin clauses will generate
! * a differently-sorted result path at essentially the same cost. We have
! * no basis for choosing one over another at this level of joining, but
! * some sort orders may be more useful than others for higher-level
! * mergejoins, so it's worth considering multiple orderings.
! *
! * Actually, it's not quite true that every mergeclause ordering will
! * generate a different path order, because some of the clauses may be
! * partially redundant (refer to the same EquivalenceClasses). Therefore,
! * what we do is convert the mergeclause list to a list of canonical
! * pathkeys, and then consider different orderings of the pathkeys.
! *
! * Generating a path for *every* permutation of the pathkeys doesn't seem
! * like a winning strategy; the cost in planning time is too high. For
! * now, we generate one path for each pathkey, listing that pathkey first
! * and the rest in random order. This should allow at least a one-clause
! * mergejoin without re-sorting against any other possible mergejoin
! * partner path. But if we've not guessed the right ordering of secondary
! * keys, we may end up evaluating clauses as qpquals when they could have
! * been done as mergeclauses. (In practice, it's rare that there's more
! * than two or three mergeclauses, so expending a huge amount of thought
! * on that is probably not worth it.)
! *
! * The pathkey order returned by select_outer_pathkeys_for_merge() has
! * some heuristics behind it (see that function), so be sure to try it
! * exactly as-is as well as making variants.
! */
! all_pathkeys = select_outer_pathkeys_for_merge(root,
! extra->mergeclause_list,
! joinrel);
!
! foreach(l, all_pathkeys)
{
! List *front_pathkey = (List *) lfirst(l);
! List *cur_mergeclauses;
! List *outerkeys;
! List *innerkeys;
! List *merge_pathkeys;
!
! /* Make a pathkey list with this guy first */
! if (l != list_head(all_pathkeys))
! outerkeys = lcons(front_pathkey,
! list_delete_ptr(list_copy(all_pathkeys),
! front_pathkey));
! else
! outerkeys = all_pathkeys; /* no work at first one... */
!
! /* Sort the mergeclauses into the corresponding ordering */
! cur_mergeclauses = find_mergeclauses_for_pathkeys(root,
! outerkeys,
! true,
! extra->mergeclause_list);
!
! /* Should have used them all... */
! Assert(list_length(cur_mergeclauses) == list_length(extra->mergeclause_list));
!
! /* Build sort pathkeys for the inner side */
! innerkeys = make_inner_pathkeys_for_merge(root,
! cur_mergeclauses,
! outerkeys);
!
! /* Build pathkeys representing output sort order */
! merge_pathkeys = build_join_pathkeys(root, joinrel, jointype,
! outerkeys);
/*
* And now we can make the path.
--- 1164,1172 ----
get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
}
! foreach(l, sort_infos)
{
! MergeJoinSortInfo *si = (MergeJoinSortInfo *) lfirst(l);
/*
* And now we can make the path.
*************** sort_inner_and_outer(PlannerInfo *root,
*** 1035,1067 ****
* 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);
}
}
--- 1175,1193 ----
* properly. try_mergejoin_path will detect that case and suppress an
* explicit sort step, so we needn't do so here.
*/
! try_mergejoin_path_common(root, joinrel, outer_path, inner_path,
! si, 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_mergejoin_path_common(root, joinrel,
! cheapest_partial_outer,
! cheapest_safe_inner,
! si, jointype, extra,
! true);
}
}
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1091,1113 ****
List *merge_pathkeys,
bool is_partial)
{
- List *mergeclauses;
- List *innersortkeys;
List *trialsortkeys;
Path *cheapest_startup_inner;
Path *cheapest_total_inner;
JoinType save_jointype = jointype;
int num_sortkeys;
int sortkeycnt;
if (jointype == JOIN_UNIQUE_OUTER || jointype == JOIN_UNIQUE_INNER)
jointype = JOIN_INNER;
/* Look for useful mergeclauses (if any) */
! mergeclauses = find_mergeclauses_for_pathkeys(root,
! outerpath->pathkeys,
! true,
! extra->mergeclause_list);
/*
* Done with this outer path if no chance for a mergejoin.
--- 1217,1240 ----
List *merge_pathkeys,
bool is_partial)
{
List *trialsortkeys;
Path *cheapest_startup_inner;
Path *cheapest_total_inner;
JoinType save_jointype = jointype;
int num_sortkeys;
int sortkeycnt;
+ MergeJoinSortInfo si;
if (jointype == JOIN_UNIQUE_OUTER || jointype == JOIN_UNIQUE_INNER)
jointype = JOIN_INNER;
+ si.merge_pathkeys = merge_pathkeys;
/* Look for useful mergeclauses (if any) */
! si.merge_clauses = find_mergeclauses_for_pathkeys(root,
! outerpath->pathkeys,
! true,
! extra->mergeclause_list);
! si.outerkeys = NIL;
/*
* Done with this outer path if no chance for a mergejoin.
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1118,1124 ****
* without any join clauses, it's necessary to generate a clauseless
* mergejoin path instead.
*/
! if (mergeclauses == NIL)
{
if (jointype == JOIN_FULL)
/* okay to try for mergejoin */ ;
--- 1245,1251 ----
* without any join clauses, it's necessary to generate a clauseless
* mergejoin path instead.
*/
! if (si.merge_clauses == NIL)
{
if (jointype == JOIN_FULL)
/* okay to try for mergejoin */ ;
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1126,1138 ****
return;
}
if (useallclauses &&
! list_length(mergeclauses) != list_length(extra->mergeclause_list))
return;
/* Compute the required ordering of the inner path */
! innersortkeys = make_inner_pathkeys_for_merge(root,
! mergeclauses,
! outerpath->pathkeys);
/*
* Generate a mergejoin on the basis of sorting the cheapest inner. Since
--- 1253,1265 ----
return;
}
if (useallclauses &&
! list_length(si.merge_clauses) != list_length(extra->mergeclause_list))
return;
/* Compute the required ordering of the inner path */
! si.innerkeys = make_inner_pathkeys_for_merge(root,
! si.merge_clauses,
! outerpath->pathkeys);
/*
* Generate a mergejoin on the basis of sorting the cheapest inner. Since
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1140,1156 ****
* 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)
--- 1267,1280 ----
* 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,
! &si,
! jointype,
! extra,
! is_partial);
/* Can't do anything else if inner path needs to be unique'd */
if (save_jointype == JOIN_UNIQUE_INNER)
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1185,1191 ****
* sorting inner_cheapest_total, whereas we did sort it above, so the
* plans being considered are different.
*/
! if (pathkeys_contained_in(innersortkeys,
inner_cheapest_total->pathkeys))
{
/* inner_cheapest_total didn't require a sort */
--- 1309,1315 ----
* sorting inner_cheapest_total, whereas we did sort it above, so the
* plans being considered are different.
*/
! if (pathkeys_contained_in(si.innerkeys,
inner_cheapest_total->pathkeys))
{
/* inner_cheapest_total didn't require a sort */
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1198,1213 ****
cheapest_startup_inner = NULL;
cheapest_total_inner = NULL;
}
! num_sortkeys = list_length(innersortkeys);
if (num_sortkeys > 1 && !useallclauses)
! trialsortkeys = list_copy(innersortkeys); /* need modifiable copy */
else
! trialsortkeys = innersortkeys; /* won't really truncate */
for (sortkeycnt = num_sortkeys; sortkeycnt > 0; sortkeycnt--)
{
Path *innerpath;
! List *newclauses = NIL;
/*
* Look for an inner path ordered well enough for the first
--- 1322,1342 ----
cheapest_startup_inner = NULL;
cheapest_total_inner = NULL;
}
! num_sortkeys = list_length(si.innerkeys);
if (num_sortkeys > 1 && !useallclauses)
! trialsortkeys = list_copy(si.innerkeys); /* need modifiable copy */
else
! trialsortkeys = si.innerkeys; /* won't really truncate */
for (sortkeycnt = num_sortkeys; sortkeycnt > 0; sortkeycnt--)
{
Path *innerpath;
! MergeJoinSortInfo si_trial;
!
! si_trial.outerkeys = NIL;
! si_trial.innerkeys = NIL;
! si_trial.merge_pathkeys = si.merge_pathkeys;
! si_trial.merge_clauses = NIL;
/*
* Look for an inner path ordered well enough for the first
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1229,1254 ****
/* Select the right mergeclauses, if we didn't already */
if (sortkeycnt < num_sortkeys)
{
! newclauses =
find_mergeclauses_for_pathkeys(root,
trialsortkeys,
false,
! mergeclauses);
! Assert(newclauses != NIL);
}
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 ... */
--- 1358,1381 ----
/* Select the right mergeclauses, if we didn't already */
if (sortkeycnt < num_sortkeys)
{
! si_trial.merge_clauses =
find_mergeclauses_for_pathkeys(root,
trialsortkeys,
false,
! si.merge_clauses);
! Assert(si_trial.merge_clauses != NIL);
}
else
! si_trial.merge_clauses = si.merge_clauses;
!
! try_mergejoin_path_common(root,
! joinrel,
! outerpath,
! innerpath,
! &si,
! jointype,
! extra,
! is_partial);
cheapest_total_inner = innerpath;
}
/* Same on the basis of cheapest startup cost ... */
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1269,1299 ****
* Avoid rebuilding clause list if we already made one; saves
* memory in big join trees...
*/
! if (newclauses == NIL)
{
if (sortkeycnt < num_sortkeys)
{
! newclauses =
find_mergeclauses_for_pathkeys(root,
trialsortkeys,
false,
! mergeclauses);
! Assert(newclauses != NIL);
}
else
! newclauses = mergeclauses;
}
! try_mergejoin_path(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! newclauses,
! NIL,
! NIL,
! jointype,
! extra,
! is_partial);
}
cheapest_startup_inner = innerpath;
}
--- 1396,1423 ----
* Avoid rebuilding clause list if we already made one; saves
* memory in big join trees...
*/
! if (si_trial.merge_clauses == NIL)
{
if (sortkeycnt < num_sortkeys)
{
! si_trial.merge_clauses =
find_mergeclauses_for_pathkeys(root,
trialsortkeys,
false,
! si.merge_clauses);
! Assert(si_trial.merge_clauses != NIL);
}
else
! si_trial.merge_clauses = si.merge_clauses;
}
! try_mergejoin_path_common(root,
! joinrel,
! outerpath,
! innerpath,
! &si,
! jointype,
! extra,
! is_partial);
}
cheapest_startup_inner = innerpath;
}
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1306,1341 ****
}
}
- /*
- * match_unsorted_outer
- * Creates possible join paths for processing a single join relation
- * 'joinrel' by employing either iterative substitution or
- * mergejoining on each of its possible outer paths (considering
- * only outer paths that are already ordered well enough for merging).
- *
- * We always generate a nestloop path for each available outer path.
- * In fact we may generate as many as five: one on the cheapest-total-cost
- * inner path, one on the same with materialization, one on the
- * cheapest-startup-cost inner path (if different), one on the
- * cheapest-total inner-indexscan path (if any), and one on the
- * cheapest-startup inner-indexscan path (if different).
- *
- * We also consider mergejoins if mergejoin clauses are available. See
- * detailed comments in generate_mergejoin_paths.
- *
- * 'joinrel' is the join relation
- * 'outerrel' is the outer join relation
- * 'innerrel' is the inner join relation
- * 'jointype' is the type of join to do
- * 'extra' contains additional input values
- */
static void
! match_unsorted_outer(PlannerInfo *root,
! RelOptInfo *joinrel,
! RelOptInfo *outerrel,
! RelOptInfo *innerrel,
! JoinType jointype,
! JoinPathExtraData *extra)
{
JoinType save_jointype = jointype;
bool nestjoinOK;
--- 1430,1442 ----
}
}
static void
! match_unsorted_outer_common(PlannerInfo *root,
! RelOptInfo *joinrel,
! RelOptInfo *outerrel,
! RelOptInfo *innerrel,
! JoinType jointype,
! JoinPathExtraData *extra)
{
JoinType save_jointype = jointype;
bool nestjoinOK;
*************** match_unsorted_outer(PlannerInfo *root,
*** 1396,1401 ****
--- 1497,1503 ----
/* No way to do this with an inner path parameterized by outer rel */
if (inner_cheapest_total == NULL)
return;
+
inner_cheapest_total = (Path *)
create_unique_path(root, innerrel, inner_cheapest_total, extra->sjinfo);
Assert(inner_cheapest_total);
*************** match_unsorted_outer(PlannerInfo *root,
*** 1433,1438 ****
--- 1535,1541 ----
{
if (outerpath != outerrel->cheapest_total_path)
continue;
+
outerpath = (Path *) create_unique_path(root, outerrel,
outerpath, extra->sjinfo);
Assert(outerpath);
*************** match_unsorted_outer(PlannerInfo *root,
*** 1452,1464 ****
* Consider nestloop join, but only with the unique-ified cheapest
* inner path
*/
! try_nestloop_path(root,
! joinrel,
! outerpath,
! inner_cheapest_total,
! merge_pathkeys,
! jointype,
! extra);
}
else if (nestjoinOK)
{
--- 1555,1568 ----
* Consider nestloop join, but only with the unique-ified cheapest
* inner path
*/
! try_nestloop_path_common(root,
! joinrel,
! outerpath,
! inner_cheapest_total,
! merge_pathkeys,
! jointype,
! extra,
! false);
}
else if (nestjoinOK)
{
*************** match_unsorted_outer(PlannerInfo *root,
*** 1474,1497 ****
{
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 */
--- 1578,1603 ----
{
Path *innerpath = (Path *) lfirst(lc2);
! try_nestloop_path_common(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! jointype,
! extra,
! false);
}
! /* Also consider materialized form of the cheapest inner path. */
if (matpath != NULL)
! try_nestloop_path_common(root,
! joinrel,
! outerpath,
! matpath,
! merge_pathkeys,
! jointype,
! extra,
! false);
}
/* Can't do anything else if outer path needs to be unique'd */
*************** match_unsorted_outer(PlannerInfo *root,
*** 1552,1557 ****
--- 1658,1698 ----
}
/*
+ * match_unsorted_outer
+ * Creates possible join paths for processing a single join relation
+ * 'joinrel' by employing either iterative substitution or
+ * mergejoining on each of its possible outer paths (considering
+ * only outer paths that are already ordered well enough for merging).
+ *
+ * We always generate a nestloop path for each available outer path.
+ * In fact we may generate as many as five: one on the cheapest-total-cost
+ * inner path, one on the same with materialization, one on the
+ * cheapest-startup-cost inner path (if different), one on the
+ * cheapest-total inner-indexscan path (if any), and one on the
+ * cheapest-startup inner-indexscan path (if different).
+ *
+ * We also consider mergejoins if mergejoin clauses are available. See
+ * detailed comments in generate_mergejoin_paths.
+ *
+ * 'joinrel' is the join relation
+ * 'outerrel' is the outer join relation
+ * 'innerrel' is the inner join relation
+ * 'jointype' is the type of join to do
+ * 'extra' contains additional input values
+ */
+ static void
+ match_unsorted_outer(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ JoinType jointype,
+ JoinPathExtraData *extra)
+ {
+ match_unsorted_outer_common(root, joinrel, outerrel, innerrel,
+ jointype, extra);
+ }
+
+ /*
* consider_parallel_mergejoin
* Try to build partial paths for a joinrel by joining a partial path
* for the outer relation to a complete path for the inner relation.
*************** consider_parallel_nestloop(PlannerInfo *
*** 1658,1667 ****
Assert(innerpath);
}
! try_partial_nestloop_path(root, joinrel, outerpath, innerpath,
! pathkeys, jointype, extra);
}
}
}
/*
--- 1799,2029 ----
Assert(innerpath);
}
! try_nestloop_path_common(root, joinrel, outerpath, innerpath,
! pathkeys, jointype, extra, true);
! }
! }
! }
!
! /*
! * The workhorse of hash_inner_and_outer(). The hashclauses list is passed by
! * caller because it does not differ between calls.
! */
! static void
! hash_inner_and_outer_common(PlannerInfo *root,
! RelOptInfo *joinrel,
! RelOptInfo *outerrel,
! RelOptInfo *innerrel,
! JoinType jointype,
! JoinPathExtraData *extra,
! List *hashclauses)
! {
! JoinType save_jointype = jointype;
!
! /*
! * We consider both the cheapest-total-cost and cheapest-startup-cost
! * outer paths. There's no need to consider any but the
! * cheapest-total-cost inner path, however.
! */
! Path *cheapest_startup_outer,
! *cheapest_total_outer,
! *cheapest_total_inner;
!
! /* Shouldn't be called w/o hashclauses. */
! Assert(hashclauses != NIL);
!
! cheapest_startup_outer = outerrel->cheapest_startup_path;
! cheapest_total_outer = outerrel->cheapest_total_path;
! cheapest_total_inner = innerrel->cheapest_total_path;
!
! /*
! * If either cheapest-total path is parameterized by the other rel, we
! * can't use a hashjoin. (There's no use looking for alternative input
! * paths, since these should already be the least-parameterized available
! * paths.)
! */
! if (PATH_PARAM_BY_REL(cheapest_total_outer, innerrel) ||
! PATH_PARAM_BY_REL(cheapest_total_inner, outerrel))
! return;
!
! /* Unique-ify if need be; we ignore parameterized possibilities */
! if (jointype == JOIN_UNIQUE_OUTER)
! {
! cheapest_total_outer = (Path *)
! create_unique_path(root, outerrel,
! cheapest_total_outer, extra->sjinfo);
! Assert(cheapest_total_outer);
! jointype = JOIN_INNER;
! try_hashjoin_path_common(root,
! joinrel,
! cheapest_total_outer,
! cheapest_total_inner,
! hashclauses,
! jointype,
! extra,
! false,
! false);
! /* no possibility of cheap startup here */
! }
! else if (jointype == JOIN_UNIQUE_INNER)
! {
! cheapest_total_inner = (Path *)
! create_unique_path(root, innerrel,
! cheapest_total_inner, extra->sjinfo);
! Assert(cheapest_total_inner);
! jointype = JOIN_INNER;
! try_hashjoin_path_common(root,
! joinrel,
! cheapest_total_outer,
! cheapest_total_inner,
! hashclauses,
! jointype,
! extra,
! false,
! false);
!
! if (cheapest_startup_outer != NULL &&
! cheapest_startup_outer != cheapest_total_outer)
! try_hashjoin_path_common(root,
! joinrel,
! cheapest_startup_outer,
! cheapest_total_inner,
! hashclauses,
! jointype,
! extra,
! false,
! false);
! }
! 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.
! *
! * For if either side of the join is grouped, we simply use
! * rel->gpi->pathlist. (cheapest_startup_outer should be NULL if
! * grouped_outer.)
! */
! ListCell *lc1;
! ListCell *lc2;
!
! if (cheapest_startup_outer != NULL)
! try_hashjoin_path_common(root,
! joinrel,
! cheapest_startup_outer,
! cheapest_total_inner,
! hashclauses,
! jointype,
! extra,
! false,
! false);
!
! 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_common(root,
! joinrel,
! outerpath,
! innerpath,
! hashclauses,
! jointype,
! extra,
! false,
! false);
! }
}
}
+
+ /*
+ * If the joinrel is parallel-safe, we may be able to consider a partial
+ * hash join. However, we can't handle JOIN_UNIQUE_OUTER, because the
+ * outer path will be partial, and therefore we won't be able to properly
+ * guarantee uniqueness. Similarly, we can't handle JOIN_FULL and
+ * JOIN_RIGHT, because they can produce false null extended rows. Also,
+ * the resulting path must not be parameterized. We would be able to
+ * support JOIN_FULL and JOIN_RIGHT for Parallel Hash, since in that case
+ * we're back to a single hash table with a single set of match bits for
+ * each batch, but that will require figuring out a deadlock-free way to
+ * wait for the probe to finish.
+ */
+ if (joinrel->consider_parallel &&
+ save_jointype != JOIN_UNIQUE_OUTER &&
+ save_jointype != JOIN_FULL &&
+ save_jointype != JOIN_RIGHT &&
+ outerrel->partial_pathlist != NIL &&
+ bms_is_empty(joinrel->lateral_relids))
+ {
+ Path *cheapest_partial_outer;
+ Path *cheapest_partial_inner = NULL;
+ Path *cheapest_safe_inner = NULL;
+
+ cheapest_partial_outer =
+ (Path *) linitial(outerrel->partial_pathlist);
+
+ /*
+ * Can we use a partial inner plan too, so that we can build a shared
+ * hash table in parallel?
+ */
+ if (innerrel->partial_pathlist != NIL && enable_parallel_hash)
+ {
+ cheapest_partial_inner =
+ (Path *) linitial(innerrel->partial_pathlist);
+ try_hashjoin_path_common(root, joinrel,
+ cheapest_partial_outer,
+ cheapest_partial_inner,
+ hashclauses, jointype, extra,
+ true,
+ true /* parallel_hash */ );
+ }
+
+
+ /*
+ * Normally, given that the joinrel is parallel-safe, the cheapest
+ * total inner path will also be parallel-safe, but if not, we'll have
+ * to search cheapest_parameterized_paths for the cheapest safe,
+ * unparameterized inner path. If doing JOIN_UNIQUE_INNER, we can't
+ * use any alternative inner path.
+ */
+ if (cheapest_total_inner->parallel_safe)
+ cheapest_safe_inner = cheapest_total_inner;
+ else if (save_jointype != JOIN_UNIQUE_INNER)
+ cheapest_safe_inner =
+ get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
+
+ if (cheapest_safe_inner != NULL)
+ try_hashjoin_path_common(root, joinrel,
+ cheapest_partial_outer,
+ cheapest_safe_inner,
+ hashclauses, jointype, extra,
+ true,
+ false); /* parallel_hash */
+ }
}
/*
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1683,1691 ****
JoinType jointype,
JoinPathExtraData *extra)
{
- JoinType save_jointype = jointype;
bool isouterjoin = IS_OUTER_JOIN(jointype);
! List *hashclauses;
ListCell *l;
/*
--- 2045,2052 ----
JoinType jointype,
JoinPathExtraData *extra)
{
bool isouterjoin = IS_OUTER_JOIN(jointype);
! List *hashclauses = NIL;
ListCell *l;
/*
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1695,1701 ****
* Scan the join's restrictinfo list to find hashjoinable clauses that are
* usable with this pair of sub-relations.
*/
- hashclauses = NIL;
foreach(l, extra->restrictlist)
{
RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);
--- 2056,2061 ----
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1720,1908 ****
hashclauses = lappend(hashclauses, restrictinfo);
}
! /* If we found any usable hashclauses, make paths */
! if (hashclauses)
! {
! /*
! * We consider both the cheapest-total-cost and cheapest-startup-cost
! * outer paths. There's no need to consider any but the
! * cheapest-total-cost inner path, however.
! */
! Path *cheapest_startup_outer = outerrel->cheapest_startup_path;
! Path *cheapest_total_outer = outerrel->cheapest_total_path;
! Path *cheapest_total_inner = innerrel->cheapest_total_path;
!
! /*
! * If either cheapest-total path is parameterized by the other rel, we
! * can't use a hashjoin. (There's no use looking for alternative
! * input paths, since these should already be the least-parameterized
! * available paths.)
! */
! if (PATH_PARAM_BY_REL(cheapest_total_outer, innerrel) ||
! PATH_PARAM_BY_REL(cheapest_total_inner, outerrel))
! return;
!
! /* Unique-ify if need be; we ignore parameterized possibilities */
! if (jointype == JOIN_UNIQUE_OUTER)
! {
! cheapest_total_outer = (Path *)
! create_unique_path(root, outerrel,
! cheapest_total_outer, extra->sjinfo);
! Assert(cheapest_total_outer);
! jointype = JOIN_INNER;
! try_hashjoin_path(root,
! joinrel,
! cheapest_total_outer,
! cheapest_total_inner,
! hashclauses,
! jointype,
! extra);
! /* no possibility of cheap startup here */
! }
! else if (jointype == JOIN_UNIQUE_INNER)
! {
! cheapest_total_inner = (Path *)
! create_unique_path(root, innerrel,
! cheapest_total_inner, extra->sjinfo);
! Assert(cheapest_total_inner);
! jointype = JOIN_INNER;
! try_hashjoin_path(root,
! joinrel,
! cheapest_total_outer,
! cheapest_total_inner,
! hashclauses,
! jointype,
! extra);
! if (cheapest_startup_outer != NULL &&
! cheapest_startup_outer != cheapest_total_outer)
! try_hashjoin_path(root,
! joinrel,
! cheapest_startup_outer,
! 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);
! }
! }
! }
!
! /*
! * If the joinrel is parallel-safe, we may be able to consider a
! * partial hash join. However, we can't handle JOIN_UNIQUE_OUTER,
! * because the outer path will be partial, and therefore we won't be
! * able to properly guarantee uniqueness. Similarly, we can't handle
! * JOIN_FULL and JOIN_RIGHT, because they can produce false null
! * extended rows. Also, the resulting path must not be parameterized.
! * We would be able to support JOIN_FULL and JOIN_RIGHT for Parallel
! * Hash, since in that case we're back to a single hash table with a
! * single set of match bits for each batch, but that will require
! * figuring out a deadlock-free way to wait for the probe to finish.
! */
! if (joinrel->consider_parallel &&
! save_jointype != JOIN_UNIQUE_OUTER &&
! save_jointype != JOIN_FULL &&
! save_jointype != JOIN_RIGHT &&
! outerrel->partial_pathlist != NIL &&
! bms_is_empty(joinrel->lateral_relids))
! {
! Path *cheapest_partial_outer;
! Path *cheapest_partial_inner = NULL;
! Path *cheapest_safe_inner = NULL;
!
! cheapest_partial_outer =
! (Path *) linitial(outerrel->partial_pathlist);
!
! /*
! * Can we use a partial inner plan too, so that we can build a
! * shared hash table in parallel?
! */
! if (innerrel->partial_pathlist != NIL && enable_parallel_hash)
! {
! cheapest_partial_inner =
! (Path *) linitial(innerrel->partial_pathlist);
! try_partial_hashjoin_path(root, joinrel,
! cheapest_partial_outer,
! cheapest_partial_inner,
! hashclauses, jointype, extra,
! true /* parallel_hash */ );
! }
!
! /*
! * Normally, given that the joinrel is parallel-safe, the cheapest
! * total inner path will also be parallel-safe, but if not, we'll
! * have to search for the cheapest safe, unparameterized inner
! * path. If doing JOIN_UNIQUE_INNER, we can't use any alternative
! * inner path.
! */
! if (cheapest_total_inner->parallel_safe)
! cheapest_safe_inner = cheapest_total_inner;
! else if (save_jointype != JOIN_UNIQUE_INNER)
! 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,
! false /* parallel_hash */ );
! }
! }
}
/*
--- 2080,2090 ----
hashclauses = lappend(hashclauses, restrictinfo);
}
! if (hashclauses == NIL)
! return;
! hash_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, hashclauses);
}
/*
agg_pushdown_v5/07_join.diff
diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c
new file mode 100644
index e0dc8d9..30ac459
*** a/src/backend/optimizer/geqo/geqo_eval.c
--- b/src/backend/optimizer/geqo/geqo_eval.c
*************** merge_clump(PlannerInfo *root, List *clu
*** 269,274 ****
--- 269,275 ----
/* Create GatherPaths for any useful partial paths for rel */
generate_gather_paths(root, joinrel, false);
+ generate_gather_paths(root, joinrel, true);
/* 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 1ccb834..36e038e
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** static void set_append_rel_pathlist(Plan
*** 97,103 ****
static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels,
List *all_child_pathkeys,
! List *partitioned_rels);
static Path *get_cheapest_parameterized_child_path(PlannerInfo *root,
RelOptInfo *rel,
Relids required_outer);
--- 97,104 ----
static void generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels,
List *all_child_pathkeys,
! List *partitioned_rels,
! bool grouped);
static Path *get_cheapest_parameterized_child_path(PlannerInfo *root,
RelOptInfo *rel,
Relids required_outer);
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1873,1879 ****
if (subpaths_valid)
generate_mergeappend_paths(root, rel, live_childrels,
all_child_pathkeys,
! partitioned_rels);
/*
* Build Append paths for each parameterization seen among the child rels.
--- 1874,1884 ----
if (subpaths_valid)
generate_mergeappend_paths(root, rel, live_childrels,
all_child_pathkeys,
! partitioned_rels, false);
! if (grouped_subpaths_valid)
! generate_mergeappend_paths(root, rel, live_childrels,
! all_child_pathkeys,
! partitioned_rels, true);
/*
* Build Append paths for each parameterization seen among the child rels.
*************** static void
*** 1949,1957 ****
generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels,
List *all_child_pathkeys,
! List *partitioned_rels)
{
ListCell *lcp;
foreach(lcp, all_child_pathkeys)
{
--- 1954,1964 ----
generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
List *live_childrels,
List *all_child_pathkeys,
! List *partitioned_rels,
! bool grouped)
{
ListCell *lcp;
+ PathTarget *target = NULL;
foreach(lcp, all_child_pathkeys)
{
*************** generate_mergeappend_paths(PlannerInfo *
*** 1960,1982 ****
List *total_subpaths = NIL;
bool startup_neq_total = false;
ListCell *lcr;
/* Select the child paths for this ordering... */
foreach(lcr, live_childrels)
{
RelOptInfo *childrel = (RelOptInfo *) lfirst(lcr);
Path *cheapest_startup,
*cheapest_total;
/* Locate the right paths, if they are available. */
cheapest_startup =
! get_cheapest_path_for_pathkeys(childrel->pathlist,
pathkeys,
NULL,
STARTUP_COST,
false);
cheapest_total =
! get_cheapest_path_for_pathkeys(childrel->pathlist,
pathkeys,
NULL,
TOTAL_COST,
--- 1967,1998 ----
List *total_subpaths = NIL;
bool startup_neq_total = false;
ListCell *lcr;
+ Path *path;
/* Select the child paths for this ordering... */
foreach(lcr, live_childrels)
{
RelOptInfo *childrel = (RelOptInfo *) lfirst(lcr);
+ List *pathlist;
Path *cheapest_startup,
*cheapest_total;
+ if (grouped &&
+ (childrel->gpi == NULL || childrel->gpi->pathlist == NIL))
+ return;
+
+ pathlist = !grouped ? childrel->pathlist :
+ childrel->gpi->pathlist;
+
/* Locate the right paths, if they are available. */
cheapest_startup =
! get_cheapest_path_for_pathkeys(pathlist,
pathkeys,
NULL,
STARTUP_COST,
false);
cheapest_total =
! get_cheapest_path_for_pathkeys(pathlist,
pathkeys,
NULL,
TOTAL_COST,
*************** generate_mergeappend_paths(PlannerInfo *
*** 2008,2029 ****
&total_subpaths, NULL);
}
/* ... and build the MergeAppend paths */
! add_path(rel, (Path *) create_merge_append_path(root,
! rel,
! 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);
}
}
--- 2024,2070 ----
&total_subpaths, NULL);
}
+ /*
+ * Special target is needed if the path output should be grouped.
+ */
+ if (grouped)
+ {
+ Assert(rel->gpi != NULL);
+ target = rel->gpi->target;
+ }
+
/* ... and build the MergeAppend paths */
! path = (Path *) create_merge_append_path(root,
! rel,
! target,
! startup_subpaths,
! pathkeys,
! NULL,
! partitioned_rels);
!
! /* pathtarget will produce the grouped relation.. */
! if (grouped)
! {
! Assert(rel->gpi != NULL && rel->gpi->target != NULL);
! path->pathtarget = rel->gpi->target;
! }
!
! add_path(rel, path, grouped);
!
if (startup_neq_total)
! {
! path = (Path *) create_merge_append_path(root,
! rel,
! target,
! total_subpaths,
! pathkeys,
! NULL,
! partitioned_rels);
! if (grouped)
! path->pathtarget = rel->gpi->target;
! add_path(rel, path, grouped);
! }
!
}
}
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
new file mode 100644
index ab739db..7422fc4
*** 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;
*************** set_join_pathlist_hook_type set_join_pat
*** 39,44 ****
--- 40,49 ----
#define PATH_PARAM_BY_REL(path, rel) \
(PATH_PARAM_BY_REL_SELF(path, rel) || PATH_PARAM_BY_PARENT(path, rel))
+ #define REL_HAS_GROUPED_PATHS(rel) ((rel)->gpi && (rel)->gpi->pathlist)
+ #define REL_HAS_PARTIAL_GROUPED_PATHS(rel) \
+ ((rel)->gpi && (rel)->gpi->partial_pathlist)
+
/*
* The sort-specific input for try_mergejoin_... functions.
*/
*************** static void try_partial_mergejoin_path(P
*** 56,62 ****
Path *inner_path,
MergeJoinSortInfo *si,
JoinType jointype,
! JoinPathExtraData *extra);
static void sort_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
--- 61,69 ----
Path *inner_path,
MergeJoinSortInfo *si,
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);
*************** static void sort_inner_and_outer_common(
*** 66,72 ****
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra,
! List *sort_infos);
static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
--- 73,82 ----
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra,
! List *sort_infos,
! 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);
*************** static void consider_parallel_nestloop(P
*** 75,88 ****
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);
--- 85,101 ----
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_outer,
! bool do_aggregate);
static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outerrel, RelOptInfo *innerrel,
JoinType jointype, JoinPathExtraData *extra);
*************** static void generate_mergejoin_paths(Pla
*** 102,108 ****
bool useallclauses,
Path *inner_cheapest_total,
List *merge_pathkeys,
! bool is_partial);
/*
--- 115,124 ----
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,
*** 317,324 ****
* 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
--- 333,340 ----
* 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
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 351,357 ****
* across joins unless there's a join-order-constraint-based reason to do so.
* So we ignore the param_source_rels restriction when this case applies.
*
! * allow_star_schema_join() returns true if the param_source_rels restriction
* should be overridden, ie, it's okay to perform this join.
*/
static inline bool
--- 367,373 ----
* across joins unless there's a join-order-constraint-based reason to do so.
* So we ignore the param_source_rels restriction when this case applies.
*
! * allow_star_schema_join() returns TRUE if the param_source_rels restriction
* should be overridden, ie, it's okay to perform this join.
*/
static inline bool
*************** try_nestloop_path(PlannerInfo *root,
*** 379,385 ****
Path *inner_path,
List *pathkeys,
JoinType jointype,
! JoinPathExtraData *extra)
{
Relids required_outer;
JoinCostWorkspace workspace;
--- 395,403 ----
Path *inner_path,
List *pathkeys,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped,
! bool do_aggregate)
{
Relids required_outer;
JoinCostWorkspace workspace;
*************** try_nestloop_path(PlannerInfo *root,
*** 389,394 ****
--- 407,426 ----
Relids outerrelids;
Relids inner_paramrels = PATH_REQ_OUTER(inner_path);
Relids outer_paramrels = PATH_REQ_OUTER(outer_path);
+ 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 || !grouped);
+
+ /*
+ * Aggregation target is necessary to produce grouped join output.
+ */
+ Assert((joinrel->gpi && joinrel->gpi->target) || !grouped ||
+ !do_aggregate);
/*
* Paths are parameterized by top-level parents, so run parameterization
*************** try_nestloop_path(PlannerInfo *root,
*** 409,420 ****
* parameterization wouldn't be sensible --- unless allow_star_schema_join
* 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(outerrelids, outer_paramrels,
innerrelids, inner_paramrels);
if (required_outer &&
! ((!bms_overlap(required_outer, extra->param_source_rels) &&
!allow_star_schema_join(root, outerrelids, inner_paramrels)) ||
have_dangerous_phv(root, outerrelids, inner_paramrels)))
{
--- 441,453 ----
* parameterization wouldn't be sensible --- unless allow_star_schema_join
* 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. Finally reject parameterized aggregation.
*/
required_outer = calc_nestloop_required_outer(outerrelids, outer_paramrels,
innerrelids, inner_paramrels);
if (required_outer &&
! (do_aggregate ||
! (!bms_overlap(required_outer, extra->param_source_rels) &&
!allow_star_schema_join(root, outerrelids, inner_paramrels)) ||
have_dangerous_phv(root, outerrelids, inner_paramrels)))
{
*************** try_nestloop_path(PlannerInfo *root,
*** 435,476 ****
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, false))
{
/*
! * If the inner path is parameterized, it is parameterized by the
! * topmost parent of the outer rel, not the outer rel itself. Fix
! * that.
*/
! if (PATH_PARAM_BY_PARENT(inner_path, outer_path->parent))
{
! inner_path = reparameterize_path_by_child(root, inner_path,
! outer_path->parent);
!
! /*
! * If we could not translate the path, we can't create nest loop
! * path.
! */
! if (!inner_path)
! {
! bms_free(required_outer);
! return;
! }
}
! add_path(joinrel, (Path *)
! create_nestloop_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! pathkeys,
! required_outer), false);
}
else
{
--- 468,526 ----
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;
!
! /*
! * If the inner path is parameterized, it is parameterized by the topmost
! * parent of the outer rel, not the outer rel itself. Fix that.
! */
! if (PATH_PARAM_BY_PARENT(inner_path, outer_path->parent))
{
+ inner_path = reparameterize_path_by_child(root, inner_path,
+ outer_path->parent);
+
/*
! * If we could not translate the path, we can't create nest loop path.
*/
! if (!inner_path)
{
! bms_free(required_outer);
! return;
}
+ }
! 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)
! {
! PartialAggInfo *agg_info = get_partial_agg_info(root,
! joinrel->gpi->target,
! joinrel->gpi->sortgroupclauses);
!
! create_grouped_path(root, joinrel, join_path, true, false,
! AGG_HASHED, agg_info);
! create_grouped_path(root, joinrel, join_path, true, false,
! AGG_SORTED, agg_info);
! }
! 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
*** 491,499 ****
Path *inner_path,
List *pathkeys,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinCostWorkspace workspace;
/*
* If the inner path is parameterized, the parameterization must be fully
--- 541,559 ----
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 || !grouped);
+ Assert((joinrel->gpi && joinrel->gpi->target) || !grouped ||
+ !do_aggregate);
/*
* If the inner path is parameterized, the parameterization must be fully
*************** try_partial_nestloop_path(PlannerInfo *r
*** 528,535 ****
*/
initial_cost_nestloop(root, &workspace, jointype,
outer_path, inner_path, extra);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, pathkeys,
! false))
return;
/*
--- 588,655 ----
*/
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)
! {
! PartialAggInfo *agg_info = get_partial_agg_info(root,
! joinrel->gpi->target,
! joinrel->gpi->sortgroupclauses);
!
! create_grouped_path(root, joinrel, join_path, true, true, AGG_HASHED,
! agg_info);
! create_grouped_path(root, joinrel, join_path, true, true, AGG_SORTED,
! agg_info);
! }
! 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 partial,
! bool do_aggregate)
! {
! /*
! * Missing GroupedPathInfo indicates that we should not try to create a
! * grouped join.
! */
! if (joinrel->gpi == NULL)
! return;
!
! /*
! * Can't preform explicit aggregation w/o the appropriate target.
! */
! if (do_aggregate && joinrel->gpi->target == NULL)
return;
/*
*************** try_partial_nestloop_path(PlannerInfo *r
*** 548,565 ****
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), false);
}
static void
--- 668,680 ----
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);
}
static void
*************** try_nestloop_path_common(PlannerInfo *ro
*** 570,598 ****
List *pathkeys,
JoinType jointype,
JoinPathExtraData *extra,
! bool partial)
{
! /* Only join plain paths. */
! if (!partial)
! try_nestloop_path(root,
! joinrel,
! outer_path,
! inner_path,
! pathkeys,
! jointype,
! extra);
else
! try_partial_nestloop_path(root,
joinrel,
outer_path,
inner_path,
pathkeys,
jointype,
! extra);
}
-
/*
* Create one MergeJoinSortInfo for each possible use of merge clauses.
*/
--- 685,744 ----
List *pathkeys,
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. */
! if (!partial)
! try_nestloop_path(root,
! joinrel,
! outer_path,
! inner_path,
! pathkeys,
! jointype,
! extra,
! grouped_join,
! do_aggregate);
! else
! try_partial_nestloop_path(root,
! joinrel,
! outer_path,
! inner_path,
! pathkeys,
! jointype,
! extra,
! grouped_join,
! do_aggregate);
! }
else
! {
! /*
! * Either exactly one of the input paths should be grouped, or no one
! * is grouped and do_aggregate is true.
! */
! try_grouped_nestloop_path(root,
joinrel,
outer_path,
inner_path,
pathkeys,
jointype,
! extra,
! partial,
! do_aggregate);
! }
}
/*
* Create one MergeJoinSortInfo for each possible use of merge clauses.
*/
*************** try_mergejoin_path(PlannerInfo *root,
*** 687,711 ****
Path *inner_path,
MergeJoinSortInfo *si,
JoinType jointype,
! JoinPathExtraData *extra)
{
Relids required_outer;
JoinCostWorkspace workspace;
List *outersortkeys = si->outerkeys;
List *innersortkeys = si->innerkeys;
/*
* 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;
}
/*
--- 833,876 ----
Path *inner_path,
MergeJoinSortInfo *si,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped,
! bool do_aggregate)
{
Relids required_outer;
JoinCostWorkspace workspace;
List *outersortkeys = si->outerkeys;
List *innersortkeys = si->innerkeys;
+ 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 || !grouped);
+
+ /*
+ * Aggregation target is necessary to produce grouped join output.
+ */
+ Assert((joinrel->gpi && joinrel->gpi->target) || !grouped ||
+ !do_aggregate);
/*
* Check to see if proposed path is still parameterized, and reject if the
! * parameterization wouldn't be sensible. Also reject parameterized
! * aggregation.
*/
! required_outer = calc_non_nestloop_required_outer(outer_path, inner_path);
! if (required_outer)
{
! if (!bms_overlap(required_outer, extra->param_source_rels) ||
! do_aggregate)
! {
! /* Waste no memory when we reject a path here */
! bms_free(required_outer);
! return;
! }
}
/*
*************** try_mergejoin_path(PlannerInfo *root,
*** 726,749 ****
outer_path, inner_path,
outersortkeys, innersortkeys, extra);
! if (add_path_precheck(joinrel,
! workspace.startup_cost, workspace.total_cost,
! si->merge_pathkeys, required_outer, false))
{
! add_path(joinrel, (Path *)
! create_mergejoin_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! extra->restrictlist,
! si->merge_pathkeys,
! required_outer,
! si->merge_clauses,
! outersortkeys,
! innersortkeys), false);
}
else
{
--- 891,939 ----
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,
! si->merge_pathkeys,
! required_outer,
! si->merge_clauses,
! outersortkeys,
! innersortkeys,
! join_target);
!
! /* Do partial aggregation if needed. */
! if (do_aggregate)
{
! PartialAggInfo *agg_info = get_partial_agg_info(root,
! joinrel->gpi->target,
! joinrel->gpi->sortgroupclauses);
!
! create_grouped_path(root, joinrel, join_path, true, false, AGG_HASHED,
! agg_info);
! create_grouped_path(root, joinrel, join_path, true, false, AGG_SORTED,
! agg_info);
! }
! else if (add_path_precheck(joinrel,
! workspace.startup_cost, workspace.total_cost,
! si->merge_pathkeys, required_outer, grouped))
! {
! add_path(joinrel, (Path *) join_path, grouped);
}
else
{
*************** try_partial_mergejoin_path(PlannerInfo *
*** 764,774 ****
Path *inner_path,
MergeJoinSortInfo *si,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinCostWorkspace workspace;
List *outersortkeys = si->outerkeys;
List *innersortkeys = si->innerkeys;
/*
* See comments in try_partial_hashjoin_path().
--- 954,974 ----
Path *inner_path,
MergeJoinSortInfo *si,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped,
! bool do_aggregate)
{
JoinCostWorkspace workspace;
List *outersortkeys = si->outerkeys;
List *innersortkeys = si->innerkeys;
+ Path *join_path;
+ PathTarget *join_target;
+
+ /* The same checks we do in try_mergejoin_path. */
+ Assert(!do_aggregate || grouped);
+ Assert(joinrel->gpi || !grouped);
+ Assert((joinrel->gpi && joinrel->gpi->target) || !grouped ||
+ !do_aggregate);
/*
* See comments in try_partial_hashjoin_path().
*************** try_partial_mergejoin_path(PlannerInfo *
*** 800,825 ****
outer_path, inner_path,
outersortkeys, innersortkeys, extra);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost,
! si->merge_pathkeys,
! false))
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,
! si->merge_pathkeys,
! NULL,
! si->merge_clauses,
! outersortkeys,
! innersortkeys), false);
}
/*
--- 1000,1083 ----
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,
! si->merge_pathkeys,
! NULL,
! si->merge_clauses,
! outersortkeys,
! innersortkeys,
! join_target);
!
! if (do_aggregate)
! {
! PartialAggInfo *agg_info = get_partial_agg_info(root,
! joinrel->gpi->target,
! joinrel->gpi->sortgroupclauses);
!
! create_grouped_path(root, joinrel, join_path, true, true, AGG_HASHED,
! agg_info);
! create_grouped_path(root, joinrel, join_path, true, true, AGG_SORTED,
! agg_info);
! }
! else if (add_partial_path_precheck(joinrel, workspace.total_cost,
! si->merge_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,
! MergeJoinSortInfo *si,
! 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;
! /*
! * Can't preform explicit aggregation w/o the appropriate target.
! */
! if (do_aggregate && joinrel->gpi->target == NULL)
! return;
!
! if (!partial)
! try_mergejoin_path(root, joinrel, outer_path, inner_path, si,
! jointype, extra, true, do_aggregate);
! else
! try_partial_mergejoin_path(root, joinrel, outer_path, inner_path, si,
! jointype, extra, true, do_aggregate);
}
/*
*************** try_mergejoin_path_common(PlannerInfo *r
*** 834,858 ****
MergeJoinSortInfo *si,
JoinType jointype,
JoinPathExtraData *extra,
! bool partial)
{
! /* Only join plain paths. */
! if (!partial)
! try_mergejoin_path(root,
! joinrel,
! outer_path,
! inner_path,
! si,
! jointype,
! extra);
else
! try_partial_mergejoin_path(root,
joinrel,
outer_path,
inner_path,
si,
jointype,
! extra);
}
/*
--- 1092,1147 ----
MergeJoinSortInfo *si,
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. */
! if (!partial)
! try_mergejoin_path(root,
! joinrel,
! outer_path,
! inner_path,
! si,
! jointype,
! extra,
! false, false);
! else
! try_partial_mergejoin_path(root,
! joinrel,
! outer_path,
! inner_path,
! si,
! jointype,
! extra,
! false, false);
! }
else
! {
! /*
! * Either exactly one of the input paths should be grouped, or no one
! * is grouped and do_aggregate is true.
! */
! try_grouped_mergejoin_path(root,
joinrel,
outer_path,
inner_path,
si,
jointype,
! extra,
! partial,
! do_aggregate);
! }
}
/*
*************** try_hashjoin_path(PlannerInfo *root,
*** 867,889 ****
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;
}
/*
--- 1156,1197 ----
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 || !grouped);
+
+ /*
+ * Aggregation target is necessary to produce grouped join output.
+ */
+ Assert((joinrel->gpi && joinrel->gpi->target) || !grouped ||
+ !do_aggregate);
/*
* Check to see if proposed path is still parameterized, and reject if the
! * parameterization wouldn't be sensible. Also reject parameterized
! * aggregation.
*/
! required_outer = calc_non_nestloop_required_outer(outer_path, inner_path);
! if (required_outer)
{
! if (!bms_overlap(required_outer, extra->param_source_rels) ||
! do_aggregate)
! {
! /* Waste no memory when we reject a path here */
! bms_free(required_outer);
! return;
! }
}
/*
*************** try_hashjoin_path(PlannerInfo *root,
*** 893,914 ****
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path, extra, false);
! if (add_path_precheck(joinrel,
! workspace.startup_cost, workspace.total_cost,
! NIL, required_outer, false))
{
! add_path(joinrel, (Path *)
! create_hashjoin_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! false, /* parallel_hash */
! extra->restrictlist,
! required_outer,
! hashclauses), false);
}
else
{
--- 1201,1245 ----
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
outer_path, inner_path, extra, false);
! /*
! * 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,
! false, /* parallel_hash */
! extra->restrictlist,
! required_outer, hashclauses,
! join_target);
!
! /* Do partial aggregation if needed. */
! if (do_aggregate)
{
! PartialAggInfo *agg_info = get_partial_agg_info(root,
! joinrel->gpi->target,
! joinrel->gpi->sortgroupclauses);
!
! /*
! * Only AGG_HASHED is suitable here as it does not expect the input
! * set to be sorted.
! */
! create_grouped_path(root, joinrel, join_path, true, false, AGG_HASHED,
! agg_info);
! }
! 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
*** 934,942 ****
List *hashclauses,
JoinType jointype,
JoinPathExtraData *extra,
! bool parallel_hash)
{
JoinCostWorkspace workspace;
/*
* If the inner path is parameterized, the parameterization must be fully
--- 1265,1283 ----
List *hashclauses,
JoinType jointype,
JoinPathExtraData *extra,
! bool parallel_hash,
! 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 || !grouped);
+ Assert((joinrel->gpi && joinrel->gpi->target) || !grouped ||
+ !do_aggregate);
/*
* If the inner path is parameterized, the parameterization must be fully
*************** try_partial_hashjoin_path(PlannerInfo *r
*** 958,980 ****
* cost. Bail out right away if it looks terrible.
*/
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
! outer_path, inner_path, extra, true);
! if (!add_partial_path_precheck(joinrel, workspace.total_cost, NIL, false))
return;
! /* Might be good enough to be worth trying, so let's try it. */
! add_partial_path(joinrel, (Path *)
! create_hashjoin_path(root,
! joinrel,
! jointype,
! &workspace,
! extra,
! outer_path,
! inner_path,
! parallel_hash,
! extra->restrictlist,
! NULL,
! hashclauses), false);
}
/*
--- 1299,1393 ----
* cost. Bail out right away if it looks terrible.
*/
initial_cost_hashjoin(root, &workspace, jointype, hashclauses,
! outer_path, inner_path, extra, parallel_hash);
!
! /*
! * 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,
! parallel_hash,
! extra->restrictlist, NULL,
! hashclauses, join_target);
!
! /* Do partial aggregation if needed. */
! if (do_aggregate)
! {
! PartialAggInfo *agg_info = get_partial_agg_info(root,
! joinrel->gpi->target,
! joinrel->gpi->sortgroupclauses);
!
! create_grouped_path(root, joinrel, join_path, true, true, AGG_HASHED,
! agg_info);
! }
! 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 partial,
! bool parallel_hash,
! bool do_aggregate
! )
! {
! /*
! * Missing GroupedPathInfo indicates that we should not try to create a
! * grouped join.
! */
! if (joinrel->gpi == NULL)
return;
! /*
! * Can't preform explicit aggregation w/o the appropriate target.
! */
! if (do_aggregate && joinrel->gpi->target == 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, parallel_hash,
! true, do_aggregate);
}
/*
*************** try_hashjoin_path_common(PlannerInfo *ro
*** 990,1020 ****
JoinType jointype,
JoinPathExtraData *extra,
bool partial,
! bool parallel_hash)
{
! /* parallel_hash can only be set if partial is too. */
! Assert(!parallel_hash || partial);
! /* Only join plain paths. */
! if (!partial)
! try_hashjoin_path(root,
! joinrel,
! outer_path,
! inner_path,
! hashclauses,
! jointype,
! extra);
else
! try_partial_hashjoin_path(root,
joinrel,
outer_path,
inner_path,
hashclauses,
jointype,
extra,
! parallel_hash);
}
/*
* clause_sides_match_join
* Determine whether a join clause is of the right form to use in this join.
--- 1403,1463 ----
JoinType jointype,
JoinPathExtraData *extra,
bool partial,
! bool parallel_hash,
! 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. */
! if (!partial)
! try_hashjoin_path(root,
! joinrel,
! outer_path,
! inner_path,
! hashclauses,
! jointype,
! extra,
! false, false);
! else
! try_partial_hashjoin_path(root,
! joinrel,
! outer_path,
! inner_path,
! hashclauses,
! jointype,
! extra,
! parallel_hash,
! false, false);
! }
else
! {
! /*
! * Either exactly one of the input paths should be grouped, or no one
! * is grouped and do_aggregate is true.
! */
! try_grouped_hashjoin_path(root,
joinrel,
outer_path,
inner_path,
hashclauses,
jointype,
extra,
! partial,
! parallel_hash,
! do_aggregate);
! }
}
+
/*
* clause_sides_match_join
* Determine whether a join clause is of the right form to use in this join.
*************** sort_inner_and_outer(PlannerInfo *root,
*** 1068,1076 ****
List *sort_infos = get_merge_join_sort_info(root, extra, joinrel,
jointype);
sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, sort_infos);
}
/*
--- 1511,1533 ----
List *sort_infos = get_merge_join_sort_info(root, extra, joinrel,
jointype);
+ /* Plain (non-grouped) join. */
+ sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
+ jointype, extra, sort_infos, false, false,
+ false);
+ /* Use all the supported strategies to generate grouped join. */
sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, sort_infos, true, false,
! false);
! sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, sort_infos, false, true,
! false);
! sort_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, sort_infos, false, false,
! true);
!
! list_free_deep(sort_infos);
}
/*
*************** sort_inner_and_outer_common(PlannerInfo
*** 1086,1092 ****
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra,
! List *sort_infos)
{
JoinType save_jointype = jointype;
Path *outer_path;
--- 1543,1552 ----
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra,
! List *sort_infos,
! bool grouped_outer,
! bool grouped_inner,
! bool do_aggregate)
{
JoinType save_jointype = jointype;
Path *outer_path;
*************** sort_inner_and_outer_common(PlannerInfo
*** 1108,1115 ****
* 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
--- 1568,1592 ----
* against mergejoins with parameterized inputs; see comments in
* src/backend/optimizer/README.
*/
! if (grouped_outer)
! {
! if (REL_HAS_GROUPED_PATHS(outerrel))
! outer_path = linitial(outerrel->gpi->pathlist);
! else
! return;
! }
! else
! outer_path = outerrel->cheapest_total_path;
!
! if (grouped_inner)
! {
! if (REL_HAS_GROUPED_PATHS(innerrel))
! inner_path = linitial(innerrel->gpi->pathlist);
! else
! return;
! }
! else
! inner_path = innerrel->cheapest_total_path;
/*
* If either cheapest-total path is parameterized by the other rel, we
*************** sort_inner_and_outer_common(PlannerInfo
*** 1127,1132 ****
--- 1604,1619 ----
*/
if (jointype == JOIN_UNIQUE_OUTER)
{
+ /*
+ * TODO This is just a temporary limitation. Before lifting it, make
+ * sure that the UniquePath does emit GroupedVars. Also try to avoid
+ * the unique-ification if outer_path comes directly from AggPath
+ * (i.e. it's not grouped path combined with plain one) and the
+ * grouping keys guaranteed the uniqueness.
+ */
+ if (grouped_outer)
+ return;
+
outer_path = (Path *) create_unique_path(root, outerrel,
outer_path, extra->sjinfo);
Assert(outer_path);
*************** sort_inner_and_outer_common(PlannerInfo
*** 1134,1139 ****
--- 1621,1630 ----
}
else if (jointype == JOIN_UNIQUE_INNER)
{
+ /* TODO Temporary limitation, like above. */
+ if (grouped_inner)
+ return;
+
inner_path = (Path *) create_unique_path(root, innerrel,
inner_path, extra->sjinfo);
Assert(inner_path);
*************** sort_inner_and_outer_common(PlannerInfo
*** 1155,1167 ****
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);
}
foreach(l, sort_infos)
--- 1646,1695 ----
outerrel->partial_pathlist != NIL &&
bms_is_empty(joinrel->lateral_relids))
{
! if (grouped_outer)
! {
! if (REL_HAS_PARTIAL_GROUPED_PATHS(outerrel))
! cheapest_partial_outer = (Path *)
! linitial(outerrel->gpi->partial_pathlist);
! else
! return;
! }
! else
! cheapest_partial_outer = (Path *)
! linitial(outerrel->partial_pathlist);
!
! if (grouped_inner)
! {
! if (REL_HAS_GROUPED_PATHS(innerrel))
! 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);
! }
}
foreach(l, sort_infos)
*************** sort_inner_and_outer_common(PlannerInfo
*** 1176,1182 ****
* explicit sort step, so we needn't do so here.
*/
try_mergejoin_path_common(root, joinrel, outer_path, inner_path,
! si, jointype, extra, false);
/*
* If we have partial outer and parallel safe inner path then try
--- 1704,1712 ----
* explicit sort step, so we needn't do so here.
*/
try_mergejoin_path_common(root, joinrel, outer_path, inner_path,
! si, jointype, extra,
! false, grouped_outer, grouped_inner,
! do_aggregate);
/*
* If we have partial outer and parallel safe inner path then try
*************** sort_inner_and_outer_common(PlannerInfo
*** 1187,1193 ****
cheapest_partial_outer,
cheapest_safe_inner,
si, jointype, extra,
! true);
}
}
--- 1717,1724 ----
cheapest_partial_outer,
cheapest_safe_inner,
si, jointype, extra,
! true, grouped_outer, grouped_inner,
! do_aggregate);
}
}
*************** sort_inner_and_outer_common(PlannerInfo
*** 1204,1209 ****
--- 1735,1744 ----
* 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?
*/
static void
generate_mergejoin_paths(PlannerInfo *root,
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1215,1221 ****
bool useallclauses,
Path *inner_cheapest_total,
List *merge_pathkeys,
! bool is_partial)
{
List *trialsortkeys;
Path *cheapest_startup_inner;
--- 1750,1759 ----
bool useallclauses,
Path *inner_cheapest_total,
List *merge_pathkeys,
! bool is_partial,
! bool grouped_outer,
! bool grouped_inner,
! bool do_aggregate)
{
List *trialsortkeys;
Path *cheapest_startup_inner;
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1274,1280 ****
&si,
jointype,
extra,
! is_partial);
/* Can't do anything else if inner path needs to be unique'd */
if (save_jointype == JOIN_UNIQUE_INNER)
--- 1812,1819 ----
&si,
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
*** 1330,1335 ****
--- 1869,1875 ----
for (sortkeycnt = num_sortkeys; sortkeycnt > 0; sortkeycnt--)
{
+ List *inner_pathlist = NIL;
Path *innerpath;
MergeJoinSortInfo si_trial;
*************** generate_mergejoin_paths(PlannerInfo *ro
*** 1338,1350 ****
si_trial.merge_pathkeys = si.merge_pathkeys;
si_trial.merge_clauses = 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,
--- 1878,1895 ----
si_trial.merge_pathkeys = si.merge_pathkeys;
si_trial.merge_clauses = 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' si.innerkeys. 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
*** 1372,1385 ****
joinrel,
outerpath,
innerpath,
! &si,
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,
--- 1917,1933 ----
joinrel,
outerpath,
innerpath,
! &si_trial,
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
*** 1414,1423 ****
joinrel,
outerpath,
innerpath,
! &si,
jointype,
extra,
! is_partial);
}
cheapest_startup_inner = innerpath;
}
--- 1962,1973 ----
joinrel,
outerpath,
innerpath,
! &si_trial,
jointype,
extra,
! is_partial,
! grouped_outer, grouped_inner,
! do_aggregate);
}
cheapest_startup_inner = innerpath;
}
*************** match_unsorted_outer_common(PlannerInfo
*** 1436,1449 ****
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra)
{
JoinType save_jointype = jointype;
bool nestjoinOK;
bool useallclauses;
! Path *inner_cheapest_total = innerrel->cheapest_total_path;
Path *matpath = NULL;
ListCell *lc1;
/*
* Nestloop only supports inner, left, semi, and anti joins. Also, if we
--- 1986,2006 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped_outer,
! bool grouped_inner,
! bool do_aggregate)
{
JoinType save_jointype = jointype;
bool nestjoinOK;
bool useallclauses;
! Path *inner_cheapest_total;
Path *matpath = NULL;
ListCell *lc1;
+ List *outer_pathlist;
+
+ /* At most one input path may be grouped. */
+ Assert(!(grouped_outer && grouped_inner));
/*
* Nestloop only supports inner, left, semi, and anti joins. Also, if we
*************** match_unsorted_outer_common(PlannerInfo
*** 1480,1492 ****
--- 2037,2074 ----
break;
}
+ if (grouped_outer)
+ {
+ if (REL_HAS_GROUPED_PATHS(outerrel))
+ outer_pathlist = outerrel->gpi->pathlist;
+ else
+ return;
+ }
+ else
+ outer_pathlist = outerrel->pathlist;
+
+ if (grouped_inner)
+ {
+ if (REL_HAS_GROUPED_PATHS(innerrel))
+ inner_cheapest_total = linitial(innerrel->gpi->pathlist);
+ else
+ return;
+ }
+ else
+ inner_cheapest_total = innerrel->cheapest_total_path;
+
/*
* If inner_cheapest_total is parameterized by the outer rel, ignore it;
* we will consider it below as a member of cheapest_parameterized_paths,
* but the other possibilities considered in this routine aren't usable.
*/
if (PATH_PARAM_BY_REL(inner_cheapest_total, outerrel))
+ {
+ /* Grouped path should not be parameterized at all. */
+ Assert(!grouped_inner);
+
inner_cheapest_total = NULL;
+ }
/*
* If we need to unique-ify the inner path, we will consider only the
*************** match_unsorted_outer_common(PlannerInfo
*** 1498,1513 ****
if (inner_cheapest_total == NULL)
return;
inner_cheapest_total = (Path *)
create_unique_path(root, innerrel, inner_cheapest_total, extra->sjinfo);
Assert(inner_cheapest_total);
}
! else if (nestjoinOK)
{
/*
* Consider materializing the cheapest inner path, unless
* enable_material is off or the path in question materializes its
* output anyway.
*/
if (enable_material && inner_cheapest_total != NULL &&
!ExecMaterializesOutput(inner_cheapest_total->pathtype))
--- 2080,2109 ----
if (inner_cheapest_total == NULL)
return;
+ /*
+ * TODO This is just a temporary limitation. Before lifting it, make
+ * sure that the UniquePath does emit GroupedVars. Also try to avoid
+ * the unique-ification if outer_path comes directly from AggPath
+ * (i.e. it's not grouped path combined with plain one) and the
+ * grouping keys guaranteed the uniqueness.
+ */
+ if (grouped_inner)
+ return;
+
inner_cheapest_total = (Path *)
create_unique_path(root, innerrel, inner_cheapest_total, extra->sjinfo);
Assert(inner_cheapest_total);
}
! else if (nestjoinOK && !grouped_inner)
{
/*
* Consider materializing the cheapest inner path, unless
* enable_material is off or the path in question materializes its
* output anyway.
+ *
+ * TODO Verify that this is ok even if grouped_inner is true (at least
+ * make sure that mathpath does contain GroupedVars) and remove
+ * grouped_inner from the condition above.
*/
if (enable_material && inner_cheapest_total != NULL &&
!ExecMaterializesOutput(inner_cheapest_total->pathtype))
*************** match_unsorted_outer_common(PlannerInfo
*** 1515,1521 ****
create_material_path(innerrel, inner_cheapest_total);
}
! foreach(lc1, outerrel->pathlist)
{
Path *outerpath = (Path *) lfirst(lc1);
List *merge_pathkeys;
--- 2111,2117 ----
create_material_path(innerrel, inner_cheapest_total);
}
! foreach(lc1, outer_pathlist)
{
Path *outerpath = (Path *) lfirst(lc1);
List *merge_pathkeys;
*************** match_unsorted_outer_common(PlannerInfo
*** 1536,1541 ****
--- 2132,2141 ----
if (outerpath != outerrel->cheapest_total_path)
continue;
+ /* TODO Temporary restriction, see above. */
+ if (grouped_outer)
+ continue;
+
outerpath = (Path *) create_unique_path(root, outerrel,
outerpath, extra->sjinfo);
Assert(outerpath);
*************** match_unsorted_outer_common(PlannerInfo
*** 1551,1556 ****
--- 2151,2160 ----
if (save_jointype == JOIN_UNIQUE_INNER)
{
+ /* TODO Temporary restriction, see above. */
+ if (grouped_inner)
+ continue;
+
/*
* Consider nestloop join, but only with the unique-ified cheapest
* inner path
*************** match_unsorted_outer_common(PlannerInfo
*** 1562,1568 ****
merge_pathkeys,
jointype,
extra,
! false);
}
else if (nestjoinOK)
{
--- 2166,2173 ----
merge_pathkeys,
jointype,
extra,
! false, grouped_outer, grouped_inner,
! do_aggregate);
}
else if (nestjoinOK)
{
*************** match_unsorted_outer_common(PlannerInfo
*** 1574,1594 ****
*/
ListCell *lc2;
! foreach(lc2, innerrel->cheapest_parameterized_paths)
{
! Path *innerpath = (Path *) lfirst(lc2);
! try_nestloop_path_common(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! jointype,
! extra,
! false);
}
! /* Also consider materialized form of the cheapest inner path. */
if (matpath != NULL)
try_nestloop_path_common(root,
joinrel,
--- 2179,2208 ----
*/
ListCell *lc2;
! /*
! * There are no grouped paths in cheapest_parameterized_paths.
! */
! if (!grouped_inner)
{
! foreach(lc2, innerrel->cheapest_parameterized_paths)
! {
! Path *innerpath = (Path *) lfirst(lc2);
! try_nestloop_path_common(root,
! joinrel,
! outerpath,
! innerpath,
! merge_pathkeys,
! jointype,
! extra,
! false, grouped_outer, grouped_inner,
! do_aggregate);
! }
}
! /*
! * Also consider materialized form of the cheapest inner path.
! */
if (matpath != NULL)
try_nestloop_path_common(root,
joinrel,
*************** match_unsorted_outer_common(PlannerInfo
*** 1597,1603 ****
merge_pathkeys,
jointype,
extra,
! false);
}
/* Can't do anything else if outer path needs to be unique'd */
--- 2211,2218 ----
merge_pathkeys,
jointype,
extra,
! false, grouped_outer, grouped_inner,
! do_aggregate);
}
/* Can't do anything else if outer path needs to be unique'd */
*************** match_unsorted_outer_common(PlannerInfo
*** 1612,1618 ****
generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
save_jointype, extra, useallclauses,
inner_cheapest_total, merge_pathkeys,
! false);
}
/*
--- 2227,2234 ----
generate_mergejoin_paths(root, joinrel, innerrel, outerpath,
save_jointype, extra, useallclauses,
inner_cheapest_total, merge_pathkeys,
! false, grouped_outer, grouped_inner,
! do_aggregate);
}
/*
*************** match_unsorted_outer_common(PlannerInfo
*** 1623,1639 ****
* we handle extra_lateral_rels, since partial paths must not be
* parameterized. Similarly, we can't handle JOIN_FULL and JOIN_RIGHT,
* because they can produce false null extended rows.
*/
if (joinrel->consider_parallel &&
save_jointype != JOIN_UNIQUE_OUTER &&
save_jointype != JOIN_FULL &&
save_jointype != JOIN_RIGHT &&
outerrel->partial_pathlist != NIL &&
! 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
--- 2239,2261 ----
* we handle extra_lateral_rels, since partial paths must not be
* parameterized. Similarly, we can't handle JOIN_FULL and JOIN_RIGHT,
* because they can produce false null extended rows.
+ *
+ * grouped_inner must be false because we're not interested in nest loop
+ * joins with the grouped path on the inner side (i.e. repeated
+ * aggregation).
*/
if (joinrel->consider_parallel &&
save_jointype != JOIN_UNIQUE_OUTER &&
save_jointype != JOIN_FULL &&
save_jointype != JOIN_RIGHT &&
outerrel->partial_pathlist != NIL &&
! bms_is_empty(joinrel->lateral_relids) &&
! !grouped_inner)
{
if (nestjoinOK)
consider_parallel_nestloop(root, joinrel, outerrel, innerrel,
! save_jointype, extra,
! grouped_outer, do_aggregate);
/*
* If inner_cheapest_total is NULL or non parallel-safe then find the
*************** match_unsorted_outer_common(PlannerInfo
*** 1653,1659 ****
if (inner_cheapest_total)
consider_parallel_mergejoin(root, joinrel, outerrel, innerrel,
save_jointype, extra,
! inner_cheapest_total);
}
}
--- 2275,2282 ----
if (inner_cheapest_total)
consider_parallel_mergejoin(root, joinrel, outerrel, innerrel,
save_jointype, extra,
! inner_cheapest_total,
! grouped_outer, do_aggregate);
}
}
*************** match_unsorted_outer_common(PlannerInfo
*** 1679,1684 ****
--- 2302,2309 ----
* '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,
*** 1688,1695 ****
JoinType jointype,
JoinPathExtraData *extra)
{
match_unsorted_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra);
}
/*
--- 2313,2329 ----
JoinType jointype,
JoinPathExtraData *extra)
{
+ /* Plain (non-grouped) join. */
match_unsorted_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, false, false, false);
!
! /* Use all the supported strategies to generate grouped join. */
! match_unsorted_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, true, false, false);
! match_unsorted_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, false, true, false);
! match_unsorted_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, false, false, true);
}
/*
*************** consider_parallel_mergejoin(PlannerInfo
*** 1711,1722 ****
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)
{
Path *outerpath = (Path *) lfirst(lc1);
List *merge_pathkeys;
--- 2345,2380 ----
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra,
! Path *inner_cheapest_total,
! bool grouped_outer,
! bool do_aggregate)
{
ListCell *lc1;
+ List *outer_pathlist;
+ bool grouped = grouped_outer || do_aggregate;
+
+ 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;
/* generate merge join path for each partial outer path */
! foreach(lc1, outer_pathlist)
{
Path *outerpath = (Path *) lfirst(lc1);
List *merge_pathkeys;
*************** consider_parallel_mergejoin(PlannerInfo
*** 1727,1735 ****
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);
}
}
--- 2385,2394 ----
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, grouped_outer, false, do_aggregate);
}
}
*************** consider_parallel_nestloop(PlannerInfo *
*** 1750,1764 ****
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;
--- 2409,2446 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped_outer, bool do_aggregate)
{
JoinType save_jointype = jointype;
+ List *outer_pathlist;
ListCell *lc1;
+ bool grouped = grouped_outer || do_aggregate;
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 *
*** 1789,1795 ****
* 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;
--- 2471,2477 ----
* 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 *
*** 1800,1806 ****
}
try_nestloop_path_common(root, joinrel, outerpath, innerpath,
! pathkeys, jointype, extra, true);
}
}
}
--- 2482,2490 ----
}
try_nestloop_path_common(root, joinrel, outerpath, innerpath,
! pathkeys, jointype, extra,
! true, grouped_outer, false,
! do_aggregate);
}
}
}
*************** hash_inner_and_outer_common(PlannerInfo
*** 1816,1822 ****
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra,
! List *hashclauses)
{
JoinType save_jointype = jointype;
--- 2500,2509 ----
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra,
! List *hashclauses,
! bool grouped_outer,
! bool grouped_inner,
! bool do_aggregate)
{
JoinType save_jointype = jointype;
*************** hash_inner_and_outer_common(PlannerInfo
*** 1832,1840 ****
/* Shouldn't be called w/o hashclauses. */
Assert(hashclauses != NIL);
! cheapest_startup_outer = outerrel->cheapest_startup_path;
! cheapest_total_outer = outerrel->cheapest_total_path;
! cheapest_total_inner = innerrel->cheapest_total_path;
/*
* If either cheapest-total path is parameterized by the other rel, we
--- 2519,2550 ----
/* Shouldn't be called w/o hashclauses. */
Assert(hashclauses != NIL);
! if (grouped_outer)
! {
! if (REL_HAS_GROUPED_PATHS(outerrel))
! {
! cheapest_total_outer = linitial(outerrel->gpi->pathlist);
! /* No parameterized grouped paths. */
! cheapest_startup_outer = NULL;
! }
! else
! return;
! }
! else
! {
! cheapest_total_outer = outerrel->cheapest_total_path;
! cheapest_startup_outer = outerrel->cheapest_startup_path;
! }
!
! if (grouped_inner)
! {
! if (REL_HAS_GROUPED_PATHS(innerrel))
! cheapest_total_inner = linitial(innerrel->gpi->pathlist);
! else
! return;
! }
! else
! cheapest_total_inner = innerrel->cheapest_total_path;
/*
* If either cheapest-total path is parameterized by the other rel, we
*************** hash_inner_and_outer_common(PlannerInfo
*** 1849,1854 ****
--- 2559,2574 ----
/* Unique-ify if need be; we ignore parameterized possibilities */
if (jointype == JOIN_UNIQUE_OUTER)
{
+ /*
+ * TODO This is just a temporary limitation. Before lifting it, make
+ * sure that the UniquePath does emit GroupedVars. Also try to avoid
+ * the unique-ification if the outer path comes directly from AggPath
+ * (i.e. it's not grouped path combined with plain one) and the
+ * grouping keys guaranteed the uniqueness.
+ */
+ if (grouped_outer)
+ return;
+
cheapest_total_outer = (Path *)
create_unique_path(root, outerrel,
cheapest_total_outer, extra->sjinfo);
*************** hash_inner_and_outer_common(PlannerInfo
*** 1862,1872 ****
jointype,
extra,
false,
! false);
/* no possibility of cheap startup here */
}
else if (jointype == JOIN_UNIQUE_INNER)
{
cheapest_total_inner = (Path *)
create_unique_path(root, innerrel,
cheapest_total_inner, extra->sjinfo);
--- 2582,2598 ----
jointype,
extra,
false,
! false,
! grouped_outer, grouped_inner,
! do_aggregate);
/* no possibility of cheap startup here */
}
else if (jointype == JOIN_UNIQUE_INNER)
{
+ /* TODO Temporary restriction, see above. */
+ if (grouped_inner)
+ return;
+
cheapest_total_inner = (Path *)
create_unique_path(root, innerrel,
cheapest_total_inner, extra->sjinfo);
*************** hash_inner_and_outer_common(PlannerInfo
*** 1880,1886 ****
jointype,
extra,
false,
! false);
if (cheapest_startup_outer != NULL &&
cheapest_startup_outer != cheapest_total_outer)
--- 2606,2614 ----
jointype,
extra,
false,
! false,
! grouped_outer, grouped_inner,
! do_aggregate);
if (cheapest_startup_outer != NULL &&
cheapest_startup_outer != cheapest_total_outer)
*************** hash_inner_and_outer_common(PlannerInfo
*** 1892,1898 ****
jointype,
extra,
false,
! false);
}
else
{
--- 2620,2628 ----
jointype,
extra,
false,
! false,
! grouped_outer, grouped_inner,
! do_aggregate);
}
else
{
*************** hash_inner_and_outer_common(PlannerInfo
*** 1909,1914 ****
--- 2639,2645 ----
*/
ListCell *lc1;
ListCell *lc2;
+ List *outer_pathlist;
if (cheapest_startup_outer != NULL)
try_hashjoin_path_common(root,
*************** hash_inner_and_outer_common(PlannerInfo
*** 1919,1929 ****
jointype,
extra,
false,
! false);
! foreach(lc1, outerrel->cheapest_parameterized_paths)
{
Path *outerpath = (Path *) lfirst(lc1);
/*
* We cannot use an outer path that is parameterized by the inner
--- 2650,2667 ----
jointype,
extra,
false,
! false,
! grouped_outer, grouped_inner,
! do_aggregate);
! outer_pathlist = !grouped_outer ?
! outerrel->cheapest_parameterized_paths :
! outerrel->gpi->pathlist;
!
! foreach(lc1, outer_pathlist)
{
Path *outerpath = (Path *) lfirst(lc1);
+ List *inner_pathlist;
/*
* We cannot use an outer path that is parameterized by the inner
*************** hash_inner_and_outer_common(PlannerInfo
*** 1932,1938 ****
if (PATH_PARAM_BY_REL(outerpath, innerrel))
continue;
! foreach(lc2, innerrel->cheapest_parameterized_paths)
{
Path *innerpath = (Path *) lfirst(lc2);
--- 2670,2680 ----
if (PATH_PARAM_BY_REL(outerpath, innerrel))
continue;
! inner_pathlist = !grouped_inner ?
! innerrel->cheapest_parameterized_paths :
! innerrel->gpi->pathlist;
!
! foreach(lc2, inner_pathlist)
{
Path *innerpath = (Path *) lfirst(lc2);
*************** hash_inner_and_outer_common(PlannerInfo
*** 1955,1961 ****
jointype,
extra,
false,
! false);
}
}
}
--- 2697,2705 ----
jointype,
extra,
false,
! false,
! grouped_outer, grouped_inner,
! do_aggregate);
}
}
}
*************** hash_inner_and_outer_common(PlannerInfo
*** 1966,1976 ****
* outer path will be partial, and therefore we won't be able to properly
* guarantee uniqueness. Similarly, we can't handle JOIN_FULL and
* JOIN_RIGHT, because they can produce false null extended rows. Also,
! * the resulting path must not be parameterized. We would be able to
! * support JOIN_FULL and JOIN_RIGHT for Parallel Hash, since in that case
! * we're back to a single hash table with a single set of match bits for
! * each batch, but that will require figuring out a deadlock-free way to
! * wait for the probe to finish.
*/
if (joinrel->consider_parallel &&
save_jointype != JOIN_UNIQUE_OUTER &&
--- 2710,2716 ----
* outer path will be partial, and therefore we won't be able to properly
* guarantee uniqueness. Similarly, we can't handle JOIN_FULL and
* JOIN_RIGHT, because they can produce false null extended rows. Also,
! * the resulting path must not be parameterized.
*/
if (joinrel->consider_parallel &&
save_jointype != JOIN_UNIQUE_OUTER &&
*************** hash_inner_and_outer_common(PlannerInfo
*** 1983,1990 ****
Path *cheapest_partial_inner = NULL;
Path *cheapest_safe_inner = NULL;
! cheapest_partial_outer =
! (Path *) linitial(outerrel->partial_pathlist);
/*
* Can we use a partial inner plan too, so that we can build a shared
--- 2723,2741 ----
Path *cheapest_partial_inner = NULL;
Path *cheapest_safe_inner = NULL;
! if (grouped_outer)
! {
! if (REL_HAS_PARTIAL_GROUPED_PATHS(outerrel))
! {
! cheapest_partial_outer =
! (Path *) linitial(outerrel->gpi->partial_pathlist);
! }
! else
! return;
! }
! else
! cheapest_partial_outer =
! (Path *) linitial(outerrel->partial_pathlist);
/*
* Can we use a partial inner plan too, so that we can build a shared
*************** hash_inner_and_outer_common(PlannerInfo
*** 1992,2008 ****
*/
if (innerrel->partial_pathlist != NIL && enable_parallel_hash)
{
! cheapest_partial_inner =
! (Path *) linitial(innerrel->partial_pathlist);
try_hashjoin_path_common(root, joinrel,
cheapest_partial_outer,
cheapest_partial_inner,
hashclauses, jointype, extra,
true,
! true /* parallel_hash */ );
}
-
/*
* Normally, given that the joinrel is parallel-safe, the cheapest
* total inner path will also be parallel-safe, but if not, we'll have
--- 2743,2772 ----
*/
if (innerrel->partial_pathlist != NIL && enable_parallel_hash)
{
! if (grouped_inner)
! {
! if (REL_HAS_PARTIAL_GROUPED_PATHS(innerrel))
! {
! cheapest_partial_inner =
! (Path *) linitial(innerrel->gpi->partial_pathlist);
! }
! else
! return;
! }
! else
! cheapest_partial_inner =
! (Path *) linitial(innerrel->partial_pathlist);
!
try_hashjoin_path_common(root, joinrel,
cheapest_partial_outer,
cheapest_partial_inner,
hashclauses, jointype, extra,
true,
! true /* parallel_hash */ ,
! grouped_outer, grouped_inner,
! do_aggregate);
}
/*
* Normally, given that the joinrel is parallel-safe, the cheapest
* total inner path will also be parallel-safe, but if not, we'll have
*************** hash_inner_and_outer_common(PlannerInfo
*** 2013,2020 ****
if (cheapest_total_inner->parallel_safe)
cheapest_safe_inner = cheapest_total_inner;
else if (save_jointype != JOIN_UNIQUE_INNER)
cheapest_safe_inner =
! get_cheapest_parallel_safe_total_inner(innerrel->pathlist);
if (cheapest_safe_inner != NULL)
try_hashjoin_path_common(root, joinrel,
--- 2777,2792 ----
if (cheapest_total_inner->parallel_safe)
cheapest_safe_inner = cheapest_total_inner;
else if (save_jointype != JOIN_UNIQUE_INNER)
+ {
+ List *inner_pathlist;
+
+ inner_pathlist = !grouped_inner ?
+ innerrel->cheapest_parameterized_paths :
+ innerrel->gpi->pathlist;
+
cheapest_safe_inner =
! get_cheapest_parallel_safe_total_inner(inner_pathlist);
! }
if (cheapest_safe_inner != NULL)
try_hashjoin_path_common(root, joinrel,
*************** hash_inner_and_outer_common(PlannerInfo
*** 2022,2028 ****
cheapest_safe_inner,
hashclauses, jointype, extra,
true,
! false); /* parallel_hash */
}
}
--- 2794,2802 ----
cheapest_safe_inner,
hashclauses, jointype, extra,
true,
! false /* parallel_hash */ ,
! grouped_outer, grouped_inner,
! do_aggregate);
}
}
*************** hash_inner_and_outer(PlannerInfo *root,
*** 2045,2052 ****
JoinType jointype,
JoinPathExtraData *extra)
{
- bool isouterjoin = IS_OUTER_JOIN(jointype);
List *hashclauses = NIL;
ListCell *l;
/*
--- 2819,2826 ----
JoinType jointype,
JoinPathExtraData *extra)
{
List *hashclauses = NIL;
+ bool isouterjoin = IS_OUTER_JOIN(jointype);
ListCell *l;
/*
*************** hash_inner_and_outer(PlannerInfo *root,
*** 2083,2090 ****
if (hashclauses == NIL)
return;
hash_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, hashclauses);
}
/*
--- 2857,2877 ----
if (hashclauses == NIL)
return;
+ /* Plain (non-grouped) join. */
hash_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, hashclauses,
! false, false, false);
!
! /* Use all the supported strategies to generate grouped join. */
! hash_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, hashclauses,
! true, false, false);
! hash_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, hashclauses,
! false, true, false);
! hash_inner_and_outer_common(root, joinrel, outerrel, innerrel,
! jointype, extra, hashclauses,
! false, false, true);
}
/*
*************** hash_inner_and_outer(PlannerInfo *root,
*** 2092,2098 ****
* Select mergejoin clauses that are usable for a particular join.
* Returns a list of RestrictInfo nodes for those clauses.
*
! * *mergejoin_allowed is normally set to true, but it is set to false if
* this is a right/full join and there are nonmergejoinable join clauses.
* The executor's mergejoin machinery cannot handle such cases, so we have
* to avoid generating a mergejoin plan. (Note that this flag does NOT
--- 2879,2885 ----
* Select mergejoin clauses that are usable for a particular join.
* Returns a list of RestrictInfo nodes for those clauses.
*
! * *mergejoin_allowed is normally set to TRUE, but it is set to FALSE if
* this is a right/full join and there are nonmergejoinable join clauses.
* The executor's mergejoin machinery cannot handle such cases, so we have
* to avoid generating a mergejoin plan. (Note that this flag does NOT
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
new file mode 100644
index 285f269..1823ee3
*** a/src/backend/optimizer/path/joinrels.c
--- b/src/backend/optimizer/path/joinrels.c
*************** try_partition_wise_join(PlannerInfo *roo
*** 1405,1411 ****
(List *) adjust_appendrel_attrs(root,
(Node *) parent_restrictlist,
nappinfos, appinfos);
- pfree(appinfos);
child_joinrel = joinrel->part_rels[cnt_parts];
if (!child_joinrel)
--- 1405,1410 ----
*************** try_partition_wise_join(PlannerInfo *roo
*** 1415,1421 ****
--- 1414,1428 ----
child_sjinfo,
child_sjinfo->jointype);
joinrel->part_rels[cnt_parts] = child_joinrel;
+
+ /*
+ * If the parent join can be grouped, so can be its children.
+ */
+ if (joinrel->gpi != NULL)
+ build_child_rel_gpi(root, child_joinrel, joinrel, nappinfos,
+ appinfos);
}
+ pfree(appinfos);
Assert(bms_equal(child_joinrel->relids, child_joinrelids));
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
new file mode 100644
index 1a0d3a8..b11afd9
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
*************** use_physical_tlist(PlannerInfo *root, Pa
*** 809,814 ****
--- 809,820 ----
return false;
/*
+ * Grouped relation's target list contains GroupedVars.
+ */
+ if (rel->gpi != NULL)
+ return false;
+
+ /*
* If a bitmap scan's tlist is empty, keep it as-is. This may allow the
* executor to skip heap page fetches, and in any case, the benefit of
* using a physical tlist instead would be minimal.
*************** create_projection_plan(PlannerInfo *root
*** 1587,1594 ****
* creation, but that would add expense to creating Paths we might end up
* not using.)
*/
! if (is_projection_capable_path(best_path->subpath) ||
! tlist_same_exprs(tlist, subplan->targetlist))
{
/* Don't need a separate Result, just assign tlist to subplan */
plan = subplan;
--- 1593,1601 ----
* creation, but that would add expense to creating Paths we might end up
* not using.)
*/
! if (!best_path->force_result &&
! (is_projection_capable_path(best_path->subpath) ||
! tlist_same_exprs(tlist, subplan->targetlist)))
{
/* Don't need a separate Result, just assign tlist to subplan */
plan = subplan;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
new file mode 100644
index 0aaac38..c3528fc
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
*************** append_startup_cost_compare(const void *
*** 1359,1369 ****
/*
* create_merge_append_path
* Creates a path corresponding to a MergeAppend plan, returning the
! * pathnode.
*/
MergeAppendPath *
create_merge_append_path(PlannerInfo *root,
RelOptInfo *rel,
List *subpaths,
List *pathkeys,
Relids required_outer,
--- 1359,1371 ----
/*
* create_merge_append_path
* Creates a path corresponding to a MergeAppend plan, returning the
! * pathnode. target can be supplied by caller. If NULL is passed, the field
! * is set to rel->reltarget.
*/
MergeAppendPath *
create_merge_append_path(PlannerInfo *root,
RelOptInfo *rel,
+ PathTarget *target,
List *subpaths,
List *pathkeys,
Relids required_outer,
*************** create_merge_append_path(PlannerInfo *ro
*** 1376,1382 ****
pathnode->path.pathtype = T_MergeAppend;
pathnode->path.parent = rel;
! pathnode->path.pathtarget = rel->reltarget;
pathnode->path.param_info = get_appendrel_parampathinfo(rel,
required_outer);
pathnode->path.parallel_aware = false;
--- 1378,1384 ----
pathnode->path.pathtype = T_MergeAppend;
pathnode->path.parent = rel;
! pathnode->path.pathtarget = target ? target : rel->reltarget;
pathnode->path.param_info = get_appendrel_parampathinfo(rel,
required_outer);
pathnode->path.parallel_aware = false;
*************** calc_non_nestloop_required_outer(Path *o
*** 2179,2184 ****
--- 2181,2187 ----
* '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,
*** 2192,2198 ****
Path *inner_path,
List *restrict_clauses,
List *pathkeys,
! Relids required_outer)
{
NestPath *pathnode = makeNode(NestPath);
Relids inner_req_outer = PATH_REQ_OUTER(inner_path);
--- 2195,2202 ----
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,
*** 2225,2231 ****
pathnode->path.pathtype = T_NestLoop;
pathnode->path.parent = joinrel;
! pathnode->path.pathtarget = joinrel->reltarget;
pathnode->path.param_info =
get_joinrel_parampathinfo(root,
joinrel,
--- 2229,2235 ----
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,
*** 2283,2295 ****
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,
--- 2287,2301 ----
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,
*** 2335,2340 ****
--- 2341,2347 ----
* '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,
*** 2347,2359 ****
bool parallel_hash,
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,
--- 2354,2368 ----
bool parallel_hash,
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,
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
new file mode 100644
index e4c2313..3fa9261
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
*************** build_join_rel(PlannerInfo *root,
*** 537,542 ****
--- 537,543 ----
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,
*** 590,595 ****
--- 591,655 ----
add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel);
/*
+ * If grouping is applicable at relation level, try to build grouped
+ * target.
+ */
+ if (root->grouped_var_list != NIL)
+ {
+ /*
+ * Try to create a grouping target.
+ */
+ prepare_rel_for_grouping(root, joinrel);
+
+ /*
+ * If the relation appears to be eligible for grouping, joinrel->gpi
+ * has been initialized. Compute width of the grouping target.
+ */
+ if (joinrel->gpi != NULL)
+ {
+ ListCell *lc;
+ int32 width = 0;
+
+ Assert(joinrel->gpi->target != NULL);
+ foreach(lc, joinrel->gpi->target->exprs)
+ {
+ Expr *expr = lfirst(lc);
+
+ if (IsA(expr, Var))
+ {
+ Var *var = castNode(Var, expr);
+ RelOptInfo *rel;
+ int ndx;
+
+ /*
+ * If set_rel_width() managed to set the width, it should
+ * be cached in the underlying base relation. Otherwise we
+ * can hardly find out more w/o inadequate effort.
+ */
+ rel = find_base_rel(root, var->varno);
+ ndx = var->varattno - rel->min_attr;
+ width += rel->attr_widths[ndx];
+ }
+ else if (IsA(expr, GroupedVar))
+ {
+ GroupedVar *gvar = castNode(GroupedVar, expr);
+ GroupedVarInfo *gvinfo = find_grouped_var_info(root,
+ gvar);
+
+ width += gvinfo->gv_width;
+ }
+ else
+
+ /*
+ * No other kind of expression should appear in a grouped
+ * target.
+ */
+ Assert(false);
+ }
+ }
+ }
+
+ /*
* add_placeholders_to_joinrel also took care of adding the ph_lateral
* sets of any PlaceHolderVars computed here to direct_lateral_relids, so
* now we can finish computing that. This is much like the computation of
*************** build_child_join_rel(PlannerInfo *root,
*** 711,716 ****
--- 771,777 ----
joinrel->cheapest_parameterized_paths = NIL;
joinrel->direct_lateral_relids = NULL;
joinrel->lateral_relids = NULL;
+ joinrel->gpi = NULL;
joinrel->relid = 0; /* indicates not a baserel */
joinrel->rtekind = RTE_JOIN;
joinrel->min_attr = 0;
*************** build_child_join_rel(PlannerInfo *root,
*** 759,765 ****
(Node *) parent_joinrel->joininfo,
nappinfos,
appinfos);
- pfree(appinfos);
/*
* Lateral relids referred in child join will be same as that referred in
--- 820,825 ----
*************** build_child_join_rel(PlannerInfo *root,
*** 795,800 ****
--- 855,862 ----
/* Add the relation to the PlannerInfo. */
add_join_rel(root, joinrel);
+ pfree(appinfos);
+
return joinrel;
}
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
new file mode 100644
index b11bd47..bbcfc7f
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern AppendPath *create_append_path(Re
*** 73,78 ****
--- 73,79 ----
List *partitioned_rels, double rows);
extern MergeAppendPath *create_merge_append_path(PlannerInfo *root,
RelOptInfo *rel,
+ PathTarget *target,
List *subpaths,
List *pathkeys,
Relids required_outer,
*************** extern NestPath *create_nestloop_path(Pl
*** 132,138 ****
Path *inner_path,
List *restrict_clauses,
List *pathkeys,
! Relids required_outer);
extern MergePath *create_mergejoin_path(PlannerInfo *root,
RelOptInfo *joinrel,
--- 133,140 ----
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(
*** 146,152 ****
Relids required_outer,
List *mergeclauses,
List *outersortkeys,
! List *innersortkeys);
extern HashPath *create_hashjoin_path(PlannerInfo *root,
RelOptInfo *joinrel,
--- 148,155 ----
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
*** 158,164 ****
bool parallel_hash,
List *restrict_clauses,
Relids required_outer,
! List *hashclauses);
extern ProjectionPath *create_projection_path(PlannerInfo *root,
RelOptInfo *rel,
--- 161,168 ----
bool parallel_hash,
List *restrict_clauses,
Relids required_outer,
! List *hashclauses,
! PathTarget *target);
extern ProjectionPath *create_projection_path(PlannerInfo *root,
RelOptInfo *rel,
*************** extern RelOptInfo *build_child_join_rel(
*** 315,320 ****
--- 319,327 ----
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
RelOptInfo *parent_joinrel, List *restrictlist,
SpecialJoinInfo *sjinfo, JoinType jointype);
+ extern void build_child_rel_gpi(PlannerInfo *root, RelOptInfo *child,
+ RelOptInfo *parent, int nappinfos,
+ AppendRelInfo **appinfos);
extern void prepare_rel_for_grouping(PlannerInfo *root, RelOptInfo *rel);
extern void build_child_rel_gpi(PlannerInfo *root, RelOptInfo *child,
RelOptInfo *parent, int nappinfos,
diff --git a/src/test/regress/expected/agg_pushdown.out b/src/test/regress/expected/agg_pushdown.out
new file mode 100644
index ...e3071de
*** a/src/test/regress/expected/agg_pushdown.out
--- b/src/test/regress/expected/agg_pushdown.out
***************
*** 0 ****
--- 1,259 ----
+ BEGIN;
+ CREATE TABLE agg_pushdown_parent (
+ i int primary key);
+ CREATE TABLE agg_pushdown_child1 (
+ j int primary key,
+ parent int references agg_pushdown_parent,
+ v double precision);
+ CREATE INDEX ON agg_pushdown_child1(parent);
+ CREATE TABLE agg_pushdown_child2 (
+ k int primary key,
+ parent int references agg_pushdown_parent,
+ v double precision);
+ INSERT INTO agg_pushdown_parent(i)
+ SELECT n
+ FROM generate_series(0, 7) AS s(n);
+ INSERT INTO agg_pushdown_child1(j, parent, v)
+ SELECT 64 * i + n, i, random()
+ FROM generate_series(0, 63) AS s(n), agg_pushdown_parent;
+ INSERT INTO agg_pushdown_child2(k, parent, v)
+ SELECT 64 * i + n, i, random()
+ FROM generate_series(0, 63) AS s(n), agg_pushdown_parent;
+ ANALYZE;
+ SET enable_agg_pushdown TO on;
+ -- The supposed advantage of pushing aggregation down below joins is that
+ -- joins will then receive less input tuples. Let's stress this difference.
+ SET cpu_tuple_cost TO 0.1;
+ -- Perform scan of a table and partially aggregate the result.
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
+ AS c1 ON c1.parent = p.i GROUP BY p.i;
+ QUERY PLAN
+ -------------------------------------------------------------------------------------
+ Finalize HashAggregate
+ Group Key: p.i
+ -> Nested Loop
+ -> Partial HashAggregate
+ Group Key: c1.parent
+ -> Seq Scan on agg_pushdown_child1 c1
+ -> Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+ Index Cond: (i = c1.parent)
+ (8 rows)
+
+ -- Perform nestloop join between agg_pushdown_child1 and agg_pushdown_child2
+ -- and partially aggregate the result.
+ SET enable_nestloop TO on;
+ SET enable_hashjoin TO off;
+ SET enable_mergejoin TO off;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+ QUERY PLAN
+ ---------------------------------------------------------------------------------------------
+ Finalize HashAggregate
+ Group Key: p.i
+ -> Nested Loop
+ -> Partial HashAggregate
+ Group Key: c1.parent
+ -> Nested Loop
+ -> Seq Scan on agg_pushdown_child1 c1
+ -> Index Scan using agg_pushdown_child2_pkey on agg_pushdown_child2 c2
+ Index Cond: (k = c1.j)
+ Filter: (c1.parent = parent)
+ -> Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+ Index Cond: (i = c1.parent)
+ (12 rows)
+
+ -- The same for hash join.
+ SET enable_nestloop TO off;
+ SET enable_hashjoin TO on;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+ QUERY PLAN
+ ----------------------------------------------------------------------------
+ Finalize HashAggregate
+ Group Key: p.i
+ -> Hash Join
+ Hash Cond: (c1.parent = p.i)
+ -> Partial HashAggregate
+ Group Key: c1.parent
+ -> Hash Join
+ Hash Cond: ((c1.parent = c2.parent) AND (c1.j = c2.k))
+ -> Seq Scan on agg_pushdown_child1 c1
+ -> Hash
+ -> Seq Scan on agg_pushdown_child2 c2
+ -> Hash
+ -> Seq Scan on agg_pushdown_parent p
+ (13 rows)
+
+ -- The same for merge join.
+ SET enable_hashjoin TO off;
+ SET enable_mergejoin TO on;
+ SET enable_seqscan TO off;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+ QUERY PLAN
+ ---------------------------------------------------------------------------------------------------
+ Finalize GroupAggregate
+ Group Key: p.i
+ -> Merge Join
+ Merge Cond: (c1.parent = p.i)
+ -> Sort
+ Sort Key: c1.parent
+ -> Partial HashAggregate
+ Group Key: c1.parent
+ -> Merge Join
+ Merge Cond: (c1.j = c2.k)
+ Join Filter: (c1.parent = c2.parent)
+ -> Index Scan using agg_pushdown_child1_pkey on agg_pushdown_child1 c1
+ -> Index Scan using agg_pushdown_child2_pkey on agg_pushdown_child2 c2
+ -> Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+ (14 rows)
+
+ -- Generic grouping expression.
+ -- Make any processing of the grouping expression expensive. If the final
+ -- relation output needs to be sorted before GroupAggregate is applied, the
+ -- (p.i / 2) expression is also evaluated by the Sort node. Thus the plan that
+ -- uses the partial aggregation is preferrable because it emits the value of
+ -- the exprssion and no node above has to evaluate it again.
+ SET cpu_operator_cost TO 0.1;
+ EXPLAIN (COSTS off)
+ SELECT p.i / 2, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i / 2;
+ QUERY PLAN
+ ---------------------------------------------------------------------------------------------------------
+ Finalize HashAggregate
+ Group Key: (((c1.parent / 2)))
+ -> Merge Join
+ Merge Cond: (c1.parent = p.i)
+ -> Sort
+ Sort Key: c1.parent
+ -> Partial HashAggregate
+ Group Key: (c1.parent / 2), c1.parent, c2.parent
+ -> Result
+ -> Merge Join
+ Merge Cond: (c1.j = c2.k)
+ Join Filter: (c1.parent = c2.parent)
+ -> Index Scan using agg_pushdown_child1_pkey on agg_pushdown_child1 c1
+ -> Index Scan using agg_pushdown_child2_pkey on agg_pushdown_child2 c2
+ -> Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+ (15 rows)
+
+ -- The same tests for parallel plans.
+ RESET ALL;
+ SET parallel_setup_cost TO 0;
+ SET parallel_tuple_cost TO 0;
+ SET min_parallel_table_scan_size TO 0;
+ SET min_parallel_index_scan_size TO 0;
+ SET max_parallel_workers_per_gather TO 4;
+ SET enable_agg_pushdown TO on;
+ SET cpu_tuple_cost TO 0.1;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
+ AS c1 ON c1.parent = p.i GROUP BY p.i;
+ QUERY PLAN
+ -------------------------------------------------------------------------------------------
+ Finalize HashAggregate
+ Group Key: p.i
+ -> Gather
+ Workers Planned: 2
+ -> Nested Loop
+ -> Partial HashAggregate
+ Group Key: c1.parent
+ -> Parallel Seq Scan on agg_pushdown_child1 c1
+ -> Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+ Index Cond: (i = c1.parent)
+ (10 rows)
+
+ SET enable_nestloop TO on;
+ SET enable_hashjoin TO off;
+ SET enable_mergejoin TO off;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+ QUERY PLAN
+ ---------------------------------------------------------------------------------------------------
+ Finalize HashAggregate
+ Group Key: p.i
+ -> Gather
+ Workers Planned: 2
+ -> Nested Loop
+ -> Partial HashAggregate
+ Group Key: c1.parent
+ -> Nested Loop
+ -> Parallel Seq Scan on agg_pushdown_child1 c1
+ -> Index Scan using agg_pushdown_child2_pkey on agg_pushdown_child2 c2
+ Index Cond: (k = c1.j)
+ Filter: (c1.parent = parent)
+ -> Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+ Index Cond: (i = c1.parent)
+ (14 rows)
+
+ SET enable_nestloop TO off;
+ SET enable_hashjoin TO on;
+ -- TODO Force the Hash Join node to stay below Gather.
+ -- EXPLAIN (COSTS off)
+ -- SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ -- agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ -- c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+ SET enable_hashjoin TO off;
+ SET enable_mergejoin TO on;
+ SET enable_seqscan TO off;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+ QUERY PLAN
+ ------------------------------------------------------------------------------------------------------------------
+ Finalize GroupAggregate
+ Group Key: p.i
+ -> Gather Merge
+ Workers Planned: 2
+ -> Merge Join
+ Merge Cond: (c1.parent = p.i)
+ -> Sort
+ Sort Key: c1.parent
+ -> Partial HashAggregate
+ Group Key: c1.parent
+ -> Merge Join
+ Merge Cond: (c1.j = c2.k)
+ Join Filter: (c1.parent = c2.parent)
+ -> Parallel Index Scan using agg_pushdown_child1_pkey on agg_pushdown_child1 c1
+ -> Index Scan using agg_pushdown_child2_pkey on agg_pushdown_child2 c2
+ -> Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+ (16 rows)
+
+ SET cpu_operator_cost TO 0.1;
+ EXPLAIN (COSTS off)
+ SELECT p.i / 2, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i / 2;
+ QUERY PLAN
+ ------------------------------------------------------------------------------------------------------------------------
+ Finalize HashAggregate
+ Group Key: (((c1.parent / 2)))
+ -> Gather
+ Workers Planned: 2
+ -> Merge Join
+ Merge Cond: (c1.parent = p.i)
+ -> Sort
+ Sort Key: c1.parent
+ -> Partial HashAggregate
+ Group Key: (c1.parent / 2), c1.parent, c2.parent
+ -> Result
+ -> Merge Join
+ Merge Cond: (c1.j = c2.k)
+ Join Filter: (c1.parent = c2.parent)
+ -> Parallel Index Scan using agg_pushdown_child1_pkey on agg_pushdown_child1 c1
+ -> Index Scan using agg_pushdown_child2_pkey on agg_pushdown_child2 c2
+ -> Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+ (17 rows)
+
+ ROLLBACK;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
new file mode 100644
index e224977..cf52728
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
*************** test: rules psql_crosstab amutils
*** 98,103 ****
--- 98,106 ----
test: select_parallel
test: write_parallel
+ # this one runs parallel workers too
+ test: agg_pushdown
+
# no relation related tests can be put in this group
test: publication subscription
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
new file mode 100644
index 9fc5f1a..ca0bf76
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
*************** test: stats_ext
*** 136,141 ****
--- 136,142 ----
test: rules
test: psql_crosstab
test: select_parallel
+ test: agg_pushdown
test: write_parallel
test: publication
test: subscription
diff --git a/src/test/regress/sql/agg_pushdown.sql b/src/test/regress/sql/agg_pushdown.sql
new file mode 100644
index ...e6e40cb
*** a/src/test/regress/sql/agg_pushdown.sql
--- b/src/test/regress/sql/agg_pushdown.sql
***************
*** 0 ****
--- 1,138 ----
+ BEGIN;
+
+ CREATE TABLE agg_pushdown_parent (
+ i int primary key);
+
+ CREATE TABLE agg_pushdown_child1 (
+ j int primary key,
+ parent int references agg_pushdown_parent,
+ v double precision);
+
+ CREATE INDEX ON agg_pushdown_child1(parent);
+
+ CREATE TABLE agg_pushdown_child2 (
+ k int primary key,
+ parent int references agg_pushdown_parent,
+ v double precision);
+
+ INSERT INTO agg_pushdown_parent(i)
+ SELECT n
+ FROM generate_series(0, 7) AS s(n);
+
+ INSERT INTO agg_pushdown_child1(j, parent, v)
+ SELECT 64 * i + n, i, random()
+ FROM generate_series(0, 63) AS s(n), agg_pushdown_parent;
+
+ INSERT INTO agg_pushdown_child2(k, parent, v)
+ SELECT 64 * i + n, i, random()
+ FROM generate_series(0, 63) AS s(n), agg_pushdown_parent;
+
+ ANALYZE;
+
+ SET enable_agg_pushdown TO on;
+
+ -- The supposed advantage of pushing aggregation down below joins is that
+ -- joins will then receive less input tuples. Let's stress this difference.
+ SET cpu_tuple_cost TO 0.1;
+
+ -- Perform scan of a table and partially aggregate the result.
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
+ AS c1 ON c1.parent = p.i GROUP BY p.i;
+
+ -- Perform nestloop join between agg_pushdown_child1 and agg_pushdown_child2
+ -- and partially aggregate the result.
+ SET enable_nestloop TO on;
+ SET enable_hashjoin TO off;
+ SET enable_mergejoin TO off;
+
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+
+ -- The same for hash join.
+ SET enable_nestloop TO off;
+ SET enable_hashjoin TO on;
+
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+
+ -- The same for merge join.
+ SET enable_hashjoin TO off;
+ SET enable_mergejoin TO on;
+ SET enable_seqscan TO off;
+
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+
+ -- Generic grouping expression.
+
+ -- Make any processing of the grouping expression expensive. If the final
+ -- relation output needs to be sorted before GroupAggregate is applied, the
+ -- (p.i / 2) expression is also evaluated by the Sort node. Thus the plan that
+ -- uses the partial aggregation is preferrable because it emits the value of
+ -- the exprssion and no node above has to evaluate it again.
+ SET cpu_operator_cost TO 0.1;
+
+ EXPLAIN (COSTS off)
+ SELECT p.i / 2, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i / 2;
+
+ -- The same tests for parallel plans.
+ RESET ALL;
+
+ SET parallel_setup_cost TO 0;
+ SET parallel_tuple_cost TO 0;
+ SET min_parallel_table_scan_size TO 0;
+ SET min_parallel_index_scan_size TO 0;
+ SET max_parallel_workers_per_gather TO 4;
+
+ SET enable_agg_pushdown TO on;
+ SET cpu_tuple_cost TO 0.1;
+
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
+ AS c1 ON c1.parent = p.i GROUP BY p.i;
+
+ SET enable_nestloop TO on;
+ SET enable_hashjoin TO off;
+ SET enable_mergejoin TO off;
+
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+
+ SET enable_nestloop TO off;
+ SET enable_hashjoin TO on;
+
+ -- TODO Force the Hash Join node to stay below Gather.
+ -- EXPLAIN (COSTS off)
+ -- SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ -- agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ -- c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+
+ SET enable_hashjoin TO off;
+ SET enable_mergejoin TO on;
+ SET enable_seqscan TO off;
+
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i;
+
+ SET cpu_operator_cost TO 0.1;
+
+ EXPLAIN (COSTS off)
+ SELECT p.i / 2, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
+ agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
+ c2.parent = p.i WHERE c1.j = c2.k GROUP BY p.i / 2;
+
+ ROLLBACK;
+
agg_pushdown_v5/08_index.diff
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
new file mode 100644
index 36e038e..045dd21
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** set_plain_rel_pathlist(PlannerInfo *root
*** 738,744 ****
/*
* Consider index scans.
*/
! create_index_paths(root, rel);
/* Consider TID scans */
create_tidscan_paths(root, rel);
--- 738,744 ----
/*
* Consider index scans.
*/
! create_index_paths(root, rel, agg_info);
/* Consider TID scans */
create_tidscan_paths(root, rel);
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
new file mode 100644
index c8bd820..5c39ef6
*** 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"
*************** typedef struct
*** 78,83 ****
--- 79,93 ----
int indexcol; /* index column we want to match to */
} ec_member_matches_arg;
+ /*
+ * A set of lists containing index paths to which specific kind of aggregation
+ * was applied.
+ */
+ typedef struct GroupedIndexPaths
+ {
+ List *paths;
+ List *paths_partial;
+ } GroupedIndexPaths;
static void consider_index_join_clauses(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index,
*************** static bool eclass_already_used(Equivale
*** 107,123 ****
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,
! List *clauses, List *other_clauses);
static Path *choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel,
List *paths);
static int path_usage_comparator(const void *a, const void *b);
--- 117,139 ----
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, PartialAggInfo *agg_info);
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,
! PartialAggInfo *agg_info,
! GroupedIndexPaths *agg_sorted,
! GroupedIndexPaths *agg_hashed);
static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
! List *clauses, List *other_clauses,
! PartialAggInfo *agg_info,
! GroupedIndexPaths *agg_hashed);
static List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
! List *clauses, List *other_clauses,
! PartialAggInfo *agg_info);
static Path *choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel,
List *paths);
static int path_usage_comparator(const void *a, const void *b);
*************** static Const *string_to_const(const char
*** 227,235 ****
* index quals ... but for now, it doesn't seem worth troubling over.
* In particular, comments below about "unparameterized" paths should be read
* as meaning "unparameterized so far as the indexquals are concerned".
*/
void
! create_index_paths(PlannerInfo *root, RelOptInfo *rel)
{
List *indexpaths;
List *bitindexpaths;
--- 243,254 ----
* index quals ... but for now, it doesn't seem worth troubling over.
* In particular, comments below about "unparameterized" paths should be read
* as meaning "unparameterized so far as the indexquals are concerned".
+ *
+ * If agg_info is passed, grouped paths are generated too.
*/
void
! create_index_paths(PlannerInfo *root, RelOptInfo *rel,
! PartialAggInfo *agg_info)
{
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
--- 293,300 ----
* 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,
! agg_info);
/*
* Identify the join clauses that can match the index. For the moment
*************** create_index_paths(PlannerInfo *root, Re
*** 310,326 ****
/*
* Generate BitmapOrPaths for any suitable OR-clauses present in the
* restriction list. Add these to bitindexpaths.
*/
indexpaths = generate_bitmap_or_paths(root, rel,
! rel->baserestrictinfo, NIL);
bitindexpaths = list_concat(bitindexpaths, indexpaths);
/*
* Likewise, generate BitmapOrPaths for any suitable OR-clauses present in
* the joinclause list. Add these to bitjoinpaths.
*/
indexpaths = generate_bitmap_or_paths(root, rel,
! joinorclauses, rel->baserestrictinfo);
bitjoinpaths = list_concat(bitjoinpaths, indexpaths);
/*
--- 329,351 ----
/*
* Generate BitmapOrPaths for any suitable OR-clauses present in the
* restriction list. Add these to bitindexpaths.
+ *
+ * Include the grouped path.
*/
indexpaths = generate_bitmap_or_paths(root, rel,
! rel->baserestrictinfo, NIL,
! agg_info);
bitindexpaths = list_concat(bitindexpaths, indexpaths);
/*
* Likewise, generate BitmapOrPaths for any suitable OR-clauses present in
* the joinclause list. Add these to bitjoinpaths.
+ *
+ * Do not include the grouped paths, as these cab be parameterized.
*/
indexpaths = generate_bitmap_or_paths(root, rel,
! joinorclauses, rel->baserestrictinfo,
! NULL);
bitjoinpaths = list_concat(bitjoinpaths, indexpaths);
/*
*************** 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
--- 692,698 ----
Assert(clauseset.nonempty);
/* Build index path(s) using the collected set of clauses */
! get_index_paths(root, rel, index, &clauseset, bitindexpaths, NULL);
/*
* Remember we considered paths for this set of relids. We use lcons not
*************** bms_equal_any(Relids relids, List *relid
*** 717,723 ****
return false;
}
-
/*
* get_index_paths
* Given an index and a set of index clauses for it, construct IndexPaths.
--- 742,747 ----
*************** bms_equal_any(Relids relids, List *relid
*** 736,765 ****
static void
get_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
! List **bitindexpaths)
{
List *indexpaths;
bool skip_nonnative_saop = false;
bool skip_lower_saop = false;
ListCell *lc;
/*
* Build simple index paths using the clauses. Allow ScalarArrayOpExpr
* clauses only if the index AM supports them natively, and skip any such
* clauses for index columns after the first (so that we produce ordered
* paths if possible).
*/
indexpaths = build_index_paths(root, rel,
index, clauses,
index->predOK,
ST_ANYSCAN,
&skip_nonnative_saop,
! &skip_lower_saop);
/*
* If we skipped any lower-order ScalarArrayOpExprs on an index with an AM
* that supports them, then try again including those clauses. This will
* produce paths with more selectivity but no ordering.
*/
if (skip_lower_saop)
{
--- 760,826 ----
static void
get_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
! List **bitindexpaths, PartialAggInfo *agg_info)
{
List *indexpaths;
+ GroupedIndexPaths agg_sorted;
+ GroupedIndexPaths *agg_sorted_p = NULL;
+ GroupedIndexPaths agg_hashed;
+ GroupedIndexPaths *agg_hashed_p = NULL;
bool skip_nonnative_saop = false;
bool skip_lower_saop = false;
ListCell *lc;
/*
+ * Set all lists of grouped paths to NIL.
+ */
+ MemSet(&agg_sorted, 0, sizeof(GroupedIndexPaths));
+ MemSet(&agg_hashed, 0, sizeof(GroupedIndexPaths));
+
+ /*
+ * If grouping at relation level might be useful for the current query,
+ * prepare for collection of the grouped paths.
+ */
+ if (agg_info != NULL)
+ {
+ /*
+ * AGG_HASHED is always possible.
+ */
+ agg_hashed_p = &agg_hashed;
+
+ /*
+ * Can this index provide input for AGG_SORTED aggregation?
+ */
+ if (index->amhasgettuple)
+ agg_sorted_p = &agg_sorted;
+ }
+
+ /*
* Build simple index paths using the clauses. Allow ScalarArrayOpExpr
* clauses only if the index AM supports them natively, and skip any such
* clauses for index columns after the first (so that we produce ordered
* paths if possible).
+ *
+ * These paths are good candidates for AGG_SORTED, so pass the output
+ * lists for this strategy. AGG_HASHED should be applied to paths with no
+ * pathkeys.
*/
indexpaths = build_index_paths(root, rel,
index, clauses,
index->predOK,
ST_ANYSCAN,
&skip_nonnative_saop,
! &skip_lower_saop,
! agg_info,
! agg_sorted_p, agg_hashed_p);
/*
* If we skipped any lower-order ScalarArrayOpExprs on an index with an AM
* that supports them, then try again including those clauses. This will
* produce paths with more selectivity but no ordering.
+ *
+ * As for the grouping paths, only AGG_HASHED is considered due to the
+ * missing ordering.
*/
if (skip_lower_saop)
{
*************** get_index_paths(PlannerInfo *root, RelOp
*** 769,775 ****
index->predOK,
ST_ANYSCAN,
&skip_nonnative_saop,
! NULL));
}
/*
--- 830,838 ----
index->predOK,
ST_ANYSCAN,
&skip_nonnative_saop,
! NULL,
! agg_info,
! NULL, agg_hashed_p));
}
/*
*************** get_index_paths(PlannerInfo *root, RelOp
*** 801,806 ****
--- 864,872 ----
* If there were ScalarArrayOpExpr clauses that the index can't handle
* natively, generate bitmap scan paths relying on executor-managed
* ScalarArrayOpExpr.
+ *
+ * As for grouping, only AGG_HASHED is possible here. Again, because
+ * there's no ordering.
*/
if (skip_nonnative_saop)
{
*************** get_index_paths(PlannerInfo *root, RelOp
*** 809,817 ****
false,
ST_BITMAPSCAN,
NULL,
! NULL);
*bitindexpaths = list_concat(*bitindexpaths, indexpaths);
}
}
/*
--- 875,909 ----
false,
ST_BITMAPSCAN,
NULL,
! NULL,
! agg_info,
! NULL, agg_hashed_p);
*bitindexpaths = list_concat(*bitindexpaths, indexpaths);
}
+
+ /*
+ * If we collected the grouped path, process them now.
+ */
+ if (agg_info != NULL)
+ {
+ List *paths;
+ AggPath *agg_path;
+
+ paths = list_concat(agg_sorted.paths, agg_hashed.paths);
+ foreach(lc, paths)
+ {
+ agg_path = lfirst_node(AggPath, lc);
+ add_path(rel, (Path *) agg_path, true);
+ }
+
+ paths = list_concat(agg_sorted.paths_partial,
+ agg_hashed.paths_partial);
+ foreach(lc, paths)
+ {
+ agg_path = lfirst_node(AggPath, lc);
+ add_partial_path(rel, (Path *) agg_path, true);
+ }
+ }
}
/*
*************** get_index_paths(PlannerInfo *root, RelOp
*** 847,859 ****
* NULL, we do not ignore non-first ScalarArrayOpExpr clauses, but they will
* result in considering the scan's output to be unordered.
*
* 'rel' is the index's heap relation
* 'index' is the index for which we want to generate paths
* 'clauses' is the collection of indexable clauses (RestrictInfo nodes)
* 'useful_predicate' indicates whether the index has a useful predicate
* 'scantype' indicates whether we need plain or bitmap scan support
* 'skip_nonnative_saop' indicates whether to accept SAOP if index AM doesn't
! * 'skip_lower_saop' indicates whether to accept non-first-column SAOP
*/
static List *
build_index_paths(PlannerInfo *root, RelOptInfo *rel,
--- 939,959 ----
* NULL, we do not ignore non-first ScalarArrayOpExpr clauses, but they will
* result in considering the scan's output to be unordered.
*
+ * If 'agg_info' is passed, 'agg_sorted' and / or 'agg_hashed' must be passed
+ * too. In that case AGG_SORTED and / or AGG_HASHED aggregation is applied to
+ * the index path (as long as the index path is appropriate) and the resulting
+ * grouped path is stored here.
+ *
* 'rel' is the index's heap relation
* 'index' is the index for which we want to generate paths
* 'clauses' is the collection of indexable clauses (RestrictInfo nodes)
* 'useful_predicate' indicates whether the index has a useful predicate
* 'scantype' indicates whether we need plain or bitmap scan support
* 'skip_nonnative_saop' indicates whether to accept SAOP if index AM doesn't
! * 'skip_lower_saop' indicates whether to accept non-first-column SAOP.
! * 'agg_info' is information needed to aggregate the join scan.
! * 'agg_sorted' is an output lists for AGG_SORTED paths.
! * 'agg_hashed' is an output lists for AGG_HASHED paths.
*/
static List *
build_index_paths(PlannerInfo *root, RelOptInfo *rel,
*************** 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;
--- 961,970 ----
bool useful_predicate,
ScanTypeControl scantype,
bool *skip_nonnative_saop,
! bool *skip_lower_saop,
! PartialAggInfo *agg_info,
! GroupedIndexPaths *agg_sorted,
! GroupedIndexPaths *agg_hashed)
{
List *result = NIL;
IndexPath *ipath;
*************** build_index_paths(PlannerInfo *root, Rel
*** 878,883 ****
--- 981,999 ----
bool index_is_ordered;
bool index_only_scan;
int indexcol;
+ bool include_grouped;
+ bool can_agg_sorted,
+ can_agg_hashed;
+ AggPath *agg_path;
+
+ /*
+ * Make some of the following tests simpler.
+ */
+ include_grouped = agg_sorted != NULL || agg_hashed != NULL;
+
+ /* Check that the agg_* arguments are consistent. */
+ Assert((agg_info == NULL && !include_grouped) ||
+ (agg_info != NULL && include_grouped));
/*
* Check that index supports the desired scan type(s)
*************** build_index_paths(PlannerInfo *root, Rel
*** 1031,1037 ****
--- 1147,1158 ----
* in the current clauses, OR the index ordering is potentially useful for
* later merging or final output ordering, OR the index has a useful
* predicate, OR an index-only scan is possible.
+ *
+ * This is where grouped path start to be considered.
*/
+ can_agg_sorted = true;
+ can_agg_hashed = true;
+
if (index_clauses != NIL || useful_pathkeys != NIL || useful_predicate ||
index_only_scan)
{
*************** build_index_paths(PlannerInfo *root, Rel
*** 1048,1055 ****
--- 1169,1212 ----
outer_relids,
loop_count,
false);
+
result = lappend(result, ipath);
+ if (include_grouped && outer_relids == NULL &&
+ rel->gpi != NULL && rel->gpi->target != NULL)
+ {
+ /*
+ * Try to create the grouped paths if caller is interested in
+ * them.
+ */
+ if (agg_sorted != NULL && useful_pathkeys != NIL)
+ {
+ agg_path = create_partial_agg_sorted_path(root,
+ (Path *) ipath,
+ true,
+ agg_info,
+ ipath->path.rows);
+
+ if (agg_path != NULL)
+ agg_sorted->paths = lappend(agg_sorted->paths, agg_path);
+ else
+ can_agg_sorted = false;
+ }
+
+ if (agg_hashed != NULL)
+ {
+ agg_path = create_partial_agg_hashed_path(root,
+ (Path *) ipath,
+ agg_info,
+ ipath->path.rows);
+
+ if (agg_path != NULL)
+ agg_hashed->paths = lappend(agg_hashed->paths, agg_path);
+ else
+ can_agg_hashed = false;
+ }
+ }
+
/*
* If appropriate, consider parallel index scan. We don't allow
* parallel index scan for bitmap index scans.
*************** build_index_paths(PlannerInfo *root, Rel
*** 1077,1083 ****
--- 1234,1285 ----
* parallel workers, just free it.
*/
if (ipath->path.parallel_workers > 0)
+ {
add_partial_path(rel, (Path *) ipath, false);
+
+ if (include_grouped && outer_relids == NULL &&
+ rel->gpi != NULL && rel->gpi->target != NULL)
+ {
+ if (agg_sorted != NULL && useful_pathkeys != NIL &&
+ can_agg_sorted)
+ {
+ /*
+ * No need to check the pathkeys again.
+ */
+ agg_path = create_partial_agg_sorted_path(root,
+ (Path *) ipath,
+ false,
+ agg_info,
+ ipath->path.rows);
+
+ /*
+ * If create_agg_sorted_path succeeded once, it should
+ * always do.
+ */
+ Assert(agg_path != NULL);
+
+ agg_sorted->paths_partial =
+ lappend(agg_sorted->paths_partial, agg_path);
+ }
+
+ if (agg_hashed != NULL && can_agg_hashed)
+ {
+ agg_path = create_partial_agg_hashed_path(root,
+ (Path *) ipath,
+ agg_info,
+ ipath->path.rows);
+
+ /*
+ * If create_agg_hashed_path succeeded once, it should
+ * always do.
+ */
+ Assert(agg_path != NULL);
+
+ agg_hashed->paths_partial =
+ lappend(agg_hashed->paths_partial, agg_path);
+ }
+ }
+ }
else
pfree(ipath);
}
*************** build_index_paths(PlannerInfo *root, Rel
*** 1105,1112 ****
--- 1307,1345 ----
outer_relids,
loop_count,
false);
+
result = lappend(result, ipath);
+ if (include_grouped && outer_relids == NULL &&
+ rel->gpi != NULL && rel->gpi->target != NULL)
+ {
+ /*
+ * As the input set ordering does not matter to AGG_HASHED,
+ * only AGG_SORTED makes sense here. (The AGG_HASHED path we'd
+ * create here should already exist.)
+ *
+ * The existing value of can_agg_sorted is not up-to-date for
+ * the new pathkeys.
+ */
+ can_agg_sorted = true;
+
+ if (agg_sorted != NULL)
+ {
+ /* pathkeys are new, so check them. */
+ agg_path = create_partial_agg_sorted_path(root,
+ (Path *) ipath,
+ true,
+ agg_info,
+ ipath->path.rows);
+
+ if (agg_path != NULL)
+ agg_sorted->paths = lappend(agg_sorted->paths,
+ agg_path);
+ else
+ can_agg_sorted = false;
+ }
+ }
+
/* If appropriate, consider parallel index scan */
if (index->amcanparallel &&
rel->consider_parallel && outer_relids == NULL &&
*************** build_index_paths(PlannerInfo *root, Rel
*** 1129,1135 ****
--- 1362,1390 ----
* using parallel workers, just free it.
*/
if (ipath->path.parallel_workers > 0)
+ {
add_partial_path(rel, (Path *) ipath, false);
+
+ if (include_grouped && outer_relids == NULL &&
+ rel->gpi != NULL && rel->gpi->target != NULL)
+ {
+ if (agg_sorted != NULL && can_agg_sorted)
+ {
+ /*
+ * The non-partial path above should have been
+ * created, so no need to check pathkeys.
+ */
+ agg_path = create_partial_agg_sorted_path(root,
+ (Path *) ipath,
+ false,
+ agg_info,
+ ipath->path.rows);
+ Assert(agg_path != NULL);
+ agg_sorted->paths_partial =
+ lappend(agg_sorted->paths_partial, agg_path);
+ }
+ }
+ }
else
pfree(ipath);
}
*************** build_index_paths(PlannerInfo *root, Rel
*** 1164,1173 ****
* 'rel' is the relation for which we want to generate index paths
* 'clauses' is the current list of clauses (RestrictInfo nodes)
* 'other_clauses' is the list of additional upper-level clauses
*/
static List *
build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
! List *clauses, List *other_clauses)
{
List *result = NIL;
List *all_clauses = NIL; /* not computed till needed */
--- 1419,1430 ----
* 'rel' is the relation for which we want to generate index paths
* 'clauses' is the current list of clauses (RestrictInfo nodes)
* 'other_clauses' is the list of additional upper-level clauses
+ * 'agg_info' indicates that grouped paths should be added to 'agg_hashed'.
*/
static List *
build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
! List *clauses, List *other_clauses,
! PartialAggInfo *agg_info, GroupedIndexPaths *agg_hashed)
{
List *result = NIL;
List *all_clauses = NIL; /* not computed till needed */
*************** build_paths_for_OR(PlannerInfo *root, Re
*** 1244,1250 ****
useful_predicate,
ST_BITMAPSCAN,
NULL,
! NULL);
result = list_concat(result, indexpaths);
}
--- 1501,1508 ----
useful_predicate,
ST_BITMAPSCAN,
NULL,
! NULL,
! agg_info, NULL, agg_hashed);
result = list_concat(result, indexpaths);
}
*************** build_paths_for_OR(PlannerInfo *root, Re
*** 1260,1273 ****
* other_clauses is a list of additional clauses that can be assumed true
* for the purpose of generating indexquals, but are not to be searched for
* ORs. (See build_paths_for_OR() for motivation.)
*/
static List *
generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
! List *clauses, List *other_clauses)
{
List *result = NIL;
List *all_clauses;
ListCell *lc;
/*
* We can use both the current and other clauses as context for
--- 1518,1545 ----
* other_clauses is a list of additional clauses that can be assumed true
* for the purpose of generating indexquals, but are not to be searched for
* ORs. (See build_paths_for_OR() for motivation.)
+ *
+ * If agg_info is not NULL, consider grouped paths in addition.
*/
static List *
generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
! List *clauses, List *other_clauses,
! PartialAggInfo *agg_info)
{
List *result = NIL;
List *all_clauses;
ListCell *lc;
+ GroupedIndexPaths agg_hashed;
+ GroupedIndexPaths *agg_hashed_p = NULL;
+
+ /*
+ * Prepare the output for grouped paths.
+ *
+ * Bitmap scan is not supposed to deliver the tuples sorted, so only
+ * consider AGG_HASHED strategy.
+ */
+ if (agg_info != NULL)
+ agg_hashed_p = &agg_hashed;
/*
* We can use both the current and other clauses as context for
*************** generate_bitmap_or_paths(PlannerInfo *ro
*** 1287,1292 ****
--- 1559,1572 ----
continue;
/*
+ * Free what the previous iteration might have allocated.
+ */
+ list_free(agg_hashed.paths);
+ agg_hashed.paths = NIL;
+ list_free(agg_hashed.paths_partial);
+ agg_hashed.paths_partial = NIL;
+
+ /*
* We must be able to match at least one index to each of the arms of
* the OR, else we can't use it.
*/
*************** generate_bitmap_or_paths(PlannerInfo *ro
*** 1303,1315 ****
indlist = build_paths_for_OR(root, rel,
andargs,
! all_clauses);
/* Recurse in case there are sub-ORs */
indlist = list_concat(indlist,
generate_bitmap_or_paths(root, rel,
andargs,
! all_clauses));
}
else
{
--- 1583,1597 ----
indlist = build_paths_for_OR(root, rel,
andargs,
! all_clauses,
! agg_info, agg_hashed_p);
/* Recurse in case there are sub-ORs */
indlist = list_concat(indlist,
generate_bitmap_or_paths(root, rel,
andargs,
! all_clauses,
! agg_info));
}
else
{
*************** generate_bitmap_or_paths(PlannerInfo *ro
*** 1321,1327 ****
indlist = build_paths_for_OR(root, rel,
orargs,
! all_clauses);
}
/*
--- 1603,1610 ----
indlist = build_paths_for_OR(root, rel,
orargs,
! all_clauses,
! agg_info, agg_hashed_p);
}
/*
*************** generate_bitmap_or_paths(PlannerInfo *ro
*** 1351,1356 ****
--- 1634,1659 ----
bitmapqual = (Path *) create_bitmap_or_path(root, rel, pathlist);
result = lappend(result, bitmapqual);
}
+
+ /*
+ * Process the grouped paths.
+ */
+ if (pathlist != NIL && agg_info != NULL)
+ {
+ AggPath *agg_path;
+
+ foreach(lc, agg_hashed_p->paths)
+ {
+ agg_path = lfirst_node(AggPath, lc);
+ add_path(rel, (Path *) agg_path, true);
+ }
+
+ foreach(lc, agg_hashed_p->paths_partial)
+ {
+ agg_path = lfirst_node(AggPath, lc);
+ add_partial_path(rel, (Path *) agg_path, true);
+ }
+ }
}
return result;
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
new file mode 100644
index 7815138..b44b8a8
*** a/src/include/optimizer/paths.h
--- b/src/include/optimizer/paths.h
*************** extern void debug_print_rel(PlannerInfo
*** 75,81 ****
* 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);
--- 75,82 ----
* indxpath.c
* routines to generate index paths
*/
! extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel,
! PartialAggInfo *agg_info);
extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
List *restrictlist,
List *exprlist, List *oprlist);
diff --git a/src/test/regress/expected/agg_pushdown.out b/src/test/regress/expected/agg_pushdown.out
new file mode 100644
index e3071de..cac70a0
*** a/src/test/regress/expected/agg_pushdown.out
--- b/src/test/regress/expected/agg_pushdown.out
*************** AS c1 ON c1.parent = p.i GROUP BY p.i;
*** 40,45 ****
--- 40,64 ----
Index Cond: (i = c1.parent)
(8 rows)
+ -- Scan index on agg_pushdown_child1(parent) column and partially aggregate
+ -- the result using AGG_SORTED strategy.
+ SET enable_seqscan TO off;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
+ AS c1 ON c1.parent = p.i GROUP BY p.i;
+ QUERY PLAN
+ ---------------------------------------------------------------------------------------------
+ Finalize GroupAggregate
+ Group Key: p.i
+ -> Nested Loop
+ -> Partial GroupAggregate
+ Group Key: c1.parent
+ -> Index Scan using agg_pushdown_child1_parent_idx on agg_pushdown_child1 c1
+ -> Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+ Index Cond: (i = c1.parent)
+ (8 rows)
+
+ SET enable_seqscan TO on;
-- Perform nestloop join between agg_pushdown_child1 and agg_pushdown_child2
-- and partially aggregate the result.
SET enable_nestloop TO on;
*************** AS c1 ON c1.parent = p.i GROUP BY p.i;
*** 171,176 ****
--- 190,214 ----
Index Cond: (i = c1.parent)
(10 rows)
+ SET enable_seqscan TO off;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
+ AS c1 ON c1.parent = p.i GROUP BY p.i;
+ QUERY PLAN
+ ------------------------------------------------------------------------------------------------------------
+ Finalize HashAggregate
+ Group Key: p.i
+ -> Gather
+ Workers Planned: 2
+ -> Nested Loop
+ -> Partial GroupAggregate
+ Group Key: c1.parent
+ -> Parallel Index Scan using agg_pushdown_child1_parent_idx on agg_pushdown_child1 c1
+ -> Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
+ Index Cond: (i = c1.parent)
+ (10 rows)
+
+ SET enable_seqscan TO on;
SET enable_nestloop TO on;
SET enable_hashjoin TO off;
SET enable_mergejoin TO off;
*************** c2.parent = p.i WHERE c1.j = c2.k GROUP
*** 198,204 ****
SET enable_nestloop TO off;
SET enable_hashjoin TO on;
! -- TODO Force the Hash Join node to stay below Gather.
-- EXPLAIN (COSTS off)
-- SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
-- agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
--- 236,244 ----
SET enable_nestloop TO off;
SET enable_hashjoin TO on;
! -- TODO Force the Hash Join node to stay below Gather. Does each parallel
! -- worker still use a separate hash table? Try again once this limitation has
! -- been fixed.
-- EXPLAIN (COSTS off)
-- SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
-- agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
diff --git a/src/test/regress/sql/agg_pushdown.sql b/src/test/regress/sql/agg_pushdown.sql
new file mode 100644
index e6e40cb..f5b9b24
*** a/src/test/regress/sql/agg_pushdown.sql
--- b/src/test/regress/sql/agg_pushdown.sql
*************** EXPLAIN (COSTS off)
*** 40,45 ****
--- 40,53 ----
SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
AS c1 ON c1.parent = p.i GROUP BY p.i;
+ -- Scan index on agg_pushdown_child1(parent) column and partially aggregate
+ -- the result using AGG_SORTED strategy.
+ SET enable_seqscan TO off;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
+ AS c1 ON c1.parent = p.i GROUP BY p.i;
+ SET enable_seqscan TO on;
+
-- Perform nestloop join between agg_pushdown_child1 and agg_pushdown_child2
-- and partially aggregate the result.
SET enable_nestloop TO on;
*************** EXPLAIN (COSTS off)
*** 100,105 ****
--- 108,119 ----
SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
AS c1 ON c1.parent = p.i GROUP BY p.i;
+ SET enable_seqscan TO off;
+ EXPLAIN (COSTS off)
+ SELECT p.i, avg(c1.v) FROM agg_pushdown_parent AS p JOIN agg_pushdown_child1
+ AS c1 ON c1.parent = p.i GROUP BY p.i;
+ SET enable_seqscan TO on;
+
SET enable_nestloop TO on;
SET enable_hashjoin TO off;
SET enable_mergejoin TO off;
*************** c2.parent = p.i WHERE c1.j = c2.k GROUP
*** 112,118 ****
SET enable_nestloop TO off;
SET enable_hashjoin TO on;
! -- TODO Force the Hash Join node to stay below Gather.
-- EXPLAIN (COSTS off)
-- SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
-- agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
--- 126,135 ----
SET enable_nestloop TO off;
SET enable_hashjoin TO on;
! -- TODO Force the Hash Join node to stay below Gather. Does each parallel
! -- worker still use a separate hash table? Try again once this limitation has
! -- been fixed.
!
-- EXPLAIN (COSTS off)
-- SELECT p.i, avg(c1.v + c2.v) FROM agg_pushdown_parent AS p JOIN
-- agg_pushdown_child1 AS c1 ON c1.parent = p.i JOIN agg_pushdown_child2 AS c2 ON
agg_pushdown_v5/09_avoid_agg_finalization.diff
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index 85e8ea3..b1dd21e
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyAggref(const Aggref *from)
*** 1362,1367 ****
--- 1362,1368 ----
COPY_SCALAR_FIELD(inputcollid);
COPY_SCALAR_FIELD(aggtranstype);
COPY_SCALAR_FIELD(aggcombinefn);
+ COPY_SCALAR_FIELD(aggfinalfn);
COPY_NODE_FIELD(aggargtypes);
COPY_NODE_FIELD(aggdirectargs);
COPY_NODE_FIELD(args);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index d70d210..1ccaee7
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outAggref(StringInfo str, const Aggref
*** 1137,1142 ****
--- 1137,1143 ----
WRITE_OID_FIELD(inputcollid);
WRITE_OID_FIELD(aggtranstype);
WRITE_OID_FIELD(aggcombinefn);
+ WRITE_OID_FIELD(aggfinalfn);
WRITE_NODE_FIELD(aggargtypes);
WRITE_NODE_FIELD(aggdirectargs);
WRITE_NODE_FIELD(args);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
new file mode 100644
index f02a963..ed5686a
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readAggref(void)
*** 601,606 ****
--- 601,607 ----
READ_OID_FIELD(inputcollid);
READ_OID_FIELD(aggtranstype);
READ_OID_FIELD(aggcombinefn);
+ READ_OID_FIELD(aggfinalfn);
READ_NODE_FIELD(aggargtypes);
READ_NODE_FIELD(aggdirectargs);
READ_NODE_FIELD(args);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
new file mode 100644
index 045dd21..f95c913
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** set_plain_rel_pathlist(PlannerInfo *root
*** 706,711 ****
--- 706,714 ----
/* Consider sequential scan, both plain and grouped. */
seq_path = create_seqscan_path(root, rel, required_outer, 0);
+ /* Try to compute unique keys. */
+ make_uniquekeys(root, seq_path);
+
add_path(rel, seq_path, false);
if (rel->gpi != NULL && rel->gpi->target != NULL &&
*************** set_plain_rel_pathlist(PlannerInfo *root
*** 736,742 ****
create_plain_partial_paths(root, rel);
/*
! * Consider index scans.
*/
create_index_paths(root, rel, agg_info);
--- 739,746 ----
create_plain_partial_paths(root, rel);
/*
! * Consider index scans, possibly including the grouped and grouped
! * partial paths.
*/
create_index_paths(root, rel, agg_info);
*************** create_grouped_path(PlannerInfo *root, R
*** 847,858 ****
if (aggstrategy == AGG_HASHED)
agg_path = (Path *) create_partial_agg_hashed_path(root, subpath,
agg_info,
! subpath->rows);
else if (aggstrategy == AGG_SORTED)
agg_path = (Path *) create_partial_agg_sorted_path(root, subpath,
true,
agg_info,
! subpath->rows);
else
elog(ERROR, "unexpected strategy %d", aggstrategy);
--- 851,864 ----
if (aggstrategy == AGG_HASHED)
agg_path = (Path *) create_partial_agg_hashed_path(root, subpath,
agg_info,
! subpath->rows,
! partial);
else if (aggstrategy == AGG_SORTED)
agg_path = (Path *) create_partial_agg_sorted_path(root, subpath,
true,
agg_info,
! subpath->rows,
! partial);
else
elog(ERROR, "unexpected strategy %d", aggstrategy);
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1864,1869 ****
--- 1870,1879 ----
false, partitioned_rels, -1);
/* pathtarget will produce the grouped relation.. */
path->pathtarget = rel->gpi->target;
+
+ /* Try to compute unique keys. */
+ make_uniquekeys(root, path);
+
add_path(rel, path, true);
}
*************** generate_mergeappend_paths(PlannerInfo *
*** 2042,2047 ****
--- 2052,2060 ----
NULL,
partitioned_rels);
+ /* Try to compute unique keys. */
+ make_uniquekeys(root, path);
+
/* pathtarget will produce the grouped relation.. */
if (grouped)
{
*************** generate_mergeappend_paths(PlannerInfo *
*** 2060,2065 ****
--- 2073,2079 ----
pathkeys,
NULL,
partitioned_rels);
+ make_uniquekeys(root, path);
if (grouped)
path->pathtarget = rel->gpi->target;
add_path(rel, path, grouped);
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
new file mode 100644
index 5c39ef6..65db91c
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
***************
*** 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"
--- 32,37 ----
*************** get_index_paths(PlannerInfo *root, RelOp
*** 852,858 ****
--- 851,866 ----
IndexPath *ipath = (IndexPath *) lfirst(lc);
if (index->amhasgettuple)
+ {
+ /*
+ * In case grouping may be pushed down, try to identify unique
+ * keys to possibly avoid final aggregation.
+ */
+ if (root->grouped_var_list != NIL)
+ make_uniquekeys(root, (Path *) ipath);
+
add_path(rel, (Path *) ipath, false);
+ }
if (index->amhasgetbitmap &&
(ipath->path.pathkeys == NIL ||
*************** build_index_paths(PlannerInfo *root, Rel
*** 1185,1191 ****
(Path *) ipath,
true,
agg_info,
! ipath->path.rows);
if (agg_path != NULL)
agg_sorted->paths = lappend(agg_sorted->paths, agg_path);
--- 1193,1200 ----
(Path *) ipath,
true,
agg_info,
! ipath->path.rows,
! false);
if (agg_path != NULL)
agg_sorted->paths = lappend(agg_sorted->paths, agg_path);
*************** build_index_paths(PlannerInfo *root, Rel
*** 1198,1204 ****
agg_path = create_partial_agg_hashed_path(root,
(Path *) ipath,
agg_info,
! ipath->path.rows);
if (agg_path != NULL)
agg_hashed->paths = lappend(agg_hashed->paths, agg_path);
--- 1207,1214 ----
agg_path = create_partial_agg_hashed_path(root,
(Path *) ipath,
agg_info,
! ipath->path.rows,
! false);
if (agg_path != NULL)
agg_hashed->paths = lappend(agg_hashed->paths, agg_path);
*************** build_index_paths(PlannerInfo *root, Rel
*** 1250,1256 ****
(Path *) ipath,
false,
agg_info,
! ipath->path.rows);
/*
* If create_agg_sorted_path succeeded once, it should
--- 1260,1267 ----
(Path *) ipath,
false,
agg_info,
! ipath->path.rows,
! true);
/*
* If create_agg_sorted_path succeeded once, it should
*************** build_index_paths(PlannerInfo *root, Rel
*** 1267,1273 ****
agg_path = create_partial_agg_hashed_path(root,
(Path *) ipath,
agg_info,
! ipath->path.rows);
/*
* If create_agg_hashed_path succeeded once, it should
--- 1278,1285 ----
agg_path = create_partial_agg_hashed_path(root,
(Path *) ipath,
agg_info,
! ipath->path.rows,
! true);
/*
* If create_agg_hashed_path succeeded once, it should
*************** build_index_paths(PlannerInfo *root, Rel
*** 1330,1336 ****
(Path *) ipath,
true,
agg_info,
! ipath->path.rows);
if (agg_path != NULL)
agg_sorted->paths = lappend(agg_sorted->paths,
--- 1342,1349 ----
(Path *) ipath,
true,
agg_info,
! ipath->path.rows,
! false);
if (agg_path != NULL)
agg_sorted->paths = lappend(agg_sorted->paths,
*************** build_index_paths(PlannerInfo *root, Rel
*** 1378,1384 ****
(Path *) ipath,
false,
agg_info,
! ipath->path.rows);
Assert(agg_path != NULL);
agg_sorted->paths_partial =
lappend(agg_sorted->paths_partial, agg_path);
--- 1391,1398 ----
(Path *) ipath,
false,
agg_info,
! ipath->path.rows,
! true);
Assert(agg_path != NULL);
agg_sorted->paths_partial =
lappend(agg_sorted->paths_partial, agg_path);
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
new file mode 100644
index 7422fc4..883d8a1
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
*************** try_nestloop_path(PlannerInfo *root,
*** 520,525 ****
--- 520,532 ----
workspace.startup_cost, workspace.total_cost,
pathkeys, required_outer, grouped))
{
+ /*
+ * For a grouped path try to identify unique keys, to possibly avoid
+ * final aggregation.
+ */
+ if (grouped)
+ make_uniquekeys(root, (Path *) join_path);
+
add_path(joinrel, join_path, grouped);
}
else
*************** try_mergejoin_path(PlannerInfo *root,
*** 933,938 ****
--- 940,952 ----
workspace.startup_cost, workspace.total_cost,
si->merge_pathkeys, required_outer, grouped))
{
+ /*
+ * For a grouped path try to identify unique keys, to possibly avoid
+ * final aggregation.
+ */
+ if (grouped)
+ make_uniquekeys(root, (Path *) join_path);
+
add_path(joinrel, (Path *) join_path, grouped);
}
else
*************** try_hashjoin_path(PlannerInfo *root,
*** 1239,1244 ****
--- 1253,1265 ----
workspace.startup_cost, workspace.total_cost,
NIL, required_outer, grouped))
{
+ /*
+ * For a grouped path try to identify unique keys, to possibly avoid
+ * final aggregation.
+ */
+ if (grouped)
+ make_uniquekeys(root, (Path *) join_path);
+
add_path(joinrel, (Path *) join_path, grouped);
}
else
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
new file mode 100644
index c6870d3..bee0157
*** a/src/backend/optimizer/path/pathkeys.c
--- b/src/backend/optimizer/path/pathkeys.c
*************** has_useful_pathkeys(PlannerInfo *root, R
*** 1563,1565 ****
--- 1563,1719 ----
return true; /* might be able to use them for ordering */
return false; /* definitely useless */
}
+
+ /*
+ * Add a new set of unique keys to path's list of unique key sets. If an
+ * identical set is already there --- free new_set instead of adding it.
+ */
+ void
+ add_uniquekeys_to_path(Path *path, Bitmapset *new_set)
+ {
+ ListCell *lc;
+
+ foreach(lc, path->uniquekeys)
+ {
+ Bitmapset *set = (Bitmapset *) lfirst(lc);
+
+ if (bms_equal(new_set, set))
+ break;
+ }
+ if (lc == NULL)
+ path->uniquekeys = lappend(path->uniquekeys, new_set);
+ else
+ bms_free(new_set);
+ }
+
+ /*
+ * Return true if path output is grouped in a way described by
+ * root->group_pathkeys. AGGSPLIT_FINAL_DESERIAL can be skipped in such a
+ * case.
+ */
+ bool
+ match_path_to_group_pathkeys(PlannerInfo *root, Path *path)
+ {
+ Bitmapset *uniquekeys_all = NULL;
+ ListCell *l1;
+ int i;
+ bool *is_group_expr;
+
+ /*
+ * group_pathkeys are essential for this function.
+ */
+ if (root->group_pathkeys == NIL)
+ return false;
+
+ /*
+ * The path is not aware of being unique.
+ */
+ if (path->uniquekeys == NIL)
+ return false;
+
+ /*
+ * There can be multiple known unique key sets. Gather pathkeys of all the
+ * unique expressions the sets may reference.
+ */
+ foreach(l1, path->uniquekeys)
+ {
+ Bitmapset *set = (Bitmapset *) lfirst(l1);
+
+ uniquekeys_all = bms_union(uniquekeys_all, set);
+ }
+
+ /*
+ * Find pathkeys for the expressions.
+ */
+ is_group_expr = (bool *)
+ palloc0(list_length(path->pathtarget->exprs) * sizeof(bool));
+
+ i = 0;
+ foreach(l1, path->pathtarget->exprs)
+ {
+ Expr *expr = (Expr *) lfirst(l1);
+
+ if (bms_is_member(i, uniquekeys_all))
+ {
+ ListCell *l2;
+ bool found = false;
+
+ /*
+ * This is an unique expression, so find its pathkey.
+ */
+ foreach(l2, root->group_pathkeys)
+ {
+ PathKey *pk = lfirst_node(PathKey, l2);
+ EquivalenceClass *ec = pk->pk_eclass;
+ ListCell *l3;
+ EquivalenceMember *em = NULL;
+
+ if (ec->ec_below_outer_join)
+ continue;
+ if (ec->ec_has_volatile)
+ continue;
+
+ foreach(l3, ec->ec_members)
+ {
+ em = lfirst_node(EquivalenceMember, l3);
+
+ if (em->em_nullable_relids)
+ continue;
+
+ if (equal(em->em_expr, expr))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ break;
+
+ }
+ is_group_expr[i] = found;
+ }
+
+ i++;
+ }
+
+ /*
+ * Now check the unique key sets and see if any one has all its
+ * expressions among group_pathkeys.
+ */
+ foreach(l1, path->uniquekeys)
+ {
+ Bitmapset *set = (Bitmapset *) lfirst(l1);
+ bool found = false;
+
+ /*
+ * Collect PKs associated with this set.
+ */
+ for (i = 0; i < list_length(path->pathtarget->exprs); i++)
+ {
+ if (bms_is_member(i, set))
+ {
+ /*
+ * If the set misses a single grouping path key, at least one
+ * expression of the unique key is outside the grouping
+ * expressions, and thus the grouping expressions are not
+ * guaranteed to be unique. Thus the avoidance of final
+ * aggregation is not justified.
+ */
+ if (!is_group_expr[i])
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ /*
+ * No problem with this set. No need to check the other ones.
+ */
+ if (!found)
+ return true;
+ }
+
+ /* No match found. */
+ return false;
+ }
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 94b1f8e..6ad1218
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** static PathTarget *make_sort_input_targe
*** 194,199 ****
--- 194,201 ----
bool *have_postponed_srfs);
static void adjust_paths_for_srfs(PlannerInfo *root, RelOptInfo *rel,
List *targets, List *targets_contain_srfs);
+ static Path *adjust_path_for_unique_group_keys(PlannerInfo *root, Path *path,
+ PathTarget *target);
/*****************************************************************************
*************** grouping_planner(PlannerInfo *root, bool
*** 1977,1982 ****
--- 1979,1985 ----
grouping_target,
&agg_costs,
gset_data);
+
/* Fix things up if grouping_target contains SRFs */
if (parse->hasTargetSRFs)
adjust_paths_for_srfs(root, current_rel,
*************** create_grouping_paths(PlannerInfo *root,
*** 4178,4183 ****
--- 4181,4212 ----
}
}
+ /*
+ * If input_rel has partially aggregated paths which emit an unique set of
+ * grouping keys, we only need to call aggfinalfn on each aggregate state
+ * instead of doing the final aggregation
+ */
+ if (input_rel->gpi != NULL && !parse->groupingSets)
+ {
+ foreach(lc, input_rel->gpi->pathlist)
+ {
+ Path *path = (Path *) lfirst(lc);
+
+ /*
+ * If aggregation could have been pushed down and if the path
+ * generates unique grouping keys, replace aggregates with the
+ * aggregate final functions alone.
+ */
+ if ((parse->groupClause || parse->hasAggs || root->hasHavingQual)
+ && root->grouped_var_list != NIL &&
+ match_path_to_group_pathkeys(root, path))
+ add_path(grouped_rel,
+ adjust_path_for_unique_group_keys(root, path,
+ target),
+ false);
+ }
+ }
+
/* Give a helpful error if we failed to find any implementation */
if (grouped_rel->pathlist == NIL)
ereport(ERROR,
*************** adjust_paths_for_srfs(PlannerInfo *root,
*** 6160,6165 ****
--- 6189,6228 ----
}
/*
+ * Project a path that generates unique grouping keys to a new one whose
+ * target, instead of the (partial) aggregates, contains the aggregates' final
+ * functions, each referencing the corresponding partial aggregate.
+ */
+ static Path *
+ adjust_path_for_unique_group_keys(PlannerInfo *root, Path *path,
+ PathTarget *target)
+ {
+ Path *result;
+
+ /*
+ * While path->pathtarget contains GroupedVars, "target contains Aggrefs.
+ * setrefs.c will recognize and handle this special case.
+ *
+ * Pass true for "force_result" so that the input path's target does not
+ * receive extra entries (such as junk TLEs) from upper nodes (e.g. Sort)
+ * that it might not be able to evaluate (e.g. aggregates)
+ */
+ result = (Path *) create_projection_path(root, path->parent, path,
+ target, true);
+
+ /*
+ * TODO Account for the evaluation of the aggfinalfn function. BTW, this
+ * might be a case for a new kind of Path instead of projection. Otherwise
+ * it's hard to detect this special kind of input and output targetlist,
+ * without checking for presence of Aggrefs and GroupedVars respectively.
+ */
+ result->startup_cost = path->startup_cost;
+ result->total_cost = path->total_cost;
+ result->rows = path->rows;
+ return result;
+ }
+
+ /*
* expression_planner
* Perform planner's transformations on a standalone expression.
*
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
new file mode 100644
index b5be81c..502c725
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
*************** static Node *fix_scan_expr_mutator(Node
*** 108,113 ****
--- 108,115 ----
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
+ static bool contains_groupedvar(Node *node);
+ static bool contains_groupedvar_walker(Node *node, void *context);
static void set_param_references(PlannerInfo *root, Plan *plan);
static Node *convert_combining_aggrefs(Node *node, void *context);
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
*************** set_upper_references(PlannerInfo *root,
*** 1786,1791 ****
--- 1788,1808 ----
replace_grouped_vars_with_aggrefs(root, subplan->targetlist);
}
}
+ else if (IsA(plan, Result))
+ {
+ /*
+ * Check if this is the projection created by
+ * adjust_path_for_unique_group_keys.
+ */
+ if (root->grouped_var_list != NIL &&
+ contains_aggref_simple((Node *) plan->targetlist) &&
+ contains_groupedvar((Node *) subplan->targetlist))
+ {
+ plan->targetlist = (List *)
+ replace_aggref_with_groupedvar((Node *) plan->targetlist,
+ root->grouped_var_list);
+ }
+ }
}
subplan_itlist = build_tlist_index(subplan->targetlist);
*************** set_upper_references(PlannerInfo *root,
*** 1841,1846 ****
--- 1858,1882 ----
}
/*
+ * Find out if the node contains at least one GroupedVar.
+ */
+ static bool
+ contains_groupedvar(Node *node)
+ {
+ return contains_groupedvar_walker(node, NULL);
+ }
+
+ static bool
+ contains_groupedvar_walker(Node *node, void *context)
+ {
+ if (node == NULL)
+ return false;
+ if (IsA(node, GroupedVar))
+ return true; /* abort the tree traversal and return true */
+ return expression_tree_walker(node, contains_groupedvar_walker, context);
+ }
+
+ /*
* set_param_references
* Initialize the initParam list in Gather or Gather merge node such that
* it contains reference of all the params that needs to be evaluated
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
new file mode 100644
index c3528fc..1d01457
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
*************** static List *reparameterize_pathlist_by_
*** 60,65 ****
--- 60,68 ----
static Path *add_grouping_expressions_to_subpath(PlannerInfo *root,
Path *subpath,
PathTarget *group_exprs);
+ static void make_uniquekeys_for_unique_index(PathTarget *reltarget,
+ IndexOptInfo *index, Path *path);
+ static void make_uniquekeys_for_append_path(PlannerInfo *root, Path *path);
/*****************************************************************************
* MISC. PATH UTILITIES
*************** create_material_path(RelOptInfo *rel, Pa
*** 1515,1520 ****
--- 1518,1524 ----
subpath->parallel_safe;
pathnode->path.parallel_workers = subpath->parallel_workers;
pathnode->path.pathkeys = subpath->pathkeys;
+ pathnode->path.uniquekeys = subpath->uniquekeys;
pathnode->subpath = subpath;
*************** create_projection_path(PlannerInfo *root
*** 2434,2439 ****
--- 2438,2444 ----
pathnode->path.parallel_workers = subpath->parallel_workers;
/* Projection does not change the sort order */
pathnode->path.pathkeys = subpath->pathkeys;
+ pathnode->path.uniquekeys = subpath->uniquekeys;
pathnode->subpath = subpath;
pathnode->force_result = force_result;
*************** create_sort_path(PlannerInfo *root,
*** 2685,2690 ****
--- 2690,2696 ----
subpath->parallel_safe;
pathnode->path.parallel_workers = subpath->parallel_workers;
pathnode->path.pathkeys = pathkeys;
+ pathnode->path.uniquekeys = subpath->uniquekeys;
pathnode->subpath = subpath;
*************** get_partial_agg_info(PlannerInfo *root,
*** 2963,2969 ****
/*
* Apply partial AGG_SORTED aggregation path to subpath if it's suitably
! * sorted.
*
* check_pathkeys can be passed FALSE if the function was already called for
* given index --- since the target should not change, we can skip the check
--- 2969,2976 ----
/*
* Apply partial AGG_SORTED aggregation path to subpath if it's suitably
! * sorted. ("partial" in the function name refers to AGGSPLIT_INITIAL_SERIAL
! * strategy, as opposed to parallel processing.)
*
* check_pathkeys can be passed FALSE if the function was already called for
* given index --- since the target should not change, we can skip the check
*************** AggPath *
*** 2977,2983 ****
create_partial_agg_sorted_path(PlannerInfo *root, Path *subpath,
bool check_pathkeys,
PartialAggInfo *agg_info,
! double input_rows)
{
RelOptInfo *rel;
AggClauseCosts agg_costs;
--- 2984,2991 ----
create_partial_agg_sorted_path(PlannerInfo *root, Path *subpath,
bool check_pathkeys,
PartialAggInfo *agg_info,
! double input_rows,
! bool parallel)
{
RelOptInfo *rel;
AggClauseCosts agg_costs;
*************** create_partial_agg_sorted_path(PlannerIn
*** 3047,3065 ****
Assert(agg_info->group_clauses != NIL);
result = create_agg_path(root, rel, subpath, rel->gpi->target,
AGG_SORTED, AGGSPLIT_INITIAL_SERIAL,
! agg_info->group_clauses, NIL, &agg_costs, dNumGroups);
return result;
}
/*
! * Apply 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,
! PartialAggInfo *agg_info, double input_rows)
{
RelOptInfo *rel;
bool can_hash;
--- 3055,3083 ----
Assert(agg_info->group_clauses != NIL);
result = create_agg_path(root, rel, subpath, rel->gpi->target,
AGG_SORTED, AGGSPLIT_INITIAL_SERIAL,
! agg_info->group_clauses, NIL, &agg_costs,
! dNumGroups);
!
! /*
! * Check if there's a chance to avoid final aggregation.
! */
! if (!parallel)
! make_uniquekeys(root, (Path *) result);
return result;
}
/*
! * Apply partial AGG_HASHED aggregation to subpath. ("partial" in the function
! * name refers to AGGSPLIT_INITIAL_SERIAL strategy, as opposed to parallel
! * processing.)
*
* Arguments have the same meaning as those of create_agg_sorted_path.
*/
AggPath *
create_partial_agg_hashed_path(PlannerInfo *root, Path *subpath,
! PartialAggInfo *agg_info, double input_rows,
! bool parallel)
{
RelOptInfo *rel;
bool can_hash;
*************** create_partial_agg_hashed_path(PlannerIn
*** 3123,3128 ****
--- 3141,3152 ----
}
}
+ /*
+ * Check if there's a chance to avoid final aggregation.
+ */
+ if (result != NULL && !parallel)
+ make_uniquekeys(root, (Path *) result);
+
return result;
}
*************** add_grouping_expressions_to_subpath(Plan
*** 4268,4270 ****
--- 4292,4881 ----
target,
true);
}
+
+ /*
+ * Find out if path produces an unique set of expressions and set uniquekeys
+ * accordingly.
+ *
+ * TODO Check if any expression of any unique key isn't nullable, whether in
+ * the table / index or by an outer join.
+ */
+ void
+ make_uniquekeys(PlannerInfo *root, Path *path)
+ {
+ RelOptInfo *rel;
+
+ /*
+ * In general, uniquekeys make no sense if the query has HAVING clause
+ * because it requires the aggregate finalization anyway.
+ *
+ * XXX If all aggregates, including those in the HAVING clause had no
+ * aggfinalfn, we could add the corresponding GroupedVars to the grouped
+ * target and construct the uniquekeys. Maybe even evaluate the HAVING
+ * quals at relation / join level?
+ */
+ if (root->parse->havingQual != NULL)
+ return;
+
+ /*
+ * The unique keys are not interesting if there's no chance to push
+ * aggregation down to base relations / joins.
+ */
+ if (root->grouped_var_list == NIL)
+ return;
+
+ /*
+ * Do not accept repeated calls of the function on the same path.
+ */
+ if (path->uniquekeys != NIL)
+ return;
+
+ rel = path->parent;
+
+ /*
+ * Base relations.
+ */
+ if (IsA(path, IndexPath) ||
+ (IsA(path, Path) &&path->pathtype == T_SeqScan) ||
+ IsA(path, AppendPath) ||IsA(path, MergeAppendPath))
+ {
+ ListCell *lc;
+
+ /*
+ * Derive grouping keys from unique indexes.
+ */
+ if (IsA(path, IndexPath) ||IsA(path, Path))
+ {
+ foreach(lc, rel->indexlist)
+ {
+ IndexOptInfo *index = lfirst_node(IndexOptInfo, lc);
+
+ make_uniquekeys_for_unique_index(rel->reltarget, index, path);
+ }
+ }
+ else if (IsA(path, AppendPath) ||IsA(path, MergeAppendPath))
+ make_uniquekeys_for_append_path(root, path);
+ #ifdef USE_ASSERT_CHECKING
+ else
+ Assert(false);
+ #endif
+ return;
+ }
+
+ if (IsA(path, AggPath))
+ {
+ /*
+ * The immediate output of aggregation essentially produces an unique
+ * set of grouping keys.
+ */
+ make_uniquekeys_for_agg_path(root, path);
+ }
+ else if (IS_JOIN_REL(path->parent))
+ {
+ JoinPath *jpath = (JoinPath *) path;
+ Path *outerpath = jpath->outerjoinpath;
+ Path *innerpath = jpath->innerjoinpath;
+ ListCell *l1;
+
+ /*
+ * Find out if the join produces unique keys for various combinations
+ * of input sets of unique keys.
+ *
+ * TODO Implement heuristic that picks a few most useful sets on each
+ * side, to avoid exponential growth of the uniquekeys list as we
+ * proceed from lower to higher joins. Maybe also discard the
+ * resulting sets containing unique expressions which are not grouping
+ * expressions (and of course which are not aggregates) of this join's
+ * target.
+ */
+ foreach(l1, outerpath->uniquekeys)
+ {
+ Bitmapset *outerset = (Bitmapset *) lfirst(l1);
+ ListCell *l2;
+
+ foreach(l2, innerpath->uniquekeys)
+ {
+ Bitmapset *innerset = (Bitmapset *) lfirst(l2);
+ Bitmapset *joinset;
+
+ /*
+ * Given that unique keys of each input relation are contained
+ * in that relation's target, the union of the key sets should
+ * be contained in the join target.
+ */
+ joinset = bms_union(outerset, innerset);
+
+ /* Add the set to the path. */
+ add_uniquekeys_to_path((Path *) jpath, joinset);
+ }
+ }
+ }
+ #ifdef USE_ASSERT_CHECKING
+
+ /*
+ * TODO Consider other ones, e.g. UniquePath.
+ */
+ else
+ Assert(false);
+ #endif
+ }
+
+ /*
+ * Create a set of positions of expressions in reltarget if the index is
+ * unique and if reltarget contains all the index columns. Add the set to
+ * uniquekeys if identical one is not already there.
+ */
+ static void
+ make_uniquekeys_for_unique_index(PathTarget *reltarget, IndexOptInfo *index,
+ Path *path)
+ {
+ int i;
+ Bitmapset *new_set = NULL;
+
+ /*
+ * Give up if the index does not guarantee uniqueness.
+ */
+ if (!index->unique || !index->immediate ||
+ (index->indpred != NIL && !index->predOK))
+ return;
+
+ /*
+ * For the index path to be acceptable, reltarget must contain all the
+ * index columns.
+ *
+ * reltarget is not supposed to contain non-var expressions, so the index
+ * should neither.
+ */
+ if (index->indexprs != NULL)
+ return;
+
+ for (i = 0; i < index->ncolumns; i++)
+ {
+ int indkey = index->indexkeys[i];
+ ListCell *lc;
+ bool found = false;
+ int j = 0;
+
+ foreach(lc, reltarget->exprs)
+ {
+ Var *var = lfirst_node(Var, lc);
+
+ if (var->varno == index->rel->relid && var->varattno == indkey)
+ {
+ new_set = bms_add_member(new_set, j);
+ found = true;
+ break;
+ }
+
+ j++;
+ }
+
+ /*
+ * If rel needs less than the whole index key then the values of the
+ * columns matched so far can be duplicate.
+ */
+ if (!found)
+ {
+ bms_free(new_set);
+ return;
+ }
+ }
+
+ /*
+ * Add the set to the path, unless it's already there.
+ */
+ add_uniquekeys_to_path(path, new_set);
+ }
+
+ /*
+ * Create uniquekeys for a path that has Aggrefs in its target.
+ * set.
+ *
+ * Besides AggPath, ForeignPath is a known use case for this function.
+ */
+ void
+ make_uniquekeys_for_agg_path(PlannerInfo *root, Path *path)
+ {
+ PathTarget *target;
+ ListCell *lc;
+ Bitmapset *keyset = NULL;
+ int i = 0;
+
+ target = path->pathtarget;
+ Assert(target->sortgrouprefs != NULL);
+
+ foreach(lc, target->exprs)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+
+ if (IsA(expr, GroupedVar))
+ {
+ GroupedVar *gvar = castNode(GroupedVar, expr);
+
+ if (!IsA(gvar->gvexpr, Aggref))
+ {
+ /*
+ * Generic grouping expression.
+ */
+ keyset = bms_add_member(keyset, i);
+ }
+ }
+ else
+ {
+ Index sortgroupref = target->sortgrouprefs[i];
+
+ Assert(IsA(expr, Var));
+
+ /*
+ * Ignore "extra grouping expressions" that might have been added
+ * by prepare_rel_for_grouping() --- these exist merely for join
+ * purposes and thus should not appear in the final targetlist.
+ */
+ if (sortgroupref > 0 && sortgroupref <= root->max_sortgroupref)
+ {
+ /*
+ * Plain Var grouping expression.
+ */
+ keyset = bms_add_member(keyset, i);
+ }
+ else
+ {
+ /*
+ * A column functionally dependent on the GROUP BY clause?
+ */
+ }
+ }
+
+ i++;
+ }
+
+ add_uniquekeys_to_path((Path *) path, keyset);
+ }
+
+ /*
+ * Create uniquekeys for a AppendPath or MergeAppendPath.
+ *
+ * Besides AggPath, ForeignPath is a known use case for this function.
+ */
+ static void
+ make_uniquekeys_for_append_path(PlannerInfo *root, Path *path)
+ {
+ RelOptInfo *rel = path->parent;
+ List *subpaths;
+ Path *subpath;
+ ListCell *l1;
+ bool first = true;
+ List *uniquekeys_common = NIL;
+
+ /*
+ * In addition to the requirement that all subpaths must have uniquekeys
+ * set, the table needs to be partitioned in a specific way. Reject
+ * non-partitioned tables immediately, the other check to follow.
+ */
+ if (!IS_PARTITIONED_REL(rel))
+ return;
+
+ if (IsA(path, AppendPath))
+ subpaths = ((AppendPath *) path)->subpaths;
+ else if (IsA(path, MergeAppendPath))
+ subpaths = ((MergeAppendPath *) path)->subpaths;
+ #ifdef USE_ASSERT_CHECKING
+ else
+ Assert(false);
+ #endif
+
+ /*
+ * Check if each subpath has uniquekeys.
+ */
+ foreach(l1, subpaths)
+ {
+ subpath = (Path *) lfirst(l1);
+
+ /*
+ * If any subpath does not have uniquekeys, the whole append path can
+ * have them neither.
+ */
+ if (subpath->uniquekeys == NIL)
+ return;
+ }
+
+ /*
+ * Choose groupkey sets that each subpath has.
+ */
+ foreach(l1, subpaths)
+ {
+ List *common_new = NIL;
+ ListCell *l2;
+
+ subpath = (Path *) lfirst(l1);
+
+ if (uniquekeys_common == NIL)
+ {
+ /* Get the initial list from the first subpath. */
+ uniquekeys_common = subpath->uniquekeys;
+ continue;
+ }
+
+ /*
+ * Remove items missing in the current subpath from uniquekeys_common.
+ */
+ foreach(l2, uniquekeys_common)
+ {
+ Bitmapset *set1 = (Bitmapset *) lfirst(l2);
+ ListCell *l3;
+
+ /*
+ * Does the current subpath contain this set?
+ */
+ foreach(l3, subpath->uniquekeys)
+ {
+ Bitmapset *set2 = (Bitmapset *) lfirst(l3);
+
+ if (bms_equal(set1, set2))
+ {
+ common_new = lappend(common_new, set1);
+ break;
+ }
+ }
+ }
+
+ /*
+ * If there are no sets in common, the next subpaths cannot help us.
+ */
+ if (common_new == NIL)
+ return;
+
+ /*
+ * Adopt the new, possibly reduced list.
+ */
+ uniquekeys_common = common_new;
+ }
+
+ foreach(l1, subpaths)
+ {
+ subpath = (Path *) lfirst(l1);
+
+ /*
+ * The following tests should ideally be performed either on
+ * rel->reltarget or rel->gpi->target outside the iteration of
+ * subpaths. However there's no easy way to find out if path is
+ * grouped or not. So we check the target of the first subpath and
+ * assume that the other ones would pass or fail in the same way:
+ * another subpath target should be the same path target whose
+ * attributes are translated to another child relations.
+ */
+ if (first)
+ {
+ List *partexprs = NIL;
+ List *partexprs_all = NIL;
+ ListCell *l2,
+ *l3;
+ AppendRelInfo **appinfos;
+ int i,
+ nappinfos;
+ bool found;
+ List *texprs = NIL;
+
+ /*
+ * Put all partition expressions into two lists --- one for
+ * non-nullable expressions, one for nullable.
+ */
+ for (i = 0; i < rel->part_scheme->partnatts; i++)
+ {
+ List *sublist;
+
+ sublist = rel->partexprs[i];
+ if (sublist != NIL)
+ {
+ partexprs = list_union(partexprs, sublist);
+ partexprs_all = list_union(partexprs_all,
+ sublist);
+ }
+
+ /*
+ * The nullable expressions should only appear in
+ * partexprs_all.
+ */
+ sublist = rel->nullable_partexprs[i];
+ if (sublist != NIL)
+ partexprs_all = list_union(partexprs_all,
+ sublist);
+ }
+ Assert(partexprs != NIL);
+
+ /*
+ * Translate the partitioning expressions so that they can match
+ * the subpath target.
+ */
+ appinfos =
+ find_appinfos_by_relids(root, subpath->parent->relids,
+ &nappinfos);
+ partexprs = (List *)
+ adjust_appendrel_attrs(root, (Node *) partexprs,
+ nappinfos, appinfos);
+ partexprs_all = (List *)
+ adjust_appendrel_attrs(root, (Node *) partexprs_all,
+ nappinfos, appinfos);
+ pfree(appinfos);
+
+ /*
+ * Since equivalence classes can be used to derive target
+ * expressions, the following checks have to consider ECs too. The
+ * targetlist we construct here contains the original expressions
+ * plus those generated from the appropriate ECs.
+ */
+ foreach(l2, subpath->pathtarget->exprs)
+ {
+ Expr *texpr = (Expr *) lfirst(l2);
+
+ if (IsA(texpr, GroupedVar))
+ {
+ GroupedVar *gvar = castNode(GroupedVar, texpr);
+
+ if (IsA(gvar->gvexpr, Aggref))
+ {
+ /*
+ * Aggregate should not appear in any EC.
+ */
+ texprs = lappend(texprs, texpr);
+ continue;
+ }
+
+ /*
+ * The contained expression (i.e. generic grouping
+ * expression) is what we'll search for in the ECs.
+ */
+ texpr = gvar->gvexpr;
+ }
+
+ /*
+ * Search for matching EC.
+ */
+ foreach(l3, root->group_pathkeys)
+ {
+ PathKey *pk = lfirst_node(PathKey, l3);
+ EquivalenceClass *ec = pk->pk_eclass;
+ ListCell *l4;
+ EquivalenceMember *em;
+
+ if (ec->ec_below_outer_join)
+ continue;
+
+ if (ec->ec_has_volatile)
+ continue;
+
+ foreach(l4, ec->ec_members)
+ {
+ em = lfirst_node(EquivalenceMember, l4);
+
+ if (em->em_nullable_relids)
+ continue;
+
+ if (!bms_is_subset(em->em_relids,
+ subpath->parent->relids))
+ continue;
+
+ if (equal(em->em_expr, texpr))
+ break;
+ }
+
+ /*
+ * texpr not found in the current EC, try the next one.
+ */
+ if (l4 == NULL)
+ continue;
+
+ /*
+ * The EC does match, so add all its members to texprs.
+ */
+ foreach(l4, ec->ec_members)
+ {
+ em = lfirst_node(EquivalenceMember, l4);
+ texprs = lappend(texprs, em->em_expr);
+ }
+
+ /*
+ * No expression is supposed to appear in multiple EC.
+ */
+ continue;
+ }
+ }
+
+ /*
+ * To ensure that no output row can be produced by multiple
+ * partitions, check that:
+ *
+ * (a) at least one output column is a partitioning expression.
+ * Thus no output row of this path can appear in multiple
+ * partitions.
+ *
+ * Only search in the non-nullable partitioning expressions
+ * because NULL value can be generated by any partition.
+ */
+ found = false;
+ foreach(l2, texprs)
+ {
+ Expr *texpr = (Expr *) lfirst(l2);
+
+ /*
+ * Try to find the matching partitioning expression.
+ */
+ foreach(l3, partexprs)
+ {
+ Expr *pexpr = lfirst(l3);
+
+ if (equal(texpr, pexpr))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (found)
+ break;
+ }
+ if (!found)
+ {
+ list_free(uniquekeys_common);
+ return;
+ }
+
+ /*
+ * (b) there's no partitioning expression not contained in the
+ * target. If there was some, then the partitioning expression
+ * found above could appear in multiple partitions.
+ *
+ * Even nullable partition key makes harm here, so consider all
+ * partition keys this time.
+ */
+ foreach(l2, partexprs_all)
+ {
+ Expr *pexpr = lfirst(l2);
+
+ found = false;
+ foreach(l3, texprs)
+ {
+ Expr *texpr = (Expr *) lfirst(l3);
+
+ if (equal(pexpr, texpr))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ list_free(uniquekeys_common);
+ return;
+ }
+ }
+ list_free(texprs);
+
+ /* Do not repeat the checks for the other subpaths. */
+ first = false;
+ }
+ }
+
+ /* All the checks passed. */
+ path->uniquekeys = uniquekeys_common;
+ }
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
new file mode 100644
index b9c2d13..3f25949
*** a/src/backend/optimizer/util/tlist.c
--- b/src/backend/optimizer/util/tlist.c
*************** replace_grouped_vars_with_aggrefs(Planne
*** 835,840 ****
--- 835,970 ----
return result;
}
+ static Node *
+ replace_aggref_with_groupedvar_mutator(Node *node, List *grouped_vars)
+ {
+ if (node == NULL)
+ return NULL;
+
+ /*
+ * No node should contain both AGGSPLIT_SIMPLE Aggref and GroupedVar.
+ * Caller should know when the replacement should be performed, otherwise
+ * he must wastes memory and time.
+ */
+ Assert(!IsA(node, GroupedVar));
+
+ if (IsA(node, Aggref))
+ {
+ Aggref *aggref = castNode(Aggref, node);
+ ListCell *l;
+
+ /*
+ * There's currently no usecase for other aggsplit. If there should be
+ * some, extra effort is needed to make the aggref match gvi->gvexpr.
+ */
+ Assert(aggref->aggsplit == AGGSPLIT_SIMPLE);
+
+ foreach(l, grouped_vars)
+ {
+ GroupedVarInfo *gvi = lfirst_node(GroupedVarInfo, l);
+
+ if (equal(gvi->gvexpr, aggref))
+ {
+ GroupedVar *gvar = makeNode(GroupedVar);
+
+ gvar = makeNode(GroupedVar);
+ gvar->gvexpr = gvi->gvexpr;
+ gvar->gvid = gvi->gvid;
+ gvar->sortgroupref = gvi->sortgroupref;
+
+ /*
+ * Arrange for the call of aggfinalfn or let the transient
+ * state appear on the output unprocessed.
+ */
+ if (OidIsValid(aggref->aggfinalfn))
+ {
+ FuncExpr *fexpr = makeNode(FuncExpr);
+
+ fexpr = makeNode(FuncExpr);
+ fexpr->funcid = aggref->aggfinalfn;
+ fexpr->funcresulttype = aggref->aggtype;
+ fexpr->funcretset = false;
+ fexpr->funcvariadic = aggref->aggvariadic;
+ fexpr->funcformat = COERCE_EXPLICIT_CALL;
+ fexpr->funccollid = aggref->aggcollid;
+ fexpr->inputcollid = aggref->inputcollid;
+ fexpr->args = list_make1(gvar);
+ fexpr->location = -1;
+ return (Node *) fexpr;
+ }
+ else
+ return (Node *) gvar;
+ }
+ }
+ /* Match should have been found. */
+ Assert(l == NULL);
+ }
+
+ return expression_tree_mutator(node,
+ replace_aggref_with_groupedvar_mutator,
+ (void *) grouped_vars);
+ }
+
+ /*
+ * Replace each Aggref (only AGGSPLIT_SIMPLE expected) in the expression with
+ * the matching GroupedVar.
+ *
+ * Do not use if node may contain GroupedVars!
+ */
+ Node *
+ replace_aggref_with_groupedvar(Node *node, List *grouped_vars)
+ {
+ return expression_tree_mutator(node,
+ replace_aggref_with_groupedvar_mutator,
+ grouped_vars);
+ }
+
+ static bool
+ contains_aggref_simple_walker(Node *node, void *context)
+ {
+ bool *result_p = (bool *) context;
+
+ if (node == NULL)
+ return false;
+
+ /*
+ * Not interested in Aggrefs wrapped in GroupedVar.
+ */
+ if (IsA(node, GroupedVar))
+ return false;
+
+ if (IsA(node, Aggref))
+ {
+ Aggref *aggref = castNode(Aggref, node);
+
+ *result_p = aggref->aggsplit == AGGSPLIT_SIMPLE;
+
+ /*
+ * Abort the tree traversal regardless *result_p. We assume that
+ * targetlist should never contain both AGGSPLIT_SIMPLE and any other
+ * value of aggsplit. Thus the search for AGGSPLIT_SIMPLE makes no
+ * more sense even if this aggregate has a different value.
+ */
+ return true;
+ }
+ return expression_tree_walker(node, contains_aggref_simple_walker,
+ context);
+ }
+
+ /*
+ * Find out if the node contains at least one Aggref having aggsplit equal to
+ * AGGSPLIT_SIMPLE.
+ */
+ bool
+ contains_aggref_simple(Node *node)
+ {
+ bool result = false;
+
+ contains_aggref_simple_walker(node, (void *) &result);
+
+ return result;
+ }
+
/*
* For each aggregate add GroupedVar to the grouped target.
*
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
new file mode 100644
index 7a98753..b5baa2c
*** a/src/backend/parser/parse_func.c
--- b/src/backend/parser/parse_func.c
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 99,104 ****
--- 99,105 ----
FuncDetailCode fdresult;
char aggkind = 0;
Oid aggcombinefn = InvalidOid;
+ Oid aggfinalfn = InvalidOid;
ParseCallbackState pcbstate;
/*
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 352,357 ****
--- 353,359 ----
classForm = (Form_pg_aggregate) GETSTRUCT(tup);
aggkind = classForm->aggkind;
aggcombinefn = classForm->aggcombinefn;
+ aggfinalfn = classForm->aggfinalfn;
catDirectArgs = classForm->aggnumdirectargs;
ReleaseSysCache(tup);
*************** ParseFuncOrColumn(ParseState *pstate, Li
*** 698,703 ****
--- 700,706 ----
aggref->aggvariadic = func_variadic;
aggref->aggkind = aggkind;
aggref->aggcombinefn = aggcombinefn;
+ aggref->aggfinalfn = aggfinalfn;
/* agglevelsup will be set by transformAggregateCall */
aggref->aggsplit = AGGSPLIT_SIMPLE; /* planner might change this */
aggref->location = location;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
new file mode 100644
index 97fd887..edc9ab1
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
*************** typedef struct Aggref
*** 297,302 ****
--- 297,303 ----
Oid inputcollid; /* OID of collation that function should use */
Oid aggtranstype; /* type Oid of aggregate's transition value */
Oid aggcombinefn; /* combine function (see pg_aggregate.h) */
+ Oid aggfinalfn; /* final function (see pg_aggregate.h) */
List *aggargtypes; /* type Oids of direct and aggregated args */
List *aggdirectargs; /* direct arguments, if an ordered-set agg */
List *args; /* aggregated arguments and sort expressions */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
new file mode 100644
index 00e73ff..5093809
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct GroupedPathInfo
*** 1089,1094 ****
--- 1089,1098 ----
*
* "pathkeys" is a List of PathKey nodes (see above), describing the sort
* ordering of the path's output rows.
+ *
+ * "uniquekeys" is a List of Bitmapset objects, each pointing at a set of
+ * expressions of "pathtarget" whose values within the path output are
+ * distinct.
*/
typedef struct Path
{
*************** typedef struct Path
*** 1112,1117 ****
--- 1116,1125 ----
List *pathkeys; /* sort ordering of path's output */
/* pathkeys is a List of PathKey nodes; see above */
+
+ List *uniquekeys; /* list of bitmapsets where each set contains
+ * positions of unique expressions within
+ * pathtarget. */
} Path;
/* Macro for extracting a path's parameterization relids; beware double eval */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
new file mode 100644
index bbcfc7f..6f3b78d
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern AggPath *create_partial_agg_sorte
*** 211,221 ****
Path *subpath,
bool check_pathkeys,
PartialAggInfo *agg_info,
! double input_rows);
extern AggPath *create_partial_agg_hashed_path(PlannerInfo *root,
Path *subpath,
PartialAggInfo *agg_info,
! double input_rows);
extern GroupingSetsPath *create_groupingsets_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
--- 211,223 ----
Path *subpath,
bool check_pathkeys,
PartialAggInfo *agg_info,
! double input_rows,
! bool parallel);
extern AggPath *create_partial_agg_hashed_path(PlannerInfo *root,
Path *subpath,
PartialAggInfo *agg_info,
! double input_rows,
! bool parallel);
extern GroupingSetsPath *create_groupingsets_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
*************** extern Path *reparameterize_path(Planner
*** 276,281 ****
--- 278,285 ----
double loop_count);
extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path,
RelOptInfo *child_rel);
+ extern void make_uniquekeys(PlannerInfo *root, Path *path);
+ extern void make_uniquekeys_for_agg_path(PlannerInfo *root, Path *path);
/*
* prototypes for relnode.c
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
new file mode 100644
index b44b8a8..61f3c69
*** a/src/include/optimizer/paths.h
--- b/src/include/optimizer/paths.h
*************** extern bool has_useful_pathkeys(PlannerI
*** 240,244 ****
extern PathKey *make_canonical_pathkey(PlannerInfo *root,
EquivalenceClass *eclass, Oid opfamily,
int strategy, bool nulls_first);
!
#endif /* PATHS_H */
--- 240,245 ----
extern PathKey *make_canonical_pathkey(PlannerInfo *root,
EquivalenceClass *eclass, Oid opfamily,
int strategy, bool nulls_first);
! extern void add_uniquekeys_to_path(Path *path, Bitmapset *new_set);
! extern bool match_path_to_group_pathkeys(PlannerInfo *root, Path *path);
#endif /* PATHS_H */
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
new file mode 100644
index dfc9352..d7d9ce4
*** a/src/include/optimizer/tlist.h
--- b/src/include/optimizer/tlist.h
*************** extern void split_pathtarget_at_srfs(Pla
*** 66,71 ****
--- 66,73 ----
/* TODO Find the best location (position and in some cases even file) for the
* following ones. */
extern List *replace_grouped_vars_with_aggrefs(PlannerInfo *root, List *src);
+ extern Node *replace_aggref_with_groupedvar(Node *node, List *grouped_vars);
+ extern bool contains_aggref_simple(Node *node);
extern void add_grouped_vars_to_target(PlannerInfo *root, PathTarget *target,
List *expressions);
extern GroupedVar *get_grouping_expression(PlannerInfo *root, Expr *expr);
diff --git a/src/test/regress/expected/agg_pushdown.out b/src/test/regress/expected/agg_pushdown.out
new file mode 100644
index cac70a0..f146994
*** a/src/test/regress/expected/agg_pushdown.out
--- b/src/test/regress/expected/agg_pushdown.out
*************** SELECT p.i, avg(c1.v) FROM agg_pushdown_
*** 30,44 ****
AS c1 ON c1.parent = p.i GROUP BY p.i;
QUERY PLAN
-------------------------------------------------------------------------------------
! Finalize HashAggregate
! Group Key: p.i
-> Nested Loop
-> Partial HashAggregate
Group Key: c1.parent
-> Seq Scan on agg_pushdown_child1 c1
-> Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
Index Cond: (i = c1.parent)
! (8 rows)
-- Scan index on agg_pushdown_child1(parent) column and partially aggregate
-- the result using AGG_SORTED strategy.
--- 30,43 ----
AS c1 ON c1.parent = p.i GROUP BY p.i;
QUERY PLAN
-------------------------------------------------------------------------------------
! Result
-> Nested Loop
-> Partial HashAggregate
Group Key: c1.parent
-> Seq Scan on agg_pushdown_child1 c1
-> Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
Index Cond: (i = c1.parent)
! (7 rows)
-- Scan index on agg_pushdown_child1(parent) column and partially aggregate
-- the result using AGG_SORTED strategy.
*************** SELECT p.i, avg(c1.v) FROM agg_pushdown_
*** 48,62 ****
AS c1 ON c1.parent = p.i GROUP BY p.i;
QUERY PLAN
---------------------------------------------------------------------------------------------
! Finalize GroupAggregate
! Group Key: p.i
-> Nested Loop
-> Partial GroupAggregate
Group Key: c1.parent
-> Index Scan using agg_pushdown_child1_parent_idx on agg_pushdown_child1 c1
-> Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
Index Cond: (i = c1.parent)
! (8 rows)
SET enable_seqscan TO on;
-- Perform nestloop join between agg_pushdown_child1 and agg_pushdown_child2
--- 47,60 ----
AS c1 ON c1.parent = p.i GROUP BY p.i;
QUERY PLAN
---------------------------------------------------------------------------------------------
! Result
-> Nested Loop
-> Partial GroupAggregate
Group Key: c1.parent
-> Index Scan using agg_pushdown_child1_parent_idx on agg_pushdown_child1 c1
-> Index Only Scan using agg_pushdown_parent_pkey on agg_pushdown_parent p
Index Cond: (i = c1.parent)
! (7 rows)
SET enable_seqscan TO on;
-- Perform nestloop join between agg_pushdown_child1 and agg_pushdown_child2
agg_pushdown_v5/10_fdw.diff
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
new file mode 100644
index 0690bd4..1e855d6
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
*************** PG_FUNCTION_INFO_V1(file_fdw_validator);
*** 117,126 ****
*/
static void fileGetForeignRelSize(PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid);
static void fileGetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid);
static ForeignScan *fileGetForeignPlan(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
--- 117,128 ----
*/
static void fileGetForeignRelSize(PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid,
! bool grouped);
static void fileGetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid,
! bool grouped);
static ForeignScan *fileGetForeignPlan(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
*************** get_file_fdw_attribute_options(Oid relid
*** 487,497 ****
static void
fileGetForeignRelSize(PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid)
{
FileFdwPlanState *fdw_private;
/*
* Fetch options. We only need filename (or program) at this point, but
* we might as well get everything and not need to re-fetch it later in
* planning.
--- 489,507 ----
static void
fileGetForeignRelSize(PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid,
! bool grouped)
{
FileFdwPlanState *fdw_private;
/*
+ * XXX Grouping at relation level is possible but this FDW does not
+ * implement it yet.
+ */
+ if (grouped)
+ return;
+
+ /*
* Fetch options. We only need filename (or program) at this point, but
* we might as well get everything and not need to re-fetch it later in
* planning.
*************** fileGetForeignRelSize(PlannerInfo *root,
*** 518,524 ****
static void
fileGetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid)
{
FileFdwPlanState *fdw_private = (FileFdwPlanState *) baserel->fdw_private;
Cost startup_cost;
--- 528,535 ----
static void
fileGetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid,
! bool grouped)
{
FileFdwPlanState *fdw_private = (FileFdwPlanState *) baserel->fdw_private;
Cost startup_cost;
*************** fileGetForeignPaths(PlannerInfo *root,
*** 526,531 ****
--- 537,549 ----
List *columns;
List *coptions = NIL;
+ /*
+ * XXX Grouping at relation level is possible but this FDW does not
+ * implement it yet.
+ */
+ if (grouped)
+ return;
+
/* Decide whether to selectively perform binary conversion */
if (check_selective_binary_conversion(baserel,
foreigntableid,
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
new file mode 100644
index 0876589..22cd89d
*** a/contrib/postgres_fdw/deparse.c
--- b/contrib/postgres_fdw/deparse.c
*************** typedef struct deparse_expr_cxt
*** 103,108 ****
--- 103,109 ----
* a base relation. */
StringInfo buf; /* output buffer to append to */
List **params_list; /* exprs that will become remote Params */
+ List *tlist;
} deparse_expr_cxt;
#define REL_ALIAS_PREFIX "r"
*************** static void deparseTargetList(StringInfo
*** 133,139 ****
bool qualify_col,
List **retrieved_attrs);
static void deparseExplicitTargetList(List *tlist, List **retrieved_attrs,
! deparse_expr_cxt *context);
static void deparseSubqueryTargetList(deparse_expr_cxt *context);
static void deparseReturningList(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel,
--- 134,140 ----
bool qualify_col,
List **retrieved_attrs);
static void deparseExplicitTargetList(List *tlist, List **retrieved_attrs,
! deparse_expr_cxt *context);
static void deparseSubqueryTargetList(deparse_expr_cxt *context);
static void deparseReturningList(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel,
*************** static void printRemoteParam(int paramin
*** 163,171 ****
static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod,
deparse_expr_cxt *context);
static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs,
! deparse_expr_cxt *context);
static void deparseLockingClause(deparse_expr_cxt *context);
! static void appendOrderByClause(List *pathkeys, deparse_expr_cxt *context);
static void appendConditions(List *exprs, deparse_expr_cxt *context);
static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root,
RelOptInfo *joinrel, bool use_alias, List **params_list);
--- 164,173 ----
static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod,
deparse_expr_cxt *context);
static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs,
! deparse_expr_cxt *context, bool grouped);
static void deparseLockingClause(deparse_expr_cxt *context);
! static void appendOrderByClause(deparse_expr_cxt *context, List *expressions,
! List *pathkeys);
static void appendConditions(List *exprs, deparse_expr_cxt *context);
static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root,
RelOptInfo *joinrel, bool use_alias, List **params_list);
*************** static void deparseAggref(Aggref *node,
*** 177,185 ****
static void appendGroupByClause(List *tlist, deparse_expr_cxt *context);
static void appendAggOrderBy(List *orderList, List *targetList,
deparse_expr_cxt *context);
! static void appendFunctionName(Oid funcid, deparse_expr_cxt *context);
static Node *deparseSortGroupClause(Index ref, List *tlist,
deparse_expr_cxt *context);
/*
* Helper functions
--- 179,188 ----
static void appendGroupByClause(List *tlist, deparse_expr_cxt *context);
static void appendAggOrderBy(List *orderList, List *targetList,
deparse_expr_cxt *context);
! static void appendFunctionName(Oid funcid, StringInfo buf, char *schemaname);
static Node *deparseSortGroupClause(Index ref, List *tlist,
deparse_expr_cxt *context);
+ static void deparseSortGroupExpr(Expr *expr, deparse_expr_cxt *context);
/*
* Helper functions
*************** foreign_expr_walker(Node *node,
*** 365,370 ****
--- 368,381 ----
}
}
break;
+ case T_GroupedVar:
+ {
+ GroupedVar *gvar = (GroupedVar *) node;
+
+ Assert(!IsA(gvar->gvexpr, Aggref));
+ return foreign_expr_walker((Node *) gvar->gvexpr, glob_cxt,
+ outer_cxt);
+ }
case T_Const:
{
Const *c = (Const *) node;
*************** foreign_expr_walker(Node *node,
*** 676,687 ****
Aggref *agg = (Aggref *) node;
ListCell *lc;
! /* Not safe to pushdown when not in grouping context */
! if (!IS_UPPER_REL(glob_cxt->foreignrel))
! return false;
!
! /* Only non-split aggregates are pushable. */
! if (agg->aggsplit != AGGSPLIT_SIMPLE)
return false;
/* As usual, it must be shippable. */
--- 687,701 ----
Aggref *agg = (Aggref *) node;
ListCell *lc;
! /*
! * Only non-split aggregates are pushable.
! *
! * XXX Like above, AGGSPLIT_SIMPLE shouldn't appear here if
! * the aggregation of the whole upper relation gets abandoned.
! * (Check all occurrences of AGGSPLIT_SIMPLE.)
! */
! if (agg->aggsplit != AGGSPLIT_SIMPLE &&
! agg->aggsplit != AGGSPLIT_INITIAL_SERIAL)
return false;
/* As usual, it must be shippable. */
*************** build_tlist_to_deparse(RelOptInfo *forei
*** 923,931 ****
*/
extern void
deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel,
! List *tlist, List *remote_conds, List *pathkeys,
! bool is_subquery, List **retrieved_attrs,
! List **params_list)
{
deparse_expr_cxt context;
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
--- 937,945 ----
*/
extern void
deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel,
! List *tlist, List *remote_conds, List *order_by_exprs,
! List *pathkeys, bool is_subquery, List **retrieved_attrs,
! List **params_list, bool grouped)
{
deparse_expr_cxt context;
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
*************** deparseSelectStmtForRel(StringInfo buf,
*** 943,951 ****
context.foreignrel = rel;
context.scanrel = IS_UPPER_REL(rel) ? fpinfo->outerrel : rel;
context.params_list = params_list;
/* Construct SELECT clause */
! deparseSelectSql(tlist, is_subquery, retrieved_attrs, &context);
/*
* For upper relations, the WHERE clause is built from the remote
--- 957,966 ----
context.foreignrel = rel;
context.scanrel = IS_UPPER_REL(rel) ? fpinfo->outerrel : rel;
context.params_list = params_list;
+ context.tlist = tlist;
/* Construct SELECT clause */
! deparseSelectSql(tlist, is_subquery, retrieved_attrs, &context, grouped);
/*
* For upper relations, the WHERE clause is built from the remote
*************** deparseSelectStmtForRel(StringInfo buf,
*** 965,977 ****
/* Construct FROM and WHERE clauses */
deparseFromExpr(quals, &context);
! if (IS_UPPER_REL(rel))
{
/* Append GROUP BY clause */
appendGroupByClause(tlist, &context);
! /* Append HAVING clause */
! if (remote_conds)
{
appendStringInfoString(buf, " HAVING ");
appendConditions(remote_conds, &context);
--- 980,1008 ----
/* Construct FROM and WHERE clauses */
deparseFromExpr(quals, &context);
! if (IS_UPPER_REL(rel) || grouped)
{
/* Append GROUP BY clause */
appendGroupByClause(tlist, &context);
! /*
! * Append HAVING clause.
! *
! * XXX As the "partition-wise join" patch will probably be committed
! * to PG core earlier than the "aggregate push-down" patch, we don't
! * add HAVING clause to query that represents remote simple relation
! * or join. The point is that the HAVING clause can't be evaluated
! * below the final aggregation node, and the final aggregation is
! * essentially local.
! *
! * To surpass this limitation, the FDW would have to know whether the
! * relation / join is a partition or the whole table. In case it's
! * partition, HAVING is only legal if the query does not use any other
! * partition. If we eventually can add the HAVING clause to scan /
! * join remote queries, we also have to change the API so that WHERE /
! * ON conditions are passed separate from the HAVING expression.
! */
! if (IS_UPPER_REL(rel) && remote_conds)
{
appendStringInfoString(buf, " HAVING ");
appendConditions(remote_conds, &context);
*************** deparseSelectStmtForRel(StringInfo buf,
*** 979,986 ****
}
/* Add ORDER BY clause if we found any useful pathkeys */
! if (pathkeys)
! appendOrderByClause(pathkeys, &context);
/* Add any necessary FOR UPDATE/SHARE. */
deparseLockingClause(&context);
--- 1010,1020 ----
}
/* Add ORDER BY clause if we found any useful pathkeys */
! if (order_by_exprs)
! {
! Assert(pathkeys);
! appendOrderByClause(&context, order_by_exprs, pathkeys);
! }
/* Add any necessary FOR UPDATE/SHARE. */
deparseLockingClause(&context);
*************** deparseSelectStmtForRel(StringInfo buf,
*** 1001,1007 ****
*/
static void
deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs,
! deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
RelOptInfo *foreignrel = context->foreignrel;
--- 1035,1041 ----
*/
static void
deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs,
! deparse_expr_cxt *context, bool grouped)
{
StringInfo buf = context->buf;
RelOptInfo *foreignrel = context->foreignrel;
*************** deparseSelectSql(List *tlist, bool is_su
*** 1030,1035 ****
--- 1064,1078 ----
*/
deparseExplicitTargetList(tlist, retrieved_attrs, context);
}
+ else if (grouped && IS_SIMPLE_REL(foreignrel) && foreignrel->gpi != NULL)
+ {
+ /*
+ * An explicit targetlist is also passed if aggregation should take
+ * place in the remote database.
+ */
+ deparseExplicitTargetList(fpinfo->grouped_tlist, retrieved_attrs,
+ context);
+ }
else
{
/*
*************** deparseRangeTblRef(StringInfo buf, Plann
*** 1504,1511 ****
/* Deparse the subquery representing the relation. */
appendStringInfoChar(buf, '(');
deparseSelectStmtForRel(buf, root, foreignrel, NIL,
! fpinfo->remote_conds, NIL, true,
! &retrieved_attrs, params_list);
appendStringInfoChar(buf, ')');
/* Append the relation alias. */
--- 1547,1554 ----
/* Deparse the subquery representing the relation. */
appendStringInfoChar(buf, '(');
deparseSelectStmtForRel(buf, root, foreignrel, NIL,
! fpinfo->remote_conds, NIL, NIL, true,
! &retrieved_attrs, params_list, false);
appendStringInfoChar(buf, ')');
/* Append the relation alias. */
*************** deparseExpr(Expr *node, deparse_expr_cxt
*** 2126,2131 ****
--- 2169,2177 ----
case T_Var:
deparseVar((Var *) node, context);
break;
+ case T_GroupedVar:
+ deparseExpr((Expr *) ((GroupedVar *) node)->gvexpr, context);
+ break;
case T_Const:
deparseConst((Const *) node, context, 0);
break;
*************** deparseFuncExpr(FuncExpr *node, deparse_
*** 2469,2475 ****
/*
* Normal function: display as proname(args).
*/
! appendFunctionName(node->funcid, context);
appendStringInfoChar(buf, '(');
/* ... and all the arguments */
--- 2515,2521 ----
/*
* Normal function: display as proname(args).
*/
! appendFunctionName(node->funcid, context->buf, NULL);
appendStringInfoChar(buf, '(');
/* ... and all the arguments */
*************** deparseAggref(Aggref *node, deparse_expr
*** 2747,2761 ****
{
StringInfo buf = context->buf;
bool use_variadic;
/* Only basic, non-split aggregation accepted. */
! Assert(node->aggsplit == AGGSPLIT_SIMPLE);
/* Check if need to print VARIADIC (cf. ruleutils.c) */
use_variadic = node->aggvariadic;
/* Find aggregate name from aggfnoid which is a pg_proc entry */
! appendFunctionName(node->aggfnoid, context);
appendStringInfoChar(buf, '(');
/* Add DISTINCT */
--- 2793,2876 ----
{
StringInfo buf = context->buf;
bool use_variadic;
+ ListCell *lc;
+ bool use_core_aggregate = true;
+ char *schemaname = NULL;
/* Only basic, non-split aggregation accepted. */
! Assert(node->aggsplit == AGGSPLIT_SIMPLE ||
! node->aggsplit == AGGSPLIT_INITIAL_SERIAL);
/* Check if need to print VARIADIC (cf. ruleutils.c) */
use_variadic = node->aggvariadic;
+ if (context->root->grouped_var_list != NIL)
+ {
+ /*
+ * The core aggregates (i.e. those in postgres catalog) do or do not
+ * perform finalization on both remote and local side. But sometimes
+ * we need the same aggregate to run w/o finalization on the remote
+ * node and with the finalization locally.
+ *
+ * Since the current Aggref is partial, it essentially returns the
+ * transient type. We need to find the original aggregate.
+ */
+ foreach(lc, context->root->grouped_var_list)
+ {
+ GroupedVarInfo *gvi;
+ Aggref *orig;
+
+ gvi = lfirst_node(GroupedVarInfo, lc);
+ /* GroupedVar currently represents only aggregates. */
+ orig = castNode(Aggref, gvi->gvexpr);
+
+ if (orig->aggfnoid == node->aggfnoid)
+ {
+ /*
+ * The core aggregate can only be useful if it does not
+ * perform finalization.
+ */
+ if (!OidIsValid(orig->aggfinalfn))
+ {
+ use_core_aggregate = true;
+
+ /*
+ * If there's no finalization, the aggregate should return
+ * value of the internal type.
+ */
+ Assert(orig->aggtype == orig->aggtranstype);
+ }
+ else
+ use_core_aggregate = false;
+
+ /*
+ * If there are multiple aggregates of the same aggfnoid in
+ * the query, they should all have the same values of the
+ * fields we're going to check. So just use the first one we
+ * find.
+ */
+ break;
+ }
+ }
+
+ /* Must have found the original aggregate in the list. */
+ Assert(lc != NULL);
+ }
+
+ /*
+ * TODO
+ *
+ * 1. Make the schema name configurable as FDW option.
+ *
+ * 2. Implement the partial aggregates. These don't fit into postgres_fdw
+ * because there's no reason to install postgres_fdw on the remote node.
+ * Separate extension (contrib module?) is probably needed.
+ */
+ if (!use_core_aggregate)
+ schemaname = "partial";
+
/* Find aggregate name from aggfnoid which is a pg_proc entry */
! appendFunctionName(node->aggfnoid, context->buf, schemaname);
appendStringInfoChar(buf, '(');
/* Add DISTINCT */
*************** deparseAggref(Aggref *node, deparse_expr
*** 2829,2834 ****
--- 2944,2965 ----
}
appendStringInfoChar(buf, ')');
+
+ /*
+ * If special (non-core) aggregate is needed on the remote node, it can
+ * return data type for which postgres does not have textual format. cast
+ * to bytea seems to be an universal solution in such a case.
+ *
+ * TODO
+ *
+ * Consider checking if aggserialfn exists --- aggtranstype is used now
+ * because the info is easier to access. Also consider a requirement that
+ * the "remote aggregates" must return bytea, unless they return a
+ * primitive type. Thus we wouldn't have to deal with the cast here at
+ * all.
+ */
+ if (schemaname != NULL && node->aggtranstype == INTERNALOID)
+ appendStringInfoString(buf, "::bytea");
}
/*
*************** appendGroupByClause(List *tlist, deparse
*** 2952,2997 ****
*/
Assert(!query->groupingSets);
! foreach(lc, query->groupClause)
{
! SortGroupClause *grp = (SortGroupClause *) lfirst(lc);
! if (!first)
! appendStringInfoString(buf, ", ");
! first = false;
! deparseSortGroupClause(grp->tleSortGroupRef, tlist, context);
}
}
/*
! * Deparse ORDER BY clause according to the given pathkeys for given base
! * relation. From given pathkeys expressions belonging entirely to the given
! * base relation are obtained and deparsed.
*/
static void
! appendOrderByClause(List *pathkeys, deparse_expr_cxt *context)
{
! ListCell *lcell;
int nestlevel;
char *delim = " ";
- RelOptInfo *baserel = context->scanrel;
StringInfo buf = context->buf;
/* Make sure any constants in the exprs are printed portably */
nestlevel = set_transmission_modes();
appendStringInfoString(buf, " ORDER BY");
! foreach(lcell, pathkeys)
{
! PathKey *pathkey = lfirst(lcell);
! Expr *em_expr;
!
! em_expr = find_em_expr_for_rel(pathkey->pk_eclass, baserel);
! Assert(em_expr != NULL);
appendStringInfoString(buf, delim);
! deparseExpr(em_expr, context);
if (pathkey->pk_strategy == BTLessStrategyNumber)
appendStringInfoString(buf, " ASC");
else
--- 3083,3142 ----
*/
Assert(!query->groupingSets);
! /*
! * Due to aggregation push-down (which includes join aggregation on the
! * remote server) we should expect multiple TLEs containing different
! * expressions but having the same sortgroupref. The typical case is that
! * an equality join clause receives the input value (var) from each side
! * of a join, and each of these vars is needed above the join for a
! * different reason. That's why we don't use query->groupClause here.
! *
! * TODO Consider using get_grouping_expressions().
! */
! foreach(lc, tlist)
{
! TargetEntry *te = lfirst_node(TargetEntry, lc);
! Index sgref = te->ressortgroupref;
! /* Check whether this expression is part of GROUP BY clause */
! if (sgref &&
! get_sortgroupref_clause_noerr(sgref, query->groupClause))
! {
! if (!first)
! appendStringInfoString(buf, ", ");
! first = false;
! deparseSortGroupExpr(te->expr, context);
! }
}
}
/*
! * Deparse ORDER BY clause.
! *
! * "expressions" and "strategies" are lists of the expressions and the
! * corresponding pathkeys respectively.
*/
static void
! appendOrderByClause(deparse_expr_cxt *context, List *expressions, List *pathkeys)
{
! ListCell *l1, *l2;
int nestlevel;
char *delim = " ";
StringInfo buf = context->buf;
/* Make sure any constants in the exprs are printed portably */
nestlevel = set_transmission_modes();
appendStringInfoString(buf, " ORDER BY");
! Assert(list_length(expressions) == list_length(pathkeys));
! forboth(l1, expressions, l2, pathkeys)
{
! Expr *expr = (Expr *) lfirst(l1);
! PathKey *pathkey = lfirst_node(PathKey, l2);
appendStringInfoString(buf, delim);
! deparseExpr(expr, context);
if (pathkey->pk_strategy == BTLessStrategyNumber)
appendStringInfoString(buf, " ASC");
else
*************** appendOrderByClause(List *pathkeys, depa
*** 3012,3020 ****
* Deparses function name from given function oid.
*/
static void
! appendFunctionName(Oid funcid, deparse_expr_cxt *context)
{
- StringInfo buf = context->buf;
HeapTuple proctup;
Form_pg_proc procform;
const char *proname;
--- 3157,3164 ----
* Deparses function name from given function oid.
*/
static void
! appendFunctionName(Oid funcid, StringInfo buf, char *schemaname)
{
HeapTuple proctup;
Form_pg_proc procform;
const char *proname;
*************** appendFunctionName(Oid funcid, deparse_e
*** 3025,3035 ****
procform = (Form_pg_proc) GETSTRUCT(proctup);
/* Print schema name only if it's not pg_catalog */
! if (procform->pronamespace != PG_CATALOG_NAMESPACE)
{
! const char *schemaname;
!
! schemaname = get_namespace_name(procform->pronamespace);
appendStringInfo(buf, "%s.", quote_identifier(schemaname));
}
--- 3169,3179 ----
procform = (Form_pg_proc) GETSTRUCT(proctup);
/* Print schema name only if it's not pg_catalog */
! if (procform->pronamespace != PG_CATALOG_NAMESPACE ||
! schemaname != NULL)
{
! if (schemaname == NULL)
! schemaname = get_namespace_name(procform->pronamespace);
appendStringInfo(buf, "%s.", quote_identifier(schemaname));
}
*************** appendFunctionName(Oid funcid, deparse_e
*** 3049,3061 ****
static Node *
deparseSortGroupClause(Index ref, List *tlist, deparse_expr_cxt *context)
{
- StringInfo buf = context->buf;
TargetEntry *tle;
Expr *expr;
tle = get_sortgroupref_tle(ref, tlist);
expr = tle->expr;
if (expr && IsA(expr, Const))
{
/*
--- 3193,3214 ----
static Node *
deparseSortGroupClause(Index ref, List *tlist, deparse_expr_cxt *context)
{
TargetEntry *tle;
Expr *expr;
tle = get_sortgroupref_tle(ref, tlist);
expr = tle->expr;
+ deparseSortGroupExpr(expr, context);
+
+ return (Node *) expr;
+ }
+
+ static void
+ deparseSortGroupExpr(Expr *expr, deparse_expr_cxt *context)
+ {
+ StringInfo buf = context->buf;
+
if (expr && IsA(expr, Const))
{
/*
*************** deparseSortGroupClause(Index ref, List *
*** 3074,3081 ****
deparseExpr(expr, context);
appendStringInfoChar(buf, ')');
}
-
- return (Node *) expr;
}
--- 3227,3232 ----
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
new file mode 100644
index 683d641..5549b07
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
*************** SELECT t1.a,t1.b FROM fprt1 t1, LATERAL
*** 7525,7527 ****
--- 7525,7650 ----
(4 rows)
RESET enable_partition_wise_join;
+ -- ===================================================================
+ -- test aggregation of base relation or join
+ -- ===================================================================
+ CREATE TABLE customers(id int primary key, name varchar);
+ INSERT INTO customers VALUES (0, 'me'), (1, 'you'), (2, 'he'), (3, 'she');
+ CREATE TABLE products(id int, price float4) PARTITION BY RANGE (id);
+ CREATE TABLE products_0 (LIKE products);
+ INSERT INTO products_0(id, price) SELECT g.i, trunc(100 * random()) /
+ 10 + 1 FROM generate_series(1, 1000) AS g(i);
+ CREATE TABLE products_1 (LIKE products);
+ INSERT INTO products_1(id, price) SELECT g.i, trunc(100 * random()) /
+ 10 + 1 FROM generate_series(1001, 2000) AS g(i);
+ CREATE TABLE orders(customer_id int, product_id int) PARTITION BY RANGE
+ (product_id);
+ CREATE TABLE orders_0 (LIKE orders);
+ INSERT INTO orders_0(customer_id, product_id) SELECT trunc(2 * random()),
+ trunc(1000 * random()) FROM generate_series(1, 5000);
+ CREATE TABLE orders_1 (LIKE orders);
+ INSERT INTO orders_1(customer_id, product_id)
+ SELECT trunc(2 * random()) + 2, trunc(1000 * random()) + 1001 FROM
+ generate_series(1, 5000);
+ CREATE FOREIGN TABLE f_orders_0 PARTITION OF orders FOR VALUES FROM (1) TO
+ (1001) SERVER loopback OPTIONS (table_name 'orders_0');
+ CREATE FOREIGN TABLE f_orders_1 PARTITION OF orders FOR VALUES FROM (1001) TO
+ (2001) SERVER loopback OPTIONS (table_name 'orders_1');
+ CREATE FOREIGN TABLE f_products_0 PARTITION OF products FOR VALUES FROM (1) TO
+ (1001) SERVER loopback OPTIONS (table_name 'products_0');
+ CREATE FOREIGN TABLE f_products_1 PARTITION OF products FOR VALUES FROM (1001)
+ TO (2001) SERVER loopback OPTIONS (table_name 'products_1');
+ ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'true');
+ ALTER SERVER loopback2 OPTIONS (ADD use_remote_estimate 'true');
+ ANALYZE;
+ SET enable_agg_pushdown TO on;
+ SET enable_partition_wise_join TO on;
+ -- Perform the aggregation on partitions of "orders" , join the result to the
+ -- "customers" local table and finalize the aggregation.
+ EXPLAIN (VERBOSE on, COSTS off)
+ SELECT c.name, count(*)
+ FROM customers AS c, orders AS o
+ WHERE c.id = o.customer_id
+ GROUP BY c.id;
+ QUERY PLAN
+ --------------------------------------------------------------------------------------------------------------------------------------------
+ Finalize GroupAggregate
+ Output: c.name, count(*), c.id
+ Group Key: c.id
+ -> Nested Loop
+ Output: c.id, c.name, (PARTIAL count(*))
+ Inner Unique: true
+ -> Merge Append
+ Sort Key: o.customer_id
+ -> Foreign Scan
+ Output: o.customer_id, (PARTIAL count(*))
+ Remote SQL: SELECT customer_id, count(*) FROM public.orders_0 GROUP BY customer_id ORDER BY customer_id ASC NULLS LAST
+ -> Foreign Scan
+ Output: o_1.customer_id, (PARTIAL count(*))
+ Remote SQL: SELECT customer_id, count(*) FROM public.orders_1 GROUP BY customer_id ORDER BY customer_id ASC NULLS LAST
+ -> Index Scan using customers_pkey on public.customers c
+ Output: c.id, c.name
+ Index Cond: (c.id = o.customer_id)
+ (17 rows)
+
+ -- Perform partition-wise join of "orders" and "products", aggregate the
+ -- result at partition level, append the outputs of partitions and join it to
+ -- the "customers" local table.
+ EXPLAIN (VERBOSE on, COSTS off)
+ SELECT p.id, c.name, sum(p.price)
+ FROM customers AS c, orders AS o, products AS p
+ WHERE c.id = o.customer_id AND p.id = o.product_id
+ GROUP BY p.id, c.id
+ ORDER BY 3;
+ QUERY PLAN
+ -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
+ Output: p.id, c.name, (PARTIAL sum(p.price)), c.id
+ Sort Key: (PARTIAL sum(p.price))
+ -> Result
+ Output: p.id, c.name, (PARTIAL sum(p.price)), c.id
+ -> Hash Join
+ Output: c.id, p.id, c.name, (PARTIAL sum(p.price))
+ Inner Unique: true
+ Hash Cond: (o.customer_id = c.id)
+ -> Append
+ -> Foreign Scan
+ Output: o.customer_id, p.id, (PARTIAL sum(p.price))
+ Relations: (public.f_orders_0 o) INNER JOIN (public.f_products_0 p)
+ Remote SQL: SELECT r5.customer_id, r8.id, sum(r8.price) FROM (public.orders_0 r5 INNER JOIN public.products_0 r8 ON (((r5.product_id = r8.id)))) GROUP BY r5.customer_id, r8.id
+ -> Foreign Scan
+ Output: o_1.customer_id, p_1.id, (PARTIAL sum(p_1.price))
+ Relations: (public.f_orders_1 o) INNER JOIN (public.f_products_1 p)
+ Remote SQL: SELECT r6.customer_id, r9.id, sum(r9.price) FROM (public.orders_1 r6 INNER JOIN public.products_1 r9 ON (((r6.product_id = r9.id)))) GROUP BY r6.customer_id, r9.id
+ -> Hash
+ Output: c.id, c.name
+ -> Seq Scan on public.customers c
+ Output: c.id, c.name
+ (22 rows)
+
+ -- The final aggregation is not necessary if the aggregated output of
+ -- partitions contains distinct set of grouping keys. Moreover, skip sort of
+ -- the result if the output of per-partition queries has the desired ordering
+ -- and so the sets can be merge-appended.
+ EXPLAIN (VERBOSE on, COSTS off)
+ SELECT p.id, sum(p.price)
+ FROM orders AS o, products AS p
+ WHERE p.id = o.product_id
+ GROUP BY p.id
+ ORDER BY 2 DESC;
+ QUERY PLAN
+ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Result
+ Output: p.id, (PARTIAL sum(p.price))
+ -> Merge Append
+ Sort Key: (PARTIAL sum(p.price)) DESC
+ -> Foreign Scan
+ Output: p.id, (PARTIAL sum(p.price))
+ Relations: (public.f_orders_0 o) INNER JOIN (public.f_products_0 p)
+ Remote SQL: SELECT r7.id, sum(r7.price) FROM (public.orders_0 r4 INNER JOIN public.products_0 r7 ON (((r4.product_id = r7.id)))) GROUP BY r7.id ORDER BY sum(r7.price) DESC NULLS FIRST
+ -> Foreign Scan
+ Output: p_1.id, (PARTIAL sum(p_1.price))
+ Relations: (public.f_orders_1 o) INNER JOIN (public.f_products_1 p)
+ Remote SQL: SELECT r8.id, sum(r8.price) FROM (public.orders_1 r5 INNER JOIN public.products_1 r8 ON (((r5.product_id = r8.id)))) GROUP BY r8.id ORDER BY sum(r8.price) DESC NULLS FIRST
+ (12 rows)
+
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
new file mode 100644
index 459c6b9..6085879
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
*************** PG_FUNCTION_INFO_V1(postgres_fdw_handler
*** 273,282 ****
*/
static void postgresGetForeignRelSize(PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid);
static void postgresGetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid);
static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
RelOptInfo *foreignrel,
Oid foreigntableid,
--- 273,283 ----
*/
static void postgresGetForeignRelSize(PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid, bool grouped);
static void postgresGetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid,
! bool grouped);
static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
RelOptInfo *foreignrel,
Oid foreigntableid,
*************** static void postgresGetForeignJoinPaths(
*** 341,347 ****
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra);
static bool postgresRecheckForeignScan(ForeignScanState *node,
TupleTableSlot *slot);
static void postgresGetForeignUpperPaths(PlannerInfo *root,
--- 342,349 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped);
static bool postgresRecheckForeignScan(ForeignScanState *node,
TupleTableSlot *slot);
static void postgresGetForeignUpperPaths(PlannerInfo *root,
*************** static void postgresGetForeignUpperPaths
*** 352,363 ****
/*
* Helper functions
*/
static void estimate_path_cost_size(PlannerInfo *root,
RelOptInfo *baserel,
List *join_conds,
List *pathkeys,
! double *p_rows, int *p_width,
! Cost *p_startup_cost, Cost *p_total_cost);
static void get_remote_estimate(const char *sql,
PGconn *conn,
double *rows,
--- 354,368 ----
/*
* Helper functions
*/
+ static void init_pgfdw_baserel_info(PlannerInfo *root, RelOptInfo *rel,
+ Oid foreigntableid, bool grouped);
static void estimate_path_cost_size(PlannerInfo *root,
RelOptInfo *baserel,
List *join_conds,
+ List *order_by_exprs,
List *pathkeys,
! EstimateInfo * estimates,
! bool grouped);
static void get_remote_estimate(const char *sql,
PGconn *conn,
double *rows,
*************** static HeapTuple make_tuple_from_result_
*** 404,416 ****
static void conversion_error_callback(void *arg);
static bool foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel,
JoinType jointype, RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinPathExtraData *extra);
static bool foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel);
static List *get_useful_pathkeys_for_relation(PlannerInfo *root,
! RelOptInfo *rel);
static List *get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel);
static void add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
! Path *epq_path);
static void add_foreign_grouping_paths(PlannerInfo *root,
RelOptInfo *input_rel,
RelOptInfo *grouped_rel);
--- 409,426 ----
static void conversion_error_callback(void *arg);
static bool foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel,
JoinType jointype, RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinPathExtraData *extra, bool grouped);
static bool foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel);
+ static List *create_remote_agg_tlist(PlannerInfo *root,
+ RelOptInfo *grouped_rel,
+ PathTarget *grouping_target);
static List *get_useful_pathkeys_for_relation(PlannerInfo *root,
! RelOptInfo *rel, bool grouped);
static List *get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel);
static void add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
! Path *epq_path, bool grouped);
! static List *get_order_by_expressions(Relids relids, List *pathkeys,
! List *fdw_scan_tlist);
static void add_foreign_grouping_paths(PlannerInfo *root,
RelOptInfo *input_rel,
RelOptInfo *grouped_rel);
*************** postgres_fdw_handler(PG_FUNCTION_ARGS)
*** 485,567 ****
static void
postgresGetForeignRelSize(PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid)
{
PgFdwRelationInfo *fpinfo;
! ListCell *lc;
! RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
! const char *namespace;
! const char *relname;
! const char *refname;
/*
* We use PgFdwRelationInfo to pass various information to subsequent
* functions.
*/
! fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
! baserel->fdw_private = (void *) fpinfo;
!
! /* Base foreign tables need to be pushed down always. */
! fpinfo->pushdown_safe = true;
!
! /* Look up foreign-table catalog info. */
! fpinfo->table = GetForeignTable(foreigntableid);
! fpinfo->server = GetForeignServer(fpinfo->table->serverid);
!
! /*
! * Extract user-settable option values. Note that per-table setting of
! * use_remote_estimate overrides per-server setting.
! */
! fpinfo->use_remote_estimate = false;
! fpinfo->fdw_startup_cost = DEFAULT_FDW_STARTUP_COST;
! fpinfo->fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST;
! fpinfo->shippable_extensions = NIL;
! fpinfo->fetch_size = 100;
!
! apply_server_options(fpinfo);
! apply_table_options(fpinfo);
!
! /*
! * If the table or the server is configured to use remote estimates,
! * identify which user to do remote access as during planning. This
! * should match what ExecCheckRTEPerms() does. If we fail due to lack of
! * permissions, the query would have failed at runtime anyway.
! */
! if (fpinfo->use_remote_estimate)
{
! Oid userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
!
! fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
}
- else
- fpinfo->user = NULL;
! /*
! * Identify which baserestrictinfo clauses can be sent to the remote
! * server and which can't.
! */
! classifyConditions(root, baserel, baserel->baserestrictinfo,
! &fpinfo->remote_conds, &fpinfo->local_conds);
! /*
! * Identify which attributes will need to be retrieved from the remote
! * server. These include all attrs needed for joins or final output, plus
! * all attrs used in the local_conds. (Note: if we end up using a
! * parameterized scan, it's possible that some of the join clauses will be
! * sent to the remote and thus we wouldn't really need to retrieve the
! * columns used in them. Doesn't seem worth detecting that case though.)
! */
! fpinfo->attrs_used = NULL;
! pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid,
! &fpinfo->attrs_used);
! foreach(lc, fpinfo->local_conds)
{
! RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
! pull_varattnos((Node *) rinfo->clause, baserel->relid,
! &fpinfo->attrs_used);
}
/*
* Compute the selectivity and cost of the local_conds, so we don't have
* to do it over again for each path. The best we can do for these
--- 495,545 ----
static void
postgresGetForeignRelSize(PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid,
! bool grouped)
{
PgFdwRelationInfo *fpinfo;
! EstimateInfo *estimates;
/*
* We use PgFdwRelationInfo to pass various information to subsequent
* functions.
+ *
+ * This function can be called twice: first time for plain scan, second
+ * time for the grouped one. fpinfo should only be initialized once.
*/
! if (baserel->fdw_private == NULL)
{
! fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
! baserel->fdw_private = fpinfo;
! init_pgfdw_baserel_info(root, baserel, foreigntableid, grouped);
}
! fpinfo = (PgFdwRelationInfo *) baserel->fdw_private;
! if (grouped)
{
! /*
! * baserestrictinfo must be evaluated below the aggregation, so all
! * its expressions must be remote.
! */
! if (fpinfo->local_conds != NIL)
! return;
! /*
! * Given that there are no local_conds and remote_conds are evaluated
! * on the foreign server, baserel->gpi->target is the only thing left
! * to check.
! *
! * XXX Do we need to verify that the target contains no
! * PlaceHolderVars?
! */
! if (!foreign_grouping_ok(root, baserel))
! return;
}
+ estimates = !grouped ? &fpinfo->est_plain : &fpinfo->est_grouped;
+
/*
* Compute the selectivity and cost of the local_conds, so we don't have
* to do it over again for each path. The best we can do for these
*************** postgresGetForeignRelSize(PlannerInfo *r
*** 580,587 ****
* when they are set to some sensible costs during one (usually the first)
* of the calls to estimate_path_cost_size().
*/
! fpinfo->rel_startup_cost = -1;
! fpinfo->rel_total_cost = -1;
/*
* If the table or the server is configured to use remote estimates,
--- 558,565 ----
* when they are set to some sensible costs during one (usually the first)
* of the calls to estimate_path_cost_size().
*/
! estimates->rel_startup_cost = -1;
! estimates->rel_total_cost = -1;
/*
* If the table or the server is configured to use remote estimates,
*************** postgresGetForeignRelSize(PlannerInfo *r
*** 597,609 ****
* values in fpinfo so we don't need to do it again to generate the
* basic foreign path.
*/
! estimate_path_cost_size(root, baserel, NIL, NIL,
! &fpinfo->rows, &fpinfo->width,
! &fpinfo->startup_cost, &fpinfo->total_cost);
/* Report estimated baserel size to planner. */
! baserel->rows = fpinfo->rows;
! baserel->reltarget->width = fpinfo->width;
}
else
{
--- 575,591 ----
* values in fpinfo so we don't need to do it again to generate the
* basic foreign path.
*/
! estimate_path_cost_size(root, baserel, NIL, NIL, NIL, estimates,
! grouped);
/* Report estimated baserel size to planner. */
!
! /*
! * TODO If grouped, make sure baserel->gpi exists and store the values
! * there.
! */
! baserel->rows = estimates->rows;
! baserel->reltarget->width = estimates->width;
}
else
{
*************** postgresGetForeignRelSize(PlannerInfo *r
*** 628,661 ****
set_baserel_size_estimates(root, baserel);
/* Fill in basically-bogus cost estimates for use later. */
! estimate_path_cost_size(root, baserel, NIL, NIL,
! &fpinfo->rows, &fpinfo->width,
! &fpinfo->startup_cost, &fpinfo->total_cost);
}
-
- /*
- * Set the name of relation in fpinfo, while we are constructing it here.
- * It will be used to build the string describing the join relation in
- * EXPLAIN output. We can't know whether VERBOSE option is specified or
- * not, so always schema-qualify the foreign table name.
- */
- fpinfo->relation_name = makeStringInfo();
- namespace = get_namespace_name(get_rel_namespace(foreigntableid));
- relname = get_rel_name(foreigntableid);
- refname = rte->eref->aliasname;
- appendStringInfo(fpinfo->relation_name, "%s.%s",
- quote_identifier(namespace),
- quote_identifier(relname));
- if (*refname && strcmp(refname, relname) != 0)
- appendStringInfo(fpinfo->relation_name, " %s",
- quote_identifier(rte->eref->aliasname));
-
- /* No outer and inner relations. */
- fpinfo->make_outerrel_subquery = false;
- fpinfo->make_innerrel_subquery = false;
- fpinfo->lower_subquery_rels = NULL;
- /* Set the relation index. */
- fpinfo->relation_index = baserel->relid;
}
/*
--- 610,618 ----
set_baserel_size_estimates(root, baserel);
/* Fill in basically-bogus cost estimates for use later. */
! estimate_path_cost_size(root, baserel, NIL, NIL, NIL, estimates,
! grouped);
}
}
/*
*************** get_useful_ecs_for_relation(PlannerInfo
*** 775,781 ****
* to figure out which pathkeys to consider.
*/
static List *
! get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel)
{
List *useful_pathkeys_list = NIL;
List *useful_eclass_list;
--- 732,739 ----
* to figure out which pathkeys to consider.
*/
static List *
! get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel,
! bool grouped)
{
List *useful_pathkeys_list = NIL;
List *useful_eclass_list;
*************** get_useful_pathkeys_for_relation(Planner
*** 790,801 ****
if (root->query_pathkeys)
{
bool query_pathkeys_ok = true;
foreach(lc, root->query_pathkeys)
{
! PathKey *pathkey = (PathKey *) lfirst(lc);
! EquivalenceClass *pathkey_ec = pathkey->pk_eclass;
! Expr *em_expr;
/*
* The planner and executor don't have any clever strategy for
--- 748,762 ----
if (root->query_pathkeys)
{
bool query_pathkeys_ok = true;
+ bool sort_pathkeys_ok = true;
+ PathKey *pathkey;
+ EquivalenceClass *pathkey_ec;
+ Expr *em_expr;
foreach(lc, root->query_pathkeys)
{
! pathkey = lfirst_node(PathKey, lc);
! pathkey_ec = pathkey->pk_eclass;
/*
* The planner and executor don't have any clever strategy for
*************** get_useful_pathkeys_for_relation(Planner
*** 815,823 ****
break;
}
}
-
if (query_pathkeys_ok)
useful_pathkeys_list = list_make1(list_copy(root->query_pathkeys));
}
/*
--- 776,807 ----
break;
}
}
if (query_pathkeys_ok)
useful_pathkeys_list = list_make1(list_copy(root->query_pathkeys));
+
+ /*
+ * If the path is grouped, then it can be sorted by aggregates. These
+ * are only present in sort_pathkeys.
+ */
+ if (grouped && root->sort_pathkeys != root->query_pathkeys)
+ {
+ foreach(lc, root->sort_pathkeys)
+ {
+ pathkey = lfirst_node(PathKey, lc);
+ pathkey_ec = pathkey->pk_eclass;
+
+ if (pathkey_ec->ec_has_volatile ||
+ !(em_expr = find_em_expr_for_rel(pathkey_ec, rel)) ||
+ !is_foreign_expr(root, rel, em_expr))
+ {
+ sort_pathkeys_ok = false;
+ break;
+ }
+ }
+ if (sort_pathkeys_ok)
+ useful_pathkeys_list = lappend(useful_pathkeys_list,
+ list_copy(root->sort_pathkeys));
+ }
}
/*
*************** get_useful_pathkeys_for_relation(Planner
*** 884,895 ****
static void
postgresGetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid)
{
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) baserel->fdw_private;
ForeignPath *path;
List *ppi_list;
ListCell *lc;
/*
* Create simplest ForeignScan path node and add it to baserel. This path
--- 868,966 ----
static void
postgresGetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid,
! bool grouped)
{
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) baserel->fdw_private;
ForeignPath *path;
+ PathTarget *target = NULL;
List *ppi_list;
ListCell *lc;
+ List *fdw_private;
+ EstimateInfo *estimates;
+
+ if (grouped)
+ {
+ /*
+ * The target must contain aggregates.
+ */
+ Assert(baserel->gpi != NULL);
+ target = baserel->gpi->target;
+
+ /*
+ * See postgresGetForeignRelSize().
+ */
+ if (fpinfo->local_conds != NIL)
+ return;
+
+ /*
+ * remote_conds will become fdw_recheck_quals for base relation, see
+ * postgresGetForeignPlan(). Check if the grouping target contains all
+ * vars necessary to evaluate the recheck quals.
+ */
+ if (fpinfo->remote_conds != NIL && IS_SIMPLE_REL(baserel))
+ {
+ foreach(lc, fpinfo->remote_conds)
+ {
+ RestrictInfo *ri = lfirst_node(RestrictInfo, lc);
+ Expr *clause = ri->clause;
+ List *vars = pull_var_clause((Node *) clause, 0);
+ ListCell *lc2;
+
+ /*
+ * Each var must exist in the grouping target.
+ */
+ foreach(lc2, vars)
+ {
+ ListCell *lc3;
+ bool found = false;
+ Var *var = lfirst_node(Var, lc2);
+
+ foreach(lc3, target->exprs)
+ {
+ Expr *gexpr = (Expr *) lfirst(lc3);
+
+ if (IsA(gexpr, Var))
+ {
+ Var *gvar = castNode(Var, gexpr);
+
+ if (gvar->varno == var->varno &&
+ gvar->varattno == var->varattno &&
+ gvar->varlevelsup == var->varlevelsup)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found)
+ break;
+ }
+ list_free(vars);
+
+ /*
+ * Haven't checked all vars with success?
+ */
+ if (lc2 != NULL)
+ return;
+ }
+ return;
+ }
+
+ /*
+ * Wasn't it possible to construct remote query?
+ */
+ if (fpinfo->grouped_tlist == NIL)
+ return;
+ }
+
+ estimates = !grouped ? &fpinfo->est_plain : &fpinfo->est_grouped;
+
+ /*
+ * Add information to the paths whether they are grouped or not. The 2nd
+ * element tells there's no ORDER BY clause.
+ */
+ fdw_private = list_make2(makeInteger(grouped ? 1 : 0), NIL);
/*
* Create simplest ForeignScan path node and add it to baserel. This path
*************** postgresGetForeignPaths(PlannerInfo *roo
*** 899,916 ****
* to estimate cost and size of this path.
*/
path = create_foreignscan_path(root, baserel,
! NULL, /* default pathtarget */
! fpinfo->rows,
! fpinfo->startup_cost,
! fpinfo->total_cost,
NIL, /* no pathkeys */
NULL, /* no outer rel either */
NULL, /* no extra plan */
! NIL); /* no fdw_private list */
! add_path(baserel, (Path *) path, false);
/* Add paths with pathkeys */
! add_paths_with_pathkeys_for_rel(root, baserel, NULL);
/*
* If we're not using remote estimates, stop here. We have no way to
--- 970,995 ----
* to estimate cost and size of this path.
*/
path = create_foreignscan_path(root, baserel,
! target,
! estimates->rows,
! estimates->startup_cost,
! estimates->total_cost,
NIL, /* no pathkeys */
NULL, /* no outer rel either */
NULL, /* no extra plan */
! fdw_private);
!
! /*
! * Grouped path essentially produces an unique set of grouping
! * keys.
! */
! if (grouped)
! make_uniquekeys_for_agg_path(root, (Path *) path);
!
! add_path(baserel, (Path *) path, grouped);
/* Add paths with pathkeys */
! add_paths_with_pathkeys_for_rel(root, baserel, NULL, grouped);
/*
* If we're not using remote estimates, stop here. We have no way to
*************** postgresGetForeignPaths(PlannerInfo *roo
*** 921,926 ****
--- 1000,1013 ----
return;
/*
+ * Grouped parameterized path would lead to repeated aggregation, which is
+ * not too interesting. Since the rest deals only with parameterized
+ * paths, return now.
+ */
+ if (grouped)
+ return;
+
+ /*
* Thumb through all join clauses for the rel to identify which outer
* relations could supply one or more safe-to-send-to-remote join clauses.
* We'll build a parameterized path for each such outer relation.
*************** postgresGetForeignPaths(PlannerInfo *roo
*** 1052,1085 ****
foreach(lc, ppi_list)
{
ParamPathInfo *param_info = (ParamPathInfo *) lfirst(lc);
! double rows;
! int width;
! Cost startup_cost;
! Cost total_cost;
/* Get a cost estimate from the remote */
estimate_path_cost_size(root, baserel,
! param_info->ppi_clauses, NIL,
! &rows, &width,
! &startup_cost, &total_cost);
/*
* ppi_rows currently won't get looked at by anything, but still we
* may as well ensure that it matches our idea of the rowcount.
*/
! param_info->ppi_rows = rows;
/* Make the path */
path = create_foreignscan_path(root, baserel,
NULL, /* default pathtarget */
! rows,
! startup_cost,
! total_cost,
NIL, /* no pathkeys */
param_info->ppi_req_outer,
NULL,
! NIL); /* no fdw_private list */
! add_path(baserel, (Path *) path, false);
}
}
--- 1139,1170 ----
foreach(lc, ppi_list)
{
ParamPathInfo *param_info = (ParamPathInfo *) lfirst(lc);
! EstimateInfo est_param;
/* Get a cost estimate from the remote */
+ memset(&est_param, 0, sizeof(EstimateInfo));
estimate_path_cost_size(root, baserel,
! param_info->ppi_clauses, NIL, NIL,
! &est_param, grouped);
/*
* ppi_rows currently won't get looked at by anything, but still we
* may as well ensure that it matches our idea of the rowcount.
*/
! param_info->ppi_rows = est_param.rows;
/* Make the path */
path = create_foreignscan_path(root, baserel,
NULL, /* default pathtarget */
! est_param.rows,
! est_param.startup_cost,
! est_param.total_cost,
NIL, /* no pathkeys */
param_info->ppi_req_outer,
NULL,
! fdw_private);
!
! add_path(baserel, (Path *) path, grouped);
}
}
*************** postgresGetForeignPlan(PlannerInfo *root
*** 1107,1119 ****
List *retrieved_attrs;
StringInfoData sql;
ListCell *lc;
if (IS_SIMPLE_REL(foreignrel))
{
! /*
! * For base relations, set scan_relid as the relid of the relation.
! */
! scan_relid = foreignrel->relid;
/*
* In a base-relation scan, we must apply the given scan_clauses.
--- 1192,1235 ----
List *retrieved_attrs;
StringInfoData sql;
ListCell *lc;
+ Value *grouped_val;
+ List *order_by_exprs;
+ bool grouped;
+
+ /*
+ * Retrieve the useful information from fdw_private.
+ */
+ Assert(list_length(best_path->fdw_private) == 2);
+ grouped_val = (Value *) linitial(best_path->fdw_private);
+ Assert(IsA(grouped_val, Integer));
+ grouped = intVal(grouped_val) == 1;
+ order_by_exprs = (List *) lsecond(best_path->fdw_private);
if (IS_SIMPLE_REL(foreignrel))
{
! if (!grouped)
! {
! /*
! * For base relations, set scan_relid as the relid of the
! * relation.
! */
! scan_relid = foreignrel->relid;
! }
! else
! {
! /*
! * XXX Something seems to be inconsistent here: deparseSelectSql()
! * would create the targetlist from foreignrel->gpi->target, but
! * not appendGroupByClause(). Try to refactor.
! */
! fdw_scan_tlist = fpinfo->grouped_tlist;
!
! /*
! * The scan tuple descriptor should be constructed from
! * fdw_scan_tlist rather than from that of the remote relation.
! */
! scan_relid = 0;
! }
/*
* In a base-relation scan, we must apply the given scan_clauses.
*************** postgresGetForeignPlan(PlannerInfo *root
*** 1160,1165 ****
--- 1276,1283 ----
}
else
{
+ List *remote_src, *local_src;
+
/*
* Join relation or upper relation - set scan_relid to 0.
*/
*************** postgresGetForeignPlan(PlannerInfo *root
*** 1176,1183 ****
* Instead we get the conditions to apply from the fdw_private
* structure.
*/
! remote_exprs = extract_actual_clauses(fpinfo->remote_conds, false);
! local_exprs = extract_actual_clauses(fpinfo->local_conds, false);
/*
* We leave fdw_recheck_quals empty in this case, since we never need
--- 1294,1313 ----
* Instead we get the conditions to apply from the fdw_private
* structure.
*/
! if (IS_UPPER_REL(foreignrel) &&
! (fpinfo->remote_conds_upper || fpinfo->local_conds_upper))
! {
! remote_src = fpinfo->remote_conds_upper;
! local_src = fpinfo->local_conds_upper;
! }
! else
! {
! remote_src = fpinfo->remote_conds;
! local_src = fpinfo->local_conds;
! }
!
! remote_exprs = extract_actual_clauses(remote_src, false);
! local_exprs = extract_actual_clauses(local_src, false);
/*
* We leave fdw_recheck_quals empty in this case, since we never need
*************** postgresGetForeignPlan(PlannerInfo *root
*** 1191,1197 ****
*/
/* Build the list of columns to be fetched from the foreign server. */
! fdw_scan_tlist = build_tlist_to_deparse(foreignrel);
/*
* Ensure that the outer plan produces a tuple whose descriptor
--- 1321,1330 ----
*/
/* Build the list of columns to be fetched from the foreign server. */
! if (!grouped)
! fdw_scan_tlist = build_tlist_to_deparse(foreignrel);
! else
! fdw_scan_tlist = fpinfo->grouped_tlist;
/*
* Ensure that the outer plan produces a tuple whose descriptor
*************** postgresGetForeignPlan(PlannerInfo *root
*** 1238,1245 ****
*/
initStringInfo(&sql);
deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist,
! remote_exprs, best_path->path.pathkeys,
! false, &retrieved_attrs, ¶ms_list);
/* Remember remote_exprs for possible use by postgresPlanDirectModify */
fpinfo->final_remote_exprs = remote_exprs;
--- 1371,1380 ----
*/
initStringInfo(&sql);
deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist,
! remote_exprs,
! order_by_exprs, best_path->path.pathkeys,
! false, &retrieved_attrs, ¶ms_list,
! grouped);
/* Remember remote_exprs for possible use by postgresPlanDirectModify */
fpinfo->final_remote_exprs = remote_exprs;
*************** postgresExplainDirectModify(ForeignScanS
*** 2481,2486 ****
--- 2616,2739 ----
}
}
+ /*
+ * init_pgfdw_rel_info
+ * Initialize PgFdwRelationInfo and assign it to the base relation. That
+ * includes estimates, as well as separation of local expressions from
+ * remote ones.
+ */
+ static void
+ init_pgfdw_baserel_info(PlannerInfo *root, RelOptInfo *rel,
+ Oid foreigntableid, bool grouped)
+
+ {
+ const char *namespace;
+ const char *relname;
+ const char *refname;
+ PgFdwRelationInfo *fpinfo;
+ ListCell *lc;
+ RangeTblEntry *rte;
+
+ rte = planner_rt_fetch(rel->relid, root);
+ fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
+
+ /* Base foreign tables need to be pushed down always. */
+ fpinfo->pushdown_safe = true;
+
+ /* Look up foreign-table catalog info. */
+ fpinfo->table = GetForeignTable(foreigntableid);
+ fpinfo->server = GetForeignServer(fpinfo->table->serverid);
+
+ /*
+ * Extract user-settable option values. Note that per-table setting of
+ * use_remote_estimate overrides per-server setting.
+ */
+ fpinfo->use_remote_estimate = false;
+ fpinfo->fdw_startup_cost = DEFAULT_FDW_STARTUP_COST;
+ fpinfo->fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST;
+ fpinfo->shippable_extensions = NIL;
+ fpinfo->fetch_size = 100;
+
+ apply_server_options(fpinfo);
+ apply_table_options(fpinfo);
+
+ /*
+ * If the table or the server is configured to use remote estimates,
+ * identify which user to do remote access as during planning. This
+ * should match what ExecCheckRTEPerms() does. If we fail due to lack of
+ * permissions, the query would have failed at runtime anyway.
+ */
+ if (fpinfo->use_remote_estimate)
+ {
+ Oid userid = rte->checkAsUser ? rte->checkAsUser :
+ GetUserId();
+
+ fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
+ }
+ else
+ fpinfo->user = NULL;
+
+ /*
+ * Identify which baserestrictinfo clauses can be sent to the remote
+ * server and which can't.
+ */
+ classifyConditions(root, rel, rel->baserestrictinfo,
+ &fpinfo->remote_conds, &fpinfo->local_conds);
+
+ /*
+ * Identify which attributes will need to be retrieved from the remote
+ * server. These include all attrs needed for joins or final output, plus
+ * all attrs used in the local_conds. (Note: if we end up using a
+ * parameterized scan, it's possible that some of the join clauses will be
+ * sent to the remote and thus we wouldn't really need to retrieve the
+ * columns used in them. Doesn't seem worth detecting that case though.)
+ */
+ fpinfo->attrs_used = NULL;
+ if (!grouped)
+ {
+ pull_varattnos((Node *) rel->reltarget->exprs, rel->relid,
+ &fpinfo->attrs_used);
+ foreach(lc, fpinfo->local_conds)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
+
+ pull_varattnos((Node *) rinfo->clause, rel->relid,
+ &fpinfo->attrs_used);
+ }
+ }
+ else
+ {
+ /*
+ * deparseExplicitTargetList() is used for the grouped relation, thus
+ * no need to retrieve attrs_used.
+ */
+ }
+
+ /*
+ * Set the name of relation in fpinfo, while we are constructing it here.
+ * It will be used to build the string describing the join relation in
+ * EXPLAIN output. We can't know whether VERBOSE option is specified or
+ * not, so always schema-qualify the foreign table name.
+ */
+ fpinfo->relation_name = makeStringInfo();
+ namespace = get_namespace_name(get_rel_namespace(foreigntableid));
+ relname = get_rel_name(foreigntableid);
+ refname = rte->eref->aliasname;
+ appendStringInfo(fpinfo->relation_name, "%s.%s",
+ quote_identifier(namespace),
+ quote_identifier(relname));
+ if (*refname && strcmp(refname, relname) != 0)
+ appendStringInfo(fpinfo->relation_name, " %s",
+ quote_identifier(rte->eref->aliasname));
+
+ /* No outer and inner relations. */
+ fpinfo->make_outerrel_subquery = false;
+ fpinfo->make_innerrel_subquery = false;
+ fpinfo->lower_subquery_rels = NULL;
+ /* Set the relation index. */
+ fpinfo->relation_index = rel->relid;
+ }
+
/*
* estimate_path_cost_size
*************** static void
*** 2498,2506 ****
estimate_path_cost_size(PlannerInfo *root,
RelOptInfo *foreignrel,
List *param_join_conds,
List *pathkeys,
! double *p_rows, int *p_width,
! Cost *p_startup_cost, Cost *p_total_cost)
{
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
double rows;
--- 2751,2760 ----
estimate_path_cost_size(PlannerInfo *root,
RelOptInfo *foreignrel,
List *param_join_conds,
+ List *order_by_exprs,
List *pathkeys,
! EstimateInfo * estimates,
! bool grouped)
{
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
double rows;
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2539,2546 ****
&remote_param_join_conds, &local_param_join_conds);
/* Build the list of columns to be fetched from the foreign server. */
! if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel))
fdw_scan_tlist = build_tlist_to_deparse(foreignrel);
else
fdw_scan_tlist = NIL;
--- 2793,2805 ----
&remote_param_join_conds, &local_param_join_conds);
/* Build the list of columns to be fetched from the foreign server. */
! if ((IS_JOIN_REL(foreignrel) && !grouped) || IS_UPPER_REL(foreignrel))
fdw_scan_tlist = build_tlist_to_deparse(foreignrel);
+ else if (grouped)
+ {
+ Assert(fpinfo->grouped_tlist != NIL);
+ fdw_scan_tlist = fpinfo->grouped_tlist;
+ }
else
fdw_scan_tlist = NIL;
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2560,2567 ****
initStringInfo(&sql);
appendStringInfoString(&sql, "EXPLAIN ");
deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist,
! remote_conds, pathkeys, false,
! &retrieved_attrs, NULL);
/* Get the remote estimate */
conn = GetConnection(fpinfo->user, false);
--- 2819,2826 ----
initStringInfo(&sql);
appendStringInfoString(&sql, "EXPLAIN ");
deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist,
! remote_conds, order_by_exprs, pathkeys, false,
! &retrieved_attrs, NULL, grouped);
/* Get the remote estimate */
conn = GetConnection(fpinfo->user, false);
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2615,2629 ****
* bare scan each time. Instead, use the costs if we have cached them
* already.
*/
! if (fpinfo->rel_startup_cost > 0 && fpinfo->rel_total_cost > 0)
{
! startup_cost = fpinfo->rel_startup_cost;
! run_cost = fpinfo->rel_total_cost - fpinfo->rel_startup_cost;
}
else if (IS_JOIN_REL(foreignrel))
{
! PgFdwRelationInfo *fpinfo_i;
! PgFdwRelationInfo *fpinfo_o;
QualCost join_cost;
QualCost remote_conds_cost;
double nrows;
--- 2874,2890 ----
* bare scan each time. Instead, use the costs if we have cached them
* already.
*/
! if (estimates->rel_startup_cost > 0 && estimates->rel_total_cost > 0)
{
! startup_cost = estimates->rel_startup_cost;
! run_cost = estimates->rel_total_cost - estimates->rel_startup_cost;
}
else if (IS_JOIN_REL(foreignrel))
{
! PgFdwRelationInfo *fpinfo_i,
! *fpinfo_o;
! EstimateInfo *est_i,
! *est_o;
QualCost join_cost;
QualCost remote_conds_cost;
double nrows;
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2632,2641 ****
Assert(fpinfo->innerrel && fpinfo->outerrel);
fpinfo_i = (PgFdwRelationInfo *) fpinfo->innerrel->fdw_private;
fpinfo_o = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private;
/* Estimate of number of rows in cross product */
! nrows = fpinfo_i->rows * fpinfo_o->rows;
/* Clamp retrieved rows estimate to at most size of cross product */
retrieved_rows = Min(retrieved_rows, nrows);
--- 2893,2904 ----
Assert(fpinfo->innerrel && fpinfo->outerrel);
fpinfo_i = (PgFdwRelationInfo *) fpinfo->innerrel->fdw_private;
+ est_i = !grouped ? &fpinfo_i->est_plain : &fpinfo_i->est_grouped;
fpinfo_o = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private;
+ est_o = !grouped ? &fpinfo_o->est_plain : &fpinfo_o->est_grouped;
/* Estimate of number of rows in cross product */
! nrows = est_i->rows * est_o->rows;
/* Clamp retrieved rows estimate to at most size of cross product */
retrieved_rows = Min(retrieved_rows, nrows);
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2659,2665 ****
* tables) since we do not know what strategy the foreign server
* is going to use.
*/
! startup_cost = fpinfo_i->rel_startup_cost + fpinfo_o->rel_startup_cost;
startup_cost += join_cost.startup;
startup_cost += remote_conds_cost.startup;
startup_cost += fpinfo->local_conds_cost.startup;
--- 2922,2928 ----
* tables) since we do not know what strategy the foreign server
* is going to use.
*/
! startup_cost = est_i->rel_startup_cost + est_o->rel_startup_cost;
startup_cost += join_cost.startup;
startup_cost += remote_conds_cost.startup;
startup_cost += fpinfo->local_conds_cost.startup;
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2679,2695 ****
* 4. Run time cost of applying nonpushable other clauses locally
* on the result fetched from the foreign server.
*/
! run_cost = fpinfo_i->rel_total_cost - fpinfo_i->rel_startup_cost;
! run_cost += fpinfo_o->rel_total_cost - fpinfo_o->rel_startup_cost;
run_cost += nrows * join_cost.per_tuple;
nrows = clamp_row_est(nrows * fpinfo->joinclause_sel);
run_cost += nrows * remote_conds_cost.per_tuple;
run_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows;
}
! else if (IS_UPPER_REL(foreignrel))
{
PgFdwRelationInfo *ofpinfo;
! PathTarget *ptarget = root->upper_targets[UPPERREL_GROUP_AGG];
AggClauseCosts aggcosts;
double input_rows;
int numGroupCols;
--- 2942,2970 ----
* 4. Run time cost of applying nonpushable other clauses locally
* on the result fetched from the foreign server.
*/
! run_cost = est_i->rel_total_cost - est_i->rel_startup_cost;
! run_cost += est_o->rel_total_cost - est_o->rel_startup_cost;
run_cost += nrows * join_cost.per_tuple;
nrows = clamp_row_est(nrows * fpinfo->joinclause_sel);
run_cost += nrows * remote_conds_cost.per_tuple;
run_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows;
}
!
! /*
! * TODO Consider a separate branch for
! *
! * (IS_SIMPLE_REL(foreignrel) && * grouped)
! *
! * and use foreignrel->gpi->target in it instead of
! * fpinfo->grouped_tlist.
! */
! else if (IS_UPPER_REL(foreignrel) ||
! (IS_SIMPLE_REL(foreignrel) && grouped)
! )
{
PgFdwRelationInfo *ofpinfo;
! EstimateInfo *est;
! PathTarget *ptarget;
AggClauseCosts aggcosts;
double input_rows;
int numGroupCols;
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2707,2717 ****
* considering remote and local conditions for costing.
*/
! ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private;
/* Get rows and width from input rel */
! input_rows = ofpinfo->rows;
! width = ofpinfo->width;
/* Collect statistics about aggregates for estimating costs. */
MemSet(&aggcosts, 0, sizeof(AggClauseCosts));
--- 2982,3001 ----
* considering remote and local conditions for costing.
*/
! if (IS_UPPER_REL(foreignrel))
! {
! ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private;
! est = &ofpinfo->est_plain;
! }
! else
! {
! ofpinfo = fpinfo;
! est = &ofpinfo->est_grouped;
! }
/* Get rows and width from input rel */
! input_rows = est->rows;
! width = est->width;
/* Collect statistics about aggregates for estimating costs. */
MemSet(&aggcosts, 0, sizeof(AggClauseCosts));
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2736,2741 ****
--- 3020,3033 ----
*/
rows = retrieved_rows = numGroups;
+ if (IS_UPPER_REL(foreignrel))
+ ptarget = root->upper_targets[UPPERREL_GROUP_AGG];
+ else
+ {
+ Assert(foreignrel->gpi != NULL);
+ ptarget = foreignrel->gpi->target;
+ }
+
/*-----
* Startup cost includes:
* 1. Startup cost for underneath input * relation
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2743,2749 ****
* 3. Startup cost for PathTarget eval
*-----
*/
! startup_cost = ofpinfo->rel_startup_cost;
startup_cost += aggcosts.transCost.startup;
startup_cost += aggcosts.transCost.per_tuple * input_rows;
startup_cost += (cpu_operator_cost * numGroupCols) * input_rows;
--- 3035,3041 ----
* 3. Startup cost for PathTarget eval
*-----
*/
! startup_cost = est->rel_startup_cost;
startup_cost += aggcosts.transCost.startup;
startup_cost += aggcosts.transCost.per_tuple * input_rows;
startup_cost += (cpu_operator_cost * numGroupCols) * input_rows;
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2756,2762 ****
* 3. PathTarget eval cost for each output row
*-----
*/
! run_cost = ofpinfo->rel_total_cost - ofpinfo->rel_startup_cost;
run_cost += aggcosts.finalCost * numGroups;
run_cost += cpu_tuple_cost * numGroups;
run_cost += ptarget->cost.per_tuple * numGroups;
--- 3048,3054 ----
* 3. PathTarget eval cost for each output row
*-----
*/
! run_cost = est->rel_total_cost - est->rel_startup_cost;
run_cost += aggcosts.finalCost * numGroups;
run_cost += cpu_tuple_cost * numGroups;
run_cost += ptarget->cost.per_tuple * numGroups;
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2809,2816 ****
*/
if (pathkeys == NIL && param_join_conds == NIL)
{
! fpinfo->rel_startup_cost = startup_cost;
! fpinfo->rel_total_cost = total_cost;
}
/*
--- 3101,3108 ----
*/
if (pathkeys == NIL && param_join_conds == NIL)
{
! estimates->rel_startup_cost = startup_cost;
! estimates->rel_total_cost = total_cost;
}
/*
*************** estimate_path_cost_size(PlannerInfo *roo
*** 2825,2834 ****
total_cost += cpu_tuple_cost * retrieved_rows;
/* Return results. */
! *p_rows = rows;
! *p_width = width;
! *p_startup_cost = startup_cost;
! *p_total_cost = total_cost;
}
/*
--- 3117,3126 ----
total_cost += cpu_tuple_cost * retrieved_rows;
/* Return results. */
! estimates->rows = rows;
! estimates->width = width;
! estimates->startup_cost = startup_cost;
! estimates->total_cost = total_cost;
}
/*
*************** postgresImportForeignSchema(ImportForeig
*** 4057,4075 ****
* Assess whether the join between inner and outer relations can be pushed down
* to the foreign server. As a side effect, save information we obtain in this
* function to PgFdwRelationInfo passed in.
*/
static bool
foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinPathExtraData *extra)
{
! PgFdwRelationInfo *fpinfo;
! PgFdwRelationInfo *fpinfo_o;
! PgFdwRelationInfo *fpinfo_i;
ListCell *lc;
List *joinclauses;
/*
* We support pushing down INNER, LEFT, RIGHT and FULL OUTER joins.
* Constructing queries representing SEMI and ANTI joins is hard, hence
* not considered right now.
--- 4349,4378 ----
* Assess whether the join between inner and outer relations can be pushed down
* to the foreign server. As a side effect, save information we obtain in this
* function to PgFdwRelationInfo passed in.
+ *
+ * Caller expects that the "grouped" argument does not affect the result, so
+ * it's enough to call the function only once per relation. XXX As that
+ * argument is only used in Assert() statement, shouldn't it be removed?
*/
static bool
foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
RelOptInfo *outerrel, RelOptInfo *innerrel,
! JoinPathExtraData *extra, bool grouped)
{
! PgFdwRelationInfo *fpinfo,
! *fpinfo_o,
! *fpinfo_i;
ListCell *lc;
List *joinclauses;
/*
+ * TODO
+ *
+ * Put the (join-relevant) initial checks of foreign_grouping_ok into a
+ * function and call it here.
+ */
+
+ /*
* We support pushing down INNER, LEFT, RIGHT and FULL OUTER joins.
* Constructing queries representing SEMI and ANTI joins is hard, hence
* not considered right now.
*************** foreign_join_ok(PlannerInfo *root, RelOp
*** 4139,4145 ****
--- 4442,4458 ----
if (is_remote_clause)
fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo);
else
+ {
+ /*
+ * We could return in the grouped case because all clauses
+ * must be evaluated below the grouping plan (Thus if the
+ * grouping is remote, those clauses must be remote too.)
+ * However the same PgFdwRelationInfo is used for both grouped
+ * and non-grouped case and we don't want to force caller to
+ * process the non-grouped case first.
+ */
fpinfo->local_conds = lappend(fpinfo->local_conds, rinfo);
+ }
}
}
*************** foreign_join_ok(PlannerInfo *root, RelOp
*** 4158,4166 ****
PlaceHolderInfo *phinfo = lfirst(lc);
Relids relids = joinrel->relids;
! if (bms_is_subset(phinfo->ph_eval_at, relids) &&
! bms_nonempty_difference(relids, phinfo->ph_eval_at))
! return false;
}
/* Save the join clauses, for later use. */
--- 4471,4496 ----
PlaceHolderInfo *phinfo = lfirst(lc);
Relids relids = joinrel->relids;
! if (bms_is_subset(phinfo->ph_eval_at, relids))
! {
! if (bms_nonempty_difference(relids, phinfo->ph_eval_at))
! {
! #ifdef USE_ASSERT_CHECKING
! if (grouped)
! {
! /*
! * Grouped join shouldn't have PlaceHolderVar even in its
! * own targetlist. It'd mean that the output of partial
! * aggregation can be set to NULL before it gets to the
! * input of the final aggregation.
! */
! Assert(false);
! }
! #endif /* USE_ASSERT_CHECKING */
!
! return false;
! }
! }
}
/* Save the join clauses, for later use. */
*************** foreign_join_ok(PlannerInfo *root, RelOp
*** 4283,4296 ****
fpinfo->user = NULL;
/*
- * Set cached relation costs to some negative value, so that we can detect
- * when they are set to some sensible costs, during one (usually the
- * first) of the calls to estimate_path_cost_size().
- */
- fpinfo->rel_startup_cost = -1;
- fpinfo->rel_total_cost = -1;
-
- /*
* Set the string describing this join relation to be used in EXPLAIN
* output of corresponding ForeignScan.
*/
--- 4613,4618 ----
*************** foreign_join_ok(PlannerInfo *root, RelOp
*** 4315,4354 ****
static void
add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
! Path *epq_path)
{
List *useful_pathkeys_list = NIL; /* List of all pathkeys */
! ListCell *lc;
! useful_pathkeys_list = get_useful_pathkeys_for_relation(root, rel);
/* Create one path for each set of pathkeys we found above. */
! foreach(lc, useful_pathkeys_list)
{
! double rows;
! int width;
! Cost startup_cost;
! Cost total_cost;
! List *useful_pathkeys = lfirst(lc);
! estimate_path_cost_size(root, rel, NIL, useful_pathkeys,
! &rows, &width, &startup_cost, &total_cost);
! add_path(rel, (Path *)
! create_foreignscan_path(root, rel,
! NULL,
! rows,
! startup_cost,
! total_cost,
! useful_pathkeys,
! NULL,
! epq_path,
! NIL),
! false);
}
}
/*
* Parse options from foreign server and apply them to fpinfo.
*
* New options might also require tweaking merge_fdw_options().
--- 4637,4858 ----
static void
add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
! Path *epq_path, bool grouped)
{
List *useful_pathkeys_list = NIL; /* List of all pathkeys */
! ListCell *l;
! PathTarget *target = NULL;
! PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
! List *fdw_scan_tlist = NIL;
! useful_pathkeys_list = get_useful_pathkeys_for_relation(root, rel,
! grouped);
!
! /*
! * If the path should be grouped, not all the ECs are necessarily present
! * in the target: the EC member applicable to the parent relation can in
! * fact be an aggregate argument which does not otherwise appear in the
! * query output.
! */
! if (grouped)
! fdw_scan_tlist = fpinfo->grouped_tlist;
!
! /*
! * Grouped foreign scan should emit tuples according to the grouped
! * target, i.e. the one containing GroupedVars.
! */
! if (grouped)
! {
! Assert(rel->gpi != NULL);
! target = rel->gpi->target;
! }
/* Create one path for each set of pathkeys we found above. */
! foreach(l, useful_pathkeys_list)
{
! List *useful_pathkeys = lfirst(l);
! EstimateInfo estimates;
! List *order_by_exprs = NIL;
! List *fdw_private;
! Path *path;
! /*
! * Perform preprocessing specific to grouped paths or reject the
! * pathkeys if they cannot be used on the remote database.
! */
! order_by_exprs = get_order_by_expressions(rel->relids,
! useful_pathkeys,
! fdw_scan_tlist);
! /*
! * Skip these pathkeys if the remote database cannot perform the
! * appropriate sort.
! */
! if (order_by_exprs == NIL)
! continue;
!
! /*
! * The cached values are not useful for join path.
! */
! estimates.rel_startup_cost = -1;
! estimates.rel_total_cost = -1;
!
! estimate_path_cost_size(root, rel, NIL, order_by_exprs,
! useful_pathkeys, &estimates, grouped);
!
! /*
! * Add information to the paths whether they are grouped or not. And if it
! * is, pass it the expressions for the ORDER BY clause.
! */
! fdw_private = list_make2(makeInteger(grouped ? 1 : 0),
! order_by_exprs);
!
! path = (Path *) create_foreignscan_path(root, rel,
! target,
! estimates.rows,
! estimates.startup_cost,
! estimates.total_cost,
! useful_pathkeys,
! NULL,
! epq_path,
! fdw_private);
!
! /*
! * Grouped path essentially produces an unique set of grouping
! * keys.
! */
! if (grouped)
! make_uniquekeys_for_agg_path(root, path);
!
! add_path(rel, path, grouped);
}
}
/*
+ * Create a list of expressions to form ORDER BY clause on the remote
+ * database. Pass fdw_scan_tlist if the pathkeys possibly contain expressions
+ * not present in the targetlist. Such expressions will be ignored.
+ */
+ static List *
+ get_order_by_expressions(Relids relids, List *pathkeys, List *fdw_scan_tlist)
+ {
+ ListCell *l1;
+ List *result = NIL;
+
+ foreach(l1, pathkeys)
+ {
+ ListCell *l2;
+ PathKey *pathkey = lfirst_node(PathKey, l1);
+ Expr *em_expr = NULL;
+ Aggref *aggref_copy = NULL;
+
+ /*
+ * Since the targetlist can contain EC-derived expressions, extra
+ * effort is needed to ensure that ORDER BY clause references
+ * expression actually contained in the targetlist.
+ */
+ foreach(l2, pathkey->pk_eclass->ec_members)
+ {
+ EquivalenceMember *em = lfirst_node(EquivalenceMember, l2);
+
+ if (bms_is_subset(em->em_relids, relids))
+ {
+ ListCell *l3;
+
+ if (fdw_scan_tlist)
+ {
+ /*
+ * The EM can be derived from an expression present in the
+ * targetlist, but is not necessarily in the list
+ * itself. However the ORDER BY clause used on the remote
+ * server can only contain the "real" targetlist
+ * expression.
+ */
+ foreach(l3, fdw_scan_tlist)
+ {
+ TargetEntry *te = lfirst_node(TargetEntry, l3);
+ Expr *expr = te->expr;
+
+ /*
+ * Even if the query has Aggref nested in a generic
+ * expression, fdw_scan_tlist should only contain
+ * separate entry for the Aggref itself. Thus we don't
+ * need to check the expressions recursively.
+ */
+ if (IsA(expr, Aggref) && IsA(em->em_expr, Aggref))
+ {
+ Aggref *aggref = castNode(Aggref, expr);
+ Aggref *aggref_em;
+
+ /*
+ * It makes no sense to sort by an aggregate if it
+ * requires finalization. First, the finalized
+ * values might end up sorted in a different
+ * order, second, the transient state might not be
+ * sortable at all.
+ */
+ if (aggref->aggfinalfn != InvalidOid)
+ {
+ list_free(result);
+ return NIL;
+ }
+
+ aggref_em = (Aggref *) em->em_expr;
+
+ /*
+ * The EC members are simple aggregates, so adjust
+ * the target expression to make match
+ * possible. Flat copy is sufficient for this
+ * purpose.
+ */
+ Assert(aggref_em->aggsplit == AGGSPLIT_SIMPLE);
+ Assert(aggref->aggsplit == AGGSPLIT_INITIAL_SERIAL);
+ aggref_copy = makeNode(Aggref);
+ memcpy(aggref_copy, aggref, sizeof(Aggref));
+ aggref_copy->aggsplit = AGGSPLIT_SIMPLE;
+ aggref_copy->aggtype = aggref_em->aggtype;
+ expr = (Expr *) aggref_copy;
+ }
+
+ if (equal(expr, em->em_expr))
+ {
+ em_expr = em->em_expr;
+ break;
+ }
+ }
+ }
+ else
+ em_expr = em->em_expr;
+ }
+
+ /*
+ * Target expression found for the current pathkey?
+ */
+ if (em_expr != NULL)
+ break;
+ }
+
+ /*
+ * A single missing expression makes the pathkey useless. This is
+ * specific to grouped relation because it can only emit grouping
+ * expressions and aggregates.
+ */
+ if (em_expr == NULL)
+ {
+ list_free(result);
+ return NIL;
+ }
+
+ result = lappend(result, em_expr);
+
+ if (aggref_copy)
+ pfree(aggref_copy);
+ }
+
+ return result;
+ }
+
+ /*
* Parse options from foreign server and apply them to fpinfo.
*
* New options might also require tweaking merge_fdw_options().
*************** postgresGetForeignJoinPaths(PlannerInfo
*** 4462,4496 ****
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra)
{
PgFdwRelationInfo *fpinfo;
ForeignPath *joinpath;
- double rows;
- int width;
- Cost startup_cost;
- Cost total_cost;
Path *epq_path; /* Path to create plan to be executed when
* EvalPlanQual gets triggered. */
! /*
! * Skip if this join combination has been considered already.
! */
! if (joinrel->fdw_private)
! return;
! /*
! * Create unfinished PgFdwRelationInfo entry which is used to indicate
! * that the join relation is already considered, so that we won't waste
! * time in judging safety of join pushdown and adding the same paths again
! * if found safe. Once we know that this join can be pushed down, we fill
! * the entry.
! */
! fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
! fpinfo->pushdown_safe = false;
! joinrel->fdw_private = fpinfo;
! /* attrs_used is only for base relations. */
! fpinfo->attrs_used = NULL;
/*
* If there is a possibility that EvalPlanQual will be executed, we need
--- 4966,5016 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped)
{
PgFdwRelationInfo *fpinfo;
+ EstimateInfo *estimates;
ForeignPath *joinpath;
Path *epq_path; /* Path to create plan to be executed when
* EvalPlanQual gets triggered. */
+ bool first_time;
+ PathTarget *target = NULL;
+ List *fdw_private;
! if (joinrel->fdw_private == NULL)
! {
! /*
! * First time through.
! *
! * Create unfinished PgFdwRelationInfo entry which is used to indicate
! * that the join relation is already considered, so that we won't
! * waste time in judging safety of join pushdown and adding the same
! * paths again if found safe. Once we know that this join can be
! * pushed down, we fill the entry.
! */
! fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
! fpinfo->pushdown_safe = false;
! joinrel->fdw_private = fpinfo;
! /* attrs_used is only for base relations. */
! fpinfo->attrs_used = NULL;
! first_time = true;
! }
! else
! {
! fpinfo = (PgFdwRelationInfo *) joinrel->fdw_private;
! first_time = false;
!
! if (!fpinfo->pushdown_safe)
! {
! /*
! * The function was already called but some test failed. No reason
! * to see that failure again.
! */
! return;
! }
! }
/*
* If there is a possibility that EvalPlanQual will be executed, we need
*************** postgresGetForeignJoinPaths(PlannerInfo
*** 4501,4527 ****
* dominate the only suitable local path available. We also do it before
* calling foreign_join_ok(), since that function updates fpinfo and marks
* it as pushable if the join is found to be pushable.
*/
! if (root->parse->commandType == CMD_DELETE ||
! root->parse->commandType == CMD_UPDATE ||
! root->rowMarks)
{
! epq_path = GetExistingLocalJoinPath(joinrel);
! if (!epq_path)
{
! elog(DEBUG3, "could not push down foreign join because a local path suitable for EPQ checks was not found");
return;
}
}
- else
- epq_path = NULL;
! if (!foreign_join_ok(root, joinrel, jointype, outerrel, innerrel, extra))
! {
! /* Free path required for EPQ if we copied one; we don't need it now */
! if (epq_path)
! pfree(epq_path);
return;
}
/*
--- 5021,5098 ----
* dominate the only suitable local path available. We also do it before
* calling foreign_join_ok(), since that function updates fpinfo and marks
* it as pushable if the join is found to be pushable.
+ *
+ * The following tests should only be performed once --- repeated, the are
+ * supposed to yield the same result. (If anything failed initially,
+ * pushdown_safe should have caused return above.)
*/
! if (first_time)
{
! if (root->parse->commandType == CMD_DELETE ||
! root->parse->commandType == CMD_UPDATE ||
! root->rowMarks)
{
! epq_path = GetExistingLocalJoinPath(joinrel);
! if (!epq_path)
! {
! elog(DEBUG3, "could not push down foreign join because a local path suitable for EPQ checks was not found");
! return;
! }
! }
! else
! epq_path = NULL;
!
! if (!foreign_join_ok(root, joinrel, jointype, outerrel, innerrel, extra,
! grouped))
! {
! /*
! * Free path required for EPQ if we copied one; we don't need it
! * now
! */
! if (epq_path)
! pfree(epq_path);
return;
}
}
! /*
! * Local expressions would be evaluated above the grouping plan, which
! * will be remote.
! */
! if (grouped && fpinfo->local_conds != NIL)
return;
+
+ /*
+ * This function should not be called repeatedly wit the same value of
+ * "grouped", but if it happened, make sure the "upper conditions" do not
+ * get duplicated or used at all if !grouped.
+ */
+ if (fpinfo->remote_conds_upper)
+ {
+ list_free(fpinfo->remote_conds_upper);
+ fpinfo->remote_conds_upper = NIL;
+ }
+ if (fpinfo->local_conds_upper)
+ {
+ list_free(fpinfo->local_conds_upper);
+ fpinfo->local_conds_upper = NIL;
+ }
+
+ if (grouped)
+ {
+ /*
+ * Create targelist to be used for the remote grouping.
+ */
+ Assert(joinrel->gpi != NULL);
+ fpinfo->grouped_tlist = create_remote_agg_tlist(root, joinrel,
+ joinrel->gpi->target);
+
+ /*
+ * Couldn't push any GROUP BY expression or aggregate to the remote
+ * server?
+ */
+ if (fpinfo->grouped_tlist == NIL)
+ return;
}
/*
*************** postgresGetForeignJoinPaths(PlannerInfo
*** 4548,4563 ****
0, fpinfo->jointype,
extra->sjinfo);
/* Estimate costs for bare join relation */
! estimate_path_cost_size(root, joinrel, NIL, NIL, &rows,
! &width, &startup_cost, &total_cost);
/* Now update this information in the joinrel */
! joinrel->rows = rows;
! joinrel->reltarget->width = width;
! fpinfo->rows = rows;
! fpinfo->width = width;
! fpinfo->startup_cost = startup_cost;
! fpinfo->total_cost = total_cost;
/*
* Create a new join path and add it to the joinrel which represents a
--- 5119,5163 ----
0, fpinfo->jointype,
extra->sjinfo);
+ /*
+ * Set cached relation costs to some negative value, so that we can detect
+ * when they are set to some sensible costs, during one (usually the
+ * first) of the calls to estimate_path_cost_size().
+ */
+ estimates = !grouped ? &fpinfo->est_plain : &fpinfo->est_grouped;
+ estimates->rel_startup_cost = -1;
+ estimates->rel_total_cost = -1;
+
/* Estimate costs for bare join relation */
!
! /*
! * TODO Include remote_conds_upper and local_conds_upper into the
! * estimates.
! */
! estimate_path_cost_size(root, joinrel, NIL, NIL, NIL, estimates, grouped);
/* Now update this information in the joinrel */
!
! /*
! * TODO If grouped, make sure baserel->gpi exists and store the values
! * there.
! */
! joinrel->rows = estimates->rows;
! joinrel->reltarget->width = estimates->width;
!
! if (grouped)
! {
! /*
! * The target must contain aggregates.
! */
! Assert(joinrel->gpi != NULL);
! target = joinrel->gpi->target;
! }
!
! /*
! * Add information to the paths whether they are grouped or not. Pass NIL
! * for ordering expressions.
! */
! fdw_private = list_make2(makeInteger(grouped ? 1 : 0), NIL);
/*
* Create a new join path and add it to the joinrel which represents a
*************** postgresGetForeignJoinPaths(PlannerInfo
*** 4565,4584 ****
*/
joinpath = create_foreignscan_path(root,
joinrel,
! NULL, /* default pathtarget */
! rows,
! startup_cost,
! total_cost,
NIL, /* no pathkeys */
NULL, /* no required_outer */
epq_path,
! NIL); /* no fdw_private */
/* Add generated path into joinrel by add_path(). */
! add_path(joinrel, (Path *) joinpath, false);
/* Consider pathkeys for the join relation */
! add_paths_with_pathkeys_for_rel(root, joinrel, epq_path);
/* XXX Consider parameterized paths for the join relation */
}
--- 5165,5191 ----
*/
joinpath = create_foreignscan_path(root,
joinrel,
! target, /* default pathtarget */
! estimates->rows,
! estimates->startup_cost,
! estimates->total_cost,
NIL, /* no pathkeys */
NULL, /* no required_outer */
epq_path,
! fdw_private); /* no fdw_private */
!
! /*
! * Grouped path essentially produces an unique set of grouping
! * keys.
! */
! if (grouped)
! make_uniquekeys_for_agg_path(root, (Path *) joinpath);
/* Add generated path into joinrel by add_path(). */
! add_path(joinrel, (Path *) joinpath, grouped);
/* Consider pathkeys for the join relation */
! add_paths_with_pathkeys_for_rel(root, joinrel, epq_path, grouped);
/* XXX Consider parameterized paths for the join relation */
}
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4594,4618 ****
Query *query = root->parse;
PathTarget *grouping_target;
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) grouped_rel->fdw_private;
! PgFdwRelationInfo *ofpinfo;
! List *aggvars;
! ListCell *lc;
! int i;
! List *tlist = NIL;
/* Grouping Sets are not pushable */
if (query->groupingSets)
return false;
/* Get the fpinfo of the underlying scan relation. */
! ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private;
/*
* If underneath input relation has any local conditions, those conditions
* are required to be applied before performing aggregation. Hence the
* aggregate cannot be pushed down.
*/
! if (ofpinfo->local_conds)
return false;
/*
--- 5201,5223 ----
Query *query = root->parse;
PathTarget *grouping_target;
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) grouped_rel->fdw_private;
! PgFdwRelationInfo *ofpinfo = NULL;
/* Grouping Sets are not pushable */
if (query->groupingSets)
return false;
/* Get the fpinfo of the underlying scan relation. */
! if (IS_UPPER_REL(grouped_rel))
! ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private;
/*
* If underneath input relation has any local conditions, those conditions
* are required to be applied before performing aggregation. Hence the
* aggregate cannot be pushed down.
*/
! if ((IS_UPPER_REL(grouped_rel) && ofpinfo->local_conds) ||
! fpinfo->local_conds)
return false;
/*
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4623,4637 ****
* different from those in the plan's targetlist. Use a copy of path
* target to record the new sortgrouprefs.
*/
! grouping_target = copy_pathtarget(root->upper_targets[UPPERREL_GROUP_AGG]);
/*
! * Evaluate grouping targets and check whether they are safe to push down
! * to the foreign side. All GROUP BY expressions will be part of the
! * grouping target and thus there is no need to evaluate it separately.
! * While doing so, add required expressions into target list which can
! * then be used to pass to foreign server.
*/
i = 0;
foreach(lc, grouping_target->exprs)
{
--- 5228,5296 ----
* different from those in the plan's targetlist. Use a copy of path
* target to record the new sortgrouprefs.
*/
! if (IS_UPPER_REL(grouped_rel))
! grouping_target = root->upper_targets[UPPERREL_GROUP_AGG];
! else
! {
! /*
! * FDW should not be asked to generate grouped paths if the simple or
! * join relation has no relevant target.
! */
! Assert(grouped_rel->gpi != NULL);
! Assert(grouped_rel->gpi->target != NULL);
!
! grouping_target = grouped_rel->gpi->target;
! }
/*
! * Create targetlist to be sent to the remote server.
*/
+ fpinfo->grouped_tlist = create_remote_agg_tlist(root, grouped_rel,
+ grouping_target);
+ if (fpinfo->grouped_tlist == NIL)
+ return false;
+
+ /* Safe to pushdown */
+ fpinfo->pushdown_safe = true;
+
+ /*
+ * Set the string describing this grouped relation to be used in EXPLAIN
+ * output of corresponding ForeignScan.
+ */
+ if (ofpinfo != NULL)
+ {
+ fpinfo->relation_name = makeStringInfo();
+ appendStringInfo(fpinfo->relation_name, "Aggregate on (%s)",
+ ofpinfo->relation_name->data);
+ }
+
+ return true;
+ }
+
+ /*
+ * Evaluate grouping targets and check whether they are safe to push down to
+ * the foreign side. All GROUP BY expressions will be part of the grouping
+ * target and thus there is no need to evaluate it separately. While doing
+ * so, add required expressions into target list which can then be used to
+ * pass to foreign server.
+ *
+ * NIL is returned if the remote aggregation appears to be impossible.
+ *
+ * Note that the function can add items to both remote_conds_upper and
+ * local_conds_upper lists of PgFdwRelationInfo.
+ */
+ static List *
+ create_remote_agg_tlist(PlannerInfo *root, RelOptInfo *grouped_rel,
+ PathTarget *grouping_target)
+ {
+ List *tlist = NIL;
+ ListCell *lc;
+ int i;
+ Query *query = root->parse;
+ PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) grouped_rel->fdw_private;
+
+ grouping_target = copy_pathtarget(grouping_target);
+
i = 0;
foreach(lc, grouping_target->exprs)
{
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4647,4659 ****
* push down aggregation to the foreign server.
*/
if (!is_foreign_expr(root, grouped_rel, expr))
! return false;
/* Pushable, add to tlist */
tlist = add_to_flat_tlist(tlist, list_make1(expr));
}
else
{
/* Check entire expression whether it is pushable or not */
if (is_foreign_expr(root, grouped_rel, expr))
{
--- 5306,5339 ----
* push down aggregation to the foreign server.
*/
if (!is_foreign_expr(root, grouped_rel, expr))
! return NIL;
/* Pushable, add to tlist */
tlist = add_to_flat_tlist(tlist, list_make1(expr));
}
else
{
+ /*
+ * Aggregate can sometimes be wrapped by GroupedVar.
+ *
+ */
+ if ((IS_SIMPLE_REL(grouped_rel) || IS_JOIN_REL(grouped_rel)) &&
+ IsA(expr, GroupedVar))
+ {
+ GroupedVar *gvar = castNode(GroupedVar, expr);
+
+ /*
+ * GroupedVar is currently used only to handle Aggrefs.
+ *
+ * XXX These may include aggregates that appear in the HAVING
+ * clause. Although the HAVING clause is currently not sent to
+ * the remote server (see deparseSelectStmtForRel), we still
+ * need to send the aggregates the HAVING clause contains.
+ */
+ Assert(IsA(gvar->agg_partial, Aggref));
+ expr = (Expr *) gvar->agg_partial;
+ }
+
/* Check entire expression whether it is pushable or not */
if (is_foreign_expr(root, grouped_rel, expr))
{
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4662,4667 ****
--- 5342,5349 ----
}
else
{
+ List *aggvars;
+
/*
* If we have sortgroupref set, then it means that we have an
* ORDER BY entry pointing to this expression. Since we are
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4670,4681 ****
if (sgref)
grouping_target->sortgrouprefs[i] = 0;
! /* Not matched exactly, pull the var with aggregates then */
aggvars = pull_var_clause((Node *) expr,
PVC_INCLUDE_AGGREGATES);
if (!is_foreign_expr(root, grouped_rel, (Expr *) aggvars))
! return false;
/*
* Add aggregates, if any, into the targetlist. Plain var
--- 5352,5365 ----
if (sgref)
grouping_target->sortgrouprefs[i] = 0;
! /*
! * Not matched exactly, pull the var with aggregates then
! */
aggvars = pull_var_clause((Node *) expr,
PVC_INCLUDE_AGGREGATES);
if (!is_foreign_expr(root, grouped_rel, (Expr *) aggvars))
! return NIL;
/*
* Add aggregates, if any, into the targetlist. Plain var
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4703,4714 ****
/*
* Classify the pushable and non-pushable having clauses and save them in
! * remote_conds and local_conds of the grouped rel's fpinfo.
*/
if (root->hasHavingQual && query->havingQual)
{
- ListCell *lc;
-
foreach(lc, (List *) query->havingQual)
{
Expr *expr = (Expr *) lfirst(lc);
--- 5387,5403 ----
/*
* Classify the pushable and non-pushable having clauses and save them in
! * remote_conds_upper and local_conds_upper of the grouped rel's fpinfo.
! */
!
! /*
! * TODO For non-upper grouped_rel, only pick those expressions for which
! * grouped_rel provides all input vars. (And Assert() that grouped_rel
! * does not emit just some of them --- in such a case grouped_rel
! * shouldn't be subject to partial aggregation at all.)
*/
if (root->hasHavingQual && query->havingQual)
{
foreach(lc, (List *) query->havingQual)
{
Expr *expr = (Expr *) lfirst(lc);
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4728,4736 ****
NULL,
NULL);
if (is_foreign_expr(root, grouped_rel, expr))
! fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo);
else
! fpinfo->local_conds = lappend(fpinfo->local_conds, rinfo);
}
}
--- 5417,5427 ----
NULL,
NULL);
if (is_foreign_expr(root, grouped_rel, expr))
! fpinfo->remote_conds_upper =
! lappend(fpinfo->remote_conds_upper, rinfo);
else
! fpinfo->local_conds_upper =
! lappend(fpinfo->local_conds_upper, rinfo);
}
}
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4738,4749 ****
* If there are any local conditions, pull Vars and aggregates from it and
* check whether they are safe to pushdown or not.
*/
! if (fpinfo->local_conds)
{
List *aggvars = NIL;
- ListCell *lc;
! foreach(lc, fpinfo->local_conds)
{
RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
--- 5429,5439 ----
* If there are any local conditions, pull Vars and aggregates from it and
* check whether they are safe to pushdown or not.
*/
! if (fpinfo->local_conds_upper)
{
List *aggvars = NIL;
! foreach(lc, fpinfo->local_conds_upper)
{
RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4765,4771 ****
if (IsA(expr, Aggref))
{
if (!is_foreign_expr(root, grouped_rel, expr))
! return false;
tlist = add_to_flat_tlist(tlist, list_make1(expr));
}
--- 5455,5461 ----
if (IsA(expr, Aggref))
{
if (!is_foreign_expr(root, grouped_rel, expr))
! return NIL;
tlist = add_to_flat_tlist(tlist, list_make1(expr));
}
*************** foreign_grouping_ok(PlannerInfo *root, R
*** 4775,4803 ****
/* Transfer any sortgroupref data to the replacement tlist */
apply_pathtarget_labeling_to_tlist(tlist, grouping_target);
! /* Store generated targetlist */
! fpinfo->grouped_tlist = tlist;
!
! /* Safe to pushdown */
! fpinfo->pushdown_safe = true;
!
! /*
! * Set cached relation costs to some negative value, so that we can detect
! * when they are set to some sensible costs, during one (usually the
! * first) of the calls to estimate_path_cost_size().
! */
! fpinfo->rel_startup_cost = -1;
! fpinfo->rel_total_cost = -1;
!
! /*
! * Set the string describing this grouped relation to be used in EXPLAIN
! * output of corresponding ForeignScan.
! */
! fpinfo->relation_name = makeStringInfo();
! appendStringInfo(fpinfo->relation_name, "Aggregate on (%s)",
! ofpinfo->relation_name->data);
!
! return true;
}
/*
--- 5465,5471 ----
/* Transfer any sortgroupref data to the replacement tlist */
apply_pathtarget_labeling_to_tlist(tlist, grouping_target);
! return tlist;
}
/*
*************** add_foreign_grouping_paths(PlannerInfo *
*** 4846,4857 ****
Query *parse = root->parse;
PgFdwRelationInfo *ifpinfo = input_rel->fdw_private;
PgFdwRelationInfo *fpinfo = grouped_rel->fdw_private;
ForeignPath *grouppath;
PathTarget *grouping_target;
! double rows;
! int width;
! Cost startup_cost;
! Cost total_cost;
/* Nothing to be done, if there is no grouping or aggregation required. */
if (!parse->groupClause && !parse->groupingSets && !parse->hasAggs &&
--- 5514,5523 ----
Query *parse = root->parse;
PgFdwRelationInfo *ifpinfo = input_rel->fdw_private;
PgFdwRelationInfo *fpinfo = grouped_rel->fdw_private;
+ EstimateInfo *estimates;
ForeignPath *grouppath;
PathTarget *grouping_target;
! List *fdw_private;
/* Nothing to be done, if there is no grouping or aggregation required. */
if (!parse->groupClause && !parse->groupingSets && !parse->hasAggs &&
*************** add_foreign_grouping_paths(PlannerInfo *
*** 4877,4902 ****
return;
/* Estimate the cost of push down */
! estimate_path_cost_size(root, grouped_rel, NIL, NIL, &rows,
! &width, &startup_cost, &total_cost);
! /* Now update this information in the fpinfo */
! fpinfo->rows = rows;
! fpinfo->width = width;
! fpinfo->startup_cost = startup_cost;
! fpinfo->total_cost = total_cost;
/* Create and add foreign path to the grouping relation. */
grouppath = create_foreignscan_path(root,
grouped_rel,
grouping_target,
! rows,
! startup_cost,
! total_cost,
NIL, /* no pathkeys */
NULL, /* no required_outer */
NULL,
! NIL); /* no fdw_private */
/* Add generated path into grouped_rel by add_path(). */
add_path(grouped_rel, (Path *) grouppath, false);
--- 5543,5570 ----
return;
/* Estimate the cost of push down */
! estimates = &fpinfo->est_grouped;
! estimate_path_cost_size(root, grouped_rel, NIL, NIL, NIL, estimates,
! true);
! /*
! * The path is considered grouped when it comes to plan creation and
! * deparsing. The 2nd element (ORDER BY expressions) is NIL because
! * UPPERREL_GROUP_AGG does not care about ordering.
! */
! fdw_private = list_make2(makeInteger(1), NIL);
/* Create and add foreign path to the grouping relation. */
grouppath = create_foreignscan_path(root,
grouped_rel,
grouping_target,
! estimates->rows,
! estimates->startup_cost,
! estimates->total_cost,
NIL, /* no pathkeys */
NULL, /* no required_outer */
NULL,
! fdw_private);
/* Add generated path into grouped_rel by add_path(). */
add_path(grouped_rel, (Path *) grouppath, false);
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
new file mode 100644
index 788b003..e9a63a8
*** a/contrib/postgres_fdw/postgres_fdw.h
--- b/contrib/postgres_fdw/postgres_fdw.h
***************
*** 20,25 ****
--- 20,37 ----
#include "libpq-fe.h"
+ typedef struct EstimateInfo
+ {
+ /* Estimated size and cost for a scan or join. */
+ double rows;
+ int width;
+ Cost startup_cost;
+ Cost total_cost;
+ /* Costs excluding costs for transferring data from the foreign server */
+ Cost rel_startup_cost;
+ Cost rel_total_cost;
+ } EstimateInfo;
+
/*
* FDW-specific planner information kept in RelOptInfo.fdw_private for a
* postgres_fdw foreign table. For a baserel, this struct is created by
*************** typedef struct PgFdwRelationInfo
*** 43,48 ****
--- 55,74 ----
List *remote_conds;
List *local_conds;
+ /*
+ * HAVING clauses.
+ *
+ * As for postgresGetForeignUpperPaths(), remote_conds / local_conds can
+ * be used for the HAVING clause because the remote_conds / local_conds of
+ * the input relation can be used for the WHERE clause. However, separate
+ * fields are needed for postgresGetForeignJoinPaths() because remote join
+ * can evaluate both WHERE and HAVING clause if it's grouped, and it does
+ * not use the concept of upper path. To be consistent, we always store
+ * the HAVING expressions into the following fields.
+ */
+ List *remote_conds_upper;
+ List *local_conds_upper;
+
/* Actual remote restriction clauses for scan (sans RestrictInfos) */
List *final_remote_exprs;
*************** typedef struct PgFdwRelationInfo
*** 56,69 ****
/* Selectivity of join conditions */
Selectivity joinclause_sel;
! /* Estimated size and cost for a scan or join. */
! double rows;
! int width;
! Cost startup_cost;
! Cost total_cost;
! /* Costs excluding costs for transferring data from the foreign server */
! Cost rel_startup_cost;
! Cost rel_total_cost;
/* Options extracted from catalogs. */
bool use_remote_estimate;
--- 82,90 ----
/* Selectivity of join conditions */
Selectivity joinclause_sel;
! /* Estimates for both plain and grouped relation. */
! EstimateInfo est_plain;
! EstimateInfo est_grouped;
/* Options extracted from catalogs. */
bool use_remote_estimate;
*************** typedef struct PgFdwRelationInfo
*** 92,98 ****
/* joinclauses contains only JOIN/ON conditions for an outer join */
List *joinclauses; /* List of RestrictInfo */
! /* Grouping information */
List *grouped_tlist;
/* Subquery information */
--- 113,121 ----
/* joinclauses contains only JOIN/ON conditions for an outer join */
List *joinclauses; /* List of RestrictInfo */
! /*
! * The targetlist used to construct the remote query.
! */
List *grouped_tlist;
/* Subquery information */
*************** extern unsigned int GetCursorNumber(PGco
*** 121,126 ****
--- 144,150 ----
extern unsigned int GetPrepStmtNumber(PGconn *conn);
extern PGresult *pgfdw_get_result(PGconn *conn, const char *query);
extern PGresult *pgfdw_exec_query(PGconn *conn, const char *query);
+ extern void pgfdw_send_query(PGconn *conn, const char *query);
extern void pgfdw_report_error(int elevel, PGresult *res, PGconn *conn,
bool clear, const char *sql);
*************** extern Expr *find_em_expr_for_rel(Equiva
*** 174,181 ****
extern List *build_tlist_to_deparse(RelOptInfo *foreignrel);
extern void deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root,
RelOptInfo *foreignrel, List *tlist,
! List *remote_conds, List *pathkeys, bool is_subquery,
! List **retrieved_attrs, List **params_list);
extern const char *get_jointype_name(JoinType jointype);
/* in shippable.c */
--- 198,207 ----
extern List *build_tlist_to_deparse(RelOptInfo *foreignrel);
extern void deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root,
RelOptInfo *foreignrel, List *tlist,
! List *remote_conds, List *order_by_exprs,
! List *pathkeys, bool is_subquery,
! List **retrieved_attrs, List **params_list,
! bool grouping);
extern const char *get_jointype_name(JoinType jointype);
/* in shippable.c */
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
new file mode 100644
index 3c3c5c7..3fc974f
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
*************** SELECT t1.a,t1.b FROM fprt1 t1, LATERAL
*** 1835,1837 ****
--- 1835,1902 ----
SELECT t1.a,t1.b FROM fprt1 t1, LATERAL (SELECT t2.a, t2.b FROM fprt2 t2 WHERE t1.a = t2.b AND t1.b = t2.a) q WHERE t1.a%25 = 0 ORDER BY 1,2;
RESET enable_partition_wise_join;
+
+ -- ===================================================================
+ -- test aggregation of base relation or join
+ -- ===================================================================
+ CREATE TABLE customers(id int primary key, name varchar);
+ INSERT INTO customers VALUES (0, 'me'), (1, 'you'), (2, 'he'), (3, 'she');
+ CREATE TABLE products(id int, price float4) PARTITION BY RANGE (id);
+ CREATE TABLE products_0 (LIKE products);
+ INSERT INTO products_0(id, price) SELECT g.i, trunc(100 * random()) /
+ 10 + 1 FROM generate_series(1, 1000) AS g(i);
+ CREATE TABLE products_1 (LIKE products);
+ INSERT INTO products_1(id, price) SELECT g.i, trunc(100 * random()) /
+ 10 + 1 FROM generate_series(1001, 2000) AS g(i);
+ CREATE TABLE orders(customer_id int, product_id int) PARTITION BY RANGE
+ (product_id);
+ CREATE TABLE orders_0 (LIKE orders);
+ INSERT INTO orders_0(customer_id, product_id) SELECT trunc(2 * random()),
+ trunc(1000 * random()) FROM generate_series(1, 5000);
+ CREATE TABLE orders_1 (LIKE orders);
+ INSERT INTO orders_1(customer_id, product_id)
+ SELECT trunc(2 * random()) + 2, trunc(1000 * random()) + 1001 FROM
+ generate_series(1, 5000);
+ CREATE FOREIGN TABLE f_orders_0 PARTITION OF orders FOR VALUES FROM (1) TO
+ (1001) SERVER loopback OPTIONS (table_name 'orders_0');
+ CREATE FOREIGN TABLE f_orders_1 PARTITION OF orders FOR VALUES FROM (1001) TO
+ (2001) SERVER loopback OPTIONS (table_name 'orders_1');
+ CREATE FOREIGN TABLE f_products_0 PARTITION OF products FOR VALUES FROM (1) TO
+ (1001) SERVER loopback OPTIONS (table_name 'products_0');
+ CREATE FOREIGN TABLE f_products_1 PARTITION OF products FOR VALUES FROM (1001)
+ TO (2001) SERVER loopback OPTIONS (table_name 'products_1');
+ ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'true');
+ ALTER SERVER loopback2 OPTIONS (ADD use_remote_estimate 'true');
+ ANALYZE;
+
+ SET enable_agg_pushdown TO on;
+ SET enable_partition_wise_join TO on;
+
+ -- Perform the aggregation on partitions of "orders" , join the result to the
+ -- "customers" local table and finalize the aggregation.
+ EXPLAIN (VERBOSE on, COSTS off)
+ SELECT c.name, count(*)
+ FROM customers AS c, orders AS o
+ WHERE c.id = o.customer_id
+ GROUP BY c.id;
+
+ -- Perform partition-wise join of "orders" and "products", aggregate the
+ -- result at partition level, append the outputs of partitions and join it to
+ -- the "customers" local table.
+ EXPLAIN (VERBOSE on, COSTS off)
+ SELECT p.id, c.name, sum(p.price)
+ FROM customers AS c, orders AS o, products AS p
+ WHERE c.id = o.customer_id AND p.id = o.product_id
+ GROUP BY p.id, c.id
+ ORDER BY 3;
+
+ -- The final aggregation is not necessary if the aggregated output of
+ -- partitions contains distinct set of grouping keys. Moreover, skip sort of
+ -- the result if the output of per-partition queries has the desired ordering
+ -- and so the sets can be merge-appended.
+ EXPLAIN (VERBOSE on, COSTS off)
+ SELECT p.id, sum(p.price)
+ FROM orders AS o, products AS p
+ WHERE p.id = o.product_id
+ GROUP BY p.id
+ ORDER BY 2 DESC;
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
new file mode 100644
index f09647e..734940a
*** a/src/backend/executor/execExpr.c
--- b/src/backend/executor/execExpr.c
*************** ExecInitExprRec(Expr *node, ExprState *s
*** 809,815 ****
* there.)
*/
if (IsA(gvar->gvexpr, Aggref))
-
ExecInitExprRec((Expr *) gvar->agg_partial, state,
resv, resnull);
else
--- 809,814 ----
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
new file mode 100644
index f95c913..3b709a3
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** set_tablesample_rel_pathlist(PlannerInfo
*** 986,996 ****
static void
set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
/* Mark rel with estimated output rows, width, etc */
! set_foreign_size_estimates(root, rel);
! /* Let FDW adjust the size estimates, if it can */
! rel->fdwroutine->GetForeignRelSize(root, rel, rte->relid);
/* ... but do not let it set the rows estimate to zero */
rel->rows = clamp_row_est(rel->rows);
--- 986,1014 ----
static void
set_foreign_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
+ Relids required_outer = rel->lateral_relids;
+
/* Mark rel with estimated output rows, width, etc */
! set_foreign_size_estimates(root, rel, false);
! /*
! * Let FDW adjust the size estimates, if it can.
! *
! * For the grouped relation it only makes sense if set_foreign_pathlist is
! * supposed to handle it, see below.
! */
! rel->fdwroutine->GetForeignRelSize(root, rel, rte->relid, false);
! if (rel->gpi != NULL && rel->gpi->target != NULL &&
! required_outer == NULL)
! {
! rel->fdwroutine->GetForeignRelSize(root, rel, rte->relid, true);
!
! /*
! * XXX Should cost_qual_eval be called separate so we don't repeat it
! * here for essentially identical baserestrictinfo?
! */
! set_foreign_size_estimates(root, rel, true);
! }
/* ... but do not let it set the rows estimate to zero */
rel->rows = clamp_row_est(rel->rows);
*************** set_foreign_size(PlannerInfo *root, RelO
*** 1003,1010 ****
static void
set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
/* Call the FDW's GetForeignPaths function to generate path(s) */
! rel->fdwroutine->GetForeignPaths(root, rel, rte->relid);
}
/*
--- 1021,1038 ----
static void
set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
+ Relids required_outer = rel->lateral_relids;
+
/* Call the FDW's GetForeignPaths function to generate path(s) */
! rel->fdwroutine->GetForeignPaths(root, rel, rte->relid, false);
!
! /*
! * Create grouped paths if grouped target exists. Do nothing if outer
! * relations are needed --- that could cause parameterized (and therefore
! * repeated) grouping.
! */
! if (rel->gpi != NULL && rel->gpi->target && required_outer == NULL)
! rel->fdwroutine->GetForeignPaths(root, rel, rte->relid, true);
}
/*
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1586,1591 ****
--- 1614,1620 ----
foreach(l, live_childrels)
{
RelOptInfo *childrel = lfirst(l);
+ List *childpaths;
ListCell *lcp;
Path *cheapest_partial_path = NULL;
*************** add_paths_to_append_rel(PlannerInfo *roo
*** 1698,1704 ****
* heuristic to indicate which sort orderings and parameterizations we
* should build Append and MergeAppend paths for.
*/
! foreach(lcp, childrel->pathlist)
{
Path *childpath = (Path *) lfirst(lcp);
List *childkeys = childpath->pathkeys;
--- 1727,1743 ----
* heuristic to indicate which sort orderings and parameterizations we
* should build Append and MergeAppend paths for.
*/
! childpaths = childrel->pathlist;
!
! /*
! * Extra orderings may be available for grouped paths, i.e. ordered by
! * aggregate. (At least ForeignPath can generate these.)
! */
! if (childrel->gpi != NULL && childrel->gpi->pathlist != NIL)
! childpaths = list_concat(list_copy(childpaths),
! childrel->gpi->pathlist);
!
! foreach(lcp, childpaths)
{
Path *childpath = (Path *) lfirst(lcp);
List *childkeys = childpath->pathkeys;
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
new file mode 100644
index 9f489c3..f28f5fc
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
*************** set_namedtuplestore_size_estimates(Plann
*** 5037,5043 ****
* already.
*/
void
! set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel)
{
/* Should only be applied to base relations */
Assert(rel->relid > 0);
--- 5037,5043 ----
* already.
*/
void
! set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel, bool grouped)
{
/* Should only be applied to base relations */
Assert(rel->relid > 0);
*************** set_foreign_size_estimates(PlannerInfo *
*** 5046,5052 ****
cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
! set_rel_width(root, rel, rel->reltarget, false);
}
--- 5046,5064 ----
cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
! if (!grouped)
! set_rel_width(root, rel, rel->reltarget, false);
! else
! {
! Assert(rel->gpi != NULL);
!
! /*
! * If the relation can produce grouped path, estimate width and costs
! * of the corresponding target.
! */
! if (rel->gpi->target)
! set_rel_width(root, rel, rel->gpi->target, true);
! }
}
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
new file mode 100644
index 883d8a1..a678bc2
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 343,351 ****
*/
if (joinrel->fdwroutine &&
joinrel->fdwroutine->GetForeignJoinPaths)
joinrel->fdwroutine->GetForeignJoinPaths(root, joinrel,
outerrel, innerrel,
! jointype, &extra);
/*
* 6. Finally, give extensions a chance to manipulate the path list.
--- 343,357 ----
*/
if (joinrel->fdwroutine &&
joinrel->fdwroutine->GetForeignJoinPaths)
+ {
joinrel->fdwroutine->GetForeignJoinPaths(root, joinrel,
outerrel, innerrel,
! jointype, &extra, false);
! if (joinrel->gpi != NULL && joinrel->gpi->target != NULL)
! joinrel->fdwroutine->GetForeignJoinPaths(root, joinrel,
! outerrel, innerrel,
! jointype, &extra, true);
! }
/*
* 6. Finally, give extensions a chance to manipulate the path list.
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
new file mode 100644
index b11afd9..ba63830
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
*************** static Plan *prepare_sort_from_pathkeys(
*** 251,256 ****
--- 251,257 ----
static EquivalenceMember *find_ec_member_for_tle(EquivalenceClass *ec,
TargetEntry *tle,
Relids relids);
+ static TargetEntry *get_aggref_from_tle(TargetEntry *tle);
static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
Relids relids);
static Sort *make_sort_from_groupcols(List *groupcls,
*************** prepare_sort_from_pathkeys(Plan *lefttre
*** 5655,5660 ****
--- 5656,5664 ----
tle = get_tle_by_resno(tlist, reqColIdx[numsortkeys]);
if (tle)
{
+ /* Handle special cases of sorting by aggregate. */
+ tle = get_aggref_from_tle(tle);
+
em = find_ec_member_for_tle(ec, tle, relids);
if (em)
{
*************** prepare_sort_from_pathkeys(Plan *lefttre
*** 5686,5691 ****
--- 5690,5698 ----
foreach(j, tlist)
{
tle = (TargetEntry *) lfirst(j);
+
+ /* Handle special cases of sorting by aggregate. */
+ tle = get_aggref_from_tle(tle);
em = find_ec_member_for_tle(ec, tle, relids);
if (em)
{
*************** find_ec_member_for_tle(EquivalenceClass
*** 5859,5864 ****
--- 5866,5893 ----
}
/*
+ * Get Aggref from target entry if it's wrapped in GroupedVar.
+ */
+ static TargetEntry *
+ get_aggref_from_tle(TargetEntry *tle)
+ {
+ if (IsA(tle->expr, GroupedVar))
+ {
+ GroupedVar *gvar = castNode(GroupedVar, tle->expr);
+
+ if (IsA(gvar->gvexpr, Aggref))
+ {
+ Aggref *aggref = castNode(Aggref, gvar->gvexpr);
+
+ tle = flatCopyTargetEntry(tle);
+ tle->expr = (Expr *) aggref;
+ }
+ }
+
+ return tle;
+ }
+
+ /*
* make_sort_from_pathkeys
* Create sort plan to sort according to given pathkeys
*
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
new file mode 100644
index 502c725..02c6c61
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
*************** set_foreignscan_references(PlannerInfo *
*** 1199,1228 ****
if (fscan->fdw_scan_tlist != NIL || fscan->scan.scanrelid == 0)
{
/*
* Adjust tlist, qual, fdw_exprs, fdw_recheck_quals to reference
! * foreign scan tuple
*/
! indexed_tlist *itlist = build_tlist_index(fscan->fdw_scan_tlist);
!
fscan->scan.plan.targetlist = (List *)
fix_upper_expr(root,
! (Node *) fscan->scan.plan.targetlist,
itlist,
INDEX_VAR,
rtoffset);
fscan->scan.plan.qual = (List *)
fix_upper_expr(root,
(Node *) fscan->scan.plan.qual,
itlist,
INDEX_VAR,
rtoffset);
fscan->fdw_exprs = (List *)
fix_upper_expr(root,
(Node *) fscan->fdw_exprs,
itlist,
INDEX_VAR,
rtoffset);
fscan->fdw_recheck_quals = (List *)
fix_upper_expr(root,
(Node *) fscan->fdw_recheck_quals,
--- 1199,1242 ----
if (fscan->fdw_scan_tlist != NIL || fscan->scan.scanrelid == 0)
{
+ List *tlist_tmp;
+ indexed_tlist *itlist = build_tlist_index(fscan->fdw_scan_tlist);
+
/*
* Adjust tlist, qual, fdw_exprs, fdw_recheck_quals to reference
! * foreign scan tuple.
! *
! * Since fdw_scan_tlist contains Aggrefs instead of GroupVars (this is
! * convenient for deparsing), the Aggrefs also need to be restored in
! * the referencing lists.
! *
! * XXX Consider thoroughly where tlist_tmp really needs to be used,
! * i.e. which of the lists can / cannot contain aggregates.
*/
! tlist_tmp =
! replace_grouped_vars_with_aggrefs(root,
! fscan->scan.plan.targetlist);
fscan->scan.plan.targetlist = (List *)
fix_upper_expr(root,
! (Node *) tlist_tmp,
itlist,
INDEX_VAR,
rtoffset);
+
fscan->scan.plan.qual = (List *)
fix_upper_expr(root,
(Node *) fscan->scan.plan.qual,
itlist,
INDEX_VAR,
rtoffset);
+
fscan->fdw_exprs = (List *)
fix_upper_expr(root,
(Node *) fscan->fdw_exprs,
itlist,
INDEX_VAR,
rtoffset);
+
fscan->fdw_recheck_quals = (List *)
fix_upper_expr(root,
(Node *) fscan->fdw_recheck_quals,
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
new file mode 100644
index 04e43cc..16d6082
*** a/src/include/foreign/fdwapi.h
--- b/src/include/foreign/fdwapi.h
*************** struct ExplainState;
*** 26,36 ****
typedef void (*GetForeignRelSize_function) (PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid);
typedef void (*GetForeignPaths_function) (PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid);
typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
RelOptInfo *baserel,
--- 26,38 ----
typedef void (*GetForeignRelSize_function) (PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid,
! bool grouped);
typedef void (*GetForeignPaths_function) (PlannerInfo *root,
RelOptInfo *baserel,
! Oid foreigntableid,
! bool grouped);
typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
RelOptInfo *baserel,
*************** typedef void (*GetForeignJoinPaths_funct
*** 57,63 ****
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra);
typedef void (*GetForeignUpperPaths_function) (PlannerInfo *root,
UpperRelationKind stage,
--- 59,66 ----
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
! JoinPathExtraData *extra,
! bool grouped);
typedef void (*GetForeignUpperPaths_function) (PlannerInfo *root,
UpperRelationKind stage,
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
new file mode 100644
index 12d8134..9956e0f
*** a/src/include/optimizer/cost.h
--- b/src/include/optimizer/cost.h
*************** extern void set_cte_size_estimates(Plann
*** 194,200 ****
double cte_rows);
extern void set_tablefunc_size_estimates(PlannerInfo *root, RelOptInfo *rel);
extern void set_namedtuplestore_size_estimates(PlannerInfo *root, RelOptInfo *rel);
! extern void set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel);
extern PathTarget *set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target);
extern double compute_bitmap_pages(PlannerInfo *root, RelOptInfo *baserel,
Path *bitmapqual, int loop_count, Cost *cost, double *tuple);
--- 194,201 ----
double cte_rows);
extern void set_tablefunc_size_estimates(PlannerInfo *root, RelOptInfo *rel);
extern void set_namedtuplestore_size_estimates(PlannerInfo *root, RelOptInfo *rel);
! extern void set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel,
! bool grouped);
extern PathTarget *set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target);
extern double compute_bitmap_pages(PlannerInfo *root, RelOptInfo *baserel,
Path *bitmapqual, int loop_count, Cost *cost, double *tuple);
agg_pushdown_v5/11_readme.diff
diff --git a/src/backend/optimizer/README b/src/backend/optimizer/README
new file mode 100644
index 1e4084d..3994394
*** a/src/backend/optimizer/README
--- b/src/backend/optimizer/README
*************** plan as possible. Expanding the range o
*** 1076,1081 ****
--- 1076,1116 ----
pushed below the Gather (and costing them accurately) is likely to keep us
busy for a long time to come.
+ Grouped Paths
+ -------------
+
+ If both query semantics and path type allow, an existing path can be subject
+ to partial aggregation (ie aggregation without calling the aggfinalfn
+ function) and stored as a "grouped path" in a separate pathlist. Both base
+ relation and join paths can be turned into a grouped path this way. If the
+ source path was partial (in terms of parallel processing, see above), then the
+ grouped path is called "partial grouped path".
+
+ If a grouped path appears at the top of the join tree, the transient state
+ values are aggregated and the aggfinalfn function is called for each
+ aggregate. Likewise, if the top-level join produces a partial grouped path, we
+ first apply Gather plan on it and then we finalize the aggregation in the same
+ way.
+
+ Besides being a result of aggregation, (partial) grouped path can be created
+ by joining an existing grouped path to an ordinary (non-grouped) path. This
+ technique can result in duplication of grouping keys, which are essentially
+ unique in the output of aggregation. The final aggregation takes these
+ duplicities into account.
+
+ In contrast, join of two grouped paths is not supported as there doesn't seem
+ to be an interesting use case for it. If we want to support this kind of join
+ in the future, additional attention needs to be paid to the duplication of
+ grouping keys mentioned above. For example, if scan of table A is joined to a
+ grouped scan of table B, then the cardinality of grouping keys of table B
+ present in (non-grouped) table A determines how many times does each
+ aggregation transient state value of B appear on the input of the final
+ aggregation path. However if A is grouped too, the number of those key values
+ on the A side can get lower and thus affect the input of the final aggregation
+ path. When compensating for this effect, we'd have to count input values of
+ each group during the aggregation of A and "multiply" the corresponding
+ transient state values of B accordingly.
+
Partition-wise joins
--------------------
A join between two similarly partitioned tables can be broken down into joins
view thread (59+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected]
Subject: Re: [HACKERS] WIP: Aggregation push-down
In-Reply-To: <18007.1513957437@localhost>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox