aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/executor')
-rw-r--r--src/backend/executor/execJunk.c195
-rw-r--r--src/backend/executor/execMain.c8
-rw-r--r--src/backend/executor/functions.c310
-rw-r--r--src/backend/executor/nodeAppend.c24
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);
}