/src/postgres/src/backend/optimizer/util/paramassign.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * paramassign.c |
4 | | * Functions for assigning PARAM_EXEC slots during planning. |
5 | | * |
6 | | * This module is responsible for managing three planner data structures: |
7 | | * |
8 | | * root->glob->paramExecTypes: records actual assignments of PARAM_EXEC slots. |
9 | | * The i'th list element holds the data type OID of the i'th parameter slot. |
10 | | * (Elements can be InvalidOid if they represent slots that are needed for |
11 | | * chgParam signaling, but will never hold a value at runtime.) This list is |
12 | | * global to the whole plan since the executor has only one PARAM_EXEC array. |
13 | | * Assignments are permanent for the plan: we never remove entries once added. |
14 | | * |
15 | | * root->plan_params: a list of PlannerParamItem nodes, recording Vars and |
16 | | * PlaceHolderVars that the root's query level needs to supply to lower-level |
17 | | * subqueries, along with the PARAM_EXEC number to use for each such value. |
18 | | * Elements are added to this list while planning a subquery, and the list |
19 | | * is reset to empty after completion of each subquery. |
20 | | * |
21 | | * root->curOuterParams: a list of NestLoopParam nodes, recording Vars and |
22 | | * PlaceHolderVars that some outer level of nestloop needs to pass down to |
23 | | * a lower-level plan node in its righthand side. Elements are added to this |
24 | | * list as createplan.c creates lower Plan nodes that need such Params, and |
25 | | * are removed when it creates a NestLoop Plan node that will supply those |
26 | | * values. |
27 | | * |
28 | | * The latter two data structures are used to prevent creating multiple |
29 | | * PARAM_EXEC slots (each requiring work to fill) when the same upper |
30 | | * SubPlan or NestLoop supplies a value that is referenced in more than |
31 | | * one place in its child plan nodes. However, when the same Var has to |
32 | | * be supplied to different subplan trees by different SubPlan or NestLoop |
33 | | * parent nodes, we don't recognize any commonality; a fresh plan_params or |
34 | | * curOuterParams entry will be made (since the old one has been removed |
35 | | * when we finished processing the earlier SubPlan or NestLoop) and a fresh |
36 | | * PARAM_EXEC number will be assigned. At one time we tried to avoid |
37 | | * allocating duplicate PARAM_EXEC numbers in such cases, but it's harder |
38 | | * than it seems to avoid bugs due to overlapping Param lifetimes, so we |
39 | | * don't risk that anymore. Minimizing the number of PARAM_EXEC slots |
40 | | * doesn't really save much executor work anyway. |
41 | | * |
42 | | * |
43 | | * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group |
44 | | * Portions Copyright (c) 1994, Regents of the University of California |
45 | | * |
46 | | * IDENTIFICATION |
47 | | * src/backend/optimizer/util/paramassign.c |
48 | | * |
49 | | *------------------------------------------------------------------------- |
50 | | */ |
51 | | #include "postgres.h" |
52 | | |
53 | | #include "nodes/nodeFuncs.h" |
54 | | #include "nodes/plannodes.h" |
55 | | #include "optimizer/paramassign.h" |
56 | | #include "optimizer/placeholder.h" |
57 | | #include "rewrite/rewriteManip.h" |
58 | | |
59 | | |
60 | | /* |
61 | | * Select a PARAM_EXEC number to identify the given Var as a parameter for |
62 | | * the current subquery. (It might already have one.) |
63 | | * Record the need for the Var in the proper upper-level root->plan_params. |
64 | | */ |
65 | | static int |
66 | | assign_param_for_var(PlannerInfo *root, Var *var) |
67 | 0 | { |
68 | 0 | ListCell *ppl; |
69 | 0 | PlannerParamItem *pitem; |
70 | 0 | Index levelsup; |
71 | | |
72 | | /* Find the query level the Var belongs to */ |
73 | 0 | for (levelsup = var->varlevelsup; levelsup > 0; levelsup--) |
74 | 0 | root = root->parent_root; |
75 | | |
76 | | /* If there's already a matching PlannerParamItem there, just use it */ |
77 | 0 | foreach(ppl, root->plan_params) |
78 | 0 | { |
79 | 0 | pitem = (PlannerParamItem *) lfirst(ppl); |
80 | 0 | if (IsA(pitem->item, Var)) |
81 | 0 | { |
82 | 0 | Var *pvar = (Var *) pitem->item; |
83 | | |
84 | | /* |
85 | | * This comparison must match _equalVar(), except for ignoring |
86 | | * varlevelsup. Note that _equalVar() ignores varnosyn, |
87 | | * varattnosyn, and location, so this does too. |
88 | | */ |
89 | 0 | if (pvar->varno == var->varno && |
90 | 0 | pvar->varattno == var->varattno && |
91 | 0 | pvar->vartype == var->vartype && |
92 | 0 | pvar->vartypmod == var->vartypmod && |
93 | 0 | pvar->varcollid == var->varcollid && |
94 | 0 | pvar->varreturningtype == var->varreturningtype && |
95 | 0 | bms_equal(pvar->varnullingrels, var->varnullingrels)) |
96 | 0 | return pitem->paramId; |
97 | 0 | } |
98 | 0 | } |
99 | | |
100 | | /* Nope, so make a new one */ |
101 | 0 | var = copyObject(var); |
102 | 0 | var->varlevelsup = 0; |
103 | |
|
104 | 0 | pitem = makeNode(PlannerParamItem); |
105 | 0 | pitem->item = (Node *) var; |
106 | 0 | pitem->paramId = list_length(root->glob->paramExecTypes); |
107 | 0 | root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, |
108 | 0 | var->vartype); |
109 | |
|
110 | 0 | root->plan_params = lappend(root->plan_params, pitem); |
111 | |
|
112 | 0 | return pitem->paramId; |
113 | 0 | } |
114 | | |
115 | | /* |
116 | | * Generate a Param node to replace the given Var, |
117 | | * which is expected to have varlevelsup > 0 (ie, it is not local). |
118 | | * Record the need for the Var in the proper upper-level root->plan_params. |
119 | | */ |
120 | | Param * |
121 | | replace_outer_var(PlannerInfo *root, Var *var) |
122 | 0 | { |
123 | 0 | Param *retval; |
124 | 0 | int i; |
125 | |
|
126 | 0 | Assert(var->varlevelsup > 0 && var->varlevelsup < root->query_level); |
127 | | |
128 | | /* Find the Var in the appropriate plan_params, or add it if not present */ |
129 | 0 | i = assign_param_for_var(root, var); |
130 | |
|
131 | 0 | retval = makeNode(Param); |
132 | 0 | retval->paramkind = PARAM_EXEC; |
133 | 0 | retval->paramid = i; |
134 | 0 | retval->paramtype = var->vartype; |
135 | 0 | retval->paramtypmod = var->vartypmod; |
136 | 0 | retval->paramcollid = var->varcollid; |
137 | 0 | retval->location = var->location; |
138 | |
|
139 | 0 | return retval; |
140 | 0 | } |
141 | | |
142 | | /* |
143 | | * Select a PARAM_EXEC number to identify the given PlaceHolderVar as a |
144 | | * parameter for the current subquery. (It might already have one.) |
145 | | * Record the need for the PHV in the proper upper-level root->plan_params. |
146 | | * |
147 | | * This is just like assign_param_for_var, except for PlaceHolderVars. |
148 | | */ |
149 | | static int |
150 | | assign_param_for_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv) |
151 | 0 | { |
152 | 0 | ListCell *ppl; |
153 | 0 | PlannerParamItem *pitem; |
154 | 0 | Index levelsup; |
155 | | |
156 | | /* Find the query level the PHV belongs to */ |
157 | 0 | for (levelsup = phv->phlevelsup; levelsup > 0; levelsup--) |
158 | 0 | root = root->parent_root; |
159 | | |
160 | | /* If there's already a matching PlannerParamItem there, just use it */ |
161 | 0 | foreach(ppl, root->plan_params) |
162 | 0 | { |
163 | 0 | pitem = (PlannerParamItem *) lfirst(ppl); |
164 | 0 | if (IsA(pitem->item, PlaceHolderVar)) |
165 | 0 | { |
166 | 0 | PlaceHolderVar *pphv = (PlaceHolderVar *) pitem->item; |
167 | | |
168 | | /* We assume comparing the PHIDs is sufficient */ |
169 | 0 | if (pphv->phid == phv->phid) |
170 | 0 | return pitem->paramId; |
171 | 0 | } |
172 | 0 | } |
173 | | |
174 | | /* Nope, so make a new one */ |
175 | 0 | phv = copyObject(phv); |
176 | 0 | IncrementVarSublevelsUp((Node *) phv, -((int) phv->phlevelsup), 0); |
177 | 0 | Assert(phv->phlevelsup == 0); |
178 | |
|
179 | 0 | pitem = makeNode(PlannerParamItem); |
180 | 0 | pitem->item = (Node *) phv; |
181 | 0 | pitem->paramId = list_length(root->glob->paramExecTypes); |
182 | 0 | root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, |
183 | 0 | exprType((Node *) phv->phexpr)); |
184 | |
|
185 | 0 | root->plan_params = lappend(root->plan_params, pitem); |
186 | |
|
187 | 0 | return pitem->paramId; |
188 | 0 | } |
189 | | |
190 | | /* |
191 | | * Generate a Param node to replace the given PlaceHolderVar, |
192 | | * which is expected to have phlevelsup > 0 (ie, it is not local). |
193 | | * Record the need for the PHV in the proper upper-level root->plan_params. |
194 | | * |
195 | | * This is just like replace_outer_var, except for PlaceHolderVars. |
196 | | */ |
197 | | Param * |
198 | | replace_outer_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv) |
199 | 0 | { |
200 | 0 | Param *retval; |
201 | 0 | int i; |
202 | |
|
203 | 0 | Assert(phv->phlevelsup > 0 && phv->phlevelsup < root->query_level); |
204 | | |
205 | | /* Find the PHV in the appropriate plan_params, or add it if not present */ |
206 | 0 | i = assign_param_for_placeholdervar(root, phv); |
207 | |
|
208 | 0 | retval = makeNode(Param); |
209 | 0 | retval->paramkind = PARAM_EXEC; |
210 | 0 | retval->paramid = i; |
211 | 0 | retval->paramtype = exprType((Node *) phv->phexpr); |
212 | 0 | retval->paramtypmod = exprTypmod((Node *) phv->phexpr); |
213 | 0 | retval->paramcollid = exprCollation((Node *) phv->phexpr); |
214 | 0 | retval->location = -1; |
215 | |
|
216 | 0 | return retval; |
217 | 0 | } |
218 | | |
219 | | /* |
220 | | * Generate a Param node to replace the given Aggref |
221 | | * which is expected to have agglevelsup > 0 (ie, it is not local). |
222 | | * Record the need for the Aggref in the proper upper-level root->plan_params. |
223 | | */ |
224 | | Param * |
225 | | replace_outer_agg(PlannerInfo *root, Aggref *agg) |
226 | 0 | { |
227 | 0 | Param *retval; |
228 | 0 | PlannerParamItem *pitem; |
229 | 0 | Index levelsup; |
230 | |
|
231 | 0 | Assert(agg->agglevelsup > 0 && agg->agglevelsup < root->query_level); |
232 | | |
233 | | /* Find the query level the Aggref belongs to */ |
234 | 0 | for (levelsup = agg->agglevelsup; levelsup > 0; levelsup--) |
235 | 0 | root = root->parent_root; |
236 | | |
237 | | /* |
238 | | * It does not seem worthwhile to try to de-duplicate references to outer |
239 | | * aggs. Just make a new slot every time. |
240 | | */ |
241 | 0 | agg = copyObject(agg); |
242 | 0 | IncrementVarSublevelsUp((Node *) agg, -((int) agg->agglevelsup), 0); |
243 | 0 | Assert(agg->agglevelsup == 0); |
244 | |
|
245 | 0 | pitem = makeNode(PlannerParamItem); |
246 | 0 | pitem->item = (Node *) agg; |
247 | 0 | pitem->paramId = list_length(root->glob->paramExecTypes); |
248 | 0 | root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, |
249 | 0 | agg->aggtype); |
250 | |
|
251 | 0 | root->plan_params = lappend(root->plan_params, pitem); |
252 | |
|
253 | 0 | retval = makeNode(Param); |
254 | 0 | retval->paramkind = PARAM_EXEC; |
255 | 0 | retval->paramid = pitem->paramId; |
256 | 0 | retval->paramtype = agg->aggtype; |
257 | 0 | retval->paramtypmod = -1; |
258 | 0 | retval->paramcollid = agg->aggcollid; |
259 | 0 | retval->location = agg->location; |
260 | |
|
261 | 0 | return retval; |
262 | 0 | } |
263 | | |
264 | | /* |
265 | | * Generate a Param node to replace the given GroupingFunc expression which is |
266 | | * expected to have agglevelsup > 0 (ie, it is not local). |
267 | | * Record the need for the GroupingFunc in the proper upper-level |
268 | | * root->plan_params. |
269 | | */ |
270 | | Param * |
271 | | replace_outer_grouping(PlannerInfo *root, GroupingFunc *grp) |
272 | 0 | { |
273 | 0 | Param *retval; |
274 | 0 | PlannerParamItem *pitem; |
275 | 0 | Index levelsup; |
276 | 0 | Oid ptype = exprType((Node *) grp); |
277 | |
|
278 | 0 | Assert(grp->agglevelsup > 0 && grp->agglevelsup < root->query_level); |
279 | | |
280 | | /* Find the query level the GroupingFunc belongs to */ |
281 | 0 | for (levelsup = grp->agglevelsup; levelsup > 0; levelsup--) |
282 | 0 | root = root->parent_root; |
283 | | |
284 | | /* |
285 | | * It does not seem worthwhile to try to de-duplicate references to outer |
286 | | * aggs. Just make a new slot every time. |
287 | | */ |
288 | 0 | grp = copyObject(grp); |
289 | 0 | IncrementVarSublevelsUp((Node *) grp, -((int) grp->agglevelsup), 0); |
290 | 0 | Assert(grp->agglevelsup == 0); |
291 | |
|
292 | 0 | pitem = makeNode(PlannerParamItem); |
293 | 0 | pitem->item = (Node *) grp; |
294 | 0 | pitem->paramId = list_length(root->glob->paramExecTypes); |
295 | 0 | root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, |
296 | 0 | ptype); |
297 | |
|
298 | 0 | root->plan_params = lappend(root->plan_params, pitem); |
299 | |
|
300 | 0 | retval = makeNode(Param); |
301 | 0 | retval->paramkind = PARAM_EXEC; |
302 | 0 | retval->paramid = pitem->paramId; |
303 | 0 | retval->paramtype = ptype; |
304 | 0 | retval->paramtypmod = -1; |
305 | 0 | retval->paramcollid = InvalidOid; |
306 | 0 | retval->location = grp->location; |
307 | |
|
308 | 0 | return retval; |
309 | 0 | } |
310 | | |
311 | | /* |
312 | | * Generate a Param node to replace the given MergeSupportFunc expression |
313 | | * which is expected to be in the RETURNING list of an upper-level MERGE |
314 | | * query. Record the need for the MergeSupportFunc in the proper upper-level |
315 | | * root->plan_params. |
316 | | */ |
317 | | Param * |
318 | | replace_outer_merge_support(PlannerInfo *root, MergeSupportFunc *msf) |
319 | 0 | { |
320 | 0 | Param *retval; |
321 | 0 | PlannerParamItem *pitem; |
322 | 0 | Oid ptype = exprType((Node *) msf); |
323 | |
|
324 | 0 | Assert(root->parse->commandType != CMD_MERGE); |
325 | | |
326 | | /* |
327 | | * The parser should have ensured that the MergeSupportFunc is in the |
328 | | * RETURNING list of an upper-level MERGE query, so find that query. |
329 | | */ |
330 | 0 | do |
331 | 0 | { |
332 | 0 | root = root->parent_root; |
333 | 0 | if (root == NULL) |
334 | 0 | elog(ERROR, "MergeSupportFunc found outside MERGE"); |
335 | 0 | } while (root->parse->commandType != CMD_MERGE); |
336 | | |
337 | | /* |
338 | | * It does not seem worthwhile to try to de-duplicate references to outer |
339 | | * MergeSupportFunc expressions. Just make a new slot every time. |
340 | | */ |
341 | 0 | msf = copyObject(msf); |
342 | |
|
343 | 0 | pitem = makeNode(PlannerParamItem); |
344 | 0 | pitem->item = (Node *) msf; |
345 | 0 | pitem->paramId = list_length(root->glob->paramExecTypes); |
346 | 0 | root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, |
347 | 0 | ptype); |
348 | |
|
349 | 0 | root->plan_params = lappend(root->plan_params, pitem); |
350 | |
|
351 | 0 | retval = makeNode(Param); |
352 | 0 | retval->paramkind = PARAM_EXEC; |
353 | 0 | retval->paramid = pitem->paramId; |
354 | 0 | retval->paramtype = ptype; |
355 | 0 | retval->paramtypmod = -1; |
356 | 0 | retval->paramcollid = InvalidOid; |
357 | 0 | retval->location = msf->location; |
358 | |
|
359 | 0 | return retval; |
360 | 0 | } |
361 | | |
362 | | /* |
363 | | * Generate a Param node to replace the given ReturningExpr expression which |
364 | | * is expected to have retlevelsup > 0 (ie, it is not local). Record the need |
365 | | * for the ReturningExpr in the proper upper-level root->plan_params. |
366 | | */ |
367 | | Param * |
368 | | replace_outer_returning(PlannerInfo *root, ReturningExpr *rexpr) |
369 | 0 | { |
370 | 0 | Param *retval; |
371 | 0 | PlannerParamItem *pitem; |
372 | 0 | Index levelsup; |
373 | 0 | Oid ptype = exprType((Node *) rexpr->retexpr); |
374 | |
|
375 | 0 | Assert(rexpr->retlevelsup > 0 && rexpr->retlevelsup < root->query_level); |
376 | | |
377 | | /* Find the query level the ReturningExpr belongs to */ |
378 | 0 | for (levelsup = rexpr->retlevelsup; levelsup > 0; levelsup--) |
379 | 0 | root = root->parent_root; |
380 | | |
381 | | /* |
382 | | * It does not seem worthwhile to try to de-duplicate references to outer |
383 | | * ReturningExprs. Just make a new slot every time. |
384 | | */ |
385 | 0 | rexpr = copyObject(rexpr); |
386 | 0 | IncrementVarSublevelsUp((Node *) rexpr, -((int) rexpr->retlevelsup), 0); |
387 | 0 | Assert(rexpr->retlevelsup == 0); |
388 | |
|
389 | 0 | pitem = makeNode(PlannerParamItem); |
390 | 0 | pitem->item = (Node *) rexpr; |
391 | 0 | pitem->paramId = list_length(root->glob->paramExecTypes); |
392 | 0 | root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, |
393 | 0 | ptype); |
394 | |
|
395 | 0 | root->plan_params = lappend(root->plan_params, pitem); |
396 | |
|
397 | 0 | retval = makeNode(Param); |
398 | 0 | retval->paramkind = PARAM_EXEC; |
399 | 0 | retval->paramid = pitem->paramId; |
400 | 0 | retval->paramtype = ptype; |
401 | 0 | retval->paramtypmod = exprTypmod((Node *) rexpr->retexpr); |
402 | 0 | retval->paramcollid = exprCollation((Node *) rexpr->retexpr); |
403 | 0 | retval->location = exprLocation((Node *) rexpr->retexpr); |
404 | |
|
405 | 0 | return retval; |
406 | 0 | } |
407 | | |
408 | | /* |
409 | | * Generate a Param node to replace the given Var, |
410 | | * which is expected to come from some upper NestLoop plan node. |
411 | | * Record the need for the Var in root->curOuterParams. |
412 | | */ |
413 | | Param * |
414 | | replace_nestloop_param_var(PlannerInfo *root, Var *var) |
415 | 0 | { |
416 | 0 | Param *param; |
417 | 0 | NestLoopParam *nlp; |
418 | 0 | ListCell *lc; |
419 | | |
420 | | /* Is this Var already listed in root->curOuterParams? */ |
421 | 0 | foreach(lc, root->curOuterParams) |
422 | 0 | { |
423 | 0 | nlp = (NestLoopParam *) lfirst(lc); |
424 | 0 | if (equal(var, nlp->paramval)) |
425 | 0 | { |
426 | | /* Yes, so just make a Param referencing this NLP's slot */ |
427 | 0 | param = makeNode(Param); |
428 | 0 | param->paramkind = PARAM_EXEC; |
429 | 0 | param->paramid = nlp->paramno; |
430 | 0 | param->paramtype = var->vartype; |
431 | 0 | param->paramtypmod = var->vartypmod; |
432 | 0 | param->paramcollid = var->varcollid; |
433 | 0 | param->location = var->location; |
434 | 0 | return param; |
435 | 0 | } |
436 | 0 | } |
437 | | |
438 | | /* No, so assign a PARAM_EXEC slot for a new NLP */ |
439 | 0 | param = generate_new_exec_param(root, |
440 | 0 | var->vartype, |
441 | 0 | var->vartypmod, |
442 | 0 | var->varcollid); |
443 | 0 | param->location = var->location; |
444 | | |
445 | | /* Add it to the list of required NLPs */ |
446 | 0 | nlp = makeNode(NestLoopParam); |
447 | 0 | nlp->paramno = param->paramid; |
448 | 0 | nlp->paramval = copyObject(var); |
449 | 0 | root->curOuterParams = lappend(root->curOuterParams, nlp); |
450 | | |
451 | | /* And return the replacement Param */ |
452 | 0 | return param; |
453 | 0 | } |
454 | | |
455 | | /* |
456 | | * Generate a Param node to replace the given PlaceHolderVar, |
457 | | * which is expected to come from some upper NestLoop plan node. |
458 | | * Record the need for the PHV in root->curOuterParams. |
459 | | * |
460 | | * This is just like replace_nestloop_param_var, except for PlaceHolderVars. |
461 | | */ |
462 | | Param * |
463 | | replace_nestloop_param_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv) |
464 | 0 | { |
465 | 0 | Param *param; |
466 | 0 | NestLoopParam *nlp; |
467 | 0 | ListCell *lc; |
468 | | |
469 | | /* Is this PHV already listed in root->curOuterParams? */ |
470 | 0 | foreach(lc, root->curOuterParams) |
471 | 0 | { |
472 | 0 | nlp = (NestLoopParam *) lfirst(lc); |
473 | 0 | if (equal(phv, nlp->paramval)) |
474 | 0 | { |
475 | | /* Yes, so just make a Param referencing this NLP's slot */ |
476 | 0 | param = makeNode(Param); |
477 | 0 | param->paramkind = PARAM_EXEC; |
478 | 0 | param->paramid = nlp->paramno; |
479 | 0 | param->paramtype = exprType((Node *) phv->phexpr); |
480 | 0 | param->paramtypmod = exprTypmod((Node *) phv->phexpr); |
481 | 0 | param->paramcollid = exprCollation((Node *) phv->phexpr); |
482 | 0 | param->location = -1; |
483 | 0 | return param; |
484 | 0 | } |
485 | 0 | } |
486 | | |
487 | | /* No, so assign a PARAM_EXEC slot for a new NLP */ |
488 | 0 | param = generate_new_exec_param(root, |
489 | 0 | exprType((Node *) phv->phexpr), |
490 | 0 | exprTypmod((Node *) phv->phexpr), |
491 | 0 | exprCollation((Node *) phv->phexpr)); |
492 | | |
493 | | /* Add it to the list of required NLPs */ |
494 | 0 | nlp = makeNode(NestLoopParam); |
495 | 0 | nlp->paramno = param->paramid; |
496 | 0 | nlp->paramval = (Var *) copyObject(phv); |
497 | 0 | root->curOuterParams = lappend(root->curOuterParams, nlp); |
498 | | |
499 | | /* And return the replacement Param */ |
500 | 0 | return param; |
501 | 0 | } |
502 | | |
503 | | /* |
504 | | * process_subquery_nestloop_params |
505 | | * Handle params of a parameterized subquery that need to be fed |
506 | | * from an outer nestloop. |
507 | | * |
508 | | * Currently, that would be *all* params that a subquery in FROM has demanded |
509 | | * from the current query level, since they must be LATERAL references. |
510 | | * |
511 | | * subplan_params is a list of PlannerParamItems that we intend to pass to |
512 | | * a subquery-in-FROM. (This was constructed in root->plan_params while |
513 | | * planning the subquery, but isn't there anymore when this is called.) |
514 | | * |
515 | | * The subplan's references to the outer variables are already represented |
516 | | * as PARAM_EXEC Params, since that conversion was done by the routines above |
517 | | * while planning the subquery. So we need not modify the subplan or the |
518 | | * PlannerParamItems here. What we do need to do is add entries to |
519 | | * root->curOuterParams to signal the parent nestloop plan node that it must |
520 | | * provide these values. This differs from replace_nestloop_param_var in |
521 | | * that the PARAM_EXEC slots to use have already been determined. |
522 | | * |
523 | | * Note that we also use root->curOuterRels as an implicit parameter for |
524 | | * sanity checks. |
525 | | */ |
526 | | void |
527 | | process_subquery_nestloop_params(PlannerInfo *root, List *subplan_params) |
528 | 0 | { |
529 | 0 | ListCell *lc; |
530 | |
|
531 | 0 | foreach(lc, subplan_params) |
532 | 0 | { |
533 | 0 | PlannerParamItem *pitem = lfirst_node(PlannerParamItem, lc); |
534 | |
|
535 | 0 | if (IsA(pitem->item, Var)) |
536 | 0 | { |
537 | 0 | Var *var = (Var *) pitem->item; |
538 | 0 | NestLoopParam *nlp; |
539 | 0 | ListCell *lc2; |
540 | | |
541 | | /* If not from a nestloop outer rel, complain */ |
542 | 0 | if (!bms_is_member(var->varno, root->curOuterRels)) |
543 | 0 | elog(ERROR, "non-LATERAL parameter required by subquery"); |
544 | | |
545 | | /* Is this param already listed in root->curOuterParams? */ |
546 | 0 | foreach(lc2, root->curOuterParams) |
547 | 0 | { |
548 | 0 | nlp = (NestLoopParam *) lfirst(lc2); |
549 | 0 | if (nlp->paramno == pitem->paramId) |
550 | 0 | { |
551 | 0 | Assert(equal(var, nlp->paramval)); |
552 | | /* Present, so nothing to do */ |
553 | 0 | break; |
554 | 0 | } |
555 | 0 | } |
556 | 0 | if (lc2 == NULL) |
557 | 0 | { |
558 | | /* No, so add it */ |
559 | 0 | nlp = makeNode(NestLoopParam); |
560 | 0 | nlp->paramno = pitem->paramId; |
561 | 0 | nlp->paramval = copyObject(var); |
562 | 0 | root->curOuterParams = lappend(root->curOuterParams, nlp); |
563 | 0 | } |
564 | 0 | } |
565 | 0 | else if (IsA(pitem->item, PlaceHolderVar)) |
566 | 0 | { |
567 | 0 | PlaceHolderVar *phv = (PlaceHolderVar *) pitem->item; |
568 | 0 | NestLoopParam *nlp; |
569 | 0 | ListCell *lc2; |
570 | | |
571 | | /* If not from a nestloop outer rel, complain */ |
572 | 0 | if (!bms_is_subset(find_placeholder_info(root, phv)->ph_eval_at, |
573 | 0 | root->curOuterRels)) |
574 | 0 | elog(ERROR, "non-LATERAL parameter required by subquery"); |
575 | | |
576 | | /* Is this param already listed in root->curOuterParams? */ |
577 | 0 | foreach(lc2, root->curOuterParams) |
578 | 0 | { |
579 | 0 | nlp = (NestLoopParam *) lfirst(lc2); |
580 | 0 | if (nlp->paramno == pitem->paramId) |
581 | 0 | { |
582 | 0 | Assert(equal(phv, nlp->paramval)); |
583 | | /* Present, so nothing to do */ |
584 | 0 | break; |
585 | 0 | } |
586 | 0 | } |
587 | 0 | if (lc2 == NULL) |
588 | 0 | { |
589 | | /* No, so add it */ |
590 | 0 | nlp = makeNode(NestLoopParam); |
591 | 0 | nlp->paramno = pitem->paramId; |
592 | 0 | nlp->paramval = (Var *) copyObject(phv); |
593 | 0 | root->curOuterParams = lappend(root->curOuterParams, nlp); |
594 | 0 | } |
595 | 0 | } |
596 | 0 | else |
597 | 0 | elog(ERROR, "unexpected type of subquery parameter"); |
598 | 0 | } |
599 | 0 | } |
600 | | |
601 | | /* |
602 | | * Identify any NestLoopParams that should be supplied by a NestLoop |
603 | | * plan node with the specified lefthand rels and required-outer rels. |
604 | | * Remove them from the active root->curOuterParams list and return |
605 | | * them as the result list. |
606 | | * |
607 | | * Vars and PHVs appearing in the result list must have nullingrel sets |
608 | | * that could validly appear in the lefthand rel's output. Ordinarily that |
609 | | * would be true already, but if we have applied outer join identity 3, |
610 | | * there could be more or fewer nullingrel bits in the nodes appearing in |
611 | | * curOuterParams than are in the nominal leftrelids. We deal with that by |
612 | | * forcing their nullingrel sets to include exactly the outer-join relids |
613 | | * that appear in leftrelids and can null the respective Var or PHV. |
614 | | * This fix is a bit ad-hoc and intellectually unsatisfactory, because it's |
615 | | * essentially jumping to the conclusion that we've placed evaluation of |
616 | | * the nestloop parameters correctly, and thus it defeats the intent of the |
617 | | * subsequent nullingrel cross-checks in setrefs.c. But the alternative |
618 | | * seems to be to generate multiple versions of each laterally-parameterized |
619 | | * subquery, which'd be unduly expensive. |
620 | | */ |
621 | | List * |
622 | | identify_current_nestloop_params(PlannerInfo *root, |
623 | | Relids leftrelids, |
624 | | Relids outerrelids) |
625 | 0 | { |
626 | 0 | List *result; |
627 | 0 | Relids allleftrelids; |
628 | 0 | ListCell *cell; |
629 | | |
630 | | /* |
631 | | * We'll be able to evaluate a PHV in the lefthand path if it uses the |
632 | | * lefthand rels plus any available required-outer rels. But don't do so |
633 | | * if it uses *only* required-outer rels; in that case it should be |
634 | | * evaluated higher in the tree. For Vars, no such hair-splitting is |
635 | | * necessary since they depend on only one relid. |
636 | | */ |
637 | 0 | if (outerrelids) |
638 | 0 | allleftrelids = bms_union(leftrelids, outerrelids); |
639 | 0 | else |
640 | 0 | allleftrelids = leftrelids; |
641 | |
|
642 | 0 | result = NIL; |
643 | 0 | foreach(cell, root->curOuterParams) |
644 | 0 | { |
645 | 0 | NestLoopParam *nlp = (NestLoopParam *) lfirst(cell); |
646 | | |
647 | | /* |
648 | | * We are looking for Vars and PHVs that can be supplied by the |
649 | | * lefthand rels. When we find one, it's okay to modify it in-place |
650 | | * because all the routines above make a fresh copy to put into |
651 | | * curOuterParams. |
652 | | */ |
653 | 0 | if (IsA(nlp->paramval, Var) && |
654 | 0 | bms_is_member(nlp->paramval->varno, leftrelids)) |
655 | 0 | { |
656 | 0 | Var *var = (Var *) nlp->paramval; |
657 | 0 | RelOptInfo *rel = root->simple_rel_array[var->varno]; |
658 | |
|
659 | 0 | root->curOuterParams = foreach_delete_current(root->curOuterParams, |
660 | 0 | cell); |
661 | 0 | var->varnullingrels = bms_intersect(rel->nulling_relids, |
662 | 0 | leftrelids); |
663 | 0 | result = lappend(result, nlp); |
664 | 0 | } |
665 | 0 | else if (IsA(nlp->paramval, PlaceHolderVar)) |
666 | 0 | { |
667 | 0 | PlaceHolderVar *phv = (PlaceHolderVar *) nlp->paramval; |
668 | 0 | PlaceHolderInfo *phinfo = find_placeholder_info(root, phv); |
669 | 0 | Relids eval_at = phinfo->ph_eval_at; |
670 | |
|
671 | 0 | if (bms_is_subset(eval_at, allleftrelids) && |
672 | 0 | bms_overlap(eval_at, leftrelids)) |
673 | 0 | { |
674 | 0 | root->curOuterParams = foreach_delete_current(root->curOuterParams, |
675 | 0 | cell); |
676 | | |
677 | | /* |
678 | | * Deal with an edge case: if the PHV was pulled up out of a |
679 | | * subquery and it contains a subquery that was originally |
680 | | * pushed down from this query level, then that will still be |
681 | | * represented as a SubLink, because SS_process_sublinks won't |
682 | | * recurse into outer PHVs, so it didn't get transformed |
683 | | * during expression preprocessing in the subquery. We need a |
684 | | * version of the PHV that has a SubPlan, which we can get |
685 | | * from the current query level's placeholder_list. This is |
686 | | * quite grotty of course, but dealing with it earlier in the |
687 | | * handling of subplan params would be just as grotty, and it |
688 | | * might end up being a waste of cycles if we don't decide to |
689 | | * treat the PHV as a NestLoopParam. (Perhaps that whole |
690 | | * mechanism should be redesigned someday, but today is not |
691 | | * that day.) |
692 | | */ |
693 | 0 | if (root->parse->hasSubLinks) |
694 | 0 | { |
695 | 0 | phv = copyObject(phinfo->ph_var); |
696 | | |
697 | | /* |
698 | | * The ph_var will have empty nullingrels, but that |
699 | | * doesn't matter since we're about to overwrite |
700 | | * phv->phnullingrels. Other fields should be OK already. |
701 | | */ |
702 | 0 | nlp->paramval = (Var *) phv; |
703 | 0 | } |
704 | |
|
705 | 0 | phv->phnullingrels = |
706 | 0 | bms_intersect(get_placeholder_nulling_relids(root, phinfo), |
707 | 0 | leftrelids); |
708 | |
|
709 | 0 | result = lappend(result, nlp); |
710 | 0 | } |
711 | 0 | } |
712 | 0 | } |
713 | 0 | return result; |
714 | 0 | } |
715 | | |
716 | | /* |
717 | | * Generate a new Param node that will not conflict with any other. |
718 | | * |
719 | | * This is used to create Params representing subplan outputs or |
720 | | * NestLoop parameters. |
721 | | * |
722 | | * We don't need to build a PlannerParamItem for such a Param, but we do |
723 | | * need to make sure we record the type in paramExecTypes (otherwise, |
724 | | * there won't be a slot allocated for it). |
725 | | */ |
726 | | Param * |
727 | | generate_new_exec_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod, |
728 | | Oid paramcollation) |
729 | 0 | { |
730 | 0 | Param *retval; |
731 | |
|
732 | 0 | retval = makeNode(Param); |
733 | 0 | retval->paramkind = PARAM_EXEC; |
734 | 0 | retval->paramid = list_length(root->glob->paramExecTypes); |
735 | 0 | root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, |
736 | 0 | paramtype); |
737 | 0 | retval->paramtype = paramtype; |
738 | 0 | retval->paramtypmod = paramtypmod; |
739 | 0 | retval->paramcollid = paramcollation; |
740 | 0 | retval->location = -1; |
741 | |
|
742 | 0 | return retval; |
743 | 0 | } |
744 | | |
745 | | /* |
746 | | * Assign a (nonnegative) PARAM_EXEC ID for a special parameter (one that |
747 | | * is not actually used to carry a value at runtime). Such parameters are |
748 | | * used for special runtime signaling purposes, such as connecting a |
749 | | * recursive union node to its worktable scan node or forcing plan |
750 | | * re-evaluation within the EvalPlanQual mechanism. No actual Param node |
751 | | * exists with this ID, however. |
752 | | */ |
753 | | int |
754 | | assign_special_exec_param(PlannerInfo *root) |
755 | 0 | { |
756 | 0 | int paramId = list_length(root->glob->paramExecTypes); |
757 | |
|
758 | 0 | root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, |
759 | 0 | InvalidOid); |
760 | 0 | return paramId; |
761 | 0 | } |