Commit 3fc6e2d7 authored by Tom Lane's avatar Tom Lane

Make the upper part of the planner work by generating and comparing Paths.

I've been saying we needed to do this for more than five years, and here it
finally is.  This patch removes the ever-growing tangle of spaghetti logic
that grouping_planner() used to use to try to identify the best plan for
post-scan/join query steps.  Now, there is (nearly) independent
consideration of each execution step, and entirely separate construction of
Paths to represent each of the possible ways to do that step.  We choose
the best Path or set of Paths using the same add_path() logic that's been
used inside query_planner() for years.

In addition, this patch removes the old restriction that subquery_planner()
could return only a single Plan.  It now returns a RelOptInfo containing a
set of Paths, just as query_planner() does, and the parent query level can
use each of those Paths as the basis of a SubqueryScanPath at its level.
This allows finding some optimizations that we missed before, wherein a
subquery was capable of returning presorted data and thereby avoiding a
sort in the parent level, making the overall cost cheaper even though
delivering sorted output was not the cheapest plan for the subquery in
isolation.  (A couple of regression test outputs change in consequence of
that.  However, there is very little change in visible planner behavior
overall, because the point of this patch is not to get immediate planning
benefits but to create the infrastructure for future improvements.)

There is a great deal left to do here.  This patch unblocks a lot of
planner work that was basically impractical in the old code structure,
such as allowing FDWs to implement remote aggregation, or rewriting
plan_set_operations() to allow consideration of multiple implementation
orders for set operations.  (The latter will likely require a full
rewrite of plan_set_operations(); what I've done here is only to fix it
to return Paths not Plans.)  I have also left unfinished some localized
refactoring in createplan.c and planner.c, because it was not necessary
to get this patch to a working state.

Thanks to Robert Haas, David Rowley, and Amit Kapila for review.
parent b642e50a
...@@ -1316,6 +1316,40 @@ GetForeignServerByName(const char *name, bool missing_ok); ...@@ -1316,6 +1316,40 @@ GetForeignServerByName(const char *name, bool missing_ok);
(<literal>extra-&gt;restrictlist</>). (<literal>extra-&gt;restrictlist</>).
</para> </para>
<para>
An FDW might additionally support direct execution of some plan actions
that are above the level of scans and joins, such as grouping or
aggregation. To offer such options, the FDW should generate paths
(probably ForeignPaths or CustomPaths) and insert them into the
appropriate <firstterm>upper relation</>. For example, a path
representing remote aggregation should be inserted into the relation
obtained from <literal>fetch_upper_rel(root, UPPERREL_GROUP_AGG,
NULL)</>, using <function>add_path</>. This path will be compared on a
cost basis with local aggregation performed by reading a simple scan path
for the foreign relation (note that such a path must also be supplied,
else there will be an error at plan time). If the remote-aggregation
path wins, which it usually would, it will be converted into a plan in
the usual way, by calling <function>GetForeignPlan</>.
</para>
<para>
<function>PlanForeignModify</> and the other callbacks described in
<xref linkend="fdw-callbacks-update"> are designed around the assumption
that the foreign relation will be scanned in the usual way and then
individual row updates will be driven by a local <literal>ModifyTable</>
plan node. This approach is necessary for the general case where an
update requires reading local tables as well as foreign tables.
However, if the operation could be executed entirely by the foreign
server, the FDW could generate a path representing that and insert it
into the <literal>UPPERREL_FINAL</> upper relation, where it would
compete against the <literal>ModifyTable</> approach. This approach
could also be used to implement remote <literal>SELECT FOR UPDATE</>,
rather than using the row locking callbacks described in
<xref linkend="fdw-callbacks-row-locking">. Keep in mind that a path
inserted into <literal>UPPERREL_FINAL</> is responsible for
implementing <emphasis>all</> behavior of the query.
</para>
<para> <para>
When planning an <command>UPDATE</> or <command>DELETE</>, When planning an <command>UPDATE</> or <command>DELETE</>,
<function>PlanForeignModify</> can look up the <structname>RelOptInfo</> <function>PlanForeignModify</> can look up the <structname>RelOptInfo</>
......
...@@ -407,17 +407,20 @@ ExecSupportsMarkRestore(Path *pathnode) ...@@ -407,17 +407,20 @@ ExecSupportsMarkRestore(Path *pathnode)
case T_Result: case T_Result:
/* /*
* Although Result supports mark/restore if it has a child plan * Result supports mark/restore iff it has a child plan that does.
* that does, we presently come here only for ResultPath nodes, *
* which represent Result plans without a child plan. So there is * We have to be careful here because there is more than one Path
* nothing to recurse to and we can just say "false". (This means * type that can produce a Result plan node.
* that Result's support for mark/restore is in fact dead code. We
* keep it since it's not much code, and someday the planner might
* be smart enough to use it. That would require making this
* function smarter too, of course.)
*/ */
Assert(IsA(pathnode, ResultPath)); if (IsA(pathnode, ProjectionPath))
return false; return ExecSupportsMarkRestore(((ProjectionPath *) pathnode)->subpath);
else if (IsA(pathnode, MinMaxAggPath))
return false; /* childless Result */
else
{
Assert(IsA(pathnode, ResultPath));
return false; /* childless Result */
}
default: default:
break; break;
......
...@@ -867,9 +867,9 @@ _copyAgg(const Agg *from) ...@@ -867,9 +867,9 @@ _copyAgg(const Agg *from)
CopyPlanFields((const Plan *) from, (Plan *) newnode); CopyPlanFields((const Plan *) from, (Plan *) newnode);
COPY_SCALAR_FIELD(aggstrategy); COPY_SCALAR_FIELD(aggstrategy);
COPY_SCALAR_FIELD(numCols);
COPY_SCALAR_FIELD(combineStates); COPY_SCALAR_FIELD(combineStates);
COPY_SCALAR_FIELD(finalizeAggs); COPY_SCALAR_FIELD(finalizeAggs);
COPY_SCALAR_FIELD(numCols);
if (from->numCols > 0) if (from->numCols > 0)
{ {
COPY_POINTER_FIELD(grpColIdx, from->numCols * sizeof(AttrNumber)); COPY_POINTER_FIELD(grpColIdx, from->numCols * sizeof(AttrNumber));
......
...@@ -706,21 +706,19 @@ _outAgg(StringInfo str, const Agg *node) ...@@ -706,21 +706,19 @@ _outAgg(StringInfo str, const Agg *node)
_outPlanInfo(str, (const Plan *) node); _outPlanInfo(str, (const Plan *) node);
WRITE_ENUM_FIELD(aggstrategy, AggStrategy); WRITE_ENUM_FIELD(aggstrategy, AggStrategy);
WRITE_BOOL_FIELD(combineStates);
WRITE_BOOL_FIELD(finalizeAggs);
WRITE_INT_FIELD(numCols); WRITE_INT_FIELD(numCols);
appendStringInfoString(str, " :grpColIdx"); appendStringInfoString(str, " :grpColIdx");
for (i = 0; i < node->numCols; i++) for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %d", node->grpColIdx[i]); appendStringInfo(str, " %d", node->grpColIdx[i]);
WRITE_BOOL_FIELD(combineStates);
WRITE_BOOL_FIELD(finalizeAggs);
appendStringInfoString(str, " :grpOperators"); appendStringInfoString(str, " :grpOperators");
for (i = 0; i < node->numCols; i++) for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %u", node->grpOperators[i]); appendStringInfo(str, " %u", node->grpOperators[i]);
WRITE_LONG_FIELD(numGroups); WRITE_LONG_FIELD(numGroups);
WRITE_NODE_FIELD(groupingSets); WRITE_NODE_FIELD(groupingSets);
WRITE_NODE_FIELD(chain); WRITE_NODE_FIELD(chain);
} }
...@@ -1603,6 +1601,15 @@ _outPathInfo(StringInfo str, const Path *node) ...@@ -1603,6 +1601,15 @@ _outPathInfo(StringInfo str, const Path *node)
if (node->pathtarget != &(node->parent->reltarget)) if (node->pathtarget != &(node->parent->reltarget))
{ {
WRITE_NODE_FIELD(pathtarget->exprs); WRITE_NODE_FIELD(pathtarget->exprs);
if (node->pathtarget->sortgrouprefs)
{
int i;
appendStringInfoString(str, " :pathtarget->sortgrouprefs");
for (i = 0; i < list_length(node->pathtarget->exprs); i++)
appendStringInfo(str, " %u",
node->pathtarget->sortgrouprefs[i]);
}
WRITE_FLOAT_FIELD(pathtarget->cost.startup, "%.2f"); WRITE_FLOAT_FIELD(pathtarget->cost.startup, "%.2f");
WRITE_FLOAT_FIELD(pathtarget->cost.per_tuple, "%.2f"); WRITE_FLOAT_FIELD(pathtarget->cost.per_tuple, "%.2f");
WRITE_INT_FIELD(pathtarget->width); WRITE_INT_FIELD(pathtarget->width);
...@@ -1703,6 +1710,16 @@ _outTidPath(StringInfo str, const TidPath *node) ...@@ -1703,6 +1710,16 @@ _outTidPath(StringInfo str, const TidPath *node)
WRITE_NODE_FIELD(tidquals); WRITE_NODE_FIELD(tidquals);
} }
static void
_outSubqueryScanPath(StringInfo str, const SubqueryScanPath *node)
{
WRITE_NODE_TYPE("SUBQUERYSCANPATH");
_outPathInfo(str, (const Path *) node);
WRITE_NODE_FIELD(subpath);
}
static void static void
_outForeignPath(StringInfo str, const ForeignPath *node) _outForeignPath(StringInfo str, const ForeignPath *node)
{ {
...@@ -1793,6 +1810,174 @@ _outGatherPath(StringInfo str, const GatherPath *node) ...@@ -1793,6 +1810,174 @@ _outGatherPath(StringInfo str, const GatherPath *node)
WRITE_BOOL_FIELD(single_copy); WRITE_BOOL_FIELD(single_copy);
} }
static void
_outProjectionPath(StringInfo str, const ProjectionPath *node)
{
WRITE_NODE_TYPE("PROJECTIONPATH");
_outPathInfo(str, (const Path *) node);
WRITE_NODE_FIELD(subpath);
}
static void
_outSortPath(StringInfo str, const SortPath *node)
{
WRITE_NODE_TYPE("SORTPATH");
_outPathInfo(str, (const Path *) node);
WRITE_NODE_FIELD(subpath);
}
static void
_outGroupPath(StringInfo str, const GroupPath *node)
{
WRITE_NODE_TYPE("GROUPPATH");
_outPathInfo(str, (const Path *) node);
WRITE_NODE_FIELD(subpath);
WRITE_NODE_FIELD(groupClause);
WRITE_NODE_FIELD(qual);
}
static void
_outUpperUniquePath(StringInfo str, const UpperUniquePath *node)
{
WRITE_NODE_TYPE("UPPERUNIQUEPATH");
_outPathInfo(str, (const Path *) node);
WRITE_NODE_FIELD(subpath);
WRITE_INT_FIELD(numkeys);
}
static void
_outAggPath(StringInfo str, const AggPath *node)
{
WRITE_NODE_TYPE("AGGPATH");
_outPathInfo(str, (const Path *) node);
WRITE_NODE_FIELD(subpath);
WRITE_ENUM_FIELD(aggstrategy, AggStrategy);
WRITE_FLOAT_FIELD(numGroups, "%.0f");
WRITE_NODE_FIELD(groupClause);
WRITE_NODE_FIELD(qual);
}
static void
_outGroupingSetsPath(StringInfo str, const GroupingSetsPath *node)
{
WRITE_NODE_TYPE("GROUPINGSETSPATH");
_outPathInfo(str, (const Path *) node);
WRITE_NODE_FIELD(subpath);
/* we don't bother to print groupColIdx */
WRITE_NODE_FIELD(rollup_groupclauses);
WRITE_NODE_FIELD(rollup_lists);
WRITE_NODE_FIELD(qual);
}
static void
_outMinMaxAggPath(StringInfo str, const MinMaxAggPath *node)
{
WRITE_NODE_TYPE("MINMAXAGGPATH");
_outPathInfo(str, (const Path *) node);
WRITE_NODE_FIELD(mmaggregates);
WRITE_NODE_FIELD(quals);
}
static void
_outWindowAggPath(StringInfo str, const WindowAggPath *node)
{
WRITE_NODE_TYPE("WINDOWAGGPATH");
_outPathInfo(str, (const Path *) node);
WRITE_NODE_FIELD(subpath);
WRITE_NODE_FIELD(winclause);
WRITE_NODE_FIELD(winpathkeys);
}
static void
_outSetOpPath(StringInfo str, const SetOpPath *node)
{
WRITE_NODE_TYPE("SETOPPATH");
_outPathInfo(str, (const Path *) node);
WRITE_NODE_FIELD(subpath);
WRITE_ENUM_FIELD(cmd, SetOpCmd);
WRITE_ENUM_FIELD(strategy, SetOpStrategy);
WRITE_NODE_FIELD(distinctList);
WRITE_INT_FIELD(flagColIdx);
WRITE_INT_FIELD(firstFlag);
WRITE_FLOAT_FIELD(numGroups, "%.0f");
}
static void
_outRecursiveUnionPath(StringInfo str, const RecursiveUnionPath *node)
{
WRITE_NODE_TYPE("RECURSIVEUNIONPATH");
_outPathInfo(str, (const Path *) node);
WRITE_NODE_FIELD(leftpath);
WRITE_NODE_FIELD(rightpath);
WRITE_NODE_FIELD(distinctList);
WRITE_INT_FIELD(wtParam);
WRITE_FLOAT_FIELD(numGroups, "%.0f");
}
static void
_outLockRowsPath(StringInfo str, const LockRowsPath *node)
{
WRITE_NODE_TYPE("LOCKROWSPATH");
_outPathInfo(str, (const Path *) node);
WRITE_NODE_FIELD(subpath);
WRITE_NODE_FIELD(rowMarks);
WRITE_INT_FIELD(epqParam);
}
static void
_outModifyTablePath(StringInfo str, const ModifyTablePath *node)
{
WRITE_NODE_TYPE("MODIFYTABLEPATH");
_outPathInfo(str, (const Path *) node);
WRITE_ENUM_FIELD(operation, CmdType);
WRITE_BOOL_FIELD(canSetTag);
WRITE_UINT_FIELD(nominalRelation);
WRITE_NODE_FIELD(resultRelations);
WRITE_NODE_FIELD(subpaths);
WRITE_NODE_FIELD(subroots);
WRITE_NODE_FIELD(withCheckOptionLists);
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(onconflict);
WRITE_INT_FIELD(epqParam);
}
static void
_outLimitPath(StringInfo str, const LimitPath *node)
{
WRITE_NODE_TYPE("LIMITPATH");
_outPathInfo(str, (const Path *) node);
WRITE_NODE_FIELD(subpath);
WRITE_NODE_FIELD(limitOffset);
WRITE_NODE_FIELD(limitCount);
}
static void static void
_outNestPath(StringInfo str, const NestPath *node) _outNestPath(StringInfo str, const NestPath *node)
{ {
...@@ -1881,6 +2066,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) ...@@ -1881,6 +2066,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
WRITE_NODE_FIELD(window_pathkeys); WRITE_NODE_FIELD(window_pathkeys);
WRITE_NODE_FIELD(distinct_pathkeys); WRITE_NODE_FIELD(distinct_pathkeys);
WRITE_NODE_FIELD(sort_pathkeys); WRITE_NODE_FIELD(sort_pathkeys);
WRITE_NODE_FIELD(processed_tlist);
WRITE_NODE_FIELD(minmax_aggs); WRITE_NODE_FIELD(minmax_aggs);
WRITE_FLOAT_FIELD(total_table_pages, "%.0f"); WRITE_FLOAT_FIELD(total_table_pages, "%.0f");
WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(tuple_fraction, "%.4f");
...@@ -1910,6 +2096,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) ...@@ -1910,6 +2096,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node)
WRITE_BOOL_FIELD(consider_param_startup); WRITE_BOOL_FIELD(consider_param_startup);
WRITE_BOOL_FIELD(consider_parallel); WRITE_BOOL_FIELD(consider_parallel);
WRITE_NODE_FIELD(reltarget.exprs); WRITE_NODE_FIELD(reltarget.exprs);
/* reltarget.sortgrouprefs is never interesting, at present anyway */
WRITE_FLOAT_FIELD(reltarget.cost.startup, "%.2f"); WRITE_FLOAT_FIELD(reltarget.cost.startup, "%.2f");
WRITE_FLOAT_FIELD(reltarget.cost.per_tuple, "%.2f"); WRITE_FLOAT_FIELD(reltarget.cost.per_tuple, "%.2f");
WRITE_INT_FIELD(reltarget.width); WRITE_INT_FIELD(reltarget.width);
...@@ -1933,7 +2120,6 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) ...@@ -1933,7 +2120,6 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node)
WRITE_UINT_FIELD(pages); WRITE_UINT_FIELD(pages);
WRITE_FLOAT_FIELD(tuples, "%.0f"); WRITE_FLOAT_FIELD(tuples, "%.0f");
WRITE_FLOAT_FIELD(allvisfrac, "%.6f"); WRITE_FLOAT_FIELD(allvisfrac, "%.6f");
WRITE_NODE_FIELD(subplan);
WRITE_NODE_FIELD(subroot); WRITE_NODE_FIELD(subroot);
WRITE_NODE_FIELD(subplan_params); WRITE_NODE_FIELD(subplan_params);
WRITE_OID_FIELD(serverid); WRITE_OID_FIELD(serverid);
...@@ -3331,6 +3517,9 @@ _outNode(StringInfo str, const void *obj) ...@@ -3331,6 +3517,9 @@ _outNode(StringInfo str, const void *obj)
case T_TidPath: case T_TidPath:
_outTidPath(str, obj); _outTidPath(str, obj);
break; break;
case T_SubqueryScanPath:
_outSubqueryScanPath(str, obj);
break;
case T_ForeignPath: case T_ForeignPath:
_outForeignPath(str, obj); _outForeignPath(str, obj);
break; break;
...@@ -3355,6 +3544,45 @@ _outNode(StringInfo str, const void *obj) ...@@ -3355,6 +3544,45 @@ _outNode(StringInfo str, const void *obj)
case T_GatherPath: case T_GatherPath:
_outGatherPath(str, obj); _outGatherPath(str, obj);
break; break;
case T_ProjectionPath:
_outProjectionPath(str, obj);
break;
case T_SortPath:
_outSortPath(str, obj);
break;
case T_GroupPath:
_outGroupPath(str, obj);
break;
case T_UpperUniquePath:
_outUpperUniquePath(str, obj);
break;
case T_AggPath:
_outAggPath(str, obj);
break;
case T_GroupingSetsPath:
_outGroupingSetsPath(str, obj);
break;
case T_MinMaxAggPath:
_outMinMaxAggPath(str, obj);
break;
case T_WindowAggPath:
_outWindowAggPath(str, obj);
break;
case T_SetOpPath:
_outSetOpPath(str, obj);
break;
case T_RecursiveUnionPath:
_outRecursiveUnionPath(str, obj);
break;
case T_LockRowsPath:
_outLockRowsPath(str, obj);
break;
case T_ModifyTablePath:
_outModifyTablePath(str, obj);
break;
case T_LimitPath:
_outLimitPath(str, obj);
break;
case T_NestPath: case T_NestPath:
_outNestPath(str, obj); _outNestPath(str, obj);
break; break;
......
...@@ -1997,10 +1997,10 @@ _readAgg(void) ...@@ -1997,10 +1997,10 @@ _readAgg(void)
ReadCommonPlan(&local_node->plan); ReadCommonPlan(&local_node->plan);
READ_ENUM_FIELD(aggstrategy, AggStrategy); READ_ENUM_FIELD(aggstrategy, AggStrategy);
READ_INT_FIELD(numCols);
READ_ATTRNUMBER_ARRAY(grpColIdx, local_node->numCols);
READ_BOOL_FIELD(combineStates); READ_BOOL_FIELD(combineStates);
READ_BOOL_FIELD(finalizeAggs); READ_BOOL_FIELD(finalizeAggs);
READ_INT_FIELD(numCols);
READ_ATTRNUMBER_ARRAY(grpColIdx, local_node->numCols);
READ_OID_ARRAY(grpOperators, local_node->numCols); READ_OID_ARRAY(grpOperators, local_node->numCols);
READ_LONG_FIELD(numGroups); READ_LONG_FIELD(numGroups);
READ_NODE_FIELD(groupingSets); READ_NODE_FIELD(groupingSets);
......
...@@ -20,7 +20,7 @@ Paths and Join Pairs ...@@ -20,7 +20,7 @@ Paths and Join Pairs
During the planning/optimizing process, we build "Path" trees representing During the planning/optimizing process, we build "Path" trees representing
the different ways of doing a query. We select the cheapest Path that the different ways of doing a query. We select the cheapest Path that
generates the desired relation and turn it into a Plan to pass to the generates the desired relation and turn it into a Plan to pass to the
executor. (There is pretty much a one-to-one correspondence between the executor. (There is pretty nearly a one-to-one correspondence between the
Path and Plan trees, but Path nodes omit info that won't be needed during Path and Plan trees, but Path nodes omit info that won't be needed during
planning, and include info needed for planning that won't be needed by the planning, and include info needed for planning that won't be needed by the
executor.) executor.)
...@@ -43,10 +43,8 @@ base rels of the query. ...@@ -43,10 +43,8 @@ base rels of the query.
Possible Paths for a primitive table relation include plain old sequential Possible Paths for a primitive table relation include plain old sequential
scan, plus index scans for any indexes that exist on the table, plus bitmap scan, plus index scans for any indexes that exist on the table, plus bitmap
index scans using one or more indexes. A subquery base relation just has index scans using one or more indexes. Specialized RTE types, such as
one Path, a "SubqueryScan" path (which links to the subplan that was built function RTEs, may have only one possible Path.
by a recursive invocation of the planner). Likewise a function-RTE base
relation has only one possible Path.
Joins always occur using two RelOptInfos. One is outer, the other inner. Joins always occur using two RelOptInfos. One is outer, the other inner.
Outers drive lookups of values in the inner. In a nested loop, lookups of Outers drive lookups of values in the inner. In a nested loop, lookups of
...@@ -59,9 +57,10 @@ hashjoin, the inner is scanned first and all its rows are entered in a ...@@ -59,9 +57,10 @@ hashjoin, the inner is scanned first and all its rows are entered in a
hashtable, then the outer is scanned and for each row we lookup the join hashtable, then the outer is scanned and for each row we lookup the join
key in the hashtable. key in the hashtable.
A Path for a join relation is actually a tree structure, with the top A Path for a join relation is actually a tree structure, with the topmost
Path node representing the join method. It has left and right subpaths Path node representing the last-applied join method. It has left and right
that represent the scan or join methods used for the two input relations. subpaths that represent the scan or join methods used for the two input
relations.
Join Tree Construction Join Tree Construction
...@@ -292,8 +291,7 @@ Optimizer Functions ...@@ -292,8 +291,7 @@ Optimizer Functions
The primary entry point is planner(). The primary entry point is planner().
planner() planner()
set up for recursive handling of subqueries set up for recursive handling of subqueries
do final cleanup after planning
-subquery_planner() -subquery_planner()
pull up sublinks and subqueries from rangetable, if possible pull up sublinks and subqueries from rangetable, if possible
canonicalize qual canonicalize qual
...@@ -326,14 +324,15 @@ planner() ...@@ -326,14 +324,15 @@ planner()
Back at standard_join_search(), apply set_cheapest() to extract the Back at standard_join_search(), apply set_cheapest() to extract the
cheapest path for each newly constructed joinrel. cheapest path for each newly constructed joinrel.
Loop back if this wasn't the top join level. Loop back if this wasn't the top join level.
Back at grouping_planner: Back at grouping_planner:
convert Path tree returned by query_planner into a Plan tree do grouping (GROUP BY) and aggregation
do grouping(GROUP) do window functions
do aggregates make unique (DISTINCT)
do window functions do sorting (ORDER BY)
make unique(DISTINCT) do limit (LIMIT/OFFSET)
make sort(ORDER BY) Back at planner():
make limit(LIMIT/OFFSET) convert finished Path tree into a Plan tree
do final cleanup after planning
Optimizer Data Structures Optimizer Data Structures
...@@ -355,12 +354,28 @@ RelOptInfo - a relation or joined relations ...@@ -355,12 +354,28 @@ RelOptInfo - a relation or joined relations
IndexPath - index scan IndexPath - index scan
BitmapHeapPath - top of a bitmapped index scan BitmapHeapPath - top of a bitmapped index scan
TidPath - scan by CTID TidPath - scan by CTID
SubqueryScanPath - scan a subquery-in-FROM
ForeignPath - scan a foreign table ForeignPath - scan a foreign table
CustomPath - for custom scan providers
AppendPath - append multiple subpaths together AppendPath - append multiple subpaths together
MergeAppendPath - merge multiple subpaths, preserving their common sort order MergeAppendPath - merge multiple subpaths, preserving their common sort order
ResultPath - a Result plan node (used for FROM-less SELECT) ResultPath - a childless Result plan node (used for FROM-less SELECT)
MaterialPath - a Material plan node MaterialPath - a Material plan node
UniquePath - remove duplicate rows UniquePath - remove duplicate rows (either by hashing or sorting)
GatherPath - collect the results of parallel workers
ProjectionPath - a Result plan node with child (used for projection)
SortPath - a Sort plan node applied to some sub-path
GroupPath - a Group plan node applied to some sub-path
UpperUniquePath - a Unique plan node applied to some sub-path
AggPath - an Agg plan node applied to some sub-path
GroupingSetsPath - an Agg plan node used to implement GROUPING SETS
MinMaxAggPath - a Result plan node with subplans performing MIN/MAX
WindowAggPath - a WindowAgg plan node applied to some sub-path
SetOpPath - a SetOp plan node applied to some sub-path
RecursiveUnionPath - a RecursiveUnion plan node applied to two sub-paths
LockRowsPath - a LockRows plan node applied to some sub-path
ModifyTablePath - a ModifyTable plan node applied to some sub-path(s)
LimitPath - a Limit plan node applied to some sub-path
NestPath - nested-loop joins NestPath - nested-loop joins
MergePath - merge joins MergePath - merge joins
HashPath - hash joins HashPath - hash joins
...@@ -851,6 +866,59 @@ lateral reference. (Perhaps now that that stuff works, we could relax the ...@@ -851,6 +866,59 @@ lateral reference. (Perhaps now that that stuff works, we could relax the
pullup restriction?) pullup restriction?)
Post scan/join planning
-----------------------
So far we have discussed only scan/join planning, that is, implementation
of the FROM and WHERE clauses of a SQL query. But the planner must also
determine how to deal with GROUP BY, aggregation, and other higher-level
features of queries; and in many cases there are multiple ways to do these
steps and thus opportunities for optimization choices. These steps, like
scan/join planning, are handled by constructing Paths representing the
different ways to do a step, then choosing the cheapest Path.
Since all Paths require a RelOptInfo as "parent", we create RelOptInfos
representing the outputs of these upper-level processing steps. These
RelOptInfos are mostly dummy, but their pathlist lists hold all the Paths
considered useful for each step. Currently, we may create these types of
additional RelOptInfos during upper-level planning:
UPPERREL_SETOP result of UNION/INTERSECT/EXCEPT, if any
UPPERREL_GROUP_AGG result of grouping/aggregation, if any
UPPERREL_WINDOW result of window functions, if any
UPPERREL_DISTINCT result of "SELECT DISTINCT", if any
UPPERREL_ORDERED result of ORDER BY, if any
UPPERREL_FINAL result of any remaining top-level actions
UPPERREL_FINAL is used to represent any final processing steps, currently
LockRows (SELECT FOR UPDATE), LIMIT/OFFSET, and ModifyTable. There is no
flexibility about the order in which these steps are done, and thus no need
to subdivide this stage more finely.
These "upper relations" are identified by the UPPERREL enum values shown
above, plus a relids set, which allows there to be more than one upperrel
of the same kind. We use NULL for the relids if there's no need for more
than one upperrel of the same kind. Currently, in fact, the relids set
is vestigial because it's always NULL, but that's expected to change in
future. For example, in planning set operations, we might need the relids
to denote which subset of the leaf SELECTs has been combined in a
particular group of Paths that are competing with each other.
The result of subquery_planner() is always returned as a set of Paths
stored in the UPPERREL_FINAL rel with NULL relids. The other types of
upperrels are created only if needed for the particular query.
The upper-relation infrastructure is designed so that things will work
properly if a particular upper relation is created and Paths are added
to it sooner than would normally happen. This allows, for example,
for an FDW's GetForeignPaths function to insert a Path representing
remote aggregation into the UPPERREL_GROUP_AGG upperrel, if it notices
that the query represents an aggregation that could be done entirely on
the foreign server. That Path will then compete with Paths representing
local aggregation on a regular scan of the foreign table, once the core
planner reaches the point of considering aggregation.
Parallel Query and Partial Paths Parallel Query and Partial Paths
-------------------------------- --------------------------------
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
#include "optimizer/planner.h" #include "optimizer/planner.h"
#include "optimizer/prep.h" #include "optimizer/prep.h"
#include "optimizer/restrictinfo.h" #include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h" #include "optimizer/var.h"
#include "parser/parse_clause.h" #include "parser/parse_clause.h"
#include "parser/parsetree.h" #include "parser/parsetree.h"
...@@ -97,7 +98,6 @@ static Path *get_cheapest_parameterized_child_path(PlannerInfo *root, ...@@ -97,7 +98,6 @@ static Path *get_cheapest_parameterized_child_path(PlannerInfo *root,
RelOptInfo *rel, RelOptInfo *rel,
Relids required_outer); Relids required_outer);
static List *accumulate_append_subpath(List *subpaths, Path *path); static List *accumulate_append_subpath(List *subpaths, Path *path);
static void set_dummy_rel_pathlist(RelOptInfo *rel);
static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
Index rti, RangeTblEntry *rte); Index rti, RangeTblEntry *rte);
static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel,
...@@ -1507,8 +1507,10 @@ accumulate_append_subpath(List *subpaths, Path *path) ...@@ -1507,8 +1507,10 @@ accumulate_append_subpath(List *subpaths, Path *path)
* *
* Rather than inventing a special "dummy" path type, we represent this as an * Rather than inventing a special "dummy" path type, we represent this as an
* AppendPath with no members (see also IS_DUMMY_PATH/IS_DUMMY_REL macros). * AppendPath with no members (see also IS_DUMMY_PATH/IS_DUMMY_REL macros).
*
* This is exported because inheritance_planner() has need for it.
*/ */
static void void
set_dummy_rel_pathlist(RelOptInfo *rel) set_dummy_rel_pathlist(RelOptInfo *rel)
{ {
/* Set dummy size estimates --- we leave attr_widths[] as zeroes */ /* Set dummy size estimates --- we leave attr_widths[] as zeroes */
...@@ -1554,15 +1556,15 @@ has_multiple_baserels(PlannerInfo *root) ...@@ -1554,15 +1556,15 @@ has_multiple_baserels(PlannerInfo *root)
/* /*
* set_subquery_pathlist * set_subquery_pathlist
* Build the (single) access path for a subquery RTE * Generate SubqueryScan access paths for a subquery RTE
* *
* We don't currently support generating parameterized paths for subqueries * We don't currently support generating parameterized paths for subqueries
* by pushing join clauses down into them; it seems too expensive to re-plan * by pushing join clauses down into them; it seems too expensive to re-plan
* the subquery multiple times to consider different alternatives. So the * the subquery multiple times to consider different alternatives.
* subquery will have exactly one path. (The path will be parameterized * (XXX that could stand to be reconsidered, now that we use Paths.)
* if the subquery contains LATERAL references, otherwise not.) Since there's * So the paths made here will be parameterized if the subquery contains
* no freedom of action here, there's no need for a separate set_subquery_size * LATERAL references, otherwise not. As long as that's true, there's no need
* phase: we just make the path right away. * for a separate set_subquery_size phase: just make the paths right away.
*/ */
static void static void
set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
...@@ -1573,8 +1575,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, ...@@ -1573,8 +1575,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer; Relids required_outer;
pushdown_safety_info safetyInfo; pushdown_safety_info safetyInfo;
double tuple_fraction; double tuple_fraction;
PlannerInfo *subroot; RelOptInfo *sub_final_rel;
List *pathkeys; ListCell *lc;
/* /*
* Must copy the Query so that planning doesn't mess up the RTE contents * Must copy the Query so that planning doesn't mess up the RTE contents
...@@ -1685,12 +1687,10 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, ...@@ -1685,12 +1687,10 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* plan_params should not be in use in current query level */ /* plan_params should not be in use in current query level */
Assert(root->plan_params == NIL); Assert(root->plan_params == NIL);
/* Generate the plan for the subquery */ /* Generate a subroot and Paths for the subquery */
rel->subplan = subquery_planner(root->glob, subquery, rel->subroot = subquery_planner(root->glob, subquery,
root, root,
false, tuple_fraction, false, tuple_fraction);
&subroot);
rel->subroot = subroot;
/* Isolate the params needed by this specific subplan */ /* Isolate the params needed by this specific subplan */
rel->subplan_params = root->plan_params; rel->subplan_params = root->plan_params;
...@@ -1698,23 +1698,44 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, ...@@ -1698,23 +1698,44 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* /*
* It's possible that constraint exclusion proved the subquery empty. If * It's possible that constraint exclusion proved the subquery empty. If
* so, it's convenient to turn it back into a dummy path so that we will * so, it's desirable to produce an unadorned dummy path so that we will
* recognize appropriate optimizations at this query level. * recognize appropriate optimizations at this query level.
*/ */
if (is_dummy_plan(rel->subplan)) sub_final_rel = fetch_upper_rel(rel->subroot, UPPERREL_FINAL, NULL);
if (IS_DUMMY_REL(sub_final_rel))
{ {
set_dummy_rel_pathlist(rel); set_dummy_rel_pathlist(rel);
return; return;
} }
/* Mark rel with estimated output rows, width, etc */ /*
* Mark rel with estimated output rows, width, etc. Note that we have to
* do this before generating outer-query paths, else cost_subqueryscan is
* not happy.
*/
set_subquery_size_estimates(root, rel); set_subquery_size_estimates(root, rel);
/* Convert subquery pathkeys to outer representation */ /*
pathkeys = convert_subquery_pathkeys(root, rel, subroot->query_pathkeys); * For each Path that subquery_planner produced, make a SubqueryScanPath
* in the outer query.
/* Generate appropriate path */ */
add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer)); foreach(lc, sub_final_rel->pathlist)
{
Path *subpath = (Path *) lfirst(lc);
List *pathkeys;
/* Convert subpath's pathkeys to outer representation */
pathkeys = convert_subquery_pathkeys(root,
rel,
subpath->pathkeys,
make_tlist_from_pathtarget(subpath->pathtarget));
/* Generate outer path using this subpath */
add_path(rel, (Path *)
create_subqueryscan_path(root, rel, subpath,
pathkeys, required_outer));
}
} }
/* /*
...@@ -1858,7 +1879,7 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) ...@@ -1858,7 +1879,7 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
cteplan = (Plan *) list_nth(root->glob->subplans, plan_id - 1); cteplan = (Plan *) list_nth(root->glob->subplans, plan_id - 1);
/* Mark rel with estimated output rows, width, etc */ /* Mark rel with estimated output rows, width, etc */
set_cte_size_estimates(root, rel, cteplan); set_cte_size_estimates(root, rel, cteplan->plan_rows);
/* /*
* We don't support pushing join clauses into the quals of a CTE scan, but * We don't support pushing join clauses into the quals of a CTE scan, but
...@@ -1881,13 +1902,13 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) ...@@ -1881,13 +1902,13 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
static void static void
set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{ {
Plan *cteplan; Path *ctepath;
PlannerInfo *cteroot; PlannerInfo *cteroot;
Index levelsup; Index levelsup;
Relids required_outer; Relids required_outer;
/* /*
* We need to find the non-recursive term's plan, which is in the plan * We need to find the non-recursive term's path, which is in the plan
* level that's processing the recursive UNION, which is one level *below* * level that's processing the recursive UNION, which is one level *below*
* where the CTE comes from. * where the CTE comes from.
*/ */
...@@ -1902,12 +1923,12 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) ...@@ -1902,12 +1923,12 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
if (!cteroot) /* shouldn't happen */ if (!cteroot) /* shouldn't happen */
elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename); elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
} }
cteplan = cteroot->non_recursive_plan; ctepath = cteroot->non_recursive_path;
if (!cteplan) /* shouldn't happen */ if (!ctepath) /* shouldn't happen */
elog(ERROR, "could not find plan for CTE \"%s\"", rte->ctename); elog(ERROR, "could not find path for CTE \"%s\"", rte->ctename);
/* Mark rel with estimated output rows, width, etc */ /* Mark rel with estimated output rows, width, etc */
set_cte_size_estimates(root, rel, cteplan); set_cte_size_estimates(root, rel, ctepath->rows);
/* /*
* We don't support pushing join clauses into the quals of a worktable * We don't support pushing join clauses into the quals of a worktable
...@@ -2859,6 +2880,9 @@ print_path(PlannerInfo *root, Path *path, int indent) ...@@ -2859,6 +2880,9 @@ print_path(PlannerInfo *root, Path *path, int indent)
case T_TidPath: case T_TidPath:
ptype = "TidScan"; ptype = "TidScan";
break; break;
case T_SubqueryScanPath:
ptype = "SubqueryScanScan";
break;
case T_ForeignPath: case T_ForeignPath:
ptype = "ForeignScan"; ptype = "ForeignScan";
break; break;
...@@ -2883,6 +2907,55 @@ print_path(PlannerInfo *root, Path *path, int indent) ...@@ -2883,6 +2907,55 @@ print_path(PlannerInfo *root, Path *path, int indent)
ptype = "Gather"; ptype = "Gather";
subpath = ((GatherPath *) path)->subpath; subpath = ((GatherPath *) path)->subpath;
break; break;
case T_ProjectionPath:
ptype = "Projection";
subpath = ((ProjectionPath *) path)->subpath;
break;
case T_SortPath:
ptype = "Sort";
subpath = ((SortPath *) path)->subpath;
break;
case T_GroupPath:
ptype = "Group";
subpath = ((GroupPath *) path)->subpath;
break;
case T_UpperUniquePath:
ptype = "UpperUnique";
subpath = ((UpperUniquePath *) path)->subpath;
break;
case T_AggPath:
ptype = "Agg";
subpath = ((AggPath *) path)->subpath;
break;
case T_GroupingSetsPath:
ptype = "GroupingSets";
subpath = ((GroupingSetsPath *) path)->subpath;
break;
case T_MinMaxAggPath:
ptype = "MinMaxAgg";
break;
case T_WindowAggPath:
ptype = "WindowAgg";
subpath = ((WindowAggPath *) path)->subpath;
break;
case T_SetOpPath:
ptype = "SetOp";
subpath = ((SetOpPath *) path)->subpath;
break;
case T_RecursiveUnionPath:
ptype = "RecursiveUnion";
break;
case T_LockRowsPath:
ptype = "LockRows";
subpath = ((LockRowsPath *) path)->subpath;
break;
case T_ModifyTablePath:
ptype = "ModifyTable";
break;
case T_LimitPath:
ptype = "Limit";
subpath = ((LimitPath *) path)->subpath;
break;
case T_NestPath: case T_NestPath:
ptype = "NestLoop"; ptype = "NestLoop";
join = true; join = true;
......
...@@ -1169,7 +1169,7 @@ cost_tidscan(Path *path, PlannerInfo *root, ...@@ -1169,7 +1169,7 @@ cost_tidscan(Path *path, PlannerInfo *root,
* 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL * 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
*/ */
void void
cost_subqueryscan(Path *path, PlannerInfo *root, cost_subqueryscan(SubqueryScanPath *path, PlannerInfo *root,
RelOptInfo *baserel, ParamPathInfo *param_info) RelOptInfo *baserel, ParamPathInfo *param_info)
{ {
Cost startup_cost; Cost startup_cost;
...@@ -1183,17 +1183,18 @@ cost_subqueryscan(Path *path, PlannerInfo *root, ...@@ -1183,17 +1183,18 @@ cost_subqueryscan(Path *path, PlannerInfo *root,
/* Mark the path with the correct row estimate */ /* Mark the path with the correct row estimate */
if (param_info) if (param_info)
path->rows = param_info->ppi_rows; path->path.rows = param_info->ppi_rows;
else else
path->rows = baserel->rows; path->path.rows = baserel->rows;
/* /*
* Cost of path is cost of evaluating the subplan, plus cost of evaluating * Cost of path is cost of evaluating the subplan, plus cost of evaluating
* any restriction clauses that will be attached to the SubqueryScan node, * any restriction clauses and tlist that will be attached to the
* plus cpu_tuple_cost to account for selection and projection overhead. * SubqueryScan node, plus cpu_tuple_cost to account for selection and
* projection overhead.
*/ */
path->startup_cost = baserel->subplan->startup_cost; path->path.startup_cost = path->subpath->startup_cost;
path->total_cost = baserel->subplan->total_cost; path->path.total_cost = path->subpath->total_cost;
get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost); get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
...@@ -1202,11 +1203,11 @@ cost_subqueryscan(Path *path, PlannerInfo *root, ...@@ -1202,11 +1203,11 @@ cost_subqueryscan(Path *path, PlannerInfo *root,
run_cost = cpu_per_tuple * baserel->tuples; run_cost = cpu_per_tuple * baserel->tuples;
/* tlist eval costs are paid per output row, not per tuple scanned */ /* tlist eval costs are paid per output row, not per tuple scanned */
startup_cost += path->pathtarget->cost.startup; startup_cost += path->path.pathtarget->cost.startup;
run_cost += path->pathtarget->cost.per_tuple * path->rows; run_cost += path->path.pathtarget->cost.per_tuple * path->path.rows;
path->startup_cost += startup_cost; path->path.startup_cost += startup_cost;
path->total_cost += startup_cost + run_cost; path->path.total_cost += startup_cost + run_cost;
} }
/* /*
...@@ -1369,14 +1370,10 @@ cost_ctescan(Path *path, PlannerInfo *root, ...@@ -1369,14 +1370,10 @@ cost_ctescan(Path *path, PlannerInfo *root,
* Determines and returns the cost of performing a recursive union, * Determines and returns the cost of performing a recursive union,
* and also the estimated output size. * and also the estimated output size.
* *
* We are given Plans for the nonrecursive and recursive terms. * We are given Paths for the nonrecursive and recursive terms.
*
* Note that the arguments and output are Plans, not Paths as in most of
* the rest of this module. That's because we don't bother setting up a
* Path representation for recursive union --- we have only one way to do it.
*/ */
void void
cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm) cost_recursive_union(Path *runion, Path *nrterm, Path *rterm)
{ {
Cost startup_cost; Cost startup_cost;
Cost total_cost; Cost total_cost;
...@@ -1385,7 +1382,7 @@ cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm) ...@@ -1385,7 +1382,7 @@ cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm)
/* We probably have decent estimates for the non-recursive term */ /* We probably have decent estimates for the non-recursive term */
startup_cost = nrterm->startup_cost; startup_cost = nrterm->startup_cost;
total_cost = nrterm->total_cost; total_cost = nrterm->total_cost;
total_rows = nrterm->plan_rows; total_rows = nrterm->rows;
/* /*
* We arbitrarily assume that about 10 recursive iterations will be * We arbitrarily assume that about 10 recursive iterations will be
...@@ -1394,7 +1391,7 @@ cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm) ...@@ -1394,7 +1391,7 @@ cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm)
* hard to see how to do better. * hard to see how to do better.
*/ */
total_cost += 10 * rterm->total_cost; total_cost += 10 * rterm->total_cost;
total_rows += 10 * rterm->plan_rows; total_rows += 10 * rterm->rows;
/* /*
* Also charge cpu_tuple_cost per row to account for the costs of * Also charge cpu_tuple_cost per row to account for the costs of
...@@ -1405,8 +1402,9 @@ cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm) ...@@ -1405,8 +1402,9 @@ cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm)
runion->startup_cost = startup_cost; runion->startup_cost = startup_cost;
runion->total_cost = total_cost; runion->total_cost = total_cost;
runion->plan_rows = total_rows; runion->rows = total_rows;
runion->plan_width = Max(nrterm->plan_width, rterm->plan_width); runion->pathtarget->width = Max(nrterm->pathtarget->width,
rterm->pathtarget->width);
} }
/* /*
...@@ -3996,8 +3994,8 @@ calc_joinrel_size_estimate(PlannerInfo *root, ...@@ -3996,8 +3994,8 @@ calc_joinrel_size_estimate(PlannerInfo *root,
* Set the size estimates for a base relation that is a subquery. * Set the size estimates for a base relation that is a subquery.
* *
* The rel's targetlist and restrictinfo list must have been constructed * The rel's targetlist and restrictinfo list must have been constructed
* already, and the plan for the subquery must have been completed. * already, and the Paths for the subquery must have been completed.
* We look at the subquery's plan and PlannerInfo to extract data. * We look at the subquery's PlannerInfo to extract data.
* *
* We set the same fields as set_baserel_size_estimates. * We set the same fields as set_baserel_size_estimates.
*/ */
...@@ -4005,6 +4003,7 @@ void ...@@ -4005,6 +4003,7 @@ void
set_subquery_size_estimates(PlannerInfo *root, RelOptInfo *rel) set_subquery_size_estimates(PlannerInfo *root, RelOptInfo *rel)
{ {
PlannerInfo *subroot = rel->subroot; PlannerInfo *subroot = rel->subroot;
RelOptInfo *sub_final_rel;
RangeTblEntry *rte PG_USED_FOR_ASSERTS_ONLY; RangeTblEntry *rte PG_USED_FOR_ASSERTS_ONLY;
ListCell *lc; ListCell *lc;
...@@ -4013,8 +4012,12 @@ set_subquery_size_estimates(PlannerInfo *root, RelOptInfo *rel) ...@@ -4013,8 +4012,12 @@ set_subquery_size_estimates(PlannerInfo *root, RelOptInfo *rel)
rte = planner_rt_fetch(rel->relid, root); rte = planner_rt_fetch(rel->relid, root);
Assert(rte->rtekind == RTE_SUBQUERY); Assert(rte->rtekind == RTE_SUBQUERY);
/* Copy raw number of output rows from subplan */ /*
rel->tuples = rel->subplan->plan_rows; * Copy raw number of output rows from subquery. All of its paths should
* have the same output rowcount, so just look at cheapest-total.
*/
sub_final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
rel->tuples = sub_final_rel->cheapest_total_path->rows;
/* /*
* Compute per-output-column width estimates by examining the subquery's * Compute per-output-column width estimates by examining the subquery's
...@@ -4144,13 +4147,13 @@ set_values_size_estimates(PlannerInfo *root, RelOptInfo *rel) ...@@ -4144,13 +4147,13 @@ set_values_size_estimates(PlannerInfo *root, RelOptInfo *rel)
* Set the size estimates for a base relation that is a CTE reference. * Set the size estimates for a base relation that is a CTE reference.
* *
* The rel's targetlist and restrictinfo list must have been constructed * The rel's targetlist and restrictinfo list must have been constructed
* already, and we need the completed plan for the CTE (if a regular CTE) * already, and we need an estimate of the number of rows returned by the CTE
* or the non-recursive term (if a self-reference). * (if a regular CTE) or the non-recursive term (if a self-reference).
* *
* We set the same fields as set_baserel_size_estimates. * We set the same fields as set_baserel_size_estimates.
*/ */
void void
set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, Plan *cteplan) set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, double cte_rows)
{ {
RangeTblEntry *rte; RangeTblEntry *rte;
...@@ -4165,12 +4168,12 @@ set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, Plan *cteplan) ...@@ -4165,12 +4168,12 @@ set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, Plan *cteplan)
* In a self-reference, arbitrarily assume the average worktable size * In a self-reference, arbitrarily assume the average worktable size
* is about 10 times the nonrecursive term's size. * is about 10 times the nonrecursive term's size.
*/ */
rel->tuples = 10 * cteplan->plan_rows; rel->tuples = 10 * cte_rows;
} }
else else
{ {
/* Otherwise just believe the CTE plan's output estimate */ /* Otherwise just believe the CTE's rowcount estimate */
rel->tuples = cteplan->plan_rows; rel->tuples = cte_rows;
} }
/* Now estimate number of output rows, etc */ /* Now estimate number of output rows, etc */
...@@ -4225,7 +4228,7 @@ set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel) ...@@ -4225,7 +4228,7 @@ set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel)
* any better number. * any better number.
* *
* The per-attribute width estimates are cached for possible re-use while * The per-attribute width estimates are cached for possible re-use while
* building join relations. * building join relations or post-scan/join pathtargets.
*/ */
static void static void
set_rel_width(PlannerInfo *root, RelOptInfo *rel) set_rel_width(PlannerInfo *root, RelOptInfo *rel)
...@@ -4373,6 +4376,91 @@ set_rel_width(PlannerInfo *root, RelOptInfo *rel) ...@@ -4373,6 +4376,91 @@ set_rel_width(PlannerInfo *root, RelOptInfo *rel)
rel->reltarget.width = tuple_width; rel->reltarget.width = tuple_width;
} }
/*
* set_pathtarget_cost_width
* Set the estimated eval cost and output width of a PathTarget tlist.
*
* As a notational convenience, returns the same PathTarget pointer passed in.
*
* Most, though not quite all, uses of this function occur after we've run
* set_rel_width() for base relations; so we can usually obtain cached width
* estimates for Vars. If we can't, fall back on datatype-based width
* estimates. Present early-planning uses of PathTargets don't need accurate
* widths badly enough to justify going to the catalogs for better data.
*/
PathTarget *
set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target)
{
int32 tuple_width = 0;
ListCell *lc;
/* Vars are assumed to have cost zero, but other exprs do not */
target->cost.startup = 0;
target->cost.per_tuple = 0;
foreach(lc, target->exprs)
{
Node *node = (Node *) lfirst(lc);
if (IsA(node, Var))
{
Var *var = (Var *) node;
int32 item_width;
/* We should not see any upper-level Vars here */
Assert(var->varlevelsup == 0);
/* Try to get data from RelOptInfo cache */
if (var->varno < root->simple_rel_array_size)
{
RelOptInfo *rel = root->simple_rel_array[var->varno];
if (rel != NULL &&
var->varattno >= rel->min_attr &&
var->varattno <= rel->max_attr)
{
int ndx = var->varattno - rel->min_attr;
if (rel->attr_widths[ndx] > 0)
{
tuple_width += rel->attr_widths[ndx];
continue;
}
}
}
/*
* No cached data available, so estimate using just the type info.
*/
item_width = get_typavgwidth(var->vartype, var->vartypmod);
Assert(item_width > 0);
tuple_width += item_width;
}
else
{
/*
* Handle general expressions using type info.
*/
int32 item_width;
QualCost cost;
item_width = get_typavgwidth(exprType(node), exprTypmod(node));
Assert(item_width > 0);
tuple_width += item_width;
/* Account for cost, too */
cost_qual_eval_node(&cost, node, root);
target->cost.startup += cost.startup;
target->cost.per_tuple += cost.per_tuple;
}
}
Assert(tuple_width >= 0);
target->width = tuple_width;
return target;
}
/* /*
* relation_byte_size * relation_byte_size
* Estimate the storage space in bytes for a given number of tuples * Estimate the storage space in bytes for a given number of tuples
......
...@@ -1998,48 +1998,6 @@ add_child_rel_equivalences(PlannerInfo *root, ...@@ -1998,48 +1998,6 @@ add_child_rel_equivalences(PlannerInfo *root,
} }
/*
* mutate_eclass_expressions
* Apply an expression tree mutator to all expressions stored in
* equivalence classes (but ignore child exprs unless include_child_exprs).
*
* This is a bit of a hack ... it's currently needed only by planagg.c,
* which needs to do a global search-and-replace of MIN/MAX Aggrefs
* after eclasses are already set up. Without changing the eclasses too,
* subsequent matching of ORDER BY and DISTINCT clauses would fail.
*
* Note that we assume the mutation won't affect relation membership or any
* other properties we keep track of (which is a bit bogus, but by the time
* planagg.c runs, it no longer matters). Also we must be called in the
* main planner memory context.
*/
void
mutate_eclass_expressions(PlannerInfo *root,
Node *(*mutator) (),
void *context,
bool include_child_exprs)
{
ListCell *lc1;
foreach(lc1, root->eq_classes)
{
EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1);
ListCell *lc2;
foreach(lc2, cur_ec->ec_members)
{
EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
if (cur_em->em_is_child && !include_child_exprs)
continue; /* ignore children unless requested */
cur_em->em_expr = (Expr *)
mutator((Node *) cur_em->em_expr, context);
}
}
}
/* /*
* generate_implied_equalities_for_column * generate_implied_equalities_for_column
* Create EC-derived joinclauses usable with a specific column. * Create EC-derived joinclauses usable with a specific column.
......
...@@ -557,6 +557,7 @@ build_expression_pathkey(PlannerInfo *root, ...@@ -557,6 +557,7 @@ build_expression_pathkey(PlannerInfo *root,
* *
* 'rel': outer query's RelOptInfo for the subquery relation. * 'rel': outer query's RelOptInfo for the subquery relation.
* 'subquery_pathkeys': the subquery's output pathkeys, in its terms. * 'subquery_pathkeys': the subquery's output pathkeys, in its terms.
* 'subquery_tlist': the subquery's output targetlist, in its terms.
* *
* It is not necessary for caller to do truncate_useless_pathkeys(), * It is not necessary for caller to do truncate_useless_pathkeys(),
* because we select keys in a way that takes usefulness of the keys into * because we select keys in a way that takes usefulness of the keys into
...@@ -564,12 +565,12 @@ build_expression_pathkey(PlannerInfo *root, ...@@ -564,12 +565,12 @@ build_expression_pathkey(PlannerInfo *root,
*/ */
List * List *
convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel, convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
List *subquery_pathkeys) List *subquery_pathkeys,
List *subquery_tlist)
{ {
List *retval = NIL; List *retval = NIL;
int retvallen = 0; int retvallen = 0;
int outer_query_keys = list_length(root->query_pathkeys); int outer_query_keys = list_length(root->query_pathkeys);
List *sub_tlist = rel->subplan->targetlist;
ListCell *i; ListCell *i;
foreach(i, subquery_pathkeys) foreach(i, subquery_pathkeys)
...@@ -589,7 +590,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel, ...@@ -589,7 +590,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
if (sub_eclass->ec_sortref == 0) /* can't happen */ if (sub_eclass->ec_sortref == 0) /* can't happen */
elog(ERROR, "volatile EquivalenceClass has no sortref"); elog(ERROR, "volatile EquivalenceClass has no sortref");
tle = get_sortgroupref_tle(sub_eclass->ec_sortref, sub_tlist); tle = get_sortgroupref_tle(sub_eclass->ec_sortref, subquery_tlist);
Assert(tle); Assert(tle);
/* resjunk items aren't visible to outer query */ /* resjunk items aren't visible to outer query */
if (!tle->resjunk) if (!tle->resjunk)
...@@ -669,7 +670,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel, ...@@ -669,7 +670,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
if (sub_member->em_is_child) if (sub_member->em_is_child)
continue; /* ignore children here */ continue; /* ignore children here */
foreach(k, sub_tlist) foreach(k, subquery_tlist)
{ {
TargetEntry *tle = (TargetEntry *) lfirst(k); TargetEntry *tle = (TargetEntry *) lfirst(k);
Expr *tle_expr; Expr *tle_expr;
......
...@@ -44,24 +44,78 @@ ...@@ -44,24 +44,78 @@
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path); /*
static Plan *create_scan_plan(PlannerInfo *root, Path *best_path); * Flag bits that can appear in the flags argument of create_plan_recurse().
* These can be OR-ed together.
*
* CP_EXACT_TLIST specifies that the generated plan node must return exactly
* the tlist specified by the path's pathtarget (this overrides both
* CP_SMALL_TLIST and CP_LABEL_TLIST, if those are set). Otherwise, the
* plan node is allowed to return just the Vars and PlaceHolderVars needed
* to evaluate the pathtarget.
*
* CP_SMALL_TLIST specifies that a narrower tlist is preferred. This is
* passed down by parent nodes such as Sort and Hash, which will have to
* store the returned tuples.
*
* CP_LABEL_TLIST specifies that the plan node must return columns matching
* any sortgrouprefs specified in its pathtarget, with appropriate
* ressortgroupref labels. This is passed down by parent nodes such as Sort
* and Group, which need these values to be available in their inputs.
*/
#define CP_EXACT_TLIST 0x0001 /* Plan must return specified tlist */
#define CP_SMALL_TLIST 0x0002 /* Prefer narrower tlists */
#define CP_LABEL_TLIST 0x0004 /* tlist must contain sortgrouprefs */
static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path,
int flags);
static Plan *create_scan_plan(PlannerInfo *root, Path *best_path,
int flags);
static List *build_path_tlist(PlannerInfo *root, Path *path); static List *build_path_tlist(PlannerInfo *root, Path *path);
static bool use_physical_tlist(PlannerInfo *root, RelOptInfo *rel); static bool use_physical_tlist(PlannerInfo *root, Path *path, int flags);
static void disuse_physical_tlist(PlannerInfo *root, Plan *plan, Path *path); static List *get_gating_quals(PlannerInfo *root, List *quals);
static Plan *create_gating_plan(PlannerInfo *root, Plan *plan, List *quals); static Plan *create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
List *gating_quals);
static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path); static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path);
static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path); static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path);
static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path); static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path);
static Result *create_result_plan(PlannerInfo *root, ResultPath *best_path); static Result *create_result_plan(PlannerInfo *root, ResultPath *best_path);
static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path); static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path,
static Plan *create_unique_plan(PlannerInfo *root, UniquePath *best_path); int flags);
static Plan *create_unique_plan(PlannerInfo *root, UniquePath *best_path,
int flags);
static Gather *create_gather_plan(PlannerInfo *root, GatherPath *best_path);
static Plan *create_projection_plan(PlannerInfo *root, ProjectionPath *best_path);
static Sort *create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags);
static Group *create_group_plan(PlannerInfo *root, GroupPath *best_path);
static Unique *create_upper_unique_plan(PlannerInfo *root, UpperUniquePath *best_path,
int flags);
static Agg *create_agg_plan(PlannerInfo *root, AggPath *best_path);
static Plan *create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path);
static Result *create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path);
static WindowAgg *create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path);
static SetOp *create_setop_plan(PlannerInfo *root, SetOpPath *best_path,
int flags);
static RecursiveUnion *create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path);
static void get_column_info_for_window(PlannerInfo *root, WindowClause *wc,
List *tlist,
int numSortCols, AttrNumber *sortColIdx,
int *partNumCols,
AttrNumber **partColIdx,
Oid **partOperators,
int *ordNumCols,
AttrNumber **ordColIdx,
Oid **ordOperators);
static LockRows *create_lockrows_plan(PlannerInfo *root, LockRowsPath *best_path,
int flags);
static ModifyTable *create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path);
static Limit *create_limit_plan(PlannerInfo *root, LimitPath *best_path,
int flags);
static SeqScan *create_seqscan_plan(PlannerInfo *root, Path *best_path, static SeqScan *create_seqscan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses); List *tlist, List *scan_clauses);
static SampleScan *create_samplescan_plan(PlannerInfo *root, Path *best_path, static SampleScan *create_samplescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses); List *tlist, List *scan_clauses);
static Gather *create_gather_plan(PlannerInfo *root,
GatherPath *best_path);
static Scan *create_indexscan_plan(PlannerInfo *root, IndexPath *best_path, static Scan *create_indexscan_plan(PlannerInfo *root, IndexPath *best_path,
List *tlist, List *scan_clauses, bool indexonly); List *tlist, List *scan_clauses, bool indexonly);
static BitmapHeapScan *create_bitmap_scan_plan(PlannerInfo *root, static BitmapHeapScan *create_bitmap_scan_plan(PlannerInfo *root,
...@@ -71,7 +125,8 @@ static Plan *create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual, ...@@ -71,7 +125,8 @@ static Plan *create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
List **qual, List **indexqual, List **indexECs); List **qual, List **indexqual, List **indexECs);
static TidScan *create_tidscan_plan(PlannerInfo *root, TidPath *best_path, static TidScan *create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
List *tlist, List *scan_clauses); List *tlist, List *scan_clauses);
static SubqueryScan *create_subqueryscan_plan(PlannerInfo *root, Path *best_path, static SubqueryScan *create_subqueryscan_plan(PlannerInfo *root,
SubqueryScanPath *best_path,
List *tlist, List *scan_clauses); List *tlist, List *scan_clauses);
static FunctionScan *create_functionscan_plan(PlannerInfo *root, Path *best_path, static FunctionScan *create_functionscan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses); List *tlist, List *scan_clauses);
...@@ -86,12 +141,9 @@ static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best ...@@ -86,12 +141,9 @@ static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best
static CustomScan *create_customscan_plan(PlannerInfo *root, static CustomScan *create_customscan_plan(PlannerInfo *root,
CustomPath *best_path, CustomPath *best_path,
List *tlist, List *scan_clauses); List *tlist, List *scan_clauses);
static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path, static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path);
Plan *outer_plan, Plan *inner_plan); static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path);
static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path, static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path);
Plan *outer_plan, Plan *inner_plan);
static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path,
Plan *outer_plan, Plan *inner_plan);
static Node *replace_nestloop_params(PlannerInfo *root, Node *expr); static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root); static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
static void process_subquery_nestloop_params(PlannerInfo *root, static void process_subquery_nestloop_params(PlannerInfo *root,
...@@ -106,8 +158,6 @@ static void copy_plan_costsize(Plan *dest, Plan *src); ...@@ -106,8 +158,6 @@ static void copy_plan_costsize(Plan *dest, Plan *src);
static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid); static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid);
static SampleScan *make_samplescan(List *qptlist, List *qpqual, Index scanrelid, static SampleScan *make_samplescan(List *qptlist, List *qpqual, Index scanrelid,
TableSampleClause *tsc); TableSampleClause *tsc);
static Gather *make_gather(List *qptlist, List *qpqual,
int nworkers, bool single_copy, Plan *subplan);
static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid, static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid,
Oid indexid, List *indexqual, List *indexqualorig, Oid indexid, List *indexqual, List *indexqualorig,
List *indexorderby, List *indexorderbyorig, List *indexorderby, List *indexorderbyorig,
...@@ -128,6 +178,10 @@ static BitmapHeapScan *make_bitmap_heapscan(List *qptlist, ...@@ -128,6 +178,10 @@ static BitmapHeapScan *make_bitmap_heapscan(List *qptlist,
Index scanrelid); Index scanrelid);
static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid, static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
List *tidquals); List *tidquals);
static SubqueryScan *make_subqueryscan(List *qptlist,
List *qpqual,
Index scanrelid,
Plan *subplan);
static FunctionScan *make_functionscan(List *qptlist, List *qpqual, static FunctionScan *make_functionscan(List *qptlist, List *qpqual,
Index scanrelid, List *functions, bool funcordinality); Index scanrelid, List *functions, bool funcordinality);
static ValuesScan *make_valuesscan(List *qptlist, List *qpqual, static ValuesScan *make_valuesscan(List *qptlist, List *qpqual,
...@@ -136,6 +190,13 @@ static CteScan *make_ctescan(List *qptlist, List *qpqual, ...@@ -136,6 +190,13 @@ static CteScan *make_ctescan(List *qptlist, List *qpqual,
Index scanrelid, int ctePlanId, int cteParam); Index scanrelid, int ctePlanId, int cteParam);
static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual, static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
Index scanrelid, int wtParam); Index scanrelid, int wtParam);
static Append *make_append(List *appendplans, List *tlist);
static RecursiveUnion *make_recursive_union(List *tlist,
Plan *lefttree,
Plan *righttree,
int wtParam,
List *distinctList,
long numGroups);
static BitmapAnd *make_bitmap_and(List *bitmapplans); static BitmapAnd *make_bitmap_and(List *bitmapplans);
static BitmapOr *make_bitmap_or(List *bitmapplans); static BitmapOr *make_bitmap_or(List *bitmapplans);
static NestLoop *make_nestloop(List *tlist, static NestLoop *make_nestloop(List *tlist,
...@@ -179,7 +240,48 @@ static Plan *prepare_sort_from_pathkeys(PlannerInfo *root, ...@@ -179,7 +240,48 @@ static Plan *prepare_sort_from_pathkeys(PlannerInfo *root,
static EquivalenceMember *find_ec_member_for_tle(EquivalenceClass *ec, static EquivalenceMember *find_ec_member_for_tle(EquivalenceClass *ec,
TargetEntry *tle, TargetEntry *tle,
Relids relids); Relids relids);
static Sort *make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree,
List *pathkeys, double limit_tuples);
static Sort *make_sort_from_sortclauses(PlannerInfo *root, List *sortcls,
Plan *lefttree);
static Sort *make_sort_from_groupcols(PlannerInfo *root,
List *groupcls,
AttrNumber *grpColIdx,
Plan *lefttree);
static Material *make_material(Plan *lefttree); static Material *make_material(Plan *lefttree);
static Agg *make_agg(List *tlist, List *qual, AggStrategy aggstrategy,
bool combineStates, bool finalizeAggs,
int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
List *groupingSets, List *chain,
double dNumGroups, Plan *lefttree);
static WindowAgg *make_windowagg(List *tlist, Index winref,
int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
int frameOptions, Node *startOffset, Node *endOffset,
Plan *lefttree);
static Group *make_group(List *tlist, List *qual, int numGroupCols,
AttrNumber *grpColIdx, Oid *grpOperators,
Plan *lefttree);
static Unique *make_unique_from_sortclauses(Plan *lefttree, List *distinctList);
static Unique *make_unique_from_pathkeys(Plan *lefttree,
List *pathkeys, int numCols);
static Gather *make_gather(List *qptlist, List *qpqual,
int nworkers, bool single_copy, Plan *subplan);
static SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
List *distinctList, AttrNumber flagColIdx, int firstFlag,
long numGroups);
static LockRows *make_lockrows(Plan *lefttree, List *rowMarks, int epqParam);
static Limit *make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount);
static Result *make_result(PlannerInfo *root,
List *tlist,
Node *resconstantqual,
Plan *subplan);
static ModifyTable *make_modifytable(PlannerInfo *root,
CmdType operation, bool canSetTag,
Index nominalRelation,
List *resultRelations, List *subplans,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict, int epqParam);
/* /*
...@@ -209,8 +311,26 @@ create_plan(PlannerInfo *root, Path *best_path) ...@@ -209,8 +311,26 @@ create_plan(PlannerInfo *root, Path *best_path)
root->curOuterRels = NULL; root->curOuterRels = NULL;
root->curOuterParams = NIL; root->curOuterParams = NIL;
/* Recursively process the path tree */ /* Recursively process the path tree, demanding the correct tlist result */
plan = create_plan_recurse(root, best_path); plan = create_plan_recurse(root, best_path, CP_EXACT_TLIST);
/*
* Make sure the topmost plan node's targetlist exposes the original
* column names and other decorative info. Targetlists generated within
* the planner don't bother with that stuff, but we must have it on the
* top-level tlist seen at execution time. However, ModifyTable plan
* nodes don't have a tlist matching the querytree targetlist.
*/
if (!IsA(plan, ModifyTable))
apply_tlist_labeling(plan->targetlist, root->processed_tlist);
/*
* Attach any initPlans created in this query level to the topmost plan
* node. (The initPlans could actually go in any plan node at or above
* where they're referenced, but there seems no reason to put them any
* lower than the topmost node for the query level.)
*/
SS_attach_initplans(root, plan);
/* Update parallel safety information if needed. */ /* Update parallel safety information if needed. */
if (!best_path->parallel_safe) if (!best_path->parallel_safe)
...@@ -234,7 +354,7 @@ create_plan(PlannerInfo *root, Path *best_path) ...@@ -234,7 +354,7 @@ create_plan(PlannerInfo *root, Path *best_path)
* Recursive guts of create_plan(). * Recursive guts of create_plan().
*/ */
static Plan * static Plan *
create_plan_recurse(PlannerInfo *root, Path *best_path) create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
{ {
Plan *plan; Plan *plan;
...@@ -253,7 +373,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path) ...@@ -253,7 +373,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
case T_WorkTableScan: case T_WorkTableScan:
case T_ForeignScan: case T_ForeignScan:
case T_CustomScan: case T_CustomScan:
plan = create_scan_plan(root, best_path); plan = create_scan_plan(root, best_path, flags);
break; break;
case T_HashJoin: case T_HashJoin:
case T_MergeJoin: case T_MergeJoin:
...@@ -270,21 +390,94 @@ create_plan_recurse(PlannerInfo *root, Path *best_path) ...@@ -270,21 +390,94 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
(MergeAppendPath *) best_path); (MergeAppendPath *) best_path);
break; break;
case T_Result: case T_Result:
plan = (Plan *) create_result_plan(root, if (IsA(best_path, ProjectionPath))
(ResultPath *) best_path); {
plan = create_projection_plan(root,
(ProjectionPath *) best_path);
}
else if (IsA(best_path, MinMaxAggPath))
{
plan = (Plan *) create_minmaxagg_plan(root,
(MinMaxAggPath *) best_path);
}
else
{
Assert(IsA(best_path, ResultPath));
plan = (Plan *) create_result_plan(root,
(ResultPath *) best_path);
}
break; break;
case T_Material: case T_Material:
plan = (Plan *) create_material_plan(root, plan = (Plan *) create_material_plan(root,
(MaterialPath *) best_path); (MaterialPath *) best_path,
flags);
break; break;
case T_Unique: case T_Unique:
plan = create_unique_plan(root, if (IsA(best_path, UpperUniquePath))
(UniquePath *) best_path); {
plan = (Plan *) create_upper_unique_plan(root,
(UpperUniquePath *) best_path,
flags);
}
else
{
Assert(IsA(best_path, UniquePath));
plan = create_unique_plan(root,
(UniquePath *) best_path,
flags);
}
break; break;
case T_Gather: case T_Gather:
plan = (Plan *) create_gather_plan(root, plan = (Plan *) create_gather_plan(root,
(GatherPath *) best_path); (GatherPath *) best_path);
break; break;
case T_Sort:
plan = (Plan *) create_sort_plan(root,
(SortPath *) best_path,
flags);
break;
case T_Group:
plan = (Plan *) create_group_plan(root,
(GroupPath *) best_path);
break;
case T_Agg:
if (IsA(best_path, GroupingSetsPath))
plan = create_groupingsets_plan(root,
(GroupingSetsPath *) best_path);
else
{
Assert(IsA(best_path, AggPath));
plan = (Plan *) create_agg_plan(root,
(AggPath *) best_path);
}
break;
case T_WindowAgg:
plan = (Plan *) create_windowagg_plan(root,
(WindowAggPath *) best_path);
break;
case T_SetOp:
plan = (Plan *) create_setop_plan(root,
(SetOpPath *) best_path,
flags);
break;
case T_RecursiveUnion:
plan = (Plan *) create_recursiveunion_plan(root,
(RecursiveUnionPath *) best_path);
break;
case T_LockRows:
plan = (Plan *) create_lockrows_plan(root,
(LockRowsPath *) best_path,
flags);
break;
case T_ModifyTable:
plan = (Plan *) create_modifytable_plan(root,
(ModifyTablePath *) best_path);
break;
case T_Limit:
plan = (Plan *) create_limit_plan(root,
(LimitPath *) best_path,
flags);
break;
default: default:
elog(ERROR, "unrecognized node type: %d", elog(ERROR, "unrecognized node type: %d",
(int) best_path->pathtype); (int) best_path->pathtype);
...@@ -300,34 +493,68 @@ create_plan_recurse(PlannerInfo *root, Path *best_path) ...@@ -300,34 +493,68 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
* Create a scan plan for the parent relation of 'best_path'. * Create a scan plan for the parent relation of 'best_path'.
*/ */
static Plan * static Plan *
create_scan_plan(PlannerInfo *root, Path *best_path) create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
{ {
RelOptInfo *rel = best_path->parent; RelOptInfo *rel = best_path->parent;
List *tlist;
List *scan_clauses; List *scan_clauses;
List *gating_clauses;
List *tlist;
Plan *plan; Plan *plan;
/*
* Extract the relevant restriction clauses from the parent relation. The
* executor must apply all these restrictions during the scan, except for
* pseudoconstants which we'll take care of below.
*/
scan_clauses = rel->baserestrictinfo;
/*
* If this is a parameterized scan, we also need to enforce all the join
* clauses available from the outer relation(s).
*
* For paranoia's sake, don't modify the stored baserestrictinfo list.
*/
if (best_path->param_info)
scan_clauses = list_concat(list_copy(scan_clauses),
best_path->param_info->ppi_clauses);
/*
* Detect whether we have any pseudoconstant quals to deal with. Then, if
* we'll need a gating Result node, it will be able to project, so there
* are no requirements on the child's tlist.
*/
gating_clauses = get_gating_quals(root, scan_clauses);
if (gating_clauses)
flags = 0;
/* /*
* For table scans, rather than using the relation targetlist (which is * For table scans, rather than using the relation targetlist (which is
* only those Vars actually needed by the query), we prefer to generate a * only those Vars actually needed by the query), we prefer to generate a
* tlist containing all Vars in order. This will allow the executor to * tlist containing all Vars in order. This will allow the executor to
* optimize away projection of the table tuples, if possible. (Note that * optimize away projection of the table tuples, if possible.
* planner.c may replace the tlist we generate here, forcing projection to
* occur.)
*/ */
if (use_physical_tlist(root, rel)) if (use_physical_tlist(root, best_path, flags))
{ {
if (best_path->pathtype == T_IndexOnlyScan) if (best_path->pathtype == T_IndexOnlyScan)
{ {
/* For index-only scan, the preferred tlist is the index's */ /* For index-only scan, the preferred tlist is the index's */
tlist = copyObject(((IndexPath *) best_path)->indexinfo->indextlist); tlist = copyObject(((IndexPath *) best_path)->indexinfo->indextlist);
/* Transfer any sortgroupref data to the replacement tlist */
apply_pathtarget_labeling_to_tlist(tlist, best_path->pathtarget);
} }
else else
{ {
tlist = build_physical_tlist(root, rel); tlist = build_physical_tlist(root, rel);
/* if fail because of dropped cols, use regular method */
if (tlist == NIL) if (tlist == NIL)
{
/* Failed because of dropped cols, so use regular method */
tlist = build_path_tlist(root, best_path); tlist = build_path_tlist(root, best_path);
}
else
{
/* Transfer any sortgroupref data to the replacement tlist */
apply_pathtarget_labeling_to_tlist(tlist, best_path->pathtarget);
}
} }
} }
else else
...@@ -335,23 +562,6 @@ create_scan_plan(PlannerInfo *root, Path *best_path) ...@@ -335,23 +562,6 @@ create_scan_plan(PlannerInfo *root, Path *best_path)
tlist = build_path_tlist(root, best_path); tlist = build_path_tlist(root, best_path);
} }
/*
* Extract the relevant restriction clauses from the parent relation. The
* executor must apply all these restrictions during the scan, except for
* pseudoconstants which we'll take care of below.
*/
scan_clauses = rel->baserestrictinfo;
/*
* If this is a parameterized scan, we also need to enforce all the join
* clauses available from the outer relation(s).
*
* For paranoia's sake, don't modify the stored baserestrictinfo list.
*/
if (best_path->param_info)
scan_clauses = list_concat(list_copy(scan_clauses),
best_path->param_info->ppi_clauses);
switch (best_path->pathtype) switch (best_path->pathtype)
{ {
case T_SeqScan: case T_SeqScan:
...@@ -400,7 +610,7 @@ create_scan_plan(PlannerInfo *root, Path *best_path) ...@@ -400,7 +610,7 @@ create_scan_plan(PlannerInfo *root, Path *best_path)
case T_SubqueryScan: case T_SubqueryScan:
plan = (Plan *) create_subqueryscan_plan(root, plan = (Plan *) create_subqueryscan_plan(root,
best_path, (SubqueryScanPath *) best_path,
tlist, tlist,
scan_clauses); scan_clauses);
break; break;
...@@ -459,27 +669,30 @@ create_scan_plan(PlannerInfo *root, Path *best_path) ...@@ -459,27 +669,30 @@ create_scan_plan(PlannerInfo *root, Path *best_path)
* gating Result node that evaluates the pseudoconstants as one-time * gating Result node that evaluates the pseudoconstants as one-time
* quals. * quals.
*/ */
if (root->hasPseudoConstantQuals) if (gating_clauses)
plan = create_gating_plan(root, plan, scan_clauses); plan = create_gating_plan(root, best_path, plan, gating_clauses);
return plan; return plan;
} }
/* /*
* Build a target list (ie, a list of TargetEntry) for the Path's output. * Build a target list (ie, a list of TargetEntry) for the Path's output.
*
* This is almost just make_tlist_from_pathtarget(), but we also have to
* deal with replacing nestloop params.
*/ */
static List * static List *
build_path_tlist(PlannerInfo *root, Path *path) build_path_tlist(PlannerInfo *root, Path *path)
{ {
RelOptInfo *rel = path->parent;
List *tlist = NIL; List *tlist = NIL;
Index *sortgrouprefs = path->pathtarget->sortgrouprefs;
int resno = 1; int resno = 1;
ListCell *v; ListCell *v;
foreach(v, rel->reltarget.exprs) foreach(v, path->pathtarget->exprs)
{ {
/* Do we really need to copy here? Not sure */ Node *node = (Node *) lfirst(v);
Node *node = (Node *) copyObject(lfirst(v)); TargetEntry *tle;
/* /*
* If it's a parameterized path, there might be lateral references in * If it's a parameterized path, there might be lateral references in
...@@ -490,10 +703,14 @@ build_path_tlist(PlannerInfo *root, Path *path) ...@@ -490,10 +703,14 @@ build_path_tlist(PlannerInfo *root, Path *path)
if (path->param_info) if (path->param_info)
node = replace_nestloop_params(root, node); node = replace_nestloop_params(root, node);
tlist = lappend(tlist, makeTargetEntry((Expr *) node, tle = makeTargetEntry((Expr *) node,
resno, resno,
NULL, NULL,
false)); false);
if (sortgrouprefs)
tle->ressortgroupref = sortgrouprefs[resno - 1];
tlist = lappend(tlist, tle);
resno++; resno++;
} }
return tlist; return tlist;
...@@ -505,11 +722,18 @@ build_path_tlist(PlannerInfo *root, Path *path) ...@@ -505,11 +722,18 @@ build_path_tlist(PlannerInfo *root, Path *path)
* rather than only those Vars actually referenced. * rather than only those Vars actually referenced.
*/ */
static bool static bool
use_physical_tlist(PlannerInfo *root, RelOptInfo *rel) use_physical_tlist(PlannerInfo *root, Path *path, int flags)
{ {
RelOptInfo *rel = path->parent;
int i; int i;
ListCell *lc; ListCell *lc;
/*
* Forget it if either exact tlist or small tlist is demanded.
*/
if (flags & (CP_EXACT_TLIST | CP_SMALL_TLIST))
return false;
/* /*
* We can do this for real relation scans, subquery scans, function scans, * We can do this for real relation scans, subquery scans, function scans,
* values scans, and CTE scans (but not for, eg, joins). * values scans, and CTE scans (but not for, eg, joins).
...@@ -523,7 +747,8 @@ use_physical_tlist(PlannerInfo *root, RelOptInfo *rel) ...@@ -523,7 +747,8 @@ use_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
/* /*
* Can't do it with inheritance cases either (mainly because Append * Can't do it with inheritance cases either (mainly because Append
* doesn't project). * doesn't project; this test may be unnecessary now that
* create_append_plan instructs its children to return an exact tlist).
*/ */
if (rel->reloptkind != RELOPT_BASEREL) if (rel->reloptkind != RELOPT_BASEREL)
return false; return false;
...@@ -552,52 +777,60 @@ use_physical_tlist(PlannerInfo *root, RelOptInfo *rel) ...@@ -552,52 +777,60 @@ use_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
return false; return false;
} }
/*
* Also, can't do it if CP_LABEL_TLIST is specified and path is requested
* to emit any sort/group columns that are not simple Vars. (If they are
* simple Vars, they should appear in the physical tlist, and
* apply_pathtarget_labeling_to_tlist will take care of getting them
* labeled again.)
*/
if ((flags & CP_LABEL_TLIST) && path->pathtarget->sortgrouprefs)
{
i = 0;
foreach(lc, path->pathtarget->exprs)
{
Expr *expr = (Expr *) lfirst(lc);
if (path->pathtarget->sortgrouprefs[i])
{
if (expr && IsA(expr, Var))
/* okay */ ;
else
return false;
}
i++;
}
}
return true; return true;
} }
/* /*
* disuse_physical_tlist * get_gating_quals
* Switch a plan node back to emitting only Vars actually referenced. * See if there are pseudoconstant quals in a node's quals list
* *
* If the plan node immediately above a scan would prefer to get only * If the node's quals list includes any pseudoconstant quals,
* needed Vars and not a physical tlist, it must call this routine to * return just those quals.
* undo the decision made by use_physical_tlist(). Currently, Hash, Sort,
* Material, and Gather nodes want this, so they don't have to store or
* transfer useless columns.
*/ */
static void static List *
disuse_physical_tlist(PlannerInfo *root, Plan *plan, Path *path) get_gating_quals(PlannerInfo *root, List *quals)
{ {
/* Only need to undo it for path types handled by create_scan_plan() */ /* No need to look if we know there are no pseudoconstants */
switch (path->pathtype) if (!root->hasPseudoConstantQuals)
{ return NIL;
case T_SeqScan:
case T_SampleScan: /* Sort into desirable execution order while still in RestrictInfo form */
case T_IndexScan: quals = order_qual_clauses(root, quals);
case T_IndexOnlyScan:
case T_BitmapHeapScan: /* Pull out any pseudoconstant quals from the RestrictInfo list */
case T_TidScan: return extract_actual_clauses(quals, true);
case T_SubqueryScan:
case T_FunctionScan:
case T_ValuesScan:
case T_CteScan:
case T_WorkTableScan:
case T_ForeignScan:
case T_CustomScan:
plan->targetlist = build_path_tlist(root, path);
break;
default:
break;
}
} }
/* /*
* create_gating_plan * create_gating_plan
* Deal with pseudoconstant qual clauses * Deal with pseudoconstant qual clauses
* *
* If the node's quals list includes any pseudoconstant quals, put them * Add a gating Result node atop the already-built plan.
* into a gating Result node atop the already-built plan. Otherwise,
* return the plan as-is.
* *
* Note that we don't change cost or size estimates when doing gating. * Note that we don't change cost or size estimates when doing gating.
* The costs of qual eval were already folded into the plan's startup cost. * The costs of qual eval were already folded into the plan's startup cost.
...@@ -611,22 +844,19 @@ disuse_physical_tlist(PlannerInfo *root, Plan *plan, Path *path) ...@@ -611,22 +844,19 @@ disuse_physical_tlist(PlannerInfo *root, Plan *plan, Path *path)
* qual being true. * qual being true.
*/ */
static Plan * static Plan *
create_gating_plan(PlannerInfo *root, Plan *plan, List *quals) create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
List *gating_quals)
{ {
List *pseudoconstants; Assert(gating_quals);
/* Sort into desirable execution order while still in RestrictInfo form */
quals = order_qual_clauses(root, quals);
/* Pull out any pseudoconstant quals from the RestrictInfo list */
pseudoconstants = extract_actual_clauses(quals, true);
if (!pseudoconstants)
return plan;
/*
* Since we need a Result node anyway, always return the path's requested
* tlist; that's never a wrong choice, even if the parent node didn't ask
* for CP_EXACT_TLIST.
*/
return (Plan *) make_result(root, return (Plan *) make_result(root,
plan->targetlist, build_path_tlist(root, path),
(Node *) pseudoconstants, (Node *) gating_quals,
plan); plan);
} }
...@@ -638,43 +868,22 @@ create_gating_plan(PlannerInfo *root, Plan *plan, List *quals) ...@@ -638,43 +868,22 @@ create_gating_plan(PlannerInfo *root, Plan *plan, List *quals)
static Plan * static Plan *
create_join_plan(PlannerInfo *root, JoinPath *best_path) create_join_plan(PlannerInfo *root, JoinPath *best_path)
{ {
Plan *outer_plan;
Plan *inner_plan;
Plan *plan; Plan *plan;
Relids saveOuterRels = root->curOuterRels; List *gating_clauses;
outer_plan = create_plan_recurse(root, best_path->outerjoinpath);
/* For a nestloop, include outer relids in curOuterRels for inner side */
if (best_path->path.pathtype == T_NestLoop)
root->curOuterRels = bms_union(root->curOuterRels,
best_path->outerjoinpath->parent->relids);
inner_plan = create_plan_recurse(root, best_path->innerjoinpath);
switch (best_path->path.pathtype) switch (best_path->path.pathtype)
{ {
case T_MergeJoin: case T_MergeJoin:
plan = (Plan *) create_mergejoin_plan(root, plan = (Plan *) create_mergejoin_plan(root,
(MergePath *) best_path, (MergePath *) best_path);
outer_plan,
inner_plan);
break; break;
case T_HashJoin: case T_HashJoin:
plan = (Plan *) create_hashjoin_plan(root, plan = (Plan *) create_hashjoin_plan(root,
(HashPath *) best_path, (HashPath *) best_path);
outer_plan,
inner_plan);
break; break;
case T_NestLoop: case T_NestLoop:
/* Restore curOuterRels */
bms_free(root->curOuterRels);
root->curOuterRels = saveOuterRels;
plan = (Plan *) create_nestloop_plan(root, plan = (Plan *) create_nestloop_plan(root,
(NestPath *) best_path, (NestPath *) best_path);
outer_plan,
inner_plan);
break; break;
default: default:
elog(ERROR, "unrecognized node type: %d", elog(ERROR, "unrecognized node type: %d",
...@@ -688,8 +897,10 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path) ...@@ -688,8 +897,10 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path)
* gating Result node that evaluates the pseudoconstants as one-time * gating Result node that evaluates the pseudoconstants as one-time
* quals. * quals.
*/ */
if (root->hasPseudoConstantQuals) gating_clauses = get_gating_quals(root, best_path->joinrestrictinfo);
plan = create_gating_plan(root, plan, best_path->joinrestrictinfo); if (gating_clauses)
plan = create_gating_plan(root, (Path *) best_path, plan,
gating_clauses);
#ifdef NOT_USED #ifdef NOT_USED
...@@ -745,8 +956,12 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path) ...@@ -745,8 +956,12 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path)
foreach(subpaths, best_path->subpaths) foreach(subpaths, best_path->subpaths)
{ {
Path *subpath = (Path *) lfirst(subpaths); Path *subpath = (Path *) lfirst(subpaths);
Plan *subplan;
/* Must insist that all children return the same tlist */
subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
subplans = lappend(subplans, create_plan_recurse(root, subpath)); subplans = lappend(subplans, subplan);
} }
/* /*
...@@ -817,7 +1032,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path) ...@@ -817,7 +1032,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path)
bool *nullsFirst; bool *nullsFirst;
/* Build the child plan */ /* Build the child plan */
subplan = create_plan_recurse(root, subpath); /* Must insist that all children return the same tlist */
subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
/* Compute sort column info, and adjust subplan's tlist as needed */ /* Compute sort column info, and adjust subplan's tlist as needed */
subplan = prepare_sort_from_pathkeys(root, subplan, pathkeys, subplan = prepare_sort_from_pathkeys(root, subplan, pathkeys,
...@@ -893,15 +1109,18 @@ create_result_plan(PlannerInfo *root, ResultPath *best_path) ...@@ -893,15 +1109,18 @@ create_result_plan(PlannerInfo *root, ResultPath *best_path)
* Returns a Plan node. * Returns a Plan node.
*/ */
static Material * static Material *
create_material_plan(PlannerInfo *root, MaterialPath *best_path) create_material_plan(PlannerInfo *root, MaterialPath *best_path, int flags)
{ {
Material *plan; Material *plan;
Plan *subplan; Plan *subplan;
subplan = create_plan_recurse(root, best_path->subpath); /*
* We don't want any excess columns in the materialized tuples, so request
/* We don't want any excess columns in the materialized tuples */ * a smaller tlist. Otherwise, since Material doesn't project, tlist
disuse_physical_tlist(root, subplan, best_path->subpath); * requirements pass through.
*/
subplan = create_plan_recurse(root, best_path->subpath,
flags | CP_SMALL_TLIST);
plan = make_material(subplan); plan = make_material(subplan);
...@@ -918,7 +1137,7 @@ create_material_plan(PlannerInfo *root, MaterialPath *best_path) ...@@ -918,7 +1137,7 @@ create_material_plan(PlannerInfo *root, MaterialPath *best_path)
* Returns a Plan node. * Returns a Plan node.
*/ */
static Plan * static Plan *
create_unique_plan(PlannerInfo *root, UniquePath *best_path) create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags)
{ {
Plan *plan; Plan *plan;
Plan *subplan; Plan *subplan;
...@@ -932,7 +1151,8 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) ...@@ -932,7 +1151,8 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
int groupColPos; int groupColPos;
ListCell *l; ListCell *l;
subplan = create_plan_recurse(root, best_path->subpath); /* Unique doesn't project, so tlist requirements pass through */
subplan = create_plan_recurse(root, best_path->subpath, flags);
/* Done if we don't need to do any actual unique-ifying */ /* Done if we don't need to do any actual unique-ifying */
if (best_path->umethod == UNIQUE_PATH_NOOP) if (best_path->umethod == UNIQUE_PATH_NOOP)
...@@ -1018,11 +1238,8 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) ...@@ -1018,11 +1238,8 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
if (best_path->umethod == UNIQUE_PATH_HASH) if (best_path->umethod == UNIQUE_PATH_HASH)
{ {
long numGroups;
Oid *groupOperators; Oid *groupOperators;
numGroups = (long) Min(best_path->path.rows, (double) LONG_MAX);
/* /*
* Get the hashable equality operators for the Agg node to use. * Get the hashable equality operators for the Agg node to use.
* Normally these are the same as the IN clause operators, but if * Normally these are the same as the IN clause operators, but if
...@@ -1047,18 +1264,17 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) ...@@ -1047,18 +1264,17 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
* minimum output tlist, without any stuff we might have added to the * minimum output tlist, without any stuff we might have added to the
* subplan tlist. * subplan tlist.
*/ */
plan = (Plan *) make_agg(root, plan = (Plan *) make_agg(build_path_tlist(root, &best_path->path),
build_path_tlist(root, &best_path->path),
NIL, NIL,
AGG_HASHED, AGG_HASHED,
NULL, false,
true,
numGroupCols, numGroupCols,
groupColIdx, groupColIdx,
groupOperators, groupOperators,
NIL, NIL,
numGroups, NIL,
false, best_path->path.rows,
true,
subplan); subplan);
} }
else else
...@@ -1106,11 +1322,11 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) ...@@ -1106,11 +1322,11 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
groupColPos++; groupColPos++;
} }
plan = (Plan *) make_sort_from_sortclauses(root, sortList, subplan); plan = (Plan *) make_sort_from_sortclauses(root, sortList, subplan);
plan = (Plan *) make_unique(plan, sortList); plan = (Plan *) make_unique_from_sortclauses(plan, sortList);
} }
/* Adjust output size estimate (other fields should be OK already) */ /* Copy cost data from Path to Plan */
plan->plan_rows = best_path->path.rows; copy_generic_path_info(plan, &best_path->path);
return plan; return plan;
} }
...@@ -1121,28 +1337,843 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) ...@@ -1121,28 +1337,843 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
* Create a Gather plan for 'best_path' and (recursively) plans * Create a Gather plan for 'best_path' and (recursively) plans
* for its subpaths. * for its subpaths.
*/ */
static Gather * static Gather *
create_gather_plan(PlannerInfo *root, GatherPath *best_path) create_gather_plan(PlannerInfo *root, GatherPath *best_path)
{
Gather *gather_plan;
Plan *subplan;
/* Must insist that all children return the same tlist */
subplan = create_plan_recurse(root, best_path->subpath, CP_EXACT_TLIST);
gather_plan = make_gather(subplan->targetlist,
NIL,
best_path->path.parallel_degree,
best_path->single_copy,
subplan);
copy_generic_path_info(&gather_plan->plan, &best_path->path);
/* use parallel mode for parallel plans. */
root->glob->parallelModeNeeded = true;
return gather_plan;
}
/*
* create_projection_plan
*
* Create a Result node to do a projection step and (recursively) plans
* for its subpaths.
*/
static Plan *
create_projection_plan(PlannerInfo *root, ProjectionPath *best_path)
{
Plan *plan;
Plan *subplan;
List *tlist;
/* Since we intend to project, we don't need to constrain child tlist */
subplan = create_plan_recurse(root, best_path->subpath, 0);
tlist = build_path_tlist(root, &best_path->path);
/*
* Although the ProjectionPath node wouldn't have been made unless its
* pathtarget is different from the subpath's, it can still happen that
* the constructed tlist matches the subplan's. (An example is that
* MergeAppend doesn't project, so we would have thought that we needed a
* projection to attach resjunk sort columns to its output ... but
* create_merge_append_plan might have added those same resjunk sort
* columns to both MergeAppend and its children.) So, if the desired
* tlist is the same expression-wise as the subplan's, just jam it in
* there. We'll have charged for a Result that doesn't actually appear in
* the plan, but that's better than having a Result we don't need.
*/
if (tlist_same_exprs(tlist, subplan->targetlist))
{
plan = subplan;
plan->targetlist = tlist;
/* Adjust cost to match what we thought during planning */
plan->startup_cost = best_path->path.startup_cost;
plan->total_cost = best_path->path.total_cost;
/* ... but be careful not to munge subplan's parallel-aware flag */
}
else
{
plan = (Plan *) make_result(root, tlist, NULL, subplan);
copy_generic_path_info(plan, (Path *) best_path);
}
return plan;
}
/*
* create_sort_plan
*
* Create a Sort plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static Sort *
create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
{
Sort *plan;
Plan *subplan;
/*
* We don't want any excess columns in the sorted tuples, so request a
* smaller tlist. Otherwise, since Sort doesn't project, tlist
* requirements pass through.
*/
subplan = create_plan_recurse(root, best_path->subpath,
flags | CP_SMALL_TLIST);
/*
* Don't need to have correct limit_tuples; that only affects the cost
* estimate, which we'll overwrite. (XXX should refactor so that we don't
* have a useless cost_sort call in here.)
*/
plan = make_sort_from_pathkeys(root,
subplan,
best_path->path.pathkeys,
-1.0);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* create_group_plan
*
* Create a Group plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static Group *
create_group_plan(PlannerInfo *root, GroupPath *best_path)
{
Group *plan;
Plan *subplan;
List *tlist;
List *quals;
/*
* Group can project, so no need to be terribly picky about child tlist,
* but we do need grouping columns to be available
*/
subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST);
tlist = build_path_tlist(root, &best_path->path);
quals = order_qual_clauses(root, best_path->qual);
plan = make_group(tlist,
quals,
list_length(best_path->groupClause),
extract_grouping_cols(best_path->groupClause,
subplan->targetlist),
extract_grouping_ops(best_path->groupClause),
subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* create_upper_unique_plan
*
* Create a Unique plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static Unique *
create_upper_unique_plan(PlannerInfo *root, UpperUniquePath *best_path, int flags)
{
Unique *plan;
Plan *subplan;
/*
* Unique doesn't project, so tlist requirements pass through; moreover we
* need grouping columns to be labeled.
*/
subplan = create_plan_recurse(root, best_path->subpath,
flags | CP_LABEL_TLIST);
plan = make_unique_from_pathkeys(subplan,
best_path->path.pathkeys,
best_path->numkeys);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* create_agg_plan
*
* Create an Agg plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static Agg *
create_agg_plan(PlannerInfo *root, AggPath *best_path)
{
Agg *plan;
Plan *subplan;
List *tlist;
List *quals;
/*
* Agg can project, so no need to be terribly picky about child tlist, but
* we do need grouping columns to be available
*/
subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST);
tlist = build_path_tlist(root, &best_path->path);
quals = order_qual_clauses(root, best_path->qual);
plan = make_agg(tlist, quals,
best_path->aggstrategy,
false,
true,
list_length(best_path->groupClause),
extract_grouping_cols(best_path->groupClause,
subplan->targetlist),
extract_grouping_ops(best_path->groupClause),
NIL,
NIL,
best_path->numGroups,
subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* Given a groupclause for a collection of grouping sets, produce the
* corresponding groupColIdx.
*
* root->grouping_map maps the tleSortGroupRef to the actual column position in
* the input tuple. So we get the ref from the entries in the groupclause and
* look them up there.
*/
static AttrNumber *
remap_groupColIdx(PlannerInfo *root, List *groupClause)
{
AttrNumber *grouping_map = root->grouping_map;
AttrNumber *new_grpColIdx;
ListCell *lc;
int i;
Assert(grouping_map);
new_grpColIdx = palloc0(sizeof(AttrNumber) * list_length(groupClause));
i = 0;
foreach(lc, groupClause)
{
SortGroupClause *clause = lfirst(lc);
new_grpColIdx[i++] = grouping_map[clause->tleSortGroupRef];
}
return new_grpColIdx;
}
/*
* create_groupingsets_plan
* Create a plan for 'best_path' and (recursively) plans
* for its subpaths.
*
* What we emit is an Agg plan with some vestigial Agg and Sort nodes
* hanging off the side. The top Agg implements the last grouping set
* specified in the GroupingSetsPath, and any additional grouping sets
* each give rise to a subsidiary Agg and Sort node in the top Agg's
* "chain" list. These nodes don't participate in the plan directly,
* but they are a convenient way to represent the required data for
* the extra steps.
*
* Returns a Plan node.
*/
static Plan *
create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
{
Agg *plan;
Plan *subplan;
AttrNumber *groupColIdx = best_path->groupColIdx;
List *rollup_groupclauses = best_path->rollup_groupclauses;
List *rollup_lists = best_path->rollup_lists;
AttrNumber *grouping_map;
int maxref;
List *chain;
int i;
ListCell *lc,
*lc2;
/* Shouldn't get here without grouping sets */
Assert(root->parse->groupingSets);
Assert(rollup_lists != NIL);
Assert(list_length(rollup_lists) == list_length(rollup_groupclauses));
/*
* Agg can project, so no need to be terribly picky about child tlist, but
* we do need grouping columns to be available
*/
subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST);
/*
* Compute the mapping from tleSortGroupRef to column index. First,
* identify max SortGroupRef in groupClause, for array sizing.
*/
maxref = 0;
foreach(lc, root->parse->groupClause)
{
SortGroupClause *gc = (SortGroupClause *) lfirst(lc);
if (gc->tleSortGroupRef > maxref)
maxref = gc->tleSortGroupRef;
}
grouping_map = (AttrNumber *) palloc0((maxref + 1) * sizeof(AttrNumber));
i = 0;
foreach(lc, root->parse->groupClause)
{
SortGroupClause *gc = (SortGroupClause *) lfirst(lc);
grouping_map[gc->tleSortGroupRef] = groupColIdx[i++];
}
/*
* During setrefs.c, we'll need the grouping_map to fix up the cols lists
* in GroupingFunc nodes. Save it for setrefs.c to use.
*
* This doesn't work if we're in an inheritance subtree (see notes in
* create_modifytable_plan). Fortunately we can't be because there would
* never be grouping in an UPDATE/DELETE; but let's Assert that.
*/
Assert(!root->hasInheritedTarget);
Assert(root->grouping_map == NULL);
root->grouping_map = grouping_map;
/*
* Generate the side nodes that describe the other sort and group
* operations besides the top one. Note that we don't worry about putting
* accurate cost estimates in the side nodes; only the topmost Agg node's
* costs will be shown by EXPLAIN.
*/
chain = NIL;
if (list_length(rollup_groupclauses) > 1)
{
forboth(lc, rollup_groupclauses, lc2, rollup_lists)
{
List *groupClause = (List *) lfirst(lc);
List *gsets = (List *) lfirst(lc2);
AttrNumber *new_grpColIdx;
Plan *sort_plan;
Plan *agg_plan;
/* We want to iterate over all but the last rollup list elements */
if (lnext(lc) == NULL)
break;
new_grpColIdx = remap_groupColIdx(root, groupClause);
sort_plan = (Plan *)
make_sort_from_groupcols(root,
groupClause,
new_grpColIdx,
subplan);
agg_plan = (Plan *) make_agg(NIL,
NIL,
AGG_SORTED,
false,
true,
list_length((List *) linitial(gsets)),
new_grpColIdx,
extract_grouping_ops(groupClause),
gsets,
NIL,
0, /* numGroups not needed */
sort_plan);
/*
* Nuke stuff we don't need to avoid bloating debug output.
*/
sort_plan->targetlist = NIL;
sort_plan->lefttree = NULL;
chain = lappend(chain, agg_plan);
}
}
/*
* Now make the final Agg node
*/
{
List *groupClause = (List *) llast(rollup_groupclauses);
List *gsets = (List *) llast(rollup_lists);
AttrNumber *top_grpColIdx;
int numGroupCols;
top_grpColIdx = remap_groupColIdx(root, groupClause);
numGroupCols = list_length((List *) linitial(gsets));
plan = make_agg(build_path_tlist(root, &best_path->path),
best_path->qual,
(numGroupCols > 0) ? AGG_SORTED : AGG_PLAIN,
false,
true,
numGroupCols,
top_grpColIdx,
extract_grouping_ops(groupClause),
gsets,
chain,
0, /* numGroups not needed */
subplan);
/* Copy cost data from Path to Plan */
copy_generic_path_info(&plan->plan, &best_path->path);
}
return (Plan *) plan;
}
/*
* create_minmaxagg_plan
*
* Create a Result plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static Result *
create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
{
Result *plan;
List *tlist;
ListCell *lc;
/* Prepare an InitPlan for each aggregate's subquery. */
foreach(lc, best_path->mmaggregates)
{
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
PlannerInfo *subroot = mminfo->subroot;
Query *subparse = subroot->parse;
Plan *plan;
/*
* Generate the plan for the subquery. We already have a Path, but we
* have to convert it to a Plan and attach a LIMIT node above it.
* Since we are entering a different planner context (subroot),
* recurse to create_plan not create_plan_recurse.
*/
plan = create_plan(subroot, mminfo->path);
plan = (Plan *) make_limit(plan,
subparse->limitOffset,
subparse->limitCount);
/* Must apply correct cost/width data to Limit node */
plan->startup_cost = mminfo->path->startup_cost;
plan->total_cost = mminfo->pathcost;
plan->plan_rows = 1;
plan->plan_width = mminfo->path->pathtarget->width;
plan->parallel_aware = false;
/* Convert the plan into an InitPlan in the outer query. */
SS_make_initplan_from_plan(root, subroot, plan, mminfo->param);
}
/* Generate the output plan --- basically just a Result */
tlist = build_path_tlist(root, &best_path->path);
plan = make_result(root, tlist, (Node *) best_path->quals, NULL);
copy_generic_path_info(&plan->plan, (Path *) best_path);
/*
* During setrefs.c, we'll need to replace references to the Agg nodes
* with InitPlan output params. (We can't just do that locally in the
* MinMaxAgg node, because path nodes above here may have Agg references
* as well.) Save the mmaggregates list to tell setrefs.c to do that.
*
* This doesn't work if we're in an inheritance subtree (see notes in
* create_modifytable_plan). Fortunately we can't be because there would
* never be aggregates in an UPDATE/DELETE; but let's Assert that.
*/
Assert(!root->hasInheritedTarget);
Assert(root->minmax_aggs == NIL);
root->minmax_aggs = best_path->mmaggregates;
return plan;
}
/*
* create_windowagg_plan
*
* Create a WindowAgg plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static WindowAgg *
create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
{
WindowAgg *plan;
WindowClause *wc = best_path->winclause;
Plan *subplan;
List *tlist;
int numsortkeys;
AttrNumber *sortColIdx;
Oid *sortOperators;
Oid *collations;
bool *nullsFirst;
int partNumCols;
AttrNumber *partColIdx;
Oid *partOperators;
int ordNumCols;
AttrNumber *ordColIdx;
Oid *ordOperators;
/*
* WindowAgg can project, so no need to be terribly picky about child
* tlist, but we do need grouping columns to be available
*/
subplan = create_plan_recurse(root, best_path->subpath, CP_LABEL_TLIST);
tlist = build_path_tlist(root, &best_path->path);
/*
* We shouldn't need to actually sort, but it's convenient to use
* prepare_sort_from_pathkeys to identify the input's sort columns.
*/
subplan = prepare_sort_from_pathkeys(root,
subplan,
best_path->winpathkeys,
NULL,
NULL,
false,
&numsortkeys,
&sortColIdx,
&sortOperators,
&collations,
&nullsFirst);
/* Now deconstruct that into partition and ordering portions */
get_column_info_for_window(root,
wc,
subplan->targetlist,
numsortkeys,
sortColIdx,
&partNumCols,
&partColIdx,
&partOperators,
&ordNumCols,
&ordColIdx,
&ordOperators);
/* And finally we can make the WindowAgg node */
plan = make_windowagg(tlist,
wc->winref,
partNumCols,
partColIdx,
partOperators,
ordNumCols,
ordColIdx,
ordOperators,
wc->frameOptions,
wc->startOffset,
wc->endOffset,
subplan);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* get_column_info_for_window
* Get the partitioning/ordering column numbers and equality operators
* for a WindowAgg node.
*
* This depends on the behavior of planner.c's make_pathkeys_for_window!
*
* We are given the target WindowClause and an array of the input column
* numbers associated with the resulting pathkeys. In the easy case, there
* are the same number of pathkey columns as partitioning + ordering columns
* and we just have to copy some data around. However, it's possible that
* some of the original partitioning + ordering columns were eliminated as
* redundant during the transformation to pathkeys. (This can happen even
* though the parser gets rid of obvious duplicates. A typical scenario is a
* window specification "PARTITION BY x ORDER BY y" coupled with a clause
* "WHERE x = y" that causes the two sort columns to be recognized as
* redundant.) In that unusual case, we have to work a lot harder to
* determine which keys are significant.
*
* The method used here is a bit brute-force: add the sort columns to a list
* one at a time and note when the resulting pathkey list gets longer. But
* it's a sufficiently uncommon case that a faster way doesn't seem worth
* the amount of code refactoring that'd be needed.
*/
static void
get_column_info_for_window(PlannerInfo *root, WindowClause *wc, List *tlist,
int numSortCols, AttrNumber *sortColIdx,
int *partNumCols,
AttrNumber **partColIdx,
Oid **partOperators,
int *ordNumCols,
AttrNumber **ordColIdx,
Oid **ordOperators)
{
int numPart = list_length(wc->partitionClause);
int numOrder = list_length(wc->orderClause);
if (numSortCols == numPart + numOrder)
{
/* easy case */
*partNumCols = numPart;
*partColIdx = sortColIdx;
*partOperators = extract_grouping_ops(wc->partitionClause);
*ordNumCols = numOrder;
*ordColIdx = sortColIdx + numPart;
*ordOperators = extract_grouping_ops(wc->orderClause);
}
else
{
List *sortclauses;
List *pathkeys;
int scidx;
ListCell *lc;
/* first, allocate what's certainly enough space for the arrays */
*partNumCols = 0;
*partColIdx = (AttrNumber *) palloc(numPart * sizeof(AttrNumber));
*partOperators = (Oid *) palloc(numPart * sizeof(Oid));
*ordNumCols = 0;
*ordColIdx = (AttrNumber *) palloc(numOrder * sizeof(AttrNumber));
*ordOperators = (Oid *) palloc(numOrder * sizeof(Oid));
sortclauses = NIL;
pathkeys = NIL;
scidx = 0;
foreach(lc, wc->partitionClause)
{
SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
List *new_pathkeys;
sortclauses = lappend(sortclauses, sgc);
new_pathkeys = make_pathkeys_for_sortclauses(root,
sortclauses,
tlist);
if (list_length(new_pathkeys) > list_length(pathkeys))
{
/* this sort clause is actually significant */
(*partColIdx)[*partNumCols] = sortColIdx[scidx++];
(*partOperators)[*partNumCols] = sgc->eqop;
(*partNumCols)++;
pathkeys = new_pathkeys;
}
}
foreach(lc, wc->orderClause)
{
SortGroupClause *sgc = (SortGroupClause *) lfirst(lc);
List *new_pathkeys;
sortclauses = lappend(sortclauses, sgc);
new_pathkeys = make_pathkeys_for_sortclauses(root,
sortclauses,
tlist);
if (list_length(new_pathkeys) > list_length(pathkeys))
{
/* this sort clause is actually significant */
(*ordColIdx)[*ordNumCols] = sortColIdx[scidx++];
(*ordOperators)[*ordNumCols] = sgc->eqop;
(*ordNumCols)++;
pathkeys = new_pathkeys;
}
}
/* complain if we didn't eat exactly the right number of sort cols */
if (scidx != numSortCols)
elog(ERROR, "failed to deconstruct sort operators into partitioning/ordering operators");
}
}
/*
* create_setop_plan
*
* Create a SetOp plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static SetOp *
create_setop_plan(PlannerInfo *root, SetOpPath *best_path, int flags)
{
SetOp *plan;
Plan *subplan;
long numGroups;
/*
* SetOp doesn't project, so tlist requirements pass through; moreover we
* need grouping columns to be labeled.
*/
subplan = create_plan_recurse(root, best_path->subpath,
flags | CP_LABEL_TLIST);
/* Convert numGroups to long int --- but 'ware overflow! */
numGroups = (long) Min(best_path->numGroups, (double) LONG_MAX);
plan = make_setop(best_path->cmd,
best_path->strategy,
subplan,
best_path->distinctList,
best_path->flagColIdx,
best_path->firstFlag,
numGroups);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* create_recursiveunion_plan
*
* Create a RecursiveUnion plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static RecursiveUnion *
create_recursiveunion_plan(PlannerInfo *root, RecursiveUnionPath *best_path)
{
RecursiveUnion *plan;
Plan *leftplan;
Plan *rightplan;
List *tlist;
long numGroups;
/* Need both children to produce same tlist, so force it */
leftplan = create_plan_recurse(root, best_path->leftpath, CP_EXACT_TLIST);
rightplan = create_plan_recurse(root, best_path->rightpath, CP_EXACT_TLIST);
tlist = build_path_tlist(root, &best_path->path);
/* Convert numGroups to long int --- but 'ware overflow! */
numGroups = (long) Min(best_path->numGroups, (double) LONG_MAX);
plan = make_recursive_union(tlist,
leftplan,
rightplan,
best_path->wtParam,
best_path->distinctList,
numGroups);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* create_lockrows_plan
*
* Create a LockRows plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static LockRows *
create_lockrows_plan(PlannerInfo *root, LockRowsPath *best_path,
int flags)
{
LockRows *plan;
Plan *subplan;
/* LockRows doesn't project, so tlist requirements pass through */
subplan = create_plan_recurse(root, best_path->subpath, flags);
plan = make_lockrows(subplan, best_path->rowMarks, best_path->epqParam);
copy_generic_path_info(&plan->plan, (Path *) best_path);
return plan;
}
/*
* create_modifytable_plan
* Create a ModifyTable plan for 'best_path'.
*
* Returns a Plan node.
*/
static ModifyTable *
create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
{
ModifyTable *plan;
List *subplans = NIL;
ListCell *subpaths,
*subroots;
/* Build the plan for each input path */
forboth(subpaths, best_path->subpaths,
subroots, best_path->subroots)
{
Path *subpath = (Path *) lfirst(subpaths);
PlannerInfo *subroot = (PlannerInfo *) lfirst(subroots);
Plan *subplan;
/*
* In an inherited UPDATE/DELETE, reference the per-child modified
* subroot while creating Plans from Paths for the child rel. This is
* a kluge, but otherwise it's too hard to ensure that Plan creation
* functions (particularly in FDWs) don't depend on the contents of
* "root" matching what they saw at Path creation time. The main
* downside is that creation functions for Plans that might appear
* below a ModifyTable cannot expect to modify the contents of "root"
* and have it "stick" for subsequent processing such as setrefs.c.
* That's not great, but it seems better than the alternative.
*/
subplan = create_plan_recurse(subroot, subpath, CP_EXACT_TLIST);
/* Transfer resname/resjunk labeling, too, to keep executor happy */
apply_tlist_labeling(subplan->targetlist, subroot->processed_tlist);
subplans = lappend(subplans, subplan);
}
plan = make_modifytable(root,
best_path->operation,
best_path->canSetTag,
best_path->nominalRelation,
best_path->resultRelations,
subplans,
best_path->withCheckOptionLists,
best_path->returningLists,
best_path->rowMarks,
best_path->onconflict,
best_path->epqParam);
copy_generic_path_info(&plan->plan, &best_path->path);
return plan;
}
/*
* create_limit_plan
*
* Create a Limit plan for 'best_path' and (recursively) plans
* for its subpaths.
*/
static Limit *
create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags)
{ {
Gather *gather_plan; Limit *plan;
Plan *subplan; Plan *subplan;
subplan = create_plan_recurse(root, best_path->subpath); /* Limit doesn't project, so tlist requirements pass through */
subplan = create_plan_recurse(root, best_path->subpath, flags);
disuse_physical_tlist(root, subplan, best_path->subpath);
gather_plan = make_gather(subplan->targetlist,
NIL,
best_path->path.parallel_degree,
best_path->single_copy,
subplan);
copy_generic_path_info(&gather_plan->plan, &best_path->path); plan = make_limit(subplan,
best_path->limitOffset,
best_path->limitCount);
/* use parallel mode for parallel plans. */ copy_generic_path_info(&plan->plan, (Path *) best_path);
root->glob->parallelModeNeeded = true;
return gather_plan; return plan;
} }
...@@ -1814,15 +2845,24 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path, ...@@ -1814,15 +2845,24 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
* with restriction clauses 'scan_clauses' and targetlist 'tlist'. * with restriction clauses 'scan_clauses' and targetlist 'tlist'.
*/ */
static SubqueryScan * static SubqueryScan *
create_subqueryscan_plan(PlannerInfo *root, Path *best_path, create_subqueryscan_plan(PlannerInfo *root, SubqueryScanPath *best_path,
List *tlist, List *scan_clauses) List *tlist, List *scan_clauses)
{ {
SubqueryScan *scan_plan; SubqueryScan *scan_plan;
Index scan_relid = best_path->parent->relid; RelOptInfo *rel = best_path->path.parent;
Index scan_relid = rel->relid;
Plan *subplan;
/* it should be a subquery base rel... */ /* it should be a subquery base rel... */
Assert(scan_relid > 0); Assert(scan_relid > 0);
Assert(best_path->parent->rtekind == RTE_SUBQUERY); Assert(rel->rtekind == RTE_SUBQUERY);
/*
* Recursively create Plan from Path for subquery. Since we are entering
* a different planner context (subroot), recurse to create_plan not
* create_plan_recurse.
*/
subplan = create_plan(rel->subroot, best_path->subpath);
/* Sort clauses into best execution order */ /* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses); scan_clauses = order_qual_clauses(root, scan_clauses);
...@@ -1831,20 +2871,20 @@ create_subqueryscan_plan(PlannerInfo *root, Path *best_path, ...@@ -1831,20 +2871,20 @@ create_subqueryscan_plan(PlannerInfo *root, Path *best_path,
scan_clauses = extract_actual_clauses(scan_clauses, false); scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Replace any outer-relation variables with nestloop params */ /* Replace any outer-relation variables with nestloop params */
if (best_path->param_info) if (best_path->path.param_info)
{ {
scan_clauses = (List *) scan_clauses = (List *)
replace_nestloop_params(root, (Node *) scan_clauses); replace_nestloop_params(root, (Node *) scan_clauses);
process_subquery_nestloop_params(root, process_subquery_nestloop_params(root,
best_path->parent->subplan_params); rel->subplan_params);
} }
scan_plan = make_subqueryscan(tlist, scan_plan = make_subqueryscan(tlist,
scan_clauses, scan_clauses,
scan_relid, scan_relid,
best_path->parent->subplan); subplan);
copy_generic_path_info(&scan_plan->scan.plan, best_path); copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
return scan_plan; return scan_plan;
} }
...@@ -2108,7 +3148,8 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, ...@@ -2108,7 +3148,8 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
/* transform the child path if any */ /* transform the child path if any */
if (best_path->fdw_outerpath) if (best_path->fdw_outerpath)
outer_plan = create_plan_recurse(root, best_path->fdw_outerpath); outer_plan = create_plan_recurse(root, best_path->fdw_outerpath,
CP_EXACT_TLIST);
/* /*
* If we're scanning a base relation, fetch its OID. (Irrelevant if * If we're scanning a base relation, fetch its OID. (Irrelevant if
...@@ -2243,7 +3284,8 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path, ...@@ -2243,7 +3284,8 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
/* Recursively transform child paths. */ /* Recursively transform child paths. */
foreach(lc, best_path->custom_paths) foreach(lc, best_path->custom_paths)
{ {
Plan *plan = create_plan_recurse(root, (Path *) lfirst(lc)); Plan *plan = create_plan_recurse(root, (Path *) lfirst(lc),
CP_EXACT_TLIST);
custom_plans = lappend(custom_plans, plan); custom_plans = lappend(custom_plans, plan);
} }
...@@ -2303,21 +3345,35 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path, ...@@ -2303,21 +3345,35 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path,
static NestLoop * static NestLoop *
create_nestloop_plan(PlannerInfo *root, create_nestloop_plan(PlannerInfo *root,
NestPath *best_path, NestPath *best_path)
Plan *outer_plan,
Plan *inner_plan)
{ {
NestLoop *join_plan; NestLoop *join_plan;
Plan *outer_plan;
Plan *inner_plan;
List *tlist = build_path_tlist(root, &best_path->path); List *tlist = build_path_tlist(root, &best_path->path);
List *joinrestrictclauses = best_path->joinrestrictinfo; List *joinrestrictclauses = best_path->joinrestrictinfo;
List *joinclauses; List *joinclauses;
List *otherclauses; List *otherclauses;
Relids outerrelids; Relids outerrelids;
List *nestParams; List *nestParams;
Relids saveOuterRels = root->curOuterRels;
ListCell *cell; ListCell *cell;
ListCell *prev; ListCell *prev;
ListCell *next; ListCell *next;
/* NestLoop can project, so no need to be picky about child tlists */
outer_plan = create_plan_recurse(root, best_path->outerjoinpath, 0);
/* For a nestloop, include outer relids in curOuterRels for inner side */
root->curOuterRels = bms_union(root->curOuterRels,
best_path->outerjoinpath->parent->relids);
inner_plan = create_plan_recurse(root, best_path->innerjoinpath, 0);
/* Restore curOuterRels */
bms_free(root->curOuterRels);
root->curOuterRels = saveOuterRels;
/* Sort join qual clauses into best execution order */ /* Sort join qual clauses into best execution order */
joinrestrictclauses = order_qual_clauses(root, joinrestrictclauses); joinrestrictclauses = order_qual_clauses(root, joinrestrictclauses);
...@@ -2394,10 +3450,11 @@ create_nestloop_plan(PlannerInfo *root, ...@@ -2394,10 +3450,11 @@ create_nestloop_plan(PlannerInfo *root,
static MergeJoin * static MergeJoin *
create_mergejoin_plan(PlannerInfo *root, create_mergejoin_plan(PlannerInfo *root,
MergePath *best_path, MergePath *best_path)
Plan *outer_plan,
Plan *inner_plan)
{ {
MergeJoin *join_plan;
Plan *outer_plan;
Plan *inner_plan;
List *tlist = build_path_tlist(root, &best_path->jpath.path); List *tlist = build_path_tlist(root, &best_path->jpath.path);
List *joinclauses; List *joinclauses;
List *otherclauses; List *otherclauses;
...@@ -2409,12 +3466,23 @@ create_mergejoin_plan(PlannerInfo *root, ...@@ -2409,12 +3466,23 @@ create_mergejoin_plan(PlannerInfo *root,
Oid *mergecollations; Oid *mergecollations;
int *mergestrategies; int *mergestrategies;
bool *mergenullsfirst; bool *mergenullsfirst;
MergeJoin *join_plan;
int i; int i;
ListCell *lc; ListCell *lc;
ListCell *lop; ListCell *lop;
ListCell *lip; ListCell *lip;
/*
* MergeJoin can project, so we don't have to demand exact tlists from the
* inputs. However, if we're intending to sort an input's result, it's
* best to request a small tlist so we aren't sorting more data than
* necessary.
*/
outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath,
(best_path->outersortkeys != NIL) ? CP_SMALL_TLIST : 0);
inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath,
(best_path->innersortkeys != NIL) ? CP_SMALL_TLIST : 0);
/* Sort join qual clauses into best execution order */ /* Sort join qual clauses into best execution order */
/* NB: do NOT reorder the mergeclauses */ /* NB: do NOT reorder the mergeclauses */
joinclauses = order_qual_clauses(root, best_path->jpath.joinrestrictinfo); joinclauses = order_qual_clauses(root, best_path->jpath.joinrestrictinfo);
...@@ -2462,11 +3530,9 @@ create_mergejoin_plan(PlannerInfo *root, ...@@ -2462,11 +3530,9 @@ create_mergejoin_plan(PlannerInfo *root,
/* /*
* Create explicit sort nodes for the outer and inner paths if necessary. * Create explicit sort nodes for the outer and inner paths if necessary.
* Make sure there are no excess columns in the inputs if sorting.
*/ */
if (best_path->outersortkeys) if (best_path->outersortkeys)
{ {
disuse_physical_tlist(root, outer_plan, best_path->jpath.outerjoinpath);
outer_plan = (Plan *) outer_plan = (Plan *)
make_sort_from_pathkeys(root, make_sort_from_pathkeys(root,
outer_plan, outer_plan,
...@@ -2479,7 +3545,6 @@ create_mergejoin_plan(PlannerInfo *root, ...@@ -2479,7 +3545,6 @@ create_mergejoin_plan(PlannerInfo *root,
if (best_path->innersortkeys) if (best_path->innersortkeys)
{ {
disuse_physical_tlist(root, inner_plan, best_path->jpath.innerjoinpath);
inner_plan = (Plan *) inner_plan = (Plan *)
make_sort_from_pathkeys(root, make_sort_from_pathkeys(root,
inner_plan, inner_plan,
...@@ -2689,10 +3754,12 @@ create_mergejoin_plan(PlannerInfo *root, ...@@ -2689,10 +3754,12 @@ create_mergejoin_plan(PlannerInfo *root,
static HashJoin * static HashJoin *
create_hashjoin_plan(PlannerInfo *root, create_hashjoin_plan(PlannerInfo *root,
HashPath *best_path, HashPath *best_path)
Plan *outer_plan,
Plan *inner_plan)
{ {
HashJoin *join_plan;
Hash *hash_plan;
Plan *outer_plan;
Plan *inner_plan;
List *tlist = build_path_tlist(root, &best_path->jpath.path); List *tlist = build_path_tlist(root, &best_path->jpath.path);
List *joinclauses; List *joinclauses;
List *otherclauses; List *otherclauses;
...@@ -2702,8 +3769,19 @@ create_hashjoin_plan(PlannerInfo *root, ...@@ -2702,8 +3769,19 @@ create_hashjoin_plan(PlannerInfo *root,
bool skewInherit = false; bool skewInherit = false;
Oid skewColType = InvalidOid; Oid skewColType = InvalidOid;
int32 skewColTypmod = -1; int32 skewColTypmod = -1;
HashJoin *join_plan;
Hash *hash_plan; /*
* HashJoin can project, so we don't have to demand exact tlists from the
* inputs. However, it's best to request a small tlist from the inner
* side, so that we aren't storing more data than necessary. Likewise, if
* we anticipate batching, request a small tlist from the outer side so
* that we don't put extra data in the outer batch files.
*/
outer_plan = create_plan_recurse(root, best_path->jpath.outerjoinpath,
(best_path->num_batches > 1) ? CP_SMALL_TLIST : 0);
inner_plan = create_plan_recurse(root, best_path->jpath.innerjoinpath,
CP_SMALL_TLIST);
/* Sort join qual clauses into best execution order */ /* Sort join qual clauses into best execution order */
joinclauses = order_qual_clauses(root, best_path->jpath.joinrestrictinfo); joinclauses = order_qual_clauses(root, best_path->jpath.joinrestrictinfo);
...@@ -2749,13 +3827,6 @@ create_hashjoin_plan(PlannerInfo *root, ...@@ -2749,13 +3827,6 @@ create_hashjoin_plan(PlannerInfo *root,
hashclauses = get_switched_clauses(best_path->path_hashclauses, hashclauses = get_switched_clauses(best_path->path_hashclauses,
best_path->jpath.outerjoinpath->parent->relids); best_path->jpath.outerjoinpath->parent->relids);
/* We don't want any excess columns in the hashed tuples */
disuse_physical_tlist(root, inner_plan, best_path->jpath.innerjoinpath);
/* If we expect batching, suppress excess columns in outer tuples too */
if (best_path->num_batches > 1)
disuse_physical_tlist(root, outer_plan, best_path->jpath.outerjoinpath);
/* /*
* If there is a single join clause and we can identify the outer variable * If there is a single join clause and we can identify the outer variable
* as a simple column reference, supply its identity for possible use in * as a simple column reference, supply its identity for possible use in
...@@ -3661,7 +4732,7 @@ make_tidscan(List *qptlist, ...@@ -3661,7 +4732,7 @@ make_tidscan(List *qptlist,
return node; return node;
} }
SubqueryScan * static SubqueryScan *
make_subqueryscan(List *qptlist, make_subqueryscan(List *qptlist,
List *qpqual, List *qpqual,
Index scanrelid, Index scanrelid,
...@@ -3805,7 +4876,7 @@ make_foreignscan(List *qptlist, ...@@ -3805,7 +4876,7 @@ make_foreignscan(List *qptlist,
return node; return node;
} }
Append * static Append *
make_append(List *appendplans, List *tlist) make_append(List *appendplans, List *tlist)
{ {
Append *node = makeNode(Append); Append *node = makeNode(Append);
...@@ -3852,7 +4923,7 @@ make_append(List *appendplans, List *tlist) ...@@ -3852,7 +4923,7 @@ make_append(List *appendplans, List *tlist)
return node; return node;
} }
RecursiveUnion * static RecursiveUnion *
make_recursive_union(List *tlist, make_recursive_union(List *tlist,
Plan *lefttree, Plan *lefttree,
Plan *righttree, Plan *righttree,
...@@ -3864,8 +4935,6 @@ make_recursive_union(List *tlist, ...@@ -3864,8 +4935,6 @@ make_recursive_union(List *tlist,
Plan *plan = &node->plan; Plan *plan = &node->plan;
int numCols = list_length(distinctList); int numCols = list_length(distinctList);
cost_recursive_union(plan, lefttree, righttree);
plan->targetlist = tlist; plan->targetlist = tlist;
plan->qual = NIL; plan->qual = NIL;
plan->lefttree = lefttree; plan->lefttree = lefttree;
...@@ -4408,7 +5477,7 @@ find_ec_member_for_tle(EquivalenceClass *ec, ...@@ -4408,7 +5477,7 @@ find_ec_member_for_tle(EquivalenceClass *ec,
* 'limit_tuples' is the bound on the number of output tuples; * 'limit_tuples' is the bound on the number of output tuples;
* -1 if no bound * -1 if no bound
*/ */
Sort * static Sort *
make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys,
double limit_tuples) double limit_tuples)
{ {
...@@ -4442,7 +5511,7 @@ make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, ...@@ -4442,7 +5511,7 @@ make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys,
* 'sortcls' is a list of SortGroupClauses * 'sortcls' is a list of SortGroupClauses
* 'lefttree' is the node which yields input tuples * 'lefttree' is the node which yields input tuples
*/ */
Sort * static Sort *
make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, Plan *lefttree) make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, Plan *lefttree)
{ {
List *sub_tlist = lefttree->targetlist; List *sub_tlist = lefttree->targetlist;
...@@ -4491,7 +5560,7 @@ make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, Plan *lefttree) ...@@ -4491,7 +5560,7 @@ make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, Plan *lefttree)
* appropriate to the grouping node. So, only the sort ordering info * appropriate to the grouping node. So, only the sort ordering info
* is used from the SortGroupClause entries. * is used from the SortGroupClause entries.
*/ */
Sort * static Sort *
make_sort_from_groupcols(PlannerInfo *root, make_sort_from_groupcols(PlannerInfo *root,
List *groupcls, List *groupcls,
AttrNumber *grpColIdx, AttrNumber *grpColIdx,
...@@ -4552,7 +5621,7 @@ make_material(Plan *lefttree) ...@@ -4552,7 +5621,7 @@ make_material(Plan *lefttree)
* materialize_finished_plan: stick a Material node atop a completed plan * materialize_finished_plan: stick a Material node atop a completed plan
* *
* There are a couple of places where we want to attach a Material node * There are a couple of places where we want to attach a Material node
* after completion of subquery_planner(), without any MaterialPath path. * after completion of create_plan(), without any MaterialPath path.
*/ */
Plan * Plan *
materialize_finished_plan(Plan *subplan) materialize_finished_plan(Plan *subplan)
...@@ -4572,81 +5641,46 @@ materialize_finished_plan(Plan *subplan) ...@@ -4572,81 +5641,46 @@ materialize_finished_plan(Plan *subplan)
matplan->total_cost = matpath.total_cost; matplan->total_cost = matpath.total_cost;
matplan->plan_rows = subplan->plan_rows; matplan->plan_rows = subplan->plan_rows;
matplan->plan_width = subplan->plan_width; matplan->plan_width = subplan->plan_width;
matplan->parallel_aware = false;
return matplan; return matplan;
} }
Agg * static Agg *
make_agg(PlannerInfo *root, List *tlist, List *qual, make_agg(List *tlist, List *qual,
AggStrategy aggstrategy, const AggClauseCosts *aggcosts, AggStrategy aggstrategy,
bool combineStates, bool finalizeAggs,
int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators, int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
List *groupingSets, long numGroups, bool combineStates, List *groupingSets, List *chain,
bool finalizeAggs, Plan *lefttree) double dNumGroups, Plan *lefttree)
{ {
Agg *node = makeNode(Agg); Agg *node = makeNode(Agg);
Plan *plan = &node->plan; Plan *plan = &node->plan;
Path agg_path; /* dummy for result of cost_agg */ long numGroups;
QualCost qual_cost;
/* Reduce to long, but 'ware overflow! */
numGroups = (long) Min(dNumGroups, (double) LONG_MAX);
node->aggstrategy = aggstrategy; node->aggstrategy = aggstrategy;
node->numCols = numGroupCols;
node->combineStates = combineStates; node->combineStates = combineStates;
node->finalizeAggs = finalizeAggs; node->finalizeAggs = finalizeAggs;
node->numCols = numGroupCols;
node->grpColIdx = grpColIdx; node->grpColIdx = grpColIdx;
node->grpOperators = grpOperators; node->grpOperators = grpOperators;
node->numGroups = numGroups; node->numGroups = numGroups;
copy_plan_costsize(plan, lefttree); /* only care about copying size */
cost_agg(&agg_path, root,
aggstrategy, aggcosts,
numGroupCols, numGroups,
lefttree->startup_cost,
lefttree->total_cost,
lefttree->plan_rows);
plan->startup_cost = agg_path.startup_cost;
plan->total_cost = agg_path.total_cost;
/*
* We will produce a single output tuple if not grouping, and a tuple per
* group otherwise.
*/
if (aggstrategy == AGG_PLAIN)
plan->plan_rows = groupingSets ? list_length(groupingSets) : 1;
else
plan->plan_rows = numGroups;
node->groupingSets = groupingSets; node->groupingSets = groupingSets;
node->chain = chain;
/*
* We also need to account for the cost of evaluation of the qual (ie, the
* HAVING clause) and the tlist. Note that cost_qual_eval doesn't charge
* anything for Aggref nodes; this is okay since they are really
* comparable to Vars.
*
* See notes in add_tlist_costs_to_plan about why only make_agg,
* make_windowagg and make_group worry about tlist eval cost.
*/
if (qual)
{
cost_qual_eval(&qual_cost, qual, root);
plan->startup_cost += qual_cost.startup;
plan->total_cost += qual_cost.startup;
plan->total_cost += qual_cost.per_tuple * plan->plan_rows;
}
add_tlist_costs_to_plan(root, plan, tlist);
plan->qual = qual; plan->qual = qual;
plan->targetlist = tlist; plan->targetlist = tlist;
plan->lefttree = lefttree; plan->lefttree = lefttree;
plan->righttree = NULL; plan->righttree = NULL;
return node; return node;
} }
WindowAgg * static WindowAgg *
make_windowagg(PlannerInfo *root, List *tlist, make_windowagg(List *tlist, Index winref,
List *windowFuncs, Index winref,
int partNumCols, AttrNumber *partColIdx, Oid *partOperators, int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators, int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
int frameOptions, Node *startOffset, Node *endOffset, int frameOptions, Node *startOffset, Node *endOffset,
...@@ -4654,7 +5688,6 @@ make_windowagg(PlannerInfo *root, List *tlist, ...@@ -4654,7 +5688,6 @@ make_windowagg(PlannerInfo *root, List *tlist,
{ {
WindowAgg *node = makeNode(WindowAgg); WindowAgg *node = makeNode(WindowAgg);
Plan *plan = &node->plan; Plan *plan = &node->plan;
Path windowagg_path; /* dummy for result of cost_windowagg */
node->winref = winref; node->winref = winref;
node->partNumCols = partNumCols; node->partNumCols = partNumCols;
...@@ -4667,23 +5700,6 @@ make_windowagg(PlannerInfo *root, List *tlist, ...@@ -4667,23 +5700,6 @@ make_windowagg(PlannerInfo *root, List *tlist,
node->startOffset = startOffset; node->startOffset = startOffset;
node->endOffset = endOffset; node->endOffset = endOffset;
copy_plan_costsize(plan, lefttree); /* only care about copying size */
cost_windowagg(&windowagg_path, root,
windowFuncs, partNumCols, ordNumCols,
lefttree->startup_cost,
lefttree->total_cost,
lefttree->plan_rows);
plan->startup_cost = windowagg_path.startup_cost;
plan->total_cost = windowagg_path.total_cost;
/*
* We also need to account for the cost of evaluation of the tlist.
*
* See notes in add_tlist_costs_to_plan about why only make_agg,
* make_windowagg and make_group worry about tlist eval cost.
*/
add_tlist_costs_to_plan(root, plan, tlist);
plan->targetlist = tlist; plan->targetlist = tlist;
plan->lefttree = lefttree; plan->lefttree = lefttree;
plan->righttree = NULL; plan->righttree = NULL;
...@@ -4693,58 +5709,23 @@ make_windowagg(PlannerInfo *root, List *tlist, ...@@ -4693,58 +5709,23 @@ make_windowagg(PlannerInfo *root, List *tlist,
return node; return node;
} }
Group * static Group *
make_group(PlannerInfo *root, make_group(List *tlist,
List *tlist,
List *qual, List *qual,
int numGroupCols, int numGroupCols,
AttrNumber *grpColIdx, AttrNumber *grpColIdx,
Oid *grpOperators, Oid *grpOperators,
double numGroups,
Plan *lefttree) Plan *lefttree)
{ {
Group *node = makeNode(Group); Group *node = makeNode(Group);
Plan *plan = &node->plan; Plan *plan = &node->plan;
Path group_path; /* dummy for result of cost_group */
QualCost qual_cost; /* caller must fill cost/size fields */
node->numCols = numGroupCols; node->numCols = numGroupCols;
node->grpColIdx = grpColIdx; node->grpColIdx = grpColIdx;
node->grpOperators = grpOperators; node->grpOperators = grpOperators;
copy_plan_costsize(plan, lefttree); /* only care about copying size */
cost_group(&group_path, root,
numGroupCols, numGroups,
lefttree->startup_cost,
lefttree->total_cost,
lefttree->plan_rows);
plan->startup_cost = group_path.startup_cost;
plan->total_cost = group_path.total_cost;
/* One output tuple per estimated result group */
plan->plan_rows = numGroups;
/*
* We also need to account for the cost of evaluation of the qual (ie, the
* HAVING clause) and the tlist.
*
* XXX this double-counts the cost of evaluation of any expressions used
* for grouping, since in reality those will have been evaluated at a
* lower plan level and will only be copied by the Group node. Worth
* fixing?
*
* See notes in add_tlist_costs_to_plan about why only make_agg,
* make_windowagg and make_group worry about tlist eval cost.
*/
if (qual)
{
cost_qual_eval(&qual_cost, qual, root);
plan->startup_cost += qual_cost.startup;
plan->total_cost += qual_cost.startup;
plan->total_cost += qual_cost.per_tuple * plan->plan_rows;
}
add_tlist_costs_to_plan(root, plan, tlist);
plan->qual = qual; plan->qual = qual;
plan->targetlist = tlist; plan->targetlist = tlist;
plan->lefttree = lefttree; plan->lefttree = lefttree;
...@@ -4758,8 +5739,8 @@ make_group(PlannerInfo *root, ...@@ -4758,8 +5739,8 @@ make_group(PlannerInfo *root,
* that should be considered by the Unique filter. The input path must * that should be considered by the Unique filter. The input path must
* already be sorted accordingly. * already be sorted accordingly.
*/ */
Unique * static Unique *
make_unique(Plan *lefttree, List *distinctList) make_unique_from_sortclauses(Plan *lefttree, List *distinctList)
{ {
Unique *node = makeNode(Unique); Unique *node = makeNode(Unique);
Plan *plan = &node->plan; Plan *plan = &node->plan;
...@@ -4769,21 +5750,6 @@ make_unique(Plan *lefttree, List *distinctList) ...@@ -4769,21 +5750,6 @@ make_unique(Plan *lefttree, List *distinctList)
Oid *uniqOperators; Oid *uniqOperators;
ListCell *slitem; ListCell *slitem;
copy_plan_costsize(plan, lefttree);
/*
* Charge one cpu_operator_cost per comparison per input tuple. We assume
* all columns get compared at most of the tuples. (XXX probably this is
* an overestimate.)
*/
plan->total_cost += cpu_operator_cost * plan->plan_rows * numCols;
/*
* plan->plan_rows is left as a copy of the input subplan's plan_rows; ie,
* we assume the filter removes nothing. The caller must alter this if he
* has a better idea.
*/
plan->targetlist = lefttree->targetlist; plan->targetlist = lefttree->targetlist;
plan->qual = NIL; plan->qual = NIL;
plan->lefttree = lefttree; plan->lefttree = lefttree;
...@@ -4815,6 +5781,111 @@ make_unique(Plan *lefttree, List *distinctList) ...@@ -4815,6 +5781,111 @@ make_unique(Plan *lefttree, List *distinctList)
return node; return node;
} }
/*
* as above, but use pathkeys to identify the sort columns and semantics
*/
static Unique *
make_unique_from_pathkeys(Plan *lefttree, List *pathkeys, int numCols)
{
Unique *node = makeNode(Unique);
Plan *plan = &node->plan;
int keyno = 0;
AttrNumber *uniqColIdx;
Oid *uniqOperators;
ListCell *lc;
plan->targetlist = lefttree->targetlist;
plan->qual = NIL;
plan->lefttree = lefttree;
plan->righttree = NULL;
/*
* Convert pathkeys list into arrays of attr indexes and equality
* operators, as wanted by executor. This has a lot in common with
* prepare_sort_from_pathkeys ... maybe unify sometime?
*/
Assert(numCols >= 0 && numCols <= list_length(pathkeys));
uniqColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numCols);
uniqOperators = (Oid *) palloc(sizeof(Oid) * numCols);
foreach(lc, pathkeys)
{
PathKey *pathkey = (PathKey *) lfirst(lc);
EquivalenceClass *ec = pathkey->pk_eclass;
EquivalenceMember *em;
TargetEntry *tle = NULL;
Oid pk_datatype = InvalidOid;
Oid eqop;
ListCell *j;
/* Ignore pathkeys beyond the specified number of columns */
if (keyno >= numCols)
break;
if (ec->ec_has_volatile)
{
/*
* If the pathkey's EquivalenceClass is volatile, then it must
* have come from an ORDER BY clause, and we have to match it to
* that same targetlist entry.
*/
if (ec->ec_sortref == 0) /* can't happen */
elog(ERROR, "volatile EquivalenceClass has no sortref");
tle = get_sortgroupref_tle(ec->ec_sortref, plan->targetlist);
Assert(tle);
Assert(list_length(ec->ec_members) == 1);
pk_datatype = ((EquivalenceMember *) linitial(ec->ec_members))->em_datatype;
}
else
{
/*
* Otherwise, we can use any non-constant expression listed in the
* pathkey's EquivalenceClass. For now, we take the first tlist
* item found in the EC.
*/
foreach(j, plan->targetlist)
{
tle = (TargetEntry *) lfirst(j);
em = find_ec_member_for_tle(ec, tle, NULL);
if (em)
{
/* found expr already in tlist */
pk_datatype = em->em_datatype;
break;
}
tle = NULL;
}
}
if (!tle)
elog(ERROR, "could not find pathkey item to sort");
/*
* Look up the correct equality operator from the PathKey's slightly
* abstracted representation.
*/
eqop = get_opfamily_member(pathkey->pk_opfamily,
pk_datatype,
pk_datatype,
BTEqualStrategyNumber);
if (!OidIsValid(eqop)) /* should not happen */
elog(ERROR, "could not find member %d(%u,%u) of opfamily %u",
BTEqualStrategyNumber, pk_datatype, pk_datatype,
pathkey->pk_opfamily);
uniqColIdx[keyno] = tle->resno;
uniqOperators[keyno] = eqop;
keyno++;
}
node->numCols = numCols;
node->uniqColIdx = uniqColIdx;
node->uniqOperators = uniqOperators;
return node;
}
static Gather * static Gather *
make_gather(List *qptlist, make_gather(List *qptlist,
List *qpqual, List *qpqual,
...@@ -4842,10 +5913,10 @@ make_gather(List *qptlist, ...@@ -4842,10 +5913,10 @@ make_gather(List *qptlist,
* items that should be considered by the SetOp filter. The input path must * items that should be considered by the SetOp filter. The input path must
* already be sorted accordingly. * already be sorted accordingly.
*/ */
SetOp * static SetOp *
make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree, make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
List *distinctList, AttrNumber flagColIdx, int firstFlag, List *distinctList, AttrNumber flagColIdx, int firstFlag,
long numGroups, double outputRows) long numGroups)
{ {
SetOp *node = makeNode(SetOp); SetOp *node = makeNode(SetOp);
Plan *plan = &node->plan; Plan *plan = &node->plan;
...@@ -4855,15 +5926,6 @@ make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree, ...@@ -4855,15 +5926,6 @@ make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
Oid *dupOperators; Oid *dupOperators;
ListCell *slitem; ListCell *slitem;
copy_plan_costsize(plan, lefttree);
plan->plan_rows = outputRows;
/*
* Charge one cpu_operator_cost per comparison per input tuple. We assume
* all columns get compared at most of the tuples.
*/
plan->total_cost += cpu_operator_cost * lefttree->plan_rows * numCols;
plan->targetlist = lefttree->targetlist; plan->targetlist = lefttree->targetlist;
plan->qual = NIL; plan->qual = NIL;
plan->lefttree = lefttree; plan->lefttree = lefttree;
...@@ -4904,17 +5966,12 @@ make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree, ...@@ -4904,17 +5966,12 @@ make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
* make_lockrows * make_lockrows
* Build a LockRows plan node * Build a LockRows plan node
*/ */
LockRows * static LockRows *
make_lockrows(Plan *lefttree, List *rowMarks, int epqParam) make_lockrows(Plan *lefttree, List *rowMarks, int epqParam)
{ {
LockRows *node = makeNode(LockRows); LockRows *node = makeNode(LockRows);
Plan *plan = &node->plan; Plan *plan = &node->plan;
copy_plan_costsize(plan, lefttree);
/* charge cpu_tuple_cost to reflect locking costs (underestimate?) */
plan->total_cost += cpu_tuple_cost * plan->plan_rows;
plan->targetlist = lefttree->targetlist; plan->targetlist = lefttree->targetlist;
plan->qual = NIL; plan->qual = NIL;
plan->lefttree = lefttree; plan->lefttree = lefttree;
...@@ -4927,68 +5984,15 @@ make_lockrows(Plan *lefttree, List *rowMarks, int epqParam) ...@@ -4927,68 +5984,15 @@ make_lockrows(Plan *lefttree, List *rowMarks, int epqParam)
} }
/* /*
* Note: offset_est and count_est are passed in to save having to repeat * make_limit
* work already done to estimate the values of the limitOffset and limitCount * Build a Limit plan node
* expressions. Their values are as returned by preprocess_limit (0 means
* "not relevant", -1 means "couldn't estimate"). Keep the code below in sync
* with that function!
*/ */
Limit * static Limit *
make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount, make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount)
int64 offset_est, int64 count_est)
{ {
Limit *node = makeNode(Limit); Limit *node = makeNode(Limit);
Plan *plan = &node->plan; Plan *plan = &node->plan;
copy_plan_costsize(plan, lefttree);
/*
* Adjust the output rows count and costs according to the offset/limit.
* This is only a cosmetic issue if we are at top level, but if we are
* building a subquery then it's important to report correct info to the
* outer planner.
*
* When the offset or count couldn't be estimated, use 10% of the
* estimated number of rows emitted from the subplan.
*/
if (offset_est != 0)
{
double offset_rows;
if (offset_est > 0)
offset_rows = (double) offset_est;
else
offset_rows = clamp_row_est(lefttree->plan_rows * 0.10);
if (offset_rows > plan->plan_rows)
offset_rows = plan->plan_rows;
if (plan->plan_rows > 0)
plan->startup_cost +=
(plan->total_cost - plan->startup_cost)
* offset_rows / plan->plan_rows;
plan->plan_rows -= offset_rows;
if (plan->plan_rows < 1)
plan->plan_rows = 1;
}
if (count_est != 0)
{
double count_rows;
if (count_est > 0)
count_rows = (double) count_est;
else
count_rows = clamp_row_est(lefttree->plan_rows * 0.10);
if (count_rows > plan->plan_rows)
count_rows = plan->plan_rows;
if (plan->plan_rows > 0)
plan->total_cost = plan->startup_cost +
(plan->total_cost - plan->startup_cost)
* count_rows / plan->plan_rows;
plan->plan_rows = count_rows;
if (plan->plan_rows < 1)
plan->plan_rows = 1;
}
plan->targetlist = lefttree->targetlist; plan->targetlist = lefttree->targetlist;
plan->qual = NIL; plan->qual = NIL;
plan->lefttree = lefttree; plan->lefttree = lefttree;
...@@ -5008,8 +6012,9 @@ make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount, ...@@ -5008,8 +6012,9 @@ make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount,
* were already factored into the subplan's startup cost, and just copy the * were already factored into the subplan's startup cost, and just copy the
* subplan cost. If there's no subplan, we should include the qual eval * subplan cost. If there's no subplan, we should include the qual eval
* cost. In either case, tlist eval cost is not to be included here. * cost. In either case, tlist eval cost is not to be included here.
* XXX really we don't want to be doing cost estimation here.
*/ */
Result * static Result *
make_result(PlannerInfo *root, make_result(PlannerInfo *root,
List *tlist, List *tlist,
Node *resconstantqual, Node *resconstantqual,
...@@ -5049,14 +6054,8 @@ make_result(PlannerInfo *root, ...@@ -5049,14 +6054,8 @@ make_result(PlannerInfo *root,
/* /*
* make_modifytable * make_modifytable
* Build a ModifyTable plan node * Build a ModifyTable plan node
*
* Currently, we don't charge anything extra for the actual table modification
* work, nor for the WITH CHECK OPTIONS or RETURNING expressions if any. It
* would only be window dressing, since these are always top-level nodes and
* there is no way for the costs to change any higher-level planning choices.
* But we might want to make it look better sometime.
*/ */
ModifyTable * static ModifyTable *
make_modifytable(PlannerInfo *root, make_modifytable(PlannerInfo *root,
CmdType operation, bool canSetTag, CmdType operation, bool canSetTag,
Index nominalRelation, Index nominalRelation,
...@@ -5065,10 +6064,7 @@ make_modifytable(PlannerInfo *root, ...@@ -5065,10 +6064,7 @@ make_modifytable(PlannerInfo *root,
List *rowMarks, OnConflictExpr *onconflict, int epqParam) List *rowMarks, OnConflictExpr *onconflict, int epqParam)
{ {
ModifyTable *node = makeNode(ModifyTable); ModifyTable *node = makeNode(ModifyTable);
Plan *plan = &node->plan;
double total_size;
List *fdw_private_list; List *fdw_private_list;
ListCell *subnode;
ListCell *lc; ListCell *lc;
int i; int i;
...@@ -5078,28 +6074,6 @@ make_modifytable(PlannerInfo *root, ...@@ -5078,28 +6074,6 @@ make_modifytable(PlannerInfo *root,
Assert(returningLists == NIL || Assert(returningLists == NIL ||
list_length(resultRelations) == list_length(returningLists)); list_length(resultRelations) == list_length(returningLists));
/*
* Compute cost as sum of subplan costs.
*/
plan->startup_cost = 0;
plan->total_cost = 0;
plan->plan_rows = 0;
total_size = 0;
foreach(subnode, subplans)
{
Plan *subplan = (Plan *) lfirst(subnode);
if (subnode == list_head(subplans)) /* first node? */
plan->startup_cost = subplan->startup_cost;
plan->total_cost += subplan->total_cost;
plan->plan_rows += subplan->plan_rows;
total_size += subplan->plan_width * subplan->plan_rows;
}
if (plan->plan_rows > 0)
plan->plan_width = rint(total_size / plan->plan_rows);
else
plan->plan_width = 0;
node->plan.lefttree = NULL; node->plan.lefttree = NULL;
node->plan.righttree = NULL; node->plan.righttree = NULL;
node->plan.qual = NIL; node->plan.qual = NIL;
...@@ -5193,6 +6167,42 @@ make_modifytable(PlannerInfo *root, ...@@ -5193,6 +6167,42 @@ make_modifytable(PlannerInfo *root,
return node; return node;
} }
/*
* is_projection_capable_path
* Check whether a given Path node is able to do projection.
*/
bool
is_projection_capable_path(Path *path)
{
/* Most plan types can project, so just list the ones that can't */
switch (path->pathtype)
{
case T_Hash:
case T_Material:
case T_Sort:
case T_Unique:
case T_SetOp:
case T_LockRows:
case T_Limit:
case T_ModifyTable:
case T_MergeAppend:
case T_RecursiveUnion:
return false;
case T_Append:
/*
* Append can't project, but if it's being used to represent a
* dummy path, claim that it can project. This prevents us from
* converting a rel from dummy to non-dummy status by applying a
* projection to its dummy path.
*/
return IS_DUMMY_PATH(path);
default:
break;
}
return true;
}
/* /*
* is_projection_capable_plan * is_projection_capable_plan
* Check whether a given Plan node is able to do projection. * Check whether a given Plan node is able to do projection.
......
...@@ -35,13 +35,14 @@ ...@@ -35,13 +35,14 @@
#include "nodes/nodeFuncs.h" #include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h" #include "optimizer/clauses.h"
#include "optimizer/cost.h" #include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h" #include "optimizer/paths.h"
#include "optimizer/planmain.h" #include "optimizer/planmain.h"
#include "optimizer/planner.h"
#include "optimizer/subselect.h" #include "optimizer/subselect.h"
#include "optimizer/tlist.h" #include "optimizer/tlist.h"
#include "parser/parsetree.h" #include "parser/parsetree.h"
#include "parser/parse_clause.h" #include "parser/parse_clause.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/syscache.h" #include "utils/syscache.h"
...@@ -50,8 +51,6 @@ static bool find_minmax_aggs_walker(Node *node, List **context); ...@@ -50,8 +51,6 @@ static bool find_minmax_aggs_walker(Node *node, List **context);
static bool build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, static bool build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
Oid eqop, Oid sortop, bool nulls_first); Oid eqop, Oid sortop, bool nulls_first);
static void minmax_qp_callback(PlannerInfo *root, void *extra); static void minmax_qp_callback(PlannerInfo *root, void *extra);
static void make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *mminfo);
static Node *replace_aggs_with_params_mutator(Node *node, PlannerInfo *root);
static Oid fetch_agg_sort_op(Oid aggfnoid); static Oid fetch_agg_sort_op(Oid aggfnoid);
...@@ -60,8 +59,14 @@ static Oid fetch_agg_sort_op(Oid aggfnoid); ...@@ -60,8 +59,14 @@ static Oid fetch_agg_sort_op(Oid aggfnoid);
* *
* Check to see whether the query contains MIN/MAX aggregate functions that * Check to see whether the query contains MIN/MAX aggregate functions that
* might be optimizable via indexscans. If it does, and all the aggregates * might be optimizable via indexscans. If it does, and all the aggregates
* are potentially optimizable, then set up root->minmax_aggs with a list of * are potentially optimizable, then create a MinMaxAggPath and add it to
* these aggregates. * the (UPPERREL_GROUP_AGG, NULL) upperrel.
*
* This should be called by grouping_planner() just before it's ready to call
* query_planner(), because we generate indexscan paths by cloning the
* planner's state and invoking query_planner() on a modified version of
* the query parsetree. Thus, all preprocessing needed before query_planner()
* must already be done.
* *
* Note: we are passed the preprocessed targetlist separately, because it's * Note: we are passed the preprocessed targetlist separately, because it's
* not necessarily equal to root->parse->targetList. * not necessarily equal to root->parse->targetList.
...@@ -74,6 +79,7 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist) ...@@ -74,6 +79,7 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
RangeTblRef *rtr; RangeTblRef *rtr;
RangeTblEntry *rte; RangeTblEntry *rte;
List *aggs_list; List *aggs_list;
RelOptInfo *grouped_rel;
ListCell *lc; ListCell *lc;
/* minmax_aggs list should be empty at this point */ /* minmax_aggs list should be empty at this point */
...@@ -91,12 +97,10 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist) ...@@ -91,12 +97,10 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
* *
* We don't handle GROUP BY or windowing, because our current * We don't handle GROUP BY or windowing, because our current
* implementations of grouping require looking at all the rows anyway, and * implementations of grouping require looking at all the rows anyway, and
* so there's not much point in optimizing MIN/MAX. (Note: relaxing this * so there's not much point in optimizing MIN/MAX.
* would likely require some restructuring in grouping_planner(), since it
* performs assorted processing related to these features between calling
* preprocess_minmax_aggregates and optimize_minmax_aggregates.)
*/ */
if (parse->groupClause || list_length(parse->groupingSets) > 1 || parse->hasWindowFuncs) if (parse->groupClause || list_length(parse->groupingSets) > 1 ||
parse->hasWindowFuncs)
return; return;
/* /*
...@@ -138,11 +142,9 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist) ...@@ -138,11 +142,9 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
/* /*
* OK, there is at least the possibility of performing the optimization. * OK, there is at least the possibility of performing the optimization.
* Build an access path for each aggregate. (We must do this now because * Build an access path for each aggregate. If any of the aggregates
* we need to call query_planner with a pristine copy of the current query * prove to be non-indexable, give up; there is no point in optimizing
* tree; it'll be too late when optimize_minmax_aggregates gets called.) * just some of them.
* If any of the aggregates prove to be non-indexable, give up; there is
* no point in optimizing just some of them.
*/ */
foreach(lc, aggs_list) foreach(lc, aggs_list)
{ {
...@@ -177,111 +179,40 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist) ...@@ -177,111 +179,40 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
} }
/* /*
* We're done until path generation is complete. Save info for later. * OK, we can do the query this way. Prepare to create a MinMaxAggPath
* (Setting root->minmax_aggs non-NIL signals we succeeded in making index * node.
* access paths for all the aggregates.)
*/
root->minmax_aggs = aggs_list;
}
/*
* optimize_minmax_aggregates - check for optimizing MIN/MAX via indexes
*
* Check to see whether using the aggregate indexscans is cheaper than the
* generic aggregate method. If so, generate and return a Plan that does it
* that way. Otherwise, return NULL.
*
* Note: it seems likely that the generic method will never be cheaper
* in practice, except maybe for tiny tables where it'd hardly matter.
* Should we skip even trying to build the standard plan, if
* preprocess_minmax_aggregates succeeds?
*
* We are passed the preprocessed tlist, as well as the estimated costs for
* doing the aggregates the regular way, and the best path devised for
* computing the input of a standard Agg node.
*/
Plan *
optimize_minmax_aggregates(PlannerInfo *root, List *tlist,
const AggClauseCosts *aggcosts, Path *best_path)
{
Query *parse = root->parse;
Cost total_cost;
Path agg_p;
Plan *plan;
Node *hqual;
ListCell *lc;
/* Nothing to do if preprocess_minmax_aggs rejected the query */
if (root->minmax_aggs == NIL)
return NULL;
/*
* Now we have enough info to compare costs against the generic aggregate
* implementation.
* *
* Note that we don't include evaluation cost of the tlist here; this is * First, create an output Param node for each agg. (If we end up not
* OK since it isn't included in best_path's cost either, and should be * using the MinMaxAggPath, we'll waste a PARAM_EXEC slot for each agg,
* the same in either case. * which is not worth worrying about. We can't wait till create_plan time
* to decide whether to make the Param, unfortunately.)
*/ */
total_cost = 0; foreach(lc, aggs_list)
foreach(lc, root->minmax_aggs)
{ {
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc); MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
total_cost += mminfo->pathcost; mminfo->param =
SS_make_initplan_output_param(root,
exprType((Node *) mminfo->target),
-1,
exprCollation((Node *) mminfo->target));
} }
cost_agg(&agg_p, root, AGG_PLAIN, aggcosts,
0, 0,
best_path->startup_cost, best_path->total_cost,
best_path->parent->rows);
if (total_cost > agg_p.total_cost)
return NULL; /* too expensive */
/* /*
* OK, we are going to generate an optimized plan. * Create a MinMaxAggPath node with the appropriate estimated costs and
* other needed data, and add it to the UPPERREL_GROUP_AGG upperrel, where
* it will compete against the standard aggregate implementation. (It
* will likely always win, but we need not assume that here.)
* *
* First, generate a subplan and output Param node for each agg. * Note: grouping_planner won't have created this upperrel yet, but it's
*/ * fine for us to create it first.
foreach(lc, root->minmax_aggs)
{
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
make_agg_subplan(root, mminfo);
}
/*
* Modify the targetlist and HAVING qual to reference subquery outputs
*/ */
tlist = (List *) replace_aggs_with_params_mutator((Node *) tlist, root); grouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG, NULL);
hqual = replace_aggs_with_params_mutator(parse->havingQual, root); add_path(grouped_rel, (Path *)
create_minmaxagg_path(root, grouped_rel,
/* create_pathtarget(root, tlist),
* We have to replace Aggrefs with Params in equivalence classes too, else aggs_list,
* ORDER BY or DISTINCT on an optimized aggregate will fail. We don't (List *) parse->havingQual));
* need to process child eclass members though, since they aren't of
* interest anymore --- and replace_aggs_with_params_mutator isn't able to
* handle Aggrefs containing translated child Vars, anyway.
*
* Note: at some point it might become necessary to mutate other data
* structures too, such as the query's sortClause or distinctClause. Right
* now, those won't be examined after this point.
*/
mutate_eclass_expressions(root,
replace_aggs_with_params_mutator,
(void *) root,
false);
/*
* Generate the output plan --- basically just a Result
*/
plan = (Plan *) make_result(root, tlist, hqual, NULL);
/* Account for evaluation cost of the tlist (make_result did the rest) */
add_tlist_costs_to_plan(root, plan, tlist);
return plan;
} }
/* /*
...@@ -403,6 +334,7 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, ...@@ -403,6 +334,7 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
PlannerInfo *subroot; PlannerInfo *subroot;
Query *parse; Query *parse;
TargetEntry *tle; TargetEntry *tle;
List *tlist;
NullTest *ntest; NullTest *ntest;
SortGroupClause *sortcl; SortGroupClause *sortcl;
RelOptInfo *final_rel; RelOptInfo *final_rel;
...@@ -410,40 +342,51 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, ...@@ -410,40 +342,51 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
Cost path_cost; Cost path_cost;
double path_fraction; double path_fraction;
/*---------- /*
* Generate modified query of the form * We are going to construct what is effectively a sub-SELECT query, so
* (SELECT col FROM tab * clone the current query level's state and adjust it to make it look
* WHERE col IS NOT NULL AND existing-quals * like a subquery. Any outer references will now be one level higher
* ORDER BY col ASC/DESC * than before. (This means that when we are done, there will be no Vars
* LIMIT 1) * of level 1, which is why the subquery can become an initplan.)
*
* We cheat a bit here by building what is effectively a subplan query
* level without taking the trouble to increment varlevelsup of outer
* references. Therefore we don't increment the subroot's query_level nor
* repoint its parent_root to the parent level. We can get away with that
* because the plan will be an initplan and therefore cannot need any
* parameters from the parent level. But see hackery in make_agg_subplan;
* we might someday need to do this the hard way.
*----------
*/ */
subroot = (PlannerInfo *) palloc(sizeof(PlannerInfo)); subroot = (PlannerInfo *) palloc(sizeof(PlannerInfo));
memcpy(subroot, root, sizeof(PlannerInfo)); memcpy(subroot, root, sizeof(PlannerInfo));
subroot->parse = parse = (Query *) copyObject(root->parse); subroot->query_level++;
subroot->parent_root = root;
/* reset subplan-related stuff */ /* reset subplan-related stuff */
subroot->plan_params = NIL; subroot->plan_params = NIL;
subroot->outer_params = NULL; subroot->outer_params = NULL;
subroot->init_plans = NIL; subroot->init_plans = NIL;
subroot->cte_plan_ids = NIL;
subroot->parse = parse = (Query *) copyObject(root->parse);
IncrementVarSublevelsUp((Node *) parse, 1, 1);
/* append_rel_list might contain outer Vars? */
subroot->append_rel_list = (List *) copyObject(root->append_rel_list);
IncrementVarSublevelsUp((Node *) subroot->append_rel_list, 1, 1);
/* There shouldn't be any OJ info to translate, as yet */ /* There shouldn't be any OJ info to translate, as yet */
Assert(subroot->join_info_list == NIL); Assert(subroot->join_info_list == NIL);
/* and we haven't made equivalence classes, either */
Assert(subroot->eq_classes == NIL);
/* and we haven't created PlaceHolderInfos, either */ /* and we haven't created PlaceHolderInfos, either */
Assert(subroot->placeholder_list == NIL); Assert(subroot->placeholder_list == NIL);
/*----------
* Generate modified query of the form
* (SELECT col FROM tab
* WHERE col IS NOT NULL AND existing-quals
* ORDER BY col ASC/DESC
* LIMIT 1)
*----------
*/
/* single tlist entry that is the aggregate target */ /* single tlist entry that is the aggregate target */
tle = makeTargetEntry(copyObject(mminfo->target), tle = makeTargetEntry(copyObject(mminfo->target),
(AttrNumber) 1, (AttrNumber) 1,
pstrdup("agg_target"), pstrdup("agg_target"),
false); false);
parse->targetList = list_make1(tle); tlist = list_make1(tle);
subroot->processed_tlist = parse->targetList = tlist;
/* No HAVING, no DISTINCT, no aggregates anymore */ /* No HAVING, no DISTINCT, no aggregates anymore */
parse->havingQual = NULL; parse->havingQual = NULL;
...@@ -467,7 +410,7 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, ...@@ -467,7 +410,7 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
/* Build suitable ORDER BY clause */ /* Build suitable ORDER BY clause */
sortcl = makeNode(SortGroupClause); sortcl = makeNode(SortGroupClause);
sortcl->tleSortGroupRef = assignSortGroupRef(tle, parse->targetList); sortcl->tleSortGroupRef = assignSortGroupRef(tle, tlist);
sortcl->eqop = eqop; sortcl->eqop = eqop;
sortcl->sortop = sortop; sortcl->sortop = sortop;
sortcl->nulls_first = nulls_first; sortcl->nulls_first = nulls_first;
...@@ -488,8 +431,16 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, ...@@ -488,8 +431,16 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
subroot->tuple_fraction = 1.0; subroot->tuple_fraction = 1.0;
subroot->limit_tuples = 1.0; subroot->limit_tuples = 1.0;
final_rel = query_planner(subroot, parse->targetList, final_rel = query_planner(subroot, tlist, minmax_qp_callback, NULL);
minmax_qp_callback, NULL);
/*
* Since we didn't go through subquery_planner() to handle the subquery,
* we have to do some of the same cleanup it would do, in particular cope
* with params and initplans used within this subquery. (This won't
* matter if we end up not using the subplan.)
*/
SS_identify_outer_params(subroot);
SS_charge_for_initplans(subroot, final_rel);
/* /*
* Get the best presorted path, that being the one that's cheapest for * Get the best presorted path, that being the one that's cheapest for
...@@ -508,6 +459,14 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, ...@@ -508,6 +459,14 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
if (!sorted_path) if (!sorted_path)
return false; return false;
/*
* The path might not return exactly what we want, so fix that. (We
* assume that this won't change any conclusions about which was the
* cheapest path.)
*/
sorted_path = apply_projection_to_path(subroot, final_rel, sorted_path,
create_pathtarget(root, tlist));
/* /*
* Determine cost to get just the first row of the presorted path. * Determine cost to get just the first row of the presorted path.
* *
...@@ -526,7 +485,7 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo, ...@@ -526,7 +485,7 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
} }
/* /*
* Compute query_pathkeys and other pathkeys during plan generation * Compute query_pathkeys and other pathkeys during query_planner()
*/ */
static void static void
minmax_qp_callback(PlannerInfo *root, void *extra) minmax_qp_callback(PlannerInfo *root, void *extra)
...@@ -543,105 +502,6 @@ minmax_qp_callback(PlannerInfo *root, void *extra) ...@@ -543,105 +502,6 @@ minmax_qp_callback(PlannerInfo *root, void *extra)
root->query_pathkeys = root->sort_pathkeys; root->query_pathkeys = root->sort_pathkeys;
} }
/*
* Construct a suitable plan for a converted aggregate query
*/
static void
make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *mminfo)
{
PlannerInfo *subroot = mminfo->subroot;
Query *subparse = subroot->parse;
Plan *plan;
/*
* Generate the plan for the subquery. We already have a Path, but we have
* to convert it to a Plan and attach a LIMIT node above it.
*/
plan = create_plan(subroot, mminfo->path);
/*
* If the top-level plan node is one that cannot do expression evaluation
* and its existing target list isn't already what we need, we must insert
* a Result node to project the desired tlist.
*/
if (!is_projection_capable_plan(plan) &&
!tlist_same_exprs(subparse->targetList, plan->targetlist))
{
plan = (Plan *) make_result(subroot,
subparse->targetList,
NULL,
plan);
}
else
{
/*
* Otherwise, just replace the subplan's flat tlist with the desired
* tlist.
*/
plan->targetlist = subparse->targetList;
}
plan = (Plan *) make_limit(plan,
subparse->limitOffset,
subparse->limitCount,
0, 1);
/*
* We have to do some of the same cleanup that subquery_planner() would
* do, namely cope with params and initplans used within this plan tree.
*
* This is a little bit messy because although we initially created the
* subroot by cloning the outer root, it really is a subplan and needs to
* consider initplans belonging to the outer root as providing available
* parameters. So temporarily change its parent_root pointer.
* (Fortunately, SS_identify_outer_params doesn't care whether the depth
* of parent_root nesting matches query_level.)
*/
subroot->parent_root = root;
SS_identify_outer_params(subroot);
subroot->parent_root = root->parent_root;
SS_attach_initplans(subroot, plan);
/*
* Convert the plan into an InitPlan, and make a Param for its result.
*/
mminfo->param =
SS_make_initplan_from_plan(root, subroot, plan,
exprType((Node *) mminfo->target),
-1,
exprCollation((Node *) mminfo->target));
}
/*
* Replace original aggregate calls with subplan output Params
*/
static Node *
replace_aggs_with_params_mutator(Node *node, PlannerInfo *root)
{
if (node == NULL)
return NULL;
if (IsA(node, Aggref))
{
Aggref *aggref = (Aggref *) node;
TargetEntry *curTarget = (TargetEntry *) linitial(aggref->args);
ListCell *lc;
foreach(lc, root->minmax_aggs)
{
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
if (mminfo->aggfnoid == aggref->aggfnoid &&
equal(mminfo->target, curTarget->expr))
return (Node *) mminfo->param;
}
elog(ERROR, "failed to re-find MinMaxAggInfo record");
}
Assert(!IsA(node, SubLink));
return expression_tree_mutator(node, replace_aggs_with_params_mutator,
(void *) root);
}
/* /*
* Get the OID of the sort operator, if any, associated with an aggregate. * Get the OID of the sort operator, if any, associated with an aggregate.
* Returns InvalidOid if there is no such operator. * Returns InvalidOid if there is no such operator.
......
...@@ -36,9 +36,7 @@ ...@@ -36,9 +36,7 @@
* Since query_planner does not handle the toplevel processing (grouping, * Since query_planner does not handle the toplevel processing (grouping,
* sorting, etc) it cannot select the best path by itself. Instead, it * sorting, etc) it cannot select the best path by itself. Instead, it
* returns the RelOptInfo for the top level of joining, and the caller * returns the RelOptInfo for the top level of joining, and the caller
* (grouping_planner) can choose one of the surviving paths for the rel. * (grouping_planner) can choose among the surviving paths for the rel.
* Normally it would choose either the rel's cheapest path, or the cheapest
* path for the desired sort order.
* *
* root describes the query to plan * root describes the query to plan
* tlist is the target list the query should produce * tlist is the target list the query should produce
...@@ -85,6 +83,7 @@ query_planner(PlannerInfo *root, List *tlist, ...@@ -85,6 +83,7 @@ query_planner(PlannerInfo *root, List *tlist,
/* The only path for it is a trivial Result path */ /* The only path for it is a trivial Result path */
add_path(final_rel, (Path *) add_path(final_rel, (Path *)
create_result_path(final_rel, create_result_path(final_rel,
&(final_rel->reltarget),
(List *) parse->jointree->quals)); (List *) parse->jointree->quals));
/* Select cheapest path (pretty easy in this case...) */ /* Select cheapest path (pretty easy in this case...) */
...@@ -104,7 +103,7 @@ query_planner(PlannerInfo *root, List *tlist, ...@@ -104,7 +103,7 @@ query_planner(PlannerInfo *root, List *tlist,
* Init planner lists to empty. * Init planner lists to empty.
* *
* NOTE: append_rel_list was set up by subquery_planner, so do not touch * NOTE: append_rel_list was set up by subquery_planner, so do not touch
* here; eq_classes and minmax_aggs may contain data already, too. * here.
*/ */
root->join_rel_list = NIL; root->join_rel_list = NIL;
root->join_rel_hash = NULL; root->join_rel_hash = NULL;
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -304,8 +304,8 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) ...@@ -304,8 +304,8 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
* in our query level. In this case apply * in our query level. In this case apply
* flatten_unplanned_rtes. * flatten_unplanned_rtes.
* *
* If it was planned but the plan is dummy, we assume that it * If it was planned but the result rel is dummy, we assume
* has been omitted from our plan tree (see * that it has been omitted from our plan tree (see
* set_subquery_pathlist), and recurse to pull up its RTEs. * set_subquery_pathlist), and recurse to pull up its RTEs.
* *
* Otherwise, it should be represented by a SubqueryScan node * Otherwise, it should be represented by a SubqueryScan node
...@@ -313,17 +313,16 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing) ...@@ -313,17 +313,16 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
* we process that plan node. * we process that plan node.
* *
* However, if we're recursing, then we should pull up RTEs * However, if we're recursing, then we should pull up RTEs
* whether the subplan is dummy or not, because we've found * whether the subquery is dummy or not, because we've found
* that some upper query level is treating this one as dummy, * that some upper query level is treating this one as dummy,
* and so we won't scan this level's plan tree at all. * and so we won't scan this level's plan tree at all.
*/ */
if (rel->subplan == NULL) if (rel->subroot == NULL)
flatten_unplanned_rtes(glob, rte); flatten_unplanned_rtes(glob, rte);
else if (recursing || is_dummy_plan(rel->subplan)) else if (recursing ||
{ IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
Assert(rel->subroot != NULL); UPPERREL_FINAL, NULL)))
add_rtes_to_flat_rtable(rel->subroot, true); add_rtes_to_flat_rtable(rel->subroot, true);
}
} }
} }
rti++; rti++;
...@@ -979,7 +978,6 @@ set_subqueryscan_references(PlannerInfo *root, ...@@ -979,7 +978,6 @@ set_subqueryscan_references(PlannerInfo *root,
/* Need to look up the subquery's RelOptInfo, since we need its subroot */ /* Need to look up the subquery's RelOptInfo, since we need its subroot */
rel = find_base_rel(root, plan->scan.scanrelid); rel = find_base_rel(root, plan->scan.scanrelid);
Assert(rel->subplan == plan->subplan);
/* Recursively process the subplan */ /* Recursively process the subplan */
plan->subplan = set_plan_references(rel->subroot, plan->subplan); plan->subplan = set_plan_references(rel->subroot, plan->subplan);
...@@ -1386,6 +1384,7 @@ fix_param_node(PlannerInfo *root, Param *p) ...@@ -1386,6 +1384,7 @@ fix_param_node(PlannerInfo *root, Param *p)
* *
* This consists of incrementing all Vars' varnos by rtoffset, * This consists of incrementing all Vars' varnos by rtoffset,
* replacing PARAM_MULTIEXPR Params, expanding PlaceHolderVars, * replacing PARAM_MULTIEXPR Params, expanding PlaceHolderVars,
* replacing Aggref nodes that should be replaced by initplan output Params,
* looking up operator opcode info for OpExpr and related nodes, * looking up operator opcode info for OpExpr and related nodes,
* and adding OIDs from regclass Const nodes into root->glob->relationOids. * and adding OIDs from regclass Const nodes into root->glob->relationOids.
*/ */
...@@ -1399,7 +1398,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset) ...@@ -1399,7 +1398,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
if (rtoffset != 0 || if (rtoffset != 0 ||
root->multiexpr_params != NIL || root->multiexpr_params != NIL ||
root->glob->lastPHId != 0) root->glob->lastPHId != 0 ||
root->minmax_aggs != NIL)
{ {
return fix_scan_expr_mutator(node, &context); return fix_scan_expr_mutator(node, &context);
} }
...@@ -1409,7 +1409,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset) ...@@ -1409,7 +1409,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
* If rtoffset == 0, we don't need to change any Vars, and if there * If rtoffset == 0, we don't need to change any Vars, and if there
* are no MULTIEXPR subqueries then we don't need to replace * are no MULTIEXPR subqueries then we don't need to replace
* PARAM_MULTIEXPR Params, and if there are no placeholders anywhere * PARAM_MULTIEXPR Params, and if there are no placeholders anywhere
* we won't need to remove them. Then it's OK to just scribble on the * we won't need to remove them, and if there are no minmax Aggrefs we
* won't need to replace them. Then it's OK to just scribble on the
* input node tree instead of copying (since the only change, filling * input node tree instead of copying (since the only change, filling
* in any unset opfuncid fields, is harmless). This saves just enough * in any unset opfuncid fields, is harmless). This saves just enough
* cycles to be noticeable on trivial queries. * cycles to be noticeable on trivial queries.
...@@ -1444,6 +1445,28 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context) ...@@ -1444,6 +1445,28 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
} }
if (IsA(node, Param)) if (IsA(node, Param))
return fix_param_node(context->root, (Param *) node); return fix_param_node(context->root, (Param *) node);
if (IsA(node, Aggref))
{
Aggref *aggref = (Aggref *) node;
/* See if the Aggref should be replaced by a Param */
if (context->root->minmax_aggs != NIL &&
list_length(aggref->args) == 1)
{
TargetEntry *curTarget = (TargetEntry *) linitial(aggref->args);
ListCell *lc;
foreach(lc, context->root->minmax_aggs)
{
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
if (mminfo->aggfnoid == aggref->aggfnoid &&
equal(mminfo->target, curTarget->expr))
return (Node *) copyObject(mminfo->param);
}
}
/* If no match, just fall through to process it normally */
}
if (IsA(node, CurrentOfExpr)) if (IsA(node, CurrentOfExpr))
{ {
CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node); CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node);
...@@ -2091,8 +2114,9 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context) ...@@ -2091,8 +2114,9 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
/* /*
* fix_upper_expr * fix_upper_expr
* Modifies an expression tree so that all Var nodes reference outputs * Modifies an expression tree so that all Var nodes reference outputs
* of a subplan. Also performs opcode lookup, and adds regclass OIDs to * of a subplan. Also looks for Aggref nodes that should be replaced
* root->glob->relationOids. * by initplan output Params. Also performs opcode lookup, and adds
* regclass OIDs to root->glob->relationOids.
* *
* This is used to fix up target and qual expressions of non-join upper-level * This is used to fix up target and qual expressions of non-join upper-level
* plan nodes, as well as index-only scan nodes. * plan nodes, as well as index-only scan nodes.
...@@ -2169,6 +2193,28 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context) ...@@ -2169,6 +2193,28 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
} }
if (IsA(node, Param)) if (IsA(node, Param))
return fix_param_node(context->root, (Param *) node); return fix_param_node(context->root, (Param *) node);
if (IsA(node, Aggref))
{
Aggref *aggref = (Aggref *) node;
/* See if the Aggref should be replaced by a Param */
if (context->root->minmax_aggs != NIL &&
list_length(aggref->args) == 1)
{
TargetEntry *curTarget = (TargetEntry *) linitial(aggref->args);
ListCell *lc;
foreach(lc, context->root->minmax_aggs)
{
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
if (mminfo->aggfnoid == aggref->aggfnoid &&
equal(mminfo->target, curTarget->expr))
return (Node *) copyObject(mminfo->param);
}
}
/* If no match, just fall through to process it normally */
}
/* Try matching more complex expressions too, if tlist has any */ /* Try matching more complex expressions too, if tlist has any */
if (context->subplan_itlist->has_non_vars) if (context->subplan_itlist->has_non_vars)
{ {
......
...@@ -478,8 +478,10 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, ...@@ -478,8 +478,10 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
Query *subquery; Query *subquery;
bool simple_exists = false; bool simple_exists = false;
double tuple_fraction; double tuple_fraction;
Plan *plan;
PlannerInfo *subroot; PlannerInfo *subroot;
RelOptInfo *final_rel;
Path *best_path;
Plan *plan;
List *plan_params; List *plan_params;
Node *result; Node *result;
...@@ -527,18 +529,24 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, ...@@ -527,18 +529,24 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
/* plan_params should not be in use in current query level */ /* plan_params should not be in use in current query level */
Assert(root->plan_params == NIL); Assert(root->plan_params == NIL);
/* /* Generate Paths for the subquery */
* Generate the plan for the subquery. subroot = subquery_planner(root->glob, subquery,
*/ root,
plan = subquery_planner(root->glob, subquery, false, tuple_fraction);
root,
false, tuple_fraction,
&subroot);
/* Isolate the params needed by this specific subplan */ /* Isolate the params needed by this specific subplan */
plan_params = root->plan_params; plan_params = root->plan_params;
root->plan_params = NIL; root->plan_params = NIL;
/*
* Select best Path and turn it into a Plan. At least for now, there
* seems no reason to postpone doing that.
*/
final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
best_path = get_cheapest_fractional_path(final_rel, tuple_fraction);
plan = create_plan(subroot, best_path);
/* And convert to SubPlan or InitPlan format. */ /* And convert to SubPlan or InitPlan format. */
result = build_subplan(root, plan, subroot, plan_params, result = build_subplan(root, plan, subroot, plan_params,
subLinkType, subLinkId, subLinkType, subLinkId,
...@@ -568,17 +576,23 @@ make_subplan(PlannerInfo *root, Query *orig_subquery, ...@@ -568,17 +576,23 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
&newtestexpr, &paramIds); &newtestexpr, &paramIds);
if (subquery) if (subquery)
{ {
/* Generate the plan for the ANY subquery; we'll need all rows */ /* Generate Paths for the ANY subquery; we'll need all rows */
plan = subquery_planner(root->glob, subquery, subroot = subquery_planner(root->glob, subquery,
root, root,
false, 0.0, false, 0.0);
&subroot);
/* Isolate the params needed by this specific subplan */ /* Isolate the params needed by this specific subplan */
plan_params = root->plan_params; plan_params = root->plan_params;
root->plan_params = NIL; root->plan_params = NIL;
/* Select best Path and turn it into a Plan */
final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
best_path = final_rel->cheapest_total_path;
plan = create_plan(subroot, best_path);
/* Now we can check if it'll fit in work_mem */ /* Now we can check if it'll fit in work_mem */
/* XXX can we check this at the Path stage? */
if (subplan_is_hashable(plan)) if (subplan_is_hashable(plan))
{ {
SubPlan *hashplan; SubPlan *hashplan;
...@@ -1133,8 +1147,10 @@ SS_process_ctes(PlannerInfo *root) ...@@ -1133,8 +1147,10 @@ SS_process_ctes(PlannerInfo *root)
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc); CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
CmdType cmdType = ((Query *) cte->ctequery)->commandType; CmdType cmdType = ((Query *) cte->ctequery)->commandType;
Query *subquery; Query *subquery;
Plan *plan;
PlannerInfo *subroot; PlannerInfo *subroot;
RelOptInfo *final_rel;
Path *best_path;
Plan *plan;
SubPlan *splan; SubPlan *splan;
int paramid; int paramid;
...@@ -1158,13 +1174,12 @@ SS_process_ctes(PlannerInfo *root) ...@@ -1158,13 +1174,12 @@ SS_process_ctes(PlannerInfo *root)
Assert(root->plan_params == NIL); Assert(root->plan_params == NIL);
/* /*
* Generate the plan for the CTE query. Always plan for full * Generate Paths for the CTE query. Always plan for full retrieval
* retrieval --- we don't have enough info to predict otherwise. * --- we don't have enough info to predict otherwise.
*/ */
plan = subquery_planner(root->glob, subquery, subroot = subquery_planner(root->glob, subquery,
root, root,
cte->cterecursive, 0.0, cte->cterecursive, 0.0);
&subroot);
/* /*
* Since the current query level doesn't yet contain any RTEs, it * Since the current query level doesn't yet contain any RTEs, it
...@@ -1174,6 +1189,15 @@ SS_process_ctes(PlannerInfo *root) ...@@ -1174,6 +1189,15 @@ SS_process_ctes(PlannerInfo *root)
if (root->plan_params) if (root->plan_params)
elog(ERROR, "unexpected outer reference in CTE query"); elog(ERROR, "unexpected outer reference in CTE query");
/*
* Select best Path and turn it into a Plan. At least for now, there
* seems no reason to postpone doing that.
*/
final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
best_path = final_rel->cheapest_total_path;
plan = create_plan(subroot, best_path);
/* /*
* Make a SubPlan node for it. This is just enough unlike * Make a SubPlan node for it. This is just enough unlike
* build_subplan that we can't share code. * build_subplan that we can't share code.
...@@ -2109,35 +2133,70 @@ SS_identify_outer_params(PlannerInfo *root) ...@@ -2109,35 +2133,70 @@ SS_identify_outer_params(PlannerInfo *root)
} }
/* /*
* SS_attach_initplans - attach initplans to topmost plan node * SS_charge_for_initplans - account for cost of initplans in Path costs
* *
* Attach any initplans created in the current query level to the topmost plan * If any initPlans have been created in the current query level, they will
* node for the query level, and increment that node's cost to account for * get attached to the Plan tree created from whichever Path we select from
* them. (The initPlans could actually go in any node at or above where * the given rel; so increment all the rel's Paths' costs to account for them.
* they're referenced, but there seems no reason to put them any lower than *
* the topmost node for the query level.) * This is separate from SS_attach_initplans because we might conditionally
* create more initPlans during create_plan(), depending on which Path we
* select. However, Paths that would generate such initPlans are expected
* to have included their cost already.
*/ */
void void
SS_attach_initplans(PlannerInfo *root, Plan *plan) SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
{ {
Cost initplan_cost;
ListCell *lc; ListCell *lc;
plan->initPlan = root->init_plans; /* Nothing to do if no initPlans */
foreach(lc, plan->initPlan) if (root->init_plans == NIL)
return;
/*
* Compute the cost increment just once, since it will be the same for all
* Paths. We assume each initPlan gets run once during top plan startup.
* This is a conservative overestimate, since in fact an initPlan might be
* executed later than plan startup, or even not at all.
*/
initplan_cost = 0;
foreach(lc, root->init_plans)
{ {
SubPlan *initsubplan = (SubPlan *) lfirst(lc); SubPlan *initsubplan = (SubPlan *) lfirst(lc);
Cost initplan_cost;
/* initplan_cost += initsubplan->startup_cost + initsubplan->per_call_cost;
* Assume each initPlan gets run once during top plan startup. This }
* is a conservative overestimate, since in fact an initPlan might be
* executed later than plan startup, or even not at all. /*
*/ * Now adjust the costs.
initplan_cost = initsubplan->startup_cost + initsubplan->per_call_cost; */
foreach(lc, final_rel->pathlist)
{
Path *path = (Path *) lfirst(lc);
plan->startup_cost += initplan_cost; path->startup_cost += initplan_cost;
plan->total_cost += initplan_cost; path->total_cost += initplan_cost;
} }
/* We needn't do set_cheapest() here, caller will do it */
}
/*
* SS_attach_initplans - attach initplans to topmost plan node
*
* Attach any initplans created in the current query level to the specified
* plan node, which should normally be the topmost node for the query level.
* (The initPlans could actually go in any node at or above where they're
* referenced; but there seems no reason to put them any lower than the
* topmost node, so we don't bother to track exactly where they came from.)
* We do not touch the plan node's cost; the initplans should have been
* accounted for in path costing.
*/
void
SS_attach_initplans(PlannerInfo *root, Plan *plan)
{
plan->initPlan = root->init_plans;
} }
/* /*
...@@ -2298,7 +2357,6 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, ...@@ -2298,7 +2357,6 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
/* We must run SS_finalize_plan on the subquery */ /* We must run SS_finalize_plan on the subquery */
rel = find_base_rel(root, sscan->scan.scanrelid); rel = find_base_rel(root, sscan->scan.scanrelid);
Assert(rel->subplan == sscan->subplan);
SS_finalize_plan(rel->subroot, sscan->subplan); SS_finalize_plan(rel->subroot, sscan->subplan);
/* Now we can add its extParams to the parent's params */ /* Now we can add its extParams to the parent's params */
...@@ -2740,21 +2798,35 @@ finalize_primnode(Node *node, finalize_primnode_context *context) ...@@ -2740,21 +2798,35 @@ finalize_primnode(Node *node, finalize_primnode_context *context)
} }
/* /*
* SS_make_initplan_from_plan - given a plan tree, make it an InitPlan * SS_make_initplan_output_param - make a Param for an initPlan's output
* *
* The plan is expected to return a scalar value of the given type/collation. * The plan is expected to return a scalar value of the given type/collation.
*
* Note that in some cases the initplan may not ever appear in the finished
* plan tree. If that happens, we'll have wasted a PARAM_EXEC slot, which
* is no big deal.
*/
Param *
SS_make_initplan_output_param(PlannerInfo *root,
Oid resulttype, int32 resulttypmod,
Oid resultcollation)
{
return generate_new_param(root, resulttype, resulttypmod, resultcollation);
}
/*
* SS_make_initplan_from_plan - given a plan tree, make it an InitPlan
*
* We build an EXPR_SUBLINK SubPlan node and put it into the initplan * We build an EXPR_SUBLINK SubPlan node and put it into the initplan
* list for the outer query level. A Param that represents the initplan's * list for the outer query level. A Param that represents the initplan's
* output is returned. * output has already been assigned using SS_make_initplan_output_param.
*/ */
Param * void
SS_make_initplan_from_plan(PlannerInfo *root, SS_make_initplan_from_plan(PlannerInfo *root,
PlannerInfo *subroot, Plan *plan, PlannerInfo *subroot, Plan *plan,
Oid resulttype, int32 resulttypmod, Param *prm)
Oid resultcollation)
{ {
SubPlan *node; SubPlan *node;
Param *prm;
/* /*
* Add the subplan and its PlannerInfo to the global lists. * Add the subplan and its PlannerInfo to the global lists.
...@@ -2769,9 +2841,12 @@ SS_make_initplan_from_plan(PlannerInfo *root, ...@@ -2769,9 +2841,12 @@ SS_make_initplan_from_plan(PlannerInfo *root,
*/ */
node = makeNode(SubPlan); node = makeNode(SubPlan);
node->subLinkType = EXPR_SUBLINK; node->subLinkType = EXPR_SUBLINK;
node->plan_id = list_length(root->glob->subplans);
node->plan_name = psprintf("InitPlan %d (returns $%d)",
node->plan_id, prm->paramid);
get_first_col_type(plan, &node->firstColType, &node->firstColTypmod, get_first_col_type(plan, &node->firstColType, &node->firstColTypmod,
&node->firstColCollation); &node->firstColCollation);
node->plan_id = list_length(root->glob->subplans); node->setParam = list_make1_int(prm->paramid);
root->init_plans = lappend(root->init_plans, node); root->init_plans = lappend(root->init_plans, node);
...@@ -2780,17 +2855,6 @@ SS_make_initplan_from_plan(PlannerInfo *root, ...@@ -2780,17 +2855,6 @@ SS_make_initplan_from_plan(PlannerInfo *root,
* parParam and args lists remain empty. * parParam and args lists remain empty.
*/ */
/* Set costs of SubPlan using info from the plan tree */
cost_subplan(subroot, node, plan); cost_subplan(subroot, node, plan);
/*
* Make a Param that will be the subplan's output.
*/
prm = generate_new_param(root, resulttype, resulttypmod, resultcollation);
node->setParam = list_make1_int(prm->paramid);
/* Label the subplan for EXPLAIN purposes */
node->plan_name = psprintf("InitPlan %d (returns $%d)",
node->plan_id, prm->paramid);
return prm;
} }
...@@ -907,9 +907,14 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, ...@@ -907,9 +907,14 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
subroot->eq_classes = NIL; subroot->eq_classes = NIL;
subroot->append_rel_list = NIL; subroot->append_rel_list = NIL;
subroot->rowMarks = NIL; subroot->rowMarks = NIL;
memset(subroot->upper_rels, 0, sizeof(subroot->upper_rels));
subroot->processed_tlist = NIL;
subroot->grouping_map = NULL;
subroot->minmax_aggs = NIL;
subroot->hasInheritedTarget = false;
subroot->hasRecursion = false; subroot->hasRecursion = false;
subroot->wt_param_id = -1; subroot->wt_param_id = -1;
subroot->non_recursive_plan = NULL; subroot->non_recursive_path = NULL;
/* No CTEs to worry about */ /* No CTEs to worry about */
Assert(subquery->cteList == NIL); Assert(subquery->cteList == NIL);
......
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
#include "nodes/nodeFuncs.h" #include "nodes/nodeFuncs.h"
#include "optimizer/cost.h" #include "optimizer/cost.h"
#include "optimizer/pathnode.h" #include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/planmain.h" #include "optimizer/planmain.h"
#include "optimizer/planner.h" #include "optimizer/planner.h"
#include "optimizer/prep.h" #include "optimizer/prep.h"
...@@ -58,35 +59,33 @@ typedef struct ...@@ -58,35 +59,33 @@ typedef struct
int sublevels_up; int sublevels_up;
} adjust_appendrel_attrs_context; } adjust_appendrel_attrs_context;
static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root, static Path *recurse_set_operations(Node *setOp, PlannerInfo *root,
double tuple_fraction,
List *colTypes, List *colCollations, List *colTypes, List *colCollations,
bool junkOK, bool junkOK,
int flag, List *refnames_tlist, int flag, List *refnames_tlist,
List **sortClauses, double *pNumGroups); List **pTargetList,
static Plan *generate_recursion_plan(SetOperationStmt *setOp, double *pNumGroups);
PlannerInfo *root, double tuple_fraction, static Path *generate_recursion_path(SetOperationStmt *setOp,
PlannerInfo *root,
List *refnames_tlist, List *refnames_tlist,
List **sortClauses); List **pTargetList);
static Plan *generate_union_plan(SetOperationStmt *op, PlannerInfo *root, static Path *generate_union_path(SetOperationStmt *op, PlannerInfo *root,
double tuple_fraction,
List *refnames_tlist, List *refnames_tlist,
List **sortClauses, double *pNumGroups); List **pTargetList,
static Plan *generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root, double *pNumGroups);
double tuple_fraction, static Path *generate_nonunion_path(SetOperationStmt *op, PlannerInfo *root,
List *refnames_tlist, List *refnames_tlist,
List **sortClauses, double *pNumGroups); List **pTargetList,
double *pNumGroups);
static List *recurse_union_children(Node *setOp, PlannerInfo *root, static List *recurse_union_children(Node *setOp, PlannerInfo *root,
double tuple_fraction,
SetOperationStmt *top_union, SetOperationStmt *top_union,
List *refnames_tlist); List *refnames_tlist,
static Plan *make_union_unique(SetOperationStmt *op, Plan *plan, List **tlist_list);
PlannerInfo *root, double tuple_fraction, static Path *make_union_unique(SetOperationStmt *op, Path *path, List *tlist,
List **sortClauses); PlannerInfo *root);
static bool choose_hashed_setop(PlannerInfo *root, List *groupClauses, static bool choose_hashed_setop(PlannerInfo *root, List *groupClauses,
Plan *input_plan, Path *input_path,
double dNumGroups, double dNumOutputRows, double dNumGroups, double dNumOutputRows,
double tuple_fraction,
const char *construct); const char *construct);
static List *generate_setop_tlist(List *colTypes, List *colCollations, static List *generate_setop_tlist(List *colTypes, List *colCollations,
int flag, int flag,
...@@ -96,7 +95,7 @@ static List *generate_setop_tlist(List *colTypes, List *colCollations, ...@@ -96,7 +95,7 @@ static List *generate_setop_tlist(List *colTypes, List *colCollations,
List *refnames_tlist); List *refnames_tlist);
static List *generate_append_tlist(List *colTypes, List *colCollations, static List *generate_append_tlist(List *colTypes, List *colCollations,
bool flag, bool flag,
List *input_plans, List *input_tlists,
List *refnames_tlist); List *refnames_tlist);
static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist); static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist);
static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, static void expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte,
...@@ -120,27 +119,24 @@ static List *adjust_inherited_tlist(List *tlist, ...@@ -120,27 +119,24 @@ static List *adjust_inherited_tlist(List *tlist,
* Plans the queries for a tree of set operations (UNION/INTERSECT/EXCEPT) * Plans the queries for a tree of set operations (UNION/INTERSECT/EXCEPT)
* *
* This routine only deals with the setOperations tree of the given query. * This routine only deals with the setOperations tree of the given query.
* Any top-level ORDER BY requested in root->parse->sortClause will be added * Any top-level ORDER BY requested in root->parse->sortClause will be handled
* when we return to grouping_planner. * when we return to grouping_planner; likewise for LIMIT.
*
* tuple_fraction is the fraction of tuples we expect will be retrieved.
* tuple_fraction is interpreted as for grouping_planner(); in particular,
* zero means "all the tuples will be fetched". Any LIMIT present at the
* top level has already been factored into tuple_fraction.
* *
* *sortClauses is an output argument: it is set to a list of SortGroupClauses * What we return is an "upperrel" RelOptInfo containing at least one Path
* representing the result ordering of the topmost set operation. (This will * that implements the set-operation tree. In addition, root->processed_tlist
* be NIL if the output isn't ordered.) * receives a targetlist representing the output of the topmost setop node.
*/ */
Plan * RelOptInfo *
plan_set_operations(PlannerInfo *root, double tuple_fraction, plan_set_operations(PlannerInfo *root)
List **sortClauses)
{ {
Query *parse = root->parse; Query *parse = root->parse;
SetOperationStmt *topop = (SetOperationStmt *) parse->setOperations; SetOperationStmt *topop = (SetOperationStmt *) parse->setOperations;
Node *node; Node *node;
RangeTblEntry *leftmostRTE; RangeTblEntry *leftmostRTE;
Query *leftmostQuery; Query *leftmostQuery;
RelOptInfo *setop_rel;
Path *path;
List *top_tlist;
Assert(topop && IsA(topop, SetOperationStmt)); Assert(topop && IsA(topop, SetOperationStmt));
...@@ -171,54 +167,82 @@ plan_set_operations(PlannerInfo *root, double tuple_fraction, ...@@ -171,54 +167,82 @@ plan_set_operations(PlannerInfo *root, double tuple_fraction,
leftmostQuery = leftmostRTE->subquery; leftmostQuery = leftmostRTE->subquery;
Assert(leftmostQuery != NULL); Assert(leftmostQuery != NULL);
/*
* We return our results in the (SETOP, NULL) upperrel. For the moment,
* this is also the parent rel of all Paths in the setop tree; we may well
* change that in future.
*/
setop_rel = fetch_upper_rel(root, UPPERREL_SETOP, NULL);
/* /*
* If the topmost node is a recursive union, it needs special processing. * If the topmost node is a recursive union, it needs special processing.
*/ */
if (root->hasRecursion) if (root->hasRecursion)
return generate_recursion_plan(topop, root, tuple_fraction, {
path = generate_recursion_path(topop, root,
leftmostQuery->targetList, leftmostQuery->targetList,
sortClauses); &top_tlist);
}
else
{
/*
* Recurse on setOperations tree to generate paths for set ops. The
* final output path should have just the column types shown as the
* output from the top-level node, plus possibly resjunk working
* columns (we can rely on upper-level nodes to deal with that).
*/
path = recurse_set_operations((Node *) topop, root,
topop->colTypes, topop->colCollations,
true, -1,
leftmostQuery->targetList,
&top_tlist,
NULL);
}
/* /* Must return the built tlist into root->processed_tlist. */
* Recurse on setOperations tree to generate plans for set ops. The final root->processed_tlist = top_tlist;
* output plan should have just the column types shown as the output from
* the top-level node, plus possibly resjunk working columns (we can rely /* Add only the final path to the SETOP upperrel. */
* on upper-level nodes to deal with that). add_path(setop_rel, path);
*/
return recurse_set_operations((Node *) topop, root, tuple_fraction, /* Select cheapest path (pretty easy at the moment) */
topop->colTypes, topop->colCollations, set_cheapest(setop_rel);
true, -1,
leftmostQuery->targetList, return setop_rel;
sortClauses, NULL);
} }
/* /*
* recurse_set_operations * recurse_set_operations
* Recursively handle one step in a tree of set operations * Recursively handle one step in a tree of set operations
* *
* tuple_fraction: fraction of tuples we expect to retrieve from node
* colTypes: OID list of set-op's result column datatypes * colTypes: OID list of set-op's result column datatypes
* colCollations: OID list of set-op's result column collations * colCollations: OID list of set-op's result column collations
* junkOK: if true, child resjunk columns may be left in the result * junkOK: if true, child resjunk columns may be left in the result
* flag: if >= 0, add a resjunk output column indicating value of flag * flag: if >= 0, add a resjunk output column indicating value of flag
* refnames_tlist: targetlist to take column names from * refnames_tlist: targetlist to take column names from
* *
* Returns a plan for the subtree, as well as these output parameters: * Returns a path for the subtree, as well as these output parameters:
* *sortClauses: receives list of SortGroupClauses for result plan, if any * *pTargetList: receives the fully-fledged tlist for the subtree's top plan
* *pNumGroups: if not NULL, we estimate the number of distinct groups * *pNumGroups: if not NULL, we estimate the number of distinct groups
* in the result, and store it there * in the result, and store it there
* *
* The pTargetList output parameter is mostly redundant with the pathtarget
* of the returned path, but for the moment we need it because much of the
* logic in this file depends on flag columns being marked resjunk. Pending
* a redesign of how that works, this is the easy way out.
*
* We don't have to care about typmods here: the only allowed difference * We don't have to care about typmods here: the only allowed difference
* between set-op input and output typmods is input is a specific typmod * between set-op input and output typmods is input is a specific typmod
* and output is -1, and that does not require a coercion. * and output is -1, and that does not require a coercion.
*/ */
static Plan * static Path *
recurse_set_operations(Node *setOp, PlannerInfo *root, recurse_set_operations(Node *setOp, PlannerInfo *root,
double tuple_fraction,
List *colTypes, List *colCollations, List *colTypes, List *colCollations,
bool junkOK, bool junkOK,
int flag, List *refnames_tlist, int flag, List *refnames_tlist,
List **sortClauses, double *pNumGroups) List **pTargetList,
double *pNumGroups)
{ {
if (IsA(setOp, RangeTblRef)) if (IsA(setOp, RangeTblRef))
{ {
...@@ -227,14 +251,16 @@ recurse_set_operations(Node *setOp, PlannerInfo *root, ...@@ -227,14 +251,16 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
Query *subquery = rte->subquery; Query *subquery = rte->subquery;
RelOptInfo *rel; RelOptInfo *rel;
PlannerInfo *subroot; PlannerInfo *subroot;
Plan *subplan, RelOptInfo *final_rel;
*plan; Path *subpath;
Path *path;
List *tlist;
Assert(subquery != NULL); Assert(subquery != NULL);
/* /*
* We need to build a RelOptInfo for each leaf subquery. This isn't * We need to build a RelOptInfo for each leaf subquery. This isn't
* used for anything here, but it carries the subroot data structures * used for much here, but it carries the subroot data structures
* forward to setrefs.c processing. * forward to setrefs.c processing.
*/ */
rel = build_simple_rel(root, rtr->rtindex, RELOPT_BASEREL); rel = build_simple_rel(root, rtr->rtindex, RELOPT_BASEREL);
...@@ -242,17 +268,11 @@ recurse_set_operations(Node *setOp, PlannerInfo *root, ...@@ -242,17 +268,11 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
/* plan_params should not be in use in current query level */ /* plan_params should not be in use in current query level */
Assert(root->plan_params == NIL); Assert(root->plan_params == NIL);
/* /* Generate a subroot and Paths for the subquery */
* Generate plan for primitive subquery subroot = rel->subroot = subquery_planner(root->glob, subquery,
*/ root,
subplan = subquery_planner(root->glob, subquery, false,
root, root->tuple_fraction);
false, tuple_fraction,
&subroot);
/* Save subroot and subplan in RelOptInfo for setrefs.c */
rel->subplan = subplan;
rel->subroot = subroot;
/* /*
* It should not be possible for the primitive query to contain any * It should not be possible for the primitive query to contain any
...@@ -261,6 +281,50 @@ recurse_set_operations(Node *setOp, PlannerInfo *root, ...@@ -261,6 +281,50 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
if (root->plan_params) if (root->plan_params)
elog(ERROR, "unexpected outer reference in set operation subquery"); elog(ERROR, "unexpected outer reference in set operation subquery");
/*
* Mark rel with estimated output rows, width, etc. Note that we have
* to do this before generating outer-query paths, else
* cost_subqueryscan is not happy.
*/
set_subquery_size_estimates(root, rel);
/*
* For the moment, we consider only a single Path for the subquery.
* This should change soon (make it look more like
* set_subquery_pathlist).
*/
final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
subpath = get_cheapest_fractional_path(final_rel,
root->tuple_fraction);
/*
* Stick a SubqueryScanPath atop that.
*
* We don't bother to determine the subquery's output ordering since
* it won't be reflected in the set-op result anyhow; so just label
* the SubqueryScanPath with nil pathkeys. (XXX that should change
* soon too, likely.)
*/
path = (Path *) create_subqueryscan_path(root, rel, subpath,
NIL, NULL);
/*
* Figure out the appropriate target list, and update the
* SubqueryScanPath with the PathTarget form of that.
*/
tlist = generate_setop_tlist(colTypes, colCollations,
flag,
rtr->rtindex,
true,
subroot->processed_tlist,
refnames_tlist);
path = apply_projection_to_path(root, rel, path,
create_pathtarget(root, tlist));
/* Return the fully-fledged tlist to caller, too */
*pTargetList = tlist;
/* /*
* Estimate number of groups if caller wants it. If the subquery used * Estimate number of groups if caller wants it. If the subquery used
* grouping or aggregation, its output is probably mostly unique * grouping or aggregation, its output is probably mostly unique
...@@ -271,50 +335,32 @@ recurse_set_operations(Node *setOp, PlannerInfo *root, ...@@ -271,50 +335,32 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
if (subquery->groupClause || subquery->groupingSets || if (subquery->groupClause || subquery->groupingSets ||
subquery->distinctClause || subquery->distinctClause ||
subroot->hasHavingQual || subquery->hasAggs) subroot->hasHavingQual || subquery->hasAggs)
*pNumGroups = subplan->plan_rows; *pNumGroups = subpath->rows;
else else
*pNumGroups = estimate_num_groups(subroot, *pNumGroups = estimate_num_groups(subroot,
get_tlist_exprs(subquery->targetList, false), get_tlist_exprs(subroot->processed_tlist, false),
subplan->plan_rows, subpath->rows,
NULL); NULL);
} }
/* return (Path *) path;
* Add a SubqueryScan with the caller-requested targetlist
*/
plan = (Plan *)
make_subqueryscan(generate_setop_tlist(colTypes, colCollations,
flag,
rtr->rtindex,
true,
subplan->targetlist,
refnames_tlist),
NIL,
rtr->rtindex,
subplan);
/*
* We don't bother to determine the subquery's output ordering since
* it won't be reflected in the set-op result anyhow.
*/
*sortClauses = NIL;
return plan;
} }
else if (IsA(setOp, SetOperationStmt)) else if (IsA(setOp, SetOperationStmt))
{ {
SetOperationStmt *op = (SetOperationStmt *) setOp; SetOperationStmt *op = (SetOperationStmt *) setOp;
Plan *plan; Path *path;
/* UNIONs are much different from INTERSECT/EXCEPT */ /* UNIONs are much different from INTERSECT/EXCEPT */
if (op->op == SETOP_UNION) if (op->op == SETOP_UNION)
plan = generate_union_plan(op, root, tuple_fraction, path = generate_union_path(op, root,
refnames_tlist, refnames_tlist,
sortClauses, pNumGroups); pTargetList,
pNumGroups);
else else
plan = generate_nonunion_plan(op, root, tuple_fraction, path = generate_nonunion_path(op, root,
refnames_tlist, refnames_tlist,
sortClauses, pNumGroups); pTargetList,
pNumGroups);
/* /*
* If necessary, add a Result node to project the caller-requested * If necessary, add a Result node to project the caller-requested
...@@ -330,45 +376,49 @@ recurse_set_operations(Node *setOp, PlannerInfo *root, ...@@ -330,45 +376,49 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
* generate_setop_tlist() to use varno 0. * generate_setop_tlist() to use varno 0.
*/ */
if (flag >= 0 || if (flag >= 0 ||
!tlist_same_datatypes(plan->targetlist, colTypes, junkOK) || !tlist_same_datatypes(*pTargetList, colTypes, junkOK) ||
!tlist_same_collations(plan->targetlist, colCollations, junkOK)) !tlist_same_collations(*pTargetList, colCollations, junkOK))
{ {
plan = (Plan *) *pTargetList = generate_setop_tlist(colTypes, colCollations,
make_result(root, flag,
generate_setop_tlist(colTypes, colCollations, 0,
flag, false,
0, *pTargetList,
false, refnames_tlist);
plan->targetlist, path = apply_projection_to_path(root,
refnames_tlist), path->parent,
NULL, path,
plan); create_pathtarget(root,
*pTargetList));
} }
return plan; return path;
} }
else else
{ {
elog(ERROR, "unrecognized node type: %d", elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(setOp)); (int) nodeTag(setOp));
*pTargetList = NIL;
return NULL; /* keep compiler quiet */ return NULL; /* keep compiler quiet */
} }
} }
/* /*
* Generate plan for a recursive UNION node * Generate path for a recursive UNION node
*/ */
static Plan * static Path *
generate_recursion_plan(SetOperationStmt *setOp, PlannerInfo *root, generate_recursion_path(SetOperationStmt *setOp, PlannerInfo *root,
double tuple_fraction,
List *refnames_tlist, List *refnames_tlist,
List **sortClauses) List **pTargetList)
{ {
Plan *plan; RelOptInfo *result_rel = fetch_upper_rel(root, UPPERREL_SETOP, NULL);
Plan *lplan; Path *path;
Plan *rplan; Path *lpath;
Path *rpath;
List *lpath_tlist;
List *rpath_tlist;
List *tlist; List *tlist;
List *groupList; List *groupList;
long numGroups; double dNumGroups;
/* Parser should have rejected other cases */ /* Parser should have rejected other cases */
if (setOp->op != SETOP_UNION) if (setOp->op != SETOP_UNION)
...@@ -380,37 +430,41 @@ generate_recursion_plan(SetOperationStmt *setOp, PlannerInfo *root, ...@@ -380,37 +430,41 @@ generate_recursion_plan(SetOperationStmt *setOp, PlannerInfo *root,
* Unlike a regular UNION node, process the left and right inputs * Unlike a regular UNION node, process the left and right inputs
* separately without any intention of combining them into one Append. * separately without any intention of combining them into one Append.
*/ */
lplan = recurse_set_operations(setOp->larg, root, tuple_fraction, lpath = recurse_set_operations(setOp->larg, root,
setOp->colTypes, setOp->colCollations, setOp->colTypes, setOp->colCollations,
false, -1, false, -1,
refnames_tlist, sortClauses, NULL); refnames_tlist,
/* The right plan will want to look at the left one ... */ &lpath_tlist,
root->non_recursive_plan = lplan; NULL);
rplan = recurse_set_operations(setOp->rarg, root, tuple_fraction, /* The right path will want to look at the left one ... */
root->non_recursive_path = lpath;
rpath = recurse_set_operations(setOp->rarg, root,
setOp->colTypes, setOp->colCollations, setOp->colTypes, setOp->colCollations,
false, -1, false, -1,
refnames_tlist, sortClauses, NULL); refnames_tlist,
root->non_recursive_plan = NULL; &rpath_tlist,
NULL);
root->non_recursive_path = NULL;
/* /*
* Generate tlist for RecursiveUnion plan node --- same as in Append cases * Generate tlist for RecursiveUnion path node --- same as in Append cases
*/ */
tlist = generate_append_tlist(setOp->colTypes, setOp->colCollations, false, tlist = generate_append_tlist(setOp->colTypes, setOp->colCollations, false,
list_make2(lplan, rplan), list_make2(lpath_tlist, rpath_tlist),
refnames_tlist); refnames_tlist);
*pTargetList = tlist;
/* /*
* If UNION, identify the grouping operators * If UNION, identify the grouping operators
*/ */
if (setOp->all) if (setOp->all)
{ {
groupList = NIL; groupList = NIL;
numGroups = 0; dNumGroups = 0;
} }
else else
{ {
double dNumGroups;
/* Identify the grouping semantics */ /* Identify the grouping semantics */
groupList = generate_setop_grouplist(setOp, tlist); groupList = generate_setop_grouplist(setOp, tlist);
...@@ -425,36 +479,41 @@ generate_recursion_plan(SetOperationStmt *setOp, PlannerInfo *root, ...@@ -425,36 +479,41 @@ generate_recursion_plan(SetOperationStmt *setOp, PlannerInfo *root,
* For the moment, take the number of distinct groups as equal to the * For the moment, take the number of distinct groups as equal to the
* total input size, ie, the worst case. * total input size, ie, the worst case.
*/ */
dNumGroups = lplan->plan_rows + rplan->plan_rows * 10; dNumGroups = lpath->rows + rpath->rows * 10;
/* Also convert to long int --- but 'ware overflow! */
numGroups = (long) Min(dNumGroups, (double) LONG_MAX);
} }
/* /*
* And make the plan node. * And make the path node.
*/ */
plan = (Plan *) make_recursive_union(tlist, lplan, rplan, path = (Path *) create_recursiveunion_path(root,
root->wt_param_id, result_rel,
groupList, numGroups); lpath,
rpath,
*sortClauses = NIL; /* RecursiveUnion result is always unsorted */ create_pathtarget(root, tlist),
groupList,
return plan; root->wt_param_id,
dNumGroups);
return path;
} }
/* /*
* Generate plan for a UNION or UNION ALL node * Generate path for a UNION or UNION ALL node
*/ */
static Plan * static Path *
generate_union_plan(SetOperationStmt *op, PlannerInfo *root, generate_union_path(SetOperationStmt *op, PlannerInfo *root,
double tuple_fraction,
List *refnames_tlist, List *refnames_tlist,
List **sortClauses, double *pNumGroups) List **pTargetList,
double *pNumGroups)
{ {
List *planlist; RelOptInfo *result_rel = fetch_upper_rel(root, UPPERREL_SETOP, NULL);
double save_fraction = root->tuple_fraction;
List *pathlist;
List *child_tlists1;
List *child_tlists2;
List *tlist_list;
List *tlist; List *tlist;
Plan *plan; Path *path;
/* /*
* If plain UNION, tell children to fetch all tuples. * If plain UNION, tell children to fetch all tuples.
...@@ -468,20 +527,21 @@ generate_union_plan(SetOperationStmt *op, PlannerInfo *root, ...@@ -468,20 +527,21 @@ generate_union_plan(SetOperationStmt *op, PlannerInfo *root,
* of preferring fast-start plans. * of preferring fast-start plans.
*/ */
if (!op->all) if (!op->all)
tuple_fraction = 0.0; root->tuple_fraction = 0.0;
/* /*
* If any of my children are identical UNION nodes (same op, all-flag, and * If any of my children are identical UNION nodes (same op, all-flag, and
* colTypes) then they can be merged into this node so that we generate * colTypes) then they can be merged into this node so that we generate
* only one Append and unique-ification for the lot. Recurse to find such * only one Append and unique-ification for the lot. Recurse to find such
* nodes and compute their children's plans. * nodes and compute their children's paths.
*/ */
planlist = list_concat(recurse_union_children(op->larg, root, pathlist = list_concat(recurse_union_children(op->larg, root,
tuple_fraction, op, refnames_tlist,
op, refnames_tlist), &child_tlists1),
recurse_union_children(op->rarg, root, recurse_union_children(op->rarg, root,
tuple_fraction, op, refnames_tlist,
op, refnames_tlist)); &child_tlists2));
tlist_list = list_concat(child_tlists1, child_tlists2);
/* /*
* Generate tlist for Append plan node. * Generate tlist for Append plan node.
...@@ -491,21 +551,24 @@ generate_union_plan(SetOperationStmt *op, PlannerInfo *root, ...@@ -491,21 +551,24 @@ generate_union_plan(SetOperationStmt *op, PlannerInfo *root,
* next plan level up. * next plan level up.
*/ */
tlist = generate_append_tlist(op->colTypes, op->colCollations, false, tlist = generate_append_tlist(op->colTypes, op->colCollations, false,
planlist, refnames_tlist); tlist_list, refnames_tlist);
*pTargetList = tlist;
/* /*
* Append the child results together. * Append the child results together.
*/ */
plan = (Plan *) make_append(planlist, tlist); path = (Path *) create_append_path(result_rel, pathlist, NULL, 0);
/* We have to manually jam the right tlist into the path; ick */
path->pathtarget = create_pathtarget(root, tlist);
/* /*
* For UNION ALL, we just need the Append plan. For UNION, need to add * For UNION ALL, we just need the Append path. For UNION, need to add
* node(s) to remove duplicates. * node(s) to remove duplicates.
*/ */
if (op->all) if (!op->all)
*sortClauses = NIL; /* result of UNION ALL is always unsorted */ path = make_union_unique(op, path, tlist, root);
else
plan = make_union_unique(op, plan, root, tuple_fraction, sortClauses);
/* /*
* Estimate number of groups if caller wants it. For now we just assume * Estimate number of groups if caller wants it. For now we just assume
...@@ -513,49 +576,63 @@ generate_union_plan(SetOperationStmt *op, PlannerInfo *root, ...@@ -513,49 +576,63 @@ generate_union_plan(SetOperationStmt *op, PlannerInfo *root,
* we want worst-case estimates anyway. * we want worst-case estimates anyway.
*/ */
if (pNumGroups) if (pNumGroups)
*pNumGroups = plan->plan_rows; *pNumGroups = path->rows;
/* Undo effects of possibly forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
return plan; return path;
} }
/* /*
* Generate plan for an INTERSECT, INTERSECT ALL, EXCEPT, or EXCEPT ALL node * Generate path for an INTERSECT, INTERSECT ALL, EXCEPT, or EXCEPT ALL node
*/ */
static Plan * static Path *
generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root, generate_nonunion_path(SetOperationStmt *op, PlannerInfo *root,
double tuple_fraction,
List *refnames_tlist, List *refnames_tlist,
List **sortClauses, double *pNumGroups) List **pTargetList,
double *pNumGroups)
{ {
Plan *lplan, RelOptInfo *result_rel = fetch_upper_rel(root, UPPERREL_SETOP, NULL);
*rplan, double save_fraction = root->tuple_fraction;
*plan; Path *lpath,
List *tlist, *rpath,
*path;
List *lpath_tlist,
*rpath_tlist,
*tlist_list,
*tlist,
*groupList, *groupList,
*planlist, *pathlist;
*child_sortclauses;
double dLeftGroups, double dLeftGroups,
dRightGroups, dRightGroups,
dNumGroups, dNumGroups,
dNumOutputRows; dNumOutputRows;
long numGroups;
bool use_hash; bool use_hash;
SetOpCmd cmd; SetOpCmd cmd;
int firstFlag; int firstFlag;
/*
* Tell children to fetch all tuples.
*/
root->tuple_fraction = 0.0;
/* Recurse on children, ensuring their outputs are marked */ /* Recurse on children, ensuring their outputs are marked */
lplan = recurse_set_operations(op->larg, root, lpath = recurse_set_operations(op->larg, root,
0.0 /* all tuples needed */ ,
op->colTypes, op->colCollations, op->colTypes, op->colCollations,
false, 0, false, 0,
refnames_tlist, refnames_tlist,
&child_sortclauses, &dLeftGroups); &lpath_tlist,
rplan = recurse_set_operations(op->rarg, root, &dLeftGroups);
0.0 /* all tuples needed */ , rpath = recurse_set_operations(op->rarg, root,
op->colTypes, op->colCollations, op->colTypes, op->colCollations,
false, 1, false, 1,
refnames_tlist, refnames_tlist,
&child_sortclauses, &dRightGroups); &rpath_tlist,
&dRightGroups);
/* Undo effects of forcing tuple_fraction to 0 */
root->tuple_fraction = save_fraction;
/* /*
* For EXCEPT, we must put the left input first. For INTERSECT, either * For EXCEPT, we must put the left input first. For INTERSECT, either
...@@ -565,12 +642,14 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root, ...@@ -565,12 +642,14 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root,
*/ */
if (op->op == SETOP_EXCEPT || dLeftGroups <= dRightGroups) if (op->op == SETOP_EXCEPT || dLeftGroups <= dRightGroups)
{ {
planlist = list_make2(lplan, rplan); pathlist = list_make2(lpath, rpath);
tlist_list = list_make2(lpath_tlist, rpath_tlist);
firstFlag = 0; firstFlag = 0;
} }
else else
{ {
planlist = list_make2(rplan, lplan); pathlist = list_make2(rpath, lpath);
tlist_list = list_make2(rpath_tlist, lpath_tlist);
firstFlag = 1; firstFlag = 1;
} }
...@@ -584,22 +663,24 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root, ...@@ -584,22 +663,24 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root,
* confused. * confused.
*/ */
tlist = generate_append_tlist(op->colTypes, op->colCollations, true, tlist = generate_append_tlist(op->colTypes, op->colCollations, true,
planlist, refnames_tlist); tlist_list, refnames_tlist);
*pTargetList = tlist;
/* /*
* Append the child results together. * Append the child results together.
*/ */
plan = (Plan *) make_append(planlist, tlist); path = (Path *) create_append_path(result_rel, pathlist, NULL, 0);
/* We have to manually jam the right tlist into the path; ick */
path->pathtarget = create_pathtarget(root, tlist);
/* Identify the grouping semantics */ /* Identify the grouping semantics */
groupList = generate_setop_grouplist(op, tlist); groupList = generate_setop_grouplist(op, tlist);
/* punt if nothing to group on (can this happen?) */ /* punt if nothing to group on (can this happen?) */
if (groupList == NIL) if (groupList == NIL)
{ return path;
*sortClauses = NIL;
return plan;
}
/* /*
* Estimate number of distinct groups that we'll need hashtable entries * Estimate number of distinct groups that we'll need hashtable entries
...@@ -612,29 +693,32 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root, ...@@ -612,29 +693,32 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root,
if (op->op == SETOP_EXCEPT) if (op->op == SETOP_EXCEPT)
{ {
dNumGroups = dLeftGroups; dNumGroups = dLeftGroups;
dNumOutputRows = op->all ? lplan->plan_rows : dNumGroups; dNumOutputRows = op->all ? lpath->rows : dNumGroups;
} }
else else
{ {
dNumGroups = Min(dLeftGroups, dRightGroups); dNumGroups = Min(dLeftGroups, dRightGroups);
dNumOutputRows = op->all ? Min(lplan->plan_rows, rplan->plan_rows) : dNumGroups; dNumOutputRows = op->all ? Min(lpath->rows, rpath->rows) : dNumGroups;
} }
/* Also convert to long int --- but 'ware overflow! */
numGroups = (long) Min(dNumGroups, (double) LONG_MAX);
/* /*
* Decide whether to hash or sort, and add a sort node if needed. * Decide whether to hash or sort, and add a sort node if needed.
*/ */
use_hash = choose_hashed_setop(root, groupList, plan, use_hash = choose_hashed_setop(root, groupList, path,
dNumGroups, dNumOutputRows, tuple_fraction, dNumGroups, dNumOutputRows,
(op->op == SETOP_INTERSECT) ? "INTERSECT" : "EXCEPT"); (op->op == SETOP_INTERSECT) ? "INTERSECT" : "EXCEPT");
if (!use_hash) if (!use_hash)
plan = (Plan *) make_sort_from_sortclauses(root, groupList, plan); path = (Path *) create_sort_path(root,
result_rel,
path,
make_pathkeys_for_sortclauses(root,
groupList,
tlist),
-1.0);
/* /*
* Finally, add a SetOp plan node to generate the correct output. * Finally, add a SetOp path node to generate the correct output.
*/ */
switch (op->op) switch (op->op)
{ {
...@@ -649,19 +733,21 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root, ...@@ -649,19 +733,21 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root,
cmd = SETOPCMD_INTERSECT; /* keep compiler quiet */ cmd = SETOPCMD_INTERSECT; /* keep compiler quiet */
break; break;
} }
plan = (Plan *) make_setop(cmd, use_hash ? SETOP_HASHED : SETOP_SORTED, path = (Path *) create_setop_path(root,
plan, groupList, result_rel,
list_length(op->colTypes) + 1, path,
use_hash ? firstFlag : -1, cmd,
numGroups, dNumOutputRows); use_hash ? SETOP_HASHED : SETOP_SORTED,
groupList,
/* Result is sorted only if we're not hashing */ list_length(op->colTypes) + 1,
*sortClauses = use_hash ? NIL : groupList; use_hash ? firstFlag : -1,
dNumGroups,
dNumOutputRows);
if (pNumGroups) if (pNumGroups)
*pNumGroups = dNumGroups; *pNumGroups = dNumGroups;
return plan; return path;
} }
/* /*
...@@ -675,15 +761,16 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root, ...@@ -675,15 +761,16 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root,
* collations have the same notion of equality. It is valid from an * collations have the same notion of equality. It is valid from an
* implementation standpoint because we don't care about the ordering of * implementation standpoint because we don't care about the ordering of
* a UNION child's result: UNION ALL results are always unordered, and * a UNION child's result: UNION ALL results are always unordered, and
* generate_union_plan will force a fresh sort if the top level is a UNION. * generate_union_path will force a fresh sort if the top level is a UNION.
*/ */
static List * static List *
recurse_union_children(Node *setOp, PlannerInfo *root, recurse_union_children(Node *setOp, PlannerInfo *root,
double tuple_fraction,
SetOperationStmt *top_union, SetOperationStmt *top_union,
List *refnames_tlist) List *refnames_tlist,
List **tlist_list)
{ {
List *child_sortclauses; List *result;
List *child_tlist;
if (IsA(setOp, SetOperationStmt)) if (IsA(setOp, SetOperationStmt))
{ {
...@@ -693,15 +780,20 @@ recurse_union_children(Node *setOp, PlannerInfo *root, ...@@ -693,15 +780,20 @@ recurse_union_children(Node *setOp, PlannerInfo *root,
(op->all == top_union->all || op->all) && (op->all == top_union->all || op->all) &&
equal(op->colTypes, top_union->colTypes)) equal(op->colTypes, top_union->colTypes))
{ {
/* Same UNION, so fold children into parent's subplan list */ /* Same UNION, so fold children into parent's subpath list */
return list_concat(recurse_union_children(op->larg, root, List *child_tlists1;
tuple_fraction, List *child_tlists2;
top_union,
refnames_tlist), result = list_concat(recurse_union_children(op->larg, root,
recurse_union_children(op->rarg, root, top_union,
tuple_fraction, refnames_tlist,
top_union, &child_tlists1),
refnames_tlist)); recurse_union_children(op->rarg, root,
top_union,
refnames_tlist,
&child_tlists2));
*tlist_list = list_concat(child_tlists1, child_tlists2);
return result;
} }
} }
...@@ -714,36 +806,34 @@ recurse_union_children(Node *setOp, PlannerInfo *root, ...@@ -714,36 +806,34 @@ recurse_union_children(Node *setOp, PlannerInfo *root,
* tuples have junk and some don't. This case only arises when we have an * tuples have junk and some don't. This case only arises when we have an
* EXCEPT or INTERSECT as child, else there won't be resjunk anyway. * EXCEPT or INTERSECT as child, else there won't be resjunk anyway.
*/ */
return list_make1(recurse_set_operations(setOp, root, result = list_make1(recurse_set_operations(setOp, root,
tuple_fraction, top_union->colTypes,
top_union->colTypes, top_union->colCollations,
top_union->colCollations, false, -1,
false, -1, refnames_tlist,
refnames_tlist, &child_tlist,
&child_sortclauses, NULL)); NULL));
*tlist_list = list_make1(child_tlist);
return result;
} }
/* /*
* Add nodes to the given plan tree to unique-ify the result of a UNION. * Add nodes to the given path tree to unique-ify the result of a UNION.
*/ */
static Plan * static Path *
make_union_unique(SetOperationStmt *op, Plan *plan, make_union_unique(SetOperationStmt *op, Path *path, List *tlist,
PlannerInfo *root, double tuple_fraction, PlannerInfo *root)
List **sortClauses)
{ {
RelOptInfo *result_rel = fetch_upper_rel(root, UPPERREL_SETOP, NULL);
List *groupList; List *groupList;
double dNumGroups; double dNumGroups;
long numGroups;
/* Identify the grouping semantics */ /* Identify the grouping semantics */
groupList = generate_setop_grouplist(op, plan->targetlist); groupList = generate_setop_grouplist(op, tlist);
/* punt if nothing to group on (can this happen?) */ /* punt if nothing to group on (can this happen?) */
if (groupList == NIL) if (groupList == NIL)
{ return path;
*sortClauses = NIL;
return plan;
}
/* /*
* XXX for the moment, take the number of distinct groups as equal to the * XXX for the moment, take the number of distinct groups as equal to the
...@@ -753,45 +843,44 @@ make_union_unique(SetOperationStmt *op, Plan *plan, ...@@ -753,45 +843,44 @@ make_union_unique(SetOperationStmt *op, Plan *plan,
* as well the propensity of novices to write UNION rather than UNION ALL * as well the propensity of novices to write UNION rather than UNION ALL
* even when they don't expect any duplicates... * even when they don't expect any duplicates...
*/ */
dNumGroups = plan->plan_rows; dNumGroups = path->rows;
/* Also convert to long int --- but 'ware overflow! */
numGroups = (long) Min(dNumGroups, (double) LONG_MAX);
/* Decide whether to hash or sort */ /* Decide whether to hash or sort */
if (choose_hashed_setop(root, groupList, plan, if (choose_hashed_setop(root, groupList, path,
dNumGroups, dNumGroups, tuple_fraction, dNumGroups, dNumGroups,
"UNION")) "UNION"))
{ {
/* Hashed aggregate plan --- no sort needed */ /* Hashed aggregate plan --- no sort needed */
plan = (Plan *) make_agg(root, path = (Path *) create_agg_path(root,
plan->targetlist, result_rel,
NIL, path,
AGG_HASHED, create_pathtarget(root, tlist),
NULL, AGG_HASHED,
list_length(groupList), groupList,
extract_grouping_cols(groupList, NIL,
plan->targetlist), NULL,
extract_grouping_ops(groupList), dNumGroups);
NIL,
numGroups,
false,
true,
plan);
/* Hashed aggregation produces randomly-ordered results */
*sortClauses = NIL;
} }
else else
{ {
/* Sort and Unique */ /* Sort and Unique */
plan = (Plan *) make_sort_from_sortclauses(root, groupList, plan); path = (Path *) create_sort_path(root,
plan = (Plan *) make_unique(plan, groupList); result_rel,
plan->plan_rows = dNumGroups; path,
/* We know the sort order of the result */ make_pathkeys_for_sortclauses(root,
*sortClauses = groupList; groupList,
tlist),
-1.0);
/* We have to manually jam the right tlist into the path; ick */
path->pathtarget = create_pathtarget(root, tlist);
path = (Path *) create_upper_unique_path(root,
result_rel,
path,
list_length(path->pathkeys),
dNumGroups);
} }
return plan; return path;
} }
/* /*
...@@ -799,9 +888,8 @@ make_union_unique(SetOperationStmt *op, Plan *plan, ...@@ -799,9 +888,8 @@ make_union_unique(SetOperationStmt *op, Plan *plan,
*/ */
static bool static bool
choose_hashed_setop(PlannerInfo *root, List *groupClauses, choose_hashed_setop(PlannerInfo *root, List *groupClauses,
Plan *input_plan, Path *input_path,
double dNumGroups, double dNumOutputRows, double dNumGroups, double dNumOutputRows,
double tuple_fraction,
const char *construct) const char *construct)
{ {
int numGroupCols = list_length(groupClauses); int numGroupCols = list_length(groupClauses);
...@@ -810,6 +898,7 @@ choose_hashed_setop(PlannerInfo *root, List *groupClauses, ...@@ -810,6 +898,7 @@ choose_hashed_setop(PlannerInfo *root, List *groupClauses,
Size hashentrysize; Size hashentrysize;
Path hashed_p; Path hashed_p;
Path sorted_p; Path sorted_p;
double tuple_fraction;
/* Check whether the operators support sorting or hashing */ /* Check whether the operators support sorting or hashing */
can_sort = grouping_is_sortable(groupClauses); can_sort = grouping_is_sortable(groupClauses);
...@@ -837,7 +926,7 @@ choose_hashed_setop(PlannerInfo *root, List *groupClauses, ...@@ -837,7 +926,7 @@ choose_hashed_setop(PlannerInfo *root, List *groupClauses,
* Don't do it if it doesn't look like the hashtable will fit into * Don't do it if it doesn't look like the hashtable will fit into
* work_mem. * work_mem.
*/ */
hashentrysize = MAXALIGN(input_plan->plan_width) + MAXALIGN(SizeofMinimalTupleHeader); hashentrysize = MAXALIGN(input_path->pathtarget->width) + MAXALIGN(SizeofMinimalTupleHeader);
if (hashentrysize * dNumGroups > work_mem * 1024L) if (hashentrysize * dNumGroups > work_mem * 1024L)
return false; return false;
...@@ -855,27 +944,28 @@ choose_hashed_setop(PlannerInfo *root, List *groupClauses, ...@@ -855,27 +944,28 @@ choose_hashed_setop(PlannerInfo *root, List *groupClauses,
*/ */
cost_agg(&hashed_p, root, AGG_HASHED, NULL, cost_agg(&hashed_p, root, AGG_HASHED, NULL,
numGroupCols, dNumGroups, numGroupCols, dNumGroups,
input_plan->startup_cost, input_plan->total_cost, input_path->startup_cost, input_path->total_cost,
input_plan->plan_rows); input_path->rows);
/* /*
* Now for the sorted case. Note that the input is *always* unsorted, * Now for the sorted case. Note that the input is *always* unsorted,
* since it was made by appending unrelated sub-relations together. * since it was made by appending unrelated sub-relations together.
*/ */
sorted_p.startup_cost = input_plan->startup_cost; sorted_p.startup_cost = input_path->startup_cost;
sorted_p.total_cost = input_plan->total_cost; sorted_p.total_cost = input_path->total_cost;
/* XXX cost_sort doesn't actually look at pathkeys, so just pass NIL */ /* XXX cost_sort doesn't actually look at pathkeys, so just pass NIL */
cost_sort(&sorted_p, root, NIL, sorted_p.total_cost, cost_sort(&sorted_p, root, NIL, sorted_p.total_cost,
input_plan->plan_rows, input_plan->plan_width, input_path->rows, input_path->pathtarget->width,
0.0, work_mem, -1.0); 0.0, work_mem, -1.0);
cost_group(&sorted_p, root, numGroupCols, dNumGroups, cost_group(&sorted_p, root, numGroupCols, dNumGroups,
sorted_p.startup_cost, sorted_p.total_cost, sorted_p.startup_cost, sorted_p.total_cost,
input_plan->plan_rows); input_path->rows);
/* /*
* Now make the decision using the top-level tuple fraction. First we * Now make the decision using the top-level tuple fraction. First we
* have to convert an absolute count (LIMIT) into fractional form. * have to convert an absolute count (LIMIT) into fractional form.
*/ */
tuple_fraction = root->tuple_fraction;
if (tuple_fraction >= 1.0) if (tuple_fraction >= 1.0)
tuple_fraction /= dNumOutputRows; tuple_fraction /= dNumOutputRows;
...@@ -995,6 +1085,14 @@ generate_setop_tlist(List *colTypes, List *colCollations, ...@@ -995,6 +1085,14 @@ generate_setop_tlist(List *colTypes, List *colCollations,
(AttrNumber) resno++, (AttrNumber) resno++,
pstrdup(reftle->resname), pstrdup(reftle->resname),
false); false);
/*
* By convention, all non-resjunk columns in a setop tree have
* ressortgroupref equal to their resno. In some cases the ref isn't
* needed, but this is a cleaner way than modifying the tlist later.
*/
tle->ressortgroupref = tle->resno;
tlist = lappend(tlist, tle); tlist = lappend(tlist, tle);
} }
...@@ -1025,17 +1123,21 @@ generate_setop_tlist(List *colTypes, List *colCollations, ...@@ -1025,17 +1123,21 @@ generate_setop_tlist(List *colTypes, List *colCollations,
* colTypes: OID list of set-op's result column datatypes * colTypes: OID list of set-op's result column datatypes
* colCollations: OID list of set-op's result column collations * colCollations: OID list of set-op's result column collations
* flag: true to create a flag column copied up from subplans * flag: true to create a flag column copied up from subplans
* input_plans: list of sub-plans of the Append * input_tlists: list of tlists for sub-plans of the Append
* refnames_tlist: targetlist to take column names from * refnames_tlist: targetlist to take column names from
* *
* The entries in the Append's targetlist should always be simple Vars; * The entries in the Append's targetlist should always be simple Vars;
* we just have to make sure they have the right datatypes/typmods/collations. * we just have to make sure they have the right datatypes/typmods/collations.
* The Vars are always generated with varno 0. * The Vars are always generated with varno 0.
*
* XXX a problem with the varno-zero approach is that set_pathtarget_cost_width
* cannot figure out a realistic width for the tlist we make here. But we
* ought to refactor this code to produce a PathTarget directly, anyway.
*/ */
static List * static List *
generate_append_tlist(List *colTypes, List *colCollations, generate_append_tlist(List *colTypes, List *colCollations,
bool flag, bool flag,
List *input_plans, List *input_tlists,
List *refnames_tlist) List *refnames_tlist)
{ {
List *tlist = NIL; List *tlist = NIL;
...@@ -1046,7 +1148,7 @@ generate_append_tlist(List *colTypes, List *colCollations, ...@@ -1046,7 +1148,7 @@ generate_append_tlist(List *colTypes, List *colCollations,
int colindex; int colindex;
TargetEntry *tle; TargetEntry *tle;
Node *expr; Node *expr;
ListCell *planl; ListCell *tlistl;
int32 *colTypmods; int32 *colTypmods;
/* /*
...@@ -1057,16 +1159,16 @@ generate_append_tlist(List *colTypes, List *colCollations, ...@@ -1057,16 +1159,16 @@ generate_append_tlist(List *colTypes, List *colCollations,
*/ */
colTypmods = (int32 *) palloc(list_length(colTypes) * sizeof(int32)); colTypmods = (int32 *) palloc(list_length(colTypes) * sizeof(int32));
foreach(planl, input_plans) foreach(tlistl, input_tlists)
{ {
Plan *subplan = (Plan *) lfirst(planl); List *subtlist = (List *) lfirst(tlistl);
ListCell *subtlist; ListCell *subtlistl;
curColType = list_head(colTypes); curColType = list_head(colTypes);
colindex = 0; colindex = 0;
foreach(subtlist, subplan->targetlist) foreach(subtlistl, subtlist)
{ {
TargetEntry *subtle = (TargetEntry *) lfirst(subtlist); TargetEntry *subtle = (TargetEntry *) lfirst(subtlistl);
if (subtle->resjunk) if (subtle->resjunk)
continue; continue;
...@@ -1076,7 +1178,7 @@ generate_append_tlist(List *colTypes, List *colCollations, ...@@ -1076,7 +1178,7 @@ generate_append_tlist(List *colTypes, List *colCollations,
/* If first subplan, copy the typmod; else compare */ /* If first subplan, copy the typmod; else compare */
int32 subtypmod = exprTypmod((Node *) subtle->expr); int32 subtypmod = exprTypmod((Node *) subtle->expr);
if (planl == list_head(input_plans)) if (tlistl == list_head(input_tlists))
colTypmods[colindex] = subtypmod; colTypmods[colindex] = subtypmod;
else if (subtypmod != colTypmods[colindex]) else if (subtypmod != colTypmods[colindex])
colTypmods[colindex] = -1; colTypmods[colindex] = -1;
...@@ -1116,6 +1218,14 @@ generate_append_tlist(List *colTypes, List *colCollations, ...@@ -1116,6 +1218,14 @@ generate_append_tlist(List *colTypes, List *colCollations,
(AttrNumber) resno++, (AttrNumber) resno++,
pstrdup(reftle->resname), pstrdup(reftle->resname),
false); false);
/*
* By convention, all non-resjunk columns in a setop tree have
* ressortgroupref equal to their resno. In some cases the ref isn't
* needed, but this is a cleaner way than modifying the tlist later.
*/
tle->ressortgroupref = tle->resno;
tlist = lappend(tlist, tle); tlist = lappend(tlist, tle);
} }
...@@ -1150,7 +1260,7 @@ generate_append_tlist(List *colTypes, List *colCollations, ...@@ -1150,7 +1260,7 @@ generate_append_tlist(List *colTypes, List *colCollations,
* list, except that the entries do not have sortgrouprefs set because * list, except that the entries do not have sortgrouprefs set because
* the parser output representation doesn't include a tlist for each * the parser output representation doesn't include a tlist for each
* setop. So what we need to do here is copy that list and install * setop. So what we need to do here is copy that list and install
* proper sortgrouprefs into it and into the targetlist. * proper sortgrouprefs into it (copying those from the targetlist).
*/ */
static List * static List *
generate_setop_grouplist(SetOperationStmt *op, List *targetlist) generate_setop_grouplist(SetOperationStmt *op, List *targetlist)
...@@ -1158,7 +1268,6 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) ...@@ -1158,7 +1268,6 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist)
List *grouplist = (List *) copyObject(op->groupClauses); List *grouplist = (List *) copyObject(op->groupClauses);
ListCell *lg; ListCell *lg;
ListCell *lt; ListCell *lt;
Index refno = 1;
lg = list_head(grouplist); lg = list_head(grouplist);
foreach(lt, targetlist) foreach(lt, targetlist)
...@@ -1166,11 +1275,15 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) ...@@ -1166,11 +1275,15 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist)
TargetEntry *tle = (TargetEntry *) lfirst(lt); TargetEntry *tle = (TargetEntry *) lfirst(lt);
SortGroupClause *sgc; SortGroupClause *sgc;
/* tlist shouldn't have any sortgrouprefs yet */
Assert(tle->ressortgroupref == 0);
if (tle->resjunk) if (tle->resjunk)
{
/* resjunk columns should not have sortgrouprefs */
Assert(tle->ressortgroupref == 0);
continue; /* ignore resjunk columns */ continue; /* ignore resjunk columns */
}
/* non-resjunk columns should have sortgroupref = resno */
Assert(tle->ressortgroupref == tle->resno);
/* non-resjunk columns should have grouping clauses */ /* non-resjunk columns should have grouping clauses */
Assert(lg != NULL); Assert(lg != NULL);
...@@ -1178,8 +1291,7 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist) ...@@ -1178,8 +1291,7 @@ generate_setop_grouplist(SetOperationStmt *op, List *targetlist)
lg = lnext(lg); lg = lnext(lg);
Assert(sgc->tleSortGroupRef == 0); Assert(sgc->tleSortGroupRef == 0);
/* we could use assignSortGroupRef here, but seems a bit silly */ sgc->tleSortGroupRef = tle->ressortgroupref;
sgc->tleSortGroupRef = tle->ressortgroupref = refno++;
} }
Assert(lg == NULL); Assert(lg == NULL);
return grouplist; return grouplist;
......
...@@ -1063,7 +1063,7 @@ create_bitmap_heap_path(PlannerInfo *root, ...@@ -1063,7 +1063,7 @@ create_bitmap_heap_path(PlannerInfo *root,
pathnode->path.param_info = get_baserel_parampathinfo(root, rel, pathnode->path.param_info = get_baserel_parampathinfo(root, rel,
required_outer); required_outer);
pathnode->path.parallel_aware = false; pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = bitmapqual->parallel_safe; pathnode->path.parallel_safe = rel->consider_parallel;
pathnode->path.parallel_degree = 0; pathnode->path.parallel_degree = 0;
pathnode->path.pathkeys = NIL; /* always unordered */ pathnode->path.pathkeys = NIL; /* always unordered */
...@@ -1208,7 +1208,7 @@ create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer, ...@@ -1208,7 +1208,7 @@ create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer,
* Compute rows and costs as sums of subplan rows and costs. We charge * Compute rows and costs as sums of subplan rows and costs. We charge
* nothing extra for the Append itself, which perhaps is too optimistic, * nothing extra for the Append itself, which perhaps is too optimistic,
* but since it doesn't do any selection or projection, it is a pretty * but since it doesn't do any selection or projection, it is a pretty
* cheap node. If you change this, see also make_append(). * cheap node.
*/ */
pathnode->path.rows = 0; pathnode->path.rows = 0;
pathnode->path.startup_cost = 0; pathnode->path.startup_cost = 0;
...@@ -1323,16 +1323,18 @@ create_merge_append_path(PlannerInfo *root, ...@@ -1323,16 +1323,18 @@ create_merge_append_path(PlannerInfo *root,
/* /*
* create_result_path * create_result_path
* Creates a path representing a Result-and-nothing-else plan. * Creates a path representing a Result-and-nothing-else plan.
* This is only used for the case of a query with an empty jointree. *
* This is only used for degenerate cases, such as a query with an empty
* jointree.
*/ */
ResultPath * ResultPath *
create_result_path(RelOptInfo *rel, List *quals) create_result_path(RelOptInfo *rel, PathTarget *target, List *quals)
{ {
ResultPath *pathnode = makeNode(ResultPath); ResultPath *pathnode = makeNode(ResultPath);
pathnode->path.pathtype = T_Result; pathnode->path.pathtype = T_Result;
pathnode->path.parent = rel; pathnode->path.parent = rel;
pathnode->path.pathtarget = &(rel->reltarget); pathnode->path.pathtarget = target;
pathnode->path.param_info = NULL; /* there are no other rels... */ pathnode->path.param_info = NULL; /* there are no other rels... */
pathnode->path.parallel_aware = false; pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel; pathnode->path.parallel_safe = rel->consider_parallel;
...@@ -1342,8 +1344,9 @@ create_result_path(RelOptInfo *rel, List *quals) ...@@ -1342,8 +1344,9 @@ create_result_path(RelOptInfo *rel, List *quals)
/* Hardly worth defining a cost_result() function ... just do it */ /* Hardly worth defining a cost_result() function ... just do it */
pathnode->path.rows = 1; pathnode->path.rows = 1;
pathnode->path.startup_cost = 0; pathnode->path.startup_cost = target->cost.startup;
pathnode->path.total_cost = cpu_tuple_cost; pathnode->path.total_cost = target->cost.startup +
cpu_tuple_cost + target->cost.per_tuple;
/* /*
* In theory we should include the qual eval cost as well, but at present * In theory we should include the qual eval cost as well, but at present
...@@ -1351,8 +1354,8 @@ create_result_path(RelOptInfo *rel, List *quals) ...@@ -1351,8 +1354,8 @@ create_result_path(RelOptInfo *rel, List *quals)
* again in make_result; since this is only used for degenerate cases, * again in make_result; since this is only used for degenerate cases,
* nothing interesting will be done with the path cost values. * nothing interesting will be done with the path cost values.
* *
* (Likewise, we don't worry about pathtarget->cost since that tlist will * XXX should refactor so that make_result does not do costing work, at
* be empty at this point.) * which point this will need to do it honestly.
*/ */
return pathnode; return pathnode;
...@@ -1375,8 +1378,9 @@ create_material_path(RelOptInfo *rel, Path *subpath) ...@@ -1375,8 +1378,9 @@ create_material_path(RelOptInfo *rel, Path *subpath)
pathnode->path.pathtarget = &(rel->reltarget); pathnode->path.pathtarget = &(rel->reltarget);
pathnode->path.param_info = subpath->param_info; pathnode->path.param_info = subpath->param_info;
pathnode->path.parallel_aware = false; pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = subpath->parallel_safe; pathnode->path.parallel_safe = rel->consider_parallel &&
pathnode->path.parallel_degree = 0; subpath->parallel_safe;
pathnode->path.parallel_degree = subpath->parallel_degree;
pathnode->path.pathkeys = subpath->pathkeys; pathnode->path.pathkeys = subpath->pathkeys;
pathnode->subpath = subpath; pathnode->subpath = subpath;
...@@ -1439,8 +1443,9 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, ...@@ -1439,8 +1443,9 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
pathnode->path.pathtarget = &(rel->reltarget); pathnode->path.pathtarget = &(rel->reltarget);
pathnode->path.param_info = subpath->param_info; pathnode->path.param_info = subpath->param_info;
pathnode->path.parallel_aware = false; pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = subpath->parallel_safe; pathnode->path.parallel_safe = rel->consider_parallel &&
pathnode->path.parallel_degree = 0; subpath->parallel_safe;
pathnode->path.parallel_degree = subpath->parallel_degree;
/* /*
* Assume the output is unsorted, since we don't necessarily have pathkeys * Assume the output is unsorted, since we don't necessarily have pathkeys
...@@ -1540,7 +1545,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, ...@@ -1540,7 +1545,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
* Charge one cpu_operator_cost per comparison per input tuple. We * Charge one cpu_operator_cost per comparison per input tuple. We
* assume all columns get compared at most of the tuples. (XXX * assume all columns get compared at most of the tuples. (XXX
* probably this is an overestimate.) This should agree with * probably this is an overestimate.) This should agree with
* make_unique. * create_upper_unique_path.
*/ */
sort_path.total_cost += cpu_operator_cost * rel->rows * numCols; sort_path.total_cost += cpu_operator_cost * rel->rows * numCols;
} }
...@@ -1607,8 +1612,37 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, ...@@ -1607,8 +1612,37 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
} }
/* /*
* create_gather_path * translate_sub_tlist - get subquery column numbers represented by tlist
*
* The given targetlist usually contains only Vars referencing the given relid.
* Extract their varattnos (ie, the column numbers of the subquery) and return
* as an integer List.
* *
* If any of the tlist items is not a simple Var, we cannot determine whether
* the subquery's uniqueness condition (if any) matches ours, so punt and
* return NIL.
*/
static List *
translate_sub_tlist(List *tlist, int relid)
{
List *result = NIL;
ListCell *l;
foreach(l, tlist)
{
Var *var = (Var *) lfirst(l);
if (!var || !IsA(var, Var) ||
var->varno != relid)
return NIL; /* punt */
result = lappend_int(result, var->varattno);
}
return result;
}
/*
* create_gather_path
* Creates a path corresponding to a gather scan, returning the * Creates a path corresponding to a gather scan, returning the
* pathnode. * pathnode.
*/ */
...@@ -1645,58 +1679,30 @@ create_gather_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath, ...@@ -1645,58 +1679,30 @@ create_gather_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
return pathnode; return pathnode;
} }
/*
* translate_sub_tlist - get subquery column numbers represented by tlist
*
* The given targetlist usually contains only Vars referencing the given relid.
* Extract their varattnos (ie, the column numbers of the subquery) and return
* as an integer List.
*
* If any of the tlist items is not a simple Var, we cannot determine whether
* the subquery's uniqueness condition (if any) matches ours, so punt and
* return NIL.
*/
static List *
translate_sub_tlist(List *tlist, int relid)
{
List *result = NIL;
ListCell *l;
foreach(l, tlist)
{
Var *var = (Var *) lfirst(l);
if (!var || !IsA(var, Var) ||
var->varno != relid)
return NIL; /* punt */
result = lappend_int(result, var->varattno);
}
return result;
}
/* /*
* create_subqueryscan_path * create_subqueryscan_path
* Creates a path corresponding to a sequential scan of a subquery, * Creates a path corresponding to a scan of a subquery,
* returning the pathnode. * returning the pathnode.
*/ */
Path * SubqueryScanPath *
create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel, create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
List *pathkeys, Relids required_outer) List *pathkeys, Relids required_outer)
{ {
Path *pathnode = makeNode(Path); SubqueryScanPath *pathnode = makeNode(SubqueryScanPath);
pathnode->pathtype = T_SubqueryScan; pathnode->path.pathtype = T_SubqueryScan;
pathnode->parent = rel; pathnode->path.parent = rel;
pathnode->pathtarget = &(rel->reltarget); pathnode->path.pathtarget = &(rel->reltarget);
pathnode->param_info = get_baserel_parampathinfo(root, rel, pathnode->path.param_info = get_baserel_parampathinfo(root, rel,
required_outer); required_outer);
pathnode->parallel_aware = false; pathnode->path.parallel_aware = false;
pathnode->parallel_safe = rel->consider_parallel; pathnode->path.parallel_safe = rel->consider_parallel &&
pathnode->parallel_degree = 0; subpath->parallel_safe;
pathnode->pathkeys = pathkeys; pathnode->path.parallel_degree = subpath->parallel_degree;
pathnode->path.pathkeys = pathkeys;
pathnode->subpath = subpath;
cost_subqueryscan(pathnode, root, rel, pathnode->param_info); cost_subqueryscan(pathnode, root, rel, pathnode->path.param_info);
return pathnode; return pathnode;
} }
...@@ -2035,7 +2041,8 @@ create_mergejoin_path(PlannerInfo *root, ...@@ -2035,7 +2041,8 @@ create_mergejoin_path(PlannerInfo *root,
pathnode->jpath.path.parallel_aware = false; pathnode->jpath.path.parallel_aware = false;
pathnode->jpath.path.parallel_safe = joinrel->consider_parallel && pathnode->jpath.path.parallel_safe = joinrel->consider_parallel &&
outer_path->parallel_safe && inner_path->parallel_safe; outer_path->parallel_safe && inner_path->parallel_safe;
pathnode->jpath.path.parallel_degree = 0; /* This is a foolish way to estimate parallel_degree, but for now... */
pathnode->jpath.path.parallel_degree = outer_path->parallel_degree;
pathnode->jpath.path.pathkeys = pathkeys; pathnode->jpath.path.pathkeys = pathkeys;
pathnode->jpath.jointype = jointype; pathnode->jpath.jointype = jointype;
pathnode->jpath.outerjoinpath = outer_path; pathnode->jpath.outerjoinpath = outer_path;
...@@ -2123,6 +2130,911 @@ create_hashjoin_path(PlannerInfo *root, ...@@ -2123,6 +2130,911 @@ create_hashjoin_path(PlannerInfo *root,
return pathnode; return pathnode;
} }
/*
* create_projection_path
* Creates a pathnode that represents performing a projection.
*
* '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);
pathnode->path.pathtype = T_Result;
pathnode->path.parent = rel;
pathnode->path.pathtarget = target;
/* For now, assume we are above any joins, so no parameterization */
pathnode->path.param_info = NULL;
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
pathnode->path.parallel_degree = subpath->parallel_degree;
/* Projection does not change the sort order */
pathnode->path.pathkeys = subpath->pathkeys;
pathnode->subpath = subpath;
/*
* The Result node's cost is cpu_tuple_cost per row, plus the cost of
* evaluating the tlist.
*/
pathnode->path.rows = subpath->rows;
pathnode->path.startup_cost = subpath->startup_cost + target->cost.startup;
pathnode->path.total_cost = subpath->total_cost + target->cost.startup +
(cpu_tuple_cost + target->cost.per_tuple) * subpath->rows;
return pathnode;
}
/*
* apply_projection_to_path
* Add a projection step, or just apply the target directly to given path.
*
* Most plan types include ExecProject, so we can implement a new projection
* without an extra plan node: just replace the given path's pathtarget with
* the desired one. If the given path can't project, add a ProjectionPath.
*
* We can also short-circuit cases where the targetlist expressions are
* actually equal; this is not an uncommon case, since it may arise from
* trying to apply a PathTarget with sortgroupref labeling to a derived
* path without such labeling.
*
* This requires knowing that the source path won't be referenced for other
* purposes (e.g., other possible paths), since we modify it in-place. Note
* also that we mustn't change the source path's parent link; so when it is
* add_path'd to "rel" things will be a bit inconsistent. So far that has
* not caused any trouble.
*
* 'rel' is the parent relation associated with the result
* 'path' is the path representing the source of data
* 'target' is the PathTarget to be computed
*/
Path *
apply_projection_to_path(PlannerInfo *root,
RelOptInfo *rel,
Path *path,
PathTarget *target)
{
QualCost oldcost;
/* Make a separate ProjectionPath if needed */
if (!is_projection_capable_path(path) &&
!equal(path->pathtarget->exprs, target->exprs))
return (Path *) create_projection_path(root, rel, path, target);
/*
* We can just jam the desired tlist into the existing path, being sure to
* update its cost estimates appropriately.
*/
oldcost = path->pathtarget->cost;
path->pathtarget = target;
path->startup_cost += target->cost.startup - oldcost.startup;
path->total_cost += target->cost.startup - oldcost.startup +
(target->cost.per_tuple - oldcost.per_tuple) * path->rows;
return path;
}
/*
* create_sort_path
* Creates a pathnode that represents performing an explicit sort.
*
* 'rel' is the parent relation associated with the result
* 'subpath' is the path representing the source of data
* 'pathkeys' represents the desired sort order
* 'limit_tuples' is the estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate
*/
SortPath *
create_sort_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
List *pathkeys,
double limit_tuples)
{
SortPath *pathnode = makeNode(SortPath);
pathnode->path.pathtype = T_Sort;
pathnode->path.parent = rel;
/* Sort doesn't project, so use source path's pathtarget */
pathnode->path.pathtarget = subpath->pathtarget;
/* For now, assume we are above any joins, so no parameterization */
pathnode->path.param_info = NULL;
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
pathnode->path.parallel_degree = subpath->parallel_degree;
pathnode->path.pathkeys = pathkeys;
pathnode->subpath = subpath;
cost_sort(&pathnode->path, root, pathkeys,
subpath->total_cost,
subpath->rows,
subpath->pathtarget->width,
0.0, /* XXX comparison_cost shouldn't be 0? */
work_mem, limit_tuples);
return pathnode;
}
/*
* create_group_path
* Creates a pathnode that represents performing grouping of presorted input
*
* '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
* 'groupClause' is a list of SortGroupClause's representing the grouping
* 'qual' is the HAVING quals if any
* 'numGroups' is the estimated number of groups
*/
GroupPath *
create_group_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
PathTarget *target,
List *groupClause,
List *qual,
double numGroups)
{
GroupPath *pathnode = makeNode(GroupPath);
pathnode->path.pathtype = T_Group;
pathnode->path.parent = rel;
pathnode->path.pathtarget = target;
/* For now, assume we are above any joins, so no parameterization */
pathnode->path.param_info = NULL;
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
pathnode->path.parallel_degree = subpath->parallel_degree;
/* Group doesn't change sort ordering */
pathnode->path.pathkeys = subpath->pathkeys;
pathnode->subpath = subpath;
pathnode->groupClause = groupClause;
pathnode->qual = qual;
cost_group(&pathnode->path, root,
list_length(groupClause),
numGroups,
subpath->startup_cost, subpath->total_cost,
subpath->rows);
/* add tlist eval cost for each output row */
pathnode->path.startup_cost += target->cost.startup;
pathnode->path.total_cost += target->cost.startup +
target->cost.per_tuple * pathnode->path.rows;
return pathnode;
}
/*
* create_upper_unique_path
* Creates a pathnode that represents performing an explicit Unique step
* on presorted input.
*
* This produces a Unique plan node, but the use-case is so different from
* create_unique_path that it doesn't seem worth trying to merge the two.
*
* 'rel' is the parent relation associated with the result
* 'subpath' is the path representing the source of data
* 'numCols' is the number of grouping columns
* 'numGroups' is the estimated number of groups
*
* The input path must be sorted on the grouping columns, plus possibly
* additional columns; so the first numCols pathkeys are the grouping columns
*/
UpperUniquePath *
create_upper_unique_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
int numCols,
double numGroups)
{
UpperUniquePath *pathnode = makeNode(UpperUniquePath);
pathnode->path.pathtype = T_Unique;
pathnode->path.parent = rel;
/* Unique doesn't project, so use source path's pathtarget */
pathnode->path.pathtarget = subpath->pathtarget;
/* For now, assume we are above any joins, so no parameterization */
pathnode->path.param_info = NULL;
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
pathnode->path.parallel_degree = subpath->parallel_degree;
/* Unique doesn't change the input ordering */
pathnode->path.pathkeys = subpath->pathkeys;
pathnode->subpath = subpath;
pathnode->numkeys = numCols;
/*
* Charge one cpu_operator_cost per comparison per input tuple. We assume
* all columns get compared at most of the tuples. (XXX probably this is
* an overestimate.)
*/
pathnode->path.startup_cost = subpath->startup_cost;
pathnode->path.total_cost = subpath->total_cost +
cpu_operator_cost * subpath->rows * numCols;
pathnode->path.rows = numGroups;
return pathnode;
}
/*
* create_agg_path
* Creates a pathnode that represents performing aggregation/grouping
*
* '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
* 'aggstrategy' is the Agg node's basic implementation strategy
* 'groupClause' is a list of SortGroupClause's representing the grouping
* 'qual' is the HAVING quals if any
* 'aggcosts' contains cost info about the aggregate functions to be computed
* 'numGroups' is the estimated number of groups (1 if not grouping)
*/
AggPath *
create_agg_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
PathTarget *target,
AggStrategy aggstrategy,
List *groupClause,
List *qual,
const AggClauseCosts *aggcosts,
double numGroups)
{
AggPath *pathnode = makeNode(AggPath);
pathnode->path.pathtype = T_Agg;
pathnode->path.parent = rel;
pathnode->path.pathtarget = target;
/* For now, assume we are above any joins, so no parameterization */
pathnode->path.param_info = NULL;
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
pathnode->path.parallel_degree = subpath->parallel_degree;
if (aggstrategy == AGG_SORTED)
pathnode->path.pathkeys = subpath->pathkeys; /* preserves order */
else
pathnode->path.pathkeys = NIL; /* output is unordered */
pathnode->subpath = subpath;
pathnode->aggstrategy = aggstrategy;
pathnode->numGroups = numGroups;
pathnode->groupClause = groupClause;
pathnode->qual = qual;
cost_agg(&pathnode->path, root,
aggstrategy, aggcosts,
list_length(groupClause), numGroups,
subpath->startup_cost, subpath->total_cost,
subpath->rows);
/* add tlist eval cost for each output row */
pathnode->path.startup_cost += target->cost.startup;
pathnode->path.total_cost += target->cost.startup +
target->cost.per_tuple * pathnode->path.rows;
return pathnode;
}
/*
* create_groupingsets_path
* Creates a pathnode that represents performing GROUPING SETS aggregation
*
* GroupingSetsPath represents sorted grouping with one or more grouping sets.
* The input path's result must be sorted to match the last entry in
* rollup_groupclauses, and groupColIdx[] identifies its sort columns.
*
* '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
* 'having_qual' is the HAVING quals if any
* 'groupColIdx' is an array of indexes of grouping columns in the source data
* 'rollup_lists' is a list of grouping sets
* 'rollup_groupclauses' is a list of grouping clauses for grouping sets
* 'agg_costs' contains cost info about the aggregate functions to be computed
* 'numGroups' is the estimated number of groups
*/
GroupingSetsPath *
create_groupingsets_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
PathTarget *target,
List *having_qual,
AttrNumber *groupColIdx,
List *rollup_lists,
List *rollup_groupclauses,
const AggClauseCosts *agg_costs,
double numGroups)
{
GroupingSetsPath *pathnode = makeNode(GroupingSetsPath);
int numGroupCols;
/* The topmost generated Plan node will be an Agg */
pathnode->path.pathtype = T_Agg;
pathnode->path.parent = rel;
pathnode->path.pathtarget = target;
pathnode->path.param_info = subpath->param_info;
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
pathnode->path.parallel_degree = subpath->parallel_degree;
pathnode->subpath = subpath;
/*
* Output will be in sorted order by group_pathkeys if, and only if, there
* is a single rollup operation on a non-empty list of grouping
* expressions.
*/
if (list_length(rollup_groupclauses) == 1 &&
((List *) linitial(rollup_groupclauses)) != NIL)
pathnode->path.pathkeys = root->group_pathkeys;
else
pathnode->path.pathkeys = NIL;
pathnode->groupColIdx = groupColIdx;
pathnode->rollup_groupclauses = rollup_groupclauses;
pathnode->rollup_lists = rollup_lists;
pathnode->qual = having_qual;
Assert(rollup_lists != NIL);
Assert(list_length(rollup_lists) == list_length(rollup_groupclauses));
/* Account for cost of the topmost Agg node */
numGroupCols = list_length((List *) linitial((List *) llast(rollup_lists)));
cost_agg(&pathnode->path, root,
(numGroupCols > 0) ? AGG_SORTED : AGG_PLAIN,
agg_costs,
numGroupCols,
numGroups,
subpath->startup_cost,
subpath->total_cost,
subpath->rows);
/*
* Add in the costs and output rows of the additional sorting/aggregation
* steps, if any. Only total costs count, since the extra sorts aren't
* run on startup.
*/
if (list_length(rollup_lists) > 1)
{
ListCell *lc;
foreach(lc, rollup_lists)
{
List *gsets = (List *) lfirst(lc);
Path sort_path; /* dummy for result of cost_sort */
Path agg_path; /* dummy for result of cost_agg */
/* We must iterate over all but the last rollup_lists element */
if (lnext(lc) == NULL)
break;
/* Account for cost of sort, but don't charge input cost again */
cost_sort(&sort_path, root, NIL,
0.0,
subpath->rows,
subpath->pathtarget->width,
0.0,
work_mem,
-1.0);
/* Account for cost of aggregation */
numGroupCols = list_length((List *) linitial(gsets));
cost_agg(&agg_path, root,
AGG_SORTED,
agg_costs,
numGroupCols,
numGroups, /* XXX surely not right for all steps? */
sort_path.startup_cost,
sort_path.total_cost,
sort_path.rows);
pathnode->path.total_cost += agg_path.total_cost;
pathnode->path.rows += agg_path.rows;
}
}
/* add tlist eval cost for each output row */
pathnode->path.startup_cost += target->cost.startup;
pathnode->path.total_cost += target->cost.startup +
target->cost.per_tuple * pathnode->path.rows;
return pathnode;
}
/*
* create_minmaxagg_path
* Creates a pathnode that represents computation of MIN/MAX aggregates
*
* 'rel' is the parent relation associated with the result
* 'target' is the PathTarget to be computed
* 'mmaggregates' is a list of MinMaxAggInfo structs
* 'quals' is the HAVING quals if any
*/
MinMaxAggPath *
create_minmaxagg_path(PlannerInfo *root,
RelOptInfo *rel,
PathTarget *target,
List *mmaggregates,
List *quals)
{
MinMaxAggPath *pathnode = makeNode(MinMaxAggPath);
Cost initplan_cost;
ListCell *lc;
/* The topmost generated Plan node will be a Result */
pathnode->path.pathtype = T_Result;
pathnode->path.parent = rel;
pathnode->path.pathtarget = target;
/* For now, assume we are above any joins, so no parameterization */
pathnode->path.param_info = NULL;
pathnode->path.parallel_aware = false;
/* A MinMaxAggPath implies use of subplans, so cannot be parallel-safe */
pathnode->path.parallel_safe = false;
pathnode->path.parallel_degree = 0;
/* Result is one unordered row */
pathnode->path.rows = 1;
pathnode->path.pathkeys = NIL;
pathnode->mmaggregates = mmaggregates;
pathnode->quals = quals;
/* Calculate cost of all the initplans ... */
initplan_cost = 0;
foreach(lc, mmaggregates)
{
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
initplan_cost += mminfo->pathcost;
}
/* add tlist eval cost for each output row, plus cpu_tuple_cost */
pathnode->path.startup_cost = initplan_cost + target->cost.startup;
pathnode->path.total_cost = initplan_cost + target->cost.startup +
target->cost.per_tuple + cpu_tuple_cost;
return pathnode;
}
/*
* create_windowagg_path
* Creates a pathnode that represents computation of window functions
*
* '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
* 'windowFuncs' is a list of WindowFunc structs
* 'winclause' is a WindowClause that is common to all the WindowFuncs
* 'winpathkeys' is the pathkeys for the PARTITION keys + ORDER keys
*
* The actual sort order of the input must match winpathkeys, but might
* have additional keys after those.
*/
WindowAggPath *
create_windowagg_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
PathTarget *target,
List *windowFuncs,
WindowClause *winclause,
List *winpathkeys)
{
WindowAggPath *pathnode = makeNode(WindowAggPath);
pathnode->path.pathtype = T_WindowAgg;
pathnode->path.parent = rel;
pathnode->path.pathtarget = target;
/* For now, assume we are above any joins, so no parameterization */
pathnode->path.param_info = NULL;
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
pathnode->path.parallel_degree = subpath->parallel_degree;
/* WindowAgg preserves the input sort order */
pathnode->path.pathkeys = subpath->pathkeys;
pathnode->subpath = subpath;
pathnode->winclause = winclause;
pathnode->winpathkeys = winpathkeys;
/*
* For costing purposes, assume that there are no redundant partitioning
* or ordering columns; it's not worth the trouble to deal with that
* corner case here. So we just pass the unmodified list lengths to
* cost_windowagg.
*/
cost_windowagg(&pathnode->path, root,
windowFuncs,
list_length(winclause->partitionClause),
list_length(winclause->orderClause),
subpath->startup_cost,
subpath->total_cost,
subpath->rows);
/* add tlist eval cost for each output row */
pathnode->path.startup_cost += target->cost.startup;
pathnode->path.total_cost += target->cost.startup +
target->cost.per_tuple * pathnode->path.rows;
return pathnode;
}
/*
* create_setop_path
* Creates a pathnode that represents computation of INTERSECT or EXCEPT
*
* 'rel' is the parent relation associated with the result
* 'subpath' is the path representing the source of data
* 'cmd' is the specific semantics (INTERSECT or EXCEPT, with/without ALL)
* 'strategy' is the implementation strategy (sorted or hashed)
* 'distinctList' is a list of SortGroupClause's representing the grouping
* 'flagColIdx' is the column number where the flag column will be, if any
* 'firstFlag' is the flag value for the first input relation when hashing;
* or -1 when sorting
* 'numGroups' is the estimated number of distinct groups
* 'outputRows' is the estimated number of output rows
*/
SetOpPath *
create_setop_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
SetOpCmd cmd,
SetOpStrategy strategy,
List *distinctList,
AttrNumber flagColIdx,
int firstFlag,
double numGroups,
double outputRows)
{
SetOpPath *pathnode = makeNode(SetOpPath);
pathnode->path.pathtype = T_SetOp;
pathnode->path.parent = rel;
/* SetOp doesn't project, so use source path's pathtarget */
pathnode->path.pathtarget = subpath->pathtarget;
/* For now, assume we are above any joins, so no parameterization */
pathnode->path.param_info = NULL;
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
pathnode->path.parallel_degree = subpath->parallel_degree;
/* SetOp preserves the input sort order if in sort mode */
pathnode->path.pathkeys =
(strategy == SETOP_SORTED) ? subpath->pathkeys : NIL;
pathnode->subpath = subpath;
pathnode->cmd = cmd;
pathnode->strategy = strategy;
pathnode->distinctList = distinctList;
pathnode->flagColIdx = flagColIdx;
pathnode->firstFlag = firstFlag;
pathnode->numGroups = numGroups;
/*
* Charge one cpu_operator_cost per comparison per input tuple. We assume
* all columns get compared at most of the tuples.
*/
pathnode->path.startup_cost = subpath->startup_cost;
pathnode->path.total_cost = subpath->total_cost +
cpu_operator_cost * subpath->rows * list_length(distinctList);
pathnode->path.rows = outputRows;
return pathnode;
}
/*
* create_recursiveunion_path
* Creates a pathnode that represents a recursive UNION node
*
* 'rel' is the parent relation associated with the result
* 'leftpath' is the source of data for the non-recursive term
* 'rightpath' is the source of data for the recursive term
* 'target' is the PathTarget to be computed
* 'distinctList' is a list of SortGroupClause's representing the grouping
* 'wtParam' is the ID of Param representing work table
* 'numGroups' is the estimated number of groups
*
* For recursive UNION ALL, distinctList is empty and numGroups is zero
*/
RecursiveUnionPath *
create_recursiveunion_path(PlannerInfo *root,
RelOptInfo *rel,
Path *leftpath,
Path *rightpath,
PathTarget *target,
List *distinctList,
int wtParam,
double numGroups)
{
RecursiveUnionPath *pathnode = makeNode(RecursiveUnionPath);
pathnode->path.pathtype = T_RecursiveUnion;
pathnode->path.parent = rel;
pathnode->path.pathtarget = target;
/* For now, assume we are above any joins, so no parameterization */
pathnode->path.param_info = NULL;
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
leftpath->parallel_safe && rightpath->parallel_safe;
/* Foolish, but we'll do it like joins for now: */
pathnode->path.parallel_degree = leftpath->parallel_degree;
/* RecursiveUnion result is always unsorted */
pathnode->path.pathkeys = NIL;
pathnode->leftpath = leftpath;
pathnode->rightpath = rightpath;
pathnode->distinctList = distinctList;
pathnode->wtParam = wtParam;
pathnode->numGroups = numGroups;
cost_recursive_union(&pathnode->path, leftpath, rightpath);
return pathnode;
}
/*
* create_lockrows_path
* Creates a pathnode that represents acquiring row locks
*
* 'rel' is the parent relation associated with the result
* 'subpath' is the path representing the source of data
* 'rowMarks' is a list of PlanRowMark's
* 'epqParam' is the ID of Param for EvalPlanQual re-eval
*/
LockRowsPath *
create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath, List *rowMarks, int epqParam)
{
LockRowsPath *pathnode = makeNode(LockRowsPath);
pathnode->path.pathtype = T_LockRows;
pathnode->path.parent = rel;
/* LockRows doesn't project, so use source path's pathtarget */
pathnode->path.pathtarget = subpath->pathtarget;
/* For now, assume we are above any joins, so no parameterization */
pathnode->path.param_info = NULL;
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = false;
pathnode->path.parallel_degree = 0;
pathnode->path.rows = subpath->rows;
/*
* The result cannot be assumed sorted, since locking might cause the sort
* key columns to be replaced with new values.
*/
pathnode->path.pathkeys = NIL;
pathnode->subpath = subpath;
pathnode->rowMarks = rowMarks;
pathnode->epqParam = epqParam;
/*
* We should charge something extra for the costs of row locking and
* possible refetches, but it's hard to say how much. For now, use
* cpu_tuple_cost per row.
*/
pathnode->path.startup_cost = subpath->startup_cost;
pathnode->path.total_cost = subpath->total_cost +
cpu_tuple_cost * subpath->rows;
return pathnode;
}
/*
* create_modifytable_path
* Creates a pathnode that represents performing INSERT/UPDATE/DELETE mods
*
* 'rel' is the parent relation associated with the result
* 'operation' is the operation type
* 'canSetTag' is true if we set the command tag/es_processed
* 'nominalRelation' is the parent RT index for use of EXPLAIN
* 'resultRelations' is an integer list of actual RT indexes of target rel(s)
* 'subpaths' is a list of Path(s) producing source data (one per rel)
* 'subroots' is a list of PlannerInfo structs (one per rel)
* 'withCheckOptionLists' is a list of WCO lists (one per rel)
* 'returningLists' is a list of RETURNING tlists (one per rel)
* 'rowMarks' is a list of PlanRowMarks (non-locking only)
* 'onconflict' is the ON CONFLICT clause, or NULL
* 'epqParam' is the ID of Param for EvalPlanQual re-eval
*/
ModifyTablePath *
create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
CmdType operation, bool canSetTag,
Index nominalRelation,
List *resultRelations, List *subpaths,
List *subroots,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict,
int epqParam)
{
ModifyTablePath *pathnode = makeNode(ModifyTablePath);
double total_size;
ListCell *lc;
Assert(list_length(resultRelations) == list_length(subpaths));
Assert(list_length(resultRelations) == list_length(subroots));
Assert(withCheckOptionLists == NIL ||
list_length(resultRelations) == list_length(withCheckOptionLists));
Assert(returningLists == NIL ||
list_length(resultRelations) == list_length(returningLists));
pathnode->path.pathtype = T_ModifyTable;
pathnode->path.parent = rel;
/* pathtarget is not interesting, just make it minimally valid */
pathnode->path.pathtarget = &(rel->reltarget);
/* For now, assume we are above any joins, so no parameterization */
pathnode->path.param_info = NULL;
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = false;
pathnode->path.parallel_degree = 0;
pathnode->path.pathkeys = NIL;
/*
* Compute cost & rowcount as sum of subpath costs & rowcounts.
*
* Currently, we don't charge anything extra for the actual table
* modification work, nor for the WITH CHECK OPTIONS or RETURNING
* expressions if any. It would only be window dressing, since
* ModifyTable is always a top-level node and there is no way for the
* costs to change any higher-level planning choices. But we might want
* to make it look better sometime.
*/
pathnode->path.startup_cost = 0;
pathnode->path.total_cost = 0;
pathnode->path.rows = 0;
total_size = 0;
foreach(lc, subpaths)
{
Path *subpath = (Path *) lfirst(lc);
if (lc == list_head(subpaths)) /* first node? */
pathnode->path.startup_cost = subpath->startup_cost;
pathnode->path.total_cost += subpath->total_cost;
pathnode->path.rows += subpath->rows;
total_size += subpath->pathtarget->width * subpath->rows;
}
/*
* Set width to the average width of the subpath outputs. XXX this is
* totally wrong: we should report zero if no RETURNING, else an average
* of the RETURNING tlist widths. But it's what happened historically,
* and improving it is a task for another day.
*/
if (pathnode->path.rows > 0)
total_size /= pathnode->path.rows;
pathnode->path.pathtarget->width = rint(total_size);
pathnode->operation = operation;
pathnode->canSetTag = canSetTag;
pathnode->nominalRelation = nominalRelation;
pathnode->resultRelations = resultRelations;
pathnode->subpaths = subpaths;
pathnode->subroots = subroots;
pathnode->withCheckOptionLists = withCheckOptionLists;
pathnode->returningLists = returningLists;
pathnode->rowMarks = rowMarks;
pathnode->onconflict = onconflict;
pathnode->epqParam = epqParam;
return pathnode;
}
/*
* create_limit_path
* Creates a pathnode that represents performing LIMIT/OFFSET
*
* In addition to providing the actual OFFSET and LIMIT expressions,
* the caller must provide estimates of their values for costing purposes.
* The estimates are as computed by preprocess_limit(), ie, 0 represents
* the clause not being present, and -1 means it's present but we could
* not estimate its value.
*
* 'rel' is the parent relation associated with the result
* 'subpath' is the path representing the source of data
* 'limitOffset' is the actual OFFSET expression, or NULL
* 'limitCount' is the actual LIMIT expression, or NULL
* 'offset_est' is the estimated value of the OFFSET expression
* 'count_est' is the estimated value of the LIMIT expression
*/
LimitPath *
create_limit_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath,
Node *limitOffset, Node *limitCount,
int64 offset_est, int64 count_est)
{
LimitPath *pathnode = makeNode(LimitPath);
pathnode->path.pathtype = T_Limit;
pathnode->path.parent = rel;
/* Limit doesn't project, so use source path's pathtarget */
pathnode->path.pathtarget = subpath->pathtarget;
/* For now, assume we are above any joins, so no parameterization */
pathnode->path.param_info = NULL;
pathnode->path.parallel_aware = false;
pathnode->path.parallel_safe = rel->consider_parallel &&
subpath->parallel_safe;
pathnode->path.parallel_degree = subpath->parallel_degree;
pathnode->path.rows = subpath->rows;
pathnode->path.startup_cost = subpath->startup_cost;
pathnode->path.total_cost = subpath->total_cost;
pathnode->path.pathkeys = subpath->pathkeys;
pathnode->subpath = subpath;
pathnode->limitOffset = limitOffset;
pathnode->limitCount = limitCount;
/*
* Adjust the output rows count and costs according to the offset/limit.
* This is only a cosmetic issue if we are at top level, but if we are
* building a subquery then it's important to report correct info to the
* outer planner.
*
* When the offset or count couldn't be estimated, use 10% of the
* estimated number of rows emitted from the subpath.
*
* XXX we don't bother to add eval costs of the offset/limit expressions
* themselves to the path costs. In theory we should, but in most cases
* those expressions are trivial and it's just not worth the trouble.
*/
if (offset_est != 0)
{
double offset_rows;
if (offset_est > 0)
offset_rows = (double) offset_est;
else
offset_rows = clamp_row_est(subpath->rows * 0.10);
if (offset_rows > pathnode->path.rows)
offset_rows = pathnode->path.rows;
if (subpath->rows > 0)
pathnode->path.startup_cost +=
(subpath->total_cost - subpath->startup_cost)
* offset_rows / subpath->rows;
pathnode->path.rows -= offset_rows;
if (pathnode->path.rows < 1)
pathnode->path.rows = 1;
}
if (count_est != 0)
{
double count_rows;
if (count_est > 0)
count_rows = (double) count_est;
else
count_rows = clamp_row_est(subpath->rows * 0.10);
if (count_rows > pathnode->path.rows)
count_rows = pathnode->path.rows;
if (subpath->rows > 0)
pathnode->path.total_cost = pathnode->path.startup_cost +
(subpath->total_cost - subpath->startup_cost)
* count_rows / subpath->rows;
pathnode->path.rows = count_rows;
if (pathnode->path.rows < 1)
pathnode->path.rows = 1;
}
return pathnode;
}
/* /*
* reparameterize_path * reparameterize_path
* Attempt to modify a Path to have greater parameterization * Attempt to modify a Path to have greater parameterization
...@@ -2186,8 +3098,15 @@ reparameterize_path(PlannerInfo *root, Path *path, ...@@ -2186,8 +3098,15 @@ reparameterize_path(PlannerInfo *root, Path *path,
loop_count); loop_count);
} }
case T_SubqueryScan: case T_SubqueryScan:
return create_subqueryscan_path(root, rel, path->pathkeys, {
required_outer); SubqueryScanPath *spath = (SubqueryScanPath *) path;
return (Path *) create_subqueryscan_path(root,
rel,
spath->subpath,
spath->path.pathkeys,
required_outer);
}
default: default:
break; break;
} }
......
...@@ -1142,7 +1142,27 @@ relation_excluded_by_constraints(PlannerInfo *root, ...@@ -1142,7 +1142,27 @@ relation_excluded_by_constraints(PlannerInfo *root,
List *safe_constraints; List *safe_constraints;
ListCell *lc; ListCell *lc;
/* Skip the test if constraint exclusion is disabled for the rel */ /*
* Regardless of the setting of constraint_exclusion, detect
* constant-FALSE-or-NULL restriction clauses. Because const-folding will
* reduce "anything AND FALSE" to just "FALSE", any such case should
* result in exactly one baserestrictinfo entry. This doesn't fire very
* often, but it seems cheap enough to be worth doing anyway. (Without
* this, we'd miss some optimizations that 9.5 and earlier found via much
* more roundabout methods.)
*/
if (list_length(rel->baserestrictinfo) == 1)
{
RestrictInfo *rinfo = (RestrictInfo *) linitial(rel->baserestrictinfo);
Expr *clause = rinfo->clause;
if (clause && IsA(clause, Const) &&
(((Const *) clause)->constisnull ||
!DatumGetBool(((Const *) clause)->constvalue)))
return true;
}
/* Skip further tests if constraint exclusion is disabled for the rel */
if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF || if (constraint_exclusion == CONSTRAINT_EXCLUSION_OFF ||
(constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION && (constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION &&
!(rel->reloptkind == RELOPT_OTHER_MEMBER_REL || !(rel->reloptkind == RELOPT_OTHER_MEMBER_REL ||
......
...@@ -107,6 +107,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) ...@@ -107,6 +107,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
rel->consider_param_startup = false; /* might get changed later */ rel->consider_param_startup = false; /* might get changed later */
rel->consider_parallel = false; /* might get changed later */ rel->consider_parallel = false; /* might get changed later */
rel->reltarget.exprs = NIL; rel->reltarget.exprs = NIL;
rel->reltarget.sortgrouprefs = NULL;
rel->reltarget.cost.startup = 0; rel->reltarget.cost.startup = 0;
rel->reltarget.cost.per_tuple = 0; rel->reltarget.cost.per_tuple = 0;
rel->reltarget.width = 0; rel->reltarget.width = 0;
...@@ -128,7 +129,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) ...@@ -128,7 +129,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
rel->pages = 0; rel->pages = 0;
rel->tuples = 0; rel->tuples = 0;
rel->allvisfrac = 0; rel->allvisfrac = 0;
rel->subplan = NULL;
rel->subroot = NULL; rel->subroot = NULL;
rel->subplan_params = NIL; rel->subplan_params = NIL;
rel->serverid = InvalidOid; rel->serverid = InvalidOid;
...@@ -394,6 +394,7 @@ build_join_rel(PlannerInfo *root, ...@@ -394,6 +394,7 @@ build_join_rel(PlannerInfo *root,
joinrel->consider_param_startup = false; joinrel->consider_param_startup = false;
joinrel->consider_parallel = false; joinrel->consider_parallel = false;
joinrel->reltarget.exprs = NIL; joinrel->reltarget.exprs = NIL;
joinrel->reltarget.sortgrouprefs = NULL;
joinrel->reltarget.cost.startup = 0; joinrel->reltarget.cost.startup = 0;
joinrel->reltarget.cost.per_tuple = 0; joinrel->reltarget.cost.per_tuple = 0;
joinrel->reltarget.width = 0; joinrel->reltarget.width = 0;
...@@ -422,7 +423,6 @@ build_join_rel(PlannerInfo *root, ...@@ -422,7 +423,6 @@ build_join_rel(PlannerInfo *root,
joinrel->pages = 0; joinrel->pages = 0;
joinrel->tuples = 0; joinrel->tuples = 0;
joinrel->allvisfrac = 0; joinrel->allvisfrac = 0;
joinrel->subplan = NULL;
joinrel->subroot = NULL; joinrel->subroot = NULL;
joinrel->subplan_params = NIL; joinrel->subplan_params = NIL;
joinrel->serverid = InvalidOid; joinrel->serverid = InvalidOid;
...@@ -839,6 +839,61 @@ build_empty_join_rel(PlannerInfo *root) ...@@ -839,6 +839,61 @@ build_empty_join_rel(PlannerInfo *root)
} }
/*
* fetch_upper_rel
* Build a RelOptInfo describing some post-scan/join query processing,
* or return a pre-existing one if somebody already built it.
*
* An "upper" relation is identified by an UpperRelationKind and a Relids set.
* The meaning of the Relids set is not specified here, and very likely will
* vary for different relation kinds.
*
* Most of the fields in an upper-level RelOptInfo are not used and are not
* set here (though makeNode should ensure they're zeroes). We basically only
* care about fields that are of interest to add_path() and set_cheapest().
*/
RelOptInfo *
fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind, Relids relids)
{
RelOptInfo *upperrel;
ListCell *lc;
/*
* For the moment, our indexing data structure is just a List for each
* relation kind. If we ever get so many of one kind that this stops
* working well, we can improve it. No code outside this function should
* assume anything about how to find a particular upperrel.
*/
/* If we already made this upperrel for the query, return it */
foreach(lc, root->upper_rels[kind])
{
upperrel = (RelOptInfo *) lfirst(lc);
if (bms_equal(upperrel->relids, relids))
return upperrel;
}
upperrel = makeNode(RelOptInfo);
upperrel->reloptkind = RELOPT_UPPER_REL;
upperrel->relids = bms_copy(relids);
/* cheap startup cost is interesting iff not all tuples to be retrieved */
upperrel->consider_startup = (root->tuple_fraction > 0);
upperrel->consider_param_startup = false;
upperrel->consider_parallel = false; /* might get changed later */
upperrel->pathlist = NIL;
upperrel->cheapest_startup_path = NULL;
upperrel->cheapest_total_path = NULL;
upperrel->cheapest_unique_path = NULL;
upperrel->cheapest_parameterized_paths = NIL;
root->upper_rels[kind] = lappend(root->upper_rels[kind], upperrel);
return upperrel;
}
/* /*
* find_childrel_appendrelinfo * find_childrel_appendrelinfo
* Get the AppendRelInfo associated with an appendrel child rel. * Get the AppendRelInfo associated with an appendrel child rel.
......
...@@ -74,13 +74,12 @@ tlist_member_ignore_relabel(Node *node, List *targetlist) ...@@ -74,13 +74,12 @@ tlist_member_ignore_relabel(Node *node, List *targetlist)
/* /*
* tlist_member_match_var * tlist_member_match_var
* Same as above, except that we match the provided Var on the basis * Same as above, except that we match the provided Var on the basis
* of varno/varattno/varlevelsup only, rather than using full equal(). * of varno/varattno/varlevelsup/vartype only, rather than full equal().
* *
* This is needed in some cases where we can't be sure of an exact typmod * This is needed in some cases where we can't be sure of an exact typmod
* match. It's probably a good idea to check the vartype anyway, but * match. For safety, though, we insist on vartype match.
* we leave it to the caller to apply any suitable sanity checks.
*/ */
TargetEntry * static TargetEntry *
tlist_member_match_var(Var *var, List *targetlist) tlist_member_match_var(Var *var, List *targetlist)
{ {
ListCell *temp; ListCell *temp;
...@@ -94,7 +93,8 @@ tlist_member_match_var(Var *var, List *targetlist) ...@@ -94,7 +93,8 @@ tlist_member_match_var(Var *var, List *targetlist)
continue; continue;
if (var->varno == tlvar->varno && if (var->varno == tlvar->varno &&
var->varattno == tlvar->varattno && var->varattno == tlvar->varattno &&
var->varlevelsup == tlvar->varlevelsup) var->varlevelsup == tlvar->varlevelsup &&
var->vartype == tlvar->vartype)
return tlentry; return tlentry;
} }
return NULL; return NULL;
...@@ -316,6 +316,34 @@ tlist_same_collations(List *tlist, List *colCollations, bool junkOK) ...@@ -316,6 +316,34 @@ tlist_same_collations(List *tlist, List *colCollations, bool junkOK)
return true; return true;
} }
/*
* apply_tlist_labeling
* Apply the TargetEntry labeling attributes of src_tlist to dest_tlist
*
* This is useful for reattaching column names etc to a plan's final output
* targetlist.
*/
void
apply_tlist_labeling(List *dest_tlist, List *src_tlist)
{
ListCell *ld,
*ls;
Assert(list_length(dest_tlist) == list_length(src_tlist));
forboth(ld, dest_tlist, ls, src_tlist)
{
TargetEntry *dest_tle = (TargetEntry *) lfirst(ld);
TargetEntry *src_tle = (TargetEntry *) lfirst(ls);
Assert(dest_tle->resno == src_tle->resno);
dest_tle->resname = src_tle->resname;
dest_tle->ressortgroupref = src_tle->ressortgroupref;
dest_tle->resorigtbl = src_tle->resorigtbl;
dest_tle->resorigcol = src_tle->resorigcol;
dest_tle->resjunk = src_tle->resjunk;
}
}
/* /*
* get_sortgroupref_tle * get_sortgroupref_tle
...@@ -506,3 +534,119 @@ grouping_is_hashable(List *groupClause) ...@@ -506,3 +534,119 @@ grouping_is_hashable(List *groupClause)
} }
return true; return true;
} }
/*****************************************************************************
* PathTarget manipulation functions
*
* PathTarget is a somewhat stripped-down version of a full targetlist; it
* omits all the TargetEntry decoration except (optionally) sortgroupref data,
* and it adds evaluation cost and output data width info.
*****************************************************************************/
/*
* make_pathtarget_from_tlist
* Construct a PathTarget equivalent to the given targetlist.
*
* This leaves the cost and width fields as zeroes. Most callers will want
* to use create_pathtarget(), so as to get those set.
*/
PathTarget *
make_pathtarget_from_tlist(List *tlist)
{
PathTarget *target = (PathTarget *) palloc0(sizeof(PathTarget));
int i;
ListCell *lc;
target->sortgrouprefs = (Index *) palloc(list_length(tlist) * sizeof(Index));
i = 0;
foreach(lc, tlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(lc);
target->exprs = lappend(target->exprs, tle->expr);
target->sortgrouprefs[i] = tle->ressortgroupref;
i++;
}
return target;
}
/*
* make_tlist_from_pathtarget
* Construct a targetlist from a PathTarget.
*/
List *
make_tlist_from_pathtarget(PathTarget *target)
{
List *tlist = NIL;
int i;
ListCell *lc;
i = 0;
foreach(lc, target->exprs)
{
Expr *expr = (Expr *) lfirst(lc);
TargetEntry *tle;
tle = makeTargetEntry(expr,
i + 1,
NULL,
false);
if (target->sortgrouprefs)
tle->ressortgroupref = target->sortgrouprefs[i];
tlist = lappend(tlist, tle);
i++;
}
return tlist;
}
/*
* apply_pathtarget_labeling_to_tlist
* Apply any sortgrouprefs in the PathTarget to matching tlist entries
*
* Here, we do not assume that the tlist entries are one-for-one with the
* PathTarget. The intended use of this function is to deal with cases
* where createplan.c has decided to use some other tlist and we have
* to identify what matches exist.
*/
void
apply_pathtarget_labeling_to_tlist(List *tlist, PathTarget *target)
{
int i;
ListCell *lc;
/* Nothing to do if PathTarget has no sortgrouprefs data */
if (target->sortgrouprefs == NULL)
return;
i = 0;
foreach(lc, target->exprs)
{
Expr *expr = (Expr *) lfirst(lc);
TargetEntry *tle;
if (target->sortgrouprefs[i])
{
/*
* For Vars, use tlist_member_match_var's weakened matching rule;
* this allows us to deal with some cases where a set-returning
* function has been inlined, so that we now have more knowledge
* about what it returns than we did when the original Var was
* created. Otherwise, use regular equal() to see if there's a
* matching TLE. (In current usage, only the Var case is actually
* needed; but it seems best to have sane behavior here for
* non-Vars too.)
*/
if (expr && IsA(expr, Var))
tle = tlist_member_match_var((Var *) expr, tlist);
else
tle = tlist_member((Node *) expr, tlist);
if (tle)
tle->ressortgroupref = target->sortgrouprefs[i];
}
i++;
}
}
...@@ -229,18 +229,33 @@ typedef enum NodeTag ...@@ -229,18 +229,33 @@ typedef enum NodeTag
T_BitmapHeapPath, T_BitmapHeapPath,
T_BitmapAndPath, T_BitmapAndPath,
T_BitmapOrPath, T_BitmapOrPath,
T_NestPath,
T_MergePath,
T_HashPath,
T_TidPath, T_TidPath,
T_SubqueryScanPath,
T_ForeignPath, T_ForeignPath,
T_CustomPath, T_CustomPath,
T_NestPath,
T_MergePath,
T_HashPath,
T_AppendPath, T_AppendPath,
T_MergeAppendPath, T_MergeAppendPath,
T_ResultPath, T_ResultPath,
T_MaterialPath, T_MaterialPath,
T_UniquePath, T_UniquePath,
T_GatherPath, T_GatherPath,
T_ProjectionPath,
T_SortPath,
T_GroupPath,
T_UpperUniquePath,
T_AggPath,
T_GroupingSetsPath,
T_MinMaxAggPath,
T_WindowAggPath,
T_SetOpPath,
T_RecursiveUnionPath,
T_LockRowsPath,
T_ModifyTablePath,
T_LimitPath,
/* these aren't subclasses of Path: */
T_EquivalenceClass, T_EquivalenceClass,
T_EquivalenceMember, T_EquivalenceMember,
T_PathKey, T_PathKey,
...@@ -653,6 +668,39 @@ typedef enum JoinType ...@@ -653,6 +668,39 @@ typedef enum JoinType
(1 << JOIN_RIGHT) | \ (1 << JOIN_RIGHT) | \
(1 << JOIN_ANTI))) != 0) (1 << JOIN_ANTI))) != 0)
/*
* AggStrategy -
* overall execution strategies for Agg plan nodes
*
* This is needed in both plannodes.h and relation.h, so put it here...
*/
typedef enum AggStrategy
{
AGG_PLAIN, /* simple agg across all input rows */
AGG_SORTED, /* grouped agg, input must be sorted */
AGG_HASHED /* grouped agg, use internal hashtable */
} AggStrategy;
/*
* SetOpCmd and SetOpStrategy -
* overall semantics and execution strategies for SetOp plan nodes
*
* This is needed in both plannodes.h and relation.h, so put it here...
*/
typedef enum SetOpCmd
{
SETOPCMD_INTERSECT,
SETOPCMD_INTERSECT_ALL,
SETOPCMD_EXCEPT,
SETOPCMD_EXCEPT_ALL
} SetOpCmd;
typedef enum SetOpStrategy
{
SETOP_SORTED, /* input must be sorted */
SETOP_HASHED /* use internal hashtable */
} SetOpStrategy;
/* /*
* OnConflictAction - * OnConflictAction -
* "ON CONFLICT" clause type of query * "ON CONFLICT" clause type of query
......
...@@ -714,23 +714,17 @@ typedef struct Group ...@@ -714,23 +714,17 @@ typedef struct Group
* we are using the Agg node to implement hash-based grouping.) * we are using the Agg node to implement hash-based grouping.)
* --------------- * ---------------
*/ */
typedef enum AggStrategy
{
AGG_PLAIN, /* simple agg across all input rows */
AGG_SORTED, /* grouped agg, input must be sorted */
AGG_HASHED /* grouped agg, use internal hashtable */
} AggStrategy;
typedef struct Agg typedef struct Agg
{ {
Plan plan; Plan plan;
AggStrategy aggstrategy; AggStrategy aggstrategy; /* basic strategy, see nodes.h */
int numCols; /* number of grouping columns */
AttrNumber *grpColIdx; /* their indexes in the target list */
bool combineStates; /* input tuples contain transition states */ bool combineStates; /* input tuples contain transition states */
bool finalizeAggs; /* should we call the finalfn on agg states? */ bool finalizeAggs; /* should we call the finalfn on agg states? */
int numCols; /* number of grouping columns */
AttrNumber *grpColIdx; /* their indexes in the target list */
Oid *grpOperators; /* equality operators to compare with */ Oid *grpOperators; /* equality operators to compare with */
long numGroups; /* estimated number of groups in input */ long numGroups; /* estimated number of groups in input */
/* Note: the planner only provides numGroups in AGG_HASHED case */
List *groupingSets; /* grouping sets to use */ List *groupingSets; /* grouping sets to use */
List *chain; /* chained Agg/Sort nodes */ List *chain; /* chained Agg/Sort nodes */
} Agg; } Agg;
...@@ -802,25 +796,11 @@ typedef struct Hash ...@@ -802,25 +796,11 @@ typedef struct Hash
* setop node * setop node
* ---------------- * ----------------
*/ */
typedef enum SetOpCmd
{
SETOPCMD_INTERSECT,
SETOPCMD_INTERSECT_ALL,
SETOPCMD_EXCEPT,
SETOPCMD_EXCEPT_ALL
} SetOpCmd;
typedef enum SetOpStrategy
{
SETOP_SORTED, /* input must be sorted */
SETOP_HASHED /* use internal hashtable */
} SetOpStrategy;
typedef struct SetOp typedef struct SetOp
{ {
Plan plan; Plan plan;
SetOpCmd cmd; /* what to do */ SetOpCmd cmd; /* what to do, see nodes.h */
SetOpStrategy strategy; /* how to do it */ SetOpStrategy strategy; /* how to do it, see nodes.h */
int numCols; /* number of columns to check for int numCols; /* number of columns to check for
* duplicate-ness */ * duplicate-ness */
AttrNumber *dupColIdx; /* their indexes in the target list */ AttrNumber *dupColIdx; /* their indexes in the target list */
......
...@@ -70,16 +70,40 @@ typedef struct AggClauseCosts ...@@ -70,16 +70,40 @@ typedef struct AggClauseCosts
* example, an indexscan might return index expressions that would otherwise * example, an indexscan might return index expressions that would otherwise
* need to be explicitly calculated. * need to be explicitly calculated.
* *
* Note that PathTarget.exprs is just a list of expressions; they do not have * exprs contains bare expressions; they do not have TargetEntry nodes on top,
* TargetEntry nodes on top, though those will appear in the finished Plan. * though those will appear in finished Plans.
*
* sortgrouprefs[] is an array of the same length as exprs, containing the
* corresponding sort/group refnos, or zeroes for expressions not referenced
* by sort/group clauses. If sortgrouprefs is NULL (which it always is in
* RelOptInfo.reltarget structs; only upper-level Paths contain this info), we
* have not identified sort/group columns in this tlist. This allows us to
* deal with sort/group refnos when needed with less expense than including
* TargetEntry nodes in the exprs list.
*/ */
typedef struct PathTarget typedef struct PathTarget
{ {
List *exprs; /* list of expressions to be computed */ List *exprs; /* list of expressions to be computed */
QualCost cost; /* cost of evaluating the above */ Index *sortgrouprefs; /* corresponding sort/group refnos, or 0 */
QualCost cost; /* cost of evaluating the expressions */
int width; /* estimated avg width of result tuples */ int width; /* estimated avg width of result tuples */
} PathTarget; } PathTarget;
/*
* This enum identifies the different types of "upper" (post-scan/join)
* relations that we might deal with during planning.
*/
typedef enum UpperRelationKind
{
UPPERREL_SETOP, /* result of UNION/INTERSECT/EXCEPT, if any */
UPPERREL_GROUP_AGG, /* result of grouping/aggregation, if any */
UPPERREL_WINDOW, /* result of window functions, if any */
UPPERREL_DISTINCT, /* result of "SELECT DISTINCT", if any */
UPPERREL_ORDERED, /* result of ORDER BY, if any */
UPPERREL_FINAL /* result of any remaining top-level actions */
/* NB: UPPERREL_FINAL must be last enum entry; it's used to size arrays */
} UpperRelationKind;
/*---------- /*----------
* PlannerGlobal * PlannerGlobal
...@@ -255,18 +279,28 @@ typedef struct PlannerInfo ...@@ -255,18 +279,28 @@ typedef struct PlannerInfo
List *placeholder_list; /* list of PlaceHolderInfos */ List *placeholder_list; /* list of PlaceHolderInfos */
List *query_pathkeys; /* desired pathkeys for query_planner(), and List *query_pathkeys; /* desired pathkeys for query_planner() */
* actual pathkeys after planning */
List *group_pathkeys; /* groupClause pathkeys, if any */ List *group_pathkeys; /* groupClause pathkeys, if any */
List *window_pathkeys; /* pathkeys of bottom window, if any */ List *window_pathkeys; /* pathkeys of bottom window, if any */
List *distinct_pathkeys; /* distinctClause pathkeys, if any */ List *distinct_pathkeys; /* distinctClause pathkeys, if any */
List *sort_pathkeys; /* sortClause pathkeys, if any */ List *sort_pathkeys; /* sortClause pathkeys, if any */
List *minmax_aggs; /* List of MinMaxAggInfos */
List *initial_rels; /* RelOptInfos we are now trying to join */ List *initial_rels; /* RelOptInfos we are now trying to join */
/* Use fetch_upper_rel() to get any particular upper rel */
List *upper_rels[UPPERREL_FINAL + 1]; /* upper-rel RelOptInfos */
/*
* grouping_planner passes back its final processed targetlist here, for
* use in relabeling the topmost tlist of the finished Plan.
*/
List *processed_tlist;
/* Fields filled during create_plan() for use in setrefs.c */
AttrNumber *grouping_map; /* for GroupingFunc fixup */
List *minmax_aggs; /* List of MinMaxAggInfos */
MemoryContext planner_cxt; /* context holding PlannerInfo */ MemoryContext planner_cxt; /* context holding PlannerInfo */
double total_table_pages; /* # of pages in all tables of query */ double total_table_pages; /* # of pages in all tables of query */
...@@ -286,7 +320,7 @@ typedef struct PlannerInfo ...@@ -286,7 +320,7 @@ typedef struct PlannerInfo
/* These fields are used only when hasRecursion is true: */ /* These fields are used only when hasRecursion is true: */
int wt_param_id; /* PARAM_EXEC ID for the work table */ int wt_param_id; /* PARAM_EXEC ID for the work table */
struct Plan *non_recursive_plan; /* plan for non-recursive term */ struct Path *non_recursive_path; /* a path for non-recursive term */
/* These fields are workspace for createplan.c */ /* These fields are workspace for createplan.c */
Relids curOuterRels; /* outer rels above current node */ Relids curOuterRels; /* outer rels above current node */
...@@ -294,9 +328,6 @@ typedef struct PlannerInfo ...@@ -294,9 +328,6 @@ typedef struct PlannerInfo
/* optional private data for join_search_hook, e.g., GEQO */ /* optional private data for join_search_hook, e.g., GEQO */
void *join_search_private; void *join_search_private;
/* for GroupingFunc fixup in setrefs */
AttrNumber *grouping_map;
} PlannerInfo; } PlannerInfo;
...@@ -328,10 +359,7 @@ typedef struct PlannerInfo ...@@ -328,10 +359,7 @@ typedef struct PlannerInfo
* *
* We also have "other rels", which are like base rels in that they refer to * We also have "other rels", which are like base rels in that they refer to
* single RT indexes; but they are not part of the join tree, and are given * single RT indexes; but they are not part of the join tree, and are given
* a different RelOptKind to identify them. Lastly, there is a RelOptKind * a different RelOptKind to identify them.
* for "dead" relations, which are base rels that we have proven we don't
* need to join after all.
*
* Currently the only kind of otherrels are those made for member relations * Currently the only kind of otherrels are those made for member relations
* of an "append relation", that is an inheritance set or UNION ALL subquery. * of an "append relation", that is an inheritance set or UNION ALL subquery.
* An append relation has a parent RTE that is a base rel, which represents * An append relation has a parent RTE that is a base rel, which represents
...@@ -346,6 +374,14 @@ typedef struct PlannerInfo ...@@ -346,6 +374,14 @@ typedef struct PlannerInfo
* handling join alias Vars. Currently this is not needed because all join * handling join alias Vars. Currently this is not needed because all join
* alias Vars are expanded to non-aliased form during preprocess_expression. * alias Vars are expanded to non-aliased form during preprocess_expression.
* *
* There is also a RelOptKind for "upper" relations, which are RelOptInfos
* that describe post-scan/join processing steps, such as aggregation.
* Many of the fields in these RelOptInfos are meaningless, but their Path
* fields always hold Paths showing ways to do that processing step.
*
* Lastly, there is a RelOptKind for "dead" relations, which are base rels
* that we have proven we don't need to join after all.
*
* Parts of this data structure are specific to various scan and join * Parts of this data structure are specific to various scan and join
* mechanisms. It didn't seem worth creating new node types for them. * mechanisms. It didn't seem worth creating new node types for them.
* *
...@@ -401,11 +437,10 @@ typedef struct PlannerInfo ...@@ -401,11 +437,10 @@ typedef struct PlannerInfo
* pages - number of disk pages in relation (zero if not a table) * pages - number of disk pages in relation (zero if not a table)
* tuples - number of tuples in relation (not considering restrictions) * tuples - number of tuples in relation (not considering restrictions)
* allvisfrac - fraction of disk pages that are marked all-visible * allvisfrac - fraction of disk pages that are marked all-visible
* subplan - plan for subquery (NULL if it's not a subquery)
* subroot - PlannerInfo for subquery (NULL if it's not a subquery) * subroot - PlannerInfo for subquery (NULL if it's not a subquery)
* subplan_params - list of PlannerParamItems to be passed to subquery * subplan_params - list of PlannerParamItems to be passed to subquery
* *
* Note: for a subquery, tuples, subplan, subroot are not set immediately * Note: for a subquery, tuples and subroot are not set immediately
* upon creation of the RelOptInfo object; they are filled in when * upon creation of the RelOptInfo object; they are filled in when
* set_subquery_pathlist processes the object. * set_subquery_pathlist processes the object.
* *
...@@ -455,6 +490,7 @@ typedef enum RelOptKind ...@@ -455,6 +490,7 @@ typedef enum RelOptKind
RELOPT_BASEREL, RELOPT_BASEREL,
RELOPT_JOINREL, RELOPT_JOINREL,
RELOPT_OTHER_MEMBER_REL, RELOPT_OTHER_MEMBER_REL,
RELOPT_UPPER_REL,
RELOPT_DEADREL RELOPT_DEADREL
} RelOptKind; } RelOptKind;
...@@ -506,8 +542,6 @@ typedef struct RelOptInfo ...@@ -506,8 +542,6 @@ typedef struct RelOptInfo
BlockNumber pages; /* size estimates derived from pg_class */ BlockNumber pages; /* size estimates derived from pg_class */
double tuples; double tuples;
double allvisfrac; double allvisfrac;
/* use "struct Plan" to avoid including plannodes.h here */
struct Plan *subplan; /* if subquery */
PlannerInfo *subroot; /* if subquery */ PlannerInfo *subroot; /* if subquery */
List *subplan_params; /* if subquery */ List *subplan_params; /* if subquery */
...@@ -938,6 +972,20 @@ typedef struct TidPath ...@@ -938,6 +972,20 @@ typedef struct TidPath
List *tidquals; /* qual(s) involving CTID = something */ List *tidquals; /* qual(s) involving CTID = something */
} TidPath; } TidPath;
/*
* SubqueryScanPath represents a scan of an unflattened subquery-in-FROM
*
* Note that the subpath comes from a different planning domain; for example
* RTE indexes within it mean something different from those known to the
* SubqueryScanPath. path.parent->subroot is the planning context needed to
* interpret the subpath.
*/
typedef struct SubqueryScanPath
{
Path path;
Path *subpath; /* path representing subquery execution */
} SubqueryScanPath;
/* /*
* ForeignPath represents a potential scan of a foreign table * ForeignPath represents a potential scan of a foreign table
* *
...@@ -1062,14 +1110,13 @@ typedef struct MaterialPath ...@@ -1062,14 +1110,13 @@ typedef struct MaterialPath
* UniquePath represents elimination of distinct rows from the output of * UniquePath represents elimination of distinct rows from the output of
* its subpath. * its subpath.
* *
* This is unlike the other Path nodes in that it can actually generate * This can represent significantly different plans: either hash-based or
* different plans: either hash-based or sort-based implementation, or a * sort-based implementation, or a no-op if the input path can be proven
* no-op if the input path can be proven distinct already. The decision * distinct already. The decision is sufficiently localized that it's not
* is sufficiently localized that it's not worth having separate Path node * worth having separate Path node types. (Note: in the no-op case, we could
* types. (Note: in the no-op case, we could eliminate the UniquePath node * eliminate the UniquePath node entirely and just return the subpath; but
* entirely and just return the subpath; but it's convenient to have a * it's convenient to have a UniquePath in the path tree to signal upper-level
* UniquePath in the path tree to signal upper-level routines that the input * routines that the input is known distinct.)
* is known distinct.)
*/ */
typedef enum typedef enum
{ {
...@@ -1180,6 +1227,195 @@ typedef struct HashPath ...@@ -1180,6 +1227,195 @@ typedef struct HashPath
int num_batches; /* number of batches expected */ int num_batches; /* number of batches expected */
} HashPath; } HashPath;
/*
* ProjectionPath represents a projection (that is, targetlist computation)
*
* This path node represents using a Result plan node to do a projection.
* It's only needed atop a node that doesn't support projection (such as
* Sort); otherwise we just jam the new desired PathTarget into the lower
* path node, and adjust that node's estimated cost accordingly.
*/
typedef struct ProjectionPath
{
Path path;
Path *subpath; /* path representing input source */
} ProjectionPath;
/*
* SortPath represents an explicit sort step
*
* The sort keys are, by definition, the same as path.pathkeys.
*
* Note: the Sort plan node cannot project, so path.pathtarget must be the
* same as the input's pathtarget.
*/
typedef struct SortPath
{
Path path;
Path *subpath; /* path representing input source */
} SortPath;
/*
* GroupPath represents grouping (of presorted input)
*
* groupClause represents the columns to be grouped on; the input path
* must be at least that well sorted.
*
* We can also apply a qual to the grouped rows (equivalent of HAVING)
*/
typedef struct GroupPath
{
Path path;
Path *subpath; /* path representing input source */
List *groupClause; /* a list of SortGroupClause's */
List *qual; /* quals (HAVING quals), if any */
} GroupPath;
/*
* UpperUniquePath represents adjacent-duplicate removal (in presorted input)
*
* The columns to be compared are the first numkeys columns of the path's
* pathkeys. The input is presumed already sorted that way.
*/
typedef struct UpperUniquePath
{
Path path;
Path *subpath; /* path representing input source */
int numkeys; /* number of pathkey columns to compare */
} UpperUniquePath;
/*
* AggPath represents generic computation of aggregate functions
*
* This may involve plain grouping (but not grouping sets), using either
* sorted or hashed grouping; for the AGG_SORTED case, the input must be
* appropriately presorted.
*/
typedef struct AggPath
{
Path path;
Path *subpath; /* path representing input source */
AggStrategy aggstrategy; /* basic strategy, see nodes.h */
double numGroups; /* estimated number of groups in input */
List *groupClause; /* a list of SortGroupClause's */
List *qual; /* quals (HAVING quals), if any */
} AggPath;
/*
* GroupingSetsPath represents a GROUPING SETS aggregation
*
* Currently we only support this in sorted not hashed form, so the input
* must always be appropriately presorted.
*/
typedef struct GroupingSetsPath
{
Path path;
Path *subpath; /* path representing input source */
AttrNumber *groupColIdx; /* grouping col indexes */
List *rollup_groupclauses; /* list of lists of SortGroupClause's */
List *rollup_lists; /* parallel list of lists of grouping sets */
List *qual; /* quals (HAVING quals), if any */
} GroupingSetsPath;
/*
* MinMaxAggPath represents computation of MIN/MAX aggregates from indexes
*/
typedef struct MinMaxAggPath
{
Path path;
List *mmaggregates; /* list of MinMaxAggInfo */
List *quals; /* HAVING quals, if any */
} MinMaxAggPath;
/*
* WindowAggPath represents generic computation of window functions
*
* Note: winpathkeys is separate from path.pathkeys because the actual sort
* order might be an extension of winpathkeys; but createplan.c needs to
* know exactly how many pathkeys match the window clause.
*/
typedef struct WindowAggPath
{
Path path;
Path *subpath; /* path representing input source */
WindowClause *winclause; /* WindowClause we'll be using */
List *winpathkeys; /* PathKeys for PARTITION keys + ORDER keys */
} WindowAggPath;
/*
* SetOpPath represents a set-operation, that is INTERSECT or EXCEPT
*/
typedef struct SetOpPath
{
Path path;
Path *subpath; /* path representing input source */
SetOpCmd cmd; /* what to do, see nodes.h */
SetOpStrategy strategy; /* how to do it, see nodes.h */
List *distinctList; /* SortGroupClauses identifying target cols */
AttrNumber flagColIdx; /* where is the flag column, if any */
int firstFlag; /* flag value for first input relation */
double numGroups; /* estimated number of groups in input */
} SetOpPath;
/*
* RecursiveUnionPath represents a recursive UNION node
*/
typedef struct RecursiveUnionPath
{
Path path;
Path *leftpath; /* paths representing input sources */
Path *rightpath;
List *distinctList; /* SortGroupClauses identifying target cols */
int wtParam; /* ID of Param representing work table */
double numGroups; /* estimated number of groups in input */
} RecursiveUnionPath;
/*
* LockRowsPath represents acquiring row locks for SELECT FOR UPDATE/SHARE
*/
typedef struct LockRowsPath
{
Path path;
Path *subpath; /* path representing input source */
List *rowMarks; /* a list of PlanRowMark's */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
} LockRowsPath;
/*
* ModifyTablePath represents performing INSERT/UPDATE/DELETE modifications
*
* We represent most things that will be in the ModifyTable plan node
* literally, except we have child Path(s) not Plan(s). But analysis of the
* OnConflictExpr is deferred to createplan.c, as is collection of FDW data.
*/
typedef struct ModifyTablePath
{
Path path;
CmdType operation; /* INSERT, UPDATE, or DELETE */
bool canSetTag; /* do we set the command tag/es_processed? */
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
List *resultRelations; /* integer list of RT indexes */
List *subpaths; /* Path(s) producing source data */
List *subroots; /* per-target-table PlannerInfos */
List *withCheckOptionLists; /* per-target-table WCO lists */
List *returningLists; /* per-target-table RETURNING tlists */
List *rowMarks; /* PlanRowMarks (non-locking only) */
OnConflictExpr *onconflict; /* ON CONFLICT clause, or NULL */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
} ModifyTablePath;
/*
* LimitPath represents applying LIMIT/OFFSET restrictions
*/
typedef struct LimitPath
{
Path path;
Path *subpath; /* path representing input source */
Node *limitOffset; /* OFFSET parameter, or NULL if none */
Node *limitCount; /* COUNT parameter, or NULL if none */
} LimitPath;
/* /*
* Restriction clause info. * Restriction clause info.
* *
...@@ -1615,8 +1851,9 @@ typedef struct PlaceHolderInfo ...@@ -1615,8 +1851,9 @@ typedef struct PlaceHolderInfo
} PlaceHolderInfo; } PlaceHolderInfo;
/* /*
* For each potentially index-optimizable MIN/MAX aggregate function, * This struct describes one potentially index-optimizable MIN/MAX aggregate
* root->minmax_aggs stores a MinMaxAggInfo describing it. * 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.
*/ */
typedef struct MinMaxAggInfo typedef struct MinMaxAggInfo
{ {
......
...@@ -85,7 +85,7 @@ extern void cost_bitmap_or_node(BitmapOrPath *path, PlannerInfo *root); ...@@ -85,7 +85,7 @@ extern void cost_bitmap_or_node(BitmapOrPath *path, PlannerInfo *root);
extern void cost_bitmap_tree_node(Path *path, Cost *cost, Selectivity *selec); extern void cost_bitmap_tree_node(Path *path, Cost *cost, Selectivity *selec);
extern void cost_tidscan(Path *path, PlannerInfo *root, extern void cost_tidscan(Path *path, PlannerInfo *root,
RelOptInfo *baserel, List *tidquals, ParamPathInfo *param_info); RelOptInfo *baserel, List *tidquals, ParamPathInfo *param_info);
extern void cost_subqueryscan(Path *path, PlannerInfo *root, extern void cost_subqueryscan(SubqueryScanPath *path, PlannerInfo *root,
RelOptInfo *baserel, ParamPathInfo *param_info); RelOptInfo *baserel, ParamPathInfo *param_info);
extern void cost_functionscan(Path *path, PlannerInfo *root, extern void cost_functionscan(Path *path, PlannerInfo *root,
RelOptInfo *baserel, ParamPathInfo *param_info); RelOptInfo *baserel, ParamPathInfo *param_info);
...@@ -93,7 +93,7 @@ extern void cost_valuesscan(Path *path, PlannerInfo *root, ...@@ -93,7 +93,7 @@ extern void cost_valuesscan(Path *path, PlannerInfo *root,
RelOptInfo *baserel, ParamPathInfo *param_info); RelOptInfo *baserel, ParamPathInfo *param_info);
extern void cost_ctescan(Path *path, PlannerInfo *root, extern void cost_ctescan(Path *path, PlannerInfo *root,
RelOptInfo *baserel, ParamPathInfo *param_info); RelOptInfo *baserel, ParamPathInfo *param_info);
extern void cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm); extern void cost_recursive_union(Path *runion, Path *nrterm, Path *rterm);
extern void cost_sort(Path *path, PlannerInfo *root, extern void cost_sort(Path *path, PlannerInfo *root,
List *pathkeys, Cost input_cost, double tuples, int width, List *pathkeys, Cost input_cost, double tuples, int width,
Cost comparison_cost, int sort_mem, Cost comparison_cost, int sort_mem,
...@@ -180,8 +180,9 @@ extern void set_subquery_size_estimates(PlannerInfo *root, RelOptInfo *rel); ...@@ -180,8 +180,9 @@ extern void set_subquery_size_estimates(PlannerInfo *root, RelOptInfo *rel);
extern void set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel); extern void set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel);
extern void set_values_size_estimates(PlannerInfo *root, RelOptInfo *rel); extern void set_values_size_estimates(PlannerInfo *root, RelOptInfo *rel);
extern void set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, extern void set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel,
Plan *cteplan); double cte_rows);
extern void set_foreign_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);
/* /*
* prototypes for clausesel.c * prototypes for clausesel.c
......
...@@ -68,13 +68,15 @@ extern MergeAppendPath *create_merge_append_path(PlannerInfo *root, ...@@ -68,13 +68,15 @@ extern MergeAppendPath *create_merge_append_path(PlannerInfo *root,
List *subpaths, List *subpaths,
List *pathkeys, List *pathkeys,
Relids required_outer); Relids required_outer);
extern ResultPath *create_result_path(RelOptInfo *rel, List *quals); extern ResultPath *create_result_path(RelOptInfo *rel,
PathTarget *target, List *quals);
extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath); extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath);
extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel, extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath, SpecialJoinInfo *sjinfo); Path *subpath, SpecialJoinInfo *sjinfo);
extern GatherPath *create_gather_path(PlannerInfo *root, extern GatherPath *create_gather_path(PlannerInfo *root,
RelOptInfo *rel, Path *subpath, Relids required_outer); RelOptInfo *rel, Path *subpath, Relids required_outer);
extern Path *create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel, extern SubqueryScanPath *create_subqueryscan_path(PlannerInfo *root,
RelOptInfo *rel, Path *subpath,
List *pathkeys, Relids required_outer); List *pathkeys, Relids required_outer);
extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel, extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
List *pathkeys, Relids required_outer); List *pathkeys, Relids required_outer);
...@@ -132,6 +134,96 @@ extern HashPath *create_hashjoin_path(PlannerInfo *root, ...@@ -132,6 +134,96 @@ extern HashPath *create_hashjoin_path(PlannerInfo *root,
Relids required_outer, Relids required_outer,
List *hashclauses); List *hashclauses);
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,
PathTarget *target);
extern SortPath *create_sort_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
List *pathkeys,
double limit_tuples);
extern GroupPath *create_group_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
PathTarget *target,
List *groupClause,
List *qual,
double numGroups);
extern UpperUniquePath *create_upper_unique_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
int numCols,
double numGroups);
extern AggPath *create_agg_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
PathTarget *target,
AggStrategy aggstrategy,
List *groupClause,
List *qual,
const AggClauseCosts *aggcosts,
double numGroups);
extern GroupingSetsPath *create_groupingsets_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
PathTarget *target,
List *having_qual,
AttrNumber *groupColIdx,
List *rollup_lists,
List *rollup_groupclauses,
const AggClauseCosts *agg_costs,
double numGroups);
extern MinMaxAggPath *create_minmaxagg_path(PlannerInfo *root,
RelOptInfo *rel,
PathTarget *target,
List *mmaggregates,
List *quals);
extern WindowAggPath *create_windowagg_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
PathTarget *target,
List *windowFuncs,
WindowClause *winclause,
List *winpathkeys);
extern SetOpPath *create_setop_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
SetOpCmd cmd,
SetOpStrategy strategy,
List *distinctList,
AttrNumber flagColIdx,
int firstFlag,
double numGroups,
double outputRows);
extern RecursiveUnionPath *create_recursiveunion_path(PlannerInfo *root,
RelOptInfo *rel,
Path *leftpath,
Path *rightpath,
PathTarget *target,
List *distinctList,
int wtParam,
double numGroups);
extern LockRowsPath *create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath, List *rowMarks, int epqParam);
extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
RelOptInfo *rel,
CmdType operation, bool canSetTag,
Index nominalRelation,
List *resultRelations, List *subpaths,
List *subroots,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict,
int epqParam);
extern LimitPath *create_limit_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath,
Node *limitOffset, Node *limitCount,
int64 offset_est, int64 count_est);
extern Path *reparameterize_path(PlannerInfo *root, Path *path, extern Path *reparameterize_path(PlannerInfo *root, Path *path,
Relids required_outer, Relids required_outer,
double loop_count); double loop_count);
...@@ -155,6 +247,8 @@ extern Relids min_join_parameterization(PlannerInfo *root, ...@@ -155,6 +247,8 @@ extern Relids min_join_parameterization(PlannerInfo *root,
RelOptInfo *outer_rel, RelOptInfo *outer_rel,
RelOptInfo *inner_rel); RelOptInfo *inner_rel);
extern RelOptInfo *build_empty_join_rel(PlannerInfo *root); extern RelOptInfo *build_empty_join_rel(PlannerInfo *root);
extern RelOptInfo *fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind,
Relids relids);
extern AppendRelInfo *find_childrel_appendrelinfo(PlannerInfo *root, extern AppendRelInfo *find_childrel_appendrelinfo(PlannerInfo *root,
RelOptInfo *rel); RelOptInfo *rel);
extern RelOptInfo *find_childrel_top_parent(PlannerInfo *root, RelOptInfo *rel); extern RelOptInfo *find_childrel_top_parent(PlannerInfo *root, RelOptInfo *rel);
......
...@@ -47,6 +47,7 @@ extern PGDLLIMPORT join_search_hook_type join_search_hook; ...@@ -47,6 +47,7 @@ extern PGDLLIMPORT join_search_hook_type join_search_hook;
extern RelOptInfo *make_one_rel(PlannerInfo *root, List *joinlist); extern RelOptInfo *make_one_rel(PlannerInfo *root, List *joinlist);
extern void set_dummy_rel_pathlist(RelOptInfo *rel);
extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed, extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,
List *initial_rels); List *initial_rels);
...@@ -137,10 +138,6 @@ extern void add_child_rel_equivalences(PlannerInfo *root, ...@@ -137,10 +138,6 @@ extern void add_child_rel_equivalences(PlannerInfo *root,
AppendRelInfo *appinfo, AppendRelInfo *appinfo,
RelOptInfo *parent_rel, RelOptInfo *parent_rel,
RelOptInfo *child_rel); RelOptInfo *child_rel);
extern void mutate_eclass_expressions(PlannerInfo *root,
Node *(*mutator) (),
void *context,
bool include_child_exprs);
extern List *generate_implied_equalities_for_column(PlannerInfo *root, extern List *generate_implied_equalities_for_column(PlannerInfo *root,
RelOptInfo *rel, RelOptInfo *rel,
ec_matches_callback_type callback, ec_matches_callback_type callback,
...@@ -182,7 +179,8 @@ extern List *build_expression_pathkey(PlannerInfo *root, Expr *expr, ...@@ -182,7 +179,8 @@ extern List *build_expression_pathkey(PlannerInfo *root, Expr *expr,
Relids nullable_relids, Oid opno, Relids nullable_relids, Oid opno,
Relids rel, bool create_it); Relids rel, bool create_it);
extern List *convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel, extern List *convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
List *subquery_pathkeys); List *subquery_pathkeys,
List *subquery_tlist);
extern List *build_join_pathkeys(PlannerInfo *root, extern List *build_join_pathkeys(PlannerInfo *root,
RelOptInfo *joinrel, RelOptInfo *joinrel,
JoinType jointype, JoinType jointype,
......
...@@ -43,60 +43,17 @@ extern RelOptInfo *query_planner(PlannerInfo *root, List *tlist, ...@@ -43,60 +43,17 @@ extern RelOptInfo *query_planner(PlannerInfo *root, List *tlist,
* prototypes for plan/planagg.c * prototypes for plan/planagg.c
*/ */
extern void preprocess_minmax_aggregates(PlannerInfo *root, List *tlist); extern void preprocess_minmax_aggregates(PlannerInfo *root, List *tlist);
extern Plan *optimize_minmax_aggregates(PlannerInfo *root, List *tlist,
const AggClauseCosts *aggcosts, Path *best_path);
/* /*
* prototypes for plan/createplan.c * prototypes for plan/createplan.c
*/ */
extern Plan *create_plan(PlannerInfo *root, Path *best_path); extern Plan *create_plan(PlannerInfo *root, Path *best_path);
extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual,
Index scanrelid, Plan *subplan);
extern ForeignScan *make_foreignscan(List *qptlist, List *qpqual, extern ForeignScan *make_foreignscan(List *qptlist, List *qpqual,
Index scanrelid, List *fdw_exprs, List *fdw_private, Index scanrelid, List *fdw_exprs, List *fdw_private,
List *fdw_scan_tlist, List *fdw_recheck_quals, List *fdw_scan_tlist, List *fdw_recheck_quals,
Plan *outer_plan); Plan *outer_plan);
extern Append *make_append(List *appendplans, List *tlist);
extern RecursiveUnion *make_recursive_union(List *tlist,
Plan *lefttree, Plan *righttree, int wtParam,
List *distinctList, long numGroups);
extern Sort *make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree,
List *pathkeys, double limit_tuples);
extern Sort *make_sort_from_sortclauses(PlannerInfo *root, List *sortcls,
Plan *lefttree);
extern Sort *make_sort_from_groupcols(PlannerInfo *root, List *groupcls,
AttrNumber *grpColIdx, Plan *lefttree);
extern Agg *make_agg(PlannerInfo *root, List *tlist, List *qual,
AggStrategy aggstrategy, const AggClauseCosts *aggcosts,
int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
List *groupingSets, long numGroups, bool combineStates,
bool finalizeAggs, Plan *lefttree);
extern WindowAgg *make_windowagg(PlannerInfo *root, List *tlist,
List *windowFuncs, Index winref,
int partNumCols, AttrNumber *partColIdx, Oid *partOperators,
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators,
int frameOptions, Node *startOffset, Node *endOffset,
Plan *lefttree);
extern Group *make_group(PlannerInfo *root, List *tlist, List *qual,
int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators,
double numGroups,
Plan *lefttree);
extern Plan *materialize_finished_plan(Plan *subplan); extern Plan *materialize_finished_plan(Plan *subplan);
extern Unique *make_unique(Plan *lefttree, List *distinctList); extern bool is_projection_capable_path(Path *path);
extern LockRows *make_lockrows(Plan *lefttree, List *rowMarks, int epqParam);
extern Limit *make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount,
int64 offset_est, int64 count_est);
extern SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
List *distinctList, AttrNumber flagColIdx, int firstFlag,
long numGroups, double outputRows);
extern Result *make_result(PlannerInfo *root, List *tlist,
Node *resconstantqual, Plan *subplan);
extern ModifyTable *make_modifytable(PlannerInfo *root,
CmdType operation, bool canSetTag,
Index nominalRelation,
List *resultRelations, List *subplans,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict, int epqParam);
extern bool is_projection_capable_plan(Plan *plan); extern bool is_projection_capable_plan(Plan *plan);
/* /*
......
...@@ -30,19 +30,18 @@ extern PlannedStmt *planner(Query *parse, int cursorOptions, ...@@ -30,19 +30,18 @@ extern PlannedStmt *planner(Query *parse, int cursorOptions,
extern PlannedStmt *standard_planner(Query *parse, int cursorOptions, extern PlannedStmt *standard_planner(Query *parse, int cursorOptions,
ParamListInfo boundParams); ParamListInfo boundParams);
extern Plan *subquery_planner(PlannerGlobal *glob, Query *parse, extern PlannerInfo *subquery_planner(PlannerGlobal *glob, Query *parse,
PlannerInfo *parent_root, PlannerInfo *parent_root,
bool hasRecursion, double tuple_fraction, bool hasRecursion, double tuple_fraction);
PlannerInfo **subroot);
extern void add_tlist_costs_to_plan(PlannerInfo *root, Plan *plan,
List *tlist);
extern bool is_dummy_plan(Plan *plan); extern bool is_dummy_plan(Plan *plan);
extern RowMarkType select_rowmark_type(RangeTblEntry *rte, extern RowMarkType select_rowmark_type(RangeTblEntry *rte,
LockClauseStrength strength); LockClauseStrength strength);
extern Path *get_cheapest_fractional_path(RelOptInfo *rel,
double tuple_fraction);
extern Expr *expression_planner(Expr *expr); extern Expr *expression_planner(Expr *expr);
extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr); extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr);
......
...@@ -53,8 +53,7 @@ extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); ...@@ -53,8 +53,7 @@ extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
/* /*
* prototypes for prepunion.c * prototypes for prepunion.c
*/ */
extern Plan *plan_set_operations(PlannerInfo *root, double tuple_fraction, extern RelOptInfo *plan_set_operations(PlannerInfo *root);
List **sortClauses);
extern void expand_inherited_tables(PlannerInfo *root); extern void expand_inherited_tables(PlannerInfo *root);
......
...@@ -26,11 +26,15 @@ extern JoinExpr *convert_EXISTS_sublink_to_join(PlannerInfo *root, ...@@ -26,11 +26,15 @@ extern JoinExpr *convert_EXISTS_sublink_to_join(PlannerInfo *root,
extern Node *SS_replace_correlation_vars(PlannerInfo *root, Node *expr); extern Node *SS_replace_correlation_vars(PlannerInfo *root, Node *expr);
extern Node *SS_process_sublinks(PlannerInfo *root, Node *expr, bool isQual); extern Node *SS_process_sublinks(PlannerInfo *root, Node *expr, bool isQual);
extern void SS_identify_outer_params(PlannerInfo *root); extern void SS_identify_outer_params(PlannerInfo *root);
extern void SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel);
extern void SS_attach_initplans(PlannerInfo *root, Plan *plan); extern void SS_attach_initplans(PlannerInfo *root, Plan *plan);
extern void SS_finalize_plan(PlannerInfo *root, Plan *plan); extern void SS_finalize_plan(PlannerInfo *root, Plan *plan);
extern Param *SS_make_initplan_from_plan(PlannerInfo *root, extern Param *SS_make_initplan_output_param(PlannerInfo *root,
Oid resulttype, int32 resulttypmod,
Oid resultcollation);
extern void SS_make_initplan_from_plan(PlannerInfo *root,
PlannerInfo *subroot, Plan *plan, PlannerInfo *subroot, Plan *plan,
Oid resulttype, int32 resulttypmod, Oid resultcollation); Param *prm);
extern Param *assign_nestloop_param_var(PlannerInfo *root, Var *var); extern Param *assign_nestloop_param_var(PlannerInfo *root, Var *var);
extern Param *assign_nestloop_param_placeholdervar(PlannerInfo *root, extern Param *assign_nestloop_param_placeholdervar(PlannerInfo *root,
PlaceHolderVar *phv); PlaceHolderVar *phv);
......
...@@ -19,7 +19,6 @@ ...@@ -19,7 +19,6 @@
extern TargetEntry *tlist_member(Node *node, List *targetlist); extern TargetEntry *tlist_member(Node *node, List *targetlist);
extern TargetEntry *tlist_member_ignore_relabel(Node *node, List *targetlist); extern TargetEntry *tlist_member_ignore_relabel(Node *node, List *targetlist);
extern TargetEntry *tlist_member_match_var(Var *var, List *targetlist);
extern List *flatten_tlist(List *tlist, PVCAggregateBehavior aggbehavior, extern List *flatten_tlist(List *tlist, PVCAggregateBehavior aggbehavior,
PVCPlaceHolderBehavior phbehavior); PVCPlaceHolderBehavior phbehavior);
...@@ -34,6 +33,8 @@ extern bool tlist_same_exprs(List *tlist1, List *tlist2); ...@@ -34,6 +33,8 @@ extern bool tlist_same_exprs(List *tlist1, List *tlist2);
extern bool tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK); extern bool tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK);
extern bool tlist_same_collations(List *tlist, List *colCollations, bool junkOK); extern bool tlist_same_collations(List *tlist, List *colCollations, bool junkOK);
extern void apply_tlist_labeling(List *dest_tlist, List *src_tlist);
extern TargetEntry *get_sortgroupref_tle(Index sortref, extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList); List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause, extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
...@@ -51,4 +52,12 @@ extern AttrNumber *extract_grouping_cols(List *groupClause, List *tlist); ...@@ -51,4 +52,12 @@ extern AttrNumber *extract_grouping_cols(List *groupClause, List *tlist);
extern bool grouping_is_sortable(List *groupClause); extern bool grouping_is_sortable(List *groupClause);
extern bool grouping_is_hashable(List *groupClause); extern bool grouping_is_hashable(List *groupClause);
extern PathTarget *make_pathtarget_from_tlist(List *tlist);
extern List *make_tlist_from_pathtarget(PathTarget *target);
extern void apply_pathtarget_labeling_to_tlist(List *tlist, PathTarget *target);
/* Convenience macro to get a PathTarget with valid cost/width fields */
#define create_pathtarget(root, tlist) \
set_pathtarget_cost_width(root, make_pathtarget_from_tlist(tlist))
#endif /* TLIST_H */ #endif /* TLIST_H */
...@@ -806,8 +806,7 @@ explain (costs off) ...@@ -806,8 +806,7 @@ explain (costs off)
select distinct min(f1), max(f1) from minmaxtest; select distinct min(f1), max(f1) from minmaxtest;
QUERY PLAN QUERY PLAN
---------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------
HashAggregate Unique
Group Key: $0, $1
InitPlan 1 (returns $0) InitPlan 1 (returns $0)
-> Limit -> Limit
-> Merge Append -> Merge Append
...@@ -832,8 +831,10 @@ explain (costs off) ...@@ -832,8 +831,10 @@ explain (costs off)
Index Cond: (f1 IS NOT NULL) Index Cond: (f1 IS NOT NULL)
-> Index Only Scan Backward using minmaxtest3i on minmaxtest3 minmaxtest3_1 -> Index Only Scan Backward using minmaxtest3i on minmaxtest3 minmaxtest3_1
Index Cond: (f1 IS NOT NULL) Index Cond: (f1 IS NOT NULL)
-> Result -> Sort
(27 rows) Sort Key: ($0), ($1)
-> Result
(28 rows)
select distinct min(f1), max(f1) from minmaxtest; select distinct min(f1), max(f1) from minmaxtest;
min | max min | max
......
...@@ -3949,39 +3949,34 @@ select d.* from d left join (select distinct * from b) s ...@@ -3949,39 +3949,34 @@ select d.* from d left join (select distinct * from b) s
explain (costs off) explain (costs off)
select d.* from d left join (select * from b group by b.id, b.c_id) s select d.* from d left join (select * from b group by b.id, b.c_id) s
on d.a = s.id; on d.a = s.id;
QUERY PLAN QUERY PLAN
--------------------------------------- ------------------------------------------
Merge Left Join Merge Right Join
Merge Cond: (d.a = s.id) Merge Cond: (b.id = d.a)
-> Group
Group Key: b.id
-> Index Scan using b_pkey on b
-> Sort -> Sort
Sort Key: d.a Sort Key: d.a
-> Seq Scan on d -> Seq Scan on d
-> Sort (8 rows)
Sort Key: s.id
-> Subquery Scan on s
-> HashAggregate
Group Key: b.id
-> Seq Scan on b
(11 rows)
-- similarly, but keying off a DISTINCT clause -- similarly, but keying off a DISTINCT clause
explain (costs off) explain (costs off)
select d.* from d left join (select distinct * from b) s select d.* from d left join (select distinct * from b) s
on d.a = s.id; on d.a = s.id;
QUERY PLAN QUERY PLAN
--------------------------------------------- --------------------------------------
Merge Left Join Merge Right Join
Merge Cond: (d.a = s.id) Merge Cond: (b.id = d.a)
-> Unique
-> Sort
Sort Key: b.id, b.c_id
-> Seq Scan on b
-> Sort -> Sort
Sort Key: d.a Sort Key: d.a
-> Seq Scan on d -> Seq Scan on d
-> Sort (9 rows)
Sort Key: s.id
-> Subquery Scan on s
-> HashAggregate
Group Key: b.id, b.c_id
-> Seq Scan on b
(11 rows)
-- check join removal works when uniqueness of the join condition is enforced -- check join removal works when uniqueness of the join condition is enforced
-- by a UNION -- by a UNION
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment