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 2337 : make_placeholder_expr(PlannerInfo *root, Expr *expr, Relids phrels)
57 : {
58 2337 : PlaceHolderVar *phv = makeNode(PlaceHolderVar);
59 :
60 2337 : phv->phexpr = expr;
61 2337 : phv->phrels = phrels;
62 2337 : phv->phnullingrels = NULL; /* caller may change this later */
63 2337 : phv->phid = ++(root->glob->lastPHId);
64 2337 : phv->phlevelsup = 0; /* caller may change this later */
65 :
66 2337 : 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 8593 : 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 8593 : if (phv->phid < root->placeholder_array_size)
95 7369 : phinfo = root->placeholder_array[phv->phid];
96 : else
97 1224 : phinfo = NULL;
98 8593 : if (phinfo != NULL)
99 : {
100 : Assert(phinfo->phid == phv->phid);
101 6629 : return phinfo;
102 : }
103 :
104 : /* Not found, so create it */
105 1964 : if (root->placeholdersFrozen)
106 0 : elog(ERROR, "too late to create a new PlaceHolderInfo");
107 :
108 1964 : phinfo = makeNode(PlaceHolderInfo);
109 :
110 1964 : phinfo->phid = phv->phid;
111 1964 : 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 1964 : 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 1964 : rels_used = pull_varnos(root, (Node *) phv->phexpr);
129 1964 : phinfo->ph_lateral = bms_difference(rels_used, phv->phrels);
130 1964 : phinfo->ph_eval_at = bms_int_members(rels_used, phv->phrels);
131 : /* If no contained vars, force evaluation at syntactic location */
132 1964 : if (bms_is_empty(phinfo->ph_eval_at))
133 : {
134 880 : phinfo->ph_eval_at = bms_copy(phv->phrels);
135 : Assert(!bms_is_empty(phinfo->ph_eval_at));
136 : }
137 1964 : phinfo->ph_needed = NULL; /* initially it's unused */
138 : /* for the moment, estimate width using just the datatype info */
139 1964 : phinfo->ph_width = get_typavgwidth(exprType((Node *) phv->phexpr),
140 1964 : 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 1964 : root->placeholder_list = lappend(root->placeholder_list, phinfo);
150 :
151 1964 : if (phinfo->phid >= root->placeholder_array_size)
152 : {
153 : /* Must allocate or enlarge placeholder_array */
154 : int new_size;
155 :
156 1224 : new_size = root->placeholder_array_size ? root->placeholder_array_size * 2 : 8;
157 1224 : while (phinfo->phid >= new_size)
158 0 : new_size *= 2;
159 1224 : 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 1224 : root->placeholder_array =
164 1224 : palloc0_array(PlaceHolderInfo *, new_size);
165 1224 : root->placeholder_array_size = new_size;
166 : }
167 1964 : 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 1964 : find_placeholders_in_expr(root, (Node *) phinfo->ph_var->phexpr);
175 :
176 1964 : 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 253490 : 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 253490 : 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 1498 : find_placeholders_recurse(root, (Node *) root->parse->jointree);
199 : }
200 253490 : }
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 7113 : find_placeholders_recurse(PlannerInfo *root, Node *jtnode)
210 : {
211 7113 : if (jtnode == NULL)
212 0 : return;
213 7113 : if (IsA(jtnode, RangeTblRef))
214 : {
215 : /* No quals to deal with here */
216 : }
217 3582 : else if (IsA(jtnode, FromExpr))
218 : {
219 1781 : FromExpr *f = (FromExpr *) jtnode;
220 : ListCell *l;
221 :
222 : /*
223 : * First, recurse to handle child joins.
224 : */
225 3794 : foreach(l, f->fromlist)
226 : {
227 2013 : find_placeholders_recurse(root, lfirst(l));
228 : }
229 :
230 : /*
231 : * Now process the top-level quals.
232 : */
233 1781 : find_placeholders_in_expr(root, f->quals);
234 : }
235 1801 : else if (IsA(jtnode, JoinExpr))
236 : {
237 1801 : JoinExpr *j = (JoinExpr *) jtnode;
238 :
239 : /*
240 : * First, recurse to handle child joins.
241 : */
242 1801 : find_placeholders_recurse(root, j->larg);
243 1801 : find_placeholders_recurse(root, j->rarg);
244 :
245 : /* Process the qual clauses */
246 1801 : 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 5546 : 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 5546 : vars = pull_var_clause(expr,
269 : PVC_RECURSE_AGGREGATES |
270 : PVC_RECURSE_WINDOWFUNCS |
271 : PVC_INCLUDE_PLACEHOLDERS);
272 11883 : foreach(vl, vars)
273 : {
274 6337 : PlaceHolderVar *phv = (PlaceHolderVar *) lfirst(vl);
275 :
276 : /* Ignore any plain Vars */
277 6337 : if (!IsA(phv, PlaceHolderVar))
278 5533 : continue;
279 :
280 : /* Create a PlaceHolderInfo entry if there's not one already */
281 804 : (void) find_placeholder_info(root, phv);
282 : }
283 5546 : list_free(vars);
284 5546 : }
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 253490 : fix_placeholder_input_needed_levels(PlannerInfo *root)
303 : {
304 : ListCell *lc;
305 :
306 255454 : foreach(lc, root->placeholder_list)
307 : {
308 1964 : PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
309 1964 : List *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
310 : PVC_RECURSE_AGGREGATES |
311 : PVC_RECURSE_WINDOWFUNCS |
312 : PVC_INCLUDE_PLACEHOLDERS);
313 :
314 1964 : add_vars_to_targetlist(root, vars, phinfo->ph_eval_at);
315 1964 : list_free(vars);
316 : }
317 253490 : }
318 :
319 : /*
320 : * rebuild_placeholder_attr_needed
321 : * Put back attr_needed bits for Vars/PHVs needed in PlaceHolderVars.
322 : *
323 : * This is used to rebuild attr_needed/ph_needed sets after removal of a
324 : * useless outer join. It should match what
325 : * fix_placeholder_input_needed_levels did, except that we call
326 : * add_vars_to_attr_needed not add_vars_to_targetlist.
327 : */
328 : void
329 9052 : rebuild_placeholder_attr_needed(PlannerInfo *root)
330 : {
331 : ListCell *lc;
332 :
333 9221 : foreach(lc, root->placeholder_list)
334 : {
335 169 : PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
336 169 : List *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
337 : PVC_RECURSE_AGGREGATES |
338 : PVC_RECURSE_WINDOWFUNCS |
339 : PVC_INCLUDE_PLACEHOLDERS);
340 :
341 169 : add_vars_to_attr_needed(root, vars, phinfo->ph_eval_at);
342 169 : list_free(vars);
343 : }
344 9052 : }
345 :
346 : /*
347 : * add_placeholders_to_base_rels
348 : * Add any required PlaceHolderVars to base rels' targetlists.
349 : *
350 : * If any placeholder can be computed at a base rel and is needed above it,
351 : * add it to that rel's targetlist. This might look like it could be merged
352 : * with fix_placeholder_input_needed_levels, but it must be separate because
353 : * join removal happens in between, and can change the ph_eval_at sets. There
354 : * is essentially the same logic in add_placeholders_to_joinrel, but we can't
355 : * do that part until joinrels are formed.
356 : */
357 : void
358 253490 : add_placeholders_to_base_rels(PlannerInfo *root)
359 : {
360 : ListCell *lc;
361 :
362 255449 : foreach(lc, root->placeholder_list)
363 : {
364 1959 : PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
365 1959 : Relids eval_at = phinfo->ph_eval_at;
366 : int varno;
367 :
368 3600 : if (bms_get_singleton_member(eval_at, &varno) &&
369 1641 : bms_nonempty_difference(phinfo->ph_needed, eval_at))
370 : {
371 1576 : RelOptInfo *rel = find_base_rel(root, varno);
372 :
373 : /*
374 : * As in add_vars_to_targetlist(), a value computed at scan level
375 : * has not yet been nulled by any outer join, so its phnullingrels
376 : * should be empty.
377 : */
378 : Assert(phinfo->ph_var->phnullingrels == NULL);
379 :
380 : /* Copying the PHV might be unnecessary here, but be safe */
381 1576 : rel->reltarget->exprs = lappend(rel->reltarget->exprs,
382 1576 : copyObject(phinfo->ph_var));
383 : /* reltarget's cost and width fields will be updated later */
384 : }
385 : }
386 253490 : }
387 :
388 : /*
389 : * add_placeholders_to_joinrel
390 : * Add any newly-computable PlaceHolderVars to a join rel's targetlist;
391 : * and if computable PHVs contain lateral references, add those
392 : * references to the joinrel's direct_lateral_relids.
393 : *
394 : * A join rel should emit a PlaceHolderVar if (a) the PHV can be computed
395 : * at or below this join level and (b) the PHV is needed above this level.
396 : * Our caller build_join_rel() has already added any PHVs that were computed
397 : * in either join input rel, so we need add only newly-computable ones to
398 : * the targetlist. However, direct_lateral_relids must be updated for every
399 : * PHV computable at or below this join, as explained below.
400 : */
401 : void
402 179394 : add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
403 : RelOptInfo *outer_rel, RelOptInfo *inner_rel,
404 : SpecialJoinInfo *sjinfo)
405 : {
406 179394 : Relids relids = joinrel->relids;
407 179394 : int64 tuple_width = joinrel->reltarget->width;
408 : ListCell *lc;
409 :
410 183070 : foreach(lc, root->placeholder_list)
411 : {
412 3676 : PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
413 :
414 : /* Is it computable here? */
415 3676 : if (bms_is_subset(phinfo->ph_eval_at, relids))
416 : {
417 : /* Is it still needed above this joinrel? */
418 2536 : if (bms_nonempty_difference(phinfo->ph_needed, relids))
419 : {
420 : /*
421 : * Yes, but only add to tlist if it wasn't computed in either
422 : * input; otherwise it should be there already. Also, we
423 : * charge the cost of evaluating the contained expression if
424 : * the PHV can be computed here but not in either input. This
425 : * is a bit bogus because we make the decision based on the
426 : * first pair of possible input relations considered for the
427 : * joinrel. With other pairs, it might be possible to compute
428 : * the PHV in one input or the other, and then we'd be double
429 : * charging the PHV's cost for some join paths. For now, live
430 : * with that; but we might want to improve it later by
431 : * refiguring the reltarget costs for each pair of inputs.
432 : */
433 1739 : if (!bms_is_subset(phinfo->ph_eval_at, outer_rel->relids) &&
434 1262 : !bms_is_subset(phinfo->ph_eval_at, inner_rel->relids))
435 : {
436 : /* Copying might be unnecessary here, but be safe */
437 393 : PlaceHolderVar *phv = copyObject(phinfo->ph_var);
438 : QualCost cost;
439 :
440 : /*
441 : * It'll start out not nulled by anything. Joins above
442 : * this one might add to its phnullingrels later, in much
443 : * the same way as for Vars.
444 : */
445 : Assert(phv->phnullingrels == NULL);
446 :
447 393 : joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
448 : phv);
449 393 : cost_qual_eval_node(&cost, (Node *) phv->phexpr, root);
450 393 : joinrel->reltarget->cost.startup += cost.startup;
451 393 : joinrel->reltarget->cost.per_tuple += cost.per_tuple;
452 393 : tuple_width += phinfo->ph_width;
453 : }
454 : }
455 :
456 : /*
457 : * Also adjust joinrel's direct_lateral_relids to include the
458 : * PHV's source rel(s). We must do this even if we're not
459 : * actually going to emit the PHV, otherwise join_is_legal() will
460 : * reject valid join orderings. (In principle maybe we could
461 : * instead remove the joinrel's lateral_relids dependency; but
462 : * that's complicated to get right, and cases where we're not
463 : * going to emit the PHV are too rare to justify the work.)
464 : *
465 : * In principle we should only do this if the join doesn't yet
466 : * include the PHV's source rel(s). But our caller
467 : * build_join_rel() will clean things up by removing the join's
468 : * own relids from its direct_lateral_relids, so we needn't
469 : * account for that here.
470 : */
471 2536 : joinrel->direct_lateral_relids =
472 2536 : bms_add_members(joinrel->direct_lateral_relids,
473 2536 : phinfo->ph_lateral);
474 : }
475 : }
476 :
477 179394 : joinrel->reltarget->width = clamp_width_est(tuple_width);
478 179394 : }
479 :
480 : /*
481 : * contain_placeholder_references_to
482 : * Detect whether any PlaceHolderVars in the given clause contain
483 : * references to the given relid (typically an OJ relid).
484 : *
485 : * "Contain" means that there's a use of the relid inside the PHV's
486 : * contained expression, so that changing the nullability status of
487 : * the rel might change what the PHV computes.
488 : *
489 : * The code here to cope with upper-level PHVs is likely dead, but keep it
490 : * anyway just in case.
491 : */
492 : bool
493 12607 : contain_placeholder_references_to(PlannerInfo *root, Node *clause,
494 : int relid)
495 : {
496 : contain_placeholder_references_context context;
497 :
498 : /* We can answer quickly in the common case that there's no PHVs at all */
499 12607 : if (root->glob->lastPHId == 0)
500 12087 : return false;
501 : /* Else run the recursive search */
502 520 : context.relid = relid;
503 520 : context.sublevels_up = 0;
504 520 : return contain_placeholder_references_walker(clause, &context);
505 : }
506 :
507 : static bool
508 1763 : contain_placeholder_references_walker(Node *node,
509 : contain_placeholder_references_context *context)
510 : {
511 1763 : if (node == NULL)
512 105 : return false;
513 1658 : if (IsA(node, PlaceHolderVar))
514 : {
515 45 : PlaceHolderVar *phv = (PlaceHolderVar *) node;
516 :
517 : /* We should just look through PHVs of other query levels */
518 45 : if (phv->phlevelsup == context->sublevels_up)
519 : {
520 : /* If phrels matches, we found what we came for */
521 45 : if (bms_is_member(context->relid, phv->phrels))
522 10 : return true;
523 :
524 : /*
525 : * We should not examine phnullingrels: what we are looking for is
526 : * references in the contained expression, not OJs that might null
527 : * the result afterwards. Also, we don't need to recurse into the
528 : * contained expression, because phrels should adequately
529 : * summarize what's in there. So we're done here.
530 : */
531 35 : return false;
532 : }
533 : }
534 1613 : else if (IsA(node, Query))
535 : {
536 : /* Recurse into RTE subquery or not-yet-planned sublink subquery */
537 : bool result;
538 :
539 0 : context->sublevels_up++;
540 0 : result = query_tree_walker((Query *) node,
541 : contain_placeholder_references_walker,
542 : context,
543 : 0);
544 0 : context->sublevels_up--;
545 0 : return result;
546 : }
547 1613 : return expression_tree_walker(node, contain_placeholder_references_walker,
548 : context);
549 : }
550 :
551 : /*
552 : * Compute the set of outer-join relids that can null a placeholder.
553 : *
554 : * This is analogous to RelOptInfo.nulling_relids for Vars, but we compute it
555 : * on-the-fly rather than saving it somewhere. Currently the value is needed
556 : * at most once per query, so there's little value in doing otherwise. If it
557 : * ever gains more widespread use, perhaps we should cache the result in
558 : * PlaceHolderInfo.
559 : */
560 : Relids
561 210 : get_placeholder_nulling_relids(PlannerInfo *root, PlaceHolderInfo *phinfo)
562 : {
563 210 : Relids result = NULL;
564 210 : int relid = -1;
565 :
566 : /*
567 : * Form the union of all potential nulling OJs for each baserel included
568 : * in ph_eval_at.
569 : */
570 480 : while ((relid = bms_next_member(phinfo->ph_eval_at, relid)) > 0)
571 : {
572 270 : RelOptInfo *rel = root->simple_rel_array[relid];
573 :
574 : /* ignore the RTE_GROUP RTE */
575 270 : if (relid == root->group_rtindex)
576 0 : continue;
577 :
578 270 : if (rel == NULL) /* must be an outer join */
579 : {
580 : Assert(bms_is_member(relid, root->outer_join_rels));
581 10 : continue;
582 : }
583 260 : result = bms_add_members(result, rel->nulling_relids);
584 : }
585 :
586 : /* Now remove any OJs already included in ph_eval_at, and we're done. */
587 210 : result = bms_del_members(result, phinfo->ph_eval_at);
588 210 : return result;
589 : }
590 :
591 : /*
592 : * strip_noop_phvs
593 : * Strip no-op PlaceHolderVar nodes from the given expression tree.
594 : *
595 : * A PlaceHolderVar that is not marked as nullable (i.e., its phnullingrels
596 : * is empty) is effectively a no-op when it appears in a relation-scan-level
597 : * expression. This function strips such PlaceHolderVars, which is useful
598 : * for matching expressions to index keys or partition keys in cases where
599 : * the expression has been wrapped in PlaceHolderVars during subquery pullup.
600 : *
601 : * IMPORTANT: the caller must ensure that the expression is a scan-level
602 : * expression, so that non-nullable PlaceHolderVars in it are indeed no-ops.
603 : *
604 : * The removal is performed recursively because PlaceHolderVars can be nested
605 : * or interleaved with other node types. We must peel back all layers to
606 : * expose the base expression.
607 : *
608 : * As a performance optimization, we first use a lightweight walker to check
609 : * for the presence of strippable PlaceHolderVars. The expensive mutator is
610 : * invoked only if a candidate is found, avoiding unnecessary memory allocation
611 : * and tree copying in the common case where no PlaceHolderVars are present.
612 : */
613 : Node *
614 3106330 : strip_noop_phvs(Node *node)
615 : {
616 : /* Don't mutate/copy if no target PHVs exist */
617 3106330 : if (!contain_noop_phv_walker(node, NULL))
618 3105370 : return node;
619 :
620 960 : return strip_noop_phvs_mutator(node, NULL);
621 : }
622 :
623 : /*
624 : * contain_noop_phv_walker
625 : * Detect if there are any PlaceHolderVars in the tree that are candidates
626 : * for stripping.
627 : *
628 : * We identify a PlaceHolderVar as strippable only if its phnullingrels is
629 : * empty.
630 : */
631 : static bool
632 3187667 : contain_noop_phv_walker(Node *node, void *context)
633 : {
634 3187667 : if (node == NULL)
635 6767 : return false;
636 :
637 3180900 : if (IsA(node, PlaceHolderVar))
638 : {
639 1035 : PlaceHolderVar *phv = (PlaceHolderVar *) node;
640 :
641 1035 : if (bms_is_empty(phv->phnullingrels))
642 960 : return true;
643 : }
644 :
645 3179940 : return expression_tree_walker(node, contain_noop_phv_walker,
646 : context);
647 : }
648 :
649 : /*
650 : * strip_noop_phvs_mutator
651 : * Recursively remove PlaceHolderVars that are not marked nullable.
652 : *
653 : * We strip a PlaceHolderVar only if its phnullingrels is empty, replacing it
654 : * with its contained expression.
655 : */
656 : static Node *
657 2850 : strip_noop_phvs_mutator(Node *node, void *context)
658 : {
659 2850 : if (node == NULL)
660 0 : return NULL;
661 :
662 2850 : if (IsA(node, PlaceHolderVar))
663 : {
664 960 : PlaceHolderVar *phv = (PlaceHolderVar *) node;
665 :
666 960 : if (bms_is_empty(phv->phnullingrels))
667 : {
668 : /* Recurse on its contained expression */
669 960 : return strip_noop_phvs_mutator((Node *) phv->phexpr,
670 : context);
671 : }
672 :
673 : /* Otherwise, keep this PHV but check its contained expression */
674 : }
675 :
676 1890 : return expression_tree_mutator(node, strip_noop_phvs_mutator,
677 : context);
678 : }
|