Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * placeholder.c
4 : : * PlaceHolderVar and PlaceHolderInfo manipulation routines
5 : : *
6 : : *
7 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
8 : : * Portions Copyright (c) 1994, Regents of the University of California
9 : : *
10 : : *
11 : : * IDENTIFICATION
12 : : * src/backend/optimizer/util/placeholder.c
13 : : *
14 : : *-------------------------------------------------------------------------
15 : : */
16 : : #include "postgres.h"
17 : :
18 : : #include "nodes/nodeFuncs.h"
19 : : #include "optimizer/cost.h"
20 : : #include "optimizer/optimizer.h"
21 : : #include "optimizer/pathnode.h"
22 : : #include "optimizer/placeholder.h"
23 : : #include "optimizer/planmain.h"
24 : : #include "utils/lsyscache.h"
25 : :
26 : :
27 : : typedef struct contain_placeholder_references_context
28 : : {
29 : : int relid;
30 : : int sublevels_up;
31 : : } contain_placeholder_references_context;
32 : :
33 : : /* Local functions */
34 : : static void find_placeholders_recurse(PlannerInfo *root, Node *jtnode);
35 : : static void find_placeholders_in_expr(PlannerInfo *root, Node *expr);
36 : : static bool contain_placeholder_references_walker(Node *node,
37 : : contain_placeholder_references_context *context);
38 : : static bool contain_noop_phv_walker(Node *node, void *context);
39 : : static Node *strip_noop_phvs_mutator(Node *node, void *context);
40 : :
41 : :
42 : : /*
43 : : * make_placeholder_expr
44 : : * Make a PlaceHolderVar for the given expression.
45 : : *
46 : : * phrels is the syntactic location (as a set of relids) to attribute
47 : : * to the expression.
48 : : *
49 : : * The caller is responsible for adjusting phlevelsup and phnullingrels
50 : : * as needed. Because we do not know here which query level the PHV
51 : : * will be associated with, it's important that this function touches
52 : : * only root->glob; messing with other parts of PlannerInfo would be
53 : : * likely to do the wrong thing.
54 : : */
55 : : PlaceHolderVar *
56 : 2404 : make_placeholder_expr(PlannerInfo *root, Expr *expr, Relids phrels)
57 : : {
58 : 2404 : PlaceHolderVar *phv = makeNode(PlaceHolderVar);
59 : :
60 : 2404 : phv->phexpr = expr;
61 : 2404 : phv->phrels = phrels;
62 : 2404 : phv->phnullingrels = NULL; /* caller may change this later */
63 : 2404 : phv->phid = ++(root->glob->lastPHId);
64 : 2404 : phv->phlevelsup = 0; /* caller may change this later */
65 : :
66 : 2404 : return phv;
67 : : }
68 : :
69 : : /*
70 : : * find_placeholder_info
71 : : * Fetch the PlaceHolderInfo for the given PHV
72 : : *
73 : : * If the PlaceHolderInfo doesn't exist yet, create it if we haven't yet
74 : : * frozen the set of PlaceHolderInfos for the query; else throw an error.
75 : : *
76 : : * This is separate from make_placeholder_expr because subquery pullup has
77 : : * to make PlaceHolderVars for expressions that might not be used at all in
78 : : * the upper query, or might not remain after const-expression simplification.
79 : : * We build PlaceHolderInfos only for PHVs that are still present in the
80 : : * simplified query passed to query_planner().
81 : : *
82 : : * Note: this should only be called after query_planner() has started.
83 : : */
84 : : PlaceHolderInfo *
85 : 9068 : find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv)
86 : : {
87 : : PlaceHolderInfo *phinfo;
88 : : Relids rels_used;
89 : :
90 : : /* if this ever isn't true, we'd need to be able to look in parent lists */
91 : : Assert(phv->phlevelsup == 0);
92 : :
93 : : /* Use placeholder_array to look up existing PlaceHolderInfo quickly */
94 [ + + ]: 9068 : if (phv->phid < root->placeholder_array_size)
95 : 7645 : phinfo = root->placeholder_array[phv->phid];
96 : : else
97 : 1423 : phinfo = NULL;
98 [ + + ]: 9068 : if (phinfo != NULL)
99 : : {
100 : : Assert(phinfo->phid == phv->phid);
101 : 6861 : return phinfo;
102 : : }
103 : :
104 : : /* Not found, so create it */
105 [ - + ]: 2207 : if (root->placeholdersFrozen)
106 [ # # ]: 0 : elog(ERROR, "too late to create a new PlaceHolderInfo");
107 : :
108 : 2207 : phinfo = makeNode(PlaceHolderInfo);
109 : :
110 : 2207 : phinfo->phid = phv->phid;
111 : 2207 : phinfo->ph_var = copyObject(phv);
112 : :
113 : : /*
114 : : * By convention, phinfo->ph_var->phnullingrels is always empty, since the
115 : : * PlaceHolderInfo represents the initially-calculated state of the
116 : : * PlaceHolderVar. PlaceHolderVars appearing in the query tree might have
117 : : * varying values of phnullingrels, reflecting outer joins applied above
118 : : * the calculation level.
119 : : */
120 : 2207 : phinfo->ph_var->phnullingrels = NULL;
121 : :
122 : : /*
123 : : * Any referenced rels that are outside the PHV's syntactic scope are
124 : : * LATERAL references, which should be included in ph_lateral but not in
125 : : * ph_eval_at. If no referenced rels are within the syntactic scope,
126 : : * force evaluation at the syntactic location.
127 : : */
128 : 2207 : rels_used = pull_varnos(root, (Node *) phv->phexpr);
129 : 2207 : phinfo->ph_lateral = bms_difference(rels_used, phv->phrels);
130 : 2207 : phinfo->ph_eval_at = bms_int_members(rels_used, phv->phrels);
131 : : /* If no contained vars, force evaluation at syntactic location */
132 [ + + ]: 2207 : if (bms_is_empty(phinfo->ph_eval_at))
133 : : {
134 : 1058 : phinfo->ph_eval_at = bms_copy(phv->phrels);
135 : : Assert(!bms_is_empty(phinfo->ph_eval_at));
136 : : }
137 : 2207 : phinfo->ph_needed = NULL; /* initially it's unused */
138 : : /* for the moment, estimate width using just the datatype info */
139 : 2207 : phinfo->ph_width = get_typavgwidth(exprType((Node *) phv->phexpr),
140 : 2207 : exprTypmod((Node *) phv->phexpr));
141 : :
142 : : /*
143 : : * Add to both placeholder_list and placeholder_array. Note: because we
144 : : * store pointers to the PlaceHolderInfos in two data structures, it'd be
145 : : * unsafe to pass the whole placeholder_list structure through
146 : : * expression_tree_mutator or the like --- or at least, you'd have to
147 : : * rebuild the placeholder_array afterwards.
148 : : */
149 : 2207 : root->placeholder_list = lappend(root->placeholder_list, phinfo);
150 : :
151 [ + + ]: 2207 : if (phinfo->phid >= root->placeholder_array_size)
152 : : {
153 : : /* Must allocate or enlarge placeholder_array */
154 : : int new_size;
155 : :
156 [ - + ]: 1423 : new_size = root->placeholder_array_size ? root->placeholder_array_size * 2 : 8;
157 [ - + ]: 1423 : while (phinfo->phid >= new_size)
158 : 0 : new_size *= 2;
159 [ - + ]: 1423 : if (root->placeholder_array)
160 : 0 : root->placeholder_array =
161 : 0 : repalloc0_array(root->placeholder_array, PlaceHolderInfo *, root->placeholder_array_size, new_size);
162 : : else
163 : 1423 : root->placeholder_array =
164 : 1423 : palloc0_array(PlaceHolderInfo *, new_size);
165 : 1423 : root->placeholder_array_size = new_size;
166 : : }
167 : 2207 : root->placeholder_array[phinfo->phid] = phinfo;
168 : :
169 : : /*
170 : : * The PHV's contained expression may contain other, lower-level PHVs. We
171 : : * now know we need to get those into the PlaceHolderInfo list, too, so we
172 : : * may as well do that immediately.
173 : : */
174 : 2207 : find_placeholders_in_expr(root, (Node *) phinfo->ph_var->phexpr);
175 : :
176 : 2207 : return phinfo;
177 : : }
178 : :
179 : : /*
180 : : * find_placeholders_in_jointree
181 : : * Search the jointree for PlaceHolderVars, and build PlaceHolderInfos
182 : : *
183 : : * We don't need to look at the targetlist because build_base_rel_tlists()
184 : : * will already have made entries for any PHVs in the tlist.
185 : : */
186 : : void
187 : 258178 : find_placeholders_in_jointree(PlannerInfo *root)
188 : : {
189 : : /* This must be done before freezing the set of PHIs */
190 : : Assert(!root->placeholdersFrozen);
191 : :
192 : : /* We need do nothing if the query contains no PlaceHolderVars */
193 [ + + ]: 258178 : if (root->glob->lastPHId != 0)
194 : : {
195 : : /* Start recursion at top of jointree */
196 : : Assert(root->parse->jointree != NULL &&
197 : : IsA(root->parse->jointree, FromExpr));
198 : 1730 : find_placeholders_recurse(root, (Node *) root->parse->jointree);
199 : : }
200 : 258178 : }
201 : :
202 : : /*
203 : : * find_placeholders_recurse
204 : : * One recursion level of find_placeholders_in_jointree.
205 : : *
206 : : * jtnode is the current jointree node to examine.
207 : : */
208 : : static void
209 : 8192 : find_placeholders_recurse(PlannerInfo *root, Node *jtnode)
210 : : {
211 [ - + ]: 8192 : if (jtnode == NULL)
212 : 0 : return;
213 [ + + ]: 8192 : if (IsA(jtnode, RangeTblRef))
214 : : {
215 : : /* No quals to deal with here */
216 : : }
217 [ + + ]: 4139 : else if (IsA(jtnode, FromExpr))
218 : : {
219 : 2048 : FromExpr *f = (FromExpr *) jtnode;
220 : : ListCell *l;
221 : :
222 : : /*
223 : : * First, recurse to handle child joins.
224 : : */
225 [ + - + + : 4328 : foreach(l, f->fromlist)
+ + ]
226 : : {
227 : 2280 : find_placeholders_recurse(root, lfirst(l));
228 : : }
229 : :
230 : : /*
231 : : * Now process the top-level quals.
232 : : */
233 : 2048 : find_placeholders_in_expr(root, f->quals);
234 : : }
235 [ + - ]: 2091 : else if (IsA(jtnode, JoinExpr))
236 : : {
237 : 2091 : JoinExpr *j = (JoinExpr *) jtnode;
238 : :
239 : : /*
240 : : * First, recurse to handle child joins.
241 : : */
242 : 2091 : find_placeholders_recurse(root, j->larg);
243 : 2091 : find_placeholders_recurse(root, j->rarg);
244 : :
245 : : /* Process the qual clauses */
246 : 2091 : find_placeholders_in_expr(root, j->quals);
247 : : }
248 : : else
249 [ # # ]: 0 : elog(ERROR, "unrecognized node type: %d",
250 : : (int) nodeTag(jtnode));
251 : : }
252 : :
253 : : /*
254 : : * find_placeholders_in_expr
255 : : * Find all PlaceHolderVars in the given expression, and create
256 : : * PlaceHolderInfo entries for them.
257 : : */
258 : : static void
259 : 6346 : find_placeholders_in_expr(PlannerInfo *root, Node *expr)
260 : : {
261 : : List *vars;
262 : : ListCell *vl;
263 : :
264 : : /*
265 : : * pull_var_clause does more than we need here, but it'll do and it's
266 : : * convenient to use.
267 : : */
268 : 6346 : vars = pull_var_clause(expr,
269 : : PVC_RECURSE_AGGREGATES |
270 : : PVC_RECURSE_WINDOWFUNCS |
271 : : PVC_INCLUDE_PLACEHOLDERS);
272 [ + + + + : 13568 : foreach(vl, vars)
+ + ]
273 : : {
274 : 7222 : PlaceHolderVar *phv = (PlaceHolderVar *) lfirst(vl);
275 : :
276 : : /* Ignore any plain Vars */
277 [ + + ]: 7222 : if (!IsA(phv, PlaceHolderVar))
278 : 6234 : continue;
279 : :
280 : : /* Create a PlaceHolderInfo entry if there's not one already */
281 : 988 : (void) find_placeholder_info(root, phv);
282 : : }
283 : 6346 : list_free(vars);
284 : 6346 : }
285 : :
286 : : /*
287 : : * fix_placeholder_input_needed_levels
288 : : * Adjust the "needed at" levels for placeholder inputs
289 : : *
290 : : * This is called after we've finished determining the eval_at levels for
291 : : * all placeholders. We need to make sure that all vars and placeholders
292 : : * needed to evaluate each placeholder will be available at the scan or join
293 : : * level where the evaluation will be done. (It might seem that scan-level
294 : : * evaluations aren't interesting, but that's not so: a LATERAL reference
295 : : * within a placeholder's expression needs to cause the referenced var or
296 : : * placeholder to be marked as needed in the scan where it's evaluated.)
297 : : * Note that this loop can have side-effects on the ph_needed sets of other
298 : : * PlaceHolderInfos; that's okay because we don't examine ph_needed here, so
299 : : * there are no ordering issues to worry about.
300 : : */
301 : : void
302 : 258178 : fix_placeholder_input_needed_levels(PlannerInfo *root)
303 : : {
304 : : ListCell *lc;
305 : :
306 [ + + + + : 260385 : foreach(lc, root->placeholder_list)
+ + ]
307 : : {
308 : 2207 : PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
309 : 2207 : List *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
310 : : PVC_RECURSE_AGGREGATES |
311 : : PVC_RECURSE_WINDOWFUNCS |
312 : : PVC_INCLUDE_PLACEHOLDERS);
313 : :
314 : 2207 : add_vars_to_targetlist(root, vars, phinfo->ph_eval_at);
315 : 2207 : list_free(vars);
316 : : }
317 : 258178 : }
318 : :
319 : : /*
320 : : * add_placeholders_to_base_rels
321 : : * Add any required PlaceHolderVars to base rels' targetlists.
322 : : *
323 : : * If any placeholder can be computed at a base rel and is needed above it,
324 : : * add it to that rel's targetlist. This might look like it could be merged
325 : : * with fix_placeholder_input_needed_levels, but it must be separate because
326 : : * join removal happens in between, and can change the ph_eval_at sets. There
327 : : * is essentially the same logic in add_placeholders_to_joinrel, but we can't
328 : : * do that part until joinrels are formed.
329 : : */
330 : : void
331 : 249576 : add_placeholders_to_base_rels(PlannerInfo *root)
332 : : {
333 : : ListCell *lc;
334 : :
335 [ + + + + : 251587 : foreach(lc, root->placeholder_list)
+ + ]
336 : : {
337 : 2011 : PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
338 : 2011 : Relids eval_at = phinfo->ph_eval_at;
339 : : int varno;
340 : :
341 [ + + + + ]: 3692 : if (bms_get_singleton_member(eval_at, &varno) &&
342 : 1681 : bms_nonempty_difference(phinfo->ph_needed, eval_at))
343 : : {
344 : 1601 : RelOptInfo *rel = find_base_rel(root, varno);
345 : :
346 : : /*
347 : : * As in add_vars_to_targetlist(), a value computed at scan level
348 : : * has not yet been nulled by any outer join, so its phnullingrels
349 : : * should be empty.
350 : : */
351 : : Assert(phinfo->ph_var->phnullingrels == NULL);
352 : :
353 : : /* Copying the PHV might be unnecessary here, but be safe */
354 : 1601 : rel->reltarget->exprs = lappend(rel->reltarget->exprs,
355 : 1601 : copyObject(phinfo->ph_var));
356 : : /* reltarget's cost and width fields will be updated later */
357 : : }
358 : : }
359 : 249576 : }
360 : :
361 : : /*
362 : : * add_placeholders_to_joinrel
363 : : * Add any newly-computable PlaceHolderVars to a join rel's targetlist;
364 : : * and if computable PHVs contain lateral references, add those
365 : : * references to the joinrel's direct_lateral_relids.
366 : : *
367 : : * A join rel should emit a PlaceHolderVar if (a) the PHV can be computed
368 : : * at or below this join level and (b) the PHV is needed above this level.
369 : : * Our caller build_join_rel() has already added any PHVs that were computed
370 : : * in either join input rel, so we need add only newly-computable ones to
371 : : * the targetlist. However, direct_lateral_relids must be updated for every
372 : : * PHV computable at or below this join, as explained below.
373 : : */
374 : : void
375 : 180304 : add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
376 : : RelOptInfo *outer_rel, RelOptInfo *inner_rel,
377 : : SpecialJoinInfo *sjinfo)
378 : : {
379 : 180304 : Relids relids = joinrel->relids;
380 : 180304 : int64 tuple_width = joinrel->reltarget->width;
381 : : ListCell *lc;
382 : :
383 [ + + + + : 184032 : foreach(lc, root->placeholder_list)
+ + ]
384 : : {
385 : 3728 : PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
386 : :
387 : : /* Is it computable here? */
388 [ + + ]: 3728 : if (bms_is_subset(phinfo->ph_eval_at, relids))
389 : : {
390 : : /* Is it still needed above this joinrel? */
391 [ + + ]: 2588 : if (bms_nonempty_difference(phinfo->ph_needed, relids))
392 : : {
393 : : /*
394 : : * Yes, but only add to tlist if it wasn't computed in either
395 : : * input; otherwise it should be there already. Also, we
396 : : * charge the cost of evaluating the contained expression if
397 : : * the PHV can be computed here but not in either input. This
398 : : * is a bit bogus because we make the decision based on the
399 : : * first pair of possible input relations considered for the
400 : : * joinrel. With other pairs, it might be possible to compute
401 : : * the PHV in one input or the other, and then we'd be double
402 : : * charging the PHV's cost for some join paths. For now, live
403 : : * with that; but we might want to improve it later by
404 : : * refiguring the reltarget costs for each pair of inputs.
405 : : */
406 [ + + ]: 1760 : if (!bms_is_subset(phinfo->ph_eval_at, outer_rel->relids) &&
407 [ + + ]: 1283 : !bms_is_subset(phinfo->ph_eval_at, inner_rel->relids))
408 : : {
409 : : /* Copying might be unnecessary here, but be safe */
410 : 404 : PlaceHolderVar *phv = copyObject(phinfo->ph_var);
411 : : QualCost cost;
412 : :
413 : : /*
414 : : * It'll start out not nulled by anything. Joins above
415 : : * this one might add to its phnullingrels later, in much
416 : : * the same way as for Vars.
417 : : */
418 : : Assert(phv->phnullingrels == NULL);
419 : :
420 : 404 : joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
421 : : phv);
422 : 404 : cost_qual_eval_node(&cost, (Node *) phv->phexpr, root);
423 : 404 : joinrel->reltarget->cost.startup += cost.startup;
424 : 404 : joinrel->reltarget->cost.per_tuple += cost.per_tuple;
425 : 404 : tuple_width += phinfo->ph_width;
426 : : }
427 : : }
428 : :
429 : : /*
430 : : * Also adjust joinrel's direct_lateral_relids to include the
431 : : * PHV's source rel(s). We must do this even if we're not
432 : : * actually going to emit the PHV, otherwise join_is_legal() will
433 : : * reject valid join orderings. (In principle maybe we could
434 : : * instead remove the joinrel's lateral_relids dependency; but
435 : : * that's complicated to get right, and cases where we're not
436 : : * going to emit the PHV are too rare to justify the work.)
437 : : *
438 : : * In principle we should only do this if the join doesn't yet
439 : : * include the PHV's source rel(s). But our caller
440 : : * build_join_rel() will clean things up by removing the join's
441 : : * own relids from its direct_lateral_relids, so we needn't
442 : : * account for that here.
443 : : */
444 : 2588 : joinrel->direct_lateral_relids =
445 : 2588 : bms_add_members(joinrel->direct_lateral_relids,
446 : 2588 : phinfo->ph_lateral);
447 : : }
448 : : }
449 : :
450 : 180304 : joinrel->reltarget->width = clamp_width_est(tuple_width);
451 : 180304 : }
452 : :
453 : : /*
454 : : * contain_placeholder_references_to
455 : : * Detect whether any PlaceHolderVars in the given clause contain
456 : : * references to the given relid (typically an OJ relid).
457 : : *
458 : : * "Contain" means that there's a use of the relid inside the PHV's
459 : : * contained expression, so that changing the nullability status of
460 : : * the rel might change what the PHV computes.
461 : : *
462 : : * The code here to cope with upper-level PHVs is likely dead, but keep it
463 : : * anyway just in case.
464 : : */
465 : : bool
466 : 12407 : contain_placeholder_references_to(PlannerInfo *root, Node *clause,
467 : : int relid)
468 : : {
469 : : contain_placeholder_references_context context;
470 : :
471 : : /* We can answer quickly in the common case that there's no PHVs at all */
472 [ + + ]: 12407 : if (root->glob->lastPHId == 0)
473 : 11835 : return false;
474 : : /* Else run the recursive search */
475 : 572 : context.relid = relid;
476 : 572 : context.sublevels_up = 0;
477 : 572 : return contain_placeholder_references_walker(clause, &context);
478 : : }
479 : :
480 : : static bool
481 : 1896 : contain_placeholder_references_walker(Node *node,
482 : : contain_placeholder_references_context *context)
483 : : {
484 [ + + ]: 1896 : if (node == NULL)
485 : 130 : return false;
486 [ + + ]: 1766 : if (IsA(node, PlaceHolderVar))
487 : : {
488 : 45 : PlaceHolderVar *phv = (PlaceHolderVar *) node;
489 : :
490 : : /* We should just look through PHVs of other query levels */
491 [ + - ]: 45 : if (phv->phlevelsup == context->sublevels_up)
492 : : {
493 : : /* If phrels matches, we found what we came for */
494 [ + + ]: 45 : if (bms_is_member(context->relid, phv->phrels))
495 : 10 : return true;
496 : :
497 : : /*
498 : : * We should not examine phnullingrels: what we are looking for is
499 : : * references in the contained expression, not OJs that might null
500 : : * the result afterwards. Also, we don't need to recurse into the
501 : : * contained expression, because phrels should adequately
502 : : * summarize what's in there. So we're done here.
503 : : */
504 : 35 : return false;
505 : : }
506 : : }
507 [ - + ]: 1721 : else if (IsA(node, Query))
508 : : {
509 : : /* Recurse into RTE subquery or not-yet-planned sublink subquery */
510 : : bool result;
511 : :
512 : 0 : context->sublevels_up++;
513 : 0 : result = query_tree_walker((Query *) node,
514 : : contain_placeholder_references_walker,
515 : : context,
516 : : 0);
517 : 0 : context->sublevels_up--;
518 : 0 : return result;
519 : : }
520 : 1721 : return expression_tree_walker(node, contain_placeholder_references_walker,
521 : : context);
522 : : }
523 : :
524 : : /*
525 : : * Compute the set of outer-join relids that can null a placeholder.
526 : : *
527 : : * This is analogous to RelOptInfo.nulling_relids for Vars, but we compute it
528 : : * on-the-fly rather than saving it somewhere. Currently the value is needed
529 : : * at most once per query, so there's little value in doing otherwise. If it
530 : : * ever gains more widespread use, perhaps we should cache the result in
531 : : * PlaceHolderInfo.
532 : : */
533 : : Relids
534 : 220 : get_placeholder_nulling_relids(PlannerInfo *root, PlaceHolderInfo *phinfo)
535 : : {
536 : 220 : Relids result = NULL;
537 : 220 : int relid = -1;
538 : :
539 : : /*
540 : : * Form the union of all potential nulling OJs for each baserel included
541 : : * in ph_eval_at.
542 : : */
543 [ + + ]: 520 : while ((relid = bms_next_member(phinfo->ph_eval_at, relid)) > 0)
544 : : {
545 : 300 : RelOptInfo *rel = root->simple_rel_array[relid];
546 : :
547 : : /* ignore the RTE_GROUP RTE */
548 [ - + ]: 300 : if (relid == root->group_rtindex)
549 : 0 : continue;
550 : :
551 [ + + ]: 300 : if (rel == NULL) /* must be an outer join */
552 : : {
553 : : Assert(bms_is_member(relid, root->outer_join_rels));
554 : 20 : continue;
555 : : }
556 : 280 : result = bms_add_members(result, rel->nulling_relids);
557 : : }
558 : :
559 : : /* Now remove any OJs already included in ph_eval_at, and we're done. */
560 : 220 : result = bms_del_members(result, phinfo->ph_eval_at);
561 : 220 : return result;
562 : : }
563 : :
564 : : /*
565 : : * strip_noop_phvs
566 : : * Strip no-op PlaceHolderVar nodes from the given expression tree.
567 : : *
568 : : * A PlaceHolderVar that is not marked as nullable (i.e., its phnullingrels
569 : : * is empty) is effectively a no-op when it appears in a relation-scan-level
570 : : * expression. This function strips such PlaceHolderVars, which is useful
571 : : * for matching expressions to index keys or partition keys in cases where
572 : : * the expression has been wrapped in PlaceHolderVars during subquery pullup.
573 : : *
574 : : * IMPORTANT: the caller must ensure that the expression is a scan-level
575 : : * expression, so that non-nullable PlaceHolderVars in it are indeed no-ops.
576 : : *
577 : : * The removal is performed recursively because PlaceHolderVars can be nested
578 : : * or interleaved with other node types. We must peel back all layers to
579 : : * expose the base expression.
580 : : *
581 : : * As a performance optimization, we first use a lightweight walker to check
582 : : * for the presence of strippable PlaceHolderVars. The expensive mutator is
583 : : * invoked only if a candidate is found, avoiding unnecessary memory allocation
584 : : * and tree copying in the common case where no PlaceHolderVars are present.
585 : : */
586 : : Node *
587 : 3025822 : strip_noop_phvs(Node *node)
588 : : {
589 : : /* Don't mutate/copy if no target PHVs exist */
590 [ + + ]: 3025822 : if (!contain_noop_phv_walker(node, NULL))
591 : 3024750 : return node;
592 : :
593 : 1072 : return strip_noop_phvs_mutator(node, NULL);
594 : : }
595 : :
596 : : /*
597 : : * contain_noop_phv_walker
598 : : * Detect if there are any PlaceHolderVars in the tree that are candidates
599 : : * for stripping.
600 : : *
601 : : * We identify a PlaceHolderVar as strippable only if its phnullingrels is
602 : : * empty.
603 : : */
604 : : static bool
605 : 3105005 : contain_noop_phv_walker(Node *node, void *context)
606 : : {
607 [ + + ]: 3105005 : if (node == NULL)
608 : 6601 : return false;
609 : :
610 [ + + ]: 3098404 : if (IsA(node, PlaceHolderVar))
611 : : {
612 : 1147 : PlaceHolderVar *phv = (PlaceHolderVar *) node;
613 : :
614 [ + + ]: 1147 : if (bms_is_empty(phv->phnullingrels))
615 : 1072 : return true;
616 : : }
617 : :
618 : 3097332 : return expression_tree_walker(node, contain_noop_phv_walker,
619 : : context);
620 : : }
621 : :
622 : : /*
623 : : * strip_noop_phvs_mutator
624 : : * Recursively remove PlaceHolderVars that are not marked nullable.
625 : : *
626 : : * We strip a PlaceHolderVar only if its phnullingrels is empty, replacing it
627 : : * with its contained expression.
628 : : */
629 : : static Node *
630 : 3074 : strip_noop_phvs_mutator(Node *node, void *context)
631 : : {
632 [ - + ]: 3074 : if (node == NULL)
633 : 0 : return NULL;
634 : :
635 [ + + ]: 3074 : if (IsA(node, PlaceHolderVar))
636 : : {
637 : 1072 : PlaceHolderVar *phv = (PlaceHolderVar *) node;
638 : :
639 [ + - ]: 1072 : if (bms_is_empty(phv->phnullingrels))
640 : : {
641 : : /* Recurse on its contained expression */
642 : 1072 : return strip_noop_phvs_mutator((Node *) phv->phexpr,
643 : : context);
644 : : }
645 : :
646 : : /* Otherwise, keep this PHV but check its contained expression */
647 : : }
648 : :
649 : 2002 : return expression_tree_mutator(node, strip_noop_phvs_mutator,
650 : : context);
651 : : }
|