diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 1b658c9ad2..8fb4250c74 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.119 2004/08/29 05:06:41 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.120 2004/10/07 18:38:48 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -18,14 +18,11 @@ #include "catalog/catname.h" #include "catalog/dependency.h" #include "catalog/indexing.h" -#include "catalog/pg_language.h" #include "catalog/pg_proc.h" -#include "executor/executor.h" -#include "fmgr.h" +#include "catalog/pg_type.h" +#include "executor/functions.h" #include "miscadmin.h" #include "mb/pg_wchar.h" -#include "parser/parse_coerce.h" -#include "parser/parse_expr.h" #include "parser/parse_type.h" #include "tcop/pquery.h" #include "tcop/tcopprot.h" @@ -350,242 +347,6 @@ create_parameternames_array(int parameterCount, const char *parameterNames[]) } -/* - * 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. - */ -bool -check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList) -{ - 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 */ - - /* 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."))); - - 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. - */ - 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; -} - - /* * Validator for internal functions @@ -776,7 +537,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS) proc->proargtypes, proc->pronargs); (void) check_sql_fn_retval(proc->prorettype, functyptype, - querytree_list); + querytree_list, NULL); } else querytree_list = pg_parse_query(prosrc); diff --git a/src/backend/executor/execJunk.c b/src/backend/executor/execJunk.c index c797c343d3..bfa4ad9d7f 100644 --- a/src/backend/executor/execJunk.c +++ b/src/backend/executor/execJunk.c @@ -1,6 +1,6 @@ /*------------------------------------------------------------------------- * - * junk.c + * execJunk.c * Junk attribute support stuff.... * * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execJunk.c,v 1.43 2004/08/29 05:06:42 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execJunk.c,v 1.44 2004/10/07 18:38:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -50,83 +50,42 @@ *------------------------------------------------------------------------- */ -/*------------------------------------------------------------------------- +/* * ExecInitJunkFilter * * Initialize the Junk filter. * - * The initial targetlist and associated tuple descriptor are passed in. + * The source targetlist is passed in. The output tuple descriptor is + * built from the non-junk tlist entries, plus the passed specification + * of whether to include room for an OID or not. * An optional resultSlot can be passed as well. - *------------------------------------------------------------------------- */ JunkFilter * -ExecInitJunkFilter(List *targetList, TupleDesc tupType, - TupleTableSlot *slot) +ExecInitJunkFilter(List *targetList, bool hasoid, TupleTableSlot *slot) { JunkFilter *junkfilter; - List *cleanTargetList; - int len, - cleanLength; TupleDesc cleanTupType; - ListCell *t; - TargetEntry *tle; - Resdom *resdom, - *cleanResdom; - bool resjunk; - AttrNumber cleanResno; + int cleanLength; AttrNumber *cleanMap; - Expr *expr; + ListCell *t; + AttrNumber cleanResno; /* - * First find the "clean" target list, i.e. all the entries in the - * original target list which have a false 'resjunk' NOTE: make copy - * of the Resdom nodes, because we have to change the 'resno's... + * Compute the tuple descriptor for the cleaned tuple. */ - cleanTargetList = NIL; - cleanResno = 1; - - foreach(t, targetList) - { - TargetEntry *rtarget = lfirst(t); - - resdom = rtarget->resdom; - expr = rtarget->expr; - resjunk = resdom->resjunk; - if (!resjunk) - { - /* - * make a copy of the resdom node, changing its resno. - */ - cleanResdom = (Resdom *) copyObject(resdom); - cleanResdom->resno = cleanResno; - cleanResno++; - - /* - * create a new target list entry - */ - tle = makeTargetEntry(cleanResdom, expr); - cleanTargetList = lappend(cleanTargetList, tle); - } - } + cleanTupType = ExecCleanTypeFromTL(targetList, hasoid); /* - * Now calculate the tuple type for the cleaned tuple (we were already - * given the type for the original targetlist). - */ - cleanTupType = ExecTypeFromTL(cleanTargetList, tupType->tdhasoid); - - len = ExecTargetListLength(targetList); - cleanLength = ExecTargetListLength(cleanTargetList); - - /* - * Now calculate the "map" between the original tuple's attributes and + * Now calculate the mapping between the original tuple's attributes and * the "clean" tuple's attributes. * * The "map" is an array of "cleanLength" attribute numbers, i.e. one * entry for every attribute of the "clean" tuple. The value of this * entry is the attribute number of the corresponding attribute of the - * "original" tuple. + * "original" tuple. (Zero indicates a NULL output attribute, but we + * do not use that feature in this routine.) */ + cleanLength = cleanTupType->natts; if (cleanLength > 0) { cleanMap = (AttrNumber *) palloc(cleanLength * sizeof(AttrNumber)); @@ -134,10 +93,9 @@ ExecInitJunkFilter(List *targetList, TupleDesc tupType, foreach(t, targetList) { TargetEntry *tle = lfirst(t); + Resdom *resdom = tle->resdom; - resdom = tle->resdom; - resjunk = resdom->resjunk; - if (!resjunk) + if (!resdom->resjunk) { cleanMap[cleanResno - 1] = resdom->resno; cleanResno++; @@ -153,10 +111,6 @@ ExecInitJunkFilter(List *targetList, TupleDesc tupType, junkfilter = makeNode(JunkFilter); junkfilter->jf_targetList = targetList; - junkfilter->jf_length = len; - junkfilter->jf_tupType = tupType; - junkfilter->jf_cleanTargetList = cleanTargetList; - junkfilter->jf_cleanLength = cleanLength; junkfilter->jf_cleanTupType = cleanTupType; junkfilter->jf_cleanMap = cleanMap; junkfilter->jf_resultSlot = slot; @@ -167,14 +121,86 @@ ExecInitJunkFilter(List *targetList, TupleDesc tupType, return junkfilter; } -/*------------------------------------------------------------------------- +/* + * ExecInitJunkFilterConversion + * + * Initialize a JunkFilter for rowtype conversions. + * + * Here, we are given the target "clean" tuple descriptor rather than + * inferring it from the targetlist. The target descriptor can contain + * deleted columns. It is assumed that the caller has checked that the + * non-deleted columns match up with the non-junk columns of the targetlist. + */ +JunkFilter * +ExecInitJunkFilterConversion(List *targetList, + TupleDesc cleanTupType, + TupleTableSlot *slot) +{ + JunkFilter *junkfilter; + int cleanLength; + AttrNumber *cleanMap; + ListCell *t; + int i; + + /* + * Calculate the mapping between the original tuple's attributes and + * the "clean" tuple's attributes. + * + * The "map" is an array of "cleanLength" attribute numbers, i.e. one + * entry for every attribute of the "clean" tuple. The value of this + * entry is the attribute number of the corresponding attribute of the + * "original" tuple. We store zero for any deleted attributes, marking + * that a NULL is needed in the output tuple. + */ + cleanLength = cleanTupType->natts; + if (cleanLength > 0) + { + cleanMap = (AttrNumber *) palloc0(cleanLength * sizeof(AttrNumber)); + t = list_head(targetList); + for (i = 0; i < cleanLength; i++) + { + if (cleanTupType->attrs[i]->attisdropped) + continue; /* map entry is already zero */ + for (;;) + { + TargetEntry *tle = lfirst(t); + Resdom *resdom = tle->resdom; + + t = lnext(t); + if (!resdom->resjunk) + { + cleanMap[i] = resdom->resno; + break; + } + } + } + } + else + cleanMap = NULL; + + /* + * Finally create and initialize the JunkFilter struct. + */ + junkfilter = makeNode(JunkFilter); + + junkfilter->jf_targetList = targetList; + junkfilter->jf_cleanTupType = cleanTupType; + junkfilter->jf_cleanMap = cleanMap; + junkfilter->jf_resultSlot = slot; + + if (slot) + ExecSetSlotDescriptor(slot, cleanTupType, false); + + return junkfilter; +} + +/* * ExecGetJunkAttribute * * Given a tuple (slot), the junk filter and a junk attribute's name, * extract & return the value and isNull flag of this attribute. * * It returns false iff no junk attribute with such name was found. - *------------------------------------------------------------------------- */ bool ExecGetJunkAttribute(JunkFilter *junkfilter, @@ -220,14 +246,14 @@ ExecGetJunkAttribute(JunkFilter *junkfilter, * Now extract the attribute value from the tuple. */ tuple = slot->val; - tupType = junkfilter->jf_tupType; + tupType = slot->ttc_tupleDescriptor; *value = heap_getattr(tuple, resno, tupType, isNull); return true; } -/*------------------------------------------------------------------------- +/* * ExecRemoveJunk * * Construct and return a tuple with all the junk attributes removed. @@ -235,35 +261,37 @@ ExecGetJunkAttribute(JunkFilter *junkfilter, * Note: for historical reasons, this does not store the constructed * tuple into the junkfilter's resultSlot. The caller should do that * if it wants to. - *------------------------------------------------------------------------- */ HeapTuple ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot) { +#define PREALLOC_SIZE 64 HeapTuple tuple; HeapTuple cleanTuple; AttrNumber *cleanMap; TupleDesc cleanTupType; TupleDesc tupType; int cleanLength; + int oldLength; int i; Datum *values; char *nulls; Datum *old_values; char *old_nulls; - Datum values_array[64]; - Datum old_values_array[64]; - char nulls_array[64]; - char old_nulls_array[64]; + Datum values_array[PREALLOC_SIZE]; + Datum old_values_array[PREALLOC_SIZE]; + char nulls_array[PREALLOC_SIZE]; + char old_nulls_array[PREALLOC_SIZE]; /* * get info from the slot and the junk filter */ tuple = slot->val; + tupType = slot->ttc_tupleDescriptor; + oldLength = tupType->natts + 1; /* +1 for NULL */ - tupType = junkfilter->jf_tupType; cleanTupType = junkfilter->jf_cleanTupType; - cleanLength = junkfilter->jf_cleanLength; + cleanLength = cleanTupType->natts; cleanMap = junkfilter->jf_cleanMap; /* @@ -273,12 +301,8 @@ ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot) * Note: we use memory on the stack to optimize things when we are * dealing with a small number of attributes. for large tuples we just * use palloc. - * - * Note: we could use just one set of arrays if we were willing to assume - * that the resno mapping is monotonic... I think it is, but won't - * take the risk of breaking things right now. */ - if (cleanLength > 64) + if (cleanLength > PREALLOC_SIZE) { values = (Datum *) palloc(cleanLength * sizeof(Datum)); nulls = (char *) palloc(cleanLength * sizeof(char)); @@ -288,10 +312,10 @@ ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot) values = values_array; nulls = nulls_array; } - if (tupType->natts > 64) + if (oldLength > PREALLOC_SIZE) { - old_values = (Datum *) palloc(tupType->natts * sizeof(Datum)); - old_nulls = (char *) palloc(tupType->natts * sizeof(char)); + old_values = (Datum *) palloc(oldLength * sizeof(Datum)); + old_nulls = (char *) palloc(oldLength * sizeof(char)); } else { @@ -300,16 +324,21 @@ ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot) } /* - * Extract all the values of the old tuple. + * Extract all the values of the old tuple, offsetting the arrays + * so that old_values[0] is NULL and old_values[1] is the first + * source attribute; this exactly matches the numbering convention + * in cleanMap. */ - heap_deformtuple(tuple, tupType, old_values, old_nulls); + heap_deformtuple(tuple, tupType, old_values + 1, old_nulls + 1); + old_values[0] = (Datum) 0; + old_nulls[0] = 'n'; /* * Transpose into proper fields of the new tuple. */ for (i = 0; i < cleanLength; i++) { - int j = cleanMap[i] - 1; + int j = cleanMap[i]; values[i] = old_values[j]; nulls[i] = old_nulls[j]; diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index ea9dce019b..d000eb7962 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -26,7 +26,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.238 2004/09/13 20:06:46 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.239 2004/10/07 18:38:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -684,8 +684,8 @@ InitPlan(QueryDesc *queryDesc, bool explainOnly) JunkFilter *j; j = ExecInitJunkFilter(subplan->plan->targetlist, - ExecGetResultType(subplan), - ExecAllocTableSlot(estate->es_tupleTable)); + resultRelInfo->ri_RelationDesc->rd_att->tdhasoid, + ExecAllocTableSlot(estate->es_tupleTable)); resultRelInfo->ri_junkFilter = j; resultRelInfo++; } @@ -703,7 +703,7 @@ InitPlan(QueryDesc *queryDesc, bool explainOnly) JunkFilter *j; j = ExecInitJunkFilter(planstate->plan->targetlist, - tupType, + tupType->tdhasoid, ExecAllocTableSlot(estate->es_tupleTable)); estate->es_junkFilter = j; if (estate->es_result_relation_info) diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 1db5a4339f..d1683a91cb 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; +} diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c index f9e0463e96..8c939043fe 100644 --- a/src/backend/executor/nodeAppend.c +++ b/src/backend/executor/nodeAppend.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/nodeAppend.c,v 1.60 2004/09/24 01:36:30 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/executor/nodeAppend.c,v 1.61 2004/10/07 18:38:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -220,7 +220,10 @@ ExecInitAppend(Append *node, EState *estate) } /* - * initialize tuple type + * Initialize tuple type. (Note: in an inherited UPDATE situation, + * the tuple type computed here corresponds to the parent table, which + * is really a lie since tuples returned from child subplans will not + * all look the same.) */ ExecAssignResultTypeFromTL(&appendstate->ps); appendstate->ps.ps_ProjInfo = NULL; @@ -282,13 +285,12 @@ ExecAppend(AppendState *node) if (!TupIsNull(result)) { /* - * if the subplan gave us something then place a copy of whatever - * we get into our result slot and return it. - * - * Note we rely on the subplan to retain ownership of the tuple for - * as long as we need it --- we don't copy it. + * if the subplan gave us something then return it as-is. We do + * NOT make use of the result slot that was set up in ExecInitAppend, + * first because there's no reason to and second because it may have + * the wrong tuple descriptor in inherited-UPDATE cases. */ - return ExecStoreTuple(result->val, result_slot, InvalidBuffer, false); + return result; } else { @@ -303,13 +305,11 @@ ExecAppend(AppendState *node) /* * return something from next node or an empty slot if all of our - * subplans have been exhausted. + * subplans have been exhausted. The empty slot is the one set up + * by ExecInitAppend. */ if (exec_append_initialize_next(node)) - { - ExecSetSlotDescriptorIsNew(result_slot, true); return ExecAppend(node); - } else return ExecClearTuple(result_slot); } diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 1f848cd9bd..b6ac7786aa 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.181 2004/10/02 22:39:48 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.182 2004/10/07 18:38:49 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -23,6 +23,7 @@ #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" +#include "executor/functions.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "optimizer/clauses.h" @@ -2116,7 +2117,7 @@ inline_function(Oid funcid, Oid result_type, List *args, */ if (polymorphic) (void) check_sql_fn_retval(result_type, get_typtype(result_type), - querytree_list); + querytree_list, NULL); /* * Additional validity checks on the expression. It mustn't return a diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 8a525035fc..b79909ad8c 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.347 2004/10/04 22:49:55 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.348 2004/10/07 18:38:50 tgl Exp $ * * NOTES * The script catalog/genbki.sh reads this file and generates .bki @@ -23,8 +23,6 @@ #ifndef PG_PROC_H #define PG_PROC_H -#include "nodes/pg_list.h" - /* ---------------- * postgres.h contains the system type definitions and the * CATALOG(), BOOTSTRAP and DATA() sugar words so this file @@ -3640,9 +3638,6 @@ extern Oid ProcedureCreate(const char *procedureName, const Oid *parameterTypes, const char *parameterNames[]); -extern bool check_sql_fn_retval(Oid rettype, char fn_typtype, - List *queryTreeList); - extern bool function_parse_error_transpose(const char *prosrc); #endif /* PG_PROC_H */ diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 7f894f26d8..21d8335cfb 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.113 2004/09/13 20:07:52 tgl Exp $ + * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.114 2004/10/07 18:38:51 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -85,8 +85,11 @@ extern TupleHashEntry LookupTupleHashEntry(TupleHashTable hashtable, /* * prototypes from functions in execJunk.c */ -extern JunkFilter *ExecInitJunkFilter(List *targetList, TupleDesc tupType, +extern JunkFilter *ExecInitJunkFilter(List *targetList, bool hasoid, TupleTableSlot *slot); +extern JunkFilter *ExecInitJunkFilterConversion(List *targetList, + TupleDesc cleanTupType, + TupleTableSlot *slot); extern bool ExecGetJunkAttribute(JunkFilter *junkfilter, TupleTableSlot *slot, char *attrName, Datum *value, bool *isNull); extern HeapTuple ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot); diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h index 5701d62056..5c20f47b10 100644 --- a/src/include/executor/functions.h +++ b/src/include/executor/functions.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/executor/functions.h,v 1.22 2004/08/29 04:13:06 momjian Exp $ + * $PostgreSQL: pgsql/src/include/executor/functions.h,v 1.23 2004/10/07 18:38:51 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -15,7 +15,13 @@ #define FUNCTIONS_H #include "fmgr.h" +#include "nodes/execnodes.h" + extern Datum fmgr_sql(PG_FUNCTION_ARGS); +extern bool check_sql_fn_retval(Oid rettype, char fn_typtype, + List *queryTreeList, + JunkFilter **junkFilter); + #endif /* FUNCTIONS_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index b9782e3b6c..0717695203 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.119 2004/08/29 05:06:57 momjian Exp $ + * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.120 2004/10/07 18:38:51 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -209,7 +209,7 @@ typedef struct ProjectionInfo * This class is used to store information regarding junk attributes. * A junk attribute is an attribute in a tuple that is needed only for * storing intermediate information in the executor, and does not belong - * in emitted tuples. For example, when we do an UPDATE query, + * in emitted tuples. For example, when we do an UPDATE query, * the planner adds a "junk" entry to the targetlist so that the tuples * returned to ExecutePlan() contain an extra attribute: the ctid of * the tuple to be updated. This is needed to do the update, but we @@ -218,12 +218,7 @@ typedef struct ProjectionInfo * real output tuple. * * targetList: the original target list (including junk attributes). - * length: the length of 'targetList'. - * tupType: the tuple descriptor for the "original" tuple - * (including the junk attributes). - * cleanTargetList: the "clean" target list (junk attributes removed). - * cleanLength: the length of 'cleanTargetList' - * cleanTupType: the tuple descriptor of the "clean" tuple (with + * cleanTupType: the tuple descriptor for the "clean" tuple (with * junk attributes removed). * cleanMap: A map with the correspondence between the non-junk * attribute numbers of the "original" tuple and the @@ -235,10 +230,6 @@ typedef struct JunkFilter { NodeTag type; List *jf_targetList; - int jf_length; - TupleDesc jf_tupType; - List *jf_cleanTargetList; - int jf_cleanLength; TupleDesc jf_cleanTupType; AttrNumber *jf_cleanMap; TupleTableSlot *jf_resultSlot;