#include "rewrite/rewriteManip.h"
+typedef struct nullingrel_info
+{
+ /*
+ * For each leaf RTE, nullingrels[rti] is the set of relids of outer joins
+ * that potentially null that RTE.
+ */
+ Relids *nullingrels;
+ /* Length of range table (maximum index in nullingrels[]) */
+ int rtlength; /* used only for assertion checks */
+} nullingrel_info;
+
typedef struct pullup_replace_vars_context
{
PlannerInfo *root;
RangeTblEntry *target_rte; /* RTE of subquery */
Relids relids; /* relids within subquery, as numbered after
* pullup (set only if target_rte->lateral) */
+ nullingrel_info *nullinfo; /* per-RTE nullingrel info (set only if
+ * target_rte->lateral) */
bool *outer_hasSubLinks; /* -> outer query's hasSubLinks */
int varno; /* varno of subquery */
bool wrap_non_vars; /* do we need all non-Var outputs to be PHVs? */
static void fix_append_rel_relids(PlannerInfo *root, int varno,
Relids subrelids);
static Node *find_jointree_node_for_rel(Node *jtnode, int relid);
+static nullingrel_info *get_nullingrels(Query *parse);
+static void get_nullingrels_recurse(Node *jtnode, Relids upper_nullingrels,
+ nullingrel_info *info);
/*
rvcontext.targetlist = subquery->targetList;
rvcontext.target_rte = rte;
if (rte->lateral)
+ {
rvcontext.relids = get_relids_in_jointree((Node *) subquery->jointree,
true, true);
- else /* won't need relids */
+ rvcontext.nullinfo = get_nullingrels(parse);
+ }
+ else /* won't need these values */
+ {
rvcontext.relids = NULL;
+ rvcontext.nullinfo = NULL;
+ }
rvcontext.outer_hasSubLinks = &parse->hasSubLinks;
rvcontext.varno = varno;
/* this flag will be set below, if needed */
* such refs to be wrapped in PlaceHolderVars, even when they're below
* the nearest outer join? But it's a pretty hokey usage, so not
* clear this is worth sweating over.)
+ *
+ * If you change this, see also the comments about lateral references
+ * in pullup_replace_vars_callback().
*/
if (lowest_outer_join != NULL)
{
rvcontext.root = root;
rvcontext.targetlist = tlist;
rvcontext.target_rte = rte;
- rvcontext.relids = NULL;
+ rvcontext.relids = NULL; /* can't be any lateral references here */
+ rvcontext.nullinfo = NULL;
rvcontext.outer_hasSubLinks = &parse->hasSubLinks;
rvcontext.varno = varno;
rvcontext.wrap_non_vars = false;
/*
* Since this function was reduced to a Const, it doesn't contain any
* lateral references, even if it's marked as LATERAL. This means we
- * don't need to fill relids.
+ * don't need to fill relids or nullinfo.
*/
rvcontext.relids = NULL;
+ rvcontext.nullinfo = NULL;
rvcontext.outer_hasSubLinks = &parse->hasSubLinks;
rvcontext.varno = ((RangeTblRef *) jtnode)->rtindex;
{
/*
* There should be Vars/PHVs within the expression that we can
- * modify. Per above discussion, modify only Vars/PHVs of the
- * subquery, not lateral references.
+ * modify. Vars/PHVs of the subquery should have the full
+ * var->varnullingrels added to them, but if there are lateral
+ * references within the expression, those must be marked with
+ * only the nullingrels that potentially apply to them. (This
+ * corresponds to the fact that the expression will now be
+ * evaluated at the join level of the Var that we are replacing:
+ * the lateral references may have bubbled up through fewer outer
+ * joins than the subquery's Vars have. Per the discussion above,
+ * we'll still get the right answers.) That relid set could be
+ * different for different lateral relations, so we have to do
+ * this work for each one.
+ *
+ * (Currently, the restrictions in is_simple_subquery() mean that
+ * at most we have to remove the lowest outer join's relid from
+ * the nullingrels of a lateral reference. However, we might
+ * relax those restrictions someday, so let's do this right.)
*/
+ if (rcon->target_rte->lateral)
+ {
+ nullingrel_info *nullinfo = rcon->nullinfo;
+ Relids lvarnos;
+ int lvarno;
+
+ /*
+ * Identify lateral varnos used within newnode. We must do
+ * this before injecting var->varnullingrels into the tree.
+ */
+ lvarnos = pull_varnos(rcon->root, newnode);
+ lvarnos = bms_del_members(lvarnos, rcon->relids);
+ /* For each one, add relevant nullingrels if any */
+ lvarno = -1;
+ while ((lvarno = bms_next_member(lvarnos, lvarno)) >= 0)
+ {
+ Relids lnullingrels;
+
+ Assert(lvarno > 0 && lvarno <= nullinfo->rtlength);
+ lnullingrels = bms_intersect(var->varnullingrels,
+ nullinfo->nullingrels[lvarno]);
+ if (!bms_is_empty(lnullingrels))
+ newnode = add_nulling_relids(newnode,
+ bms_make_singleton(lvarno),
+ lnullingrels);
+ }
+ }
+
+ /* Finally, deal with Vars/PHVs of the subquery itself */
newnode = add_nulling_relids(newnode,
rcon->relids,
var->varnullingrels);
(int) nodeTag(jtnode));
return NULL;
}
+
+/*
+ * get_nullingrels: collect info about which outer joins null which relations
+ *
+ * The result struct contains, for each leaf relation used in the query,
+ * the set of relids of outer joins that potentially null that rel.
+ */
+static nullingrel_info *
+get_nullingrels(Query *parse)
+{
+ nullingrel_info *result = palloc_object(nullingrel_info);
+
+ result->rtlength = list_length(parse->rtable);
+ result->nullingrels = palloc0_array(Relids, result->rtlength + 1);
+ get_nullingrels_recurse((Node *) parse->jointree, NULL, result);
+ return result;
+}
+
+/*
+ * Recursive guts of get_nullingrels().
+ *
+ * Note: at any recursion level, the passed-down upper_nullingrels must be
+ * treated as a constant, but it can be stored directly into *info
+ * if we're at leaf level. Upper recursion levels do not free their mutated
+ * copies of the nullingrels, because those are probably referenced by
+ * at least one leaf rel.
+ */
+static void
+get_nullingrels_recurse(Node *jtnode, Relids upper_nullingrels,
+ nullingrel_info *info)
+{
+ if (jtnode == NULL)
+ return;
+ if (IsA(jtnode, RangeTblRef))
+ {
+ int varno = ((RangeTblRef *) jtnode)->rtindex;
+
+ Assert(varno > 0 && varno <= info->rtlength);
+ info->nullingrels[varno] = upper_nullingrels;
+ }
+ else if (IsA(jtnode, FromExpr))
+ {
+ FromExpr *f = (FromExpr *) jtnode;
+ ListCell *l;
+
+ foreach(l, f->fromlist)
+ {
+ get_nullingrels_recurse(lfirst(l), upper_nullingrels, info);
+ }
+ }
+ else if (IsA(jtnode, JoinExpr))
+ {
+ JoinExpr *j = (JoinExpr *) jtnode;
+ Relids local_nullingrels;
+
+ switch (j->jointype)
+ {
+ case JOIN_INNER:
+ get_nullingrels_recurse(j->larg, upper_nullingrels, info);
+ get_nullingrels_recurse(j->rarg, upper_nullingrels, info);
+ break;
+ case JOIN_LEFT:
+ case JOIN_SEMI:
+ case JOIN_ANTI:
+ local_nullingrels = bms_add_member(bms_copy(upper_nullingrels),
+ j->rtindex);
+ get_nullingrels_recurse(j->larg, upper_nullingrels, info);
+ get_nullingrels_recurse(j->rarg, local_nullingrels, info);
+ break;
+ case JOIN_FULL:
+ local_nullingrels = bms_add_member(bms_copy(upper_nullingrels),
+ j->rtindex);
+ get_nullingrels_recurse(j->larg, local_nullingrels, info);
+ get_nullingrels_recurse(j->rarg, local_nullingrels, info);
+ break;
+ case JOIN_RIGHT:
+ local_nullingrels = bms_add_member(bms_copy(upper_nullingrels),
+ j->rtindex);
+ get_nullingrels_recurse(j->larg, local_nullingrels, info);
+ get_nullingrels_recurse(j->rarg, upper_nullingrels, info);
+ break;
+ default:
+ elog(ERROR, "unrecognized join type: %d",
+ (int) j->jointype);
+ break;
+ }
+ }
+ else
+ elog(ERROR, "unrecognized node type: %d",
+ (int) nodeTag(jtnode));
+}