diff options
Diffstat (limited to 'src/backend/executor/functions.c')
-rw-r--r-- | src/backend/executor/functions.c | 310 |
1 files changed, 296 insertions, 14 deletions
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 1db5a4339ff..d1683a91cb4 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.89 2004/09/13 20:06:46 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.90 2004/10/07 18:38:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -18,10 +18,11 @@ #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/trigger.h" -#include "executor/execdefs.h" #include "executor/executor.h" #include "executor/functions.h" -#include "tcop/pquery.h" +#include "parser/parse_coerce.h" +#include "parser/parse_expr.h" +#include "parser/parse_type.h" #include "tcop/tcopprot.h" #include "tcop/utility.h" #include "utils/builtins.h" @@ -69,6 +70,8 @@ typedef struct ParamListInfo paramLI; /* Param list representing current args */ + JunkFilter *junkFilter; /* used only if returnsTuple */ + /* head of linked list of execution_state records */ execution_state *func_state; } SQLFunctionCache; @@ -268,11 +271,16 @@ init_sql_fcache(FmgrInfo *finfo) * result, or just regurgitating a rowtype expression result. In the * latter case we clear returnsTuple because we need not act different * from the scalar result case. + * + * In the returnsTuple case, check_sql_fn_retval will also construct + * a JunkFilter we can use to coerce the returned rowtype to the desired + * form. */ if (haspolyarg || fcache->returnsTuple) fcache->returnsTuple = check_sql_fn_retval(rettype, get_typtype(rettype), - queryTree_list); + queryTree_list, + &fcache->junkFilter); /* Finally, plan the queries */ fcache->func_state = init_execution_state(queryTree_list, @@ -477,24 +485,40 @@ postquel_execute(execution_state *es, /* * Set up to return the function value. */ - tup = slot->val; - tupDesc = slot->ttc_tupleDescriptor; - if (fcache->returnsTuple) { /* - * We are returning the whole tuple, so copy it into current - * execution context and make sure it is a valid Datum. + * We are returning the whole tuple, so filter it and apply the + * proper labeling to make it a valid Datum. There are several + * reasons why we do this: * - * XXX do we need to remove junk attrs from the result tuple? - * Probably OK to leave them, as long as they are at the end. + * 1. To copy the tuple out of the child execution context and + * into our own context. + * + * 2. To remove any junk attributes present in the raw subselect + * result. (This is probably not absolutely necessary, but it + * seems like good policy.) + * + * 3. To insert dummy null columns if the declared result type + * has any attisdropped columns. */ + HeapTuple newtup; HeapTupleHeader dtup; + uint32 t_len; Oid dtuptype; int32 dtuptypmod; - dtup = (HeapTupleHeader) palloc(tup->t_len); - memcpy((char *) dtup, (char *) tup->t_data, tup->t_len); + newtup = ExecRemoveJunk(fcache->junkFilter, slot); + + /* + * Compress out the HeapTuple header data. We assume that + * heap_formtuple made the tuple with header and body in one + * palloc'd chunk. We want to return a pointer to the chunk + * start so that it will work if someone tries to free it. + */ + t_len = newtup->t_len; + dtup = (HeapTupleHeader) newtup; + memmove((char *) dtup, (char *) newtup->t_data, t_len); /* * Use the declared return type if it's not RECORD; else take @@ -510,6 +534,7 @@ postquel_execute(execution_state *es, else { /* function is declared to return RECORD */ + tupDesc = fcache->junkFilter->jf_cleanTupType; if (tupDesc->tdtypeid == RECORDOID && tupDesc->tdtypmod < 0) assign_record_type_typmod(tupDesc); @@ -517,7 +542,7 @@ postquel_execute(execution_state *es, dtuptypmod = tupDesc->tdtypmod; } - HeapTupleHeaderSetDatumLength(dtup, tup->t_len); + HeapTupleHeaderSetDatumLength(dtup, t_len); HeapTupleHeaderSetTypeId(dtup, dtuptype); HeapTupleHeaderSetTypMod(dtup, dtuptypmod); @@ -531,6 +556,9 @@ postquel_execute(execution_state *es, * column of the SELECT result, and then copy into current * execution context if needed. */ + tup = slot->val; + tupDesc = slot->ttc_tupleDescriptor; + value = heap_getattr(tup, 1, tupDesc, &(fcinfo->isnull)); if (!fcinfo->isnull) @@ -808,3 +836,257 @@ ShutdownSQLFunction(Datum arg) /* execUtils will deregister the callback... */ fcache->shutdown_reg = false; } + + +/* + * check_sql_fn_retval() -- 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 type checking here + * to be sure that the user is returning the type he claims. + * + * This is normally applied during function definition, but in the case + * of a function with polymorphic arguments, we instead apply it during + * function execution startup. The rettype is then the actual resolved + * output type of the function, rather than the declared type. (Therefore, + * we should never see ANYARRAY or ANYELEMENT as rettype.) + * + * The return value is true if the function returns the entire tuple result + * of its final SELECT, and false otherwise. Note that because we allow + * "SELECT rowtype_expression", this may be false even when the declared + * function return type is a rowtype. + * + * If junkFilter isn't NULL, then *junkFilter is set to a JunkFilter defined + * to convert the function's tuple result to the correct output tuple type. + * Whenever the result value is false (ie, the function isn't returning a + * tuple result), *junkFilter is set to NULL. + */ +bool +check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList, + JunkFilter **junkFilter) +{ + Query *parse; + int cmd; + List *tlist; + ListCell *tlistitem; + int tlistlen; + Oid typerelid; + Oid restype; + Relation reln; + int relnatts; /* physical number of columns in rel */ + int rellogcols; /* # of nondeleted columns in rel */ + int colindex; /* physical column index */ + + if (junkFilter) + *junkFilter = NULL; /* default result */ + + /* guard against empty function body; OK only if void return type */ + if (queryTreeList == NIL) + { + if (rettype != VOIDOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("return type mismatch in function declared to return %s", + format_type_be(rettype)), + errdetail("Function's final statement must be a SELECT."))); + return false; + } + + /* find the final query */ + parse = (Query *) lfirst(list_tail(queryTreeList)); + + cmd = parse->commandType; + tlist = parse->targetList; + + /* + * The last query must be a SELECT if and only if return type isn't + * VOID. + */ + if (rettype == VOIDOID) + { + if (cmd == CMD_SELECT) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("return type mismatch in function declared to return %s", + format_type_be(rettype)), + errdetail("Function's final statement must not be a SELECT."))); + return false; + } + + /* by here, the function is declared to return some type */ + if (cmd != CMD_SELECT) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("return type mismatch in function declared to return %s", + format_type_be(rettype)), + errdetail("Function's final statement must be a SELECT."))); + + /* + * Count the non-junk entries in the result targetlist. + */ + tlistlen = ExecCleanTargetListLength(tlist); + + typerelid = typeidTypeRelid(rettype); + + if (fn_typtype == 'b' || fn_typtype == 'd') + { + /* Shouldn't have a typerelid */ + Assert(typerelid == InvalidOid); + + /* + * For base-type returns, the target list should have exactly one + * entry, and its type should agree with what the user declared. + * (As of Postgres 7.2, we accept binary-compatible types too.) + */ + if (tlistlen != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("return type mismatch in function declared to return %s", + format_type_be(rettype)), + errdetail("Final SELECT must return exactly one column."))); + + restype = ((TargetEntry *) linitial(tlist))->resdom->restype; + if (!IsBinaryCoercible(restype, rettype)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("return type mismatch in function declared to return %s", + format_type_be(rettype)), + errdetail("Actual return type is %s.", + format_type_be(restype)))); + } + else if (fn_typtype == 'c') + { + /* Must have a typerelid */ + Assert(typerelid != InvalidOid); + + /* + * If the target list is of length 1, and the type of the varnode + * in the target list matches the declared return type, this is + * okay. This can happen, for example, where the body of the + * function is 'SELECT func2()', where func2 has the same return + * type as the function that's calling it. + */ + if (tlistlen == 1) + { + restype = ((TargetEntry *) linitial(tlist))->resdom->restype; + if (IsBinaryCoercible(restype, rettype)) + return false; /* NOT returning whole tuple */ + } + + /* + * Otherwise verify that the targetlist matches the return tuple + * type. This part of the typechecking is a hack. We look up the + * relation that is the declared return type, and scan the + * non-deleted attributes to ensure that they match the datatypes + * of the non-resjunk columns. + */ + reln = relation_open(typerelid, AccessShareLock); + relnatts = reln->rd_rel->relnatts; + rellogcols = 0; /* we'll count nondeleted cols as we go */ + colindex = 0; + + foreach(tlistitem, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tlistitem); + Form_pg_attribute attr; + Oid tletype; + Oid atttype; + + if (tle->resdom->resjunk) + continue; + + do + { + colindex++; + if (colindex > relnatts) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("return type mismatch in function declared to return %s", + format_type_be(rettype)), + errdetail("Final SELECT returns too many columns."))); + attr = reln->rd_att->attrs[colindex - 1]; + } while (attr->attisdropped); + rellogcols++; + + tletype = exprType((Node *) tle->expr); + atttype = attr->atttypid; + if (!IsBinaryCoercible(tletype, atttype)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("return type mismatch in function declared to return %s", + format_type_be(rettype)), + errdetail("Final SELECT returns %s instead of %s at column %d.", + format_type_be(tletype), + format_type_be(atttype), + rellogcols))); + } + + for (;;) + { + colindex++; + if (colindex > relnatts) + break; + if (!reln->rd_att->attrs[colindex - 1]->attisdropped) + rellogcols++; + } + + if (tlistlen != rellogcols) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("return type mismatch in function declared to return %s", + format_type_be(rettype)), + errdetail("Final SELECT returns too few columns."))); + + /* Set up junk filter if needed */ + if (junkFilter) + *junkFilter = ExecInitJunkFilterConversion(tlist, + CreateTupleDescCopy(reln->rd_att), + NULL); + + relation_close(reln, AccessShareLock); + + /* Report that we are returning entire tuple result */ + return true; + } + else if (rettype == RECORDOID) + { + /* + * If the target list is of length 1, and the type of the varnode + * in the target list matches the declared return type, this is + * okay. This can happen, for example, where the body of the + * function is 'SELECT func2()', where func2 has the same return + * type as the function that's calling it. + */ + if (tlistlen == 1) + { + restype = ((TargetEntry *) linitial(tlist))->resdom->restype; + if (IsBinaryCoercible(restype, rettype)) + return false; /* NOT returning whole tuple */ + } + + /* + * Otherwise assume we are returning the whole tuple. + * Crosschecking against what the caller expects will happen at + * runtime. + */ + if (junkFilter) + *junkFilter = ExecInitJunkFilter(tlist, false, NULL); + + return true; + } + else if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID) + { + /* This should already have been caught ... */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("cannot determine result data type"), + errdetail("A function returning \"anyarray\" or \"anyelement\" must have at least one argument of either type."))); + } + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("return type %s is not supported for SQL functions", + format_type_be(rettype)))); + + return false; +} |