case RTE_NAMEDTUPLESTORE:
APP_JUMB_STRING(rte->enrname);
break;
+ case RTE_RESULT:
+ break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
break;
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
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 */
}
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))
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))
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))
{
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);
}
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);
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);
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;
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);
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);
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);
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
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
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);
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;
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;
* infrastructure to support that.
*/
return;
+
+ case RTE_RESULT:
+ /* RESULT RTEs, in themselves, are no problem. */
+ break;
}
/*
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
case T_SampleScan:
ptype = "SampleScan";
break;
- case T_SubqueryScan:
- ptype = "SubqueryScan";
- break;
case T_FunctionScan:
ptype = "FunctionScan";
break;
case T_CteScan:
ptype = "CteScan";
break;
+ case T_NamedTuplestoreScan:
+ ptype = "NamedTuplestoreScan";
+ break;
+ case T_Result:
+ ptype = "Result";
+ break;
case T_WorkTableScan:
ptype = "WorkTableScan";
break;
ptype = "TidScan";
break;
case T_SubqueryScanPath:
- ptype = "SubqueryScanScan";
+ ptype = "SubqueryScan";
break;
case T_ForeignPath:
ptype = "ForeignScan";
case T_MergeAppendPath:
ptype = "MergeAppend";
break;
- case T_ResultPath:
- ptype = "Result";
+ case T_GroupResultPath:
+ ptype = "GroupResult";
break;
case T_MaterialPath:
ptype = "Material";
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,
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.
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);
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,
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:
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,
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
*/
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.
}
/*
- * 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;
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'
* 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;
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.
*
*/
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.
List *newWithCheckOptions;
List *newHaving;
bool hasOuterJoins;
+ bool hasResultRTEs;
RelOptInfo *final_rel;
ListCell *l;
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
/*
* 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);
if (IS_OUTER_JOIN(rte->jointype))
hasOuterJoins = true;
}
+ else if (rte->rtekind == RTE_RESULT)
+ {
+ hasResultRTEs = true;
+ }
if (rte->lateral)
root->hasLateralRTEs = true;
}
/*
* 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);
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.
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 *)
{
/* 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);
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
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.
*
* Planner preprocessing for subqueries and join tree manipulation.
*
* NOTE: the intended sequence for invoking these operations is
+ * replace_empty_jointree
* pull_up_sublinks
* inline_set_returning_functions
* pull_up_subqueries
* flatten_simple_union_all
* do expression preprocessing (including flattening JOIN alias vars)
* reduce_outer_joins
+ * remove_useless_result_rtes
*
*
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
static Node *pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
JoinExpr *lowest_outer_join,
JoinExpr *lowest_nulling_outer_join,
- AppendRelInfo *containing_appendrel,
- bool deletion_ok);
+ AppendRelInfo *containing_appendrel);
static Node *pull_up_simple_subquery(PlannerInfo *root, Node *jtnode,
RangeTblEntry *rte,
JoinExpr *lowest_outer_join,
JoinExpr *lowest_nulling_outer_join,
- AppendRelInfo *containing_appendrel,
- bool deletion_ok);
+ AppendRelInfo *containing_appendrel);
static Node *pull_up_simple_union_all(PlannerInfo *root, Node *jtnode,
RangeTblEntry *rte);
static void pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root,
static void make_setop_translation_list(Query *query, Index newvarno,
List **translated_vars);
static bool is_simple_subquery(Query *subquery, RangeTblEntry *rte,
- JoinExpr *lowest_outer_join,
- bool deletion_ok);
+ JoinExpr *lowest_outer_join);
static Node *pull_up_simple_values(PlannerInfo *root, Node *jtnode,
RangeTblEntry *rte);
-static bool is_simple_values(PlannerInfo *root, RangeTblEntry *rte,
- bool deletion_ok);
+static bool is_simple_values(PlannerInfo *root, RangeTblEntry *rte);
static bool is_simple_union_all(Query *subquery);
static bool is_simple_union_all_recurse(Node *setOp, Query *setOpQuery,
List *colTypes);
replace_rte_variables_context *context);
static Query *pullup_replace_vars_subquery(Query *query,
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 void reduce_outer_joins_pass2(Node *jtnode,
reduce_outer_joins_state *state,
Relids nonnullable_rels,
List *nonnullable_vars,
List *forced_null_vars);
-static void substitute_multiple_relids(Node *node,
- int varno, Relids subrelids);
+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);
static void fix_append_rel_relids(List *append_rel_list, int varno,
Relids subrelids);
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
* Attempt to pull up ANY and EXISTS SubLinks to be treated as
{
/* Top level of jointree must always be a 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 */
root->parse->jointree = (FromExpr *)
pull_up_subqueries_recurse(root, (Node *) root->parse->jointree,
- NULL, NULL, NULL, false);
- /* Apply cleanup phase if necessary */
- if (root->hasDeletedRTEs)
- root->parse->jointree = (FromExpr *)
- pull_up_subqueries_cleanup((Node *) root->parse->jointree);
+ NULL, NULL, NULL);
+ /* We should still have a FromExpr */
Assert(IsA(root->parse->jointree, FromExpr));
}
* Recursive guts of pull_up_subqueries.
*
* 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
* lowest_outer_join references the lowest such JoinExpr node; otherwise
* This forces use of the PlaceHolderVar mechanism for all non-Var targetlist
* 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
* to replace Vars that reference the subquery's outputs throughout the
* parent query, including quals attached to jointree nodes above the one
- * we are currently processing! We handle this by being careful not to
- * change the jointree structure while recursing: no nodes other than leaf
- * RangeTblRef entries and entirely-empty FromExprs will be replaced or
- * deleted. Also, we can't turn pullup_replace_vars loose on the whole
- * jointree, because it'll return a mutated copy of the tree; we have to
+ * we are currently processing! We handle this by being careful to maintain
+ * validity of the jointree structure while recursing, in the following sense:
+ * whenever we recurse, all qual expressions in the tree must be reachable
+ * from the top level, in case the recursive call needs to modify them.
+ *
+ * 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
* reasonable to pass lowest_outer_join and lowest_nulling_outer_join as
* pointers rather than some more-indirect way of identifying the lowest
* OJs. Likewise, we don't replace append_rel_list members but only their
* 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 *
pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
JoinExpr *lowest_outer_join,
JoinExpr *lowest_nulling_outer_join,
- AppendRelInfo *containing_appendrel,
- bool deletion_ok)
+ AppendRelInfo *containing_appendrel)
{
Assert(jtnode != NULL);
if (IsA(jtnode, RangeTblRef))
* unless is_safe_append_member says so.
*/
if (rte->rtekind == RTE_SUBQUERY &&
- is_simple_subquery(rte->subquery, rte,
- lowest_outer_join, deletion_ok) &&
+ is_simple_subquery(rte->subquery, rte, lowest_outer_join) &&
(containing_appendrel == NULL ||
is_safe_append_member(rte->subquery)))
return pull_up_simple_subquery(root, jtnode, rte,
lowest_outer_join,
lowest_nulling_outer_join,
- containing_appendrel,
- deletion_ok);
+ containing_appendrel);
/*
* Alternatively, is it a simple UNION ALL subquery? If so, flatten
if (rte->rtekind == RTE_VALUES &&
lowest_outer_join == NULL &&
containing_appendrel == NULL &&
- is_simple_values(root, rte, deletion_ok))
+ is_simple_values(root, rte))
return pull_up_simple_values(root, jtnode, rte);
/* Otherwise, do nothing at this node. */
else if (IsA(jtnode, FromExpr))
{
FromExpr *f = (FromExpr *) jtnode;
- bool have_undeleted_child = false;
ListCell *l;
Assert(containing_appendrel == NULL);
-
- /*
- * If the FromExpr has quals, it's not deletable even if its parent
- * would allow deletion.
- */
- if (f->quals)
- deletion_ok = false;
-
+ /* Recursively transform all the child nodes */
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),
lowest_outer_join,
lowest_nulling_outer_join,
- 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;
+ NULL);
}
}
else if (IsA(jtnode, JoinExpr))
switch (j->jointype)
{
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,
lowest_outer_join,
lowest_nulling_outer_join,
- NULL,
- true);
+ NULL);
j->rarg = pull_up_subqueries_recurse(root, j->rarg,
lowest_outer_join,
lowest_nulling_outer_join,
- NULL,
- j->larg != NULL);
+ NULL);
break;
case JOIN_LEFT:
case JOIN_SEMI:
j->larg = pull_up_subqueries_recurse(root, j->larg,
j,
lowest_nulling_outer_join,
- NULL,
- false);
+ NULL);
j->rarg = pull_up_subqueries_recurse(root, j->rarg,
j,
j,
- NULL,
- false);
+ NULL);
break;
case JOIN_FULL:
j->larg = pull_up_subqueries_recurse(root, j->larg,
j,
j,
- NULL,
- false);
+ NULL);
j->rarg = pull_up_subqueries_recurse(root, j->rarg,
j,
j,
- NULL,
- false);
+ NULL);
break;
case JOIN_RIGHT:
j->larg = pull_up_subqueries_recurse(root, j->larg,
j,
j,
- NULL,
- false);
+ NULL);
j->rarg = pull_up_subqueries_recurse(root, j->rarg,
j,
lowest_nulling_outer_join,
- NULL,
- false);
+ NULL);
break;
default:
elog(ERROR, "unrecognized join type: %d",
*
* jtnode is a RangeTblRef that has been tentatively identified as a simple
* 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
- * determine that the subquery can't be pulled up after all.
+ * or jtnode itself if we determine that the subquery can't be pulled up
+ * after all.
*
* rte is the RangeTblEntry referenced by jtnode. Remaining parameters are
* as for pull_up_subqueries_recurse.
pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
JoinExpr *lowest_outer_join,
JoinExpr *lowest_nulling_outer_join,
- AppendRelInfo *containing_appendrel,
- bool deletion_ok)
+ AppendRelInfo *containing_appendrel)
{
Query *parse = root->parse;
int varno = ((RangeTblRef *) jtnode)->rtindex;
/* No CTEs to worry about */
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
* leave unoptimized SubLinks behind.
* easier just to keep this "if" looking the same as the one in
* pull_up_subqueries_recurse.
*/
- if (is_simple_subquery(subquery, rte,
- lowest_outer_join, deletion_ok) &&
+ if (is_simple_subquery(subquery, rte, lowest_outer_join) &&
(containing_appendrel == NULL || is_safe_append_member(subquery)))
{
/* good to go */
case RTE_JOIN:
case RTE_CTE:
case RTE_NAMEDTUPLESTORE:
+ case RTE_RESULT:
/* these can't contain any lateral references */
break;
}
Relids subrelids;
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);
}
/*
* Return the adjusted subquery jointree to replace the RangeTblRef entry
- * in parent's jointree; or, if we're flattening a subquery with empty
- * FROM list, return NULL to signal deletion of the subquery from the
- * parent jointree (and set hasDeletedRTEs to ensure cleanup later).
+ * in parent's jointree; or, if the FromExpr is degenerate, just return
+ * its single member.
*/
- if (subquery->jointree->fromlist == NIL)
- {
- Assert(deletion_ok);
- Assert(subquery->jointree->quals == NULL);
- root->hasDeletedRTEs = true;
- return NULL;
- }
+ Assert(IsA(subquery->jointree, FromExpr));
+ Assert(subquery->jointree->fromlist != NIL);
+ if (subquery->jointree->quals == NULL &&
+ list_length(subquery->jointree->fromlist) == 1)
+ return (Node *) linitial(subquery->jointree->fromlist);
return (Node *) subquery->jointree;
}
rtr = makeNode(RangeTblRef);
rtr->rtindex = childRTindex;
(void) pull_up_subqueries_recurse(root, (Node *) rtr,
- NULL, NULL, appinfo, false);
+ NULL, NULL, appinfo);
}
else if (IsA(setOp, SetOperationStmt))
{
* (Note subquery is not necessarily equal to rte->subquery; it could be a
* processed copy of that.)
* 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
is_simple_subquery(Query *subquery, RangeTblEntry *rte,
- JoinExpr *lowest_outer_join,
- bool deletion_ok)
+ JoinExpr *lowest_outer_join)
{
/*
* Let's just make sure it's a valid subselect ...
if (rte->security_barrier)
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.
*/
* Pull up a single 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
- * can be deleted entirely (all failure cases should have been detected by
- * is_simple_values()).
+ * by pull_up_subqueries. We always return a RangeTblRef representing a
+ * RESULT RTE to replace it (all failure cases should have been detected by
+ * 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
* possible usage of VALUES RTEs, we do not need the remaining parameters
Assert(root->placeholder_list == NIL);
/*
- * Return NULL to signal deletion of the VALUES RTE from the parent
- * jointree (and set hasDeletedRTEs to ensure cleanup later).
+ * Replace the VALUES RTE with a RESULT RTE. The VALUES RTE is the only
+ * rtable entry in the current query level, so this is easy.
*/
- root->hasDeletedRTEs = true;
- return NULL;
+ Assert(list_length(parse->rtable) == 1);
+
+ /* 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;
}
/*
* to pull up into the parent query.
*
* 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
-is_simple_values(PlannerInfo *root, RangeTblEntry *rte, bool deletion_ok)
+is_simple_values(PlannerInfo *root, RangeTblEntry *rte)
{
Assert(rte->rtekind == RTE_VALUES);
/*
- * We can only pull up a VALUES RTE if deletion_ok is true. It's
- * basically the same case as a sub-select with empty FROM list; see
- * comments in is_simple_subquery().
- */
- if (!deletion_ok)
- return false;
-
- /*
- * Also, there must be exactly one VALUES list, else it's not semantically
- * correct to delete the VALUES RTE.
+ * There must be exactly one VALUES list, else it's not semantically
+ * correct to replace the VALUES RTE with a RESULT RTE, nor would we have
+ * a unique set of expressions to substitute into the parent query.
*/
if (list_length(rte->values_lists) != 1)
return false;
/*
* Don't pull up a VALUES that contains any set-returning or volatile
- * functions. Again, the considerations here are basically identical to
- * restrictions on a subquery's targetlist.
+ * functions. The considerations here are basically identical to the
+ * restrictions on a pull-able subquery's targetlist.
*/
if (expression_returns_set((Node *) rte->values_lists) ||
contain_volatile_functions((Node *) rte->values_lists))
/*
* 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
- * 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
* put them in an appendrel. (This is a bit annoying...) If we didn't
* fix_append_rel_relids().
*/
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))
{
if (jtnode->quals != NULL)
case RTE_JOIN:
case RTE_CTE:
case RTE_NAMEDTUPLESTORE:
+ case RTE_RESULT:
/* these shouldn't be marked LATERAL */
Assert(false);
break;
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
(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
- * a subquery
+ * substitute_phv_relids - adjust PlaceHolderVar relid sets after pulling up
+ * a subquery or removing an RTE_RESULT jointree item
*
* Find any PlaceHolderVar nodes in the given tree that reference the
* pulled-up relid, and change them to reference the replacement relid(s).
int varno;
int sublevels_up;
Relids subrelids;
-} substitute_multiple_relids_context;
+} substitute_phv_relids_context;
static bool
-substitute_multiple_relids_walker(Node *node,
- substitute_multiple_relids_context *context)
+substitute_phv_relids_walker(Node *node,
+ substitute_phv_relids_context *context)
{
if (node == NULL)
return false;
context->subrelids);
phv->phrels = bms_del_member(phv->phrels,
context->varno);
+ /* Assert we haven't broken the PHV */
+ Assert(!bms_is_empty(phv->phrels));
}
/* fall through to examine children */
}
context->sublevels_up++;
result = query_tree_walker((Query *) node,
- substitute_multiple_relids_walker,
+ substitute_phv_relids_walker,
(void *) context, 0);
context->sublevels_up--;
return result;
Assert(!IsA(node, PlaceHolderInfo));
Assert(!IsA(node, MinMaxAggInfo));
- return expression_tree_walker(node, substitute_multiple_relids_walker,
+ return expression_tree_walker(node, substitute_phv_relids_walker,
(void *) context);
}
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.sublevels_up = 0;
* Must be prepared to start with a Query or a bare expression tree.
*/
query_or_expression_tree_walker(node,
- substitute_multiple_relids_walker,
+ substitute_phv_relids_walker,
(void *) &context,
0);
}
*
* 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
- * 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.
*
* We assume we may modify the AppendRelInfo nodes in-place.
appinfo->child_relid = subvarno;
}
- /* Also finish fixups for its translated vars */
- substitute_multiple_relids((Node *) appinfo->translated_vars,
- varno, subrelids);
+ /* Also fix up any PHVs in its translated vars */
+ substitute_phv_relids((Node *) appinfo->translated_vars,
+ varno, subrelids);
}
}
* 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
{
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;
}
}
/*
- * 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;
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 +
* 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;
}
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,
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;
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);
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;
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);
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;
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;
}
-/*
- * 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,
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);
}
}
break;
+ case RTE_RESULT:
+ /* These expose no columns, so nothing to do */
+ break;
default:
elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
}
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);
}
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 */
case RTE_VALUES:
case RTE_TABLEFUNC:
case RTE_NAMEDTUPLESTORE:
+ case RTE_RESULT:
/* not a simple relation, leave it unmarked */
break;
case RTE_CTE:
case RTE_RELATION:
case RTE_VALUES:
case RTE_NAMEDTUPLESTORE:
+ case RTE_RESULT:
/*
* This case should not occur: a column of a table, values list,
case RTE_RELATION:
case RTE_VALUES:
case RTE_NAMEDTUPLESTORE:
+ case RTE_RESULT:
/*
* This case should not occur: a column of a table, values list,
T_HashPath,
T_AppendPath,
T_MergeAppendPath,
- T_ResultPath,
+ T_GroupResultPath,
T_MaterialPath,
T_UniquePath,
T_GatherPath,
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
* 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 */
} 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
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,
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,
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);
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,
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);
/*
* 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);
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;
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:
# 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; }
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"
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
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
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)
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)
----
(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)
-> 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
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
-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
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[])
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[])
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
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)
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)
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)
(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