OSDN Git Service

Fix problems with SQL functions returning rowtypes that have dropped
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 7 Oct 2004 18:38:51 +0000 (18:38 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 7 Oct 2004 18:38:51 +0000 (18:38 +0000)
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.

src/backend/catalog/pg_proc.c
src/backend/executor/execJunk.c
src/backend/executor/execMain.c
src/backend/executor/functions.c
src/backend/executor/nodeAppend.c
src/backend/optimizer/util/clauses.c
src/include/catalog/pg_proc.h
src/include/executor/executor.h
src/include/executor/functions.h
src/include/nodes/execnodes.h

index 1b658c9..8fb4250 100644 (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 $
  *
  *-------------------------------------------------------------------------
  */
 #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);
index c797c34..bfa4ad9 100644 (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 $
  *
  *-------------------------------------------------------------------------
  */
  *-------------------------------------------------------------------------
  */
 
-/*-------------------------------------------------------------------------
+/*
  * 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];
index ea9dce0..d000eb7 100644 (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)
index 1db5a43..d1683a9 100644 (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 $
  *
  *-------------------------------------------------------------------------
  */
 #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;
+}
index f9e0463..8c93904 100644 (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);
        }
index 1f848cd..b6ac778 100644 (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
index 8a52503..b79909a 100644 (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 */
index 7f894f2..21d8335 100644 (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);
index 5701d62..5c20f47 100644 (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 $
  *
  *-------------------------------------------------------------------------
  */
 #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 */
index b9782e3..0717695 100644 (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;