diff options
Diffstat (limited to 'src/backend/catalog/pg_proc.c')
-rw-r--r-- | src/backend/catalog/pg_proc.c | 130 |
1 files changed, 127 insertions, 3 deletions
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index ab6ce91e46d..98d0cdca0c4 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.47 2000/08/21 17:22:35 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.48 2000/08/21 20:55:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,8 +20,9 @@ #include "catalog/pg_language.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" +#include "executor/executor.h" #include "miscadmin.h" -#include "optimizer/planner.h" +#include "parser/parse_expr.h" #include "parser/parse_type.h" #include "tcop/tcopprot.h" #include "utils/builtins.h" @@ -30,6 +31,9 @@ #include "utils/syscache.h" +static void checkretval(Oid rettype, List *queryTreeList); + + /* ---------------------------------------------------------------- * ProcedureCreate * ---------------------------------------------------------------- @@ -220,7 +224,7 @@ ProcedureCreate(char *procedureName, { querytree_list = pg_parse_and_rewrite(prosrc, typev, parameterCount); /* typecheck return value */ - pg_checkretval(typeObjectId, querytree_list); + checkretval(typeObjectId, querytree_list); } /* @@ -334,3 +338,123 @@ ProcedureCreate(char *procedureName, heap_freetuple(tup); return retval; } + +/* + * checkretval() -- check return value of a list of sql parse trees. + * + * The return value of a sql function is the value returned by + * the final query in the function. We do some ad-hoc define-time + * type checking here to be sure that the user is returning the + * type he claims. + */ +static void +checkretval(Oid rettype, List *queryTreeList) +{ + Query *parse; + int cmd; + List *tlist; + List *tlistitem; + int tlistlen; + Type typ; + Resdom *resnode; + Relation reln; + Oid relid; + int relnatts; + int i; + + /* find the final query */ + parse = (Query *) nth(length(queryTreeList) - 1, queryTreeList); + + cmd = parse->commandType; + tlist = parse->targetList; + + /* + * The last query must be a SELECT if and only if there is a return type. + */ + if (rettype == InvalidOid) + { + if (cmd == CMD_SELECT) + elog(ERROR, "function declared with no return type, but final query is a SELECT"); + return; + } + + /* by here, the function is declared to return some type */ + if ((typ = typeidType(rettype)) == NULL) + elog(ERROR, "can't find return type %u for function", rettype); + + if (cmd != CMD_SELECT) + elog(ERROR, "function declared to return %s, but final query is not a SELECT", typeTypeName(typ)); + + /* + * Count the non-junk entries in the result targetlist. + */ + tlistlen = ExecCleanTargetListLength(tlist); + + /* + * For base-type returns, the target list should have exactly one entry, + * and its type should agree with what the user declared. + */ + if (typeTypeRelid(typ) == InvalidOid) + { + if (tlistlen != 1) + elog(ERROR, "function declared to return %s returns multiple columns in final SELECT", typeTypeName(typ)); + + resnode = (Resdom *) ((TargetEntry *) lfirst(tlist))->resdom; + if (resnode->restype != rettype) + elog(ERROR, "return type mismatch in function: declared to return %s, returns %s", typeTypeName(typ), typeidTypeName(resnode->restype)); + + return; + } + + /* + * If the target list is of length 1, and the type of the varnode in + * the target list is the same as the declared return type, this is + * okay. This can happen, for example, where the body of the function + * is 'SELECT (x = func2())', where func2 has the same return type + * as the function that's calling it. + */ + if (tlistlen == 1) + { + resnode = (Resdom *) ((TargetEntry *) lfirst(tlist))->resdom; + if (resnode->restype == rettype) + return; + } + + /* + * By here, the procedure returns a tuple or set of tuples. This part of + * the typechecking is a hack. We look up the relation that is the + * declared return type, and be sure that attributes 1 .. n in the target + * list match the declared types. + */ + reln = heap_open(typeTypeRelid(typ), AccessShareLock); + relid = reln->rd_id; + relnatts = reln->rd_rel->relnatts; + + if (tlistlen != relnatts) + elog(ERROR, "function declared to return %s does not SELECT the right number of columns (%d)", typeTypeName(typ), relnatts); + + /* expect attributes 1 .. n in order */ + i = 0; + foreach(tlistitem, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tlistitem); + Oid tletype; + + if (tle->resdom->resjunk) + continue; + tletype = exprType(tle->expr); + if (tletype != reln->rd_att->attrs[i]->atttypid) + elog(ERROR, "function declared to return %s returns %s instead of %s at column %d", + typeTypeName(typ), + typeidTypeName(tletype), + typeidTypeName(reln->rd_att->attrs[i]->atttypid), + i+1); + i++; + } + + /* this shouldn't happen, but let's just check... */ + if (i != relnatts) + elog(ERROR, "function declared to return %s does not SELECT the right number of columns (%d)", typeTypeName(typ), relnatts); + + heap_close(reln, AccessShareLock); +} |