|
42 | 42 | #include "rewrite/rewriteManip.h"
|
43 | 43 |
|
44 | 44 |
|
| 45 | +typedef struct nullingrel_info |
| 46 | +{ |
| 47 | + /* |
| 48 | + * For each leaf RTE, nullingrels[rti] is the set of relids of outer joins |
| 49 | + * that potentially null that RTE. |
| 50 | + */ |
| 51 | + Relids *nullingrels; |
| 52 | + /* Length of range table (maximum index in nullingrels[]) */ |
| 53 | + int rtlength; /* used only for assertion checks */ |
| 54 | +} nullingrel_info; |
| 55 | + |
45 | 56 | typedef struct pullup_replace_vars_context
|
46 | 57 | {
|
47 | 58 | PlannerInfo *root;
|
48 | 59 | List *targetlist; /* tlist of subquery being pulled up */
|
49 | 60 | RangeTblEntry *target_rte; /* RTE of subquery */
|
50 | 61 | Relids relids; /* relids within subquery, as numbered after
|
51 | 62 | * pullup (set only if target_rte->lateral) */
|
| 63 | + nullingrel_info *nullinfo; /* per-RTE nullingrel info (set only if |
| 64 | + * target_rte->lateral) */ |
52 | 65 | bool *outer_hasSubLinks; /* -> outer query's hasSubLinks */
|
53 | 66 | int varno; /* varno of subquery */
|
54 | 67 | bool wrap_non_vars; /* do we need all non-Var outputs to be PHVs? */
|
@@ -142,6 +155,9 @@ static void substitute_phv_relids(Node *node,
|
142 | 155 | static void fix_append_rel_relids(PlannerInfo *root, int varno,
|
143 | 156 | Relids subrelids);
|
144 | 157 | static Node *find_jointree_node_for_rel(Node *jtnode, int relid);
|
| 158 | +static nullingrel_info *get_nullingrels(Query *parse); |
| 159 | +static void get_nullingrels_recurse(Node *jtnode, Relids upper_nullingrels, |
| 160 | + nullingrel_info *info); |
145 | 161 |
|
146 | 162 |
|
147 | 163 | /*
|
@@ -1259,10 +1275,16 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
|
1259 | 1275 | rvcontext.targetlist = subquery->targetList;
|
1260 | 1276 | rvcontext.target_rte = rte;
|
1261 | 1277 | if (rte->lateral)
|
| 1278 | + { |
1262 | 1279 | rvcontext.relids = get_relids_in_jointree((Node *) subquery->jointree,
|
1263 | 1280 | true, true);
|
1264 |
| - else /* won't need relids */ |
| 1281 | + rvcontext.nullinfo = get_nullingrels(parse); |
| 1282 | + } |
| 1283 | + else /* won't need these values */ |
| 1284 | + { |
1265 | 1285 | rvcontext.relids = NULL;
|
| 1286 | + rvcontext.nullinfo = NULL; |
| 1287 | + } |
1266 | 1288 | rvcontext.outer_hasSubLinks = &parse->hasSubLinks;
|
1267 | 1289 | rvcontext.varno = varno;
|
1268 | 1290 | /* this flag will be set below, if needed */
|
@@ -1725,6 +1747,9 @@ is_simple_subquery(PlannerInfo *root, Query *subquery, RangeTblEntry *rte,
|
1725 | 1747 | * such refs to be wrapped in PlaceHolderVars, even when they're below
|
1726 | 1748 | * the nearest outer join? But it's a pretty hokey usage, so not
|
1727 | 1749 | * clear this is worth sweating over.)
|
| 1750 | + * |
| 1751 | + * If you change this, see also the comments about lateral references |
| 1752 | + * in pullup_replace_vars_callback(). |
1728 | 1753 | */
|
1729 | 1754 | if (lowest_outer_join != NULL)
|
1730 | 1755 | {
|
@@ -1809,7 +1834,8 @@ pull_up_simple_values(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte)
|
1809 | 1834 | rvcontext.root = root;
|
1810 | 1835 | rvcontext.targetlist = tlist;
|
1811 | 1836 | rvcontext.target_rte = rte;
|
1812 |
| - rvcontext.relids = NULL; |
| 1837 | + rvcontext.relids = NULL; /* can't be any lateral references here */ |
| 1838 | + rvcontext.nullinfo = NULL; |
1813 | 1839 | rvcontext.outer_hasSubLinks = &parse->hasSubLinks;
|
1814 | 1840 | rvcontext.varno = varno;
|
1815 | 1841 | rvcontext.wrap_non_vars = false;
|
@@ -1971,9 +1997,10 @@ pull_up_constant_function(PlannerInfo *root, Node *jtnode,
|
1971 | 1997 | /*
|
1972 | 1998 | * Since this function was reduced to a Const, it doesn't contain any
|
1973 | 1999 | * lateral references, even if it's marked as LATERAL. This means we
|
1974 |
| - * don't need to fill relids. |
| 2000 | + * don't need to fill relids or nullinfo. |
1975 | 2001 | */
|
1976 | 2002 | rvcontext.relids = NULL;
|
| 2003 | + rvcontext.nullinfo = NULL; |
1977 | 2004 |
|
1978 | 2005 | rvcontext.outer_hasSubLinks = &parse->hasSubLinks;
|
1979 | 2006 | rvcontext.varno = ((RangeTblRef *) jtnode)->rtindex;
|
@@ -2688,9 +2715,52 @@ pullup_replace_vars_callback(Var *var,
|
2688 | 2715 | {
|
2689 | 2716 | /*
|
2690 | 2717 | * There should be Vars/PHVs within the expression that we can
|
2691 |
| - * modify. Per above discussion, modify only Vars/PHVs of the |
2692 |
| - * subquery, not lateral references. |
| 2718 | + * modify. Vars/PHVs of the subquery should have the full |
| 2719 | + * var->varnullingrels added to them, but if there are lateral |
| 2720 | + * references within the expression, those must be marked with |
| 2721 | + * only the nullingrels that potentially apply to them. (This |
| 2722 | + * corresponds to the fact that the expression will now be |
| 2723 | + * evaluated at the join level of the Var that we are replacing: |
| 2724 | + * the lateral references may have bubbled up through fewer outer |
| 2725 | + * joins than the subquery's Vars have. Per the discussion above, |
| 2726 | + * we'll still get the right answers.) That relid set could be |
| 2727 | + * different for different lateral relations, so we have to do |
| 2728 | + * this work for each one. |
| 2729 | + * |
| 2730 | + * (Currently, the restrictions in is_simple_subquery() mean that |
| 2731 | + * at most we have to remove the lowest outer join's relid from |
| 2732 | + * the nullingrels of a lateral reference. However, we might |
| 2733 | + * relax those restrictions someday, so let's do this right.) |
2693 | 2734 | */
|
| 2735 | + if (rcon->target_rte->lateral) |
| 2736 | + { |
| 2737 | + nullingrel_info *nullinfo = rcon->nullinfo; |
| 2738 | + Relids lvarnos; |
| 2739 | + int lvarno; |
| 2740 | + |
| 2741 | + /* |
| 2742 | + * Identify lateral varnos used within newnode. We must do |
| 2743 | + * this before injecting var->varnullingrels into the tree. |
| 2744 | + */ |
| 2745 | + lvarnos = pull_varnos(rcon->root, newnode); |
| 2746 | + lvarnos = bms_del_members(lvarnos, rcon->relids); |
| 2747 | + /* For each one, add relevant nullingrels if any */ |
| 2748 | + lvarno = -1; |
| 2749 | + while ((lvarno = bms_next_member(lvarnos, lvarno)) >= 0) |
| 2750 | + { |
| 2751 | + Relids lnullingrels; |
| 2752 | + |
| 2753 | + Assert(lvarno > 0 && lvarno <= nullinfo->rtlength); |
| 2754 | + lnullingrels = bms_intersect(var->varnullingrels, |
| 2755 | + nullinfo->nullingrels[lvarno]); |
| 2756 | + if (!bms_is_empty(lnullingrels)) |
| 2757 | + newnode = add_nulling_relids(newnode, |
| 2758 | + bms_make_singleton(lvarno), |
| 2759 | + lnullingrels); |
| 2760 | + } |
| 2761 | + } |
| 2762 | + |
| 2763 | + /* Finally, deal with Vars/PHVs of the subquery itself */ |
2694 | 2764 | newnode = add_nulling_relids(newnode,
|
2695 | 2765 | rcon->relids,
|
2696 | 2766 | var->varnullingrels);
|
@@ -4120,3 +4190,94 @@ find_jointree_node_for_rel(Node *jtnode, int relid)
|
4120 | 4190 | (int) nodeTag(jtnode));
|
4121 | 4191 | return NULL;
|
4122 | 4192 | }
|
| 4193 | + |
| 4194 | +/* |
| 4195 | + * get_nullingrels: collect info about which outer joins null which relations |
| 4196 | + * |
| 4197 | + * The result struct contains, for each leaf relation used in the query, |
| 4198 | + * the set of relids of outer joins that potentially null that rel. |
| 4199 | + */ |
| 4200 | +static nullingrel_info * |
| 4201 | +get_nullingrels(Query *parse) |
| 4202 | +{ |
| 4203 | + nullingrel_info *result = palloc_object(nullingrel_info); |
| 4204 | + |
| 4205 | + result->rtlength = list_length(parse->rtable); |
| 4206 | + result->nullingrels = palloc0_array(Relids, result->rtlength + 1); |
| 4207 | + get_nullingrels_recurse((Node *) parse->jointree, NULL, result); |
| 4208 | + return result; |
| 4209 | +} |
| 4210 | + |
| 4211 | +/* |
| 4212 | + * Recursive guts of get_nullingrels(). |
| 4213 | + * |
| 4214 | + * Note: at any recursion level, the passed-down upper_nullingrels must be |
| 4215 | + * treated as a constant, but it can be stored directly into *info |
| 4216 | + * if we're at leaf level. Upper recursion levels do not free their mutated |
| 4217 | + * copies of the nullingrels, because those are probably referenced by |
| 4218 | + * at least one leaf rel. |
| 4219 | + */ |
| 4220 | +static void |
| 4221 | +get_nullingrels_recurse(Node *jtnode, Relids upper_nullingrels, |
| 4222 | + nullingrel_info *info) |
| 4223 | +{ |
| 4224 | + if (jtnode == NULL) |
| 4225 | + return; |
| 4226 | + if (IsA(jtnode, RangeTblRef)) |
| 4227 | + { |
| 4228 | + int varno = ((RangeTblRef *) jtnode)->rtindex; |
| 4229 | + |
| 4230 | + Assert(varno > 0 && varno <= info->rtlength); |
| 4231 | + info->nullingrels[varno] = upper_nullingrels; |
| 4232 | + } |
| 4233 | + else if (IsA(jtnode, FromExpr)) |
| 4234 | + { |
| 4235 | + FromExpr *f = (FromExpr *) jtnode; |
| 4236 | + ListCell *l; |
| 4237 | + |
| 4238 | + foreach(l, f->fromlist) |
| 4239 | + { |
| 4240 | + get_nullingrels_recurse(lfirst(l), upper_nullingrels, info); |
| 4241 | + } |
| 4242 | + } |
| 4243 | + else if (IsA(jtnode, JoinExpr)) |
| 4244 | + { |
| 4245 | + JoinExpr *j = (JoinExpr *) jtnode; |
| 4246 | + Relids local_nullingrels; |
| 4247 | + |
| 4248 | + switch (j->jointype) |
| 4249 | + { |
| 4250 | + case JOIN_INNER: |
| 4251 | + get_nullingrels_recurse(j->larg, upper_nullingrels, info); |
| 4252 | + get_nullingrels_recurse(j->rarg, upper_nullingrels, info); |
| 4253 | + break; |
| 4254 | + case JOIN_LEFT: |
| 4255 | + case JOIN_SEMI: |
| 4256 | + case JOIN_ANTI: |
| 4257 | + local_nullingrels = bms_add_member(bms_copy(upper_nullingrels), |
| 4258 | + j->rtindex); |
| 4259 | + get_nullingrels_recurse(j->larg, upper_nullingrels, info); |
| 4260 | + get_nullingrels_recurse(j->rarg, local_nullingrels, info); |
| 4261 | + break; |
| 4262 | + case JOIN_FULL: |
| 4263 | + local_nullingrels = bms_add_member(bms_copy(upper_nullingrels), |
| 4264 | + j->rtindex); |
| 4265 | + get_nullingrels_recurse(j->larg, local_nullingrels, info); |
| 4266 | + get_nullingrels_recurse(j->rarg, local_nullingrels, info); |
| 4267 | + break; |
| 4268 | + case JOIN_RIGHT: |
| 4269 | + local_nullingrels = bms_add_member(bms_copy(upper_nullingrels), |
| 4270 | + j->rtindex); |
| 4271 | + get_nullingrels_recurse(j->larg, local_nullingrels, info); |
| 4272 | + get_nullingrels_recurse(j->rarg, upper_nullingrels, info); |
| 4273 | + break; |
| 4274 | + default: |
| 4275 | + elog(ERROR, "unrecognized join type: %d", |
| 4276 | + (int) j->jointype); |
| 4277 | + break; |
| 4278 | + } |
| 4279 | + } |
| 4280 | + else |
| 4281 | + elog(ERROR, "unrecognized node type: %d", |
| 4282 | + (int) nodeTag(jtnode)); |
| 4283 | +} |
0 commit comments