In the planner, replace an empty FROM clause with a dummy RTE.
The fact that "SELECT expression" has no base relations has long been a thorn in the side of the planner. It makes it hard to flatten a sub-query that looks like that, or is a trivial VALUES() item, because the planner generally uses relid sets to identify sub-relations, and such a sub-query would have an empty relid set if we flattened it. prepjointree.c contains some baroque logic that works around this in certain special cases --- but there is a much better answer. We can replace an empty FROM clause with a dummy RTE that acts like a table of one row and no columns, and then there are no such corner cases to worry about. Instead we need some logic to get rid of useless dummy RTEs, but that's simpler and covers more cases than what was there before. For really trivial cases, where the query is just "SELECT expression" and nothing else, there's a hazard that adding the extra RTE makes for a noticeable slowdown; even though it's not much processing, there's not that much for the planner to do overall. However testing says that the penalty is very small, close to the noise level. In more complex queries, this is able to find optimizations that we could not find before. The new RTE type is called RTE_RESULT, since the "scan" plan type it gives rise to is a Result node (the same plan we produced for a "SELECT expression" query before). To avoid confusion, rename the old ResultPath path type to GroupResultPath, reflecting that it's only used in degenerate grouping cases where we know the query produces just one grouped row. (It wouldn't work to unify the two cases, because there are different rules about where the associated quals live during query_planner.) Note: although this touches readfuncs.c, I don't think a catversion bump is required, because the added case can't occur in stored rules, only plans. Patch by me, reviewed by David Rowley and Mark Dilger Discussion: https://postgr.es/m/15944.1521127664@sss.pgh.pa.us
This commit is contained in:
parent
5c11867512
commit
4be058fe9e
|
@ -2476,6 +2476,8 @@ JumbleRangeTable(pgssJumbleState *jstate, List *rtable)
|
|||
case RTE_NAMEDTUPLESTORE:
|
||||
APP_JUMB_STRING(rte->enrname);
|
||||
break;
|
||||
case RTE_RESULT:
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
|
||||
break;
|
||||
|
|
|
@ -5361,7 +5361,7 @@ INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass;
|
|||
QUERY PLAN
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Insert on public.ft2
|
||||
Output: (tableoid)::regclass
|
||||
Output: (ft2.tableoid)::regclass
|
||||
Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
-> Result
|
||||
Output: 1200, 999, NULL::integer, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft2 '::character(10), NULL::user_enum
|
||||
|
|
|
@ -437,9 +437,12 @@ ExecSupportsMarkRestore(Path *pathnode)
|
|||
return ExecSupportsMarkRestore(((ProjectionPath *) pathnode)->subpath);
|
||||
else if (IsA(pathnode, MinMaxAggPath))
|
||||
return false; /* childless Result */
|
||||
else if (IsA(pathnode, GroupResultPath))
|
||||
return false; /* childless Result */
|
||||
else
|
||||
{
|
||||
Assert(IsA(pathnode, ResultPath));
|
||||
/* Simple RTE_RESULT base relation */
|
||||
Assert(IsA(pathnode, Path));
|
||||
return false; /* childless Result */
|
||||
}
|
||||
|
||||
|
|
|
@ -2329,10 +2329,6 @@ range_table_walker(List *rtable,
|
|||
if (walker(rte->tablesample, context))
|
||||
return true;
|
||||
break;
|
||||
case RTE_CTE:
|
||||
case RTE_NAMEDTUPLESTORE:
|
||||
/* nothing to do */
|
||||
break;
|
||||
case RTE_SUBQUERY:
|
||||
if (!(flags & QTW_IGNORE_RT_SUBQUERIES))
|
||||
if (walker(rte->subquery, context))
|
||||
|
@ -2355,6 +2351,11 @@ range_table_walker(List *rtable,
|
|||
if (walker(rte->values_lists, context))
|
||||
return true;
|
||||
break;
|
||||
case RTE_CTE:
|
||||
case RTE_NAMEDTUPLESTORE:
|
||||
case RTE_RESULT:
|
||||
/* nothing to do */
|
||||
break;
|
||||
}
|
||||
|
||||
if (walker(rte->securityQuals, context))
|
||||
|
@ -3164,10 +3165,6 @@ range_table_mutator(List *rtable,
|
|||
TableSampleClause *);
|
||||
/* we don't bother to copy eref, aliases, etc; OK? */
|
||||
break;
|
||||
case RTE_CTE:
|
||||
case RTE_NAMEDTUPLESTORE:
|
||||
/* nothing to do */
|
||||
break;
|
||||
case RTE_SUBQUERY:
|
||||
if (!(flags & QTW_IGNORE_RT_SUBQUERIES))
|
||||
{
|
||||
|
@ -3198,6 +3195,11 @@ range_table_mutator(List *rtable,
|
|||
case RTE_VALUES:
|
||||
MUTATE(newrte->values_lists, rte->values_lists, List *);
|
||||
break;
|
||||
case RTE_CTE:
|
||||
case RTE_NAMEDTUPLESTORE:
|
||||
case RTE_RESULT:
|
||||
/* nothing to do */
|
||||
break;
|
||||
}
|
||||
MUTATE(newrte->securityQuals, rte->securityQuals, List *);
|
||||
newrt = lappend(newrt, newrte);
|
||||
|
|
|
@ -1855,9 +1855,9 @@ _outMergeAppendPath(StringInfo str, const MergeAppendPath *node)
|
|||
}
|
||||
|
||||
static void
|
||||
_outResultPath(StringInfo str, const ResultPath *node)
|
||||
_outGroupResultPath(StringInfo str, const GroupResultPath *node)
|
||||
{
|
||||
WRITE_NODE_TYPE("RESULTPATH");
|
||||
WRITE_NODE_TYPE("GROUPRESULTPATH");
|
||||
|
||||
_outPathInfo(str, (const Path *) node);
|
||||
|
||||
|
@ -2213,7 +2213,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
|
|||
WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind);
|
||||
WRITE_BOOL_FIELD(hasJoinRTEs);
|
||||
WRITE_BOOL_FIELD(hasLateralRTEs);
|
||||
WRITE_BOOL_FIELD(hasDeletedRTEs);
|
||||
WRITE_BOOL_FIELD(hasHavingQual);
|
||||
WRITE_BOOL_FIELD(hasPseudoConstantQuals);
|
||||
WRITE_BOOL_FIELD(hasRecursion);
|
||||
|
@ -3060,6 +3059,9 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
|
|||
WRITE_NODE_FIELD(coltypmods);
|
||||
WRITE_NODE_FIELD(colcollations);
|
||||
break;
|
||||
case RTE_RESULT:
|
||||
/* no extra fields */
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unrecognized RTE kind: %d", (int) node->rtekind);
|
||||
break;
|
||||
|
@ -3943,8 +3945,8 @@ outNode(StringInfo str, const void *obj)
|
|||
case T_MergeAppendPath:
|
||||
_outMergeAppendPath(str, obj);
|
||||
break;
|
||||
case T_ResultPath:
|
||||
_outResultPath(str, obj);
|
||||
case T_GroupResultPath:
|
||||
_outGroupResultPath(str, obj);
|
||||
break;
|
||||
case T_MaterialPath:
|
||||
_outMaterialPath(str, obj);
|
||||
|
|
|
@ -295,6 +295,10 @@ print_rt(const List *rtable)
|
|||
printf("%d\t%s\t[tuplestore]",
|
||||
i, rte->eref->aliasname);
|
||||
break;
|
||||
case RTE_RESULT:
|
||||
printf("%d\t%s\t[result]",
|
||||
i, rte->eref->aliasname);
|
||||
break;
|
||||
default:
|
||||
printf("%d\t%s\t[unknown rtekind]",
|
||||
i, rte->eref->aliasname);
|
||||
|
|
|
@ -1411,6 +1411,9 @@ _readRangeTblEntry(void)
|
|||
READ_NODE_FIELD(coltypmods);
|
||||
READ_NODE_FIELD(colcollations);
|
||||
break;
|
||||
case RTE_RESULT:
|
||||
/* no extra fields */
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unrecognized RTE kind: %d",
|
||||
(int) local_node->rtekind);
|
||||
|
|
|
@ -361,7 +361,16 @@ RelOptInfo - a relation or joined relations
|
|||
join clauses)
|
||||
|
||||
Path - every way to generate a RelOptInfo(sequential,index,joins)
|
||||
SeqScan - represents a sequential scan plan
|
||||
A plain Path node can represent several simple plans, per its pathtype:
|
||||
T_SeqScan - sequential scan
|
||||
T_SampleScan - tablesample scan
|
||||
T_FunctionScan - function-in-FROM scan
|
||||
T_TableFuncScan - table function scan
|
||||
T_ValuesScan - VALUES scan
|
||||
T_CteScan - CTE (WITH) scan
|
||||
T_NamedTuplestoreScan - ENR scan
|
||||
T_WorkTableScan - scan worktable of a recursive CTE
|
||||
T_Result - childless Result plan node (used for FROM-less SELECT)
|
||||
IndexPath - index scan
|
||||
BitmapHeapPath - top of a bitmapped index scan
|
||||
TidPath - scan by CTID
|
||||
|
@ -370,7 +379,7 @@ RelOptInfo - a relation or joined relations
|
|||
CustomPath - for custom scan providers
|
||||
AppendPath - append multiple subpaths together
|
||||
MergeAppendPath - merge multiple subpaths, preserving their common sort order
|
||||
ResultPath - a childless Result plan node (used for FROM-less SELECT)
|
||||
GroupResultPath - childless Result plan node (used for degenerate grouping)
|
||||
MaterialPath - a Material plan node
|
||||
UniquePath - remove duplicate rows (either by hashing or sorting)
|
||||
GatherPath - collect the results of parallel workers
|
||||
|
|
|
@ -117,6 +117,8 @@ static void set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
|||
RangeTblEntry *rte);
|
||||
static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
RangeTblEntry *rte);
|
||||
static void set_result_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
RangeTblEntry *rte);
|
||||
static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
RangeTblEntry *rte);
|
||||
static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist);
|
||||
|
@ -437,8 +439,13 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
|
|||
set_cte_pathlist(root, rel, rte);
|
||||
break;
|
||||
case RTE_NAMEDTUPLESTORE:
|
||||
/* Might as well just build the path immediately */
|
||||
set_namedtuplestore_pathlist(root, rel, rte);
|
||||
break;
|
||||
case RTE_RESULT:
|
||||
/* Might as well just build the path immediately */
|
||||
set_result_pathlist(root, rel, rte);
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
|
||||
break;
|
||||
|
@ -510,6 +517,9 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
|||
case RTE_NAMEDTUPLESTORE:
|
||||
/* tuplestore reference --- fully handled during set_rel_size */
|
||||
break;
|
||||
case RTE_RESULT:
|
||||
/* simple Result --- fully handled during set_rel_size */
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
|
||||
break;
|
||||
|
@ -712,6 +722,10 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
|
|||
* infrastructure to support that.
|
||||
*/
|
||||
return;
|
||||
|
||||
case RTE_RESULT:
|
||||
/* RESULT RTEs, in themselves, are no problem. */
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -2509,6 +2523,36 @@ set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
|||
set_cheapest(rel);
|
||||
}
|
||||
|
||||
/*
|
||||
* set_result_pathlist
|
||||
* Build the (single) access path for an RTE_RESULT RTE
|
||||
*
|
||||
* There's no need for a separate set_result_size phase, since we
|
||||
* don't support join-qual-parameterized paths for these RTEs.
|
||||
*/
|
||||
static void
|
||||
set_result_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
RangeTblEntry *rte)
|
||||
{
|
||||
Relids required_outer;
|
||||
|
||||
/* Mark rel with estimated output rows, width, etc */
|
||||
set_result_size_estimates(root, rel);
|
||||
|
||||
/*
|
||||
* We don't support pushing join clauses into the quals of a Result scan,
|
||||
* but it could still have required parameterization due to LATERAL refs
|
||||
* in its tlist.
|
||||
*/
|
||||
required_outer = rel->lateral_relids;
|
||||
|
||||
/* Generate appropriate path */
|
||||
add_path(rel, create_resultscan_path(root, rel, required_outer));
|
||||
|
||||
/* Select cheapest path (pretty easy in this case...) */
|
||||
set_cheapest(rel);
|
||||
}
|
||||
|
||||
/*
|
||||
* set_worktable_pathlist
|
||||
* Build the (single) access path for a self-reference CTE RTE
|
||||
|
@ -3677,9 +3721,6 @@ print_path(PlannerInfo *root, Path *path, int indent)
|
|||
case T_SampleScan:
|
||||
ptype = "SampleScan";
|
||||
break;
|
||||
case T_SubqueryScan:
|
||||
ptype = "SubqueryScan";
|
||||
break;
|
||||
case T_FunctionScan:
|
||||
ptype = "FunctionScan";
|
||||
break;
|
||||
|
@ -3692,6 +3733,12 @@ print_path(PlannerInfo *root, Path *path, int indent)
|
|||
case T_CteScan:
|
||||
ptype = "CteScan";
|
||||
break;
|
||||
case T_NamedTuplestoreScan:
|
||||
ptype = "NamedTuplestoreScan";
|
||||
break;
|
||||
case T_Result:
|
||||
ptype = "Result";
|
||||
break;
|
||||
case T_WorkTableScan:
|
||||
ptype = "WorkTableScan";
|
||||
break;
|
||||
|
@ -3716,7 +3763,7 @@ print_path(PlannerInfo *root, Path *path, int indent)
|
|||
ptype = "TidScan";
|
||||
break;
|
||||
case T_SubqueryScanPath:
|
||||
ptype = "SubqueryScanScan";
|
||||
ptype = "SubqueryScan";
|
||||
break;
|
||||
case T_ForeignPath:
|
||||
ptype = "ForeignScan";
|
||||
|
@ -3742,8 +3789,8 @@ print_path(PlannerInfo *root, Path *path, int indent)
|
|||
case T_MergeAppendPath:
|
||||
ptype = "MergeAppend";
|
||||
break;
|
||||
case T_ResultPath:
|
||||
ptype = "Result";
|
||||
case T_GroupResultPath:
|
||||
ptype = "GroupResult";
|
||||
break;
|
||||
case T_MaterialPath:
|
||||
ptype = "Material";
|
||||
|
|
|
@ -1570,6 +1570,40 @@ cost_namedtuplestorescan(Path *path, PlannerInfo *root,
|
|||
path->total_cost = startup_cost + run_cost;
|
||||
}
|
||||
|
||||
/*
|
||||
* cost_resultscan
|
||||
* Determines and returns the cost of scanning an RTE_RESULT relation.
|
||||
*/
|
||||
void
|
||||
cost_resultscan(Path *path, PlannerInfo *root,
|
||||
RelOptInfo *baserel, ParamPathInfo *param_info)
|
||||
{
|
||||
Cost startup_cost = 0;
|
||||
Cost run_cost = 0;
|
||||
QualCost qpqual_cost;
|
||||
Cost cpu_per_tuple;
|
||||
|
||||
/* Should only be applied to RTE_RESULT base relations */
|
||||
Assert(baserel->relid > 0);
|
||||
Assert(baserel->rtekind == RTE_RESULT);
|
||||
|
||||
/* Mark the path with the correct row estimate */
|
||||
if (param_info)
|
||||
path->rows = param_info->ppi_rows;
|
||||
else
|
||||
path->rows = baserel->rows;
|
||||
|
||||
/* We charge qual cost plus cpu_tuple_cost */
|
||||
get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
|
||||
|
||||
startup_cost += qpqual_cost.startup;
|
||||
cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
|
||||
run_cost += cpu_per_tuple * baserel->tuples;
|
||||
|
||||
path->startup_cost = startup_cost;
|
||||
path->total_cost = startup_cost + run_cost;
|
||||
}
|
||||
|
||||
/*
|
||||
* cost_recursive_union
|
||||
* Determines and returns the cost of performing a recursive union,
|
||||
|
@ -5044,6 +5078,29 @@ set_namedtuplestore_size_estimates(PlannerInfo *root, RelOptInfo *rel)
|
|||
set_baserel_size_estimates(root, rel);
|
||||
}
|
||||
|
||||
/*
|
||||
* set_result_size_estimates
|
||||
* Set the size estimates for an RTE_RESULT base relation
|
||||
*
|
||||
* The rel's targetlist and restrictinfo list must have been constructed
|
||||
* already.
|
||||
*
|
||||
* We set the same fields as set_baserel_size_estimates.
|
||||
*/
|
||||
void
|
||||
set_result_size_estimates(PlannerInfo *root, RelOptInfo *rel)
|
||||
{
|
||||
/* Should only be applied to RTE_RESULT base relations */
|
||||
Assert(rel->relid > 0);
|
||||
Assert(planner_rt_fetch(rel->relid, root)->rtekind == RTE_RESULT);
|
||||
|
||||
/* RTE_RESULT always generates a single row, natively */
|
||||
rel->tuples = 1;
|
||||
|
||||
/* Now estimate number of output rows, etc */
|
||||
set_baserel_size_estimates(root, rel);
|
||||
}
|
||||
|
||||
/*
|
||||
* set_foreign_size_estimates
|
||||
* Set the size estimates for a base relation that is a foreign table.
|
||||
|
|
|
@ -84,7 +84,8 @@ static Plan *create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
|
|||
static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path);
|
||||
static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path);
|
||||
static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path);
|
||||
static Result *create_result_plan(PlannerInfo *root, ResultPath *best_path);
|
||||
static Result *create_group_result_plan(PlannerInfo *root,
|
||||
GroupResultPath *best_path);
|
||||
static ProjectSet *create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path);
|
||||
static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path,
|
||||
int flags);
|
||||
|
@ -138,6 +139,8 @@ static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path,
|
|||
List *tlist, List *scan_clauses);
|
||||
static NamedTuplestoreScan *create_namedtuplestorescan_plan(PlannerInfo *root,
|
||||
Path *best_path, List *tlist, List *scan_clauses);
|
||||
static Result *create_resultscan_plan(PlannerInfo *root, Path *best_path,
|
||||
List *tlist, List *scan_clauses);
|
||||
static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_path,
|
||||
List *tlist, List *scan_clauses);
|
||||
static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
|
||||
|
@ -403,11 +406,16 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
|
|||
plan = (Plan *) create_minmaxagg_plan(root,
|
||||
(MinMaxAggPath *) best_path);
|
||||
}
|
||||
else if (IsA(best_path, GroupResultPath))
|
||||
{
|
||||
plan = (Plan *) create_group_result_plan(root,
|
||||
(GroupResultPath *) best_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert(IsA(best_path, ResultPath));
|
||||
plan = (Plan *) create_result_plan(root,
|
||||
(ResultPath *) best_path);
|
||||
/* Simple RTE_RESULT base relation */
|
||||
Assert(IsA(best_path, Path));
|
||||
plan = create_scan_plan(root, best_path, flags);
|
||||
}
|
||||
break;
|
||||
case T_ProjectSet:
|
||||
|
@ -691,6 +699,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
|
|||
scan_clauses);
|
||||
break;
|
||||
|
||||
case T_Result:
|
||||
plan = (Plan *) create_resultscan_plan(root,
|
||||
best_path,
|
||||
tlist,
|
||||
scan_clauses);
|
||||
break;
|
||||
|
||||
case T_WorkTableScan:
|
||||
plan = (Plan *) create_worktablescan_plan(root,
|
||||
best_path,
|
||||
|
@ -922,9 +937,26 @@ create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
|
|||
List *gating_quals)
|
||||
{
|
||||
Plan *gplan;
|
||||
Plan *splan;
|
||||
|
||||
Assert(gating_quals);
|
||||
|
||||
/*
|
||||
* We might have a trivial Result plan already. Stacking one Result atop
|
||||
* another is silly, so if that applies, just discard the input plan.
|
||||
* (We're assuming its targetlist is uninteresting; it should be either
|
||||
* the same as the result of build_path_tlist, or a simplified version.)
|
||||
*/
|
||||
splan = plan;
|
||||
if (IsA(plan, Result))
|
||||
{
|
||||
Result *rplan = (Result *) plan;
|
||||
|
||||
if (rplan->plan.lefttree == NULL &&
|
||||
rplan->resconstantqual == NULL)
|
||||
splan = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Since we need a Result node anyway, always return the path's requested
|
||||
* tlist; that's never a wrong choice, even if the parent node didn't ask
|
||||
|
@ -932,7 +964,7 @@ create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
|
|||
*/
|
||||
gplan = (Plan *) make_result(build_path_tlist(root, path),
|
||||
(Node *) gating_quals,
|
||||
plan);
|
||||
splan);
|
||||
|
||||
/*
|
||||
* Notice that we don't change cost or size estimates when doing gating.
|
||||
|
@ -1254,15 +1286,14 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path)
|
|||
}
|
||||
|
||||
/*
|
||||
* create_result_plan
|
||||
* create_group_result_plan
|
||||
* Create a Result plan for 'best_path'.
|
||||
* This is only used for degenerate cases, such as a query with an empty
|
||||
* jointree.
|
||||
* This is only used for degenerate grouping cases.
|
||||
*
|
||||
* Returns a Plan node.
|
||||
*/
|
||||
static Result *
|
||||
create_result_plan(PlannerInfo *root, ResultPath *best_path)
|
||||
create_group_result_plan(PlannerInfo *root, GroupResultPath *best_path)
|
||||
{
|
||||
Result *plan;
|
||||
List *tlist;
|
||||
|
@ -3477,6 +3508,44 @@ create_namedtuplestorescan_plan(PlannerInfo *root, Path *best_path,
|
|||
return scan_plan;
|
||||
}
|
||||
|
||||
/*
|
||||
* create_resultscan_plan
|
||||
* Returns a Result plan for the RTE_RESULT base relation scanned by
|
||||
* 'best_path' with restriction clauses 'scan_clauses' and targetlist
|
||||
* 'tlist'.
|
||||
*/
|
||||
static Result *
|
||||
create_resultscan_plan(PlannerInfo *root, Path *best_path,
|
||||
List *tlist, List *scan_clauses)
|
||||
{
|
||||
Result *scan_plan;
|
||||
Index scan_relid = best_path->parent->relid;
|
||||
RangeTblEntry *rte PG_USED_FOR_ASSERTS_ONLY;
|
||||
|
||||
Assert(scan_relid > 0);
|
||||
rte = planner_rt_fetch(scan_relid, root);
|
||||
Assert(rte->rtekind == RTE_RESULT);
|
||||
|
||||
/* Sort clauses into best execution order */
|
||||
scan_clauses = order_qual_clauses(root, scan_clauses);
|
||||
|
||||
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
|
||||
scan_clauses = extract_actual_clauses(scan_clauses, false);
|
||||
|
||||
/* Replace any outer-relation variables with nestloop params */
|
||||
if (best_path->param_info)
|
||||
{
|
||||
scan_clauses = (List *)
|
||||
replace_nestloop_params(root, (Node *) scan_clauses);
|
||||
}
|
||||
|
||||
scan_plan = make_result(tlist, (Node *) scan_clauses, NULL);
|
||||
|
||||
copy_generic_path_info(&scan_plan->plan, best_path);
|
||||
|
||||
return scan_plan;
|
||||
}
|
||||
|
||||
/*
|
||||
* create_worktablescan_plan
|
||||
* Returns a worktablescan plan for the base relation scanned by 'best_path'
|
||||
|
|
|
@ -827,7 +827,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
|
|||
* all below it, so we should report inner_join_rels = qualscope. If
|
||||
* there was exactly one element, we should (and already did) report
|
||||
* whatever its inner_join_rels were. If there were no elements (is
|
||||
* that possible?) the initialization before the loop fixed it.
|
||||
* that still possible?) the initialization before the loop fixed it.
|
||||
*/
|
||||
if (list_length(f->fromlist) > 1)
|
||||
*inner_join_rels = *qualscope;
|
||||
|
|
|
@ -60,44 +60,6 @@ query_planner(PlannerInfo *root, List *tlist,
|
|||
List *joinlist;
|
||||
RelOptInfo *final_rel;
|
||||
|
||||
/*
|
||||
* If the query has an empty join tree, then it's something easy like
|
||||
* "SELECT 2+2;" or "INSERT ... VALUES()". Fall through quickly.
|
||||
*/
|
||||
if (parse->jointree->fromlist == NIL)
|
||||
{
|
||||
/* We need a dummy joinrel to describe the empty set of baserels */
|
||||
final_rel = build_empty_join_rel(root);
|
||||
|
||||
/*
|
||||
* If query allows parallelism in general, check whether the quals are
|
||||
* parallel-restricted. (We need not check final_rel->reltarget
|
||||
* because it's empty at this point. Anything parallel-restricted in
|
||||
* the query tlist will be dealt with later.)
|
||||
*/
|
||||
if (root->glob->parallelModeOK)
|
||||
final_rel->consider_parallel =
|
||||
is_parallel_safe(root, parse->jointree->quals);
|
||||
|
||||
/* The only path for it is a trivial Result path */
|
||||
add_path(final_rel, (Path *)
|
||||
create_result_path(root, final_rel,
|
||||
final_rel->reltarget,
|
||||
(List *) parse->jointree->quals));
|
||||
|
||||
/* Select cheapest path (pretty easy in this case...) */
|
||||
set_cheapest(final_rel);
|
||||
|
||||
/*
|
||||
* We still are required to call qp_callback, in case it's something
|
||||
* like "SELECT 2+2 ORDER BY 1".
|
||||
*/
|
||||
root->canon_pathkeys = NIL;
|
||||
(*qp_callback) (root, qp_extra);
|
||||
|
||||
return final_rel;
|
||||
}
|
||||
|
||||
/*
|
||||
* Init planner lists to empty.
|
||||
*
|
||||
|
@ -124,6 +86,71 @@ query_planner(PlannerInfo *root, List *tlist,
|
|||
*/
|
||||
setup_simple_rel_arrays(root);
|
||||
|
||||
/*
|
||||
* In the trivial case where the jointree is a single RTE_RESULT relation,
|
||||
* bypass all the rest of this function and just make a RelOptInfo and its
|
||||
* one access path. This is worth optimizing because it applies for
|
||||
* common cases like "SELECT expression" and "INSERT ... VALUES()".
|
||||
*/
|
||||
Assert(parse->jointree->fromlist != NIL);
|
||||
if (list_length(parse->jointree->fromlist) == 1)
|
||||
{
|
||||
Node *jtnode = (Node *) linitial(parse->jointree->fromlist);
|
||||
|
||||
if (IsA(jtnode, RangeTblRef))
|
||||
{
|
||||
int varno = ((RangeTblRef *) jtnode)->rtindex;
|
||||
RangeTblEntry *rte = root->simple_rte_array[varno];
|
||||
|
||||
Assert(rte != NULL);
|
||||
if (rte->rtekind == RTE_RESULT)
|
||||
{
|
||||
/* Make the RelOptInfo for it directly */
|
||||
final_rel = build_simple_rel(root, varno, NULL);
|
||||
|
||||
/*
|
||||
* If query allows parallelism in general, check whether the
|
||||
* quals are parallel-restricted. (We need not check
|
||||
* final_rel->reltarget because it's empty at this point.
|
||||
* Anything parallel-restricted in the query tlist will be
|
||||
* dealt with later.) This is normally pretty silly, because
|
||||
* a Result-only plan would never be interesting to
|
||||
* parallelize. However, if force_parallel_mode is on, then
|
||||
* we want to execute the Result in a parallel worker if
|
||||
* possible, so we must do this.
|
||||
*/
|
||||
if (root->glob->parallelModeOK &&
|
||||
force_parallel_mode != FORCE_PARALLEL_OFF)
|
||||
final_rel->consider_parallel =
|
||||
is_parallel_safe(root, parse->jointree->quals);
|
||||
|
||||
/*
|
||||
* The only path for it is a trivial Result path. We cheat a
|
||||
* bit here by using a GroupResultPath, because that way we
|
||||
* can just jam the quals into it without preprocessing them.
|
||||
* (But, if you hold your head at the right angle, a FROM-less
|
||||
* SELECT is a kind of degenerate-grouping case, so it's not
|
||||
* that much of a cheat.)
|
||||
*/
|
||||
add_path(final_rel, (Path *)
|
||||
create_group_result_path(root, final_rel,
|
||||
final_rel->reltarget,
|
||||
(List *) parse->jointree->quals));
|
||||
|
||||
/* Select cheapest path (pretty easy in this case...) */
|
||||
set_cheapest(final_rel);
|
||||
|
||||
/*
|
||||
* We still are required to call qp_callback, in case it's
|
||||
* something like "SELECT 2+2 ORDER BY 1".
|
||||
*/
|
||||
(*qp_callback) (root, qp_extra);
|
||||
|
||||
return final_rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Populate append_rel_array with each AppendRelInfo to allow direct
|
||||
* lookups by child relid.
|
||||
|
|
|
@ -611,6 +611,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
|
|||
List *newWithCheckOptions;
|
||||
List *newHaving;
|
||||
bool hasOuterJoins;
|
||||
bool hasResultRTEs;
|
||||
RelOptInfo *final_rel;
|
||||
ListCell *l;
|
||||
|
||||
|
@ -651,6 +652,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
|
|||
if (parse->cteList)
|
||||
SS_process_ctes(root);
|
||||
|
||||
/*
|
||||
* If the FROM clause is empty, replace it with a dummy RTE_RESULT RTE, so
|
||||
* that we don't need so many special cases to deal with that situation.
|
||||
*/
|
||||
replace_empty_jointree(parse);
|
||||
|
||||
/*
|
||||
* Look for ANY and EXISTS SubLinks in WHERE and JOIN/ON clauses, and try
|
||||
* to transform them into joins. Note that this step does not descend
|
||||
|
@ -684,14 +691,16 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
|
|||
|
||||
/*
|
||||
* Detect whether any rangetable entries are RTE_JOIN kind; if not, we can
|
||||
* avoid the expense of doing flatten_join_alias_vars(). Also check for
|
||||
* outer joins --- if none, we can skip reduce_outer_joins(). And check
|
||||
* for LATERAL RTEs, too. This must be done after we have done
|
||||
* pull_up_subqueries(), of course.
|
||||
* avoid the expense of doing flatten_join_alias_vars(). Likewise check
|
||||
* whether any are RTE_RESULT kind; if not, we can skip
|
||||
* remove_useless_result_rtes(). Also check for outer joins --- if none,
|
||||
* we can skip reduce_outer_joins(). And check for LATERAL RTEs, too.
|
||||
* This must be done after we have done pull_up_subqueries(), of course.
|
||||
*/
|
||||
root->hasJoinRTEs = false;
|
||||
root->hasLateralRTEs = false;
|
||||
hasOuterJoins = false;
|
||||
hasResultRTEs = false;
|
||||
foreach(l, parse->rtable)
|
||||
{
|
||||
RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
|
||||
|
@ -702,6 +711,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
|
|||
if (IS_OUTER_JOIN(rte->jointype))
|
||||
hasOuterJoins = true;
|
||||
}
|
||||
else if (rte->rtekind == RTE_RESULT)
|
||||
{
|
||||
hasResultRTEs = true;
|
||||
}
|
||||
if (rte->lateral)
|
||||
root->hasLateralRTEs = true;
|
||||
}
|
||||
|
@ -717,10 +730,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
|
|||
/*
|
||||
* Expand any rangetable entries that are inheritance sets into "append
|
||||
* relations". This can add entries to the rangetable, but they must be
|
||||
* plain base relations not joins, so it's OK (and marginally more
|
||||
* efficient) to do it after checking for join RTEs. We must do it after
|
||||
* pulling up subqueries, else we'd fail to handle inherited tables in
|
||||
* subqueries.
|
||||
* plain RTE_RELATION entries, so it's OK (and marginally more efficient)
|
||||
* to do it after checking for joins and other special RTEs. We must do
|
||||
* this after pulling up subqueries, else we'd fail to handle inherited
|
||||
* tables in subqueries.
|
||||
*/
|
||||
expand_inherited_tables(root);
|
||||
|
||||
|
@ -967,6 +980,14 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
|
|||
if (hasOuterJoins)
|
||||
reduce_outer_joins(root);
|
||||
|
||||
/*
|
||||
* If we have any RTE_RESULT relations, see if they can be deleted from
|
||||
* the jointree. This step is most effectively done after we've done
|
||||
* expression preprocessing and outer join reduction.
|
||||
*/
|
||||
if (hasResultRTEs)
|
||||
remove_useless_result_rtes(root);
|
||||
|
||||
/*
|
||||
* Do the main planning. If we have an inherited target relation, that
|
||||
* needs special processing, else go straight to grouping_planner.
|
||||
|
@ -3894,9 +3915,9 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
|
|||
while (--nrows >= 0)
|
||||
{
|
||||
path = (Path *)
|
||||
create_result_path(root, grouped_rel,
|
||||
grouped_rel->reltarget,
|
||||
(List *) parse->havingQual);
|
||||
create_group_result_path(root, grouped_rel,
|
||||
grouped_rel->reltarget,
|
||||
(List *) parse->havingQual);
|
||||
paths = lappend(paths, path);
|
||||
}
|
||||
path = (Path *)
|
||||
|
@ -3914,9 +3935,9 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
|
|||
{
|
||||
/* No grouping sets, or just one, so one output row */
|
||||
path = (Path *)
|
||||
create_result_path(root, grouped_rel,
|
||||
grouped_rel->reltarget,
|
||||
(List *) parse->havingQual);
|
||||
create_group_result_path(root, grouped_rel,
|
||||
grouped_rel->reltarget,
|
||||
(List *) parse->havingQual);
|
||||
}
|
||||
|
||||
add_path(grouped_rel, path);
|
||||
|
|
|
@ -1114,12 +1114,6 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
|
|||
if (!simplify_EXISTS_query(root, subselect))
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* The subquery must have a nonempty jointree, else we won't have a join.
|
||||
*/
|
||||
if (subselect->jointree->fromlist == NIL)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Separate out the WHERE clause. (We could theoretically also remove
|
||||
* top-level plain JOIN/ON clauses, but it's probably not worth the
|
||||
|
@ -1148,6 +1142,11 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
|
|||
if (contain_volatile_functions(whereClause))
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* The subquery must have a nonempty jointree, but we can make it so.
|
||||
*/
|
||||
replace_empty_jointree(subselect);
|
||||
|
||||
/*
|
||||
* Prepare to pull up the sub-select into top range table.
|
||||
*
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1707,7 +1707,7 @@ contain_leaked_vars_walker(Node *node, void *context)
|
|||
* find_nonnullable_vars() is that the tested conditions really are different:
|
||||
* a clause like "t1.v1 IS NOT NULL OR t1.v2 IS NOT NULL" does not prove
|
||||
* that either v1 or v2 can't be NULL, but it does prove that the t1 row
|
||||
* as a whole can't be all-NULL.
|
||||
* as a whole can't be all-NULL. Also, the behavior for PHVs is different.
|
||||
*
|
||||
* top_level is true while scanning top-level AND/OR structure; here, showing
|
||||
* the result is either FALSE or NULL is good enough. top_level is false when
|
||||
|
@ -1893,7 +1893,24 @@ find_nonnullable_rels_walker(Node *node, bool top_level)
|
|||
{
|
||||
PlaceHolderVar *phv = (PlaceHolderVar *) node;
|
||||
|
||||
/*
|
||||
* If the contained expression forces any rels non-nullable, so does
|
||||
* the PHV.
|
||||
*/
|
||||
result = find_nonnullable_rels_walker((Node *) phv->phexpr, top_level);
|
||||
|
||||
/*
|
||||
* If the PHV's syntactic scope is exactly one rel, it will be forced
|
||||
* to be evaluated at that rel, and so it will behave like a Var of
|
||||
* that rel: if the rel's entire output goes to null, so will the PHV.
|
||||
* (If the syntactic scope is a join, we know that the PHV will go to
|
||||
* null if the whole join does; but that is AND semantics while we
|
||||
* need OR semantics for find_nonnullable_rels' result, so we can't do
|
||||
* anything with the knowledge.)
|
||||
*/
|
||||
if (phv->phlevelsup == 0 &&
|
||||
bms_membership(phv->phrels) == BMS_SINGLETON)
|
||||
result = bms_add_members(result, phv->phrels);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1430,17 +1430,17 @@ create_merge_append_path(PlannerInfo *root,
|
|||
}
|
||||
|
||||
/*
|
||||
* create_result_path
|
||||
* create_group_result_path
|
||||
* Creates a path representing a Result-and-nothing-else plan.
|
||||
*
|
||||
* This is only used for degenerate cases, such as a query with an empty
|
||||
* jointree.
|
||||
* This is only used for degenerate grouping cases, in which we know we
|
||||
* need to produce one result row, possibly filtered by a HAVING qual.
|
||||
*/
|
||||
ResultPath *
|
||||
create_result_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
PathTarget *target, List *resconstantqual)
|
||||
GroupResultPath *
|
||||
create_group_result_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
PathTarget *target, List *havingqual)
|
||||
{
|
||||
ResultPath *pathnode = makeNode(ResultPath);
|
||||
GroupResultPath *pathnode = makeNode(GroupResultPath);
|
||||
|
||||
pathnode->path.pathtype = T_Result;
|
||||
pathnode->path.parent = rel;
|
||||
|
@ -1450,9 +1450,13 @@ create_result_path(PlannerInfo *root, RelOptInfo *rel,
|
|||
pathnode->path.parallel_safe = rel->consider_parallel;
|
||||
pathnode->path.parallel_workers = 0;
|
||||
pathnode->path.pathkeys = NIL;
|
||||
pathnode->quals = resconstantqual;
|
||||
pathnode->quals = havingqual;
|
||||
|
||||
/* Hardly worth defining a cost_result() function ... just do it */
|
||||
/*
|
||||
* We can't quite use cost_resultscan() because the quals we want to
|
||||
* account for are not baserestrict quals of the rel. Might as well just
|
||||
* hack it here.
|
||||
*/
|
||||
pathnode->path.rows = 1;
|
||||
pathnode->path.startup_cost = target->cost.startup;
|
||||
pathnode->path.total_cost = target->cost.startup +
|
||||
|
@ -1462,12 +1466,12 @@ create_result_path(PlannerInfo *root, RelOptInfo *rel,
|
|||
* Add cost of qual, if any --- but we ignore its selectivity, since our
|
||||
* rowcount estimate should be 1 no matter what the qual is.
|
||||
*/
|
||||
if (resconstantqual)
|
||||
if (havingqual)
|
||||
{
|
||||
QualCost qual_cost;
|
||||
|
||||
cost_qual_eval(&qual_cost, resconstantqual, root);
|
||||
/* resconstantqual is evaluated once at startup */
|
||||
cost_qual_eval(&qual_cost, havingqual, root);
|
||||
/* havingqual is evaluated once at startup */
|
||||
pathnode->path.startup_cost += qual_cost.startup + qual_cost.per_tuple;
|
||||
pathnode->path.total_cost += qual_cost.startup + qual_cost.per_tuple;
|
||||
}
|
||||
|
@ -2020,6 +2024,32 @@ create_namedtuplestorescan_path(PlannerInfo *root, RelOptInfo *rel,
|
|||
return pathnode;
|
||||
}
|
||||
|
||||
/*
|
||||
* create_resultscan_path
|
||||
* Creates a path corresponding to a scan of an RTE_RESULT relation,
|
||||
* returning the pathnode.
|
||||
*/
|
||||
Path *
|
||||
create_resultscan_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
Relids required_outer)
|
||||
{
|
||||
Path *pathnode = makeNode(Path);
|
||||
|
||||
pathnode->pathtype = T_Result;
|
||||
pathnode->parent = rel;
|
||||
pathnode->pathtarget = rel->reltarget;
|
||||
pathnode->param_info = get_baserel_parampathinfo(root, rel,
|
||||
required_outer);
|
||||
pathnode->parallel_aware = false;
|
||||
pathnode->parallel_safe = rel->consider_parallel;
|
||||
pathnode->parallel_workers = 0;
|
||||
pathnode->pathkeys = NIL; /* result is always unordered */
|
||||
|
||||
cost_resultscan(pathnode, root, rel, pathnode->param_info);
|
||||
|
||||
return pathnode;
|
||||
}
|
||||
|
||||
/*
|
||||
* create_worktablescan_path
|
||||
* Creates a path corresponding to a scan of a self-reference CTE,
|
||||
|
@ -3560,6 +3590,11 @@ reparameterize_path(PlannerInfo *root, Path *path,
|
|||
spath->path.pathkeys,
|
||||
required_outer);
|
||||
}
|
||||
case T_Result:
|
||||
/* Supported only for RTE_RESULT scan paths */
|
||||
if (IsA(path, Path))
|
||||
return create_resultscan_path(root, rel, required_outer);
|
||||
break;
|
||||
case T_Append:
|
||||
{
|
||||
AppendPath *apath = (AppendPath *) path;
|
||||
|
|
|
@ -1628,6 +1628,7 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
|
|||
case RTE_VALUES:
|
||||
case RTE_CTE:
|
||||
case RTE_NAMEDTUPLESTORE:
|
||||
case RTE_RESULT:
|
||||
/* Not all of these can have dropped cols, but share code anyway */
|
||||
expandRTE(rte, varno, 0, -1, true /* include dropped */ ,
|
||||
NULL, &colvars);
|
||||
|
|
|
@ -194,7 +194,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
|
|||
rel->baserestrict_min_security = UINT_MAX;
|
||||
rel->joininfo = NIL;
|
||||
rel->has_eclass_joins = false;
|
||||
rel->consider_partitionwise_join = false; /* might get changed later */
|
||||
rel->consider_partitionwise_join = false; /* might get changed later */
|
||||
rel->part_scheme = NULL;
|
||||
rel->nparts = 0;
|
||||
rel->boundinfo = NULL;
|
||||
|
@ -247,6 +247,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
|
|||
rel->attr_widths = (int32 *)
|
||||
palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(int32));
|
||||
break;
|
||||
case RTE_RESULT:
|
||||
/* RTE_RESULT has no columns, nor could it have whole-row Var */
|
||||
rel->min_attr = 0;
|
||||
rel->max_attr = -1;
|
||||
rel->attr_needed = NULL;
|
||||
rel->attr_widths = NULL;
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unrecognized RTE kind: %d",
|
||||
(int) rte->rtekind);
|
||||
|
@ -609,7 +616,7 @@ build_join_rel(PlannerInfo *root,
|
|||
joinrel->baserestrict_min_security = UINT_MAX;
|
||||
joinrel->joininfo = NIL;
|
||||
joinrel->has_eclass_joins = false;
|
||||
joinrel->consider_partitionwise_join = false; /* might get changed later */
|
||||
joinrel->consider_partitionwise_join = false; /* might get changed later */
|
||||
joinrel->top_parent_relids = NULL;
|
||||
joinrel->part_scheme = NULL;
|
||||
joinrel->nparts = 0;
|
||||
|
@ -784,7 +791,7 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
|
|||
joinrel->baserestrictcost.per_tuple = 0;
|
||||
joinrel->joininfo = NIL;
|
||||
joinrel->has_eclass_joins = false;
|
||||
joinrel->consider_partitionwise_join = false; /* might get changed later */
|
||||
joinrel->consider_partitionwise_join = false; /* might get changed later */
|
||||
joinrel->top_parent_relids = NULL;
|
||||
joinrel->part_scheme = NULL;
|
||||
joinrel->nparts = 0;
|
||||
|
@ -1108,36 +1115,6 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel,
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* build_empty_join_rel
|
||||
* Build a dummy join relation describing an empty set of base rels.
|
||||
*
|
||||
* This is used for queries with empty FROM clauses, such as "SELECT 2+2" or
|
||||
* "INSERT INTO foo VALUES(...)". We don't try very hard to make the empty
|
||||
* joinrel completely valid, since no real planning will be done with it ---
|
||||
* we just need it to carry a simple Result path out of query_planner().
|
||||
*/
|
||||
RelOptInfo *
|
||||
build_empty_join_rel(PlannerInfo *root)
|
||||
{
|
||||
RelOptInfo *joinrel;
|
||||
|
||||
/* The dummy join relation should be the only one ... */
|
||||
Assert(root->join_rel_list == NIL);
|
||||
|
||||
joinrel = makeNode(RelOptInfo);
|
||||
joinrel->reloptkind = RELOPT_JOINREL;
|
||||
joinrel->relids = NULL; /* empty set */
|
||||
joinrel->rows = 1; /* we produce one row for such cases */
|
||||
joinrel->rtekind = RTE_JOIN;
|
||||
joinrel->reltarget = create_empty_pathtarget();
|
||||
|
||||
root->join_rel_list = lappend(root->join_rel_list, joinrel);
|
||||
|
||||
return joinrel;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* fetch_upper_rel
|
||||
* Build a RelOptInfo describing some post-scan/join query processing,
|
||||
|
|
|
@ -2884,6 +2884,9 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
|
|||
LCS_asString(lc->strength)),
|
||||
parser_errposition(pstate, thisrel->location)));
|
||||
break;
|
||||
|
||||
/* Shouldn't be possible to see RTE_RESULT here */
|
||||
|
||||
default:
|
||||
elog(ERROR, "unrecognized RTE type: %d",
|
||||
(int) rte->rtekind);
|
||||
|
|
|
@ -2519,6 +2519,9 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
|
|||
}
|
||||
}
|
||||
break;
|
||||
case RTE_RESULT:
|
||||
/* These expose no columns, so nothing to do */
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
|
||||
}
|
||||
|
@ -2911,6 +2914,14 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
|
|||
rte->eref->aliasname)));
|
||||
}
|
||||
break;
|
||||
case RTE_RESULT:
|
||||
/* this probably can't happen ... */
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||
errmsg("column %d of relation \"%s\" does not exist",
|
||||
attnum,
|
||||
rte->eref->aliasname)));
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
|
||||
}
|
||||
|
@ -3039,6 +3050,15 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
|
|||
result = false; /* keep compiler quiet */
|
||||
}
|
||||
break;
|
||||
case RTE_RESULT:
|
||||
/* this probably can't happen ... */
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||
errmsg("column %d of relation \"%s\" does not exist",
|
||||
attnum,
|
||||
rte->eref->aliasname)));
|
||||
result = false; /* keep compiler quiet */
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
|
||||
result = false; /* keep compiler quiet */
|
||||
|
|
|
@ -398,6 +398,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
|
|||
case RTE_VALUES:
|
||||
case RTE_TABLEFUNC:
|
||||
case RTE_NAMEDTUPLESTORE:
|
||||
case RTE_RESULT:
|
||||
/* not a simple relation, leave it unmarked */
|
||||
break;
|
||||
case RTE_CTE:
|
||||
|
@ -1531,6 +1532,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
|
|||
case RTE_RELATION:
|
||||
case RTE_VALUES:
|
||||
case RTE_NAMEDTUPLESTORE:
|
||||
case RTE_RESULT:
|
||||
|
||||
/*
|
||||
* This case should not occur: a column of a table, values list,
|
||||
|
|
|
@ -7011,6 +7011,7 @@ get_name_for_var_field(Var *var, int fieldno,
|
|||
case RTE_RELATION:
|
||||
case RTE_VALUES:
|
||||
case RTE_NAMEDTUPLESTORE:
|
||||
case RTE_RESULT:
|
||||
|
||||
/*
|
||||
* This case should not occur: a column of a table, values list,
|
||||
|
|
|
@ -237,7 +237,7 @@ typedef enum NodeTag
|
|||
T_HashPath,
|
||||
T_AppendPath,
|
||||
T_MergeAppendPath,
|
||||
T_ResultPath,
|
||||
T_GroupResultPath,
|
||||
T_MaterialPath,
|
||||
T_UniquePath,
|
||||
T_GatherPath,
|
||||
|
|
|
@ -950,7 +950,10 @@ typedef enum RTEKind
|
|||
RTE_TABLEFUNC, /* TableFunc(.., column list) */
|
||||
RTE_VALUES, /* VALUES (<exprlist>), (<exprlist>), ... */
|
||||
RTE_CTE, /* common table expr (WITH list element) */
|
||||
RTE_NAMEDTUPLESTORE /* tuplestore, e.g. for AFTER triggers */
|
||||
RTE_NAMEDTUPLESTORE, /* tuplestore, e.g. for AFTER triggers */
|
||||
RTE_RESULT /* RTE represents an empty FROM clause; such
|
||||
* RTEs are added by the planner, they're not
|
||||
* present during parsing or rewriting */
|
||||
} RTEKind;
|
||||
|
||||
typedef struct RangeTblEntry
|
||||
|
|
|
@ -324,7 +324,6 @@ typedef struct PlannerInfo
|
|||
* partitioned table */
|
||||
bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */
|
||||
bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */
|
||||
bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */
|
||||
bool hasHavingQual; /* true if havingQual was non-null */
|
||||
bool hasPseudoConstantQuals; /* true if any RestrictInfo has
|
||||
* pseudoconstant = true */
|
||||
|
@ -1345,17 +1344,17 @@ typedef struct MergeAppendPath
|
|||
} MergeAppendPath;
|
||||
|
||||
/*
|
||||
* ResultPath represents use of a Result plan node to compute a variable-free
|
||||
* targetlist with no underlying tables (a "SELECT expressions" query).
|
||||
* The query could have a WHERE clause, too, represented by "quals".
|
||||
* GroupResultPath represents use of a Result plan node to compute the
|
||||
* output of a degenerate GROUP BY case, wherein we know we should produce
|
||||
* exactly one row, which might then be filtered by a HAVING qual.
|
||||
*
|
||||
* Note that quals is a list of bare clauses, not RestrictInfos.
|
||||
*/
|
||||
typedef struct ResultPath
|
||||
typedef struct GroupResultPath
|
||||
{
|
||||
Path path;
|
||||
List *quals;
|
||||
} ResultPath;
|
||||
} GroupResultPath;
|
||||
|
||||
/*
|
||||
* MaterialPath represents use of a Material plan node, i.e., caching of
|
||||
|
|
|
@ -105,6 +105,8 @@ extern void cost_ctescan(Path *path, PlannerInfo *root,
|
|||
RelOptInfo *baserel, ParamPathInfo *param_info);
|
||||
extern void cost_namedtuplestorescan(Path *path, PlannerInfo *root,
|
||||
RelOptInfo *baserel, ParamPathInfo *param_info);
|
||||
extern void cost_resultscan(Path *path, PlannerInfo *root,
|
||||
RelOptInfo *baserel, ParamPathInfo *param_info);
|
||||
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,
|
||||
|
@ -196,6 +198,7 @@ extern void set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel,
|
|||
double cte_rows);
|
||||
extern void set_tablefunc_size_estimates(PlannerInfo *root, RelOptInfo *rel);
|
||||
extern void set_namedtuplestore_size_estimates(PlannerInfo *root, RelOptInfo *rel);
|
||||
extern void set_result_size_estimates(PlannerInfo *root, RelOptInfo *rel);
|
||||
extern void set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel);
|
||||
extern PathTarget *set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target);
|
||||
extern double compute_bitmap_pages(PlannerInfo *root, RelOptInfo *baserel,
|
||||
|
|
|
@ -75,8 +75,10 @@ extern MergeAppendPath *create_merge_append_path(PlannerInfo *root,
|
|||
List *pathkeys,
|
||||
Relids required_outer,
|
||||
List *partitioned_rels);
|
||||
extern ResultPath *create_result_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
PathTarget *target, List *resconstantqual);
|
||||
extern GroupResultPath *create_group_result_path(PlannerInfo *root,
|
||||
RelOptInfo *rel,
|
||||
PathTarget *target,
|
||||
List *havingqual);
|
||||
extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath);
|
||||
extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
Path *subpath, SpecialJoinInfo *sjinfo);
|
||||
|
@ -105,6 +107,8 @@ extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel,
|
|||
Relids required_outer);
|
||||
extern Path *create_namedtuplestorescan_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
Relids required_outer);
|
||||
extern Path *create_resultscan_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
Relids required_outer);
|
||||
extern Path *create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
Relids required_outer);
|
||||
extern ForeignPath *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
|
||||
|
@ -275,7 +279,6 @@ extern Relids min_join_parameterization(PlannerInfo *root,
|
|||
Relids joinrelids,
|
||||
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 Relids find_childrel_parents(PlannerInfo *root, RelOptInfo *rel);
|
||||
|
|
|
@ -21,11 +21,13 @@
|
|||
/*
|
||||
* prototypes for prepjointree.c
|
||||
*/
|
||||
extern void replace_empty_jointree(Query *parse);
|
||||
extern void pull_up_sublinks(PlannerInfo *root);
|
||||
extern void inline_set_returning_functions(PlannerInfo *root);
|
||||
extern void pull_up_subqueries(PlannerInfo *root);
|
||||
extern void flatten_simple_union_all(PlannerInfo *root);
|
||||
extern void reduce_outer_joins(PlannerInfo *root);
|
||||
extern void remove_useless_result_rtes(PlannerInfo *root);
|
||||
extern Relids get_relids_in_jointree(Node *jtnode, bool include_joins);
|
||||
extern Relids get_relids_for_join(PlannerInfo *root, int joinrelid);
|
||||
|
||||
|
|
|
@ -239,9 +239,9 @@ id value
|
|||
starting permutation: wrjt selectjoinforupdate c2 c1
|
||||
step wrjt: UPDATE jointest SET data = 42 WHERE id = 7;
|
||||
step selectjoinforupdate:
|
||||
set enable_nestloop to 0;
|
||||
set enable_hashjoin to 0;
|
||||
set enable_seqscan to 0;
|
||||
set local enable_nestloop to 0;
|
||||
set local enable_hashjoin to 0;
|
||||
set local enable_seqscan to 0;
|
||||
explain (costs off)
|
||||
select * from jointest a join jointest b on a.id=b.id for update;
|
||||
select * from jointest a join jointest b on a.id=b.id for update;
|
||||
|
@ -269,6 +269,45 @@ id data id data
|
|||
10 0 10 0
|
||||
step c1: COMMIT;
|
||||
|
||||
starting permutation: wrjt selectresultforupdate c2 c1
|
||||
step wrjt: UPDATE jointest SET data = 42 WHERE id = 7;
|
||||
step selectresultforupdate:
|
||||
select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
|
||||
left join table_a a on a.id = x, jointest jt
|
||||
where jt.id = y;
|
||||
explain (verbose, costs off)
|
||||
select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
|
||||
left join table_a a on a.id = x, jointest jt
|
||||
where jt.id = y for update of jt, ss1, ss2;
|
||||
select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
|
||||
left join table_a a on a.id = x, jointest jt
|
||||
where jt.id = y for update of jt, ss1, ss2;
|
||||
<waiting ...>
|
||||
step c2: COMMIT;
|
||||
step selectresultforupdate: <... completed>
|
||||
x y id value id data
|
||||
|
||||
1 7 1 tableAValue 7 0
|
||||
QUERY PLAN
|
||||
|
||||
LockRows
|
||||
Output: 1, 7, a.id, a.value, jt.id, jt.data, jt.ctid, a.ctid
|
||||
-> Nested Loop Left Join
|
||||
Output: 1, 7, a.id, a.value, jt.id, jt.data, jt.ctid, a.ctid
|
||||
-> Nested Loop
|
||||
Output: jt.id, jt.data, jt.ctid
|
||||
-> Seq Scan on public.jointest jt
|
||||
Output: jt.id, jt.data, jt.ctid
|
||||
Filter: (jt.id = 7)
|
||||
-> Result
|
||||
-> Seq Scan on public.table_a a
|
||||
Output: a.id, a.value, a.ctid
|
||||
Filter: (a.id = 1)
|
||||
x y id value id data
|
||||
|
||||
1 7 1 tableAValue 7 42
|
||||
step c1: COMMIT;
|
||||
|
||||
starting permutation: wrtwcte multireadwcte c1 c2
|
||||
step wrtwcte: UPDATE table_a SET value = 'tableAValue2' WHERE id = 1;
|
||||
step multireadwcte:
|
||||
|
|
|
@ -102,14 +102,29 @@ step "updateforcip" {
|
|||
# these tests exercise mark/restore during EPQ recheck, cf bug #15032
|
||||
|
||||
step "selectjoinforupdate" {
|
||||
set enable_nestloop to 0;
|
||||
set enable_hashjoin to 0;
|
||||
set enable_seqscan to 0;
|
||||
set local enable_nestloop to 0;
|
||||
set local enable_hashjoin to 0;
|
||||
set local enable_seqscan to 0;
|
||||
explain (costs off)
|
||||
select * from jointest a join jointest b on a.id=b.id for update;
|
||||
select * from jointest a join jointest b on a.id=b.id for update;
|
||||
}
|
||||
|
||||
# these tests exercise Result plan nodes participating in EPQ
|
||||
|
||||
step "selectresultforupdate" {
|
||||
select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
|
||||
left join table_a a on a.id = x, jointest jt
|
||||
where jt.id = y;
|
||||
explain (verbose, costs off)
|
||||
select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
|
||||
left join table_a a on a.id = x, jointest jt
|
||||
where jt.id = y for update of jt, ss1, ss2;
|
||||
select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
|
||||
left join table_a a on a.id = x, jointest jt
|
||||
where jt.id = y for update of jt, ss1, ss2;
|
||||
}
|
||||
|
||||
|
||||
session "s2"
|
||||
setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
|
||||
|
@ -190,4 +205,5 @@ permutation "updateforcip" "updateforcip2" "c1" "c2" "read_a"
|
|||
permutation "updateforcip" "updateforcip3" "c1" "c2" "read_a"
|
||||
permutation "wrtwcte" "readwcte" "c1" "c2"
|
||||
permutation "wrjt" "selectjoinforupdate" "c2" "c1"
|
||||
permutation "wrjt" "selectresultforupdate" "c2" "c1"
|
||||
permutation "wrtwcte" "multireadwcte" "c1" "c2"
|
||||
|
|
|
@ -31,6 +31,10 @@ INSERT INTO J2_TBL VALUES (5, -5);
|
|||
INSERT INTO J2_TBL VALUES (0, NULL);
|
||||
INSERT INTO J2_TBL VALUES (NULL, NULL);
|
||||
INSERT INTO J2_TBL VALUES (NULL, 0);
|
||||
-- useful in some tests below
|
||||
create temp table onerow();
|
||||
insert into onerow default values;
|
||||
analyze onerow;
|
||||
--
|
||||
-- CORRELATION NAMES
|
||||
-- Make sure that table/column aliases are supported
|
||||
|
@ -2227,20 +2231,17 @@ explain (costs off)
|
|||
select * from int8_tbl i1 left join (int8_tbl i2 join
|
||||
(select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2
|
||||
order by 1, 2;
|
||||
QUERY PLAN
|
||||
-------------------------------------------------
|
||||
QUERY PLAN
|
||||
-------------------------------------------
|
||||
Sort
|
||||
Sort Key: i1.q1, i1.q2
|
||||
-> Hash Left Join
|
||||
Hash Cond: (i1.q2 = i2.q2)
|
||||
-> Seq Scan on int8_tbl i1
|
||||
-> Hash
|
||||
-> Hash Join
|
||||
Hash Cond: (i2.q1 = (123))
|
||||
-> Seq Scan on int8_tbl i2
|
||||
-> Hash
|
||||
-> Result
|
||||
(11 rows)
|
||||
-> Seq Scan on int8_tbl i2
|
||||
Filter: (q1 = 123)
|
||||
(8 rows)
|
||||
|
||||
select * from int8_tbl i1 left join (int8_tbl i2 join
|
||||
(select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2
|
||||
|
@ -3133,8 +3134,8 @@ select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
|
|||
tenk1 t1
|
||||
inner join int4_tbl i1
|
||||
left join (select v1.x2, v2.y1, 11 AS d1
|
||||
from (values(1,0)) v1(x1,x2)
|
||||
left join (values(3,1)) v2(y1,y2)
|
||||
from (select 1,0 from onerow) v1(x1,x2)
|
||||
left join (select 3,1 from onerow) v2(y1,y2)
|
||||
on v1.x1 = v2.y2) subq1
|
||||
on (i1.f1 = subq1.x2)
|
||||
on (t1.unique2 = subq1.d1)
|
||||
|
@ -3144,27 +3145,26 @@ where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
|
|||
QUERY PLAN
|
||||
-----------------------------------------------------------------------
|
||||
Nested Loop
|
||||
Join Filter: (t1.stringu1 > t2.stringu2)
|
||||
-> Nested Loop
|
||||
Join Filter: ((0) = i1.f1)
|
||||
Join Filter: (t1.stringu1 > t2.stringu2)
|
||||
-> Nested Loop
|
||||
-> Nested Loop
|
||||
Join Filter: ((1) = (1))
|
||||
-> Result
|
||||
-> Result
|
||||
-> Seq Scan on onerow
|
||||
-> Seq Scan on onerow onerow_1
|
||||
-> Index Scan using tenk1_unique2 on tenk1 t1
|
||||
Index Cond: ((unique2 = (11)) AND (unique2 < 42))
|
||||
-> Seq Scan on int4_tbl i1
|
||||
-> Index Scan using tenk1_unique1 on tenk1 t2
|
||||
Index Cond: (unique1 = (3))
|
||||
(14 rows)
|
||||
-> Index Scan using tenk1_unique1 on tenk1 t2
|
||||
Index Cond: (unique1 = (3))
|
||||
-> Seq Scan on int4_tbl i1
|
||||
Filter: (f1 = 0)
|
||||
(13 rows)
|
||||
|
||||
select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
|
||||
tenk1 t1
|
||||
inner join int4_tbl i1
|
||||
left join (select v1.x2, v2.y1, 11 AS d1
|
||||
from (values(1,0)) v1(x1,x2)
|
||||
left join (values(3,1)) v2(y1,y2)
|
||||
from (select 1,0 from onerow) v1(x1,x2)
|
||||
left join (select 3,1 from onerow) v2(y1,y2)
|
||||
on v1.x1 = v2.y2) subq1
|
||||
on (i1.f1 = subq1.x2)
|
||||
on (t1.unique2 = subq1.d1)
|
||||
|
@ -3196,6 +3196,50 @@ where t1.unique1 < i4.f1;
|
|||
----
|
||||
(0 rows)
|
||||
|
||||
-- this variant is foldable by the remove-useless-RESULT-RTEs code
|
||||
explain (costs off)
|
||||
select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
|
||||
tenk1 t1
|
||||
inner join int4_tbl i1
|
||||
left join (select v1.x2, v2.y1, 11 AS d1
|
||||
from (values(1,0)) v1(x1,x2)
|
||||
left join (values(3,1)) v2(y1,y2)
|
||||
on v1.x1 = v2.y2) subq1
|
||||
on (i1.f1 = subq1.x2)
|
||||
on (t1.unique2 = subq1.d1)
|
||||
left join tenk1 t2
|
||||
on (subq1.y1 = t2.unique1)
|
||||
where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
|
||||
QUERY PLAN
|
||||
-----------------------------------------------------------------
|
||||
Nested Loop
|
||||
Join Filter: (t1.stringu1 > t2.stringu2)
|
||||
-> Nested Loop
|
||||
-> Seq Scan on int4_tbl i1
|
||||
Filter: (f1 = 0)
|
||||
-> Index Scan using tenk1_unique2 on tenk1 t1
|
||||
Index Cond: ((unique2 = (11)) AND (unique2 < 42))
|
||||
-> Index Scan using tenk1_unique1 on tenk1 t2
|
||||
Index Cond: (unique1 = (3))
|
||||
(9 rows)
|
||||
|
||||
select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
|
||||
tenk1 t1
|
||||
inner join int4_tbl i1
|
||||
left join (select v1.x2, v2.y1, 11 AS d1
|
||||
from (values(1,0)) v1(x1,x2)
|
||||
left join (values(3,1)) v2(y1,y2)
|
||||
on v1.x1 = v2.y2) subq1
|
||||
on (i1.f1 = subq1.x2)
|
||||
on (t1.unique2 = subq1.d1)
|
||||
left join tenk1 t2
|
||||
on (subq1.y1 = t2.unique1)
|
||||
where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
|
||||
unique2 | stringu1 | unique1 | stringu2
|
||||
---------+----------+---------+----------
|
||||
11 | WFAAAA | 3 | LKIAAA
|
||||
(1 row)
|
||||
|
||||
--
|
||||
-- test extraction of restriction OR clauses from join OR clause
|
||||
-- (we used to only do this for indexable clauses)
|
||||
|
@ -3596,7 +3640,7 @@ select t1.* from
|
|||
-> Hash Right Join
|
||||
Output: i8.q2
|
||||
Hash Cond: ((NULL::integer) = i8b1.q2)
|
||||
-> Hash Left Join
|
||||
-> Hash Join
|
||||
Output: i8.q2, (NULL::integer)
|
||||
Hash Cond: (i8.q1 = i8b2.q1)
|
||||
-> Seq Scan on public.int8_tbl i8
|
||||
|
@ -4018,10 +4062,10 @@ select * from
|
|||
QUERY PLAN
|
||||
---------------------------------------
|
||||
Nested Loop Left Join
|
||||
Join Filter: ((1) = COALESCE((1)))
|
||||
-> Result
|
||||
-> Hash Full Join
|
||||
Hash Cond: (a1.unique1 = (1))
|
||||
Filter: (1 = COALESCE((1)))
|
||||
-> Seq Scan on tenk1 a1
|
||||
-> Hash
|
||||
-> Result
|
||||
|
@ -4951,13 +4995,10 @@ select v.* from
|
|||
-4567890123456789 |
|
||||
(20 rows)
|
||||
|
||||
create temp table dual();
|
||||
insert into dual default values;
|
||||
analyze dual;
|
||||
select v.* from
|
||||
(int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1)
|
||||
left join int4_tbl z on z.f1 = x.q2,
|
||||
lateral (select x.q1,y.q1 from dual union all select x.q2,y.q2 from dual) v(vx,vy);
|
||||
lateral (select x.q1,y.q1 from onerow union all select x.q2,y.q2 from onerow) v(vx,vy);
|
||||
vx | vy
|
||||
-------------------+-------------------
|
||||
4567890123456789 | 123
|
||||
|
|
|
@ -999,7 +999,7 @@ select * from
|
|||
QUERY PLAN
|
||||
----------------------------------------------------------
|
||||
Subquery Scan on ss
|
||||
Output: x, u
|
||||
Output: ss.x, ss.u
|
||||
Filter: tattle(ss.x, 8)
|
||||
-> ProjectSet
|
||||
Output: 9, unnest('{1,2,3,11,12,13}'::integer[])
|
||||
|
@ -1061,7 +1061,7 @@ select * from
|
|||
QUERY PLAN
|
||||
----------------------------------------------------------
|
||||
Subquery Scan on ss
|
||||
Output: x, u
|
||||
Output: ss.x, ss.u
|
||||
Filter: tattle(ss.x, ss.u)
|
||||
-> ProjectSet
|
||||
Output: 9, unnest('{1,2,3,11,12,13}'::integer[])
|
||||
|
|
|
@ -37,6 +37,12 @@ INSERT INTO J2_TBL VALUES (0, NULL);
|
|||
INSERT INTO J2_TBL VALUES (NULL, NULL);
|
||||
INSERT INTO J2_TBL VALUES (NULL, 0);
|
||||
|
||||
-- useful in some tests below
|
||||
create temp table onerow();
|
||||
insert into onerow default values;
|
||||
analyze onerow;
|
||||
|
||||
|
||||
--
|
||||
-- CORRELATION NAMES
|
||||
-- Make sure that table/column aliases are supported
|
||||
|
@ -940,8 +946,8 @@ select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
|
|||
tenk1 t1
|
||||
inner join int4_tbl i1
|
||||
left join (select v1.x2, v2.y1, 11 AS d1
|
||||
from (values(1,0)) v1(x1,x2)
|
||||
left join (values(3,1)) v2(y1,y2)
|
||||
from (select 1,0 from onerow) v1(x1,x2)
|
||||
left join (select 3,1 from onerow) v2(y1,y2)
|
||||
on v1.x1 = v2.y2) subq1
|
||||
on (i1.f1 = subq1.x2)
|
||||
on (t1.unique2 = subq1.d1)
|
||||
|
@ -953,8 +959,8 @@ select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
|
|||
tenk1 t1
|
||||
inner join int4_tbl i1
|
||||
left join (select v1.x2, v2.y1, 11 AS d1
|
||||
from (values(1,0)) v1(x1,x2)
|
||||
left join (values(3,1)) v2(y1,y2)
|
||||
from (select 1,0 from onerow) v1(x1,x2)
|
||||
left join (select 3,1 from onerow) v2(y1,y2)
|
||||
on v1.x1 = v2.y2) subq1
|
||||
on (i1.f1 = subq1.x2)
|
||||
on (t1.unique2 = subq1.d1)
|
||||
|
@ -980,6 +986,35 @@ select ss1.d1 from
|
|||
on t1.tenthous = ss1.d1
|
||||
where t1.unique1 < i4.f1;
|
||||
|
||||
-- this variant is foldable by the remove-useless-RESULT-RTEs code
|
||||
|
||||
explain (costs off)
|
||||
select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
|
||||
tenk1 t1
|
||||
inner join int4_tbl i1
|
||||
left join (select v1.x2, v2.y1, 11 AS d1
|
||||
from (values(1,0)) v1(x1,x2)
|
||||
left join (values(3,1)) v2(y1,y2)
|
||||
on v1.x1 = v2.y2) subq1
|
||||
on (i1.f1 = subq1.x2)
|
||||
on (t1.unique2 = subq1.d1)
|
||||
left join tenk1 t2
|
||||
on (subq1.y1 = t2.unique1)
|
||||
where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
|
||||
|
||||
select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
|
||||
tenk1 t1
|
||||
inner join int4_tbl i1
|
||||
left join (select v1.x2, v2.y1, 11 AS d1
|
||||
from (values(1,0)) v1(x1,x2)
|
||||
left join (values(3,1)) v2(y1,y2)
|
||||
on v1.x1 = v2.y2) subq1
|
||||
on (i1.f1 = subq1.x2)
|
||||
on (t1.unique2 = subq1.d1)
|
||||
left join tenk1 t2
|
||||
on (subq1.y1 = t2.unique1)
|
||||
where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
|
||||
|
||||
--
|
||||
-- test extraction of restriction OR clauses from join OR clause
|
||||
-- (we used to only do this for indexable clauses)
|
||||
|
@ -1661,13 +1696,10 @@ select v.* from
|
|||
(int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1)
|
||||
left join int4_tbl z on z.f1 = x.q2,
|
||||
lateral (select x.q1,y.q1 union all select x.q2,y.q2) v(vx,vy);
|
||||
create temp table dual();
|
||||
insert into dual default values;
|
||||
analyze dual;
|
||||
select v.* from
|
||||
(int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1)
|
||||
left join int4_tbl z on z.f1 = x.q2,
|
||||
lateral (select x.q1,y.q1 from dual union all select x.q2,y.q2 from dual) v(vx,vy);
|
||||
lateral (select x.q1,y.q1 from onerow union all select x.q2,y.q2 from onerow) v(vx,vy);
|
||||
|
||||
explain (verbose, costs off)
|
||||
select * from
|
||||
|
|
Loading…
Reference in New Issue