]> granicus.if.org Git - postgresql/blob - src/backend/optimizer/plan/planner.c
Add:
[postgresql] / src / backend / optimizer / plan / planner.c
1 /*-------------------------------------------------------------------------
2  *
3  * planner.c
4  *        The query optimizer external interface.
5  *
6  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *        $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.73 2000/01/26 05:56:37 momjian Exp $
12  *
13  *-------------------------------------------------------------------------
14  */
15 #include <sys/types.h>
16
17 #include "postgres.h"
18
19 #include "access/genam.h"
20 #include "access/heapam.h"
21 #include "catalog/pg_type.h"
22 #include "executor/executor.h"
23 #include "nodes/makefuncs.h"
24 #include "optimizer/clauses.h"
25 #include "optimizer/internal.h"
26 #include "optimizer/paths.h"
27 #include "optimizer/planmain.h"
28 #include "optimizer/planner.h"
29 #include "optimizer/prep.h"
30 #include "optimizer/subselect.h"
31 #include "optimizer/tlist.h"
32 #include "optimizer/var.h"
33 #include "parser/parse_expr.h"
34 #include "parser/parse_oper.h"
35 #include "utils/builtins.h"
36 #include "utils/lsyscache.h"
37 #include "utils/syscache.h"
38
39 static List *make_subplanTargetList(Query *parse, List *tlist,
40                                                                         AttrNumber **groupColIdx);
41 static Plan *make_groupplan(List *group_tlist, bool tuplePerGroup,
42                                                         List *groupClause, AttrNumber *grpColIdx,
43                                                         bool is_presorted, Plan *subplan);
44 static Plan *make_sortplan(List *tlist, List *sortcls, Plan *plannode);
45
46 /*****************************************************************************
47  *
48  *         Query optimizer entry point
49  *
50  *****************************************************************************/
51 Plan *
52 planner(Query *parse)
53 {
54         Plan       *result_plan;
55
56         /* Initialize state for subselects */
57         PlannerQueryLevel = 1;
58         PlannerInitPlan = NULL;
59         PlannerParamVar = NULL;
60         PlannerPlanId = 0;
61
62         transformKeySetQuery(parse);
63
64         result_plan = union_planner(parse);
65
66         Assert(PlannerQueryLevel == 1);
67         if (PlannerPlanId > 0)
68         {
69                 result_plan->initPlan = PlannerInitPlan;
70                 (void) SS_finalize_plan(result_plan);
71         }
72         result_plan->nParamExec = length(PlannerParamVar);
73
74         set_plan_references(result_plan);
75
76         return result_plan;
77 }
78
79 /*
80  * union_planner
81  *
82  *        Invokes the planner on union queries if there are any left,
83  *        recursing if necessary to get them all, then processes normal plans.
84  *
85  * Returns a query plan.
86  *
87  */
88 Plan *
89 union_planner(Query *parse)
90 {
91         List       *tlist = parse->targetList;
92         List       *rangetable = parse->rtable;
93         Plan       *result_plan = (Plan *) NULL;
94         AttrNumber *groupColIdx = NULL;
95         List       *current_pathkeys = NIL;
96         Index           rt_index;
97
98         /*
99          * A HAVING clause without aggregates is equivalent to a WHERE clause
100          * (except it can only refer to grouped fields).  If there are no
101          * aggs anywhere in the query, then we don't want to create an Agg
102          * plan node, so merge the HAVING condition into WHERE.  (We used to
103          * consider this an error condition, but it seems to be legal SQL.)
104          */
105         if (parse->havingQual != NULL && ! parse->hasAggs)
106         {
107                 if (parse->qual == NULL)
108                         parse->qual = parse->havingQual;
109                 else
110                         parse->qual = (Node *) make_andclause(lappend(lcons(parse->qual,
111                                                                                                                                 NIL),
112                                                                                                                   parse->havingQual));
113                 parse->havingQual = NULL;
114         }
115
116         /*
117          * Simplify constant expressions in targetlist and quals.
118          *
119          * Note that at this point the qual has not yet been converted to
120          * implicit-AND form, so we can apply eval_const_expressions directly.
121          * Also note that we need to do this before SS_process_sublinks,
122          * because that routine inserts bogus "Const" nodes.
123          */
124         tlist = (List *) eval_const_expressions((Node *) tlist);
125         parse->qual = eval_const_expressions(parse->qual);
126         parse->havingQual = eval_const_expressions(parse->havingQual);
127
128
129         if (parse->unionClause)
130         {
131                 result_plan = (Plan *) plan_union_queries(parse);
132                 /* XXX do we need to do this? bjm 12/19/97 */
133                 tlist = preprocess_targetlist(tlist,
134                                                                           parse->commandType,
135                                                                           parse->resultRelation,
136                                                                           parse->rtable);
137                 /*
138                  * We leave current_pathkeys NIL indicating we do not know sort order.
139                  * Actually, for a normal UNION we have done an explicit sort; ought
140                  * to change interface to plan_union_queries to pass that info back!
141                  */
142         }
143         else if ((rt_index = first_inherit_rt_entry(rangetable)) != -1)
144         {
145                 List       *sub_tlist;
146
147                 /*
148                  * Generate appropriate target list for subplan; may be different
149                  * from tlist if grouping or aggregation is needed.
150                  */
151                 sub_tlist = make_subplanTargetList(parse, tlist, &groupColIdx);
152
153                 /*
154                  * Recursively plan the subqueries needed for inheritance
155                  */
156                 result_plan = (Plan *) plan_inherit_queries(parse, sub_tlist,
157                                                                                                         rt_index);
158
159                 /*
160                  * Fix up outer target list.  NOTE: unlike the case for non-inherited
161                  * query, we pass the unfixed tlist to subplans, which do their own
162                  * fixing.  But we still want to fix the outer target list afterwards.
163                  * I *think* this is correct --- doing the fix before recursing is
164                  * definitely wrong, because preprocess_targetlist() will do the
165                  * wrong thing if invoked twice on the same list. Maybe that is a bug?
166                  * tgl 6/6/99
167                  */
168                 tlist = preprocess_targetlist(tlist,
169                                                                           parse->commandType,
170                                                                           parse->resultRelation,
171                                                                           parse->rtable);
172
173                 if (parse->rowMark != NULL)
174                         elog(ERROR, "SELECT FOR UPDATE is not supported for inherit queries");
175                 /*
176                  * We leave current_pathkeys NIL indicating we do not know sort order
177                  * of the Append-ed results.
178                  */
179         }
180         else
181         {
182                 List       *sub_tlist;
183
184                 /* Preprocess targetlist in case we are inside an INSERT/UPDATE. */
185                 tlist = preprocess_targetlist(tlist,
186                                                                           parse->commandType,
187                                                                           parse->resultRelation,
188                                                                           parse->rtable);
189
190                 /*
191                  * Add row-mark targets for UPDATE (should this be done in
192                  * preprocess_targetlist?)
193                  */
194                 if (parse->rowMark != NULL)
195                 {
196                         List       *l;
197
198                         foreach(l, parse->rowMark)
199                         {
200                                 RowMark    *rowmark = (RowMark *) lfirst(l);
201                                 TargetEntry *ctid;
202                                 Resdom     *resdom;
203                                 Var                *var;
204                                 char       *resname;
205
206                                 if (!(rowmark->info & ROW_MARK_FOR_UPDATE))
207                                         continue;
208
209                                 resname = (char *) palloc(32);
210                                 sprintf(resname, "ctid%u", rowmark->rti);
211                                 resdom = makeResdom(length(tlist) + 1,
212                                                                         TIDOID,
213                                                                         -1,
214                                                                         resname,
215                                                                         0,
216                                                                         0,
217                                                                         true);
218
219                                 var = makeVar(rowmark->rti, -1, TIDOID, -1, 0);
220
221                                 ctid = makeTargetEntry(resdom, (Node *) var);
222                                 tlist = lappend(tlist, ctid);
223                         }
224                 }
225
226                 /*
227                  * Generate appropriate target list for subplan; may be different
228                  * from tlist if grouping or aggregation is needed.
229                  */
230                 sub_tlist = make_subplanTargetList(parse, tlist, &groupColIdx);
231
232                 /*
233                  * Figure out whether we need a sorted result from query_planner.
234                  *
235                  * If we have a GROUP BY clause, then we want a result sorted
236                  * properly for grouping.  Otherwise, if there is an ORDER BY clause,
237                  * we want to sort by the ORDER BY clause.
238                  */
239                 if (parse->groupClause)
240                 {
241                         parse->query_pathkeys =
242                                 make_pathkeys_for_sortclauses(parse->groupClause, tlist);
243                 }
244                 else if (parse->sortClause)
245                 {
246                         parse->query_pathkeys =
247                                 make_pathkeys_for_sortclauses(parse->sortClause, tlist);
248                 }
249                 else
250                 {
251                         parse->query_pathkeys = NIL;
252                 }
253
254                 /* Generate the (sub) plan */
255                 result_plan = query_planner(parse,
256                                                                         sub_tlist,
257                                                                         (List *) parse->qual);
258
259                 /* query_planner returns actual sort order (which is not
260                  * necessarily what we requested) in query_pathkeys.
261                  */
262                 current_pathkeys = parse->query_pathkeys;
263         }
264
265         /* query_planner returns NULL if it thinks plan is bogus */
266         if (! result_plan)
267                 elog(ERROR, "union_planner: failed to create plan");
268
269         /*
270          * If we have a GROUP BY clause, insert a group node (plus the
271          * appropriate sort node, if necessary).
272          */
273         if (parse->groupClause)
274         {
275                 bool            tuplePerGroup;
276                 List       *group_tlist;
277                 List       *group_pathkeys;
278                 bool            is_sorted;
279
280                 /*
281                  * Decide whether how many tuples per group the Group node needs
282                  * to return. (Needs only one tuple per group if no aggregate is
283                  * present. Otherwise, need every tuple from the group to do the
284                  * aggregation.)  Note tuplePerGroup is named backwards :-(
285                  */
286                 tuplePerGroup = parse->hasAggs;
287
288                 /*
289                  * If there are aggregates then the Group node should just return
290                  * the same set of vars as the subplan did (but we can exclude
291                  * any GROUP BY expressions).  If there are no aggregates
292                  * then the Group node had better compute the final tlist.
293                  */
294                 if (parse->hasAggs)
295                         group_tlist = flatten_tlist(result_plan->targetlist);
296                 else
297                         group_tlist = tlist;
298
299                 /*
300                  * Figure out whether the path result is already ordered the way we
301                  * need it --- if so, no need for an explicit sort step.
302                  */
303                 group_pathkeys = make_pathkeys_for_sortclauses(parse->groupClause,
304                                                                                                            tlist);
305                 if (pathkeys_contained_in(group_pathkeys, current_pathkeys))
306                 {
307                         is_sorted = true;       /* no sort needed now */
308                         /* current_pathkeys remains unchanged */
309                 }
310                 else
311                 {
312                         /* We will need to do an explicit sort by the GROUP BY clause.
313                          * make_groupplan will do the work, but set current_pathkeys
314                          * to indicate the resulting order.
315                          */
316                         is_sorted = false;
317                         current_pathkeys = group_pathkeys;
318                 }
319
320                 result_plan = make_groupplan(group_tlist,
321                                                                          tuplePerGroup,
322                                                                          parse->groupClause,
323                                                                          groupColIdx,
324                                                                          is_sorted,
325                                                                          result_plan);
326         }
327
328         /*
329          * If we have a HAVING clause, do the necessary things with it.
330          * This code should parallel query_planner()'s initial processing
331          * of the WHERE clause.
332          */
333         if (parse->havingQual)
334         {
335                 /* Convert the havingQual to implicit-AND normal form */
336                 parse->havingQual = (Node *)
337                         canonicalize_qual((Expr *) parse->havingQual, true);
338
339                 /* Replace uplevel Vars with Params */
340                 if (PlannerQueryLevel > 1)
341                         parse->havingQual = SS_replace_correlation_vars(parse->havingQual);
342
343                 if (parse->hasSubLinks)
344                 {
345                         /* Expand SubLinks to SubPlans */
346                         parse->havingQual = SS_process_sublinks(parse->havingQual);
347                         /* Check for ungrouped variables passed to subplans */
348                         check_subplans_for_ungrouped_vars(parse->havingQual,
349                                                                                           parse,
350                                                                                           parse->targetList);
351                 }
352         }
353
354         /*
355          * If aggregate is present, insert the agg node
356          */
357         if (parse->hasAggs)
358         {
359                 result_plan = (Plan *) make_agg(tlist, result_plan);
360
361                 /* HAVING clause, if any, becomes qual of the Agg node */
362                 result_plan->qual = (List *) parse->havingQual;
363
364                 /* Note: Agg does not affect any existing sort order of the tuples */
365         }
366
367         /*
368          * If we were not able to make the plan come out in the right order,
369          * add an explicit sort step.
370          */
371         if (parse->sortClause)
372         {
373                 List       *sort_pathkeys;
374
375                 sort_pathkeys = make_pathkeys_for_sortclauses(parse->sortClause,
376                                                                                                           tlist);
377                 if (! pathkeys_contained_in(sort_pathkeys, current_pathkeys))
378                 {
379                         result_plan = make_sortplan(tlist, parse->sortClause, result_plan);
380                 }
381         }
382
383         /*
384          * Finally, if there is a UNIQUE clause, add the UNIQUE node.
385          */
386         if (parse->uniqueFlag)
387         {
388                 result_plan = (Plan *) make_unique(tlist, result_plan,
389                                                                                    parse->uniqueFlag);
390         }
391
392         return result_plan;
393 }
394
395 /*---------------
396  * make_subplanTargetList
397  *        Generate appropriate target list when grouping is required.
398  *
399  * When union_planner inserts Aggregate and/or Group plan nodes above
400  * the result of query_planner, we typically want to pass a different
401  * target list to query_planner than the outer plan nodes should have.
402  * This routine generates the correct target list for the subplan.
403  *
404  * The initial target list passed from the parser already contains entries
405  * for all ORDER BY and GROUP BY expressions, but it will not have entries
406  * for variables used only in HAVING clauses; so we need to add those
407  * variables to the subplan target list.  Also, if we are doing either
408  * grouping or aggregation, we flatten all expressions except GROUP BY items
409  * into their component variables; the other expressions will be computed by
410  * the inserted nodes rather than by the subplan.  For example,
411  * given a query like
412  *              SELECT a+b,SUM(c+d) FROM table GROUP BY a+b;
413  * we want to pass this targetlist to the subplan:
414  *              a,b,c,d,a+b
415  * where the a+b target will be used by the Sort/Group steps, and the
416  * other targets will be used for computing the final results.  (In the
417  * above example we could theoretically suppress the a and b targets and
418  * use only a+b, but it's not really worth the trouble.)
419  *
420  * 'parse' is the query being processed.
421  * 'tlist' is the query's target list.
422  * 'groupColIdx' receives an array of column numbers for the GROUP BY
423  * expressions (if there are any) in the subplan's target list.
424  *
425  * The result is the targetlist to be passed to the subplan.
426  *---------------
427  */
428 static List *
429 make_subplanTargetList(Query *parse,
430                                            List *tlist,
431                                            AttrNumber **groupColIdx)
432 {
433         List       *sub_tlist;
434         List       *extravars;
435         int                     numCols;
436
437         *groupColIdx = NULL;
438
439         /*
440          * If we're not grouping or aggregating, nothing to do here;
441          * query_planner should receive the unmodified target list.
442          */
443         if (!parse->hasAggs && !parse->groupClause && !parse->havingQual)
444                 return tlist;
445
446         /*
447          * Otherwise, start with a "flattened" tlist (having just the vars
448          * mentioned in the targetlist and HAVING qual --- but not upper-
449          * level Vars; they will be replaced by Params later on).
450          */
451         sub_tlist = flatten_tlist(tlist);
452         extravars = pull_var_clause(parse->havingQual, false);
453         sub_tlist = add_to_flat_tlist(sub_tlist, extravars);
454         freeList(extravars);
455
456         /*
457          * If grouping, create sub_tlist entries for all GROUP BY expressions
458          * (GROUP BY items that are simple Vars should be in the list already),
459          * and make an array showing where the group columns are in the sub_tlist.
460          */
461         numCols = length(parse->groupClause);
462         if (numCols > 0)
463         {
464                 int                     keyno = 0;
465                 AttrNumber *grpColIdx;
466                 List       *gl;
467
468                 grpColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numCols);
469                 *groupColIdx = grpColIdx;
470
471                 foreach(gl, parse->groupClause)
472                 {
473                         GroupClause        *grpcl = (GroupClause *) lfirst(gl);
474                         Node               *groupexpr = get_sortgroupclause_expr(grpcl, tlist);
475                         TargetEntry        *te = NULL;
476                         List               *sl;
477
478                         /* Find or make a matching sub_tlist entry */
479                         foreach(sl, sub_tlist)
480                         {
481                                 te = (TargetEntry *) lfirst(sl);
482                                 if (equal(groupexpr, te->expr))
483                                         break;
484                         }
485                         if (! sl)
486                         {
487                                 te = makeTargetEntry(makeResdom(length(sub_tlist) + 1,
488                                                                                                 exprType(groupexpr),
489                                                                                                 exprTypmod(groupexpr),
490                                                                                                 NULL,
491                                                                                                 (Index) 0,
492                                                                                                 (Oid) 0,
493                                                                                                 false),
494                                                                          groupexpr);
495                                 sub_tlist = lappend(sub_tlist, te);
496                         }
497
498                         /* and save its resno */
499                         grpColIdx[keyno++] = te->resdom->resno;
500                 }
501         }
502
503         return sub_tlist;
504 }
505
506 /*
507  * make_groupplan
508  *              Add a Group node for GROUP BY processing.
509  *              If we couldn't make the subplan produce presorted output for grouping,
510  *              first add an explicit Sort node.
511  */
512 static Plan *
513 make_groupplan(List *group_tlist,
514                            bool tuplePerGroup,
515                            List *groupClause,
516                            AttrNumber *grpColIdx,
517                            bool is_presorted,
518                            Plan *subplan)
519 {
520         int                     numCols = length(groupClause);
521
522         if (! is_presorted)
523         {
524                 /*
525                  * The Sort node always just takes a copy of the subplan's tlist
526                  * plus ordering information.  (This might seem inefficient if the
527                  * subplan contains complex GROUP BY expressions, but in fact Sort
528                  * does not evaluate its targetlist --- it only outputs the same
529                  * tuples in a new order.  So the expressions we might be copying
530                  * are just dummies with no extra execution cost.)
531                  */
532                 List       *sort_tlist = new_unsorted_tlist(subplan->targetlist);
533                 int                     keyno = 0;
534                 List       *gl;
535
536                 foreach(gl, groupClause)
537                 {
538                         GroupClause        *grpcl = (GroupClause *) lfirst(gl);
539                         TargetEntry        *te = nth(grpColIdx[keyno]-1, sort_tlist);
540                         Resdom             *resdom = te->resdom;
541
542                         /*
543                          * Check for the possibility of duplicate group-by clauses --- the
544                          * parser should have removed 'em, but the Sort executor will get
545                          * terribly confused if any get through!
546                          */
547                         if (resdom->reskey == 0)
548                         {
549                                 /* OK, insert the ordering info needed by the executor. */
550                                 resdom->reskey = ++keyno;
551                                 resdom->reskeyop = get_opcode(grpcl->sortop);
552                         }
553                 }
554
555                 subplan = (Plan *) make_sort(sort_tlist,
556                                                                          _NONAME_RELATION_ID_,
557                                                                          subplan,
558                                                                          keyno);
559         }
560
561         return (Plan *) make_group(group_tlist, tuplePerGroup, numCols,
562                                                            grpColIdx, subplan);
563 }
564
565 /*
566  * make_sortplan
567  *        Add a Sort node to implement an explicit ORDER BY clause.
568  */
569 static Plan *
570 make_sortplan(List *tlist, List *sortcls, Plan *plannode)
571 {
572         List       *temp_tlist;
573         List       *i;
574         int                     keyno = 0;
575
576         /*
577          * First make a copy of the tlist so that we don't corrupt the
578          * original.
579          */
580
581         temp_tlist = new_unsorted_tlist(tlist);
582
583         foreach(i, sortcls)
584         {
585                 SortClause *sortcl = (SortClause *) lfirst(i);
586                 Index           refnumber = sortcl->tleSortGroupRef;
587                 TargetEntry *tle = NULL;
588                 Resdom     *resdom;
589                 List       *l;
590
591                 foreach(l, temp_tlist)
592                 {
593                         tle = (TargetEntry *) lfirst(l);
594                         if (tle->resdom->ressortgroupref == refnumber)
595                                 break;
596                 }
597                 if (l == NIL)
598                         elog(ERROR, "make_sortplan: ORDER BY expression not found in targetlist");
599                 resdom = tle->resdom;
600
601                 /*
602                  * Check for the possibility of duplicate order-by clauses --- the
603                  * parser should have removed 'em, but the executor will get terribly
604                  * confused if any get through!
605                  */
606                 if (resdom->reskey == 0)
607                 {
608                         /* OK, insert the ordering info needed by the executor. */
609                         resdom->reskey = ++keyno;
610                         resdom->reskeyop = get_opcode(sortcl->sortop);
611                 }
612         }
613
614         return (Plan *) make_sort(temp_tlist,
615                                                           _NONAME_RELATION_ID_,
616                                                           plannode,
617                                                           keyno);
618 }
619
620 /*
621  * pg_checkretval() -- check return value of a list of sql parse
622  *                                              trees.
623  *
624  * The return value of a sql function is the value returned by
625  * the final query in the function.  We do some ad-hoc define-time
626  * type checking here to be sure that the user is returning the
627  * type he claims.
628  *
629  * XXX Why is this function in this module?
630  */
631 void
632 pg_checkretval(Oid rettype, List *queryTreeList)
633 {
634         Query      *parse;
635         List       *tlist;
636         List       *rt;
637         int                     cmd;
638         Type            typ;
639         Resdom     *resnode;
640         Relation        reln;
641         Oid                     relid;
642         int                     relnatts;
643         int                     i;
644
645         /* find the final query */
646         parse = (Query *) nth(length(queryTreeList) - 1, queryTreeList);
647
648         /*
649          * test 1:      if the last query is a utility invocation, then there had
650          * better not be a return value declared.
651          */
652         if (parse->commandType == CMD_UTILITY)
653         {
654                 if (rettype == InvalidOid)
655                         return;
656                 else
657                         elog(ERROR, "return type mismatch in function decl: final query is a catalog utility");
658         }
659
660         /* okay, it's an ordinary query */
661         tlist = parse->targetList;
662         rt = parse->rtable;
663         cmd = parse->commandType;
664
665         /*
666          * test 2:      if the function is declared to return no value, then the
667          * final query had better not be a retrieve.
668          */
669         if (rettype == InvalidOid)
670         {
671                 if (cmd == CMD_SELECT)
672                         elog(ERROR,
673                                  "function declared with no return type, but final query is a retrieve");
674                 else
675                         return;
676         }
677
678         /* by here, the function is declared to return some type */
679         if ((typ = typeidType(rettype)) == NULL)
680                 elog(ERROR, "can't find return type %u for function\n", rettype);
681
682         /*
683          * test 3:      if the function is declared to return a value, then the
684          * final query had better be a retrieve.
685          */
686         if (cmd != CMD_SELECT)
687                 elog(ERROR, "function declared to return type %s, but final query is not a retrieve", typeTypeName(typ));
688
689         /*
690          * test 4:      for base type returns, the target list should have exactly
691          * one entry, and its type should agree with what the user declared.
692          */
693
694         if (typeTypeRelid(typ) == InvalidOid)
695         {
696                 if (ExecTargetListLength(tlist) > 1)
697                         elog(ERROR, "function declared to return %s returns multiple values in final retrieve", typeTypeName(typ));
698
699                 resnode = (Resdom *) ((TargetEntry *) lfirst(tlist))->resdom;
700                 if (resnode->restype != rettype)
701                         elog(ERROR, "return type mismatch in function: declared to return %s, returns %s", typeTypeName(typ), typeidTypeName(resnode->restype));
702
703                 /* by here, base return types match */
704                 return;
705         }
706
707         /*
708          * If the target list is of length 1, and the type of the varnode in
709          * the target list is the same as the declared return type, this is
710          * okay.  This can happen, for example, where the body of the function
711          * is 'retrieve (x = func2())', where func2 has the same return type
712          * as the function that's calling it.
713          */
714         if (ExecTargetListLength(tlist) == 1)
715         {
716                 resnode = (Resdom *) ((TargetEntry *) lfirst(tlist))->resdom;
717                 if (resnode->restype == rettype)
718                         return;
719         }
720
721         /*
722          * By here, the procedure returns a (set of) tuples.  This part of the
723          * typechecking is a hack.      We look up the relation that is the
724          * declared return type, and be sure that attributes 1 .. n in the
725          * target list match the declared types.
726          */
727         reln = heap_open(typeTypeRelid(typ), AccessShareLock);
728         relid = reln->rd_id;
729         relnatts = reln->rd_rel->relnatts;
730
731         if (ExecTargetListLength(tlist) != relnatts)
732                 elog(ERROR, "function declared to return type %s does not retrieve (%s.*)", typeTypeName(typ), typeTypeName(typ));
733
734         /* expect attributes 1 .. n in order */
735         for (i = 1; i <= relnatts; i++)
736         {
737                 TargetEntry *tle = lfirst(tlist);
738                 Node       *thenode = tle->expr;
739                 Oid                     tletype = exprType(thenode);
740
741                 if (tletype != reln->rd_att->attrs[i - 1]->atttypid)
742                         elog(ERROR, "function declared to return type %s does not retrieve (%s.all)", typeTypeName(typ), typeTypeName(typ));
743                 tlist = lnext(tlist);
744         }
745
746         heap_close(reln, AccessShareLock);
747 }