*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#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"
}
-/*
- * 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
proc->proargtypes,
proc->pronargs);
(void) check_sql_fn_retval(proc->prorettype, functyptype,
- querytree_list);
+ querytree_list, NULL);
}
else
querytree_list = pg_parse_query(prosrc);
/*-------------------------------------------------------------------------
*
- * junk.c
+ * execJunk.c
* Junk attribute support stuff....
*
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
*-------------------------------------------------------------------------
*/
-/*-------------------------------------------------------------------------
+/*
* 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;
+ }
}
}
}
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;
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,
* 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.
* 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;
/*
* 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));
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
{
}
/*
- * 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];
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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++;
}
JunkFilter *j;
j = ExecInitJunkFilter(planstate->plan->targetlist,
- tupType,
+ tupType->tdhasoid,
ExecAllocTableSlot(estate->es_tupleTable));
estate->es_junkFilter = j;
if (estate->es_result_relation_info)
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#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"
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;
* 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,
/*
* 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
else
{
/* function is declared to return RECORD */
+ tupDesc = fcache->junkFilter->jf_cleanTupType;
if (tupDesc->tdtypeid == RECORDOID &&
tupDesc->tdtypmod < 0)
assign_record_type_typmod(tupDesc);
dtuptypmod = tupDesc->tdtypmod;
}
- HeapTupleHeaderSetDatumLength(dtup, tup->t_len);
+ HeapTupleHeaderSetDatumLength(dtup, t_len);
HeapTupleHeaderSetTypeId(dtup, dtuptype);
HeapTupleHeaderSetTypMod(dtup, dtuptypmod);
* 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)
/* 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;
+}
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
}
/*
- * 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;
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
{
/*
* 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);
}
*
*
* 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
#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"
*/
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
* 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
#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
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 */
* 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 $
*
*-------------------------------------------------------------------------
*/
/*
* 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);
* 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 $
*
*-------------------------------------------------------------------------
*/
#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 */
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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
* 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
{
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;