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.
This commit is contained in:
Tom Lane 2016-03-07 15:58:22 -05:00
parent b642e50aea
commit 3fc6e2d7f5
35 changed files with 5591 additions and 3126 deletions

View File

@ -1316,6 +1316,40 @@ GetForeignServerByName(const char *name, bool missing_ok);
(<literal>extra-&gt;restrictlist</>).
</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>
When planning an <command>UPDATE</> or <command>DELETE</>,
<function>PlanForeignModify</> can look up the <structname>RelOptInfo</>

View File

@ -407,17 +407,20 @@ ExecSupportsMarkRestore(Path *pathnode)
case T_Result:
/*
* Although Result supports mark/restore if it has a child plan
* that does, we presently come here only for ResultPath nodes,
* which represent Result plans without a child plan. So there is
* nothing to recurse to and we can just say "false". (This means
* 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.)
* Result supports mark/restore iff it has a child plan that does.
*
* We have to be careful here because there is more than one Path
* type that can produce a Result plan node.
*/
Assert(IsA(pathnode, ResultPath));
return false;
if (IsA(pathnode, ProjectionPath))
return ExecSupportsMarkRestore(((ProjectionPath *) pathnode)->subpath);
else if (IsA(pathnode, MinMaxAggPath))
return false; /* childless Result */
else
{
Assert(IsA(pathnode, ResultPath));
return false; /* childless Result */
}
default:
break;

View File

@ -867,9 +867,9 @@ _copyAgg(const Agg *from)
CopyPlanFields((const Plan *) from, (Plan *) newnode);
COPY_SCALAR_FIELD(aggstrategy);
COPY_SCALAR_FIELD(numCols);
COPY_SCALAR_FIELD(combineStates);
COPY_SCALAR_FIELD(finalizeAggs);
COPY_SCALAR_FIELD(numCols);
if (from->numCols > 0)
{
COPY_POINTER_FIELD(grpColIdx, from->numCols * sizeof(AttrNumber));

View File

@ -706,21 +706,19 @@ _outAgg(StringInfo str, const Agg *node)
_outPlanInfo(str, (const Plan *) node);
WRITE_ENUM_FIELD(aggstrategy, AggStrategy);
WRITE_BOOL_FIELD(combineStates);
WRITE_BOOL_FIELD(finalizeAggs);
WRITE_INT_FIELD(numCols);
appendStringInfoString(str, " :grpColIdx");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %d", node->grpColIdx[i]);
WRITE_BOOL_FIELD(combineStates);
WRITE_BOOL_FIELD(finalizeAggs);
appendStringInfoString(str, " :grpOperators");
for (i = 0; i < node->numCols; i++)
appendStringInfo(str, " %u", node->grpOperators[i]);
WRITE_LONG_FIELD(numGroups);
WRITE_NODE_FIELD(groupingSets);
WRITE_NODE_FIELD(chain);
}
@ -1603,6 +1601,15 @@ _outPathInfo(StringInfo str, const Path *node)
if (node->pathtarget != &(node->parent->reltarget))
{
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.per_tuple, "%.2f");
WRITE_INT_FIELD(pathtarget->width);
@ -1703,6 +1710,16 @@ _outTidPath(StringInfo str, const TidPath *node)
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
_outForeignPath(StringInfo str, const ForeignPath *node)
{
@ -1793,6 +1810,174 @@ _outGatherPath(StringInfo str, const GatherPath *node)
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
_outNestPath(StringInfo str, const NestPath *node)
{
@ -1881,6 +2066,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
WRITE_NODE_FIELD(window_pathkeys);
WRITE_NODE_FIELD(distinct_pathkeys);
WRITE_NODE_FIELD(sort_pathkeys);
WRITE_NODE_FIELD(processed_tlist);
WRITE_NODE_FIELD(minmax_aggs);
WRITE_FLOAT_FIELD(total_table_pages, "%.0f");
WRITE_FLOAT_FIELD(tuple_fraction, "%.4f");
@ -1910,6 +2096,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node)
WRITE_BOOL_FIELD(consider_param_startup);
WRITE_BOOL_FIELD(consider_parallel);
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.per_tuple, "%.2f");
WRITE_INT_FIELD(reltarget.width);
@ -1933,7 +2120,6 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node)
WRITE_UINT_FIELD(pages);
WRITE_FLOAT_FIELD(tuples, "%.0f");
WRITE_FLOAT_FIELD(allvisfrac, "%.6f");
WRITE_NODE_FIELD(subplan);
WRITE_NODE_FIELD(subroot);
WRITE_NODE_FIELD(subplan_params);
WRITE_OID_FIELD(serverid);
@ -3331,6 +3517,9 @@ _outNode(StringInfo str, const void *obj)
case T_TidPath:
_outTidPath(str, obj);
break;
case T_SubqueryScanPath:
_outSubqueryScanPath(str, obj);
break;
case T_ForeignPath:
_outForeignPath(str, obj);
break;
@ -3355,6 +3544,45 @@ _outNode(StringInfo str, const void *obj)
case T_GatherPath:
_outGatherPath(str, obj);
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:
_outNestPath(str, obj);
break;

View File

@ -1997,10 +1997,10 @@ _readAgg(void)
ReadCommonPlan(&local_node->plan);
READ_ENUM_FIELD(aggstrategy, AggStrategy);
READ_INT_FIELD(numCols);
READ_ATTRNUMBER_ARRAY(grpColIdx, local_node->numCols);
READ_BOOL_FIELD(combineStates);
READ_BOOL_FIELD(finalizeAggs);
READ_INT_FIELD(numCols);
READ_ATTRNUMBER_ARRAY(grpColIdx, local_node->numCols);
READ_OID_ARRAY(grpOperators, local_node->numCols);
READ_LONG_FIELD(numGroups);
READ_NODE_FIELD(groupingSets);

View File

@ -20,7 +20,7 @@ Paths and Join Pairs
During the planning/optimizing process, we build "Path" trees representing
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
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
planning, and include info needed for planning that won't be needed by the
executor.)
@ -43,10 +43,8 @@ base rels of the query.
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
index scans using one or more indexes. A subquery base relation just has
one Path, a "SubqueryScan" path (which links to the subplan that was built
by a recursive invocation of the planner). Likewise a function-RTE base
relation has only one possible Path.
index scans using one or more indexes. Specialized RTE types, such as
function RTEs, may have only one possible Path.
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
@ -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
key in the hashtable.
A Path for a join relation is actually a tree structure, with the top
Path node representing the join method. It has left and right subpaths
that represent the scan or join methods used for the two input relations.
A Path for a join relation is actually a tree structure, with the topmost
Path node representing the last-applied join method. It has left and right
subpaths that represent the scan or join methods used for the two input
relations.
Join Tree Construction
@ -292,8 +291,7 @@ Optimizer Functions
The primary entry point is planner().
planner()
set up for recursive handling of subqueries
do final cleanup after planning
set up for recursive handling of subqueries
-subquery_planner()
pull up sublinks and subqueries from rangetable, if possible
canonicalize qual
@ -326,14 +324,15 @@ planner()
Back at standard_join_search(), apply set_cheapest() to extract the
cheapest path for each newly constructed joinrel.
Loop back if this wasn't the top join level.
Back at grouping_planner:
convert Path tree returned by query_planner into a Plan tree
do grouping(GROUP)
do aggregates
do window functions
make unique(DISTINCT)
make sort(ORDER BY)
make limit(LIMIT/OFFSET)
Back at grouping_planner:
do grouping (GROUP BY) and aggregation
do window functions
make unique (DISTINCT)
do sorting (ORDER BY)
do limit (LIMIT/OFFSET)
Back at planner():
convert finished Path tree into a Plan tree
do final cleanup after planning
Optimizer Data Structures
@ -355,12 +354,28 @@ RelOptInfo - a relation or joined relations
IndexPath - index scan
BitmapHeapPath - top of a bitmapped index scan
TidPath - scan by CTID
SubqueryScanPath - scan a subquery-in-FROM
ForeignPath - scan a foreign table
CustomPath - for custom scan providers
AppendPath - append multiple subpaths together
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
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
MergePath - merge joins
HashPath - hash joins
@ -851,6 +866,59 @@ lateral reference. (Perhaps now that that stuff works, we could relax the
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
--------------------------------

View File

@ -37,6 +37,7 @@
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "parser/parse_clause.h"
#include "parser/parsetree.h"
@ -97,7 +98,6 @@ static Path *get_cheapest_parameterized_child_path(PlannerInfo *root,
RelOptInfo *rel,
Relids required_outer);
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,
Index rti, RangeTblEntry *rte);
static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel,
@ -1507,8 +1507,10 @@ accumulate_append_subpath(List *subpaths, Path *path)
*
* 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).
*
* This is exported because inheritance_planner() has need for it.
*/
static void
void
set_dummy_rel_pathlist(RelOptInfo *rel)
{
/* Set dummy size estimates --- we leave attr_widths[] as zeroes */
@ -1554,15 +1556,15 @@ has_multiple_baserels(PlannerInfo *root)
/*
* 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
* by pushing join clauses down into them; it seems too expensive to re-plan
* the subquery multiple times to consider different alternatives. So the
* subquery will have exactly one path. (The path will be parameterized
* if the subquery contains LATERAL references, otherwise not.) Since there's
* no freedom of action here, there's no need for a separate set_subquery_size
* phase: we just make the path right away.
* the subquery multiple times to consider different alternatives.
* (XXX that could stand to be reconsidered, now that we use Paths.)
* So the paths made here will be parameterized if the subquery contains
* LATERAL references, otherwise not. As long as that's true, there's no need
* for a separate set_subquery_size phase: just make the paths right away.
*/
static void
set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
@ -1573,8 +1575,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer;
pushdown_safety_info safetyInfo;
double tuple_fraction;
PlannerInfo *subroot;
List *pathkeys;
RelOptInfo *sub_final_rel;
ListCell *lc;
/*
* 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,
/* plan_params should not be in use in current query level */
Assert(root->plan_params == NIL);
/* Generate the plan for the subquery */
rel->subplan = subquery_planner(root->glob, subquery,
/* Generate a subroot and Paths for the subquery */
rel->subroot = subquery_planner(root->glob, subquery,
root,
false, tuple_fraction,
&subroot);
rel->subroot = subroot;
false, tuple_fraction);
/* Isolate the params needed by this specific subplan */
rel->subplan_params = root->plan_params;
@ -1698,23 +1698,44 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
/*
* 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.
*/
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);
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);
/* 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.
*/
foreach(lc, sub_final_rel->pathlist)
{
Path *subpath = (Path *) lfirst(lc);
List *pathkeys;
/* Generate appropriate path */
add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer));
/* 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)
cteplan = (Plan *) list_nth(root->glob->subplans, plan_id - 1);
/* 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
@ -1881,13 +1902,13 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
static void
set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
Plan *cteplan;
Path *ctepath;
PlannerInfo *cteroot;
Index levelsup;
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*
* where the CTE comes from.
*/
@ -1902,12 +1923,12 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
if (!cteroot) /* shouldn't happen */
elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
}
cteplan = cteroot->non_recursive_plan;
if (!cteplan) /* shouldn't happen */
elog(ERROR, "could not find plan for CTE \"%s\"", rte->ctename);
ctepath = cteroot->non_recursive_path;
if (!ctepath) /* shouldn't happen */
elog(ERROR, "could not find path for CTE \"%s\"", rte->ctename);
/* 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
@ -2859,6 +2880,9 @@ print_path(PlannerInfo *root, Path *path, int indent)
case T_TidPath:
ptype = "TidScan";
break;
case T_SubqueryScanPath:
ptype = "SubqueryScanScan";
break;
case T_ForeignPath:
ptype = "ForeignScan";
break;
@ -2883,6 +2907,55 @@ print_path(PlannerInfo *root, Path *path, int indent)
ptype = "Gather";
subpath = ((GatherPath *) path)->subpath;
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:
ptype = "NestLoop";
join = true;

View File

@ -1169,7 +1169,7 @@ cost_tidscan(Path *path, PlannerInfo *root,
* 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
*/
void
cost_subqueryscan(Path *path, PlannerInfo *root,
cost_subqueryscan(SubqueryScanPath *path, PlannerInfo *root,
RelOptInfo *baserel, ParamPathInfo *param_info)
{
Cost startup_cost;
@ -1183,17 +1183,18 @@ cost_subqueryscan(Path *path, PlannerInfo *root,
/* Mark the path with the correct row estimate */
if (param_info)
path->rows = param_info->ppi_rows;
path->path.rows = param_info->ppi_rows;
else
path->rows = baserel->rows;
path->path.rows = baserel->rows;
/*
* Cost of path is cost of evaluating the subplan, plus cost of evaluating
* any restriction clauses that will be attached to the SubqueryScan node,
* plus cpu_tuple_cost to account for selection and projection overhead.
* any restriction clauses and tlist that will be attached to the
* SubqueryScan node, plus cpu_tuple_cost to account for selection and
* projection overhead.
*/
path->startup_cost = baserel->subplan->startup_cost;
path->total_cost = baserel->subplan->total_cost;
path->path.startup_cost = path->subpath->startup_cost;
path->path.total_cost = path->subpath->total_cost;
get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
@ -1202,11 +1203,11 @@ cost_subqueryscan(Path *path, PlannerInfo *root,
run_cost = cpu_per_tuple * baserel->tuples;
/* tlist eval costs are paid per output row, not per tuple scanned */
startup_cost += path->pathtarget->cost.startup;
run_cost += path->pathtarget->cost.per_tuple * path->rows;
startup_cost += path->path.pathtarget->cost.startup;
run_cost += path->path.pathtarget->cost.per_tuple * path->path.rows;
path->startup_cost += startup_cost;
path->total_cost += startup_cost + run_cost;
path->path.startup_cost += startup_cost;
path->path.total_cost += startup_cost + run_cost;
}
/*
@ -1369,14 +1370,10 @@ cost_ctescan(Path *path, PlannerInfo *root,
* Determines and returns the cost of performing a recursive union,
* and also the estimated output size.
*
* We are given Plans 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.
* We are given Paths for the nonrecursive and recursive terms.
*/
void
cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm)
cost_recursive_union(Path *runion, Path *nrterm, Path *rterm)
{
Cost startup_cost;
Cost total_cost;
@ -1385,7 +1382,7 @@ cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm)
/* We probably have decent estimates for the non-recursive term */
startup_cost = nrterm->startup_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
@ -1394,7 +1391,7 @@ cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm)
* hard to see how to do better.
*/
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
@ -1405,8 +1402,9 @@ cost_recursive_union(Plan *runion, Plan *nrterm, Plan *rterm)
runion->startup_cost = startup_cost;
runion->total_cost = total_cost;
runion->plan_rows = total_rows;
runion->plan_width = Max(nrterm->plan_width, rterm->plan_width);
runion->rows = total_rows;
runion->pathtarget->width = Max(nrterm->pathtarget->width,
rterm->pathtarget->width);
}
/*
@ -3996,8 +3994,8 @@ calc_joinrel_size_estimate(PlannerInfo *root,
* Set the size estimates for a base relation that is a subquery.
*
* The rel's targetlist and restrictinfo list must have been constructed
* already, and the plan for the subquery must have been completed.
* We look at the subquery's plan and PlannerInfo to extract data.
* already, and the Paths for the subquery must have been completed.
* We look at the subquery's PlannerInfo to extract data.
*
* We set the same fields as set_baserel_size_estimates.
*/
@ -4005,6 +4003,7 @@ void
set_subquery_size_estimates(PlannerInfo *root, RelOptInfo *rel)
{
PlannerInfo *subroot = rel->subroot;
RelOptInfo *sub_final_rel;
RangeTblEntry *rte PG_USED_FOR_ASSERTS_ONLY;
ListCell *lc;
@ -4013,8 +4012,12 @@ set_subquery_size_estimates(PlannerInfo *root, RelOptInfo *rel)
rte = planner_rt_fetch(rel->relid, root);
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
@ -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.
*
* 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)
* or the non-recursive term (if a self-reference).
* already, and we need an estimate of the number of rows returned by the CTE
* (if a regular CTE) or the non-recursive term (if a self-reference).
*
* We set the same fields as set_baserel_size_estimates.
*/
void
set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, Plan *cteplan)
set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, double cte_rows)
{
RangeTblEntry *rte;
@ -4165,12 +4168,12 @@ set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, Plan *cteplan)
* In a self-reference, arbitrarily assume the average worktable size
* is about 10 times the nonrecursive term's size.
*/
rel->tuples = 10 * cteplan->plan_rows;
rel->tuples = 10 * cte_rows;
}
else
{
/* Otherwise just believe the CTE plan's output estimate */
rel->tuples = cteplan->plan_rows;
/* Otherwise just believe the CTE's rowcount estimate */
rel->tuples = cte_rows;
}
/* Now estimate number of output rows, etc */
@ -4225,7 +4228,7 @@ set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel)
* any better number.
*
* 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
set_rel_width(PlannerInfo *root, RelOptInfo *rel)
@ -4373,6 +4376,91 @@ set_rel_width(PlannerInfo *root, RelOptInfo *rel)
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
* Estimate the storage space in bytes for a given number of tuples

View File

@ -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
* Create EC-derived joinclauses usable with a specific column.

View File

@ -557,6 +557,7 @@ build_expression_pathkey(PlannerInfo *root,
*
* 'rel': outer query's RelOptInfo for the subquery relation.
* '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(),
* because we select keys in a way that takes usefulness of the keys into
@ -564,12 +565,12 @@ build_expression_pathkey(PlannerInfo *root,
*/
List *
convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
List *subquery_pathkeys)
List *subquery_pathkeys,
List *subquery_tlist)
{
List *retval = NIL;
int retvallen = 0;
int outer_query_keys = list_length(root->query_pathkeys);
List *sub_tlist = rel->subplan->targetlist;
ListCell *i;
foreach(i, subquery_pathkeys)
@ -589,7 +590,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
if (sub_eclass->ec_sortref == 0) /* can't happen */
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);
/* resjunk items aren't visible to outer query */
if (!tle->resjunk)
@ -669,7 +670,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
if (sub_member->em_is_child)
continue; /* ignore children here */
foreach(k, sub_tlist)
foreach(k, subquery_tlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(k);
Expr *tle_expr;

File diff suppressed because it is too large Load Diff

View File

@ -35,13 +35,14 @@
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
#include "optimizer/planner.h"
#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "parser/parsetree.h"
#include "parser/parse_clause.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
@ -50,8 +51,6 @@ static bool find_minmax_aggs_walker(Node *node, List **context);
static bool build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
Oid eqop, Oid sortop, bool nulls_first);
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);
@ -60,8 +59,14 @@ static Oid fetch_agg_sort_op(Oid aggfnoid);
*
* Check to see whether the query contains MIN/MAX aggregate functions that
* 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
* these aggregates.
* are potentially optimizable, then create a MinMaxAggPath and add it to
* 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
* not necessarily equal to root->parse->targetList.
@ -74,6 +79,7 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
RangeTblRef *rtr;
RangeTblEntry *rte;
List *aggs_list;
RelOptInfo *grouped_rel;
ListCell *lc;
/* minmax_aggs list should be empty at this point */
@ -91,12 +97,10 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
*
* We don't handle GROUP BY or windowing, because our current
* implementations of grouping require looking at all the rows anyway, and
* so there's not much point in optimizing MIN/MAX. (Note: relaxing this
* 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.)
* so there's not much point in optimizing MIN/MAX.
*/
if (parse->groupClause || list_length(parse->groupingSets) > 1 || parse->hasWindowFuncs)
if (parse->groupClause || list_length(parse->groupingSets) > 1 ||
parse->hasWindowFuncs)
return;
/*
@ -138,11 +142,9 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
/*
* OK, there is at least the possibility of performing the optimization.
* Build an access path for each aggregate. (We must do this now because
* we need to call query_planner with a pristine copy of the current query
* tree; it'll be too late when optimize_minmax_aggregates gets called.)
* If any of the aggregates prove to be non-indexable, give up; there is
* no point in optimizing just some of them.
* Build an access path for each aggregate. 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)
{
@ -177,111 +179,40 @@ preprocess_minmax_aggregates(PlannerInfo *root, List *tlist)
}
/*
* We're done until path generation is complete. Save info for later.
* (Setting root->minmax_aggs non-NIL signals we succeeded in making index
* 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.
* OK, we can do the query this way. Prepare to create a MinMaxAggPath
* node.
*
* Note that we don't include evaluation cost of the tlist here; this is
* OK since it isn't included in best_path's cost either, and should be
* the same in either case.
* First, create an output Param node for each agg. (If we end up not
* using the MinMaxAggPath, we'll waste a PARAM_EXEC slot for each agg,
* 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, root->minmax_aggs)
foreach(lc, aggs_list)
{
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
total_cost += mminfo->pathcost;
}
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.
*
* First, generate a subplan and output Param node for each agg.
*/
foreach(lc, root->minmax_aggs)
{
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
make_agg_subplan(root, mminfo);
mminfo->param =
SS_make_initplan_output_param(root,
exprType((Node *) mminfo->target),
-1,
exprCollation((Node *) mminfo->target));
}
/*
* Modify the targetlist and HAVING qual to reference subquery outputs
*/
tlist = (List *) replace_aggs_with_params_mutator((Node *) tlist, root);
hqual = replace_aggs_with_params_mutator(parse->havingQual, root);
/*
* We have to replace Aggrefs with Params in equivalence classes too, else
* ORDER BY or DISTINCT on an optimized aggregate will fail. We don't
* 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.
* 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.)
*
* 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.
* Note: grouping_planner won't have created this upperrel yet, but it's
* fine for us to create it first.
*/
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;
grouped_rel = fetch_upper_rel(root, UPPERREL_GROUP_AGG, NULL);
add_path(grouped_rel, (Path *)
create_minmaxagg_path(root, grouped_rel,
create_pathtarget(root, tlist),
aggs_list,
(List *) parse->havingQual));
}
/*
@ -403,6 +334,7 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
PlannerInfo *subroot;
Query *parse;
TargetEntry *tle;
List *tlist;
NullTest *ntest;
SortGroupClause *sortcl;
RelOptInfo *final_rel;
@ -410,40 +342,51 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
Cost path_cost;
double path_fraction;
/*
* We are going to construct what is effectively a sub-SELECT query, so
* clone the current query level's state and adjust it to make it look
* like a subquery. Any outer references will now be one level higher
* than before. (This means that when we are done, there will be no Vars
* of level 1, which is why the subquery can become an initplan.)
*/
subroot = (PlannerInfo *) palloc(sizeof(PlannerInfo));
memcpy(subroot, root, sizeof(PlannerInfo));
subroot->query_level++;
subroot->parent_root = root;
/* reset subplan-related stuff */
subroot->plan_params = NIL;
subroot->outer_params = NULL;
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 */
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 */
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)
*
* 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));
memcpy(subroot, root, sizeof(PlannerInfo));
subroot->parse = parse = (Query *) copyObject(root->parse);
/* reset subplan-related stuff */
subroot->plan_params = NIL;
subroot->outer_params = NULL;
subroot->init_plans = NIL;
/* There shouldn't be any OJ info to translate, as yet */
Assert(subroot->join_info_list == NIL);
/* and we haven't created PlaceHolderInfos, either */
Assert(subroot->placeholder_list == NIL);
/* single tlist entry that is the aggregate target */
tle = makeTargetEntry(copyObject(mminfo->target),
(AttrNumber) 1,
pstrdup("agg_target"),
false);
parse->targetList = list_make1(tle);
tlist = list_make1(tle);
subroot->processed_tlist = parse->targetList = tlist;
/* No HAVING, no DISTINCT, no aggregates anymore */
parse->havingQual = NULL;
@ -467,7 +410,7 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
/* Build suitable ORDER BY clause */
sortcl = makeNode(SortGroupClause);
sortcl->tleSortGroupRef = assignSortGroupRef(tle, parse->targetList);
sortcl->tleSortGroupRef = assignSortGroupRef(tle, tlist);
sortcl->eqop = eqop;
sortcl->sortop = sortop;
sortcl->nulls_first = nulls_first;
@ -488,8 +431,16 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
subroot->tuple_fraction = 1.0;
subroot->limit_tuples = 1.0;
final_rel = query_planner(subroot, parse->targetList,
minmax_qp_callback, NULL);
final_rel = query_planner(subroot, tlist, 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
@ -508,6 +459,14 @@ build_minmax_path(PlannerInfo *root, MinMaxAggInfo *mminfo,
if (!sorted_path)
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.
*
@ -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
minmax_qp_callback(PlannerInfo *root, void *extra)
@ -543,105 +502,6 @@ minmax_qp_callback(PlannerInfo *root, void *extra)
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.
* Returns InvalidOid if there is no such operator.

View File

@ -36,9 +36,7 @@
* Since query_planner does not handle the toplevel processing (grouping,
* sorting, etc) it cannot select the best path by itself. Instead, it
* returns the RelOptInfo for the top level of joining, and the caller
* (grouping_planner) can choose one of 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.
* (grouping_planner) can choose among the surviving paths for the rel.
*
* root describes the query to plan
* tlist is the target list the query should produce
@ -85,6 +83,7 @@ query_planner(PlannerInfo *root, List *tlist,
/* The only path for it is a trivial Result path */
add_path(final_rel, (Path *)
create_result_path(final_rel,
&(final_rel->reltarget),
(List *) parse->jointree->quals));
/* Select cheapest path (pretty easy in this case...) */
@ -104,7 +103,7 @@ query_planner(PlannerInfo *root, List *tlist,
* Init planner lists to empty.
*
* 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_hash = NULL;

File diff suppressed because it is too large Load Diff

View File

@ -304,8 +304,8 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
* in our query level. In this case apply
* flatten_unplanned_rtes.
*
* If it was planned but the plan is dummy, we assume that it
* has been omitted from our plan tree (see
* If it was planned but the result rel is dummy, we assume
* that it has been omitted from our plan tree (see
* set_subquery_pathlist), and recurse to pull up its RTEs.
*
* Otherwise, it should be represented by a SubqueryScan node
@ -313,17 +313,16 @@ add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
* we process that plan node.
*
* 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,
* 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);
else if (recursing || is_dummy_plan(rel->subplan))
{
Assert(rel->subroot != NULL);
else if (recursing ||
IS_DUMMY_REL(fetch_upper_rel(rel->subroot,
UPPERREL_FINAL, NULL)))
add_rtes_to_flat_rtable(rel->subroot, true);
}
}
}
rti++;
@ -979,7 +978,6 @@ set_subqueryscan_references(PlannerInfo *root,
/* Need to look up the subquery's RelOptInfo, since we need its subroot */
rel = find_base_rel(root, plan->scan.scanrelid);
Assert(rel->subplan == plan->subplan);
/* Recursively process the subplan */
plan->subplan = set_plan_references(rel->subroot, plan->subplan);
@ -1386,6 +1384,7 @@ fix_param_node(PlannerInfo *root, Param *p)
*
* This consists of incrementing all Vars' varnos by rtoffset,
* 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,
* and adding OIDs from regclass Const nodes into root->glob->relationOids.
*/
@ -1399,7 +1398,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
if (rtoffset != 0 ||
root->multiexpr_params != NIL ||
root->glob->lastPHId != 0)
root->glob->lastPHId != 0 ||
root->minmax_aggs != NIL)
{
return fix_scan_expr_mutator(node, &context);
}
@ -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
* are no MULTIEXPR subqueries then we don't need to replace
* 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
* in any unset opfuncid fields, is harmless). This saves just enough
* cycles to be noticeable on trivial queries.
@ -1444,6 +1445,28 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
}
if (IsA(node, Param))
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))
{
CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node);
@ -2091,8 +2114,9 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
/*
* fix_upper_expr
* Modifies an expression tree so that all Var nodes reference outputs
* of a subplan. Also performs opcode lookup, and adds regclass OIDs to
* root->glob->relationOids.
* of a subplan. Also looks for Aggref nodes that should be replaced
* 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
* plan nodes, as well as index-only scan nodes.
@ -2169,6 +2193,28 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
}
if (IsA(node, Param))
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 */
if (context->subplan_itlist->has_non_vars)
{

View File

@ -478,8 +478,10 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
Query *subquery;
bool simple_exists = false;
double tuple_fraction;
Plan *plan;
PlannerInfo *subroot;
RelOptInfo *final_rel;
Path *best_path;
Plan *plan;
List *plan_params;
Node *result;
@ -527,18 +529,24 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
/* plan_params should not be in use in current query level */
Assert(root->plan_params == NIL);
/*
* Generate the plan for the subquery.
*/
plan = subquery_planner(root->glob, subquery,
root,
false, tuple_fraction,
&subroot);
/* Generate Paths for the subquery */
subroot = subquery_planner(root->glob, subquery,
root,
false, tuple_fraction);
/* Isolate the params needed by this specific subplan */
plan_params = root->plan_params;
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. */
result = build_subplan(root, plan, subroot, plan_params,
subLinkType, subLinkId,
@ -568,17 +576,23 @@ make_subplan(PlannerInfo *root, Query *orig_subquery,
&newtestexpr, &paramIds);
if (subquery)
{
/* Generate the plan for the ANY subquery; we'll need all rows */
plan = subquery_planner(root->glob, subquery,
root,
false, 0.0,
&subroot);
/* Generate Paths for the ANY subquery; we'll need all rows */
subroot = subquery_planner(root->glob, subquery,
root,
false, 0.0);
/* Isolate the params needed by this specific subplan */
plan_params = root->plan_params;
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 */
/* XXX can we check this at the Path stage? */
if (subplan_is_hashable(plan))
{
SubPlan *hashplan;
@ -1133,8 +1147,10 @@ SS_process_ctes(PlannerInfo *root)
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
CmdType cmdType = ((Query *) cte->ctequery)->commandType;
Query *subquery;
Plan *plan;
PlannerInfo *subroot;
RelOptInfo *final_rel;
Path *best_path;
Plan *plan;
SubPlan *splan;
int paramid;
@ -1158,13 +1174,12 @@ SS_process_ctes(PlannerInfo *root)
Assert(root->plan_params == NIL);
/*
* Generate the plan for the CTE query. Always plan for full
* retrieval --- we don't have enough info to predict otherwise.
* Generate Paths for the CTE query. Always plan for full retrieval
* --- we don't have enough info to predict otherwise.
*/
plan = subquery_planner(root->glob, subquery,
root,
cte->cterecursive, 0.0,
&subroot);
subroot = subquery_planner(root->glob, subquery,
root,
cte->cterecursive, 0.0);
/*
* Since the current query level doesn't yet contain any RTEs, it
@ -1174,6 +1189,15 @@ SS_process_ctes(PlannerInfo *root)
if (root->plan_params)
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
* build_subplan that we can't share code.
@ -2108,36 +2132,71 @@ SS_identify_outer_params(PlannerInfo *root)
root->outer_params = outer_params;
}
/*
* SS_charge_for_initplans - account for cost of initplans in Path costs
*
* If any initPlans have been created in the current query level, they will
* get attached to the Plan tree created from whichever Path we select from
* the given rel; so increment all the rel's Paths' costs to account for them.
*
* 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
SS_charge_for_initplans(PlannerInfo *root, RelOptInfo *final_rel)
{
Cost initplan_cost;
ListCell *lc;
/* Nothing to do if no initPlans */
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);
initplan_cost += initsubplan->startup_cost + initsubplan->per_call_cost;
}
/*
* Now adjust the costs.
*/
foreach(lc, final_rel->pathlist)
{
Path *path = (Path *) lfirst(lc);
path->startup_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 topmost plan
* node for the query level, and increment that node's cost to account for
* them. (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 for the query level.)
* 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)
{
ListCell *lc;
plan->initPlan = root->init_plans;
foreach(lc, plan->initPlan)
{
SubPlan *initsubplan = (SubPlan *) lfirst(lc);
Cost initplan_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.
*/
initplan_cost = initsubplan->startup_cost + initsubplan->per_call_cost;
plan->startup_cost += initplan_cost;
plan->total_cost += initplan_cost;
}
}
/*
@ -2298,7 +2357,6 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
/* We must run SS_finalize_plan on the subquery */
rel = find_base_rel(root, sscan->scan.scanrelid);
Assert(rel->subplan == sscan->subplan);
SS_finalize_plan(rel->subroot, sscan->subplan);
/* Now we can add its extParams to the parent's params */
@ -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.
* 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
* output is returned.
*
* 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
* list for the outer query level. A Param that represents the initplan's
* output has already been assigned using SS_make_initplan_output_param.
*/
void
SS_make_initplan_from_plan(PlannerInfo *root,
PlannerInfo *subroot, Plan *plan,
Oid resulttype, int32 resulttypmod,
Oid resultcollation)
Param *prm)
{
SubPlan *node;
Param *prm;
/*
* Add the subplan and its PlannerInfo to the global lists.
@ -2769,9 +2841,12 @@ SS_make_initplan_from_plan(PlannerInfo *root,
*/
node = makeNode(SubPlan);
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,
&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);
@ -2780,17 +2855,6 @@ SS_make_initplan_from_plan(PlannerInfo *root,
* parParam and args lists remain empty.
*/
/* Set costs of SubPlan using info from the plan tree */
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;
}

View File

@ -907,9 +907,14 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
subroot->eq_classes = NIL;
subroot->append_rel_list = 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->wt_param_id = -1;
subroot->non_recursive_plan = NULL;
subroot->non_recursive_path = NULL;
/* No CTEs to worry about */
Assert(subquery->cteList == NIL);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1142,7 +1142,27 @@ relation_excluded_by_constraints(PlannerInfo *root,
List *safe_constraints;
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 ||
(constraint_exclusion == CONSTRAINT_EXCLUSION_PARTITION &&
!(rel->reloptkind == RELOPT_OTHER_MEMBER_REL ||

View File

@ -107,6 +107,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
rel->consider_param_startup = false; /* might get changed later */
rel->consider_parallel = false; /* might get changed later */
rel->reltarget.exprs = NIL;
rel->reltarget.sortgrouprefs = NULL;
rel->reltarget.cost.startup = 0;
rel->reltarget.cost.per_tuple = 0;
rel->reltarget.width = 0;
@ -128,7 +129,6 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
rel->pages = 0;
rel->tuples = 0;
rel->allvisfrac = 0;
rel->subplan = NULL;
rel->subroot = NULL;
rel->subplan_params = NIL;
rel->serverid = InvalidOid;
@ -394,6 +394,7 @@ build_join_rel(PlannerInfo *root,
joinrel->consider_param_startup = false;
joinrel->consider_parallel = false;
joinrel->reltarget.exprs = NIL;
joinrel->reltarget.sortgrouprefs = NULL;
joinrel->reltarget.cost.startup = 0;
joinrel->reltarget.cost.per_tuple = 0;
joinrel->reltarget.width = 0;
@ -422,7 +423,6 @@ build_join_rel(PlannerInfo *root,
joinrel->pages = 0;
joinrel->tuples = 0;
joinrel->allvisfrac = 0;
joinrel->subplan = NULL;
joinrel->subroot = NULL;
joinrel->subplan_params = NIL;
joinrel->serverid = InvalidOid;
@ -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
* Get the AppendRelInfo associated with an appendrel child rel.

View File

@ -74,13 +74,12 @@ tlist_member_ignore_relabel(Node *node, List *targetlist)
/*
* tlist_member_match_var
* 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
* match. It's probably a good idea to check the vartype anyway, but
* we leave it to the caller to apply any suitable sanity checks.
* match. For safety, though, we insist on vartype match.
*/
TargetEntry *
static TargetEntry *
tlist_member_match_var(Var *var, List *targetlist)
{
ListCell *temp;
@ -94,7 +93,8 @@ tlist_member_match_var(Var *var, List *targetlist)
continue;
if (var->varno == tlvar->varno &&
var->varattno == tlvar->varattno &&
var->varlevelsup == tlvar->varlevelsup)
var->varlevelsup == tlvar->varlevelsup &&
var->vartype == tlvar->vartype)
return tlentry;
}
return NULL;
@ -316,6 +316,34 @@ tlist_same_collations(List *tlist, List *colCollations, bool junkOK)
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
@ -506,3 +534,119 @@ grouping_is_hashable(List *groupClause)
}
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++;
}
}

View File

@ -229,18 +229,33 @@ typedef enum NodeTag
T_BitmapHeapPath,
T_BitmapAndPath,
T_BitmapOrPath,
T_TidPath,
T_SubqueryScanPath,
T_ForeignPath,
T_CustomPath,
T_NestPath,
T_MergePath,
T_HashPath,
T_TidPath,
T_ForeignPath,
T_CustomPath,
T_AppendPath,
T_MergeAppendPath,
T_ResultPath,
T_MaterialPath,
T_UniquePath,
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_EquivalenceMember,
T_PathKey,
@ -653,6 +668,39 @@ typedef enum JoinType
(1 << JOIN_RIGHT) | \
(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 -
* "ON CONFLICT" clause type of query

View File

@ -714,23 +714,17 @@ typedef struct Group
* 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
{
Plan plan;
AggStrategy aggstrategy;
int numCols; /* number of grouping columns */
AttrNumber *grpColIdx; /* their indexes in the target list */
AggStrategy aggstrategy; /* basic strategy, see nodes.h */
bool combineStates; /* input tuples contain transition 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 */
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 *chain; /* chained Agg/Sort nodes */
} Agg;
@ -802,25 +796,11 @@ typedef struct Hash
* 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
{
Plan plan;
SetOpCmd cmd; /* what to do */
SetOpStrategy strategy; /* how to do it */
SetOpCmd cmd; /* what to do, see nodes.h */
SetOpStrategy strategy; /* how to do it, see nodes.h */
int numCols; /* number of columns to check for
* duplicate-ness */
AttrNumber *dupColIdx; /* their indexes in the target list */

View File

@ -70,16 +70,40 @@ typedef struct AggClauseCosts
* example, an indexscan might return index expressions that would otherwise
* need to be explicitly calculated.
*
* Note that PathTarget.exprs is just a list of expressions; they do not have
* TargetEntry nodes on top, though those will appear in the finished Plan.
* exprs contains bare expressions; they do not have TargetEntry nodes on top,
* 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
{
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 */
} 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
@ -255,18 +279,28 @@ typedef struct PlannerInfo
List *placeholder_list; /* list of PlaceHolderInfos */
List *query_pathkeys; /* desired pathkeys for query_planner(), and
* actual pathkeys after planning */
List *query_pathkeys; /* desired pathkeys for query_planner() */
List *group_pathkeys; /* groupClause pathkeys, if any */
List *window_pathkeys; /* pathkeys of bottom window, if any */
List *distinct_pathkeys; /* distinctClause 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 */
/* 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 */
double total_table_pages; /* # of pages in all tables of query */
@ -286,7 +320,7 @@ typedef struct PlannerInfo
/* These fields are used only when hasRecursion is true: */
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 */
Relids curOuterRels; /* outer rels above current node */
@ -294,9 +328,6 @@ typedef struct PlannerInfo
/* optional private data for join_search_hook, e.g., GEQO */
void *join_search_private;
/* for GroupingFunc fixup in setrefs */
AttrNumber *grouping_map;
} PlannerInfo;
@ -328,10 +359,7 @@ typedef struct PlannerInfo
*
* 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
* a different RelOptKind to identify them. Lastly, there is a RelOptKind
* for "dead" relations, which are base rels that we have proven we don't
* need to join after all.
*
* a different RelOptKind to identify them.
* 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.
* An append relation has a parent RTE that is a base rel, which represents
@ -346,6 +374,14 @@ typedef struct PlannerInfo
* handling join alias Vars. Currently this is not needed because all join
* 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
* mechanisms. It didn't seem worth creating new node types for them.
*
@ -401,11 +437,10 @@ typedef struct PlannerInfo
* pages - number of disk pages in relation (zero if not a table)
* tuples - number of tuples in relation (not considering restrictions)
* 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)
* 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
* set_subquery_pathlist processes the object.
*
@ -455,6 +490,7 @@ typedef enum RelOptKind
RELOPT_BASEREL,
RELOPT_JOINREL,
RELOPT_OTHER_MEMBER_REL,
RELOPT_UPPER_REL,
RELOPT_DEADREL
} RelOptKind;
@ -506,8 +542,6 @@ typedef struct RelOptInfo
BlockNumber pages; /* size estimates derived from pg_class */
double tuples;
double allvisfrac;
/* use "struct Plan" to avoid including plannodes.h here */
struct Plan *subplan; /* if subquery */
PlannerInfo *subroot; /* if subquery */
List *subplan_params; /* if subquery */
@ -938,6 +972,20 @@ typedef struct TidPath
List *tidquals; /* qual(s) involving CTID = something */
} 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
*
@ -1062,14 +1110,13 @@ typedef struct MaterialPath
* UniquePath represents elimination of distinct rows from the output of
* its subpath.
*
* This is unlike the other Path nodes in that it can actually generate
* different plans: either hash-based or sort-based implementation, or a
* no-op if the input path can be proven distinct already. The decision
* is sufficiently localized that it's not worth having separate Path node
* types. (Note: in the no-op case, we could eliminate the UniquePath node
* entirely and just return the subpath; but it's convenient to have a
* UniquePath in the path tree to signal upper-level routines that the input
* is known distinct.)
* This can represent significantly different plans: either hash-based or
* sort-based implementation, or a no-op if the input path can be proven
* distinct already. The decision is sufficiently localized that it's not
* worth having separate Path node types. (Note: in the no-op case, we could
* eliminate the UniquePath node entirely and just return the subpath; but
* it's convenient to have a UniquePath in the path tree to signal upper-level
* routines that the input is known distinct.)
*/
typedef enum
{
@ -1180,6 +1227,195 @@ typedef struct HashPath
int num_batches; /* number of batches expected */
} 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.
*
@ -1615,8 +1851,9 @@ typedef struct PlaceHolderInfo
} PlaceHolderInfo;
/*
* For each potentially index-optimizable MIN/MAX aggregate function,
* root->minmax_aggs stores a MinMaxAggInfo describing it.
* This struct describes one potentially index-optimizable MIN/MAX aggregate
* function. MinMaxAggPath contains a list of these, and if we accept that
* path, the list is stored into root->minmax_aggs for use during setrefs.c.
*/
typedef struct MinMaxAggInfo
{

View File

@ -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_tidscan(Path *path, PlannerInfo *root,
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);
extern void cost_functionscan(Path *path, PlannerInfo *root,
RelOptInfo *baserel, ParamPathInfo *param_info);
@ -93,7 +93,7 @@ extern void cost_valuesscan(Path *path, PlannerInfo *root,
RelOptInfo *baserel, ParamPathInfo *param_info);
extern void cost_ctescan(Path *path, PlannerInfo *root,
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,
List *pathkeys, Cost input_cost, double tuples, int width,
Cost comparison_cost, int sort_mem,
@ -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_values_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 PathTarget *set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target);
/*
* prototypes for clausesel.c

View File

@ -68,13 +68,15 @@ extern MergeAppendPath *create_merge_append_path(PlannerInfo *root,
List *subpaths,
List *pathkeys,
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 UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath, SpecialJoinInfo *sjinfo);
extern GatherPath *create_gather_path(PlannerInfo *root,
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);
extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
List *pathkeys, Relids required_outer);
@ -132,6 +134,96 @@ extern HashPath *create_hashjoin_path(PlannerInfo *root,
Relids required_outer,
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,
Relids required_outer,
double loop_count);
@ -155,6 +247,8 @@ extern Relids min_join_parameterization(PlannerInfo *root,
RelOptInfo *outer_rel,
RelOptInfo *inner_rel);
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,
RelOptInfo *rel);
extern RelOptInfo *find_childrel_top_parent(PlannerInfo *root, RelOptInfo *rel);

View File

@ -47,6 +47,7 @@ extern PGDLLIMPORT join_search_hook_type join_search_hook;
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,
List *initial_rels);
@ -137,10 +138,6 @@ extern void add_child_rel_equivalences(PlannerInfo *root,
AppendRelInfo *appinfo,
RelOptInfo *parent_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,
RelOptInfo *rel,
ec_matches_callback_type callback,
@ -182,7 +179,8 @@ extern List *build_expression_pathkey(PlannerInfo *root, Expr *expr,
Relids nullable_relids, Oid opno,
Relids rel, bool create_it);
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,
RelOptInfo *joinrel,
JoinType jointype,

View File

@ -43,60 +43,17 @@ extern RelOptInfo *query_planner(PlannerInfo *root, List *tlist,
* prototypes for plan/planagg.c
*/
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
*/
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,
Index scanrelid, List *fdw_exprs, List *fdw_private,
List *fdw_scan_tlist, List *fdw_recheck_quals,
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 Unique *make_unique(Plan *lefttree, List *distinctList);
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_path(Path *path);
extern bool is_projection_capable_plan(Plan *plan);
/*

View File

@ -30,19 +30,18 @@ extern PlannedStmt *planner(Query *parse, int cursorOptions,
extern PlannedStmt *standard_planner(Query *parse, int cursorOptions,
ParamListInfo boundParams);
extern Plan *subquery_planner(PlannerGlobal *glob, Query *parse,
extern PlannerInfo *subquery_planner(PlannerGlobal *glob, Query *parse,
PlannerInfo *parent_root,
bool hasRecursion, double tuple_fraction,
PlannerInfo **subroot);
extern void add_tlist_costs_to_plan(PlannerInfo *root, Plan *plan,
List *tlist);
bool hasRecursion, double tuple_fraction);
extern bool is_dummy_plan(Plan *plan);
extern RowMarkType select_rowmark_type(RangeTblEntry *rte,
LockClauseStrength strength);
extern Path *get_cheapest_fractional_path(RelOptInfo *rel,
double tuple_fraction);
extern Expr *expression_planner(Expr *expr);
extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr);

View File

@ -53,8 +53,7 @@ extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
/*
* prototypes for prepunion.c
*/
extern Plan *plan_set_operations(PlannerInfo *root, double tuple_fraction,
List **sortClauses);
extern RelOptInfo *plan_set_operations(PlannerInfo *root);
extern void expand_inherited_tables(PlannerInfo *root);

View File

@ -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_process_sublinks(PlannerInfo *root, Node *expr, bool isQual);
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_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,
Oid resulttype, int32 resulttypmod, Oid resultcollation);
Param *prm);
extern Param *assign_nestloop_param_var(PlannerInfo *root, Var *var);
extern Param *assign_nestloop_param_placeholdervar(PlannerInfo *root,
PlaceHolderVar *phv);

View File

@ -19,7 +19,6 @@
extern TargetEntry *tlist_member(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,
PVCPlaceHolderBehavior phbehavior);
@ -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_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,
List *targetList);
extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause,
@ -51,4 +52,12 @@ extern AttrNumber *extract_grouping_cols(List *groupClause, List *tlist);
extern bool grouping_is_sortable(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 */

View File

@ -806,8 +806,7 @@ explain (costs off)
select distinct min(f1), max(f1) from minmaxtest;
QUERY PLAN
----------------------------------------------------------------------------------------------
HashAggregate
Group Key: $0, $1
Unique
InitPlan 1 (returns $0)
-> Limit
-> Merge Append
@ -832,8 +831,10 @@ explain (costs off)
Index Cond: (f1 IS NOT NULL)
-> Index Only Scan Backward using minmaxtest3i on minmaxtest3 minmaxtest3_1
Index Cond: (f1 IS NOT NULL)
-> Result
(27 rows)
-> Sort
Sort Key: ($0), ($1)
-> Result
(28 rows)
select distinct min(f1), max(f1) from minmaxtest;
min | max

View File

@ -3949,39 +3949,34 @@ select d.* from d left join (select distinct * from b) s
explain (costs off)
select d.* from d left join (select * from b group by b.id, b.c_id) s
on d.a = s.id;
QUERY PLAN
---------------------------------------
Merge Left Join
Merge Cond: (d.a = s.id)
QUERY PLAN
------------------------------------------
Merge Right Join
Merge Cond: (b.id = d.a)
-> Group
Group Key: b.id
-> Index Scan using b_pkey on b
-> Sort
Sort Key: d.a
-> Seq Scan on d
-> Sort
Sort Key: s.id
-> Subquery Scan on s
-> HashAggregate
Group Key: b.id
-> Seq Scan on b
(11 rows)
(8 rows)
-- similarly, but keying off a DISTINCT clause
explain (costs off)
select d.* from d left join (select distinct * from b) s
on d.a = s.id;
QUERY PLAN
---------------------------------------------
Merge Left Join
Merge Cond: (d.a = s.id)
QUERY PLAN
--------------------------------------
Merge Right Join
Merge Cond: (b.id = d.a)
-> Unique
-> Sort
Sort Key: b.id, b.c_id
-> Seq Scan on b
-> Sort
Sort Key: d.a
-> Seq Scan on d
-> Sort
Sort Key: s.id
-> Subquery Scan on s
-> HashAggregate
Group Key: b.id, b.c_id
-> Seq Scan on b
(11 rows)
(9 rows)
-- check join removal works when uniqueness of the join condition is enforced
-- by a UNION