diff options
Diffstat (limited to 'src/backend/executor')
-rw-r--r-- | src/backend/executor/execJunk.c | 195 | ||||
-rw-r--r-- | src/backend/executor/execMain.c | 8 | ||||
-rw-r--r-- | src/backend/executor/functions.c | 310 | ||||
-rw-r--r-- | src/backend/executor/nodeAppend.c | 24 |
4 files changed, 424 insertions, 113 deletions
diff --git a/src/backend/executor/execJunk.c b/src/backend/executor/execJunk.c index c797c343d35..bfa4ad9d7fd 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,97 +50,128 @@ *------------------------------------------------------------------------- */ -/*------------------------------------------------------------------------- +/* * 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; + int cleanLength; + AttrNumber *cleanMap; ListCell *t; - TargetEntry *tle; - Resdom *resdom, - *cleanResdom; - bool resjunk; AttrNumber cleanResno; - AttrNumber *cleanMap; - Expr *expr; /* - * 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; + cleanTupType = ExecCleanTypeFromTL(targetList, hasoid); - foreach(t, targetList) + /* + * 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. (Zero indicates a NULL output attribute, but we + * do not use that feature in this routine.) + */ + cleanLength = cleanTupType->natts; + if (cleanLength > 0) { - TargetEntry *rtarget = lfirst(t); - - resdom = rtarget->resdom; - expr = rtarget->expr; - resjunk = resdom->resjunk; - if (!resjunk) + cleanMap = (AttrNumber *) palloc(cleanLength * sizeof(AttrNumber)); + cleanResno = 1; + foreach(t, targetList) { - /* - * 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); + TargetEntry *tle = lfirst(t); + Resdom *resdom = tle->resdom; + + if (!resdom->resjunk) + { + cleanMap[cleanResno - 1] = resdom->resno; + cleanResno++; + } } } + else + cleanMap = NULL; /* - * Now calculate the tuple type for the cleaned tuple (we were already - * given the type for the original targetlist). + * Finally create and initialize the JunkFilter struct. */ - cleanTupType = ExecTypeFromTL(cleanTargetList, tupType->tdhasoid); + junkfilter = makeNode(JunkFilter); - len = ExecTargetListLength(targetList); - cleanLength = ExecTargetListLength(cleanTargetList); + junkfilter->jf_targetList = targetList; + junkfilter->jf_cleanTupType = cleanTupType; + junkfilter->jf_cleanMap = cleanMap; + junkfilter->jf_resultSlot = slot; + + if (slot) + ExecSetSlotDescriptor(slot, cleanTupType, false); + + 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; /* - * Now calculate the "map" between the original tuple's attributes and + * 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. 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 *) palloc(cleanLength * sizeof(AttrNumber)); - cleanResno = 1; - foreach(t, targetList) + cleanMap = (AttrNumber *) palloc0(cleanLength * sizeof(AttrNumber)); + t = list_head(targetList); + for (i = 0; i < cleanLength; i++) { - TargetEntry *tle = lfirst(t); - - resdom = tle->resdom; - resjunk = resdom->resjunk; - if (!resjunk) + if (cleanTupType->attrs[i]->attisdropped) + continue; /* map entry is already zero */ + for (;;) { - cleanMap[cleanResno - 1] = resdom->resno; - cleanResno++; + TargetEntry *tle = lfirst(t); + Resdom *resdom = tle->resdom; + + t = lnext(t); + if (!resdom->resjunk) + { + cleanMap[i] = resdom->resno; + break; + } } } } @@ -153,10 +184,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 +194,13 @@ ExecInitJunkFilter(List *targetList, TupleDesc tupType, 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 ea9dce019b1..d000eb79627 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 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; +} diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c index f9e0463e969..8c939043fe0 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); } |