Commit 4be058fe authored by Tom Lane's avatar Tom Lane

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
parent 5c118675
...@@ -2476,6 +2476,8 @@ JumbleRangeTable(pgssJumbleState *jstate, List *rtable) ...@@ -2476,6 +2476,8 @@ JumbleRangeTable(pgssJumbleState *jstate, List *rtable)
case RTE_NAMEDTUPLESTORE: case RTE_NAMEDTUPLESTORE:
APP_JUMB_STRING(rte->enrname); APP_JUMB_STRING(rte->enrname);
break; break;
case RTE_RESULT:
break;
default: default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind); elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
break; break;
......
...@@ -5361,7 +5361,7 @@ INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; ...@@ -5361,7 +5361,7 @@ INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass;
QUERY PLAN QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Insert on public.ft2 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) 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 -> 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 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) ...@@ -437,9 +437,12 @@ ExecSupportsMarkRestore(Path *pathnode)
return ExecSupportsMarkRestore(((ProjectionPath *) pathnode)->subpath); return ExecSupportsMarkRestore(((ProjectionPath *) pathnode)->subpath);
else if (IsA(pathnode, MinMaxAggPath)) else if (IsA(pathnode, MinMaxAggPath))
return false; /* childless Result */ return false; /* childless Result */
else if (IsA(pathnode, GroupResultPath))
return false; /* childless Result */
else else
{ {
Assert(IsA(pathnode, ResultPath)); /* Simple RTE_RESULT base relation */
Assert(IsA(pathnode, Path));
return false; /* childless Result */ return false; /* childless Result */
} }
......
...@@ -2329,10 +2329,6 @@ range_table_walker(List *rtable, ...@@ -2329,10 +2329,6 @@ range_table_walker(List *rtable,
if (walker(rte->tablesample, context)) if (walker(rte->tablesample, context))
return true; return true;
break; break;
case RTE_CTE:
case RTE_NAMEDTUPLESTORE:
/* nothing to do */
break;
case RTE_SUBQUERY: case RTE_SUBQUERY:
if (!(flags & QTW_IGNORE_RT_SUBQUERIES)) if (!(flags & QTW_IGNORE_RT_SUBQUERIES))
if (walker(rte->subquery, context)) if (walker(rte->subquery, context))
...@@ -2355,6 +2351,11 @@ range_table_walker(List *rtable, ...@@ -2355,6 +2351,11 @@ range_table_walker(List *rtable,
if (walker(rte->values_lists, context)) if (walker(rte->values_lists, context))
return true; return true;
break; break;
case RTE_CTE:
case RTE_NAMEDTUPLESTORE:
case RTE_RESULT:
/* nothing to do */
break;
} }
if (walker(rte->securityQuals, context)) if (walker(rte->securityQuals, context))
...@@ -3164,10 +3165,6 @@ range_table_mutator(List *rtable, ...@@ -3164,10 +3165,6 @@ range_table_mutator(List *rtable,
TableSampleClause *); TableSampleClause *);
/* we don't bother to copy eref, aliases, etc; OK? */ /* we don't bother to copy eref, aliases, etc; OK? */
break; break;
case RTE_CTE:
case RTE_NAMEDTUPLESTORE:
/* nothing to do */
break;
case RTE_SUBQUERY: case RTE_SUBQUERY:
if (!(flags & QTW_IGNORE_RT_SUBQUERIES)) if (!(flags & QTW_IGNORE_RT_SUBQUERIES))
{ {
...@@ -3198,6 +3195,11 @@ range_table_mutator(List *rtable, ...@@ -3198,6 +3195,11 @@ range_table_mutator(List *rtable,
case RTE_VALUES: case RTE_VALUES:
MUTATE(newrte->values_lists, rte->values_lists, List *); MUTATE(newrte->values_lists, rte->values_lists, List *);
break; break;
case RTE_CTE:
case RTE_NAMEDTUPLESTORE:
case RTE_RESULT:
/* nothing to do */
break;
} }
MUTATE(newrte->securityQuals, rte->securityQuals, List *); MUTATE(newrte->securityQuals, rte->securityQuals, List *);
newrt = lappend(newrt, newrte); newrt = lappend(newrt, newrte);
......
...@@ -1855,9 +1855,9 @@ _outMergeAppendPath(StringInfo str, const MergeAppendPath *node) ...@@ -1855,9 +1855,9 @@ _outMergeAppendPath(StringInfo str, const MergeAppendPath *node)
} }
static void 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); _outPathInfo(str, (const Path *) node);
...@@ -2213,7 +2213,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) ...@@ -2213,7 +2213,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind);
WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasJoinRTEs);
WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasLateralRTEs);
WRITE_BOOL_FIELD(hasDeletedRTEs);
WRITE_BOOL_FIELD(hasHavingQual); WRITE_BOOL_FIELD(hasHavingQual);
WRITE_BOOL_FIELD(hasPseudoConstantQuals); WRITE_BOOL_FIELD(hasPseudoConstantQuals);
WRITE_BOOL_FIELD(hasRecursion); WRITE_BOOL_FIELD(hasRecursion);
...@@ -3060,6 +3059,9 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node) ...@@ -3060,6 +3059,9 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
WRITE_NODE_FIELD(coltypmods); WRITE_NODE_FIELD(coltypmods);
WRITE_NODE_FIELD(colcollations); WRITE_NODE_FIELD(colcollations);
break; break;
case RTE_RESULT:
/* no extra fields */
break;
default: default:
elog(ERROR, "unrecognized RTE kind: %d", (int) node->rtekind); elog(ERROR, "unrecognized RTE kind: %d", (int) node->rtekind);
break; break;
...@@ -3943,8 +3945,8 @@ outNode(StringInfo str, const void *obj) ...@@ -3943,8 +3945,8 @@ outNode(StringInfo str, const void *obj)
case T_MergeAppendPath: case T_MergeAppendPath:
_outMergeAppendPath(str, obj); _outMergeAppendPath(str, obj);
break; break;
case T_ResultPath: case T_GroupResultPath:
_outResultPath(str, obj); _outGroupResultPath(str, obj);
break; break;
case T_MaterialPath: case T_MaterialPath:
_outMaterialPath(str, obj); _outMaterialPath(str, obj);
......
...@@ -295,6 +295,10 @@ print_rt(const List *rtable) ...@@ -295,6 +295,10 @@ print_rt(const List *rtable)
printf("%d\t%s\t[tuplestore]", printf("%d\t%s\t[tuplestore]",
i, rte->eref->aliasname); i, rte->eref->aliasname);
break; break;
case RTE_RESULT:
printf("%d\t%s\t[result]",
i, rte->eref->aliasname);
break;
default: default:
printf("%d\t%s\t[unknown rtekind]", printf("%d\t%s\t[unknown rtekind]",
i, rte->eref->aliasname); i, rte->eref->aliasname);
......
...@@ -1411,6 +1411,9 @@ _readRangeTblEntry(void) ...@@ -1411,6 +1411,9 @@ _readRangeTblEntry(void)
READ_NODE_FIELD(coltypmods); READ_NODE_FIELD(coltypmods);
READ_NODE_FIELD(colcollations); READ_NODE_FIELD(colcollations);
break; break;
case RTE_RESULT:
/* no extra fields */
break;
default: default:
elog(ERROR, "unrecognized RTE kind: %d", elog(ERROR, "unrecognized RTE kind: %d",
(int) local_node->rtekind); (int) local_node->rtekind);
......
...@@ -361,7 +361,16 @@ RelOptInfo - a relation or joined relations ...@@ -361,7 +361,16 @@ RelOptInfo - a relation or joined relations
join clauses) join clauses)
Path - every way to generate a RelOptInfo(sequential,index,joins) 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 IndexPath - index scan
BitmapHeapPath - top of a bitmapped index scan BitmapHeapPath - top of a bitmapped index scan
TidPath - scan by CTID TidPath - scan by CTID
...@@ -370,7 +379,7 @@ RelOptInfo - a relation or joined relations ...@@ -370,7 +379,7 @@ RelOptInfo - a relation or joined relations
CustomPath - for custom scan providers CustomPath - for custom scan providers
AppendPath - append multiple subpaths together AppendPath - append multiple subpaths together
MergeAppendPath - merge multiple subpaths, preserving their common sort order MergeAppendPath - merge multiple subpaths, preserving their common sort order
ResultPath - a childless Result plan node (used for FROM-less SELECT) GroupResultPath - childless Result plan node (used for degenerate grouping)
MaterialPath - a Material plan node MaterialPath - a Material plan node
UniquePath - remove duplicate rows (either by hashing or sorting) UniquePath - remove duplicate rows (either by hashing or sorting)
GatherPath - collect the results of parallel workers GatherPath - collect the results of parallel workers
......
...@@ -117,6 +117,8 @@ static void set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, ...@@ -117,6 +117,8 @@ static void set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte); RangeTblEntry *rte);
static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte); RangeTblEntry *rte);
static void set_result_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte); RangeTblEntry *rte);
static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist);
...@@ -437,8 +439,13 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel, ...@@ -437,8 +439,13 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
set_cte_pathlist(root, rel, rte); set_cte_pathlist(root, rel, rte);
break; break;
case RTE_NAMEDTUPLESTORE: case RTE_NAMEDTUPLESTORE:
/* Might as well just build the path immediately */
set_namedtuplestore_pathlist(root, rel, rte); set_namedtuplestore_pathlist(root, rel, rte);
break; break;
case RTE_RESULT:
/* Might as well just build the path immediately */
set_result_pathlist(root, rel, rte);
break;
default: default:
elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind); elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
break; break;
...@@ -510,6 +517,9 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, ...@@ -510,6 +517,9 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
case RTE_NAMEDTUPLESTORE: case RTE_NAMEDTUPLESTORE:
/* tuplestore reference --- fully handled during set_rel_size */ /* tuplestore reference --- fully handled during set_rel_size */
break; break;
case RTE_RESULT:
/* simple Result --- fully handled during set_rel_size */
break;
default: default:
elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind); elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
break; break;
...@@ -712,6 +722,10 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel, ...@@ -712,6 +722,10 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* infrastructure to support that. * infrastructure to support that.
*/ */
return; return;
case RTE_RESULT:
/* RESULT RTEs, in themselves, are no problem. */
break;
} }
/* /*
...@@ -2509,6 +2523,36 @@ set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel, ...@@ -2509,6 +2523,36 @@ set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel,
set_cheapest(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 * set_worktable_pathlist
* Build the (single) access path for a self-reference CTE RTE * Build the (single) access path for a self-reference CTE RTE
...@@ -3677,9 +3721,6 @@ print_path(PlannerInfo *root, Path *path, int indent) ...@@ -3677,9 +3721,6 @@ print_path(PlannerInfo *root, Path *path, int indent)
case T_SampleScan: case T_SampleScan:
ptype = "SampleScan"; ptype = "SampleScan";
break; break;
case T_SubqueryScan:
ptype = "SubqueryScan";
break;
case T_FunctionScan: case T_FunctionScan:
ptype = "FunctionScan"; ptype = "FunctionScan";
break; break;
...@@ -3692,6 +3733,12 @@ print_path(PlannerInfo *root, Path *path, int indent) ...@@ -3692,6 +3733,12 @@ print_path(PlannerInfo *root, Path *path, int indent)
case T_CteScan: case T_CteScan:
ptype = "CteScan"; ptype = "CteScan";
break; break;
case T_NamedTuplestoreScan:
ptype = "NamedTuplestoreScan";
break;
case T_Result:
ptype = "Result";
break;
case T_WorkTableScan: case T_WorkTableScan:
ptype = "WorkTableScan"; ptype = "WorkTableScan";
break; break;
...@@ -3716,7 +3763,7 @@ print_path(PlannerInfo *root, Path *path, int indent) ...@@ -3716,7 +3763,7 @@ print_path(PlannerInfo *root, Path *path, int indent)
ptype = "TidScan"; ptype = "TidScan";
break; break;
case T_SubqueryScanPath: case T_SubqueryScanPath:
ptype = "SubqueryScanScan"; ptype = "SubqueryScan";
break; break;
case T_ForeignPath: case T_ForeignPath:
ptype = "ForeignScan"; ptype = "ForeignScan";
...@@ -3742,8 +3789,8 @@ print_path(PlannerInfo *root, Path *path, int indent) ...@@ -3742,8 +3789,8 @@ print_path(PlannerInfo *root, Path *path, int indent)
case T_MergeAppendPath: case T_MergeAppendPath:
ptype = "MergeAppend"; ptype = "MergeAppend";
break; break;
case T_ResultPath: case T_GroupResultPath:
ptype = "Result"; ptype = "GroupResult";
break; break;
case T_MaterialPath: case T_MaterialPath:
ptype = "Material"; ptype = "Material";
......
...@@ -1570,6 +1570,40 @@ cost_namedtuplestorescan(Path *path, PlannerInfo *root, ...@@ -1570,6 +1570,40 @@ cost_namedtuplestorescan(Path *path, PlannerInfo *root,
path->total_cost = startup_cost + run_cost; 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 * cost_recursive_union
* Determines and returns the cost of performing a recursive union, * Determines and returns the cost of performing a recursive union,
...@@ -5044,6 +5078,29 @@ set_namedtuplestore_size_estimates(PlannerInfo *root, RelOptInfo *rel) ...@@ -5044,6 +5078,29 @@ set_namedtuplestore_size_estimates(PlannerInfo *root, RelOptInfo *rel)
set_baserel_size_estimates(root, 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_foreign_size_estimates
* Set the size estimates for a base relation that is a foreign table. * 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, ...@@ -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_join_plan(PlannerInfo *root, JoinPath *best_path);
static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path); static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path);
static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path); static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path);
static Result *create_result_plan(PlannerInfo *root, ResultPath *best_path); static Result *create_group_result_plan(PlannerInfo *root,
GroupResultPath *best_path);
static ProjectSet *create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path); static ProjectSet *create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path);
static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path, static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path,
int flags); int flags);
...@@ -138,6 +139,8 @@ static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path, ...@@ -138,6 +139,8 @@ static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses); List *tlist, List *scan_clauses);
static NamedTuplestoreScan *create_namedtuplestorescan_plan(PlannerInfo *root, static NamedTuplestoreScan *create_namedtuplestorescan_plan(PlannerInfo *root,
Path *best_path, List *tlist, List *scan_clauses); 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, static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses); List *tlist, List *scan_clauses);
static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
...@@ -403,11 +406,16 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags) ...@@ -403,11 +406,16 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
plan = (Plan *) create_minmaxagg_plan(root, plan = (Plan *) create_minmaxagg_plan(root,
(MinMaxAggPath *) best_path); (MinMaxAggPath *) best_path);
} }
else if (IsA(best_path, GroupResultPath))
{
plan = (Plan *) create_group_result_plan(root,
(GroupResultPath *) best_path);
}
else else
{ {
Assert(IsA(best_path, ResultPath)); /* Simple RTE_RESULT base relation */
plan = (Plan *) create_result_plan(root, Assert(IsA(best_path, Path));
(ResultPath *) best_path); plan = create_scan_plan(root, best_path, flags);
} }
break; break;
case T_ProjectSet: case T_ProjectSet:
...@@ -691,6 +699,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags) ...@@ -691,6 +699,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
scan_clauses); scan_clauses);
break; break;
case T_Result:
plan = (Plan *) create_resultscan_plan(root,
best_path,
tlist,
scan_clauses);
break;
case T_WorkTableScan: case T_WorkTableScan:
plan = (Plan *) create_worktablescan_plan(root, plan = (Plan *) create_worktablescan_plan(root,
best_path, best_path,
...@@ -922,9 +937,26 @@ create_gating_plan(PlannerInfo *root, Path *path, Plan *plan, ...@@ -922,9 +937,26 @@ create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
List *gating_quals) List *gating_quals)
{ {
Plan *gplan; Plan *gplan;
Plan *splan;
Assert(gating_quals); 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 * 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 * 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, ...@@ -932,7 +964,7 @@ create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
*/ */
gplan = (Plan *) make_result(build_path_tlist(root, path), gplan = (Plan *) make_result(build_path_tlist(root, path),
(Node *) gating_quals, (Node *) gating_quals,
plan); splan);
/* /*
* Notice that we don't change cost or size estimates when doing gating. * 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) ...@@ -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'. * Create a Result plan for 'best_path'.
* This is only used for degenerate cases, such as a query with an empty * This is only used for degenerate grouping cases.
* jointree.
* *
* Returns a Plan node. * Returns a Plan node.
*/ */
static Result * static Result *
create_result_plan(PlannerInfo *root, ResultPath *best_path) create_group_result_plan(PlannerInfo *root, GroupResultPath *best_path)
{ {
Result *plan; Result *plan;
List *tlist; List *tlist;
...@@ -3477,6 +3508,44 @@ create_namedtuplestorescan_plan(PlannerInfo *root, Path *best_path, ...@@ -3477,6 +3508,44 @@ create_namedtuplestorescan_plan(PlannerInfo *root, Path *best_path,
return scan_plan; 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 * create_worktablescan_plan
* Returns a worktablescan plan for the base relation scanned by 'best_path' * 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, ...@@ -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 * all below it, so we should report inner_join_rels = qualscope. If
* there was exactly one element, we should (and already did) report * there was exactly one element, we should (and already did) report
* whatever its inner_join_rels were. If there were no elements (is * 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) if (list_length(f->fromlist) > 1)
*inner_join_rels = *qualscope; *inner_join_rels = *qualscope;
......
...@@ -60,44 +60,6 @@ query_planner(PlannerInfo *root, List *tlist, ...@@ -60,44 +60,6 @@ query_planner(PlannerInfo *root, List *tlist,
List *joinlist; List *joinlist;
RelOptInfo *final_rel; 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. * Init planner lists to empty.
* *
...@@ -124,6 +86,71 @@ query_planner(PlannerInfo *root, List *tlist, ...@@ -124,6 +86,71 @@ query_planner(PlannerInfo *root, List *tlist,
*/ */
setup_simple_rel_arrays(root); 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 * Populate append_rel_array with each AppendRelInfo to allow direct
* lookups by child relid. * lookups by child relid.
......
...@@ -611,6 +611,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, ...@@ -611,6 +611,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
List *newWithCheckOptions; List *newWithCheckOptions;
List *newHaving; List *newHaving;
bool hasOuterJoins; bool hasOuterJoins;
bool hasResultRTEs;
RelOptInfo *final_rel; RelOptInfo *final_rel;
ListCell *l; ListCell *l;
...@@ -651,6 +652,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse, ...@@ -651,6 +652,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
if (parse->cteList) if (parse->cteList)
SS_process_ctes(root); 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 * 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 * to transform them into joins. Note that this step does not descend
...@@ -684,14 +691,16 @@ subquery_planner(PlannerGlobal *glob, Query *parse, ...@@ -684,14 +691,16 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
/* /*
* Detect whether any rangetable entries are RTE_JOIN kind; if not, we can * 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 * avoid the expense of doing flatten_join_alias_vars(). Likewise check
* outer joins --- if none, we can skip reduce_outer_joins(). And check * whether any are RTE_RESULT kind; if not, we can skip
* for LATERAL RTEs, too. This must be done after we have done * remove_useless_result_rtes(). Also check for outer joins --- if none,
* pull_up_subqueries(), of course. * 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->hasJoinRTEs = false;
root->hasLateralRTEs = false; root->hasLateralRTEs = false;
hasOuterJoins = false; hasOuterJoins = false;
hasResultRTEs = false;
foreach(l, parse->rtable) foreach(l, parse->rtable)
{ {
RangeTblEntry *rte = lfirst_node(RangeTblEntry, l); RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
...@@ -702,6 +711,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse, ...@@ -702,6 +711,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
if (IS_OUTER_JOIN(rte->jointype)) if (IS_OUTER_JOIN(rte->jointype))
hasOuterJoins = true; hasOuterJoins = true;
} }
else if (rte->rtekind == RTE_RESULT)
{
hasResultRTEs = true;
}
if (rte->lateral) if (rte->lateral)
root->hasLateralRTEs = true; root->hasLateralRTEs = true;
} }
...@@ -717,10 +730,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse, ...@@ -717,10 +730,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
/* /*
* Expand any rangetable entries that are inheritance sets into "append * Expand any rangetable entries that are inheritance sets into "append
* relations". This can add entries to the rangetable, but they must be * relations". This can add entries to the rangetable, but they must be
* plain base relations not joins, so it's OK (and marginally more * plain RTE_RELATION entries, so it's OK (and marginally more efficient)
* efficient) to do it after checking for join RTEs. We must do it after * to do it after checking for joins and other special RTEs. We must do
* pulling up subqueries, else we'd fail to handle inherited tables in * this after pulling up subqueries, else we'd fail to handle inherited
* subqueries. * tables in subqueries.
*/ */
expand_inherited_tables(root); expand_inherited_tables(root);
...@@ -967,6 +980,14 @@ subquery_planner(PlannerGlobal *glob, Query *parse, ...@@ -967,6 +980,14 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
if (hasOuterJoins) if (hasOuterJoins)
reduce_outer_joins(root); 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 * Do the main planning. If we have an inherited target relation, that
* needs special processing, else go straight to grouping_planner. * needs special processing, else go straight to grouping_planner.
...@@ -3894,7 +3915,7 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, ...@@ -3894,7 +3915,7 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
while (--nrows >= 0) while (--nrows >= 0)
{ {
path = (Path *) path = (Path *)
create_result_path(root, grouped_rel, create_group_result_path(root, grouped_rel,
grouped_rel->reltarget, grouped_rel->reltarget,
(List *) parse->havingQual); (List *) parse->havingQual);
paths = lappend(paths, path); paths = lappend(paths, path);
...@@ -3914,7 +3935,7 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, ...@@ -3914,7 +3935,7 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
{ {
/* No grouping sets, or just one, so one output row */ /* No grouping sets, or just one, so one output row */
path = (Path *) path = (Path *)
create_result_path(root, grouped_rel, create_group_result_path(root, grouped_rel,
grouped_rel->reltarget, grouped_rel->reltarget,
(List *) parse->havingQual); (List *) parse->havingQual);
} }
......
...@@ -1114,12 +1114,6 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink, ...@@ -1114,12 +1114,6 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
if (!simplify_EXISTS_query(root, subselect)) if (!simplify_EXISTS_query(root, subselect))
return NULL; 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 * Separate out the WHERE clause. (We could theoretically also remove
* top-level plain JOIN/ON clauses, but it's probably not worth the * 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, ...@@ -1148,6 +1142,11 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
if (contain_volatile_functions(whereClause)) if (contain_volatile_functions(whereClause))
return NULL; 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. * Prepare to pull up the sub-select into top range table.
* *
......
...@@ -4,12 +4,14 @@ ...@@ -4,12 +4,14 @@
* Planner preprocessing for subqueries and join tree manipulation. * Planner preprocessing for subqueries and join tree manipulation.
* *
* NOTE: the intended sequence for invoking these operations is * NOTE: the intended sequence for invoking these operations is
* replace_empty_jointree
* pull_up_sublinks * pull_up_sublinks
* inline_set_returning_functions * inline_set_returning_functions
* pull_up_subqueries * pull_up_subqueries
* flatten_simple_union_all * flatten_simple_union_all
* do expression preprocessing (including flattening JOIN alias vars) * do expression preprocessing (including flattening JOIN alias vars)
* reduce_outer_joins * reduce_outer_joins
* remove_useless_result_rtes
* *
* *
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
...@@ -66,14 +68,12 @@ static Node *pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node, ...@@ -66,14 +68,12 @@ static Node *pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
static Node *pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, static Node *pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
JoinExpr *lowest_outer_join, JoinExpr *lowest_outer_join,
JoinExpr *lowest_nulling_outer_join, JoinExpr *lowest_nulling_outer_join,
AppendRelInfo *containing_appendrel, AppendRelInfo *containing_appendrel);
bool deletion_ok);
static Node *pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, static Node *pull_up_simple_subquery(PlannerInfo *root, Node *jtnode,
RangeTblEntry *rte, RangeTblEntry *rte,
JoinExpr *lowest_outer_join, JoinExpr *lowest_outer_join,
JoinExpr *lowest_nulling_outer_join, JoinExpr *lowest_nulling_outer_join,
AppendRelInfo *containing_appendrel, AppendRelInfo *containing_appendrel);
bool deletion_ok);
static Node *pull_up_simple_union_all(PlannerInfo *root, Node *jtnode, static Node *pull_up_simple_union_all(PlannerInfo *root, Node *jtnode,
RangeTblEntry *rte); RangeTblEntry *rte);
static void pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, static void pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root,
...@@ -82,12 +82,10 @@ static void pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, ...@@ -82,12 +82,10 @@ static void pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root,
static void make_setop_translation_list(Query *query, Index newvarno, static void make_setop_translation_list(Query *query, Index newvarno,
List **translated_vars); List **translated_vars);
static bool is_simple_subquery(Query *subquery, RangeTblEntry *rte, static bool is_simple_subquery(Query *subquery, RangeTblEntry *rte,
JoinExpr *lowest_outer_join, JoinExpr *lowest_outer_join);
bool deletion_ok);
static Node *pull_up_simple_values(PlannerInfo *root, Node *jtnode, static Node *pull_up_simple_values(PlannerInfo *root, Node *jtnode,
RangeTblEntry *rte); RangeTblEntry *rte);
static bool is_simple_values(PlannerInfo *root, RangeTblEntry *rte, static bool is_simple_values(PlannerInfo *root, RangeTblEntry *rte);
bool deletion_ok);
static bool is_simple_union_all(Query *subquery); static bool is_simple_union_all(Query *subquery);
static bool is_simple_union_all_recurse(Node *setOp, Query *setOpQuery, static bool is_simple_union_all_recurse(Node *setOp, Query *setOpQuery,
List *colTypes); List *colTypes);
...@@ -103,7 +101,6 @@ static Node *pullup_replace_vars_callback(Var *var, ...@@ -103,7 +101,6 @@ static Node *pullup_replace_vars_callback(Var *var,
replace_rte_variables_context *context); replace_rte_variables_context *context);
static Query *pullup_replace_vars_subquery(Query *query, static Query *pullup_replace_vars_subquery(Query *query,
pullup_replace_vars_context *context); pullup_replace_vars_context *context);
static Node *pull_up_subqueries_cleanup(Node *jtnode);
static reduce_outer_joins_state *reduce_outer_joins_pass1(Node *jtnode); static reduce_outer_joins_state *reduce_outer_joins_pass1(Node *jtnode);
static void reduce_outer_joins_pass2(Node *jtnode, static void reduce_outer_joins_pass2(Node *jtnode,
reduce_outer_joins_state *state, reduce_outer_joins_state *state,
...@@ -111,13 +108,61 @@ static void reduce_outer_joins_pass2(Node *jtnode, ...@@ -111,13 +108,61 @@ static void reduce_outer_joins_pass2(Node *jtnode,
Relids nonnullable_rels, Relids nonnullable_rels,
List *nonnullable_vars, List *nonnullable_vars,
List *forced_null_vars); List *forced_null_vars);
static void substitute_multiple_relids(Node *node, static Node *remove_useless_results_recurse(PlannerInfo *root, Node *jtnode);
static int get_result_relid(PlannerInfo *root, Node *jtnode);
static void remove_result_refs(PlannerInfo *root, int varno, Node *newjtloc);
static bool find_dependent_phvs(Node *node, int varno);
static void substitute_phv_relids(Node *node,
int varno, Relids subrelids); int varno, Relids subrelids);
static void fix_append_rel_relids(List *append_rel_list, int varno, static void fix_append_rel_relids(List *append_rel_list, int varno,
Relids subrelids); Relids subrelids);
static Node *find_jointree_node_for_rel(Node *jtnode, int relid); static Node *find_jointree_node_for_rel(Node *jtnode, int relid);
/*
* replace_empty_jointree
* If the Query's jointree is empty, replace it with a dummy RTE_RESULT
* relation.
*
* By doing this, we can avoid a bunch of corner cases that formerly existed
* for SELECTs with omitted FROM clauses. An example is that a subquery
* with empty jointree previously could not be pulled up, because that would
* have resulted in an empty relid set, making the subquery not uniquely
* identifiable for join or PlaceHolderVar processing.
*
* Unlike most other functions in this file, this function doesn't recurse;
* we rely on other processing to invoke it on sub-queries at suitable times.
*/
void
replace_empty_jointree(Query *parse)
{
RangeTblEntry *rte;
Index rti;
RangeTblRef *rtr;
/* Nothing to do if jointree is already nonempty */
if (parse->jointree->fromlist != NIL)
return;
/* We mustn't change it in the top level of a setop tree, either */
if (parse->setOperations)
return;
/* Create suitable RTE */
rte = makeNode(RangeTblEntry);
rte->rtekind = RTE_RESULT;
rte->eref = makeAlias("*RESULT*", NIL);
/* Add it to rangetable */
parse->rtable = lappend(parse->rtable, rte);
rti = list_length(parse->rtable);
/* And jam a reference into the jointree */
rtr = makeNode(RangeTblRef);
rtr->rtindex = rti;
parse->jointree->fromlist = list_make1(rtr);
}
/* /*
* pull_up_sublinks * pull_up_sublinks
* Attempt to pull up ANY and EXISTS SubLinks to be treated as * Attempt to pull up ANY and EXISTS SubLinks to be treated as
...@@ -611,16 +656,11 @@ pull_up_subqueries(PlannerInfo *root) ...@@ -611,16 +656,11 @@ pull_up_subqueries(PlannerInfo *root)
{ {
/* Top level of jointree must always be a FromExpr */ /* Top level of jointree must always be a FromExpr */
Assert(IsA(root->parse->jointree, FromExpr)); Assert(IsA(root->parse->jointree, FromExpr));
/* Reset flag saying we need a deletion cleanup pass */
root->hasDeletedRTEs = false;
/* Recursion starts with no containing join nor appendrel */ /* Recursion starts with no containing join nor appendrel */
root->parse->jointree = (FromExpr *) root->parse->jointree = (FromExpr *)
pull_up_subqueries_recurse(root, (Node *) root->parse->jointree, pull_up_subqueries_recurse(root, (Node *) root->parse->jointree,
NULL, NULL, NULL, false); NULL, NULL, NULL);
/* Apply cleanup phase if necessary */ /* We should still have a FromExpr */
if (root->hasDeletedRTEs)
root->parse->jointree = (FromExpr *)
pull_up_subqueries_cleanup((Node *) root->parse->jointree);
Assert(IsA(root->parse->jointree, FromExpr)); Assert(IsA(root->parse->jointree, FromExpr));
} }
...@@ -629,8 +669,6 @@ pull_up_subqueries(PlannerInfo *root) ...@@ -629,8 +669,6 @@ pull_up_subqueries(PlannerInfo *root)
* Recursive guts of pull_up_subqueries. * Recursive guts of pull_up_subqueries.
* *
* This recursively processes the jointree and returns a modified jointree. * This recursively processes the jointree and returns a modified jointree.
* Or, if it's valid to drop the current node from the jointree completely,
* it returns NULL.
* *
* If this jointree node is within either side of an outer join, then * If this jointree node is within either side of an outer join, then
* lowest_outer_join references the lowest such JoinExpr node; otherwise * lowest_outer_join references the lowest such JoinExpr node; otherwise
...@@ -647,37 +685,27 @@ pull_up_subqueries(PlannerInfo *root) ...@@ -647,37 +685,27 @@ pull_up_subqueries(PlannerInfo *root)
* This forces use of the PlaceHolderVar mechanism for all non-Var targetlist * This forces use of the PlaceHolderVar mechanism for all non-Var targetlist
* items, and puts some additional restrictions on what can be pulled up. * items, and puts some additional restrictions on what can be pulled up.
* *
* deletion_ok is true if the caller can cope with us returning NULL for a
* deletable leaf node (for example, a VALUES RTE that could be pulled up).
* If it's false, we'll avoid pullup in such cases.
*
* A tricky aspect of this code is that if we pull up a subquery we have * A tricky aspect of this code is that if we pull up a subquery we have
* to replace Vars that reference the subquery's outputs throughout the * to replace Vars that reference the subquery's outputs throughout the
* parent query, including quals attached to jointree nodes above the one * parent query, including quals attached to jointree nodes above the one
* we are currently processing! We handle this by being careful not to * we are currently processing! We handle this by being careful to maintain
* change the jointree structure while recursing: no nodes other than leaf * validity of the jointree structure while recursing, in the following sense:
* RangeTblRef entries and entirely-empty FromExprs will be replaced or * whenever we recurse, all qual expressions in the tree must be reachable
* deleted. Also, we can't turn pullup_replace_vars loose on the whole * from the top level, in case the recursive call needs to modify them.
* jointree, because it'll return a mutated copy of the tree; we have to *
* Notice also that we can't turn pullup_replace_vars loose on the whole
* jointree, because it'd return a mutated copy of the tree; we have to
* invoke it just on the quals, instead. This behavior is what makes it * invoke it just on the quals, instead. This behavior is what makes it
* reasonable to pass lowest_outer_join and lowest_nulling_outer_join as * reasonable to pass lowest_outer_join and lowest_nulling_outer_join as
* pointers rather than some more-indirect way of identifying the lowest * pointers rather than some more-indirect way of identifying the lowest
* OJs. Likewise, we don't replace append_rel_list members but only their * OJs. Likewise, we don't replace append_rel_list members but only their
* substructure, so the containing_appendrel reference is safe to use. * substructure, so the containing_appendrel reference is safe to use.
*
* Because of the rule that no jointree nodes with substructure can be
* replaced, we cannot fully handle the case of deleting nodes from the tree:
* when we delete one child of a JoinExpr, we need to replace the JoinExpr
* with a FromExpr, and that can't happen here. Instead, we set the
* root->hasDeletedRTEs flag, which tells pull_up_subqueries() that an
* additional pass over the tree is needed to clean up.
*/ */
static Node * static Node *
pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
JoinExpr *lowest_outer_join, JoinExpr *lowest_outer_join,
JoinExpr *lowest_nulling_outer_join, JoinExpr *lowest_nulling_outer_join,
AppendRelInfo *containing_appendrel, AppendRelInfo *containing_appendrel)
bool deletion_ok)
{ {
Assert(jtnode != NULL); Assert(jtnode != NULL);
if (IsA(jtnode, RangeTblRef)) if (IsA(jtnode, RangeTblRef))
...@@ -693,15 +721,13 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, ...@@ -693,15 +721,13 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
* unless is_safe_append_member says so. * unless is_safe_append_member says so.
*/ */
if (rte->rtekind == RTE_SUBQUERY && if (rte->rtekind == RTE_SUBQUERY &&
is_simple_subquery(rte->subquery, rte, is_simple_subquery(rte->subquery, rte, lowest_outer_join) &&
lowest_outer_join, deletion_ok) &&
(containing_appendrel == NULL || (containing_appendrel == NULL ||
is_safe_append_member(rte->subquery))) is_safe_append_member(rte->subquery)))
return pull_up_simple_subquery(root, jtnode, rte, return pull_up_simple_subquery(root, jtnode, rte,
lowest_outer_join, lowest_outer_join,
lowest_nulling_outer_join, lowest_nulling_outer_join,
containing_appendrel, containing_appendrel);
deletion_ok);
/* /*
* Alternatively, is it a simple UNION ALL subquery? If so, flatten * Alternatively, is it a simple UNION ALL subquery? If so, flatten
...@@ -725,7 +751,7 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, ...@@ -725,7 +751,7 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
if (rte->rtekind == RTE_VALUES && if (rte->rtekind == RTE_VALUES &&
lowest_outer_join == NULL && lowest_outer_join == NULL &&
containing_appendrel == NULL && containing_appendrel == NULL &&
is_simple_values(root, rte, deletion_ok)) is_simple_values(root, rte))
return pull_up_simple_values(root, jtnode, rte); return pull_up_simple_values(root, jtnode, rte);
/* Otherwise, do nothing at this node. */ /* Otherwise, do nothing at this node. */
...@@ -733,50 +759,16 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, ...@@ -733,50 +759,16 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
else if (IsA(jtnode, FromExpr)) else if (IsA(jtnode, FromExpr))
{ {
FromExpr *f = (FromExpr *) jtnode; FromExpr *f = (FromExpr *) jtnode;
bool have_undeleted_child = false;
ListCell *l; ListCell *l;
Assert(containing_appendrel == NULL); Assert(containing_appendrel == NULL);
/* Recursively transform all the child nodes */
/*
* If the FromExpr has quals, it's not deletable even if its parent
* would allow deletion.
*/
if (f->quals)
deletion_ok = false;
foreach(l, f->fromlist) foreach(l, f->fromlist)
{ {
/*
* In a non-deletable FromExpr, we can allow deletion of child
* nodes so long as at least one child remains; so it's okay
* either if any previous child survives, or if there's more to
* come. If all children are deletable in themselves, we'll force
* the last one to remain unflattened.
*
* As a separate matter, we can allow deletion of all children of
* the top-level FromExpr in a query, since that's a special case
* anyway.
*/
bool sub_deletion_ok = (deletion_ok ||
have_undeleted_child ||
lnext(l) != NULL ||
f == root->parse->jointree);
lfirst(l) = pull_up_subqueries_recurse(root, lfirst(l), lfirst(l) = pull_up_subqueries_recurse(root, lfirst(l),
lowest_outer_join, lowest_outer_join,
lowest_nulling_outer_join, lowest_nulling_outer_join,
NULL, NULL);
sub_deletion_ok);
if (lfirst(l) != NULL)
have_undeleted_child = true;
}
if (deletion_ok && !have_undeleted_child)
{
/* OK to delete this FromExpr entirely */
root->hasDeletedRTEs = true; /* probably is set already */
return NULL;
} }
} }
else if (IsA(jtnode, JoinExpr)) else if (IsA(jtnode, JoinExpr))
...@@ -788,22 +780,14 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, ...@@ -788,22 +780,14 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
switch (j->jointype) switch (j->jointype)
{ {
case JOIN_INNER: case JOIN_INNER:
/*
* INNER JOIN can allow deletion of either child node, but not
* both. So right child gets permission to delete only if
* left child didn't get removed.
*/
j->larg = pull_up_subqueries_recurse(root, j->larg, j->larg = pull_up_subqueries_recurse(root, j->larg,
lowest_outer_join, lowest_outer_join,
lowest_nulling_outer_join, lowest_nulling_outer_join,
NULL, NULL);
true);
j->rarg = pull_up_subqueries_recurse(root, j->rarg, j->rarg = pull_up_subqueries_recurse(root, j->rarg,
lowest_outer_join, lowest_outer_join,
lowest_nulling_outer_join, lowest_nulling_outer_join,
NULL, NULL);
j->larg != NULL);
break; break;
case JOIN_LEFT: case JOIN_LEFT:
case JOIN_SEMI: case JOIN_SEMI:
...@@ -811,37 +795,31 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, ...@@ -811,37 +795,31 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
j->larg = pull_up_subqueries_recurse(root, j->larg, j->larg = pull_up_subqueries_recurse(root, j->larg,
j, j,
lowest_nulling_outer_join, lowest_nulling_outer_join,
NULL, NULL);
false);
j->rarg = pull_up_subqueries_recurse(root, j->rarg, j->rarg = pull_up_subqueries_recurse(root, j->rarg,
j, j,
j, j,
NULL, NULL);
false);
break; break;
case JOIN_FULL: case JOIN_FULL:
j->larg = pull_up_subqueries_recurse(root, j->larg, j->larg = pull_up_subqueries_recurse(root, j->larg,
j, j,
j, j,
NULL, NULL);
false);
j->rarg = pull_up_subqueries_recurse(root, j->rarg, j->rarg = pull_up_subqueries_recurse(root, j->rarg,
j, j,
j, j,
NULL, NULL);
false);
break; break;
case JOIN_RIGHT: case JOIN_RIGHT:
j->larg = pull_up_subqueries_recurse(root, j->larg, j->larg = pull_up_subqueries_recurse(root, j->larg,
j, j,
j, j,
NULL, NULL);
false);
j->rarg = pull_up_subqueries_recurse(root, j->rarg, j->rarg = pull_up_subqueries_recurse(root, j->rarg,
j, j,
lowest_nulling_outer_join, lowest_nulling_outer_join,
NULL, NULL);
false);
break; break;
default: default:
elog(ERROR, "unrecognized join type: %d", elog(ERROR, "unrecognized join type: %d",
...@@ -861,8 +839,8 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode, ...@@ -861,8 +839,8 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
* *
* jtnode is a RangeTblRef that has been tentatively identified as a simple * jtnode is a RangeTblRef that has been tentatively identified as a simple
* subquery by pull_up_subqueries. We return the replacement jointree node, * subquery by pull_up_subqueries. We return the replacement jointree node,
* or NULL if the subquery can be deleted entirely, or jtnode itself if we * or jtnode itself if we determine that the subquery can't be pulled up
* determine that the subquery can't be pulled up after all. * after all.
* *
* rte is the RangeTblEntry referenced by jtnode. Remaining parameters are * rte is the RangeTblEntry referenced by jtnode. Remaining parameters are
* as for pull_up_subqueries_recurse. * as for pull_up_subqueries_recurse.
...@@ -871,8 +849,7 @@ static Node * ...@@ -871,8 +849,7 @@ static Node *
pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
JoinExpr *lowest_outer_join, JoinExpr *lowest_outer_join,
JoinExpr *lowest_nulling_outer_join, JoinExpr *lowest_nulling_outer_join,
AppendRelInfo *containing_appendrel, AppendRelInfo *containing_appendrel)
bool deletion_ok)
{ {
Query *parse = root->parse; Query *parse = root->parse;
int varno = ((RangeTblRef *) jtnode)->rtindex; int varno = ((RangeTblRef *) jtnode)->rtindex;
...@@ -925,6 +902,12 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, ...@@ -925,6 +902,12 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
/* No CTEs to worry about */ /* No CTEs to worry about */
Assert(subquery->cteList == NIL); Assert(subquery->cteList == NIL);
/*
* 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(subquery);
/* /*
* Pull up any SubLinks within the subquery's quals, so that we don't * Pull up any SubLinks within the subquery's quals, so that we don't
* leave unoptimized SubLinks behind. * leave unoptimized SubLinks behind.
...@@ -957,8 +940,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, ...@@ -957,8 +940,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
* easier just to keep this "if" looking the same as the one in * easier just to keep this "if" looking the same as the one in
* pull_up_subqueries_recurse. * pull_up_subqueries_recurse.
*/ */
if (is_simple_subquery(subquery, rte, if (is_simple_subquery(subquery, rte, lowest_outer_join) &&
lowest_outer_join, deletion_ok) &&
(containing_appendrel == NULL || is_safe_append_member(subquery))) (containing_appendrel == NULL || is_safe_append_member(subquery)))
{ {
/* good to go */ /* good to go */
...@@ -1159,6 +1141,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, ...@@ -1159,6 +1141,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
case RTE_JOIN: case RTE_JOIN:
case RTE_CTE: case RTE_CTE:
case RTE_NAMEDTUPLESTORE: case RTE_NAMEDTUPLESTORE:
case RTE_RESULT:
/* these can't contain any lateral references */ /* these can't contain any lateral references */
break; break;
} }
...@@ -1195,7 +1178,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, ...@@ -1195,7 +1178,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
Relids subrelids; Relids subrelids;
subrelids = get_relids_in_jointree((Node *) subquery->jointree, false); subrelids = get_relids_in_jointree((Node *) subquery->jointree, false);
substitute_multiple_relids((Node *) parse, varno, subrelids); substitute_phv_relids((Node *) parse, varno, subrelids);
fix_append_rel_relids(root->append_rel_list, varno, subrelids); fix_append_rel_relids(root->append_rel_list, varno, subrelids);
} }
...@@ -1235,17 +1218,14 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, ...@@ -1235,17 +1218,14 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
/* /*
* Return the adjusted subquery jointree to replace the RangeTblRef entry * Return the adjusted subquery jointree to replace the RangeTblRef entry
* in parent's jointree; or, if we're flattening a subquery with empty * in parent's jointree; or, if the FromExpr is degenerate, just return
* FROM list, return NULL to signal deletion of the subquery from the * its single member.
* parent jointree (and set hasDeletedRTEs to ensure cleanup later).
*/ */
if (subquery->jointree->fromlist == NIL) Assert(IsA(subquery->jointree, FromExpr));
{ Assert(subquery->jointree->fromlist != NIL);
Assert(deletion_ok); if (subquery->jointree->quals == NULL &&
Assert(subquery->jointree->quals == NULL); list_length(subquery->jointree->fromlist) == 1)
root->hasDeletedRTEs = true; return (Node *) linitial(subquery->jointree->fromlist);
return NULL;
}
return (Node *) subquery->jointree; return (Node *) subquery->jointree;
} }
...@@ -1381,7 +1361,7 @@ pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex, ...@@ -1381,7 +1361,7 @@ pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex,
rtr = makeNode(RangeTblRef); rtr = makeNode(RangeTblRef);
rtr->rtindex = childRTindex; rtr->rtindex = childRTindex;
(void) pull_up_subqueries_recurse(root, (Node *) rtr, (void) pull_up_subqueries_recurse(root, (Node *) rtr,
NULL, NULL, appinfo, false); NULL, NULL, appinfo);
} }
else if (IsA(setOp, SetOperationStmt)) else if (IsA(setOp, SetOperationStmt))
{ {
...@@ -1436,12 +1416,10 @@ make_setop_translation_list(Query *query, Index newvarno, ...@@ -1436,12 +1416,10 @@ make_setop_translation_list(Query *query, Index newvarno,
* (Note subquery is not necessarily equal to rte->subquery; it could be a * (Note subquery is not necessarily equal to rte->subquery; it could be a
* processed copy of that.) * processed copy of that.)
* lowest_outer_join is the lowest outer join above the subquery, or NULL. * lowest_outer_join is the lowest outer join above the subquery, or NULL.
* deletion_ok is true if it'd be okay to delete the subquery entirely.
*/ */
static bool static bool
is_simple_subquery(Query *subquery, RangeTblEntry *rte, is_simple_subquery(Query *subquery, RangeTblEntry *rte,
JoinExpr *lowest_outer_join, JoinExpr *lowest_outer_join)
bool deletion_ok)
{ {
/* /*
* Let's just make sure it's a valid subselect ... * Let's just make sure it's a valid subselect ...
...@@ -1490,44 +1468,6 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte, ...@@ -1490,44 +1468,6 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte,
if (rte->security_barrier) if (rte->security_barrier)
return false; return false;
/*
* Don't pull up a subquery with an empty jointree, unless it has no quals
* and deletion_ok is true and we're not underneath an outer join.
*
* query_planner() will correctly generate a Result plan for a jointree
* that's totally empty, but we can't cope with an empty FromExpr
* appearing lower down in a jointree: we identify join rels via baserelid
* sets, so we couldn't distinguish a join containing such a FromExpr from
* one without it. We can only handle such cases if the place where the
* subquery is linked is a FromExpr or inner JOIN that would still be
* nonempty after removal of the subquery, so that it's still identifiable
* via its contained baserelids. Safe contexts are signaled by
* deletion_ok.
*
* But even in a safe context, we must keep the subquery if it has any
* quals, because it's unclear where to put them in the upper query.
*
* Also, we must forbid pullup if such a subquery is underneath an outer
* join, because then we might need to wrap its output columns with
* PlaceHolderVars, and the PHVs would then have empty relid sets meaning
* we couldn't tell where to evaluate them. (This test is separate from
* the deletion_ok flag for possible future expansion: deletion_ok tells
* whether the immediate parent site in the jointree could cope, not
* whether we'd have PHV issues. It's possible this restriction could be
* fixed by letting the PHVs use the relids of the parent jointree item,
* but that complication is for another day.)
*
* Note that deletion of a subquery is also dependent on the check below
* that its targetlist contains no set-returning functions. Deletion from
* a FROM list or inner JOIN is okay only if the subquery must return
* exactly one row.
*/
if (subquery->jointree->fromlist == NIL &&
(subquery->jointree->quals != NULL ||
!deletion_ok ||
lowest_outer_join != NULL))
return false;
/* /*
* If the subquery is LATERAL, check for pullup restrictions from that. * If the subquery is LATERAL, check for pullup restrictions from that.
*/ */
...@@ -1602,9 +1542,10 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte, ...@@ -1602,9 +1542,10 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte,
* Pull up a single simple VALUES RTE. * Pull up a single simple VALUES RTE.
* *
* jtnode is a RangeTblRef that has been identified as a simple VALUES RTE * jtnode is a RangeTblRef that has been identified as a simple VALUES RTE
* by pull_up_subqueries. We always return NULL indicating that the RTE * by pull_up_subqueries. We always return a RangeTblRef representing a
* can be deleted entirely (all failure cases should have been detected by * RESULT RTE to replace it (all failure cases should have been detected by
* is_simple_values()). * is_simple_values()). Actually, what we return is just jtnode, because
* we replace the VALUES RTE in the rangetable with the RESULT RTE.
* *
* rte is the RangeTblEntry referenced by jtnode. Because of the limited * rte is the RangeTblEntry referenced by jtnode. Because of the limited
* possible usage of VALUES RTEs, we do not need the remaining parameters * possible usage of VALUES RTEs, we do not need the remaining parameters
...@@ -1703,11 +1644,23 @@ pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte) ...@@ -1703,11 +1644,23 @@ pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
Assert(root->placeholder_list == NIL); Assert(root->placeholder_list == NIL);
/* /*
* Return NULL to signal deletion of the VALUES RTE from the parent * Replace the VALUES RTE with a RESULT RTE. The VALUES RTE is the only
* jointree (and set hasDeletedRTEs to ensure cleanup later). * rtable entry in the current query level, so this is easy.
*/ */
root->hasDeletedRTEs = true; Assert(list_length(parse->rtable) == 1);
return NULL;
/* Create suitable RTE */
rte = makeNode(RangeTblEntry);
rte->rtekind = RTE_RESULT;
rte->eref = makeAlias("*RESULT*", NIL);
/* Replace rangetable */
parse->rtable = list_make1(rte);
/* We could manufacture a new RangeTblRef, but the one we have is fine */
Assert(varno == 1);
return jtnode;
} }
/* /*
...@@ -1716,24 +1669,16 @@ pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte) ...@@ -1716,24 +1669,16 @@ pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
* to pull up into the parent query. * to pull up into the parent query.
* *
* rte is the RTE_VALUES RangeTblEntry to check. * rte is the RTE_VALUES RangeTblEntry to check.
* deletion_ok is true if it'd be okay to delete the VALUES RTE entirely.
*/ */
static bool static bool
is_simple_values(PlannerInfo *root, RangeTblEntry *rte, bool deletion_ok) is_simple_values(PlannerInfo *root, RangeTblEntry *rte)
{ {
Assert(rte->rtekind == RTE_VALUES); Assert(rte->rtekind == RTE_VALUES);
/* /*
* We can only pull up a VALUES RTE if deletion_ok is true. It's * There must be exactly one VALUES list, else it's not semantically
* basically the same case as a sub-select with empty FROM list; see * correct to replace the VALUES RTE with a RESULT RTE, nor would we have
* comments in is_simple_subquery(). * a unique set of expressions to substitute into the parent query.
*/
if (!deletion_ok)
return false;
/*
* Also, there must be exactly one VALUES list, else it's not semantically
* correct to delete the VALUES RTE.
*/ */
if (list_length(rte->values_lists) != 1) if (list_length(rte->values_lists) != 1)
return false; return false;
...@@ -1746,8 +1691,8 @@ is_simple_values(PlannerInfo *root, RangeTblEntry *rte, bool deletion_ok) ...@@ -1746,8 +1691,8 @@ is_simple_values(PlannerInfo *root, RangeTblEntry *rte, bool deletion_ok)
/* /*
* Don't pull up a VALUES that contains any set-returning or volatile * Don't pull up a VALUES that contains any set-returning or volatile
* functions. Again, the considerations here are basically identical to * functions. The considerations here are basically identical to the
* restrictions on a subquery's targetlist. * restrictions on a pull-able subquery's targetlist.
*/ */
if (expression_returns_set((Node *) rte->values_lists) || if (expression_returns_set((Node *) rte->values_lists) ||
contain_volatile_functions((Node *) rte->values_lists)) contain_volatile_functions((Node *) rte->values_lists))
...@@ -1850,7 +1795,9 @@ is_safe_append_member(Query *subquery) ...@@ -1850,7 +1795,9 @@ is_safe_append_member(Query *subquery)
/* /*
* It's only safe to pull up the child if its jointree contains exactly * It's only safe to pull up the child if its jointree contains exactly
* one RTE, else the AppendRelInfo data structure breaks. The one base RTE * one RTE, else the AppendRelInfo data structure breaks. The one base RTE
* could be buried in several levels of FromExpr, however. * could be buried in several levels of FromExpr, however. Also, if the
* child's jointree is completely empty, we can pull up because
* pull_up_simple_subquery will insert a single RTE_RESULT RTE instead.
* *
* Also, the child can't have any WHERE quals because there's no place to * Also, the child can't have any WHERE quals because there's no place to
* put them in an appendrel. (This is a bit annoying...) If we didn't * put them in an appendrel. (This is a bit annoying...) If we didn't
...@@ -1859,6 +1806,11 @@ is_safe_append_member(Query *subquery) ...@@ -1859,6 +1806,11 @@ is_safe_append_member(Query *subquery)
* fix_append_rel_relids(). * fix_append_rel_relids().
*/ */
jtnode = subquery->jointree; jtnode = subquery->jointree;
Assert(IsA(jtnode, FromExpr));
/* Check the completely-empty case */
if (jtnode->fromlist == NIL && jtnode->quals == NULL)
return true;
/* Check the more general case */
while (IsA(jtnode, FromExpr)) while (IsA(jtnode, FromExpr))
{ {
if (jtnode->quals != NULL) if (jtnode->quals != NULL)
...@@ -2014,6 +1966,7 @@ replace_vars_in_jointree(Node *jtnode, ...@@ -2014,6 +1966,7 @@ replace_vars_in_jointree(Node *jtnode,
case RTE_JOIN: case RTE_JOIN:
case RTE_CTE: case RTE_CTE:
case RTE_NAMEDTUPLESTORE: case RTE_NAMEDTUPLESTORE:
case RTE_RESULT:
/* these shouldn't be marked LATERAL */ /* these shouldn't be marked LATERAL */
Assert(false); Assert(false);
break; break;
...@@ -2290,65 +2243,6 @@ pullup_replace_vars_subquery(Query *query, ...@@ -2290,65 +2243,6 @@ pullup_replace_vars_subquery(Query *query,
NULL); NULL);
} }
/*
* pull_up_subqueries_cleanup
* Recursively fix up jointree after deletion of some subqueries.
*
* The jointree now contains some NULL subtrees, which we need to get rid of.
* In a FromExpr, just rebuild the child-node list with null entries deleted.
* In an inner JOIN, replace the JoinExpr node with a one-child FromExpr.
*/
static Node *
pull_up_subqueries_cleanup(Node *jtnode)
{
Assert(jtnode != NULL);
if (IsA(jtnode, RangeTblRef))
{
/* Nothing to do at leaf nodes. */
}
else if (IsA(jtnode, FromExpr))
{
FromExpr *f = (FromExpr *) jtnode;
List *newfrom = NIL;
ListCell *l;
foreach(l, f->fromlist)
{
Node *child = (Node *) lfirst(l);
if (child == NULL)
continue;
child = pull_up_subqueries_cleanup(child);
newfrom = lappend(newfrom, child);
}
f->fromlist = newfrom;
}
else if (IsA(jtnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) jtnode;
if (j->larg)
j->larg = pull_up_subqueries_cleanup(j->larg);
if (j->rarg)
j->rarg = pull_up_subqueries_cleanup(j->rarg);
if (j->larg == NULL)
{
Assert(j->jointype == JOIN_INNER);
Assert(j->rarg != NULL);
return (Node *) makeFromExpr(list_make1(j->rarg), j->quals);
}
else if (j->rarg == NULL)
{
Assert(j->jointype == JOIN_INNER);
return (Node *) makeFromExpr(list_make1(j->larg), j->quals);
}
}
else
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(jtnode));
return jtnode;
}
/* /*
* flatten_simple_union_all * flatten_simple_union_all
...@@ -2858,9 +2752,399 @@ reduce_outer_joins_pass2(Node *jtnode, ...@@ -2858,9 +2752,399 @@ reduce_outer_joins_pass2(Node *jtnode,
(int) nodeTag(jtnode)); (int) nodeTag(jtnode));
} }
/*
* remove_useless_result_rtes
* Attempt to remove RTE_RESULT RTEs from the join tree.
*
* We can remove RTE_RESULT entries from the join tree using the knowledge
* that RTE_RESULT returns exactly one row and has no output columns. Hence,
* if one is inner-joined to anything else, we can delete it. Optimizations
* are also possible for some outer-join cases, as detailed below.
*
* Some of these optimizations depend on recognizing empty (constant-true)
* quals for FromExprs and JoinExprs. That makes it useful to apply this
* optimization pass after expression preprocessing, since that will have
* eliminated constant-true quals, allowing more cases to be recognized as
* optimizable. What's more, the usual reason for an RTE_RESULT to be present
* is that we pulled up a subquery or VALUES clause, thus very possibly
* replacing Vars with constants, making it more likely that a qual can be
* reduced to constant true. Also, because some optimizations depend on
* the outer-join type, it's best to have done reduce_outer_joins() first.
*
* A PlaceHolderVar referencing an RTE_RESULT RTE poses an obstacle to this
* process: we must remove the RTE_RESULT's relid from the PHV's phrels, but
* we must not reduce the phrels set to empty. If that would happen, and
* the RTE_RESULT is an immediate child of an outer join, we have to give up
* and not remove the RTE_RESULT: there is noplace else to evaluate the
* PlaceHolderVar. (That is, in such cases the RTE_RESULT *does* have output
* columns.) But if the RTE_RESULT is an immediate child of an inner join,
* we can change the PlaceHolderVar's phrels so as to evaluate it at the
* inner join instead. This is OK because we really only care that PHVs are
* evaluated above or below the correct outer joins.
*
* We used to try to do this work as part of pull_up_subqueries() where the
* potentially-optimizable cases get introduced; but it's way simpler, and
* more effective, to do it separately.
*/
void
remove_useless_result_rtes(PlannerInfo *root)
{
ListCell *cell;
ListCell *prev;
ListCell *next;
/* Top level of jointree must always be a FromExpr */
Assert(IsA(root->parse->jointree, FromExpr));
/* Recurse ... */
root->parse->jointree = (FromExpr *)
remove_useless_results_recurse(root, (Node *) root->parse->jointree);
/* We should still have a FromExpr */
Assert(IsA(root->parse->jointree, FromExpr));
/*
* Remove any PlanRowMark referencing an RTE_RESULT RTE. We obviously
* must do that for any RTE_RESULT that we just removed. But one for a
* RTE that we did not remove can be dropped anyway: since the RTE has
* only one possible output row, there is no need for EPQ to mark and
* restore that row.
*
* It's necessary, not optional, to remove the PlanRowMark for a surviving
* RTE_RESULT RTE; otherwise we'll generate a whole-row Var for the
* RTE_RESULT, which the executor has no support for.
*/
prev = NULL;
for (cell = list_head(root->rowMarks); cell; cell = next)
{
PlanRowMark *rc = (PlanRowMark *) lfirst(cell);
next = lnext(cell);
if (rt_fetch(rc->rti, root->parse->rtable)->rtekind == RTE_RESULT)
root->rowMarks = list_delete_cell(root->rowMarks, cell, prev);
else
prev = cell;
}
}
/*
* remove_useless_results_recurse
* Recursive guts of remove_useless_result_rtes.
*
* This recursively processes the jointree and returns a modified jointree.
*/
static Node *
remove_useless_results_recurse(PlannerInfo *root, Node *jtnode)
{
Assert(jtnode != NULL);
if (IsA(jtnode, RangeTblRef))
{
/* Can't immediately do anything with a RangeTblRef */
}
else if (IsA(jtnode, FromExpr))
{
FromExpr *f = (FromExpr *) jtnode;
Relids result_relids = NULL;
ListCell *cell;
ListCell *prev;
ListCell *next;
/*
* We can drop RTE_RESULT rels from the fromlist so long as at least
* one child remains, since joining to a one-row table changes
* nothing. The easiest way to mechanize this rule is to modify the
* list in-place, using list_delete_cell.
*/
prev = NULL;
for (cell = list_head(f->fromlist); cell; cell = next)
{
Node *child = (Node *) lfirst(cell);
int varno;
/* Recursively transform child ... */
child = remove_useless_results_recurse(root, child);
/* ... and stick it back into the tree */
lfirst(cell) = child;
next = lnext(cell);
/*
* If it's an RTE_RESULT with at least one sibling, we can drop
* it. We don't yet know what the inner join's final relid set
* will be, so postpone cleanup of PHVs etc till after this loop.
*/
if (list_length(f->fromlist) > 1 &&
(varno = get_result_relid(root, child)) != 0)
{
f->fromlist = list_delete_cell(f->fromlist, cell, prev);
result_relids = bms_add_member(result_relids, varno);
}
else
prev = cell;
}
/*
* Clean up if we dropped any RTE_RESULT RTEs. This is a bit
* inefficient if there's more than one, but it seems better to
* optimize the support code for the single-relid case.
*/
if (result_relids)
{
int varno = -1;
while ((varno = bms_next_member(result_relids, varno)) >= 0)
remove_result_refs(root, varno, (Node *) f);
}
/*
* If we're not at the top of the jointree, it's valid to simplify a
* degenerate FromExpr into its single child. (At the top, we must
* keep the FromExpr since Query.jointree is required to point to a
* FromExpr.)
*/
if (f != root->parse->jointree &&
f->quals == NULL &&
list_length(f->fromlist) == 1)
return (Node *) linitial(f->fromlist);
}
else if (IsA(jtnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) jtnode;
int varno;
/* First, recurse */
j->larg = remove_useless_results_recurse(root, j->larg);
j->rarg = remove_useless_results_recurse(root, j->rarg);
/* Apply join-type-specific optimization rules */
switch (j->jointype)
{
case JOIN_INNER:
/*
* An inner join is equivalent to a FromExpr, so if either
* side was simplified to an RTE_RESULT rel, we can replace
* the join with a FromExpr with just the other side; and if
* the qual is empty (JOIN ON TRUE) then we can omit the
* FromExpr as well.
*/
if ((varno = get_result_relid(root, j->larg)) != 0)
{
remove_result_refs(root, varno, j->rarg);
if (j->quals)
jtnode = (Node *)
makeFromExpr(list_make1(j->rarg), j->quals);
else
jtnode = j->rarg;
}
else if ((varno = get_result_relid(root, j->rarg)) != 0)
{
remove_result_refs(root, varno, j->larg);
if (j->quals)
jtnode = (Node *)
makeFromExpr(list_make1(j->larg), j->quals);
else
jtnode = j->larg;
}
break;
case JOIN_LEFT:
/*
* We can simplify this case if the RHS is an RTE_RESULT, with
* two different possibilities:
*
* If the qual is empty (JOIN ON TRUE), then the join can be
* strength-reduced to a plain inner join, since each LHS row
* necessarily has exactly one join partner. So we can always
* discard the RHS, much as in the JOIN_INNER case above.
*
* Otherwise, it's still true that each LHS row should be
* returned exactly once, and since the RHS returns no columns
* (unless there are PHVs that have to be evaluated there), we
* don't much care if it's null-extended or not. So in this
* case also, we can just ignore the qual and discard the left
* join.
*/
if ((varno = get_result_relid(root, j->rarg)) != 0 &&
(j->quals == NULL ||
!find_dependent_phvs((Node *) root->parse, varno)))
{
remove_result_refs(root, varno, j->larg);
jtnode = j->larg;
}
break;
case JOIN_RIGHT:
/* Mirror-image of the JOIN_LEFT case */
if ((varno = get_result_relid(root, j->larg)) != 0 &&
(j->quals == NULL ||
!find_dependent_phvs((Node *) root->parse, varno)))
{
remove_result_refs(root, varno, j->rarg);
jtnode = j->rarg;
}
break;
case JOIN_SEMI:
/*
* We may simplify this case if the RHS is an RTE_RESULT; the
* join qual becomes effectively just a filter qual for the
* LHS, since we should either return the LHS row or not. For
* simplicity we inject the filter qual into a new FromExpr.
*
* Unlike the LEFT/RIGHT cases, we just Assert that there are
* no PHVs that need to be evaluated at the semijoin's RHS,
* since the rest of the query couldn't reference any outputs
* of the semijoin's RHS.
*/
if ((varno = get_result_relid(root, j->rarg)) != 0)
{
Assert(!find_dependent_phvs((Node *) root->parse, varno));
remove_result_refs(root, varno, j->larg);
if (j->quals)
jtnode = (Node *)
makeFromExpr(list_make1(j->larg), j->quals);
else
jtnode = j->larg;
}
break;
case JOIN_FULL:
case JOIN_ANTI:
/* We have no special smarts for these cases */
break;
default:
elog(ERROR, "unrecognized join type: %d",
(int) j->jointype);
break;
}
}
else
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(jtnode));
return jtnode;
}
/*
* get_result_relid
* If jtnode is a RangeTblRef for an RTE_RESULT RTE, return its relid;
* otherwise return 0.
*/
static inline int
get_result_relid(PlannerInfo *root, Node *jtnode)
{
int varno;
if (!IsA(jtnode, RangeTblRef))
return 0;
varno = ((RangeTblRef *) jtnode)->rtindex;
if (rt_fetch(varno, root->parse->rtable)->rtekind != RTE_RESULT)
return 0;
return varno;
}
/*
* remove_result_refs
* Helper routine for dropping an unneeded RTE_RESULT RTE.
*
* This doesn't physically remove the RTE from the jointree, because that's
* more easily handled in remove_useless_results_recurse. What it does do
* is the necessary cleanup in the rest of the tree: we must adjust any PHVs
* that may reference the RTE. Be sure to call this at a point where the
* jointree is valid (no disconnected nodes).
*
* Note that we don't need to process the append_rel_list, since RTEs
* referenced directly in the jointree won't be appendrel members.
*
* varno is the RTE_RESULT's relid.
* newjtloc is the jointree location at which any PHVs referencing the
* RTE_RESULT should be evaluated instead.
*/
static void
remove_result_refs(PlannerInfo *root, int varno, Node *newjtloc)
{
/* Fix up PlaceHolderVars as needed */
/* If there are no PHVs anywhere, we can skip this bit */
if (root->glob->lastPHId != 0)
{
Relids subrelids;
subrelids = get_relids_in_jointree(newjtloc, false);
Assert(!bms_is_empty(subrelids));
substitute_phv_relids((Node *) root->parse, varno, subrelids);
}
/*
* We also need to remove any PlanRowMark referencing the RTE, but we
* postpone that work until we return to remove_useless_result_rtes.
*/
}
/*
* find_dependent_phvs - are there any PlaceHolderVars whose relids are
* exactly the given varno?
*/
typedef struct
{
Relids relids;
int sublevels_up;
} find_dependent_phvs_context;
static bool
find_dependent_phvs_walker(Node *node,
find_dependent_phvs_context *context)
{
if (node == NULL)
return false;
if (IsA(node, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
if (phv->phlevelsup == context->sublevels_up &&
bms_equal(context->relids, phv->phrels))
return true;
/* fall through to examine children */
}
if (IsA(node, Query))
{
/* Recurse into subselects */
bool result;
context->sublevels_up++;
result = query_tree_walker((Query *) node,
find_dependent_phvs_walker,
(void *) context, 0);
context->sublevels_up--;
return result;
}
/* Shouldn't need to handle planner auxiliary nodes here */
Assert(!IsA(node, SpecialJoinInfo));
Assert(!IsA(node, AppendRelInfo));
Assert(!IsA(node, PlaceHolderInfo));
Assert(!IsA(node, MinMaxAggInfo));
return expression_tree_walker(node, find_dependent_phvs_walker,
(void *) context);
}
static bool
find_dependent_phvs(Node *node, int varno)
{
find_dependent_phvs_context context;
context.relids = bms_make_singleton(varno);
context.sublevels_up = 0;
/*
* Must be prepared to start with a Query or a bare expression tree.
*/
return query_or_expression_tree_walker(node,
find_dependent_phvs_walker,
(void *) &context,
0);
}
/* /*
* substitute_multiple_relids - adjust node relid sets after pulling up * substitute_phv_relids - adjust PlaceHolderVar relid sets after pulling up
* a subquery * a subquery or removing an RTE_RESULT jointree item
* *
* Find any PlaceHolderVar nodes in the given tree that reference the * Find any PlaceHolderVar nodes in the given tree that reference the
* pulled-up relid, and change them to reference the replacement relid(s). * pulled-up relid, and change them to reference the replacement relid(s).
...@@ -2876,11 +3160,11 @@ typedef struct ...@@ -2876,11 +3160,11 @@ typedef struct
int varno; int varno;
int sublevels_up; int sublevels_up;
Relids subrelids; Relids subrelids;
} substitute_multiple_relids_context; } substitute_phv_relids_context;
static bool static bool
substitute_multiple_relids_walker(Node *node, substitute_phv_relids_walker(Node *node,
substitute_multiple_relids_context *context) substitute_phv_relids_context *context)
{ {
if (node == NULL) if (node == NULL)
return false; return false;
...@@ -2895,6 +3179,8 @@ substitute_multiple_relids_walker(Node *node, ...@@ -2895,6 +3179,8 @@ substitute_multiple_relids_walker(Node *node,
context->subrelids); context->subrelids);
phv->phrels = bms_del_member(phv->phrels, phv->phrels = bms_del_member(phv->phrels,
context->varno); context->varno);
/* Assert we haven't broken the PHV */
Assert(!bms_is_empty(phv->phrels));
} }
/* fall through to examine children */ /* fall through to examine children */
} }
...@@ -2905,7 +3191,7 @@ substitute_multiple_relids_walker(Node *node, ...@@ -2905,7 +3191,7 @@ substitute_multiple_relids_walker(Node *node,
context->sublevels_up++; context->sublevels_up++;
result = query_tree_walker((Query *) node, result = query_tree_walker((Query *) node,
substitute_multiple_relids_walker, substitute_phv_relids_walker,
(void *) context, 0); (void *) context, 0);
context->sublevels_up--; context->sublevels_up--;
return result; return result;
...@@ -2916,14 +3202,14 @@ substitute_multiple_relids_walker(Node *node, ...@@ -2916,14 +3202,14 @@ substitute_multiple_relids_walker(Node *node,
Assert(!IsA(node, PlaceHolderInfo)); Assert(!IsA(node, PlaceHolderInfo));
Assert(!IsA(node, MinMaxAggInfo)); Assert(!IsA(node, MinMaxAggInfo));
return expression_tree_walker(node, substitute_multiple_relids_walker, return expression_tree_walker(node, substitute_phv_relids_walker,
(void *) context); (void *) context);
} }
static void static void
substitute_multiple_relids(Node *node, int varno, Relids subrelids) substitute_phv_relids(Node *node, int varno, Relids subrelids)
{ {
substitute_multiple_relids_context context; substitute_phv_relids_context context;
context.varno = varno; context.varno = varno;
context.sublevels_up = 0; context.sublevels_up = 0;
...@@ -2933,7 +3219,7 @@ substitute_multiple_relids(Node *node, int varno, Relids subrelids) ...@@ -2933,7 +3219,7 @@ substitute_multiple_relids(Node *node, int varno, Relids subrelids)
* Must be prepared to start with a Query or a bare expression tree. * Must be prepared to start with a Query or a bare expression tree.
*/ */
query_or_expression_tree_walker(node, query_or_expression_tree_walker(node,
substitute_multiple_relids_walker, substitute_phv_relids_walker,
(void *) &context, (void *) &context,
0); 0);
} }
...@@ -2943,7 +3229,7 @@ substitute_multiple_relids(Node *node, int varno, Relids subrelids) ...@@ -2943,7 +3229,7 @@ substitute_multiple_relids(Node *node, int varno, Relids subrelids)
* *
* When we pull up a subquery, any AppendRelInfo references to the subquery's * When we pull up a subquery, any AppendRelInfo references to the subquery's
* RT index have to be replaced by the substituted relid (and there had better * RT index have to be replaced by the substituted relid (and there had better
* be only one). We also need to apply substitute_multiple_relids to their * be only one). We also need to apply substitute_phv_relids to their
* translated_vars lists, since those might contain PlaceHolderVars. * translated_vars lists, since those might contain PlaceHolderVars.
* *
* We assume we may modify the AppendRelInfo nodes in-place. * We assume we may modify the AppendRelInfo nodes in-place.
...@@ -2974,8 +3260,8 @@ fix_append_rel_relids(List *append_rel_list, int varno, Relids subrelids) ...@@ -2974,8 +3260,8 @@ fix_append_rel_relids(List *append_rel_list, int varno, Relids subrelids)
appinfo->child_relid = subvarno; appinfo->child_relid = subvarno;
} }
/* Also finish fixups for its translated vars */ /* Also fix up any PHVs in its translated vars */
substitute_multiple_relids((Node *) appinfo->translated_vars, substitute_phv_relids((Node *) appinfo->translated_vars,
varno, subrelids); varno, subrelids);
} }
} }
......
...@@ -1707,7 +1707,7 @@ contain_leaked_vars_walker(Node *node, void *context) ...@@ -1707,7 +1707,7 @@ contain_leaked_vars_walker(Node *node, void *context)
* find_nonnullable_vars() is that the tested conditions really are different: * 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 * 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 * 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 * 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 * 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) ...@@ -1893,7 +1893,24 @@ find_nonnullable_rels_walker(Node *node, bool top_level)
{ {
PlaceHolderVar *phv = (PlaceHolderVar *) node; 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); 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; return result;
} }
......
...@@ -1430,17 +1430,17 @@ create_merge_append_path(PlannerInfo *root, ...@@ -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. * Creates a path representing a Result-and-nothing-else plan.
* *
* This is only used for degenerate cases, such as a query with an empty * This is only used for degenerate grouping cases, in which we know we
* jointree. * need to produce one result row, possibly filtered by a HAVING qual.
*/ */
ResultPath * GroupResultPath *
create_result_path(PlannerInfo *root, RelOptInfo *rel, create_group_result_path(PlannerInfo *root, RelOptInfo *rel,
PathTarget *target, List *resconstantqual) PathTarget *target, List *havingqual)
{ {
ResultPath *pathnode = makeNode(ResultPath); GroupResultPath *pathnode = makeNode(GroupResultPath);
pathnode->path.pathtype = T_Result; pathnode->path.pathtype = T_Result;
pathnode->path.parent = rel; pathnode->path.parent = rel;
...@@ -1450,9 +1450,13 @@ create_result_path(PlannerInfo *root, RelOptInfo *rel, ...@@ -1450,9 +1450,13 @@ create_result_path(PlannerInfo *root, RelOptInfo *rel,
pathnode->path.parallel_safe = rel->consider_parallel; pathnode->path.parallel_safe = rel->consider_parallel;
pathnode->path.parallel_workers = 0; pathnode->path.parallel_workers = 0;
pathnode->path.pathkeys = NIL; 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.rows = 1;
pathnode->path.startup_cost = target->cost.startup; pathnode->path.startup_cost = target->cost.startup;
pathnode->path.total_cost = target->cost.startup + pathnode->path.total_cost = target->cost.startup +
...@@ -1462,12 +1466,12 @@ create_result_path(PlannerInfo *root, RelOptInfo *rel, ...@@ -1462,12 +1466,12 @@ create_result_path(PlannerInfo *root, RelOptInfo *rel,
* Add cost of qual, if any --- but we ignore its selectivity, since our * Add cost of qual, if any --- but we ignore its selectivity, since our
* rowcount estimate should be 1 no matter what the qual is. * rowcount estimate should be 1 no matter what the qual is.
*/ */
if (resconstantqual) if (havingqual)
{ {
QualCost qual_cost; QualCost qual_cost;
cost_qual_eval(&qual_cost, resconstantqual, root); cost_qual_eval(&qual_cost, havingqual, root);
/* resconstantqual is evaluated once at startup */ /* havingqual is evaluated once at startup */
pathnode->path.startup_cost += qual_cost.startup + qual_cost.per_tuple; pathnode->path.startup_cost += qual_cost.startup + qual_cost.per_tuple;
pathnode->path.total_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, ...@@ -2020,6 +2024,32 @@ create_namedtuplestorescan_path(PlannerInfo *root, RelOptInfo *rel,
return pathnode; 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 * create_worktablescan_path
* Creates a path corresponding to a scan of a self-reference CTE, * Creates a path corresponding to a scan of a self-reference CTE,
...@@ -3560,6 +3590,11 @@ reparameterize_path(PlannerInfo *root, Path *path, ...@@ -3560,6 +3590,11 @@ reparameterize_path(PlannerInfo *root, Path *path,
spath->path.pathkeys, spath->path.pathkeys,
required_outer); 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: case T_Append:
{ {
AppendPath *apath = (AppendPath *) path; AppendPath *apath = (AppendPath *) path;
......
...@@ -1628,6 +1628,7 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel) ...@@ -1628,6 +1628,7 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
case RTE_VALUES: case RTE_VALUES:
case RTE_CTE: case RTE_CTE:
case RTE_NAMEDTUPLESTORE: case RTE_NAMEDTUPLESTORE:
case RTE_RESULT:
/* Not all of these can have dropped cols, but share code anyway */ /* Not all of these can have dropped cols, but share code anyway */
expandRTE(rte, varno, 0, -1, true /* include dropped */ , expandRTE(rte, varno, 0, -1, true /* include dropped */ ,
NULL, &colvars); NULL, &colvars);
......
...@@ -247,6 +247,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) ...@@ -247,6 +247,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
rel->attr_widths = (int32 *) rel->attr_widths = (int32 *)
palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(int32)); palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(int32));
break; 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: default:
elog(ERROR, "unrecognized RTE kind: %d", elog(ERROR, "unrecognized RTE kind: %d",
(int) rte->rtekind); (int) rte->rtekind);
...@@ -1108,36 +1115,6 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel, ...@@ -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 * fetch_upper_rel
* Build a RelOptInfo describing some post-scan/join query processing, * Build a RelOptInfo describing some post-scan/join query processing,
......
...@@ -2884,6 +2884,9 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc, ...@@ -2884,6 +2884,9 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
LCS_asString(lc->strength)), LCS_asString(lc->strength)),
parser_errposition(pstate, thisrel->location))); parser_errposition(pstate, thisrel->location)));
break; break;
/* Shouldn't be possible to see RTE_RESULT here */
default: default:
elog(ERROR, "unrecognized RTE type: %d", elog(ERROR, "unrecognized RTE type: %d",
(int) rte->rtekind); (int) rte->rtekind);
......
...@@ -2519,6 +2519,9 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, ...@@ -2519,6 +2519,9 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
} }
} }
break; break;
case RTE_RESULT:
/* These expose no columns, so nothing to do */
break;
default: default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind); elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
} }
...@@ -2911,6 +2914,14 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, ...@@ -2911,6 +2914,14 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
rte->eref->aliasname))); rte->eref->aliasname)));
} }
break; 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: default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind); elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
} }
...@@ -3039,6 +3050,15 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum) ...@@ -3039,6 +3050,15 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
result = false; /* keep compiler quiet */ result = false; /* keep compiler quiet */
} }
break; 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: default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind); elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
result = false; /* keep compiler quiet */ result = false; /* keep compiler quiet */
......
...@@ -398,6 +398,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle, ...@@ -398,6 +398,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
case RTE_VALUES: case RTE_VALUES:
case RTE_TABLEFUNC: case RTE_TABLEFUNC:
case RTE_NAMEDTUPLESTORE: case RTE_NAMEDTUPLESTORE:
case RTE_RESULT:
/* not a simple relation, leave it unmarked */ /* not a simple relation, leave it unmarked */
break; break;
case RTE_CTE: case RTE_CTE:
...@@ -1531,6 +1532,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup) ...@@ -1531,6 +1532,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
case RTE_RELATION: case RTE_RELATION:
case RTE_VALUES: case RTE_VALUES:
case RTE_NAMEDTUPLESTORE: case RTE_NAMEDTUPLESTORE:
case RTE_RESULT:
/* /*
* This case should not occur: a column of a table, values list, * 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, ...@@ -7011,6 +7011,7 @@ get_name_for_var_field(Var *var, int fieldno,
case RTE_RELATION: case RTE_RELATION:
case RTE_VALUES: case RTE_VALUES:
case RTE_NAMEDTUPLESTORE: case RTE_NAMEDTUPLESTORE:
case RTE_RESULT:
/* /*
* This case should not occur: a column of a table, values list, * This case should not occur: a column of a table, values list,
......
...@@ -237,7 +237,7 @@ typedef enum NodeTag ...@@ -237,7 +237,7 @@ typedef enum NodeTag
T_HashPath, T_HashPath,
T_AppendPath, T_AppendPath,
T_MergeAppendPath, T_MergeAppendPath,
T_ResultPath, T_GroupResultPath,
T_MaterialPath, T_MaterialPath,
T_UniquePath, T_UniquePath,
T_GatherPath, T_GatherPath,
......
...@@ -950,7 +950,10 @@ typedef enum RTEKind ...@@ -950,7 +950,10 @@ typedef enum RTEKind
RTE_TABLEFUNC, /* TableFunc(.., column list) */ RTE_TABLEFUNC, /* TableFunc(.., column list) */
RTE_VALUES, /* VALUES (<exprlist>), (<exprlist>), ... */ RTE_VALUES, /* VALUES (<exprlist>), (<exprlist>), ... */
RTE_CTE, /* common table expr (WITH list element) */ 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; } RTEKind;
typedef struct RangeTblEntry typedef struct RangeTblEntry
......
...@@ -324,7 +324,6 @@ typedef struct PlannerInfo ...@@ -324,7 +324,6 @@ typedef struct PlannerInfo
* partitioned table */ * partitioned table */
bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */ bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */
bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */ 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 hasHavingQual; /* true if havingQual was non-null */
bool hasPseudoConstantQuals; /* true if any RestrictInfo has bool hasPseudoConstantQuals; /* true if any RestrictInfo has
* pseudoconstant = true */ * pseudoconstant = true */
...@@ -1345,17 +1344,17 @@ typedef struct MergeAppendPath ...@@ -1345,17 +1344,17 @@ typedef struct MergeAppendPath
} MergeAppendPath; } MergeAppendPath;
/* /*
* ResultPath represents use of a Result plan node to compute a variable-free * GroupResultPath represents use of a Result plan node to compute the
* targetlist with no underlying tables (a "SELECT expressions" query). * output of a degenerate GROUP BY case, wherein we know we should produce
* The query could have a WHERE clause, too, represented by "quals". * exactly one row, which might then be filtered by a HAVING qual.
* *
* Note that quals is a list of bare clauses, not RestrictInfos. * Note that quals is a list of bare clauses, not RestrictInfos.
*/ */
typedef struct ResultPath typedef struct GroupResultPath
{ {
Path path; Path path;
List *quals; List *quals;
} ResultPath; } GroupResultPath;
/* /*
* MaterialPath represents use of a Material plan node, i.e., caching of * MaterialPath represents use of a Material plan node, i.e., caching of
......
...@@ -105,6 +105,8 @@ extern void cost_ctescan(Path *path, PlannerInfo *root, ...@@ -105,6 +105,8 @@ extern void cost_ctescan(Path *path, PlannerInfo *root,
RelOptInfo *baserel, ParamPathInfo *param_info); RelOptInfo *baserel, ParamPathInfo *param_info);
extern void cost_namedtuplestorescan(Path *path, PlannerInfo *root, extern void cost_namedtuplestorescan(Path *path, PlannerInfo *root,
RelOptInfo *baserel, ParamPathInfo *param_info); 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_recursive_union(Path *runion, Path *nrterm, Path *rterm);
extern void cost_sort(Path *path, PlannerInfo *root, extern void cost_sort(Path *path, PlannerInfo *root,
List *pathkeys, Cost input_cost, double tuples, int width, List *pathkeys, Cost input_cost, double tuples, int width,
...@@ -196,6 +198,7 @@ extern void set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel, ...@@ -196,6 +198,7 @@ extern void set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel,
double cte_rows); double cte_rows);
extern void set_tablefunc_size_estimates(PlannerInfo *root, RelOptInfo *rel); extern void set_tablefunc_size_estimates(PlannerInfo *root, RelOptInfo *rel);
extern void set_namedtuplestore_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 void set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel);
extern PathTarget *set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target); extern PathTarget *set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target);
extern double compute_bitmap_pages(PlannerInfo *root, RelOptInfo *baserel, extern double compute_bitmap_pages(PlannerInfo *root, RelOptInfo *baserel,
......
...@@ -75,8 +75,10 @@ extern MergeAppendPath *create_merge_append_path(PlannerInfo *root, ...@@ -75,8 +75,10 @@ extern MergeAppendPath *create_merge_append_path(PlannerInfo *root,
List *pathkeys, List *pathkeys,
Relids required_outer, Relids required_outer,
List *partitioned_rels); List *partitioned_rels);
extern ResultPath *create_result_path(PlannerInfo *root, RelOptInfo *rel, extern GroupResultPath *create_group_result_path(PlannerInfo *root,
PathTarget *target, List *resconstantqual); RelOptInfo *rel,
PathTarget *target,
List *havingqual);
extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath); extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath);
extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel, extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath, SpecialJoinInfo *sjinfo); Path *subpath, SpecialJoinInfo *sjinfo);
...@@ -105,6 +107,8 @@ extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel, ...@@ -105,6 +107,8 @@ extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer); Relids required_outer);
extern Path *create_namedtuplestorescan_path(PlannerInfo *root, RelOptInfo *rel, extern Path *create_namedtuplestorescan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer); Relids required_outer);
extern Path *create_resultscan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer);
extern Path *create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel, extern Path *create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer); Relids required_outer);
extern ForeignPath *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel, extern ForeignPath *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
...@@ -275,7 +279,6 @@ extern Relids min_join_parameterization(PlannerInfo *root, ...@@ -275,7 +279,6 @@ extern Relids min_join_parameterization(PlannerInfo *root,
Relids joinrelids, Relids joinrelids,
RelOptInfo *outer_rel, RelOptInfo *outer_rel,
RelOptInfo *inner_rel); RelOptInfo *inner_rel);
extern RelOptInfo *build_empty_join_rel(PlannerInfo *root);
extern RelOptInfo *fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind, extern RelOptInfo *fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind,
Relids relids); Relids relids);
extern Relids find_childrel_parents(PlannerInfo *root, RelOptInfo *rel); extern Relids find_childrel_parents(PlannerInfo *root, RelOptInfo *rel);
......
...@@ -21,11 +21,13 @@ ...@@ -21,11 +21,13 @@
/* /*
* prototypes for prepjointree.c * prototypes for prepjointree.c
*/ */
extern void replace_empty_jointree(Query *parse);
extern void pull_up_sublinks(PlannerInfo *root); extern void pull_up_sublinks(PlannerInfo *root);
extern void inline_set_returning_functions(PlannerInfo *root); extern void inline_set_returning_functions(PlannerInfo *root);
extern void pull_up_subqueries(PlannerInfo *root); extern void pull_up_subqueries(PlannerInfo *root);
extern void flatten_simple_union_all(PlannerInfo *root); extern void flatten_simple_union_all(PlannerInfo *root);
extern void reduce_outer_joins(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_in_jointree(Node *jtnode, bool include_joins);
extern Relids get_relids_for_join(PlannerInfo *root, int joinrelid); extern Relids get_relids_for_join(PlannerInfo *root, int joinrelid);
......
...@@ -239,9 +239,9 @@ id value ...@@ -239,9 +239,9 @@ id value
starting permutation: wrjt selectjoinforupdate c2 c1 starting permutation: wrjt selectjoinforupdate c2 c1
step wrjt: UPDATE jointest SET data = 42 WHERE id = 7; step wrjt: UPDATE jointest SET data = 42 WHERE id = 7;
step selectjoinforupdate: step selectjoinforupdate:
set enable_nestloop to 0; set local enable_nestloop to 0;
set enable_hashjoin to 0; set local enable_hashjoin to 0;
set enable_seqscan to 0; set local enable_seqscan to 0;
explain (costs off) 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;
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 ...@@ -269,6 +269,45 @@ id data id data
10 0 10 0 10 0 10 0
step c1: COMMIT; 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 starting permutation: wrtwcte multireadwcte c1 c2
step wrtwcte: UPDATE table_a SET value = 'tableAValue2' WHERE id = 1; step wrtwcte: UPDATE table_a SET value = 'tableAValue2' WHERE id = 1;
step multireadwcte: step multireadwcte:
......
...@@ -102,14 +102,29 @@ step "updateforcip" { ...@@ -102,14 +102,29 @@ step "updateforcip" {
# these tests exercise mark/restore during EPQ recheck, cf bug #15032 # these tests exercise mark/restore during EPQ recheck, cf bug #15032
step "selectjoinforupdate" { step "selectjoinforupdate" {
set enable_nestloop to 0; set local enable_nestloop to 0;
set enable_hashjoin to 0; set local enable_hashjoin to 0;
set enable_seqscan to 0; set local enable_seqscan to 0;
explain (costs off) 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;
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" session "s2"
setup { BEGIN ISOLATION LEVEL READ COMMITTED; } setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
...@@ -190,4 +205,5 @@ permutation "updateforcip" "updateforcip2" "c1" "c2" "read_a" ...@@ -190,4 +205,5 @@ permutation "updateforcip" "updateforcip2" "c1" "c2" "read_a"
permutation "updateforcip" "updateforcip3" "c1" "c2" "read_a" permutation "updateforcip" "updateforcip3" "c1" "c2" "read_a"
permutation "wrtwcte" "readwcte" "c1" "c2" permutation "wrtwcte" "readwcte" "c1" "c2"
permutation "wrjt" "selectjoinforupdate" "c2" "c1" permutation "wrjt" "selectjoinforupdate" "c2" "c1"
permutation "wrjt" "selectresultforupdate" "c2" "c1"
permutation "wrtwcte" "multireadwcte" "c1" "c2" permutation "wrtwcte" "multireadwcte" "c1" "c2"
...@@ -31,6 +31,10 @@ INSERT INTO J2_TBL VALUES (5, -5); ...@@ -31,6 +31,10 @@ INSERT INTO J2_TBL VALUES (5, -5);
INSERT INTO J2_TBL VALUES (0, NULL); INSERT INTO J2_TBL VALUES (0, NULL);
INSERT INTO J2_TBL VALUES (NULL, NULL); INSERT INTO J2_TBL VALUES (NULL, NULL);
INSERT INTO J2_TBL VALUES (NULL, 0); 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 -- CORRELATION NAMES
-- Make sure that table/column aliases are supported -- Make sure that table/column aliases are supported
...@@ -2228,19 +2232,16 @@ select * from int8_tbl i1 left join (int8_tbl i2 join ...@@ -2228,19 +2232,16 @@ 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 (select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2
order by 1, 2; order by 1, 2;
QUERY PLAN QUERY PLAN
------------------------------------------------- -------------------------------------------
Sort Sort
Sort Key: i1.q1, i1.q2 Sort Key: i1.q1, i1.q2
-> Hash Left Join -> Hash Left Join
Hash Cond: (i1.q2 = i2.q2) Hash Cond: (i1.q2 = i2.q2)
-> Seq Scan on int8_tbl i1 -> Seq Scan on int8_tbl i1
-> Hash -> Hash
-> Hash Join
Hash Cond: (i2.q1 = (123))
-> Seq Scan on int8_tbl i2 -> Seq Scan on int8_tbl i2
-> Hash Filter: (q1 = 123)
-> Result (8 rows)
(11 rows)
select * from int8_tbl i1 left join (int8_tbl i2 join 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 (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 ...@@ -3133,8 +3134,8 @@ select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
tenk1 t1 tenk1 t1
inner join int4_tbl i1 inner join int4_tbl i1
left join (select v1.x2, v2.y1, 11 AS d1 left join (select v1.x2, v2.y1, 11 AS d1
from (values(1,0)) v1(x1,x2) from (select 1,0 from onerow) v1(x1,x2)
left join (values(3,1)) v2(y1,y2) left join (select 3,1 from onerow) v2(y1,y2)
on v1.x1 = v2.y2) subq1 on v1.x1 = v2.y2) subq1
on (i1.f1 = subq1.x2) on (i1.f1 = subq1.x2)
on (t1.unique2 = subq1.d1) on (t1.unique2 = subq1.d1)
...@@ -3144,27 +3145,26 @@ where t1.unique2 < 42 and t1.stringu1 > t2.stringu2; ...@@ -3144,27 +3145,26 @@ where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
QUERY PLAN QUERY PLAN
----------------------------------------------------------------------- -----------------------------------------------------------------------
Nested Loop Nested Loop
Join Filter: (t1.stringu1 > t2.stringu2)
-> Nested Loop -> Nested Loop
Join Filter: ((0) = i1.f1) Join Filter: (t1.stringu1 > t2.stringu2)
-> Nested Loop -> Nested Loop
-> Nested Loop -> Nested Loop
Join Filter: ((1) = (1)) -> Seq Scan on onerow
-> Result -> Seq Scan on onerow onerow_1
-> Result
-> Index Scan using tenk1_unique2 on tenk1 t1 -> Index Scan using tenk1_unique2 on tenk1 t1
Index Cond: ((unique2 = (11)) AND (unique2 < 42)) Index Cond: ((unique2 = (11)) AND (unique2 < 42))
-> Seq Scan on int4_tbl i1
-> Index Scan using tenk1_unique1 on tenk1 t2 -> Index Scan using tenk1_unique1 on tenk1 t2
Index Cond: (unique1 = (3)) Index Cond: (unique1 = (3))
(14 rows) -> Seq Scan on int4_tbl i1
Filter: (f1 = 0)
(13 rows)
select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
tenk1 t1 tenk1 t1
inner join int4_tbl i1 inner join int4_tbl i1
left join (select v1.x2, v2.y1, 11 AS d1 left join (select v1.x2, v2.y1, 11 AS d1
from (values(1,0)) v1(x1,x2) from (select 1,0 from onerow) v1(x1,x2)
left join (values(3,1)) v2(y1,y2) left join (select 3,1 from onerow) v2(y1,y2)
on v1.x1 = v2.y2) subq1 on v1.x1 = v2.y2) subq1
on (i1.f1 = subq1.x2) on (i1.f1 = subq1.x2)
on (t1.unique2 = subq1.d1) on (t1.unique2 = subq1.d1)
...@@ -3196,6 +3196,50 @@ where t1.unique1 < i4.f1; ...@@ -3196,6 +3196,50 @@ where t1.unique1 < i4.f1;
---- ----
(0 rows) (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 -- test extraction of restriction OR clauses from join OR clause
-- (we used to only do this for indexable clauses) -- (we used to only do this for indexable clauses)
...@@ -3596,7 +3640,7 @@ select t1.* from ...@@ -3596,7 +3640,7 @@ select t1.* from
-> Hash Right Join -> Hash Right Join
Output: i8.q2 Output: i8.q2
Hash Cond: ((NULL::integer) = i8b1.q2) Hash Cond: ((NULL::integer) = i8b1.q2)
-> Hash Left Join -> Hash Join
Output: i8.q2, (NULL::integer) Output: i8.q2, (NULL::integer)
Hash Cond: (i8.q1 = i8b2.q1) Hash Cond: (i8.q1 = i8b2.q1)
-> Seq Scan on public.int8_tbl i8 -> Seq Scan on public.int8_tbl i8
...@@ -4018,10 +4062,10 @@ select * from ...@@ -4018,10 +4062,10 @@ select * from
QUERY PLAN QUERY PLAN
--------------------------------------- ---------------------------------------
Nested Loop Left Join Nested Loop Left Join
Join Filter: ((1) = COALESCE((1)))
-> Result -> Result
-> Hash Full Join -> Hash Full Join
Hash Cond: (a1.unique1 = (1)) Hash Cond: (a1.unique1 = (1))
Filter: (1 = COALESCE((1)))
-> Seq Scan on tenk1 a1 -> Seq Scan on tenk1 a1
-> Hash -> Hash
-> Result -> Result
...@@ -4951,13 +4995,10 @@ select v.* from ...@@ -4951,13 +4995,10 @@ select v.* from
-4567890123456789 | -4567890123456789 |
(20 rows) (20 rows)
create temp table dual();
insert into dual default values;
analyze dual;
select v.* from select v.* from
(int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1) (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, 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 vx | vy
-------------------+------------------- -------------------+-------------------
4567890123456789 | 123 4567890123456789 | 123
......
...@@ -999,7 +999,7 @@ select * from ...@@ -999,7 +999,7 @@ select * from
QUERY PLAN QUERY PLAN
---------------------------------------------------------- ----------------------------------------------------------
Subquery Scan on ss Subquery Scan on ss
Output: x, u Output: ss.x, ss.u
Filter: tattle(ss.x, 8) Filter: tattle(ss.x, 8)
-> ProjectSet -> ProjectSet
Output: 9, unnest('{1,2,3,11,12,13}'::integer[]) Output: 9, unnest('{1,2,3,11,12,13}'::integer[])
...@@ -1061,7 +1061,7 @@ select * from ...@@ -1061,7 +1061,7 @@ select * from
QUERY PLAN QUERY PLAN
---------------------------------------------------------- ----------------------------------------------------------
Subquery Scan on ss Subquery Scan on ss
Output: x, u Output: ss.x, ss.u
Filter: tattle(ss.x, ss.u) Filter: tattle(ss.x, ss.u)
-> ProjectSet -> ProjectSet
Output: 9, unnest('{1,2,3,11,12,13}'::integer[]) Output: 9, unnest('{1,2,3,11,12,13}'::integer[])
......
...@@ -37,6 +37,12 @@ INSERT INTO J2_TBL VALUES (0, NULL); ...@@ -37,6 +37,12 @@ INSERT INTO J2_TBL VALUES (0, NULL);
INSERT INTO J2_TBL VALUES (NULL, NULL); INSERT INTO J2_TBL VALUES (NULL, NULL);
INSERT INTO J2_TBL VALUES (NULL, 0); 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 -- CORRELATION NAMES
-- Make sure that table/column aliases are supported -- Make sure that table/column aliases are supported
...@@ -940,8 +946,8 @@ select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from ...@@ -940,8 +946,8 @@ select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
tenk1 t1 tenk1 t1
inner join int4_tbl i1 inner join int4_tbl i1
left join (select v1.x2, v2.y1, 11 AS d1 left join (select v1.x2, v2.y1, 11 AS d1
from (values(1,0)) v1(x1,x2) from (select 1,0 from onerow) v1(x1,x2)
left join (values(3,1)) v2(y1,y2) left join (select 3,1 from onerow) v2(y1,y2)
on v1.x1 = v2.y2) subq1 on v1.x1 = v2.y2) subq1
on (i1.f1 = subq1.x2) on (i1.f1 = subq1.x2)
on (t1.unique2 = subq1.d1) on (t1.unique2 = subq1.d1)
...@@ -953,8 +959,8 @@ select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from ...@@ -953,8 +959,8 @@ select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
tenk1 t1 tenk1 t1
inner join int4_tbl i1 inner join int4_tbl i1
left join (select v1.x2, v2.y1, 11 AS d1 left join (select v1.x2, v2.y1, 11 AS d1
from (values(1,0)) v1(x1,x2) from (select 1,0 from onerow) v1(x1,x2)
left join (values(3,1)) v2(y1,y2) left join (select 3,1 from onerow) v2(y1,y2)
on v1.x1 = v2.y2) subq1 on v1.x1 = v2.y2) subq1
on (i1.f1 = subq1.x2) on (i1.f1 = subq1.x2)
on (t1.unique2 = subq1.d1) on (t1.unique2 = subq1.d1)
...@@ -980,6 +986,35 @@ select ss1.d1 from ...@@ -980,6 +986,35 @@ select ss1.d1 from
on t1.tenthous = ss1.d1 on t1.tenthous = ss1.d1
where t1.unique1 < i4.f1; 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 -- test extraction of restriction OR clauses from join OR clause
-- (we used to only do this for indexable clauses) -- (we used to only do this for indexable clauses)
...@@ -1661,13 +1696,10 @@ select v.* from ...@@ -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) (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, 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); 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 select v.* from
(int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1) (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, 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) explain (verbose, costs off)
select * from select * from
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment