]> granicus.if.org Git - postgresql/blob - src/backend/optimizer/plan/subselect.c
First stage of reclaiming memory in executor by resetting short-term
[postgresql] / src / backend / optimizer / plan / subselect.c
1 /*-------------------------------------------------------------------------
2  *
3  * subselect.c
4  *        Planning routines for subselects and parameters.
5  *
6  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  * IDENTIFICATION
10  *        $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.39 2000/07/12 02:37:09 tgl Exp $
11  *
12  *-------------------------------------------------------------------------
13  */
14 #include "postgres.h"
15
16 #include "catalog/pg_operator.h"
17 #include "catalog/pg_type.h"
18 #include "nodes/makefuncs.h"
19 #include "optimizer/clauses.h"
20 #include "optimizer/cost.h"
21 #include "optimizer/planmain.h"
22 #include "optimizer/planner.h"
23 #include "optimizer/subselect.h"
24 #include "parser/parse_expr.h"
25 #include "parser/parse_oper.h"
26 #include "utils/lsyscache.h"
27
28
29 Index           PlannerQueryLevel;      /* level of current query */
30 List       *PlannerInitPlan;    /* init subplans for current query */
31 List       *PlannerParamVar;    /* to get Var from Param->paramid */
32 int                     PlannerPlanId;          /* to assign unique ID to subquery plans */
33
34 /*--------------------
35  * PlannerParamVar is a list of Var nodes, wherein the n'th entry
36  * (n counts from 0) corresponds to Param->paramid = n.  The Var nodes
37  * are ordinary except for one thing: their varlevelsup field does NOT
38  * have the usual interpretation of "subplan levels out from current".
39  * Instead, it contains the absolute plan level, with the outermost
40  * plan being level 1 and nested plans having higher level numbers.
41  * This nonstandardness is useful because we don't have to run around
42  * and update the list elements when we enter or exit a subplan
43  * recursion level.  But we must pay attention not to confuse this
44  * meaning with the normal meaning of varlevelsup.
45  *--------------------
46  */
47
48
49 /*
50  * Create a new entry in the PlannerParamVar list, and return its index.
51  *
52  * var contains the data to be copied, except for varlevelsup which
53  * is set from the absolute level value given by varlevel.
54  */
55 static int
56 new_param(Var *var, Index varlevel)
57 {
58         Var                *paramVar = (Var *) copyObject(var);
59
60         paramVar->varlevelsup = varlevel;
61
62         PlannerParamVar = lappend(PlannerParamVar, paramVar);
63
64         return length(PlannerParamVar) - 1;
65 }
66
67 /*
68  * Generate a Param node to replace the given Var,
69  * which is expected to have varlevelsup > 0 (ie, it is not local).
70  */
71 static Param *
72 replace_var(Var *var)
73 {
74         List       *ppv;
75         Param      *retval;
76         Index           varlevel;
77         int                     i;
78
79         Assert(var->varlevelsup > 0 && var->varlevelsup < PlannerQueryLevel);
80         varlevel = PlannerQueryLevel - var->varlevelsup;
81
82         /*
83          * If there's already a PlannerParamVar entry for this same Var, just
84          * use it.      NOTE: in situations involving UNION or inheritance, it is
85          * possible for the same varno/varlevel to refer to different RTEs in
86          * different parts of the parsetree, so that different fields might
87          * end up sharing the same Param number.  As long as we check the
88          * vartype as well, I believe that this sort of aliasing will cause no
89          * trouble. The correct field should get stored into the Param slot at
90          * execution in each part of the tree.
91          */
92         i = 0;
93         foreach(ppv, PlannerParamVar)
94         {
95                 Var                *pvar = lfirst(ppv);
96
97                 if (pvar->varno == var->varno &&
98                         pvar->varattno == var->varattno &&
99                         pvar->varlevelsup == varlevel &&
100                         pvar->vartype == var->vartype)
101                         break;
102                 i++;
103         }
104
105         if (!ppv)
106         {
107                 /* Nope, so make a new one */
108                 i = new_param(var, varlevel);
109         }
110
111         retval = makeNode(Param);
112         retval->paramkind = PARAM_EXEC;
113         retval->paramid = (AttrNumber) i;
114         retval->paramtype = var->vartype;
115
116         return retval;
117 }
118
119 /*
120  * Convert a bare SubLink (as created by the parser) into a SubPlan.
121  */
122 static Node *
123 make_subplan(SubLink *slink)
124 {
125         SubPlan    *node = makeNode(SubPlan);
126         Query      *subquery = (Query *) (slink->subselect);
127         double          tuple_fraction;
128         Plan       *plan;
129         List       *lst;
130         Node       *result;
131         List       *saved_ip = PlannerInitPlan;
132
133         PlannerInitPlan = NULL;
134
135         PlannerQueryLevel++;            /* we become child */
136
137         /* Check to see if this node was already processed; if so we have
138          * trouble.  Someday should change tree representation so that we can
139          * cope with multiple links to the same subquery, but for now...
140          */
141         if (subquery == NULL)
142                 elog(ERROR, "make_subplan: invalid expression structure (subquery already processed?)");
143
144         /*
145          * For an EXISTS subplan, tell lower-level planner to expect that only
146          * the first tuple will be retrieved.  For ALL and ANY subplans, we
147          * will be able to stop evaluating if the test condition fails, so
148          * very often not all the tuples will be retrieved; for lack of a
149          * better idea, specify 50% retrieval.  For EXPR and MULTIEXPR
150          * subplans, use default behavior (we're only expecting one row out,
151          * anyway).
152          *
153          * NOTE: if you change these numbers, also change cost_qual_eval_walker()
154          * in path/costsize.c.
155          *
156          * XXX If an ALL/ANY subplan is uncorrelated, we may decide to
157          * materialize its result below.  In that case it would've been better
158          * to specify full retrieval.  At present, however, we can only detect
159          * correlation or lack of it after we've made the subplan :-(. Perhaps
160          * detection of correlation should be done as a separate step.
161          * Meanwhile, we don't want to be too optimistic about the percentage
162          * of tuples retrieved, for fear of selecting a plan that's bad for
163          * the materialization case.
164          */
165         if (slink->subLinkType == EXISTS_SUBLINK)
166                 tuple_fraction = 1.0;   /* just like a LIMIT 1 */
167         else if (slink->subLinkType == ALL_SUBLINK ||
168                          slink->subLinkType == ANY_SUBLINK)
169                 tuple_fraction = 0.5;   /* 50% */
170         else
171                 tuple_fraction = -1.0;  /* default behavior */
172
173         node->plan = plan = subquery_planner(subquery, tuple_fraction);
174
175         /*
176          * Assign subPlan, extParam and locParam to plan nodes. At the moment,
177          * SS_finalize_plan doesn't handle initPlan-s and so we assign them to
178          * the topmost plan node and take care about its extParam too.
179          */
180         (void) SS_finalize_plan(plan);
181         plan->initPlan = PlannerInitPlan;
182
183         /* Create extParam list as union of InitPlan-s' lists */
184         foreach(lst, PlannerInitPlan)
185         {
186                 List       *lp;
187
188                 foreach(lp, ((SubPlan *) lfirst(lst))->plan->extParam)
189                 {
190                         if (!intMember(lfirsti(lp), plan->extParam))
191                                 plan->extParam = lappendi(plan->extParam, lfirsti(lp));
192                 }
193         }
194
195         /* and now we are parent again */
196         PlannerInitPlan = saved_ip;
197         PlannerQueryLevel--;
198
199         node->plan_id = PlannerPlanId++;
200         node->rtable = subquery->rtable;
201         node->sublink = slink;
202         slink->subselect = NULL;        /* cool ?! see error check above! */
203
204         /* make parParam list of params coming from current query level */
205         foreach(lst, plan->extParam)
206         {
207                 Var                *var = nth(lfirsti(lst), PlannerParamVar);
208
209                 /* note varlevelsup is absolute level number */
210                 if (var->varlevelsup == PlannerQueryLevel)
211                         node->parParam = lappendi(node->parParam, lfirsti(lst));
212         }
213
214         /*
215          * Un-correlated or undirect correlated plans of EXISTS, EXPR, or
216          * MULTIEXPR types can be used as initPlans.  For EXISTS or EXPR, we
217          * just produce a Param referring to the result of evaluating the
218          * initPlan.  For MULTIEXPR, we must build an AND or OR-clause of the
219          * individual comparison operators, using the appropriate lefthand
220          * side expressions and Params for the initPlan's target items.
221          */
222         if (node->parParam == NIL && slink->subLinkType == EXISTS_SUBLINK)
223         {
224                 Var                *var = makeVar(0, 0, BOOLOID, -1, 0);
225                 Param      *prm = makeNode(Param);
226
227                 prm->paramkind = PARAM_EXEC;
228                 prm->paramid = (AttrNumber) new_param(var, PlannerQueryLevel);
229                 prm->paramtype = var->vartype;
230                 pfree(var);                             /* var is only needed for new_param */
231                 node->setParam = lappendi(node->setParam, prm->paramid);
232                 PlannerInitPlan = lappend(PlannerInitPlan, node);
233                 result = (Node *) prm;
234         }
235         else if (node->parParam == NIL && slink->subLinkType == EXPR_SUBLINK)
236         {
237                 TargetEntry *te = lfirst(plan->targetlist);
238
239                 /* need a var node just to pass to new_param()... */
240                 Var                *var = makeVar(0, 0, te->resdom->restype,
241                                                                   te->resdom->restypmod, 0);
242                 Param      *prm = makeNode(Param);
243
244                 prm->paramkind = PARAM_EXEC;
245                 prm->paramid = (AttrNumber) new_param(var, PlannerQueryLevel);
246                 prm->paramtype = var->vartype;
247                 pfree(var);                             /* var is only needed for new_param */
248                 node->setParam = lappendi(node->setParam, prm->paramid);
249                 PlannerInitPlan = lappend(PlannerInitPlan, node);
250                 result = (Node *) prm;
251         }
252         else if (node->parParam == NIL && slink->subLinkType == MULTIEXPR_SUBLINK)
253         {
254                 List       *newoper = NIL;
255                 int                     i = 0;
256
257                 /*
258                  * Convert oper list of Opers into a list of Exprs, using lefthand
259                  * arguments and Params representing inside results.
260                  */
261                 foreach(lst, slink->oper)
262                 {
263                         Oper       *oper = (Oper *) lfirst(lst);
264                         Node       *lefthand = nth(i, slink->lefthand);
265                         TargetEntry *te = nth(i, plan->targetlist);
266
267                         /* need a var node just to pass to new_param()... */
268                         Var                *var = makeVar(0, 0, te->resdom->restype,
269                                                                           te->resdom->restypmod, 0);
270                         Param      *prm = makeNode(Param);
271                         Operator        tup;
272                         Form_pg_operator opform;
273                         Node       *left,
274                                            *right;
275
276                         prm->paramkind = PARAM_EXEC;
277                         prm->paramid = (AttrNumber) new_param(var, PlannerQueryLevel);
278                         prm->paramtype = var->vartype;
279                         pfree(var);                     /* var is only needed for new_param */
280
281                         Assert(IsA(oper, Oper));
282                         tup = get_operator_tuple(oper->opno);
283                         Assert(HeapTupleIsValid(tup));
284                         opform = (Form_pg_operator) GETSTRUCT(tup);
285
286                         /*
287                          * Note: we use make_operand in case runtime type conversion
288                          * function calls must be inserted for this operator!
289                          */
290                         left = make_operand("", lefthand,
291                                                                 exprType(lefthand), opform->oprleft);
292                         right = make_operand("", (Node *) prm,
293                                                                  prm->paramtype, opform->oprright);
294                         newoper = lappend(newoper,
295                                                           make_opclause(oper,
296                                                                                         (Var *) left,
297                                                                                         (Var *) right));
298                         node->setParam = lappendi(node->setParam, prm->paramid);
299                         i++;
300                 }
301                 slink->oper = newoper;
302                 slink->lefthand = NIL;
303                 PlannerInitPlan = lappend(PlannerInitPlan, node);
304                 if (i > 1)
305                         result = (Node *) ((slink->useor) ? make_orclause(newoper) :
306                                                            make_andclause(newoper));
307                 else
308                         result = (Node *) lfirst(newoper);
309         }
310         else
311         {
312                 Expr       *expr = makeNode(Expr);
313                 List       *args = NIL;
314                 List       *newoper = NIL;
315                 int                     i = 0;
316
317                 /*
318                  * We can't convert subplans of ALL_SUBLINK or ANY_SUBLINK types
319                  * to initPlans, even when they are uncorrelated or undirect
320                  * correlated, because we need to scan the output of the subplan
321                  * for each outer tuple.  However, we have the option to tack a
322                  * MATERIAL node onto the top of an uncorrelated/undirect
323                  * correlated subplan, which lets us do the work of evaluating the
324                  * subplan only once.  We do this if the subplan's top plan node
325                  * is anything more complicated than a plain sequential scan, and
326                  * we do it even for seqscan if the qual appears selective enough
327                  * to eliminate many tuples.
328                  */
329                 if (node->parParam == NIL)
330                 {
331                         bool            use_material;
332
333                         switch (nodeTag(plan))
334                         {
335                                 case T_SeqScan:
336                                         if (plan->initPlan || plan->subPlan)
337                                                 use_material = true;
338                                         else
339                                         {
340                                                 Selectivity qualsel;
341
342                                                 qualsel = clauselist_selectivity(subquery,
343                                                                                                                  plan->qual,
344                                                                                                                  0);
345                                                 /* Is 10% selectivity a good threshold?? */
346                                                 use_material = qualsel < 0.10;
347                                         }
348                                         break;
349                                 case T_Material:
350                                 case T_Sort:
351
352                                         /*
353                                          * Don't add another Material node if there's one
354                                          * already, nor if the top node is a Sort, since Sort
355                                          * materializes its output anyway.      (I doubt either
356                                          * case can happen in practice for a subplan, but...)
357                                          */
358                                         use_material = false;
359                                         break;
360                                 default:
361                                         use_material = true;
362                                         break;
363                         }
364                         if (use_material)
365                         {
366                                 plan = (Plan *) make_material(plan->targetlist, plan);
367                                 node->plan = plan;
368                         }
369                 }
370
371                 /*
372                  * Make expression of SUBPLAN type
373                  */
374                 expr->typeOid = BOOLOID;/* bogus, but we don't really care */
375                 expr->opType = SUBPLAN_EXPR;
376                 expr->oper = (Node *) node;
377
378                 /*
379                  * Make expr->args from parParam.
380                  */
381                 foreach(lst, node->parParam)
382                 {
383                         Var                *var = nth(lfirsti(lst), PlannerParamVar);
384
385                         var = (Var *) copyObject(var);
386
387                         /*
388                          * Must fix absolute-level varlevelsup from the
389                          * PlannerParamVar entry.  But since var is at current subplan
390                          * level, this is easy:
391                          */
392                         var->varlevelsup = 0;
393                         args = lappend(args, var);
394                 }
395                 expr->args = args;
396
397                 /*
398                  * Convert oper list of Opers into a list of Exprs, using lefthand
399                  * arguments and Consts representing inside results.
400                  */
401                 foreach(lst, slink->oper)
402                 {
403                         Oper       *oper = (Oper *) lfirst(lst);
404                         Node       *lefthand = nth(i, slink->lefthand);
405                         TargetEntry *te = nth(i, plan->targetlist);
406                         Const      *con;
407                         Operator        tup;
408                         Form_pg_operator opform;
409                         Node       *left,
410                                            *right;
411
412                         /*
413                          * XXX really ought to fill in constlen and constbyval
414                          * correctly, but right now ExecEvalExpr won't look at them...
415                          */
416                         con = makeConst(te->resdom->restype, 0, 0, true, 0, 0, 0);
417
418                         Assert(IsA(oper, Oper));
419                         tup = get_operator_tuple(oper->opno);
420                         Assert(HeapTupleIsValid(tup));
421                         opform = (Form_pg_operator) GETSTRUCT(tup);
422
423                         /*
424                          * Note: we use make_operand in case runtime type conversion
425                          * function calls must be inserted for this operator!
426                          */
427                         left = make_operand("", lefthand,
428                                                                 exprType(lefthand), opform->oprleft);
429                         right = make_operand("", (Node *) con,
430                                                                  con->consttype, opform->oprright);
431                         newoper = lappend(newoper,
432                                                           make_opclause(oper,
433                                                                                         (Var *) left,
434                                                                                         (Var *) right));
435                         i++;
436                 }
437                 slink->oper = newoper;
438                 slink->lefthand = NIL;
439                 result = (Node *) expr;
440         }
441
442         return result;
443 }
444
445 /* this oughta be merged with LispUnioni */
446
447 static List *
448 set_unioni(List *l1, List *l2)
449 {
450         if (l1 == NULL)
451                 return l2;
452         if (l2 == NULL)
453                 return l1;
454
455         return nconc(l1, set_differencei(l2, l1));
456 }
457
458 /*
459  * finalize_primnode: build lists of subplans and params appearing
460  * in the given expression tree.  NOTE: items are added to lists passed in,
461  * so caller must initialize lists to NIL before first call!
462  *
463  * Note: the subplan list that is constructed here and assigned to the
464  * plan's subPlan field will be replaced with an up-to-date list in
465  * set_plan_references().  We could almost dispense with building this
466  * subplan list at all; I believe the only place that uses it is the
467  * check in make_subplan to see whether a subselect has any subselects.
468  */
469
470 typedef struct finalize_primnode_results
471 {
472         List       *subplans;           /* List of subplans found in expr */
473         List       *paramids;           /* List of PARAM_EXEC paramids found */
474 } finalize_primnode_results;
475
476 static bool
477 finalize_primnode(Node *node, finalize_primnode_results *results)
478 {
479         if (node == NULL)
480                 return false;
481         if (IsA(node, Param))
482         {
483                 if (((Param *) node)->paramkind == PARAM_EXEC)
484                 {
485                         int                     paramid = (int) ((Param *) node)->paramid;
486
487                         if (!intMember(paramid, results->paramids))
488                                 results->paramids = lconsi(paramid, results->paramids);
489                 }
490                 return false;                   /* no more to do here */
491         }
492         if (is_subplan(node))
493         {
494                 SubPlan    *subplan = (SubPlan *) ((Expr *) node)->oper;
495                 List       *lst;
496
497                 /* Add subplan to subplans list */
498                 results->subplans = lappend(results->subplans, subplan);
499                 /* Check extParam list for params to add to paramids */
500                 foreach(lst, subplan->plan->extParam)
501                 {
502                         int                     paramid = lfirsti(lst);
503                         Var                *var = nth(paramid, PlannerParamVar);
504
505                         /* note varlevelsup is absolute level number */
506                         if (var->varlevelsup < PlannerQueryLevel &&
507                                 !intMember(paramid, results->paramids))
508                                 results->paramids = lconsi(paramid, results->paramids);
509                 }
510                 /* fall through to recurse into subplan args */
511         }
512         return expression_tree_walker(node, finalize_primnode,
513                                                                   (void *) results);
514 }
515
516 /*
517  * Replace correlation vars (uplevel vars) with Params.
518  */
519
520 static Node *replace_correlation_vars_mutator(Node *node, void *context);
521
522 Node *
523 SS_replace_correlation_vars(Node *expr)
524 {
525         /* No setup needed for tree walk, so away we go */
526         return replace_correlation_vars_mutator(expr, NULL);
527 }
528
529 static Node *
530 replace_correlation_vars_mutator(Node *node, void *context)
531 {
532         if (node == NULL)
533                 return NULL;
534         if (IsA(node, Var))
535         {
536                 if (((Var *) node)->varlevelsup > 0)
537                         return (Node *) replace_var((Var *) node);
538         }
539         return expression_tree_mutator(node,
540                                                                    replace_correlation_vars_mutator,
541                                                                    context);
542 }
543
544 /*
545  * Expand SubLinks to SubPlans in the given expression.
546  */
547
548 static Node *process_sublinks_mutator(Node *node, void *context);
549
550 Node *
551 SS_process_sublinks(Node *expr)
552 {
553         /* No setup needed for tree walk, so away we go */
554         return process_sublinks_mutator(expr, NULL);
555 }
556
557 static Node *
558 process_sublinks_mutator(Node *node, void *context)
559 {
560         if (node == NULL)
561                 return NULL;
562         if (IsA(node, SubLink))
563         {
564                 SubLink    *sublink = (SubLink *) node;
565
566                 /*
567                  * First, scan the lefthand-side expressions, if any. This is a
568                  * tad klugy since we modify the input SubLink node, but that
569                  * should be OK (make_subplan does it too!)
570                  */
571                 sublink->lefthand = (List *)
572                         process_sublinks_mutator((Node *) sublink->lefthand, context);
573                 /* Now build the SubPlan node and make the expr to return */
574                 return make_subplan(sublink);
575         }
576
577         /*
578          * Note that we will never see a SubPlan expression in the input
579          * (since this is the very routine that creates 'em to begin with). So
580          * the code in expression_tree_mutator() that might do inappropriate
581          * things with SubPlans or SubLinks will not be exercised.
582          */
583         Assert(!is_subplan(node));
584
585         return expression_tree_mutator(node,
586                                                                    process_sublinks_mutator,
587                                                                    context);
588 }
589
590 List *
591 SS_finalize_plan(Plan *plan)
592 {
593         List       *extParam = NIL;
594         List       *locParam = NIL;
595         finalize_primnode_results results;
596         List       *lst;
597
598         if (plan == NULL)
599                 return NIL;
600
601         results.subplans = NIL;         /* initialize lists to NIL */
602         results.paramids = NIL;
603
604         /*
605          * When we call finalize_primnode, results.paramids lists are
606          * automatically merged together.  But when recursing to self, we have
607          * to do it the hard way.  We want the paramids list to include params
608          * in subplans as well as at this level. (We don't care about finding
609          * subplans of subplans, though.)
610          */
611
612         /* Find params and subplans in targetlist and qual */
613         finalize_primnode((Node *) plan->targetlist, &results);
614         finalize_primnode((Node *) plan->qual, &results);
615
616         /* Check additional node-type-specific fields */
617         switch (nodeTag(plan))
618         {
619                 case T_Result:
620                         finalize_primnode(((Result *) plan)->resconstantqual,
621                                                           &results);
622                         break;
623
624                 case T_Append:
625                         foreach(lst, ((Append *) plan)->appendplans)
626                                 results.paramids = set_unioni(results.paramids,
627                                                                  SS_finalize_plan((Plan *) lfirst(lst)));
628                         break;
629
630                 case T_IndexScan:
631                         finalize_primnode((Node *) ((IndexScan *) plan)->indxqual,
632                                                           &results);
633
634                         /*
635                          * we need not look at indxqualorig, since it will have the
636                          * same param references as indxqual, and we aren't really
637                          * concerned yet about having a complete subplan list.
638                          */
639                         break;
640
641                 case T_MergeJoin:
642                         finalize_primnode((Node *) ((MergeJoin *) plan)->mergeclauses,
643                                                           &results);
644                         break;
645
646                 case T_HashJoin:
647                         finalize_primnode((Node *) ((HashJoin *) plan)->hashclauses,
648                                                           &results);
649                         break;
650
651                 case T_Hash:
652                         finalize_primnode(((Hash *) plan)->hashkey,
653                                                           &results);
654                         break;
655
656                 case T_TidScan:
657                         finalize_primnode((Node *) ((TidScan *) plan)->tideval,
658                                                           &results);
659                         break;
660
661                 case T_Agg:
662                 case T_SeqScan:
663                 case T_NestLoop:
664                 case T_Material:
665                 case T_Sort:
666                 case T_Unique:
667                 case T_Group:
668                         break;
669
670                 default:
671                         elog(ERROR, "SS_finalize_plan: node %d unsupported",
672                                  nodeTag(plan));
673         }
674
675         /* Process left and right subplans, if any */
676         results.paramids = set_unioni(results.paramids,
677                                                                   SS_finalize_plan(plan->lefttree));
678         results.paramids = set_unioni(results.paramids,
679                                                                   SS_finalize_plan(plan->righttree));
680
681         /* Now we have all the paramids and subplans */
682
683         foreach(lst, results.paramids)
684         {
685                 Var                *var = nth(lfirsti(lst), PlannerParamVar);
686
687                 /* note varlevelsup is absolute level number */
688                 if (var->varlevelsup < PlannerQueryLevel)
689                         extParam = lappendi(extParam, lfirsti(lst));
690                 else if (var->varlevelsup > PlannerQueryLevel)
691                         elog(ERROR, "SS_finalize_plan: plan shouldn't reference subplan's variable");
692                 else
693                 {
694                         Assert(var->varno == 0 && var->varattno == 0);
695                         locParam = lappendi(locParam, lfirsti(lst));
696                 }
697         }
698
699         plan->extParam = extParam;
700         plan->locParam = locParam;
701         plan->subPlan = results.subplans;
702
703         return results.paramids;
704 }