In the planner, replace an empty FROM clause with a dummy RTE.

The fact that "SELECT expression" has no base relations has long been a
thorn in the side of the planner.  It makes it hard to flatten a sub-query
that looks like that, or is a trivial VALUES() item, because the planner
generally uses relid sets to identify sub-relations, and such a sub-query
would have an empty relid set if we flattened it.  prepjointree.c contains
some baroque logic that works around this in certain special cases --- but
there is a much better answer.  We can replace an empty FROM clause with a
dummy RTE that acts like a table of one row and no columns, and then there
are no such corner cases to worry about.  Instead we need some logic to
get rid of useless dummy RTEs, but that's simpler and covers more cases
than what was there before.

For really trivial cases, where the query is just "SELECT expression" and
nothing else, there's a hazard that adding the extra RTE makes for a
noticeable slowdown; even though it's not much processing, there's not
that much for the planner to do overall.  However testing says that the
penalty is very small, close to the noise level.  In more complex queries,
this is able to find optimizations that we could not find before.

The new RTE type is called RTE_RESULT, since the "scan" plan type it
gives rise to is a Result node (the same plan we produced for a "SELECT
expression" query before).  To avoid confusion, rename the old ResultPath
path type to GroupResultPath, reflecting that it's only used in degenerate
grouping cases where we know the query produces just one grouped row.
(It wouldn't work to unify the two cases, because there are different
rules about where the associated quals live during query_planner.)

Note: although this touches readfuncs.c, I don't think a catversion
bump is required, because the added case can't occur in stored rules,
only plans.

Patch by me, reviewed by David Rowley and Mark Dilger

Discussion: https://postgr.es/m/15944.1521127664@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2019-01-28 17:54:10 -05:00
parent 5c11867512
commit 4be058fe9e
35 changed files with 1171 additions and 446 deletions

View File

@ -2476,6 +2476,8 @@ JumbleRangeTable(pgssJumbleState *jstate, List *rtable)
case RTE_NAMEDTUPLESTORE:
APP_JUMB_STRING(rte->enrname);
break;
case RTE_RESULT:
break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
break;

View File

@ -5361,7 +5361,7 @@ INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Insert on public.ft2
Output: (tableoid)::regclass
Output: (ft2.tableoid)::regclass
Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
-> Result
Output: 1200, 999, NULL::integer, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft2 '::character(10), NULL::user_enum

View File

@ -437,9 +437,12 @@ ExecSupportsMarkRestore(Path *pathnode)
return ExecSupportsMarkRestore(((ProjectionPath *) pathnode)->subpath);
else if (IsA(pathnode, MinMaxAggPath))
return false; /* childless Result */
else if (IsA(pathnode, GroupResultPath))
return false; /* childless Result */
else
{
Assert(IsA(pathnode, ResultPath));
/* Simple RTE_RESULT base relation */
Assert(IsA(pathnode, Path));
return false; /* childless Result */
}

View File

@ -2329,10 +2329,6 @@ range_table_walker(List *rtable,
if (walker(rte->tablesample, context))
return true;
break;
case RTE_CTE:
case RTE_NAMEDTUPLESTORE:
/* nothing to do */
break;
case RTE_SUBQUERY:
if (!(flags & QTW_IGNORE_RT_SUBQUERIES))
if (walker(rte->subquery, context))
@ -2355,6 +2351,11 @@ range_table_walker(List *rtable,
if (walker(rte->values_lists, context))
return true;
break;
case RTE_CTE:
case RTE_NAMEDTUPLESTORE:
case RTE_RESULT:
/* nothing to do */
break;
}
if (walker(rte->securityQuals, context))
@ -3164,10 +3165,6 @@ range_table_mutator(List *rtable,
TableSampleClause *);
/* we don't bother to copy eref, aliases, etc; OK? */
break;
case RTE_CTE:
case RTE_NAMEDTUPLESTORE:
/* nothing to do */
break;
case RTE_SUBQUERY:
if (!(flags & QTW_IGNORE_RT_SUBQUERIES))
{
@ -3198,6 +3195,11 @@ range_table_mutator(List *rtable,
case RTE_VALUES:
MUTATE(newrte->values_lists, rte->values_lists, List *);
break;
case RTE_CTE:
case RTE_NAMEDTUPLESTORE:
case RTE_RESULT:
/* nothing to do */
break;
}
MUTATE(newrte->securityQuals, rte->securityQuals, List *);
newrt = lappend(newrt, newrte);

View File

@ -1855,9 +1855,9 @@ _outMergeAppendPath(StringInfo str, const MergeAppendPath *node)
}
static void
_outResultPath(StringInfo str, const ResultPath *node)
_outGroupResultPath(StringInfo str, const GroupResultPath *node)
{
WRITE_NODE_TYPE("RESULTPATH");
WRITE_NODE_TYPE("GROUPRESULTPATH");
_outPathInfo(str, (const Path *) node);
@ -2213,7 +2213,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind);
WRITE_BOOL_FIELD(hasJoinRTEs);
WRITE_BOOL_FIELD(hasLateralRTEs);
WRITE_BOOL_FIELD(hasDeletedRTEs);
WRITE_BOOL_FIELD(hasHavingQual);
WRITE_BOOL_FIELD(hasPseudoConstantQuals);
WRITE_BOOL_FIELD(hasRecursion);
@ -3060,6 +3059,9 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
WRITE_NODE_FIELD(coltypmods);
WRITE_NODE_FIELD(colcollations);
break;
case RTE_RESULT:
/* no extra fields */
break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) node->rtekind);
break;
@ -3943,8 +3945,8 @@ outNode(StringInfo str, const void *obj)
case T_MergeAppendPath:
_outMergeAppendPath(str, obj);
break;
case T_ResultPath:
_outResultPath(str, obj);
case T_GroupResultPath:
_outGroupResultPath(str, obj);
break;
case T_MaterialPath:
_outMaterialPath(str, obj);

View File

@ -295,6 +295,10 @@ print_rt(const List *rtable)
printf("%d\t%s\t[tuplestore]",
i, rte->eref->aliasname);
break;
case RTE_RESULT:
printf("%d\t%s\t[result]",
i, rte->eref->aliasname);
break;
default:
printf("%d\t%s\t[unknown rtekind]",
i, rte->eref->aliasname);

View File

@ -1411,6 +1411,9 @@ _readRangeTblEntry(void)
READ_NODE_FIELD(coltypmods);
READ_NODE_FIELD(colcollations);
break;
case RTE_RESULT:
/* no extra fields */
break;
default:
elog(ERROR, "unrecognized RTE kind: %d",
(int) local_node->rtekind);

View File

@ -361,7 +361,16 @@ RelOptInfo - a relation or joined relations
join clauses)
Path - every way to generate a RelOptInfo(sequential,index,joins)
SeqScan - represents a sequential scan plan
A plain Path node can represent several simple plans, per its pathtype:
T_SeqScan - sequential scan
T_SampleScan - tablesample scan
T_FunctionScan - function-in-FROM scan
T_TableFuncScan - table function scan
T_ValuesScan - VALUES scan
T_CteScan - CTE (WITH) scan
T_NamedTuplestoreScan - ENR scan
T_WorkTableScan - scan worktable of a recursive CTE
T_Result - childless Result plan node (used for FROM-less SELECT)
IndexPath - index scan
BitmapHeapPath - top of a bitmapped index scan
TidPath - scan by CTID
@ -370,7 +379,7 @@ RelOptInfo - a relation or joined relations
CustomPath - for custom scan providers
AppendPath - append multiple subpaths together
MergeAppendPath - merge multiple subpaths, preserving their common sort order
ResultPath - a childless Result plan node (used for FROM-less SELECT)
GroupResultPath - childless Result plan node (used for degenerate grouping)
MaterialPath - a Material plan node
UniquePath - remove duplicate rows (either by hashing or sorting)
GatherPath - collect the results of parallel workers

View File

@ -117,6 +117,8 @@ static void set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static void set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static void set_result_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist);
@ -437,8 +439,13 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
set_cte_pathlist(root, rel, rte);
break;
case RTE_NAMEDTUPLESTORE:
/* Might as well just build the path immediately */
set_namedtuplestore_pathlist(root, rel, rte);
break;
case RTE_RESULT:
/* Might as well just build the path immediately */
set_result_pathlist(root, rel, rte);
break;
default:
elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
break;
@ -510,6 +517,9 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
case RTE_NAMEDTUPLESTORE:
/* tuplestore reference --- fully handled during set_rel_size */
break;
case RTE_RESULT:
/* simple Result --- fully handled during set_rel_size */
break;
default:
elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
break;
@ -712,6 +722,10 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* infrastructure to support that.
*/
return;
case RTE_RESULT:
/* RESULT RTEs, in themselves, are no problem. */
break;
}
/*
@ -2509,6 +2523,36 @@ set_namedtuplestore_pathlist(PlannerInfo *root, RelOptInfo *rel,
set_cheapest(rel);
}
/*
* set_result_pathlist
* Build the (single) access path for an RTE_RESULT RTE
*
* There's no need for a separate set_result_size phase, since we
* don't support join-qual-parameterized paths for these RTEs.
*/
static void
set_result_pathlist(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte)
{
Relids required_outer;
/* Mark rel with estimated output rows, width, etc */
set_result_size_estimates(root, rel);
/*
* We don't support pushing join clauses into the quals of a Result scan,
* but it could still have required parameterization due to LATERAL refs
* in its tlist.
*/
required_outer = rel->lateral_relids;
/* Generate appropriate path */
add_path(rel, create_resultscan_path(root, rel, required_outer));
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(rel);
}
/*
* set_worktable_pathlist
* Build the (single) access path for a self-reference CTE RTE
@ -3677,9 +3721,6 @@ print_path(PlannerInfo *root, Path *path, int indent)
case T_SampleScan:
ptype = "SampleScan";
break;
case T_SubqueryScan:
ptype = "SubqueryScan";
break;
case T_FunctionScan:
ptype = "FunctionScan";
break;
@ -3692,6 +3733,12 @@ print_path(PlannerInfo *root, Path *path, int indent)
case T_CteScan:
ptype = "CteScan";
break;
case T_NamedTuplestoreScan:
ptype = "NamedTuplestoreScan";
break;
case T_Result:
ptype = "Result";
break;
case T_WorkTableScan:
ptype = "WorkTableScan";
break;
@ -3716,7 +3763,7 @@ print_path(PlannerInfo *root, Path *path, int indent)
ptype = "TidScan";
break;
case T_SubqueryScanPath:
ptype = "SubqueryScanScan";
ptype = "SubqueryScan";
break;
case T_ForeignPath:
ptype = "ForeignScan";
@ -3742,8 +3789,8 @@ print_path(PlannerInfo *root, Path *path, int indent)
case T_MergeAppendPath:
ptype = "MergeAppend";
break;
case T_ResultPath:
ptype = "Result";
case T_GroupResultPath:
ptype = "GroupResult";
break;
case T_MaterialPath:
ptype = "Material";

View File

@ -1570,6 +1570,40 @@ cost_namedtuplestorescan(Path *path, PlannerInfo *root,
path->total_cost = startup_cost + run_cost;
}
/*
* cost_resultscan
* Determines and returns the cost of scanning an RTE_RESULT relation.
*/
void
cost_resultscan(Path *path, PlannerInfo *root,
RelOptInfo *baserel, ParamPathInfo *param_info)
{
Cost startup_cost = 0;
Cost run_cost = 0;
QualCost qpqual_cost;
Cost cpu_per_tuple;
/* Should only be applied to RTE_RESULT base relations */
Assert(baserel->relid > 0);
Assert(baserel->rtekind == RTE_RESULT);
/* Mark the path with the correct row estimate */
if (param_info)
path->rows = param_info->ppi_rows;
else
path->rows = baserel->rows;
/* We charge qual cost plus cpu_tuple_cost */
get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
startup_cost += qpqual_cost.startup;
cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
run_cost += cpu_per_tuple * baserel->tuples;
path->startup_cost = startup_cost;
path->total_cost = startup_cost + run_cost;
}
/*
* cost_recursive_union
* Determines and returns the cost of performing a recursive union,
@ -5044,6 +5078,29 @@ set_namedtuplestore_size_estimates(PlannerInfo *root, RelOptInfo *rel)
set_baserel_size_estimates(root, rel);
}
/*
* set_result_size_estimates
* Set the size estimates for an RTE_RESULT base relation
*
* The rel's targetlist and restrictinfo list must have been constructed
* already.
*
* We set the same fields as set_baserel_size_estimates.
*/
void
set_result_size_estimates(PlannerInfo *root, RelOptInfo *rel)
{
/* Should only be applied to RTE_RESULT base relations */
Assert(rel->relid > 0);
Assert(planner_rt_fetch(rel->relid, root)->rtekind == RTE_RESULT);
/* RTE_RESULT always generates a single row, natively */
rel->tuples = 1;
/* Now estimate number of output rows, etc */
set_baserel_size_estimates(root, rel);
}
/*
* set_foreign_size_estimates
* Set the size estimates for a base relation that is a foreign table.

View File

@ -84,7 +84,8 @@ static Plan *create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path);
static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path);
static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path);
static Result *create_result_plan(PlannerInfo *root, ResultPath *best_path);
static Result *create_group_result_plan(PlannerInfo *root,
GroupResultPath *best_path);
static ProjectSet *create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path);
static Material *create_material_plan(PlannerInfo *root, MaterialPath *best_path,
int flags);
@ -138,6 +139,8 @@ static CteScan *create_ctescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
static NamedTuplestoreScan *create_namedtuplestorescan_plan(PlannerInfo *root,
Path *best_path, List *tlist, List *scan_clauses);
static Result *create_resultscan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
@ -403,11 +406,16 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
plan = (Plan *) create_minmaxagg_plan(root,
(MinMaxAggPath *) best_path);
}
else if (IsA(best_path, GroupResultPath))
{
plan = (Plan *) create_group_result_plan(root,
(GroupResultPath *) best_path);
}
else
{
Assert(IsA(best_path, ResultPath));
plan = (Plan *) create_result_plan(root,
(ResultPath *) best_path);
/* Simple RTE_RESULT base relation */
Assert(IsA(best_path, Path));
plan = create_scan_plan(root, best_path, flags);
}
break;
case T_ProjectSet:
@ -691,6 +699,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
scan_clauses);
break;
case T_Result:
plan = (Plan *) create_resultscan_plan(root,
best_path,
tlist,
scan_clauses);
break;
case T_WorkTableScan:
plan = (Plan *) create_worktablescan_plan(root,
best_path,
@ -922,9 +937,26 @@ create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
List *gating_quals)
{
Plan *gplan;
Plan *splan;
Assert(gating_quals);
/*
* We might have a trivial Result plan already. Stacking one Result atop
* another is silly, so if that applies, just discard the input plan.
* (We're assuming its targetlist is uninteresting; it should be either
* the same as the result of build_path_tlist, or a simplified version.)
*/
splan = plan;
if (IsA(plan, Result))
{
Result *rplan = (Result *) plan;
if (rplan->plan.lefttree == NULL &&
rplan->resconstantqual == NULL)
splan = NULL;
}
/*
* Since we need a Result node anyway, always return the path's requested
* tlist; that's never a wrong choice, even if the parent node didn't ask
@ -932,7 +964,7 @@ create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
*/
gplan = (Plan *) make_result(build_path_tlist(root, path),
(Node *) gating_quals,
plan);
splan);
/*
* Notice that we don't change cost or size estimates when doing gating.
@ -1254,15 +1286,14 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path)
}
/*
* create_result_plan
* create_group_result_plan
* Create a Result plan for 'best_path'.
* This is only used for degenerate cases, such as a query with an empty
* jointree.
* This is only used for degenerate grouping cases.
*
* Returns a Plan node.
*/
static Result *
create_result_plan(PlannerInfo *root, ResultPath *best_path)
create_group_result_plan(PlannerInfo *root, GroupResultPath *best_path)
{
Result *plan;
List *tlist;
@ -3477,6 +3508,44 @@ create_namedtuplestorescan_plan(PlannerInfo *root, Path *best_path,
return scan_plan;
}
/*
* create_resultscan_plan
* Returns a Result plan for the RTE_RESULT base relation scanned by
* 'best_path' with restriction clauses 'scan_clauses' and targetlist
* 'tlist'.
*/
static Result *
create_resultscan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses)
{
Result *scan_plan;
Index scan_relid = best_path->parent->relid;
RangeTblEntry *rte PG_USED_FOR_ASSERTS_ONLY;
Assert(scan_relid > 0);
rte = planner_rt_fetch(scan_relid, root);
Assert(rte->rtekind == RTE_RESULT);
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
scan_clauses = extract_actual_clauses(scan_clauses, false);
/* Replace any outer-relation variables with nestloop params */
if (best_path->param_info)
{
scan_clauses = (List *)
replace_nestloop_params(root, (Node *) scan_clauses);
}
scan_plan = make_result(tlist, (Node *) scan_clauses, NULL);
copy_generic_path_info(&scan_plan->plan, best_path);
return scan_plan;
}
/*
* create_worktablescan_plan
* Returns a worktablescan plan for the base relation scanned by 'best_path'

View File

@ -827,7 +827,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
* all below it, so we should report inner_join_rels = qualscope. If
* there was exactly one element, we should (and already did) report
* whatever its inner_join_rels were. If there were no elements (is
* that possible?) the initialization before the loop fixed it.
* that still possible?) the initialization before the loop fixed it.
*/
if (list_length(f->fromlist) > 1)
*inner_join_rels = *qualscope;

View File

@ -60,44 +60,6 @@ query_planner(PlannerInfo *root, List *tlist,
List *joinlist;
RelOptInfo *final_rel;
/*
* If the query has an empty join tree, then it's something easy like
* "SELECT 2+2;" or "INSERT ... VALUES()". Fall through quickly.
*/
if (parse->jointree->fromlist == NIL)
{
/* We need a dummy joinrel to describe the empty set of baserels */
final_rel = build_empty_join_rel(root);
/*
* If query allows parallelism in general, check whether the quals are
* parallel-restricted. (We need not check final_rel->reltarget
* because it's empty at this point. Anything parallel-restricted in
* the query tlist will be dealt with later.)
*/
if (root->glob->parallelModeOK)
final_rel->consider_parallel =
is_parallel_safe(root, parse->jointree->quals);
/* The only path for it is a trivial Result path */
add_path(final_rel, (Path *)
create_result_path(root, final_rel,
final_rel->reltarget,
(List *) parse->jointree->quals));
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(final_rel);
/*
* We still are required to call qp_callback, in case it's something
* like "SELECT 2+2 ORDER BY 1".
*/
root->canon_pathkeys = NIL;
(*qp_callback) (root, qp_extra);
return final_rel;
}
/*
* Init planner lists to empty.
*
@ -124,6 +86,71 @@ query_planner(PlannerInfo *root, List *tlist,
*/
setup_simple_rel_arrays(root);
/*
* In the trivial case where the jointree is a single RTE_RESULT relation,
* bypass all the rest of this function and just make a RelOptInfo and its
* one access path. This is worth optimizing because it applies for
* common cases like "SELECT expression" and "INSERT ... VALUES()".
*/
Assert(parse->jointree->fromlist != NIL);
if (list_length(parse->jointree->fromlist) == 1)
{
Node *jtnode = (Node *) linitial(parse->jointree->fromlist);
if (IsA(jtnode, RangeTblRef))
{
int varno = ((RangeTblRef *) jtnode)->rtindex;
RangeTblEntry *rte = root->simple_rte_array[varno];
Assert(rte != NULL);
if (rte->rtekind == RTE_RESULT)
{
/* Make the RelOptInfo for it directly */
final_rel = build_simple_rel(root, varno, NULL);
/*
* If query allows parallelism in general, check whether the
* quals are parallel-restricted. (We need not check
* final_rel->reltarget because it's empty at this point.
* Anything parallel-restricted in the query tlist will be
* dealt with later.) This is normally pretty silly, because
* a Result-only plan would never be interesting to
* parallelize. However, if force_parallel_mode is on, then
* we want to execute the Result in a parallel worker if
* possible, so we must do this.
*/
if (root->glob->parallelModeOK &&
force_parallel_mode != FORCE_PARALLEL_OFF)
final_rel->consider_parallel =
is_parallel_safe(root, parse->jointree->quals);
/*
* The only path for it is a trivial Result path. We cheat a
* bit here by using a GroupResultPath, because that way we
* can just jam the quals into it without preprocessing them.
* (But, if you hold your head at the right angle, a FROM-less
* SELECT is a kind of degenerate-grouping case, so it's not
* that much of a cheat.)
*/
add_path(final_rel, (Path *)
create_group_result_path(root, final_rel,
final_rel->reltarget,
(List *) parse->jointree->quals));
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(final_rel);
/*
* We still are required to call qp_callback, in case it's
* something like "SELECT 2+2 ORDER BY 1".
*/
(*qp_callback) (root, qp_extra);
return final_rel;
}
}
}
/*
* Populate append_rel_array with each AppendRelInfo to allow direct
* lookups by child relid.

View File

@ -611,6 +611,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
List *newWithCheckOptions;
List *newHaving;
bool hasOuterJoins;
bool hasResultRTEs;
RelOptInfo *final_rel;
ListCell *l;
@ -651,6 +652,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
if (parse->cteList)
SS_process_ctes(root);
/*
* If the FROM clause is empty, replace it with a dummy RTE_RESULT RTE, so
* that we don't need so many special cases to deal with that situation.
*/
replace_empty_jointree(parse);
/*
* Look for ANY and EXISTS SubLinks in WHERE and JOIN/ON clauses, and try
* to transform them into joins. Note that this step does not descend
@ -684,14 +691,16 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
/*
* Detect whether any rangetable entries are RTE_JOIN kind; if not, we can
* avoid the expense of doing flatten_join_alias_vars(). Also check for
* outer joins --- if none, we can skip reduce_outer_joins(). And check
* for LATERAL RTEs, too. This must be done after we have done
* pull_up_subqueries(), of course.
* avoid the expense of doing flatten_join_alias_vars(). Likewise check
* whether any are RTE_RESULT kind; if not, we can skip
* remove_useless_result_rtes(). Also check for outer joins --- if none,
* we can skip reduce_outer_joins(). And check for LATERAL RTEs, too.
* This must be done after we have done pull_up_subqueries(), of course.
*/
root->hasJoinRTEs = false;
root->hasLateralRTEs = false;
hasOuterJoins = false;
hasResultRTEs = false;
foreach(l, parse->rtable)
{
RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
@ -702,6 +711,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
if (IS_OUTER_JOIN(rte->jointype))
hasOuterJoins = true;
}
else if (rte->rtekind == RTE_RESULT)
{
hasResultRTEs = true;
}
if (rte->lateral)
root->hasLateralRTEs = true;
}
@ -717,10 +730,10 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
/*
* Expand any rangetable entries that are inheritance sets into "append
* relations". This can add entries to the rangetable, but they must be
* plain base relations not joins, so it's OK (and marginally more
* efficient) to do it after checking for join RTEs. We must do it after
* pulling up subqueries, else we'd fail to handle inherited tables in
* subqueries.
* plain RTE_RELATION entries, so it's OK (and marginally more efficient)
* to do it after checking for joins and other special RTEs. We must do
* this after pulling up subqueries, else we'd fail to handle inherited
* tables in subqueries.
*/
expand_inherited_tables(root);
@ -967,6 +980,14 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
if (hasOuterJoins)
reduce_outer_joins(root);
/*
* If we have any RTE_RESULT relations, see if they can be deleted from
* the jointree. This step is most effectively done after we've done
* expression preprocessing and outer join reduction.
*/
if (hasResultRTEs)
remove_useless_result_rtes(root);
/*
* Do the main planning. If we have an inherited target relation, that
* needs special processing, else go straight to grouping_planner.
@ -3894,9 +3915,9 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
while (--nrows >= 0)
{
path = (Path *)
create_result_path(root, grouped_rel,
grouped_rel->reltarget,
(List *) parse->havingQual);
create_group_result_path(root, grouped_rel,
grouped_rel->reltarget,
(List *) parse->havingQual);
paths = lappend(paths, path);
}
path = (Path *)
@ -3914,9 +3935,9 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
{
/* No grouping sets, or just one, so one output row */
path = (Path *)
create_result_path(root, grouped_rel,
grouped_rel->reltarget,
(List *) parse->havingQual);
create_group_result_path(root, grouped_rel,
grouped_rel->reltarget,
(List *) parse->havingQual);
}
add_path(grouped_rel, path);

View File

@ -1114,12 +1114,6 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
if (!simplify_EXISTS_query(root, subselect))
return NULL;
/*
* The subquery must have a nonempty jointree, else we won't have a join.
*/
if (subselect->jointree->fromlist == NIL)
return NULL;
/*
* Separate out the WHERE clause. (We could theoretically also remove
* top-level plain JOIN/ON clauses, but it's probably not worth the
@ -1148,6 +1142,11 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
if (contain_volatile_functions(whereClause))
return NULL;
/*
* The subquery must have a nonempty jointree, but we can make it so.
*/
replace_empty_jointree(subselect);
/*
* Prepare to pull up the sub-select into top range table.
*

File diff suppressed because it is too large Load Diff

View File

@ -1707,7 +1707,7 @@ contain_leaked_vars_walker(Node *node, void *context)
* find_nonnullable_vars() is that the tested conditions really are different:
* a clause like "t1.v1 IS NOT NULL OR t1.v2 IS NOT NULL" does not prove
* that either v1 or v2 can't be NULL, but it does prove that the t1 row
* as a whole can't be all-NULL.
* as a whole can't be all-NULL. Also, the behavior for PHVs is different.
*
* top_level is true while scanning top-level AND/OR structure; here, showing
* the result is either FALSE or NULL is good enough. top_level is false when
@ -1893,7 +1893,24 @@ find_nonnullable_rels_walker(Node *node, bool top_level)
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
/*
* If the contained expression forces any rels non-nullable, so does
* the PHV.
*/
result = find_nonnullable_rels_walker((Node *) phv->phexpr, top_level);
/*
* If the PHV's syntactic scope is exactly one rel, it will be forced
* to be evaluated at that rel, and so it will behave like a Var of
* that rel: if the rel's entire output goes to null, so will the PHV.
* (If the syntactic scope is a join, we know that the PHV will go to
* null if the whole join does; but that is AND semantics while we
* need OR semantics for find_nonnullable_rels' result, so we can't do
* anything with the knowledge.)
*/
if (phv->phlevelsup == 0 &&
bms_membership(phv->phrels) == BMS_SINGLETON)
result = bms_add_members(result, phv->phrels);
}
return result;
}

View File

@ -1430,17 +1430,17 @@ create_merge_append_path(PlannerInfo *root,
}
/*
* create_result_path
* create_group_result_path
* Creates a path representing a Result-and-nothing-else plan.
*
* This is only used for degenerate cases, such as a query with an empty
* jointree.
* This is only used for degenerate grouping cases, in which we know we
* need to produce one result row, possibly filtered by a HAVING qual.
*/
ResultPath *
create_result_path(PlannerInfo *root, RelOptInfo *rel,
PathTarget *target, List *resconstantqual)
GroupResultPath *
create_group_result_path(PlannerInfo *root, RelOptInfo *rel,
PathTarget *target, List *havingqual)
{
ResultPath *pathnode = makeNode(ResultPath);
GroupResultPath *pathnode = makeNode(GroupResultPath);
pathnode->path.pathtype = T_Result;
pathnode->path.parent = rel;
@ -1450,9 +1450,13 @@ create_result_path(PlannerInfo *root, RelOptInfo *rel,
pathnode->path.parallel_safe = rel->consider_parallel;
pathnode->path.parallel_workers = 0;
pathnode->path.pathkeys = NIL;
pathnode->quals = resconstantqual;
pathnode->quals = havingqual;
/* Hardly worth defining a cost_result() function ... just do it */
/*
* We can't quite use cost_resultscan() because the quals we want to
* account for are not baserestrict quals of the rel. Might as well just
* hack it here.
*/
pathnode->path.rows = 1;
pathnode->path.startup_cost = target->cost.startup;
pathnode->path.total_cost = target->cost.startup +
@ -1462,12 +1466,12 @@ create_result_path(PlannerInfo *root, RelOptInfo *rel,
* Add cost of qual, if any --- but we ignore its selectivity, since our
* rowcount estimate should be 1 no matter what the qual is.
*/
if (resconstantqual)
if (havingqual)
{
QualCost qual_cost;
cost_qual_eval(&qual_cost, resconstantqual, root);
/* resconstantqual is evaluated once at startup */
cost_qual_eval(&qual_cost, havingqual, root);
/* havingqual is evaluated once at startup */
pathnode->path.startup_cost += qual_cost.startup + qual_cost.per_tuple;
pathnode->path.total_cost += qual_cost.startup + qual_cost.per_tuple;
}
@ -2020,6 +2024,32 @@ create_namedtuplestorescan_path(PlannerInfo *root, RelOptInfo *rel,
return pathnode;
}
/*
* create_resultscan_path
* Creates a path corresponding to a scan of an RTE_RESULT relation,
* returning the pathnode.
*/
Path *
create_resultscan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer)
{
Path *pathnode = makeNode(Path);
pathnode->pathtype = T_Result;
pathnode->parent = rel;
pathnode->pathtarget = rel->reltarget;
pathnode->param_info = get_baserel_parampathinfo(root, rel,
required_outer);
pathnode->parallel_aware = false;
pathnode->parallel_safe = rel->consider_parallel;
pathnode->parallel_workers = 0;
pathnode->pathkeys = NIL; /* result is always unordered */
cost_resultscan(pathnode, root, rel, pathnode->param_info);
return pathnode;
}
/*
* create_worktablescan_path
* Creates a path corresponding to a scan of a self-reference CTE,
@ -3560,6 +3590,11 @@ reparameterize_path(PlannerInfo *root, Path *path,
spath->path.pathkeys,
required_outer);
}
case T_Result:
/* Supported only for RTE_RESULT scan paths */
if (IsA(path, Path))
return create_resultscan_path(root, rel, required_outer);
break;
case T_Append:
{
AppendPath *apath = (AppendPath *) path;

View File

@ -1628,6 +1628,7 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
case RTE_VALUES:
case RTE_CTE:
case RTE_NAMEDTUPLESTORE:
case RTE_RESULT:
/* Not all of these can have dropped cols, but share code anyway */
expandRTE(rte, varno, 0, -1, true /* include dropped */ ,
NULL, &colvars);

View File

@ -194,7 +194,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
rel->baserestrict_min_security = UINT_MAX;
rel->joininfo = NIL;
rel->has_eclass_joins = false;
rel->consider_partitionwise_join = false; /* might get changed later */
rel->consider_partitionwise_join = false; /* might get changed later */
rel->part_scheme = NULL;
rel->nparts = 0;
rel->boundinfo = NULL;
@ -247,6 +247,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
rel->attr_widths = (int32 *)
palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(int32));
break;
case RTE_RESULT:
/* RTE_RESULT has no columns, nor could it have whole-row Var */
rel->min_attr = 0;
rel->max_attr = -1;
rel->attr_needed = NULL;
rel->attr_widths = NULL;
break;
default:
elog(ERROR, "unrecognized RTE kind: %d",
(int) rte->rtekind);
@ -609,7 +616,7 @@ build_join_rel(PlannerInfo *root,
joinrel->baserestrict_min_security = UINT_MAX;
joinrel->joininfo = NIL;
joinrel->has_eclass_joins = false;
joinrel->consider_partitionwise_join = false; /* might get changed later */
joinrel->consider_partitionwise_join = false; /* might get changed later */
joinrel->top_parent_relids = NULL;
joinrel->part_scheme = NULL;
joinrel->nparts = 0;
@ -784,7 +791,7 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
joinrel->baserestrictcost.per_tuple = 0;
joinrel->joininfo = NIL;
joinrel->has_eclass_joins = false;
joinrel->consider_partitionwise_join = false; /* might get changed later */
joinrel->consider_partitionwise_join = false; /* might get changed later */
joinrel->top_parent_relids = NULL;
joinrel->part_scheme = NULL;
joinrel->nparts = 0;
@ -1108,36 +1115,6 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel,
}
/*
* build_empty_join_rel
* Build a dummy join relation describing an empty set of base rels.
*
* This is used for queries with empty FROM clauses, such as "SELECT 2+2" or
* "INSERT INTO foo VALUES(...)". We don't try very hard to make the empty
* joinrel completely valid, since no real planning will be done with it ---
* we just need it to carry a simple Result path out of query_planner().
*/
RelOptInfo *
build_empty_join_rel(PlannerInfo *root)
{
RelOptInfo *joinrel;
/* The dummy join relation should be the only one ... */
Assert(root->join_rel_list == NIL);
joinrel = makeNode(RelOptInfo);
joinrel->reloptkind = RELOPT_JOINREL;
joinrel->relids = NULL; /* empty set */
joinrel->rows = 1; /* we produce one row for such cases */
joinrel->rtekind = RTE_JOIN;
joinrel->reltarget = create_empty_pathtarget();
root->join_rel_list = lappend(root->join_rel_list, joinrel);
return joinrel;
}
/*
* fetch_upper_rel
* Build a RelOptInfo describing some post-scan/join query processing,

View File

@ -2884,6 +2884,9 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
LCS_asString(lc->strength)),
parser_errposition(pstate, thisrel->location)));
break;
/* Shouldn't be possible to see RTE_RESULT here */
default:
elog(ERROR, "unrecognized RTE type: %d",
(int) rte->rtekind);

View File

@ -2519,6 +2519,9 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
}
}
break;
case RTE_RESULT:
/* These expose no columns, so nothing to do */
break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
}
@ -2911,6 +2914,14 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
rte->eref->aliasname)));
}
break;
case RTE_RESULT:
/* this probably can't happen ... */
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column %d of relation \"%s\" does not exist",
attnum,
rte->eref->aliasname)));
break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
}
@ -3039,6 +3050,15 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
result = false; /* keep compiler quiet */
}
break;
case RTE_RESULT:
/* this probably can't happen ... */
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column %d of relation \"%s\" does not exist",
attnum,
rte->eref->aliasname)));
result = false; /* keep compiler quiet */
break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
result = false; /* keep compiler quiet */

View File

@ -398,6 +398,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
case RTE_VALUES:
case RTE_TABLEFUNC:
case RTE_NAMEDTUPLESTORE:
case RTE_RESULT:
/* not a simple relation, leave it unmarked */
break;
case RTE_CTE:
@ -1531,6 +1532,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
case RTE_RELATION:
case RTE_VALUES:
case RTE_NAMEDTUPLESTORE:
case RTE_RESULT:
/*
* This case should not occur: a column of a table, values list,

View File

@ -7011,6 +7011,7 @@ get_name_for_var_field(Var *var, int fieldno,
case RTE_RELATION:
case RTE_VALUES:
case RTE_NAMEDTUPLESTORE:
case RTE_RESULT:
/*
* This case should not occur: a column of a table, values list,

View File

@ -237,7 +237,7 @@ typedef enum NodeTag
T_HashPath,
T_AppendPath,
T_MergeAppendPath,
T_ResultPath,
T_GroupResultPath,
T_MaterialPath,
T_UniquePath,
T_GatherPath,

View File

@ -950,7 +950,10 @@ typedef enum RTEKind
RTE_TABLEFUNC, /* TableFunc(.., column list) */
RTE_VALUES, /* VALUES (<exprlist>), (<exprlist>), ... */
RTE_CTE, /* common table expr (WITH list element) */
RTE_NAMEDTUPLESTORE /* tuplestore, e.g. for AFTER triggers */
RTE_NAMEDTUPLESTORE, /* tuplestore, e.g. for AFTER triggers */
RTE_RESULT /* RTE represents an empty FROM clause; such
* RTEs are added by the planner, they're not
* present during parsing or rewriting */
} RTEKind;
typedef struct RangeTblEntry

View File

@ -324,7 +324,6 @@ typedef struct PlannerInfo
* partitioned table */
bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */
bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */
bool hasDeletedRTEs; /* true if any RTE was deleted from jointree */
bool hasHavingQual; /* true if havingQual was non-null */
bool hasPseudoConstantQuals; /* true if any RestrictInfo has
* pseudoconstant = true */
@ -1345,17 +1344,17 @@ typedef struct MergeAppendPath
} MergeAppendPath;
/*
* ResultPath represents use of a Result plan node to compute a variable-free
* targetlist with no underlying tables (a "SELECT expressions" query).
* The query could have a WHERE clause, too, represented by "quals".
* GroupResultPath represents use of a Result plan node to compute the
* output of a degenerate GROUP BY case, wherein we know we should produce
* exactly one row, which might then be filtered by a HAVING qual.
*
* Note that quals is a list of bare clauses, not RestrictInfos.
*/
typedef struct ResultPath
typedef struct GroupResultPath
{
Path path;
List *quals;
} ResultPath;
} GroupResultPath;
/*
* MaterialPath represents use of a Material plan node, i.e., caching of

View File

@ -105,6 +105,8 @@ extern void cost_ctescan(Path *path, PlannerInfo *root,
RelOptInfo *baserel, ParamPathInfo *param_info);
extern void cost_namedtuplestorescan(Path *path, PlannerInfo *root,
RelOptInfo *baserel, ParamPathInfo *param_info);
extern void cost_resultscan(Path *path, PlannerInfo *root,
RelOptInfo *baserel, ParamPathInfo *param_info);
extern void cost_recursive_union(Path *runion, Path *nrterm, Path *rterm);
extern void cost_sort(Path *path, PlannerInfo *root,
List *pathkeys, Cost input_cost, double tuples, int width,
@ -196,6 +198,7 @@ extern void set_cte_size_estimates(PlannerInfo *root, RelOptInfo *rel,
double cte_rows);
extern void set_tablefunc_size_estimates(PlannerInfo *root, RelOptInfo *rel);
extern void set_namedtuplestore_size_estimates(PlannerInfo *root, RelOptInfo *rel);
extern void set_result_size_estimates(PlannerInfo *root, RelOptInfo *rel);
extern void set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel);
extern PathTarget *set_pathtarget_cost_width(PlannerInfo *root, PathTarget *target);
extern double compute_bitmap_pages(PlannerInfo *root, RelOptInfo *baserel,

View File

@ -75,8 +75,10 @@ extern MergeAppendPath *create_merge_append_path(PlannerInfo *root,
List *pathkeys,
Relids required_outer,
List *partitioned_rels);
extern ResultPath *create_result_path(PlannerInfo *root, RelOptInfo *rel,
PathTarget *target, List *resconstantqual);
extern GroupResultPath *create_group_result_path(PlannerInfo *root,
RelOptInfo *rel,
PathTarget *target,
List *havingqual);
extern MaterialPath *create_material_path(RelOptInfo *rel, Path *subpath);
extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath, SpecialJoinInfo *sjinfo);
@ -105,6 +107,8 @@ extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer);
extern Path *create_namedtuplestorescan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer);
extern Path *create_resultscan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer);
extern Path *create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel,
Relids required_outer);
extern ForeignPath *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
@ -275,7 +279,6 @@ extern Relids min_join_parameterization(PlannerInfo *root,
Relids joinrelids,
RelOptInfo *outer_rel,
RelOptInfo *inner_rel);
extern RelOptInfo *build_empty_join_rel(PlannerInfo *root);
extern RelOptInfo *fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind,
Relids relids);
extern Relids find_childrel_parents(PlannerInfo *root, RelOptInfo *rel);

View File

@ -21,11 +21,13 @@
/*
* prototypes for prepjointree.c
*/
extern void replace_empty_jointree(Query *parse);
extern void pull_up_sublinks(PlannerInfo *root);
extern void inline_set_returning_functions(PlannerInfo *root);
extern void pull_up_subqueries(PlannerInfo *root);
extern void flatten_simple_union_all(PlannerInfo *root);
extern void reduce_outer_joins(PlannerInfo *root);
extern void remove_useless_result_rtes(PlannerInfo *root);
extern Relids get_relids_in_jointree(Node *jtnode, bool include_joins);
extern Relids get_relids_for_join(PlannerInfo *root, int joinrelid);

View File

@ -239,9 +239,9 @@ id value
starting permutation: wrjt selectjoinforupdate c2 c1
step wrjt: UPDATE jointest SET data = 42 WHERE id = 7;
step selectjoinforupdate:
set enable_nestloop to 0;
set enable_hashjoin to 0;
set enable_seqscan to 0;
set local enable_nestloop to 0;
set local enable_hashjoin to 0;
set local enable_seqscan to 0;
explain (costs off)
select * from jointest a join jointest b on a.id=b.id for update;
select * from jointest a join jointest b on a.id=b.id for update;
@ -269,6 +269,45 @@ id data id data
10 0 10 0
step c1: COMMIT;
starting permutation: wrjt selectresultforupdate c2 c1
step wrjt: UPDATE jointest SET data = 42 WHERE id = 7;
step selectresultforupdate:
select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
left join table_a a on a.id = x, jointest jt
where jt.id = y;
explain (verbose, costs off)
select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
left join table_a a on a.id = x, jointest jt
where jt.id = y for update of jt, ss1, ss2;
select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
left join table_a a on a.id = x, jointest jt
where jt.id = y for update of jt, ss1, ss2;
<waiting ...>
step c2: COMMIT;
step selectresultforupdate: <... completed>
x y id value id data
1 7 1 tableAValue 7 0
QUERY PLAN
LockRows
Output: 1, 7, a.id, a.value, jt.id, jt.data, jt.ctid, a.ctid
-> Nested Loop Left Join
Output: 1, 7, a.id, a.value, jt.id, jt.data, jt.ctid, a.ctid
-> Nested Loop
Output: jt.id, jt.data, jt.ctid
-> Seq Scan on public.jointest jt
Output: jt.id, jt.data, jt.ctid
Filter: (jt.id = 7)
-> Result
-> Seq Scan on public.table_a a
Output: a.id, a.value, a.ctid
Filter: (a.id = 1)
x y id value id data
1 7 1 tableAValue 7 42
step c1: COMMIT;
starting permutation: wrtwcte multireadwcte c1 c2
step wrtwcte: UPDATE table_a SET value = 'tableAValue2' WHERE id = 1;
step multireadwcte:

View File

@ -102,14 +102,29 @@ step "updateforcip" {
# these tests exercise mark/restore during EPQ recheck, cf bug #15032
step "selectjoinforupdate" {
set enable_nestloop to 0;
set enable_hashjoin to 0;
set enable_seqscan to 0;
set local enable_nestloop to 0;
set local enable_hashjoin to 0;
set local enable_seqscan to 0;
explain (costs off)
select * from jointest a join jointest b on a.id=b.id for update;
select * from jointest a join jointest b on a.id=b.id for update;
}
# these tests exercise Result plan nodes participating in EPQ
step "selectresultforupdate" {
select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
left join table_a a on a.id = x, jointest jt
where jt.id = y;
explain (verbose, costs off)
select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
left join table_a a on a.id = x, jointest jt
where jt.id = y for update of jt, ss1, ss2;
select * from (select 1 as x) ss1 join (select 7 as y) ss2 on true
left join table_a a on a.id = x, jointest jt
where jt.id = y for update of jt, ss1, ss2;
}
session "s2"
setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
@ -190,4 +205,5 @@ permutation "updateforcip" "updateforcip2" "c1" "c2" "read_a"
permutation "updateforcip" "updateforcip3" "c1" "c2" "read_a"
permutation "wrtwcte" "readwcte" "c1" "c2"
permutation "wrjt" "selectjoinforupdate" "c2" "c1"
permutation "wrjt" "selectresultforupdate" "c2" "c1"
permutation "wrtwcte" "multireadwcte" "c1" "c2"

View File

@ -31,6 +31,10 @@ INSERT INTO J2_TBL VALUES (5, -5);
INSERT INTO J2_TBL VALUES (0, NULL);
INSERT INTO J2_TBL VALUES (NULL, NULL);
INSERT INTO J2_TBL VALUES (NULL, 0);
-- useful in some tests below
create temp table onerow();
insert into onerow default values;
analyze onerow;
--
-- CORRELATION NAMES
-- Make sure that table/column aliases are supported
@ -2227,20 +2231,17 @@ explain (costs off)
select * from int8_tbl i1 left join (int8_tbl i2 join
(select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2
order by 1, 2;
QUERY PLAN
-------------------------------------------------
QUERY PLAN
-------------------------------------------
Sort
Sort Key: i1.q1, i1.q2
-> Hash Left Join
Hash Cond: (i1.q2 = i2.q2)
-> Seq Scan on int8_tbl i1
-> Hash
-> Hash Join
Hash Cond: (i2.q1 = (123))
-> Seq Scan on int8_tbl i2
-> Hash
-> Result
(11 rows)
-> Seq Scan on int8_tbl i2
Filter: (q1 = 123)
(8 rows)
select * from int8_tbl i1 left join (int8_tbl i2 join
(select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2
@ -3133,8 +3134,8 @@ select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
tenk1 t1
inner join int4_tbl i1
left join (select v1.x2, v2.y1, 11 AS d1
from (values(1,0)) v1(x1,x2)
left join (values(3,1)) v2(y1,y2)
from (select 1,0 from onerow) v1(x1,x2)
left join (select 3,1 from onerow) v2(y1,y2)
on v1.x1 = v2.y2) subq1
on (i1.f1 = subq1.x2)
on (t1.unique2 = subq1.d1)
@ -3144,27 +3145,26 @@ where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
QUERY PLAN
-----------------------------------------------------------------------
Nested Loop
Join Filter: (t1.stringu1 > t2.stringu2)
-> Nested Loop
Join Filter: ((0) = i1.f1)
Join Filter: (t1.stringu1 > t2.stringu2)
-> Nested Loop
-> Nested Loop
Join Filter: ((1) = (1))
-> Result
-> Result
-> Seq Scan on onerow
-> Seq Scan on onerow onerow_1
-> Index Scan using tenk1_unique2 on tenk1 t1
Index Cond: ((unique2 = (11)) AND (unique2 < 42))
-> Seq Scan on int4_tbl i1
-> Index Scan using tenk1_unique1 on tenk1 t2
Index Cond: (unique1 = (3))
(14 rows)
-> Index Scan using tenk1_unique1 on tenk1 t2
Index Cond: (unique1 = (3))
-> Seq Scan on int4_tbl i1
Filter: (f1 = 0)
(13 rows)
select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
tenk1 t1
inner join int4_tbl i1
left join (select v1.x2, v2.y1, 11 AS d1
from (values(1,0)) v1(x1,x2)
left join (values(3,1)) v2(y1,y2)
from (select 1,0 from onerow) v1(x1,x2)
left join (select 3,1 from onerow) v2(y1,y2)
on v1.x1 = v2.y2) subq1
on (i1.f1 = subq1.x2)
on (t1.unique2 = subq1.d1)
@ -3196,6 +3196,50 @@ where t1.unique1 < i4.f1;
----
(0 rows)
-- this variant is foldable by the remove-useless-RESULT-RTEs code
explain (costs off)
select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
tenk1 t1
inner join int4_tbl i1
left join (select v1.x2, v2.y1, 11 AS d1
from (values(1,0)) v1(x1,x2)
left join (values(3,1)) v2(y1,y2)
on v1.x1 = v2.y2) subq1
on (i1.f1 = subq1.x2)
on (t1.unique2 = subq1.d1)
left join tenk1 t2
on (subq1.y1 = t2.unique1)
where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
QUERY PLAN
-----------------------------------------------------------------
Nested Loop
Join Filter: (t1.stringu1 > t2.stringu2)
-> Nested Loop
-> Seq Scan on int4_tbl i1
Filter: (f1 = 0)
-> Index Scan using tenk1_unique2 on tenk1 t1
Index Cond: ((unique2 = (11)) AND (unique2 < 42))
-> Index Scan using tenk1_unique1 on tenk1 t2
Index Cond: (unique1 = (3))
(9 rows)
select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
tenk1 t1
inner join int4_tbl i1
left join (select v1.x2, v2.y1, 11 AS d1
from (values(1,0)) v1(x1,x2)
left join (values(3,1)) v2(y1,y2)
on v1.x1 = v2.y2) subq1
on (i1.f1 = subq1.x2)
on (t1.unique2 = subq1.d1)
left join tenk1 t2
on (subq1.y1 = t2.unique1)
where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
unique2 | stringu1 | unique1 | stringu2
---------+----------+---------+----------
11 | WFAAAA | 3 | LKIAAA
(1 row)
--
-- test extraction of restriction OR clauses from join OR clause
-- (we used to only do this for indexable clauses)
@ -3596,7 +3640,7 @@ select t1.* from
-> Hash Right Join
Output: i8.q2
Hash Cond: ((NULL::integer) = i8b1.q2)
-> Hash Left Join
-> Hash Join
Output: i8.q2, (NULL::integer)
Hash Cond: (i8.q1 = i8b2.q1)
-> Seq Scan on public.int8_tbl i8
@ -4018,10 +4062,10 @@ select * from
QUERY PLAN
---------------------------------------
Nested Loop Left Join
Join Filter: ((1) = COALESCE((1)))
-> Result
-> Hash Full Join
Hash Cond: (a1.unique1 = (1))
Filter: (1 = COALESCE((1)))
-> Seq Scan on tenk1 a1
-> Hash
-> Result
@ -4951,13 +4995,10 @@ select v.* from
-4567890123456789 |
(20 rows)
create temp table dual();
insert into dual default values;
analyze dual;
select v.* from
(int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1)
left join int4_tbl z on z.f1 = x.q2,
lateral (select x.q1,y.q1 from dual union all select x.q2,y.q2 from dual) v(vx,vy);
lateral (select x.q1,y.q1 from onerow union all select x.q2,y.q2 from onerow) v(vx,vy);
vx | vy
-------------------+-------------------
4567890123456789 | 123

View File

@ -999,7 +999,7 @@ select * from
QUERY PLAN
----------------------------------------------------------
Subquery Scan on ss
Output: x, u
Output: ss.x, ss.u
Filter: tattle(ss.x, 8)
-> ProjectSet
Output: 9, unnest('{1,2,3,11,12,13}'::integer[])
@ -1061,7 +1061,7 @@ select * from
QUERY PLAN
----------------------------------------------------------
Subquery Scan on ss
Output: x, u
Output: ss.x, ss.u
Filter: tattle(ss.x, ss.u)
-> ProjectSet
Output: 9, unnest('{1,2,3,11,12,13}'::integer[])

View File

@ -37,6 +37,12 @@ INSERT INTO J2_TBL VALUES (0, NULL);
INSERT INTO J2_TBL VALUES (NULL, NULL);
INSERT INTO J2_TBL VALUES (NULL, 0);
-- useful in some tests below
create temp table onerow();
insert into onerow default values;
analyze onerow;
--
-- CORRELATION NAMES
-- Make sure that table/column aliases are supported
@ -940,8 +946,8 @@ select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
tenk1 t1
inner join int4_tbl i1
left join (select v1.x2, v2.y1, 11 AS d1
from (values(1,0)) v1(x1,x2)
left join (values(3,1)) v2(y1,y2)
from (select 1,0 from onerow) v1(x1,x2)
left join (select 3,1 from onerow) v2(y1,y2)
on v1.x1 = v2.y2) subq1
on (i1.f1 = subq1.x2)
on (t1.unique2 = subq1.d1)
@ -953,8 +959,8 @@ select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
tenk1 t1
inner join int4_tbl i1
left join (select v1.x2, v2.y1, 11 AS d1
from (values(1,0)) v1(x1,x2)
left join (values(3,1)) v2(y1,y2)
from (select 1,0 from onerow) v1(x1,x2)
left join (select 3,1 from onerow) v2(y1,y2)
on v1.x1 = v2.y2) subq1
on (i1.f1 = subq1.x2)
on (t1.unique2 = subq1.d1)
@ -980,6 +986,35 @@ select ss1.d1 from
on t1.tenthous = ss1.d1
where t1.unique1 < i4.f1;
-- this variant is foldable by the remove-useless-RESULT-RTEs code
explain (costs off)
select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
tenk1 t1
inner join int4_tbl i1
left join (select v1.x2, v2.y1, 11 AS d1
from (values(1,0)) v1(x1,x2)
left join (values(3,1)) v2(y1,y2)
on v1.x1 = v2.y2) subq1
on (i1.f1 = subq1.x2)
on (t1.unique2 = subq1.d1)
left join tenk1 t2
on (subq1.y1 = t2.unique1)
where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from
tenk1 t1
inner join int4_tbl i1
left join (select v1.x2, v2.y1, 11 AS d1
from (values(1,0)) v1(x1,x2)
left join (values(3,1)) v2(y1,y2)
on v1.x1 = v2.y2) subq1
on (i1.f1 = subq1.x2)
on (t1.unique2 = subq1.d1)
left join tenk1 t2
on (subq1.y1 = t2.unique1)
where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
--
-- test extraction of restriction OR clauses from join OR clause
-- (we used to only do this for indexable clauses)
@ -1661,13 +1696,10 @@ select v.* from
(int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1)
left join int4_tbl z on z.f1 = x.q2,
lateral (select x.q1,y.q1 union all select x.q2,y.q2) v(vx,vy);
create temp table dual();
insert into dual default values;
analyze dual;
select v.* from
(int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1)
left join int4_tbl z on z.f1 = x.q2,
lateral (select x.q1,y.q1 from dual union all select x.q2,y.q2 from dual) v(vx,vy);
lateral (select x.q1,y.q1 from onerow union all select x.q2,y.q2 from onerow) v(vx,vy);
explain (verbose, costs off)
select * from