aboutsummaryrefslogtreecommitdiff
path: root/src/backend/optimizer/util
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2019-01-28 17:54:10 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2019-01-28 17:54:23 -0500
commit4be058fe9ec5e630239b656af21fc083371f30ed (patch)
tree1e422ec3af7f08b7a1cdd7c57b71cd3215cec152 /src/backend/optimizer/util
parent5c1186751214416fdf88f33a89c3dc88391d2d60 (diff)
downloadpostgresql-4be058fe9ec5e630239b656af21fc083371f30ed.tar.gz
postgresql-4be058fe9ec5e630239b656af21fc083371f30ed.zip
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
Diffstat (limited to 'src/backend/optimizer/util')
-rw-r--r--src/backend/optimizer/util/clauses.c19
-rw-r--r--src/backend/optimizer/util/pathnode.c59
-rw-r--r--src/backend/optimizer/util/plancat.c1
-rw-r--r--src/backend/optimizer/util/relnode.c43
4 files changed, 76 insertions, 46 deletions
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 94b8fa0c3dd..ea11dbb67c3 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -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;
}
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index b2637d0e89a..0402ffe9a74 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -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;
}
@@ -2021,6 +2025,32 @@ create_namedtuplestorescan_path(PlannerInfo *root, RelOptInfo *rel,
}
/*
+ * 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,
* returning the pathnode.
@@ -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;
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 261492e6b71..243344a0110 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -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);
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index fe83ec45192..f04c6b76f49 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -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;
@@ -1109,36 +1116,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,
* or return a pre-existing one if somebody already built it.