aboutsummaryrefslogtreecommitdiff
path: root/src/backend/optimizer/prep/prepjointree.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/optimizer/prep/prepjointree.c')
-rw-r--r--src/backend/optimizer/prep/prepjointree.c796
1 files changed, 541 insertions, 255 deletions
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 77dbf4eba3d..bcbca1a0f50 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -4,12 +4,14 @@
* 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
@@ -66,14 +68,12 @@ static Node *pull_up_sublinks_qual_recurse(PlannerInfo *root, Node *node,
static Node *pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
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,
@@ -82,12 +82,10 @@ static void pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root,
static void make_setop_translation_list(Query *query, Index newvarno,
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);
@@ -103,7 +101,6 @@ static Node *pullup_replace_vars_callback(Var *var,
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,
@@ -111,14 +108,62 @@ static void reduce_outer_joins_pass2(Node *jtnode,
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
* semijoins or anti-semijoins.
@@ -611,16 +656,11 @@ pull_up_subqueries(PlannerInfo *root)
{
/* 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));
}
@@ -629,8 +669,6 @@ pull_up_subqueries(PlannerInfo *root)
* 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
@@ -647,37 +685,27 @@ pull_up_subqueries(PlannerInfo *root)
* 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))
@@ -693,15 +721,13 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
* 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
@@ -725,7 +751,7 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
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. */
@@ -733,50 +759,16 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
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))
@@ -788,22 +780,14 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
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:
@@ -811,37 +795,31 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
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",
@@ -861,8 +839,8 @@ pull_up_subqueries_recurse(PlannerInfo *root, Node *jtnode,
*
* 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.
@@ -871,8 +849,7 @@ 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)
{
Query *parse = root->parse;
int varno = ((RangeTblRef *) jtnode)->rtindex;
@@ -926,6 +903,12 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
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.
*/
@@ -957,8 +940,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
* easier just to keep this "if" looking the same as the one in
* 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 */
@@ -1159,6 +1141,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
case RTE_JOIN:
case RTE_CTE:
case RTE_NAMEDTUPLESTORE:
+ case RTE_RESULT:
/* these can't contain any lateral references */
break;
}
@@ -1195,7 +1178,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
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);
}
@@ -1235,17 +1218,14 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
/*
* 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;
}
@@ -1381,7 +1361,7 @@ pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex,
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))
{
@@ -1436,12 +1416,10 @@ make_setop_translation_list(Query *query, Index newvarno,
* (Note subquery is not necessarily equal to rte->subquery; it could be a
* 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 ...
@@ -1491,44 +1469,6 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte,
return false;
/*
- * Don't pull up a subquery with an empty jointree, unless it has no quals
- * and deletion_ok is true and we're not underneath an outer join.
- *
- * query_planner() will correctly generate a Result plan for a jointree
- * that's totally empty, but we can't cope with an empty FromExpr
- * appearing lower down in a jointree: we identify join rels via baserelid
- * sets, so we couldn't distinguish a join containing such a FromExpr from
- * one without it. We can only handle such cases if the place where the
- * subquery is linked is a FromExpr or inner JOIN that would still be
- * nonempty after removal of the subquery, so that it's still identifiable
- * via its contained baserelids. Safe contexts are signaled by
- * deletion_ok.
- *
- * But even in a safe context, we must keep the subquery if it has any
- * quals, because it's unclear where to put them in the upper query.
- *
- * Also, we must forbid pullup if such a subquery is underneath an outer
- * join, because then we might need to wrap its output columns with
- * PlaceHolderVars, and the PHVs would then have empty relid sets meaning
- * we couldn't tell where to evaluate them. (This test is separate from
- * the deletion_ok flag for possible future expansion: deletion_ok tells
- * whether the immediate parent site in the jointree could cope, not
- * whether we'd have PHV issues. It's possible this restriction could be
- * fixed by letting the PHVs use the relids of the parent jointree item,
- * but that complication is for another day.)
- *
- * Note that deletion of a subquery is also dependent on the check below
- * that its targetlist contains no set-returning functions. Deletion from
- * a FROM list or inner JOIN is okay only if the subquery must return
- * exactly one row.
- */
- if (subquery->jointree->fromlist == NIL &&
- (subquery->jointree->quals != NULL ||
- !deletion_ok ||
- lowest_outer_join != NULL))
- return false;
-
- /*
* If the subquery is LATERAL, check for pullup restrictions from that.
*/
if (rte->lateral)
@@ -1602,9 +1542,10 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte,
* 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
@@ -1703,11 +1644,23 @@ pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
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;
}
/*
@@ -1716,24 +1669,16 @@ pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
* 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;
@@ -1746,8 +1691,8 @@ is_simple_values(PlannerInfo *root, RangeTblEntry *rte, bool deletion_ok)
/*
* Don't pull up a VALUES that contains any set-returning or volatile
- * 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))
@@ -1850,7 +1795,9 @@ is_safe_append_member(Query *subquery)
/*
* 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
@@ -1859,6 +1806,11 @@ is_safe_append_member(Query *subquery)
* 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)
@@ -2014,6 +1966,7 @@ replace_vars_in_jointree(Node *jtnode,
case RTE_JOIN:
case RTE_CTE:
case RTE_NAMEDTUPLESTORE:
+ case RTE_RESULT:
/* these shouldn't be marked LATERAL */
Assert(false);
break;
@@ -2290,65 +2243,6 @@ pullup_replace_vars_subquery(Query *query,
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
@@ -2858,9 +2752,399 @@ reduce_outer_joins_pass2(Node *jtnode,
(int) nodeTag(jtnode));
}
+
+/*
+ * remove_useless_result_rtes
+ * Attempt to remove RTE_RESULT RTEs from the join tree.
+ *
+ * We can remove RTE_RESULT entries from the join tree using the knowledge
+ * that RTE_RESULT returns exactly one row and has no output columns. Hence,
+ * if one is inner-joined to anything else, we can delete it. Optimizations
+ * are also possible for some outer-join cases, as detailed below.
+ *
+ * Some of these optimizations depend on recognizing empty (constant-true)
+ * quals for FromExprs and JoinExprs. That makes it useful to apply this
+ * optimization pass after expression preprocessing, since that will have
+ * eliminated constant-true quals, allowing more cases to be recognized as
+ * optimizable. What's more, the usual reason for an RTE_RESULT to be present
+ * is that we pulled up a subquery or VALUES clause, thus very possibly
+ * replacing Vars with constants, making it more likely that a qual can be
+ * reduced to constant true. Also, because some optimizations depend on
+ * the outer-join type, it's best to have done reduce_outer_joins() first.
+ *
+ * A PlaceHolderVar referencing an RTE_RESULT RTE poses an obstacle to this
+ * process: we must remove the RTE_RESULT's relid from the PHV's phrels, but
+ * we must not reduce the phrels set to empty. If that would happen, and
+ * the RTE_RESULT is an immediate child of an outer join, we have to give up
+ * and not remove the RTE_RESULT: there is noplace else to evaluate the
+ * PlaceHolderVar. (That is, in such cases the RTE_RESULT *does* have output
+ * columns.) But if the RTE_RESULT is an immediate child of an inner join,
+ * we can change the PlaceHolderVar's phrels so as to evaluate it at the
+ * inner join instead. This is OK because we really only care that PHVs are
+ * evaluated above or below the correct outer joins.
+ *
+ * We used to try to do this work as part of pull_up_subqueries() where the
+ * potentially-optimizable cases get introduced; but it's way simpler, and
+ * more effective, to do it separately.
+ */
+void
+remove_useless_result_rtes(PlannerInfo *root)
+{
+ ListCell *cell;
+ ListCell *prev;
+ ListCell *next;
+
+ /* Top level of jointree must always be a FromExpr */
+ Assert(IsA(root->parse->jointree, FromExpr));
+ /* Recurse ... */
+ root->parse->jointree = (FromExpr *)
+ remove_useless_results_recurse(root, (Node *) root->parse->jointree);
+ /* We should still have a FromExpr */
+ Assert(IsA(root->parse->jointree, FromExpr));
+
+ /*
+ * Remove any PlanRowMark referencing an RTE_RESULT RTE. We obviously
+ * must do that for any RTE_RESULT that we just removed. But one for a
+ * RTE that we did not remove can be dropped anyway: since the RTE has
+ * only one possible output row, there is no need for EPQ to mark and
+ * restore that row.
+ *
+ * It's necessary, not optional, to remove the PlanRowMark for a surviving
+ * RTE_RESULT RTE; otherwise we'll generate a whole-row Var for the
+ * RTE_RESULT, which the executor has no support for.
+ */
+ prev = NULL;
+ for (cell = list_head(root->rowMarks); cell; cell = next)
+ {
+ PlanRowMark *rc = (PlanRowMark *) lfirst(cell);
+
+ next = lnext(cell);
+ if (rt_fetch(rc->rti, root->parse->rtable)->rtekind == RTE_RESULT)
+ root->rowMarks = list_delete_cell(root->rowMarks, cell, prev);
+ else
+ prev = cell;
+ }
+}
+
+/*
+ * remove_useless_results_recurse
+ * Recursive guts of remove_useless_result_rtes.
+ *
+ * This recursively processes the jointree and returns a modified jointree.
+ */
+static Node *
+remove_useless_results_recurse(PlannerInfo *root, Node *jtnode)
+{
+ Assert(jtnode != NULL);
+ if (IsA(jtnode, RangeTblRef))
+ {
+ /* Can't immediately do anything with a RangeTblRef */
+ }
+ else if (IsA(jtnode, FromExpr))
+ {
+ FromExpr *f = (FromExpr *) jtnode;
+ Relids result_relids = NULL;
+ ListCell *cell;
+ ListCell *prev;
+ ListCell *next;
+
+ /*
+ * We can drop RTE_RESULT rels from the fromlist so long as at least
+ * one child remains, since joining to a one-row table changes
+ * nothing. The easiest way to mechanize this rule is to modify the
+ * list in-place, using list_delete_cell.
+ */
+ prev = NULL;
+ for (cell = list_head(f->fromlist); cell; cell = next)
+ {
+ Node *child = (Node *) lfirst(cell);
+ int varno;
+
+ /* Recursively transform child ... */
+ child = remove_useless_results_recurse(root, child);
+ /* ... and stick it back into the tree */
+ lfirst(cell) = child;
+ next = lnext(cell);
+
+ /*
+ * If it's an RTE_RESULT with at least one sibling, we can drop
+ * it. We don't yet know what the inner join's final relid set
+ * will be, so postpone cleanup of PHVs etc till after this loop.
+ */
+ if (list_length(f->fromlist) > 1 &&
+ (varno = get_result_relid(root, child)) != 0)
+ {
+ f->fromlist = list_delete_cell(f->fromlist, cell, prev);
+ result_relids = bms_add_member(result_relids, varno);
+ }
+ else
+ prev = cell;
+ }
+
+ /*
+ * Clean up if we dropped any RTE_RESULT RTEs. This is a bit
+ * inefficient if there's more than one, but it seems better to
+ * optimize the support code for the single-relid case.
+ */
+ if (result_relids)
+ {
+ int varno = -1;
+
+ while ((varno = bms_next_member(result_relids, varno)) >= 0)
+ remove_result_refs(root, varno, (Node *) f);
+ }
+
+ /*
+ * If we're not at the top of the jointree, it's valid to simplify a
+ * degenerate FromExpr into its single child. (At the top, we must
+ * keep the FromExpr since Query.jointree is required to point to a
+ * FromExpr.)
+ */
+ if (f != root->parse->jointree &&
+ f->quals == NULL &&
+ list_length(f->fromlist) == 1)
+ return (Node *) linitial(f->fromlist);
+ }
+ else if (IsA(jtnode, JoinExpr))
+ {
+ JoinExpr *j = (JoinExpr *) jtnode;
+ int varno;
+
+ /* First, recurse */
+ j->larg = remove_useless_results_recurse(root, j->larg);
+ j->rarg = remove_useless_results_recurse(root, j->rarg);
+
+ /* Apply join-type-specific optimization rules */
+ switch (j->jointype)
+ {
+ case JOIN_INNER:
+
+ /*
+ * An inner join is equivalent to a FromExpr, so if either
+ * side was simplified to an RTE_RESULT rel, we can replace
+ * the join with a FromExpr with just the other side; and if
+ * the qual is empty (JOIN ON TRUE) then we can omit the
+ * FromExpr as well.
+ */
+ if ((varno = get_result_relid(root, j->larg)) != 0)
+ {
+ remove_result_refs(root, varno, j->rarg);
+ if (j->quals)
+ jtnode = (Node *)
+ makeFromExpr(list_make1(j->rarg), j->quals);
+ else
+ jtnode = j->rarg;
+ }
+ else if ((varno = get_result_relid(root, j->rarg)) != 0)
+ {
+ remove_result_refs(root, varno, j->larg);
+ if (j->quals)
+ jtnode = (Node *)
+ makeFromExpr(list_make1(j->larg), j->quals);
+ else
+ jtnode = j->larg;
+ }
+ break;
+ case JOIN_LEFT:
+
+ /*
+ * We can simplify this case if the RHS is an RTE_RESULT, with
+ * two different possibilities:
+ *
+ * If the qual is empty (JOIN ON TRUE), then the join can be
+ * strength-reduced to a plain inner join, since each LHS row
+ * necessarily has exactly one join partner. So we can always
+ * discard the RHS, much as in the JOIN_INNER case above.
+ *
+ * Otherwise, it's still true that each LHS row should be
+ * returned exactly once, and since the RHS returns no columns
+ * (unless there are PHVs that have to be evaluated there), we
+ * don't much care if it's null-extended or not. So in this
+ * case also, we can just ignore the qual and discard the left
+ * join.
+ */
+ if ((varno = get_result_relid(root, j->rarg)) != 0 &&
+ (j->quals == NULL ||
+ !find_dependent_phvs((Node *) root->parse, varno)))
+ {
+ remove_result_refs(root, varno, j->larg);
+ jtnode = j->larg;
+ }
+ break;
+ case JOIN_RIGHT:
+ /* Mirror-image of the JOIN_LEFT case */
+ if ((varno = get_result_relid(root, j->larg)) != 0 &&
+ (j->quals == NULL ||
+ !find_dependent_phvs((Node *) root->parse, varno)))
+ {
+ remove_result_refs(root, varno, j->rarg);
+ jtnode = j->rarg;
+ }
+ break;
+ case JOIN_SEMI:
+
+ /*
+ * We may simplify this case if the RHS is an RTE_RESULT; the
+ * join qual becomes effectively just a filter qual for the
+ * LHS, since we should either return the LHS row or not. For
+ * simplicity we inject the filter qual into a new FromExpr.
+ *
+ * Unlike the LEFT/RIGHT cases, we just Assert that there are
+ * no PHVs that need to be evaluated at the semijoin's RHS,
+ * since the rest of the query couldn't reference any outputs
+ * of the semijoin's RHS.
+ */
+ if ((varno = get_result_relid(root, j->rarg)) != 0)
+ {
+ Assert(!find_dependent_phvs((Node *) root->parse, varno));
+ remove_result_refs(root, varno, j->larg);
+ if (j->quals)
+ jtnode = (Node *)
+ makeFromExpr(list_make1(j->larg), j->quals);
+ else
+ jtnode = j->larg;
+ }
+ break;
+ case JOIN_FULL:
+ case JOIN_ANTI:
+ /* We have no special smarts for these cases */
+ break;
+ default:
+ elog(ERROR, "unrecognized join type: %d",
+ (int) j->jointype);
+ break;
+ }
+ }
+ else
+ elog(ERROR, "unrecognized node type: %d",
+ (int) nodeTag(jtnode));
+ return jtnode;
+}
+
+/*
+ * get_result_relid
+ * If jtnode is a RangeTblRef for an RTE_RESULT RTE, return its relid;
+ * otherwise return 0.
+ */
+static inline int
+get_result_relid(PlannerInfo *root, Node *jtnode)
+{
+ int varno;
+
+ if (!IsA(jtnode, RangeTblRef))
+ return 0;
+ varno = ((RangeTblRef *) jtnode)->rtindex;
+ if (rt_fetch(varno, root->parse->rtable)->rtekind != RTE_RESULT)
+ return 0;
+ return varno;
+}
+
+/*
+ * remove_result_refs
+ * Helper routine for dropping an unneeded RTE_RESULT RTE.
+ *
+ * This doesn't physically remove the RTE from the jointree, because that's
+ * more easily handled in remove_useless_results_recurse. What it does do
+ * is the necessary cleanup in the rest of the tree: we must adjust any PHVs
+ * that may reference the RTE. Be sure to call this at a point where the
+ * jointree is valid (no disconnected nodes).
+ *
+ * Note that we don't need to process the append_rel_list, since RTEs
+ * referenced directly in the jointree won't be appendrel members.
+ *
+ * varno is the RTE_RESULT's relid.
+ * newjtloc is the jointree location at which any PHVs referencing the
+ * RTE_RESULT should be evaluated instead.
+ */
+static void
+remove_result_refs(PlannerInfo *root, int varno, Node *newjtloc)
+{
+ /* Fix up PlaceHolderVars as needed */
+ /* If there are no PHVs anywhere, we can skip this bit */
+ if (root->glob->lastPHId != 0)
+ {
+ Relids subrelids;
+
+ subrelids = get_relids_in_jointree(newjtloc, false);
+ Assert(!bms_is_empty(subrelids));
+ substitute_phv_relids((Node *) root->parse, varno, subrelids);
+ }
+
+ /*
+ * We also need to remove any PlanRowMark referencing the RTE, but we
+ * postpone that work until we return to remove_useless_result_rtes.
+ */
+}
+
+
+/*
+ * find_dependent_phvs - are there any PlaceHolderVars whose relids are
+ * exactly the given varno?
+ */
+
+typedef struct
+{
+ Relids relids;
+ int sublevels_up;
+} find_dependent_phvs_context;
+
+static bool
+find_dependent_phvs_walker(Node *node,
+ find_dependent_phvs_context *context)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, PlaceHolderVar))
+ {
+ PlaceHolderVar *phv = (PlaceHolderVar *) node;
+
+ if (phv->phlevelsup == context->sublevels_up &&
+ bms_equal(context->relids, phv->phrels))
+ return true;
+ /* fall through to examine children */
+ }
+ if (IsA(node, Query))
+ {
+ /* Recurse into subselects */
+ bool result;
+
+ context->sublevels_up++;
+ result = query_tree_walker((Query *) node,
+ find_dependent_phvs_walker,
+ (void *) context, 0);
+ context->sublevels_up--;
+ return result;
+ }
+ /* Shouldn't need to handle planner auxiliary nodes here */
+ Assert(!IsA(node, SpecialJoinInfo));
+ Assert(!IsA(node, AppendRelInfo));
+ Assert(!IsA(node, PlaceHolderInfo));
+ Assert(!IsA(node, MinMaxAggInfo));
+
+ return expression_tree_walker(node, find_dependent_phvs_walker,
+ (void *) context);
+}
+
+static bool
+find_dependent_phvs(Node *node, int varno)
+{
+ find_dependent_phvs_context context;
+
+ context.relids = bms_make_singleton(varno);
+ context.sublevels_up = 0;
+
+ /*
+ * Must be prepared to start with a Query or a bare expression tree.
+ */
+ return query_or_expression_tree_walker(node,
+ find_dependent_phvs_walker,
+ (void *) &context,
+ 0);
+}
+
/*
- * substitute_multiple_relids - adjust node relid sets after pulling up
- * 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).
@@ -2876,11 +3160,11 @@ typedef struct
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;
@@ -2895,6 +3179,8 @@ substitute_multiple_relids_walker(Node *node,
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 */
}
@@ -2905,7 +3191,7 @@ substitute_multiple_relids_walker(Node *node,
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;
@@ -2916,14 +3202,14 @@ substitute_multiple_relids_walker(Node *node,
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;
@@ -2933,7 +3219,7 @@ substitute_multiple_relids(Node *node, int varno, Relids subrelids)
* Must be prepared to start with a Query or a bare expression tree.
*/
query_or_expression_tree_walker(node,
- substitute_multiple_relids_walker,
+ substitute_phv_relids_walker,
(void *) &context,
0);
}
@@ -2943,7 +3229,7 @@ substitute_multiple_relids(Node *node, int varno, Relids subrelids)
*
* When we pull up a subquery, any AppendRelInfo references to the subquery's
* 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.
@@ -2974,9 +3260,9 @@ fix_append_rel_relids(List *append_rel_list, int varno, Relids subrelids)
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);
}
}