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