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-2019, 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 | { |
68 | ListCell *ppl; |
69 | PlannerParamItem *pitem; |
70 | Index levelsup; |
71 | |
72 | /* Find the query level the Var belongs to */ |
73 | for (levelsup = var->varlevelsup; levelsup > 0; levelsup--) |
74 | root = root->parent_root; |
75 | |
76 | /* If there's already a matching PlannerParamItem there, just use it */ |
77 | foreach(ppl, root->plan_params) |
78 | { |
79 | pitem = (PlannerParamItem *) lfirst(ppl); |
80 | if (IsA(pitem->item, Var)) |
81 | { |
82 | Var *pvar = (Var *) pitem->item; |
83 | |
84 | /* |
85 | * This comparison must match _equalVar(), except for ignoring |
86 | * varlevelsup. Note that _equalVar() ignores the location. |
87 | */ |
88 | if (pvar->varno == var->varno && |
89 | pvar->varattno == var->varattno && |
90 | pvar->vartype == var->vartype && |
91 | pvar->vartypmod == var->vartypmod && |
92 | pvar->varcollid == var->varcollid && |
93 | pvar->varnoold == var->varnoold && |
94 | pvar->varoattno == var->varoattno) |
95 | return pitem->paramId; |
96 | } |
97 | } |
98 | |
99 | /* Nope, so make a new one */ |
100 | var = copyObject(var); |
101 | var->varlevelsup = 0; |
102 | |
103 | pitem = makeNode(PlannerParamItem); |
104 | pitem->item = (Node *) var; |
105 | pitem->paramId = list_length(root->glob->paramExecTypes); |
106 | root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, |
107 | var->vartype); |
108 | |
109 | root->plan_params = lappend(root->plan_params, pitem); |
110 | |
111 | return pitem->paramId; |
112 | } |
113 | |
114 | /* |
115 | * Generate a Param node to replace the given Var, |
116 | * which is expected to have varlevelsup > 0 (ie, it is not local). |
117 | * Record the need for the Var in the proper upper-level root->plan_params. |
118 | */ |
119 | Param * |
120 | replace_outer_var(PlannerInfo *root, Var *var) |
121 | { |
122 | Param *retval; |
123 | int i; |
124 | |
125 | Assert(var->varlevelsup > 0 && var->varlevelsup < root->query_level); |
126 | |
127 | /* Find the Var in the appropriate plan_params, or add it if not present */ |
128 | i = assign_param_for_var(root, var); |
129 | |
130 | retval = makeNode(Param); |
131 | retval->paramkind = PARAM_EXEC; |
132 | retval->paramid = i; |
133 | retval->paramtype = var->vartype; |
134 | retval->paramtypmod = var->vartypmod; |
135 | retval->paramcollid = var->varcollid; |
136 | retval->location = var->location; |
137 | |
138 | return retval; |
139 | } |
140 | |
141 | /* |
142 | * Select a PARAM_EXEC number to identify the given PlaceHolderVar as a |
143 | * parameter for the current subquery. (It might already have one.) |
144 | * Record the need for the PHV in the proper upper-level root->plan_params. |
145 | * |
146 | * This is just like assign_param_for_var, except for PlaceHolderVars. |
147 | */ |
148 | static int |
149 | assign_param_for_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv) |
150 | { |
151 | ListCell *ppl; |
152 | PlannerParamItem *pitem; |
153 | Index levelsup; |
154 | |
155 | /* Find the query level the PHV belongs to */ |
156 | for (levelsup = phv->phlevelsup; levelsup > 0; levelsup--) |
157 | root = root->parent_root; |
158 | |
159 | /* If there's already a matching PlannerParamItem there, just use it */ |
160 | foreach(ppl, root->plan_params) |
161 | { |
162 | pitem = (PlannerParamItem *) lfirst(ppl); |
163 | if (IsA(pitem->item, PlaceHolderVar)) |
164 | { |
165 | PlaceHolderVar *pphv = (PlaceHolderVar *) pitem->item; |
166 | |
167 | /* We assume comparing the PHIDs is sufficient */ |
168 | if (pphv->phid == phv->phid) |
169 | return pitem->paramId; |
170 | } |
171 | } |
172 | |
173 | /* Nope, so make a new one */ |
174 | phv = copyObject(phv); |
175 | IncrementVarSublevelsUp((Node *) phv, -((int) phv->phlevelsup), 0); |
176 | Assert(phv->phlevelsup == 0); |
177 | |
178 | pitem = makeNode(PlannerParamItem); |
179 | pitem->item = (Node *) phv; |
180 | pitem->paramId = list_length(root->glob->paramExecTypes); |
181 | root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, |
182 | exprType((Node *) phv->phexpr)); |
183 | |
184 | root->plan_params = lappend(root->plan_params, pitem); |
185 | |
186 | return pitem->paramId; |
187 | } |
188 | |
189 | /* |
190 | * Generate a Param node to replace the given PlaceHolderVar, |
191 | * which is expected to have phlevelsup > 0 (ie, it is not local). |
192 | * Record the need for the PHV in the proper upper-level root->plan_params. |
193 | * |
194 | * This is just like replace_outer_var, except for PlaceHolderVars. |
195 | */ |
196 | Param * |
197 | replace_outer_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv) |
198 | { |
199 | Param *retval; |
200 | int i; |
201 | |
202 | Assert(phv->phlevelsup > 0 && phv->phlevelsup < root->query_level); |
203 | |
204 | /* Find the PHV in the appropriate plan_params, or add it if not present */ |
205 | i = assign_param_for_placeholdervar(root, phv); |
206 | |
207 | retval = makeNode(Param); |
208 | retval->paramkind = PARAM_EXEC; |
209 | retval->paramid = i; |
210 | retval->paramtype = exprType((Node *) phv->phexpr); |
211 | retval->paramtypmod = exprTypmod((Node *) phv->phexpr); |
212 | retval->paramcollid = exprCollation((Node *) phv->phexpr); |
213 | retval->location = -1; |
214 | |
215 | return retval; |
216 | } |
217 | |
218 | /* |
219 | * Generate a Param node to replace the given Aggref |
220 | * which is expected to have agglevelsup > 0 (ie, it is not local). |
221 | * Record the need for the Aggref in the proper upper-level root->plan_params. |
222 | */ |
223 | Param * |
224 | replace_outer_agg(PlannerInfo *root, Aggref *agg) |
225 | { |
226 | Param *retval; |
227 | PlannerParamItem *pitem; |
228 | Index levelsup; |
229 | |
230 | Assert(agg->agglevelsup > 0 && agg->agglevelsup < root->query_level); |
231 | |
232 | /* Find the query level the Aggref belongs to */ |
233 | for (levelsup = agg->agglevelsup; levelsup > 0; levelsup--) |
234 | root = root->parent_root; |
235 | |
236 | /* |
237 | * It does not seem worthwhile to try to de-duplicate references to outer |
238 | * aggs. Just make a new slot every time. |
239 | */ |
240 | agg = copyObject(agg); |
241 | IncrementVarSublevelsUp((Node *) agg, -((int) agg->agglevelsup), 0); |
242 | Assert(agg->agglevelsup == 0); |
243 | |
244 | pitem = makeNode(PlannerParamItem); |
245 | pitem->item = (Node *) agg; |
246 | pitem->paramId = list_length(root->glob->paramExecTypes); |
247 | root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, |
248 | agg->aggtype); |
249 | |
250 | root->plan_params = lappend(root->plan_params, pitem); |
251 | |
252 | retval = makeNode(Param); |
253 | retval->paramkind = PARAM_EXEC; |
254 | retval->paramid = pitem->paramId; |
255 | retval->paramtype = agg->aggtype; |
256 | retval->paramtypmod = -1; |
257 | retval->paramcollid = agg->aggcollid; |
258 | retval->location = agg->location; |
259 | |
260 | return retval; |
261 | } |
262 | |
263 | /* |
264 | * Generate a Param node to replace the given GroupingFunc expression which is |
265 | * expected to have agglevelsup > 0 (ie, it is not local). |
266 | * Record the need for the GroupingFunc in the proper upper-level |
267 | * root->plan_params. |
268 | */ |
269 | Param * |
270 | replace_outer_grouping(PlannerInfo *root, GroupingFunc *grp) |
271 | { |
272 | Param *retval; |
273 | PlannerParamItem *pitem; |
274 | Index levelsup; |
275 | Oid ptype = exprType((Node *) grp); |
276 | |
277 | Assert(grp->agglevelsup > 0 && grp->agglevelsup < root->query_level); |
278 | |
279 | /* Find the query level the GroupingFunc belongs to */ |
280 | for (levelsup = grp->agglevelsup; levelsup > 0; levelsup--) |
281 | root = root->parent_root; |
282 | |
283 | /* |
284 | * It does not seem worthwhile to try to de-duplicate references to outer |
285 | * aggs. Just make a new slot every time. |
286 | */ |
287 | grp = copyObject(grp); |
288 | IncrementVarSublevelsUp((Node *) grp, -((int) grp->agglevelsup), 0); |
289 | Assert(grp->agglevelsup == 0); |
290 | |
291 | pitem = makeNode(PlannerParamItem); |
292 | pitem->item = (Node *) grp; |
293 | pitem->paramId = list_length(root->glob->paramExecTypes); |
294 | root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, |
295 | ptype); |
296 | |
297 | root->plan_params = lappend(root->plan_params, pitem); |
298 | |
299 | retval = makeNode(Param); |
300 | retval->paramkind = PARAM_EXEC; |
301 | retval->paramid = pitem->paramId; |
302 | retval->paramtype = ptype; |
303 | retval->paramtypmod = -1; |
304 | retval->paramcollid = InvalidOid; |
305 | retval->location = grp->location; |
306 | |
307 | return retval; |
308 | } |
309 | |
310 | /* |
311 | * Generate a Param node to replace the given Var, |
312 | * which is expected to come from some upper NestLoop plan node. |
313 | * Record the need for the Var in root->curOuterParams. |
314 | */ |
315 | Param * |
316 | replace_nestloop_param_var(PlannerInfo *root, Var *var) |
317 | { |
318 | Param *param; |
319 | NestLoopParam *nlp; |
320 | ListCell *lc; |
321 | |
322 | /* Is this Var already listed in root->curOuterParams? */ |
323 | foreach(lc, root->curOuterParams) |
324 | { |
325 | nlp = (NestLoopParam *) lfirst(lc); |
326 | if (equal(var, nlp->paramval)) |
327 | { |
328 | /* Yes, so just make a Param referencing this NLP's slot */ |
329 | param = makeNode(Param); |
330 | param->paramkind = PARAM_EXEC; |
331 | param->paramid = nlp->paramno; |
332 | param->paramtype = var->vartype; |
333 | param->paramtypmod = var->vartypmod; |
334 | param->paramcollid = var->varcollid; |
335 | param->location = var->location; |
336 | return param; |
337 | } |
338 | } |
339 | |
340 | /* No, so assign a PARAM_EXEC slot for a new NLP */ |
341 | param = generate_new_exec_param(root, |
342 | var->vartype, |
343 | var->vartypmod, |
344 | var->varcollid); |
345 | param->location = var->location; |
346 | |
347 | /* Add it to the list of required NLPs */ |
348 | nlp = makeNode(NestLoopParam); |
349 | nlp->paramno = param->paramid; |
350 | nlp->paramval = copyObject(var); |
351 | root->curOuterParams = lappend(root->curOuterParams, nlp); |
352 | |
353 | /* And return the replacement Param */ |
354 | return param; |
355 | } |
356 | |
357 | /* |
358 | * Generate a Param node to replace the given PlaceHolderVar, |
359 | * which is expected to come from some upper NestLoop plan node. |
360 | * Record the need for the PHV in root->curOuterParams. |
361 | * |
362 | * This is just like replace_nestloop_param_var, except for PlaceHolderVars. |
363 | */ |
364 | Param * |
365 | replace_nestloop_param_placeholdervar(PlannerInfo *root, PlaceHolderVar *phv) |
366 | { |
367 | Param *param; |
368 | NestLoopParam *nlp; |
369 | ListCell *lc; |
370 | |
371 | /* Is this PHV already listed in root->curOuterParams? */ |
372 | foreach(lc, root->curOuterParams) |
373 | { |
374 | nlp = (NestLoopParam *) lfirst(lc); |
375 | if (equal(phv, nlp->paramval)) |
376 | { |
377 | /* Yes, so just make a Param referencing this NLP's slot */ |
378 | param = makeNode(Param); |
379 | param->paramkind = PARAM_EXEC; |
380 | param->paramid = nlp->paramno; |
381 | param->paramtype = exprType((Node *) phv->phexpr); |
382 | param->paramtypmod = exprTypmod((Node *) phv->phexpr); |
383 | param->paramcollid = exprCollation((Node *) phv->phexpr); |
384 | param->location = -1; |
385 | return param; |
386 | } |
387 | } |
388 | |
389 | /* No, so assign a PARAM_EXEC slot for a new NLP */ |
390 | param = generate_new_exec_param(root, |
391 | exprType((Node *) phv->phexpr), |
392 | exprTypmod((Node *) phv->phexpr), |
393 | exprCollation((Node *) phv->phexpr)); |
394 | |
395 | /* Add it to the list of required NLPs */ |
396 | nlp = makeNode(NestLoopParam); |
397 | nlp->paramno = param->paramid; |
398 | nlp->paramval = (Var *) copyObject(phv); |
399 | root->curOuterParams = lappend(root->curOuterParams, nlp); |
400 | |
401 | /* And return the replacement Param */ |
402 | return param; |
403 | } |
404 | |
405 | /* |
406 | * process_subquery_nestloop_params |
407 | * Handle params of a parameterized subquery that need to be fed |
408 | * from an outer nestloop. |
409 | * |
410 | * Currently, that would be *all* params that a subquery in FROM has demanded |
411 | * from the current query level, since they must be LATERAL references. |
412 | * |
413 | * subplan_params is a list of PlannerParamItems that we intend to pass to |
414 | * a subquery-in-FROM. (This was constructed in root->plan_params while |
415 | * planning the subquery, but isn't there anymore when this is called.) |
416 | * |
417 | * The subplan's references to the outer variables are already represented |
418 | * as PARAM_EXEC Params, since that conversion was done by the routines above |
419 | * while planning the subquery. So we need not modify the subplan or the |
420 | * PlannerParamItems here. What we do need to do is add entries to |
421 | * root->curOuterParams to signal the parent nestloop plan node that it must |
422 | * provide these values. This differs from replace_nestloop_param_var in |
423 | * that the PARAM_EXEC slots to use have already been determined. |
424 | * |
425 | * Note that we also use root->curOuterRels as an implicit parameter for |
426 | * sanity checks. |
427 | */ |
428 | void |
429 | process_subquery_nestloop_params(PlannerInfo *root, List *subplan_params) |
430 | { |
431 | ListCell *lc; |
432 | |
433 | foreach(lc, subplan_params) |
434 | { |
435 | PlannerParamItem *pitem = castNode(PlannerParamItem, lfirst(lc)); |
436 | |
437 | if (IsA(pitem->item, Var)) |
438 | { |
439 | Var *var = (Var *) pitem->item; |
440 | NestLoopParam *nlp; |
441 | ListCell *lc; |
442 | |
443 | /* If not from a nestloop outer rel, complain */ |
444 | if (!bms_is_member(var->varno, root->curOuterRels)) |
445 | elog(ERROR, "non-LATERAL parameter required by subquery" ); |
446 | |
447 | /* Is this param already listed in root->curOuterParams? */ |
448 | foreach(lc, root->curOuterParams) |
449 | { |
450 | nlp = (NestLoopParam *) lfirst(lc); |
451 | if (nlp->paramno == pitem->paramId) |
452 | { |
453 | Assert(equal(var, nlp->paramval)); |
454 | /* Present, so nothing to do */ |
455 | break; |
456 | } |
457 | } |
458 | if (lc == NULL) |
459 | { |
460 | /* No, so add it */ |
461 | nlp = makeNode(NestLoopParam); |
462 | nlp->paramno = pitem->paramId; |
463 | nlp->paramval = copyObject(var); |
464 | root->curOuterParams = lappend(root->curOuterParams, nlp); |
465 | } |
466 | } |
467 | else if (IsA(pitem->item, PlaceHolderVar)) |
468 | { |
469 | PlaceHolderVar *phv = (PlaceHolderVar *) pitem->item; |
470 | NestLoopParam *nlp; |
471 | ListCell *lc; |
472 | |
473 | /* If not from a nestloop outer rel, complain */ |
474 | if (!bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at, |
475 | root->curOuterRels)) |
476 | elog(ERROR, "non-LATERAL parameter required by subquery" ); |
477 | |
478 | /* Is this param already listed in root->curOuterParams? */ |
479 | foreach(lc, root->curOuterParams) |
480 | { |
481 | nlp = (NestLoopParam *) lfirst(lc); |
482 | if (nlp->paramno == pitem->paramId) |
483 | { |
484 | Assert(equal(phv, nlp->paramval)); |
485 | /* Present, so nothing to do */ |
486 | break; |
487 | } |
488 | } |
489 | if (lc == NULL) |
490 | { |
491 | /* No, so add it */ |
492 | nlp = makeNode(NestLoopParam); |
493 | nlp->paramno = pitem->paramId; |
494 | nlp->paramval = (Var *) copyObject(phv); |
495 | root->curOuterParams = lappend(root->curOuterParams, nlp); |
496 | } |
497 | } |
498 | else |
499 | elog(ERROR, "unexpected type of subquery parameter" ); |
500 | } |
501 | } |
502 | |
503 | /* |
504 | * Identify any NestLoopParams that should be supplied by a NestLoop plan |
505 | * node with the specified lefthand rels. Remove them from the active |
506 | * root->curOuterParams list and return them as the result list. |
507 | */ |
508 | List * |
509 | identify_current_nestloop_params(PlannerInfo *root, Relids leftrelids) |
510 | { |
511 | List *result; |
512 | ListCell *cell; |
513 | ListCell *prev; |
514 | ListCell *next; |
515 | |
516 | result = NIL; |
517 | prev = NULL; |
518 | for (cell = list_head(root->curOuterParams); cell; cell = next) |
519 | { |
520 | NestLoopParam *nlp = (NestLoopParam *) lfirst(cell); |
521 | |
522 | next = lnext(cell); |
523 | |
524 | /* |
525 | * We are looking for Vars and PHVs that can be supplied by the |
526 | * lefthand rels. The "bms_overlap" test is just an optimization to |
527 | * allow skipping find_placeholder_info() if the PHV couldn't match. |
528 | */ |
529 | if (IsA(nlp->paramval, Var) && |
530 | bms_is_member(nlp->paramval->varno, leftrelids)) |
531 | { |
532 | root->curOuterParams = list_delete_cell(root->curOuterParams, |
533 | cell, prev); |
534 | result = lappend(result, nlp); |
535 | } |
536 | else if (IsA(nlp->paramval, PlaceHolderVar) && |
537 | bms_overlap(((PlaceHolderVar *) nlp->paramval)->phrels, |
538 | leftrelids) && |
539 | bms_is_subset(find_placeholder_info(root, |
540 | (PlaceHolderVar *) nlp->paramval, |
541 | false)->ph_eval_at, |
542 | leftrelids)) |
543 | { |
544 | root->curOuterParams = list_delete_cell(root->curOuterParams, |
545 | cell, prev); |
546 | result = lappend(result, nlp); |
547 | } |
548 | else |
549 | prev = cell; |
550 | } |
551 | return result; |
552 | } |
553 | |
554 | /* |
555 | * Generate a new Param node that will not conflict with any other. |
556 | * |
557 | * This is used to create Params representing subplan outputs or |
558 | * NestLoop parameters. |
559 | * |
560 | * We don't need to build a PlannerParamItem for such a Param, but we do |
561 | * need to make sure we record the type in paramExecTypes (otherwise, |
562 | * there won't be a slot allocated for it). |
563 | */ |
564 | Param * |
565 | generate_new_exec_param(PlannerInfo *root, Oid paramtype, int32 paramtypmod, |
566 | Oid paramcollation) |
567 | { |
568 | Param *retval; |
569 | |
570 | retval = makeNode(Param); |
571 | retval->paramkind = PARAM_EXEC; |
572 | retval->paramid = list_length(root->glob->paramExecTypes); |
573 | root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, |
574 | paramtype); |
575 | retval->paramtype = paramtype; |
576 | retval->paramtypmod = paramtypmod; |
577 | retval->paramcollid = paramcollation; |
578 | retval->location = -1; |
579 | |
580 | return retval; |
581 | } |
582 | |
583 | /* |
584 | * Assign a (nonnegative) PARAM_EXEC ID for a special parameter (one that |
585 | * is not actually used to carry a value at runtime). Such parameters are |
586 | * used for special runtime signaling purposes, such as connecting a |
587 | * recursive union node to its worktable scan node or forcing plan |
588 | * re-evaluation within the EvalPlanQual mechanism. No actual Param node |
589 | * exists with this ID, however. |
590 | */ |
591 | int |
592 | assign_special_exec_param(PlannerInfo *root) |
593 | { |
594 | int paramId = list_length(root->glob->paramExecTypes); |
595 | |
596 | root->glob->paramExecTypes = lappend_oid(root->glob->paramExecTypes, |
597 | InvalidOid); |
598 | return paramId; |
599 | } |
600 | |