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