-/*
- * pull_up_subqueries
- * Look for subqueries in the rangetable that can be pulled up into
- * the parent query. If the subquery has no special features like
- * grouping/aggregation then we can merge it into the parent's jointree.
- *
- * below_outer_join is true if this jointree node is within the nullable
- * side of an outer join. This restricts what we can do.
- *
- * 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
- * subquery RangeTblRef entries will be replaced. Also, we can't turn
- * ResolveNew loose on the whole jointree, because it'll return a mutated
- * copy of the tree; we have to invoke it just on the quals, instead.
- */
-static Node *
-pull_up_subqueries(Query *parse, Node *jtnode, bool below_outer_join)
-{
- if (jtnode == NULL)
- return NULL;
- if (IsA(jtnode, RangeTblRef))
- {
- int varno = ((RangeTblRef *) jtnode)->rtindex;
- RangeTblEntry *rte = rt_fetch(varno, parse->rtable);
- Query *subquery = rte->subquery;
-
- /*
- * Is this a subquery RTE, and if so, is the subquery simple
- * enough to pull up? (If not, do nothing at this node.)
- *
- * If we are inside an outer join, only pull up subqueries whose
- * targetlists are nullable --- otherwise substituting their tlist
- * entries for upper Var references would do the wrong thing
- * (the results wouldn't become NULL when they're supposed to).
- * XXX This could be improved by generating pseudo-variables for
- * such expressions; we'd have to figure out how to get the pseudo-
- * variables evaluated at the right place in the modified plan tree.
- * Fix it someday.
- *
- * Note: even if the subquery itself is simple enough, we can't pull
- * it up if there is a reference to its whole tuple result. Perhaps
- * a pseudo-variable is the answer here too.
- */
- if (rte->rtekind == RTE_SUBQUERY && is_simple_subquery(subquery) &&
- (!below_outer_join || has_nullable_targetlist(subquery)) &&
- !contain_whole_tuple_var((Node *) parse, varno, 0))
- {
- int rtoffset;
- List *subtlist;
- List *rt;
-
- /*
- * First, recursively pull up the subquery's subqueries, so
- * that this routine's processing is complete for its jointree
- * and rangetable. NB: if the same subquery is referenced
- * from multiple jointree items (which can't happen normally,
- * but might after rule rewriting), then we will invoke this
- * processing multiple times on that subquery. OK because
- * nothing will happen after the first time. We do have to be
- * careful to copy everything we pull up, however, or risk
- * having chunks of structure multiply linked.
- */
- subquery->jointree = (FromExpr *)
- pull_up_subqueries(subquery, (Node *) subquery->jointree,
- below_outer_join);
-
- /*
- * Now make a modifiable copy of the subquery that we can
- * run OffsetVarNodes on.
- */
- subquery = copyObject(subquery);
-
- /*
- * Adjust varnos in subquery so that we can append its
- * rangetable to upper query's.
- */
- rtoffset = length(parse->rtable);
- OffsetVarNodes((Node *) subquery, rtoffset, 0);
-
- /*
- * Replace all of the top query's references to the subquery's
- * outputs with copies of the adjusted subtlist items, being
- * careful not to replace any of the jointree structure.
- * (This'd be a lot cleaner if we could use query_tree_mutator.)
- */
- subtlist = subquery->targetList;
- parse->targetList = (List *)
- ResolveNew((Node *) parse->targetList,
- varno, 0, subtlist, CMD_SELECT, 0);
- resolvenew_in_jointree((Node *) parse->jointree, varno, subtlist);
- Assert(parse->setOperations == NULL);
- parse->havingQual =
- ResolveNew(parse->havingQual,
- varno, 0, subtlist, CMD_SELECT, 0);
-
- foreach(rt, parse->rtable)
- {
- RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
-
- if (rte->rtekind == RTE_JOIN)
- rte->joinaliasvars = (List *)
- ResolveNew((Node *) rte->joinaliasvars,
- varno, 0, subtlist, CMD_SELECT, 0);
- }
-
- /*
- * Now append the adjusted rtable entries to upper query.
- * (We hold off until after fixing the upper rtable entries;
- * no point in running that code on the subquery ones too.)
- */
- parse->rtable = nconc(parse->rtable, subquery->rtable);
-
- /*
- * Pull up any FOR UPDATE markers, too. (OffsetVarNodes
- * already adjusted the marker values, so just nconc the list.)
- */
- parse->rowMarks = nconc(parse->rowMarks, subquery->rowMarks);
-
- /*
- * Miscellaneous housekeeping.
- */
- parse->hasSubLinks |= subquery->hasSubLinks;
- /* subquery won't be pulled up if it hasAggs, so no work there */
-
- /*
- * Return the adjusted subquery jointree to replace the
- * RangeTblRef entry in my jointree.
- */
- return (Node *) subquery->jointree;
- }
- }
- else if (IsA(jtnode, FromExpr))
- {
- FromExpr *f = (FromExpr *) jtnode;
- List *l;
-
- foreach(l, f->fromlist)
- lfirst(l) = pull_up_subqueries(parse, lfirst(l),
- below_outer_join);
- }
- else if (IsA(jtnode, JoinExpr))
- {
- JoinExpr *j = (JoinExpr *) jtnode;
-
- /* Recurse, being careful to tell myself when inside outer join */
- switch (j->jointype)
- {
- case JOIN_INNER:
- j->larg = pull_up_subqueries(parse, j->larg,
- below_outer_join);
- j->rarg = pull_up_subqueries(parse, j->rarg,
- below_outer_join);
- break;
- case JOIN_LEFT:
- j->larg = pull_up_subqueries(parse, j->larg,
- below_outer_join);
- j->rarg = pull_up_subqueries(parse, j->rarg,
- true);
- break;
- case JOIN_FULL:
- j->larg = pull_up_subqueries(parse, j->larg,
- true);
- j->rarg = pull_up_subqueries(parse, j->rarg,
- true);
- break;
- case JOIN_RIGHT:
- j->larg = pull_up_subqueries(parse, j->larg,
- true);
- j->rarg = pull_up_subqueries(parse, j->rarg,
- below_outer_join);
- break;
- case JOIN_UNION:
-
- /*
- * This is where we fail if upper levels of planner
- * haven't rewritten UNION JOIN as an Append ...
- */
- elog(ERROR, "UNION JOIN is not implemented yet");
- break;
- default:
- elog(ERROR, "pull_up_subqueries: unexpected join type %d",
- j->jointype);
- break;
- }
- }
- else
- elog(ERROR, "pull_up_subqueries: unexpected node type %d",
- nodeTag(jtnode));
- return jtnode;
-}
-
-/*
- * is_simple_subquery
- * Check a subquery in the range table to see if it's simple enough
- * to pull up into the parent query.
- */
-static bool
-is_simple_subquery(Query *subquery)
-{
- /*
- * Let's just make sure it's a valid subselect ...
- */
- if (!IsA(subquery, Query) ||
- subquery->commandType != CMD_SELECT ||
- subquery->resultRelation != 0 ||
- subquery->into != NULL ||
- subquery->isPortal)
- elog(ERROR, "is_simple_subquery: subquery is bogus");
-
- /*
- * Can't currently pull up a query with setops. Maybe after querytree
- * redesign...
- */
- if (subquery->setOperations)
- return false;
-
- /*
- * Can't pull up a subquery involving grouping, aggregation, sorting,
- * or limiting.
- */
- if (subquery->hasAggs ||
- subquery->groupClause ||
- subquery->havingQual ||
- subquery->sortClause ||
- subquery->distinctClause ||
- subquery->limitOffset ||
- subquery->limitCount)
- return false;
-
- /*
- * Don't pull up a subquery that has any set-returning functions in
- * its targetlist. Otherwise we might well wind up inserting
- * set-returning functions into places where they mustn't go,
- * such as quals of higher queries.
- */
- if (expression_returns_set((Node *) subquery->targetList))
- return false;
-
- /*
- * Hack: don't try to pull up a subquery with an empty jointree.
- * query_planner() will correctly generate a Result plan for a
- * jointree that's totally empty, but I don't think the right things
- * happen if an empty FromExpr appears lower down in a jointree. Not
- * worth working hard on this, just to collapse SubqueryScan/Result
- * into Result...
- */
- if (subquery->jointree->fromlist == NIL)
- return false;
-
- return true;
-}
-
-/*
- * has_nullable_targetlist
- * Check a subquery in the range table to see if all the non-junk
- * targetlist items are simple variables (and, hence, will correctly
- * go to NULL when examined above the point of an outer join).
- *
- * A possible future extension is to accept strict functions of simple
- * variables, eg, "x + 1".
- */
-static bool
-has_nullable_targetlist(Query *subquery)
-{
- List *l;
-
- foreach(l, subquery->targetList)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
-
- /* ignore resjunk columns */
- if (tle->resdom->resjunk)
- continue;
-
- /* Okay if tlist item is a simple Var */
- if (tle->expr && IsA(tle->expr, Var))
- continue;
-
- return false;
- }
- return true;
-}
-
-/*
- * Helper routine for pull_up_subqueries: do ResolveNew on every expression
- * in the jointree, without changing the jointree structure itself. Ugly,
- * but there's no other way...
- */
-static void
-resolvenew_in_jointree(Node *jtnode, int varno, List *subtlist)
-{
- if (jtnode == NULL)
- return;
- if (IsA(jtnode, RangeTblRef))
- {
- /* nothing to do here */
- }
- else if (IsA(jtnode, FromExpr))
- {
- FromExpr *f = (FromExpr *) jtnode;
- List *l;
-
- foreach(l, f->fromlist)
- resolvenew_in_jointree(lfirst(l), varno, subtlist);
- f->quals = ResolveNew(f->quals,
- varno, 0, subtlist, CMD_SELECT, 0);
- }
- else if (IsA(jtnode, JoinExpr))
- {
- JoinExpr *j = (JoinExpr *) jtnode;
-
- resolvenew_in_jointree(j->larg, varno, subtlist);
- resolvenew_in_jointree(j->rarg, varno, subtlist);
- j->quals = ResolveNew(j->quals,
- varno, 0, subtlist, CMD_SELECT, 0);
-
- /*
- * We don't bother to update the colvars list, since it won't be
- * used again ...
- */
- }
- else
- elog(ERROR, "resolvenew_in_jointree: unexpected node type %d",
- nodeTag(jtnode));
-}
-
-/*
- * preprocess_jointree
- * Attempt to simplify a query's jointree.
- *
- * If we succeed in pulling up a subquery then we might form a jointree
- * in which a FromExpr is a direct child of another FromExpr. In that
- * case we can consider collapsing the two FromExprs into one. This is
- * an optional conversion, since the planner will work correctly either
- * way. But we may find a better plan (at the cost of more planning time)
- * if we merge the two nodes.
- *
- * NOTE: don't try to do this in the same jointree scan that does subquery
- * pullup! Since we're changing the jointree structure here, that wouldn't
- * work reliably --- see comments for pull_up_subqueries().
- */
-static Node *
-preprocess_jointree(Query *parse, Node *jtnode)
-{
- if (jtnode == NULL)
- return NULL;
- if (IsA(jtnode, RangeTblRef))
- {
- /* nothing to do here... */
- }
- else if (IsA(jtnode, FromExpr))
- {
- FromExpr *f = (FromExpr *) jtnode;
- List *newlist = NIL;
- List *l;
-
- foreach(l, f->fromlist)
- {
- Node *child = (Node *) lfirst(l);
-
- /* Recursively simplify the child... */
- child = preprocess_jointree(parse, child);
- /* Now, is it a FromExpr? */
- if (child && IsA(child, FromExpr))
- {
- /*
- * Yes, so do we want to merge it into parent? Always do
- * so if child has just one element (since that doesn't
- * make the parent's list any longer). Otherwise we have
- * to be careful about the increase in planning time
- * caused by combining the two join search spaces into
- * one. Our heuristic is to merge if the merge will
- * produce a join list no longer than GEQO_RELS/2.
- * (Perhaps need an additional user parameter?)
- */
- FromExpr *subf = (FromExpr *) child;
- int childlen = length(subf->fromlist);
- int myothers = length(newlist) + length(lnext(l));
-
- if (childlen <= 1 || (childlen + myothers) <= geqo_rels / 2)
- {
- newlist = nconc(newlist, subf->fromlist);
- f->quals = make_and_qual(f->quals, subf->quals);
- }
- else
- newlist = lappend(newlist, child);
- }
- else
- newlist = lappend(newlist, child);
- }
- f->fromlist = newlist;
- }
- else if (IsA(jtnode, JoinExpr))
- {
- JoinExpr *j = (JoinExpr *) jtnode;
-
- /* Can't usefully change the JoinExpr, but recurse on children */
- j->larg = preprocess_jointree(parse, j->larg);
- j->rarg = preprocess_jointree(parse, j->rarg);
- }
- else
- elog(ERROR, "preprocess_jointree: unexpected node type %d",
- nodeTag(jtnode));
- return jtnode;
-}
-