Fix problems with SQL functions returning rowtypes that have dropped

columns.  The returned tuple needs to have appropriate NULL columns
inserted so that it actually matches the declared rowtype.  It seemed
convenient to use a JunkFilter for this, so I made some cleanups and
simplifications in the JunkFilter code to allow it to support this
additional functionality.  (That in turn exposed a latent bug in
nodeAppend.c, which is that it was returning a tuple slot whose
descriptor didn't match its data.)  Also, move check_sql_fn_retval
out of pg_proc.c and into functions.c, where it seems to more naturally
belong.
This commit is contained in:
Tom Lane 2004-10-07 18:38:51 +00:00
parent 6d46ea25f2
commit a8487e15ed
10 changed files with 450 additions and 382 deletions

View File

@ -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);

View File

@ -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];

View File

@ -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)

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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

View File

@ -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 */

View File

@ -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);

View File

@ -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 */

View File

@ -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;