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 1b658c9ad2693b4d5e24733d2b99dbb2cd48e513..8fb4250c748ea351dd95bb8bc2c0fe1d79670659 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 c797c343d356ad1ea7f72970fd75577210e5d030..bfa4ad9d7fd791e8b77ef3d89c176c9904bd44b8 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 ea9dce019b1a3c527d1f44c6bd9e2bbbb84c1773..d000eb7962723b77d81dcbe4e8a77075cc5e6e93 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 1db5a4339ffe7f5879a57ff42ff034402f6cae27..d1683a91cb4961248a7b4968ea2c4de68fcc3444 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 f9e0463e969026aa6b6c61ef39a78579b7cdfc2d..8c939043fe068047daa42a80fc333ecee5647431 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 1f848cd9bdb6d3e0216a23f415efa6d77cb01296..b6ac7786aa7a96d7a95912271cf1cbd9418a7094 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 8a525035fc0b35ee1e7d4c6df78808de1ff41e3d..b79909ad8c0897471f84afde9601be48f1047c6e 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 7f894f26d8b1bd01e7e978e8c46882681ea38881..21d8335cfb1784667dc4e809b172eddf44a39b4e 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 5701d62056e3665521d6bb48cdf6f7a6664c9e38..5c20f47b101f5bc06fc61b4ceafab912ebde6e42 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 b9782e3b6c1cf01b69f3d84a61630ea3b8065406..07176952032d38568ad3c7714660c80d99cc6e15 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;