/*-------------------------------------------------------------------------
 *
 * fcache.c--
 *    Code for the 'function cache' used in Oper and Func nodes....
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    /usr/local/devel/pglite/cvs/src/backend/utils/cache/fcache.c,v 1.7 1995/03/17 20:26:59 andrew Exp
 *
 *-------------------------------------------------------------------------
 */
#include "c.h"
#include "access/htup.h"
#include "utils/catcache.h"
#include "utils/syscache.h"
#include "catalog/pg_type.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_language.h"
#include "catalog/pg_class.h"
#include "parser/parsetree.h"		/* for getrelname() */
#include "utils/builtins.h"
#include "utils/fcache.h"
#include "utils/elog.h"
#include "utils/palloc.h"
#include "nodes/primnodes.h"
#include "nodes/execnodes.h"

static Oid GetDynamicFuncArgType(Var *arg, ExprContext *econtext);
static FunctionCachePtr init_fcache(Oid foid,
			     bool use_syscache,
			     List *argList,
			     ExprContext *econtext);

/*-----------------------------------------------------------------
 *
 * Initialize the 'FunctionCache' given the PG_PROC oid.
 *
 *
 * NOTE:  This function can be called when the system cache is being
 *        initialized.  Therefore, use_syscache should ONLY be true
 *        when the function return type is interesting (ie: set_fcache).
 *-----------------------------------------------------------------
 */
#define FuncArgTypeIsDynamic(arg) \
    (IsA(arg,Var) && ((Var*)arg)->varattno == InvalidAttrNumber)

static Oid
GetDynamicFuncArgType(Var *arg, ExprContext *econtext)
{
    char *relname;
    int rtid;
    HeapTuple tup;
    
    Assert(IsA(arg,Var));
    
    rtid = ((Var*)arg)->varno;
    relname = (char*)getrelname(rtid, econtext->ecxt_range_table);

    
    tup = SearchSysCacheTuple(TYPNAME, relname, NULL, NULL, NULL);
    if (!tup)
	elog(WARN, "Lookup failed on type tuple for class %s",
	     relname);
    
    return tup->t_oid;
}

static FunctionCachePtr
init_fcache(Oid foid,
	    bool use_syscache,
	    List *argList,
	    ExprContext *econtext)
{
    HeapTuple        procedureTuple;
    HeapTuple        typeTuple;
    Form_pg_proc     procedureStruct;
    TypeTupleForm     typeStruct;
    FunctionCachePtr retval;
    text	     *tmp;
    int              nargs;
    
    /* ----------------
     *   get the procedure tuple corresponding to the given
     *   functionOid.  If this fails, returnValue has been
     *   pre-initialized to "null" so we just return it.
     * ----------------
     */
    retval = (FunctionCachePtr) palloc(sizeof(FunctionCache));
    
    if (!use_syscache)
	elog(WARN, "what the ????, init the fcache without the catalogs?");
    
    procedureTuple = SearchSysCacheTuple(PROOID,
					 (char *) ObjectIdGetDatum(foid),
					 (char *) NULL, (char *) NULL,
					 (char *) NULL);
    
    if (!HeapTupleIsValid(procedureTuple))
	elog(WARN,
	     "init_fcache: %s %d",
	     "Cache lookup failed for procedure", foid);
    
    /* ----------------
     *   get the return type from the procedure tuple
     * ----------------
     */
    procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);
    
    /* ----------------
     *   get the type tuple corresponding to the return type
     *   If this fails, returnValue has been pre-initialized
     *   to "null" so we just return it.
     * ----------------
     */
    typeTuple = SearchSysCacheTuple(TYPOID,
				    (char *)
				    ObjectIdGetDatum(procedureStruct->prorettype),
				    (char *) NULL, (char *) NULL,
				    (char *) NULL);
    
    if (!HeapTupleIsValid(typeTuple))
	elog(WARN,
	     "init_fcache: %s %d",
	     "Cache lookup failed for type",
	     (procedureStruct)->prorettype);
    
    /* ----------------
     *   get the type length and by-value from the type tuple and
     *   save the information in our one element cache.
     * ----------------
     */
    typeStruct = (TypeTupleForm) GETSTRUCT(typeTuple);
    
    retval->typlen = (typeStruct)->typlen;
    if ((typeStruct)->typrelid == InvalidOid) {
	/* The return type is not a relation, so just use byval */
	retval->typbyval = (typeStruct)->typbyval ? true : false ;
    } else {
	/* This is a hack.  We assume here that any function returning
	 * a relation returns it by reference.  This needs to be
	 * fixed.
	 */
	retval->typbyval = false;
    }
    retval->foid = foid;
    retval->language = procedureStruct->prolang;
    retval->func_state = (char *)NULL;
    retval->setArg     = NULL;
    retval->hasSetArg  = false;
    retval->oneResult  = ! procedureStruct->proretset;
    retval->istrusted  = procedureStruct->proistrusted;
    
    /*
     * If we are returning exactly one result then we have to copy
     * tuples and by reference results because we have to end the execution
     * before we return the results.  When you do this everything allocated
     * by the executor (i.e. slots and tuples) is freed.
     */
    if ((retval->language == SQLlanguageId) &&
	(retval->oneResult) &&
	!(retval->typbyval))
	{
	    Form_pg_class relationStruct;
	    HeapTuple relationTuple;
	    TupleDesc td;
	    TupleTableSlot *slot;

	    slot = makeNode(TupleTableSlot);
	    slot->ttc_shouldFree = true;
	    slot->ttc_descIsNew = true;
	    slot->ttc_tupleDescriptor = (TupleDesc) NULL;
	    slot->ttc_buffer = InvalidBuffer;
	    slot->ttc_whichplan = -1;
	    retval->funcSlot = (Pointer)slot;

	    relationTuple = (HeapTuple)
		SearchSysCacheTuple(RELNAME,
				    (char *)&typeStruct->typname,
				    (char *)NULL,
				    (char *)NULL,
				    (char *)NULL);
	    
	    if (relationTuple)
		{
		    relationStruct = (Form_pg_class)GETSTRUCT(relationTuple);
		    td = CreateTemplateTupleDesc(relationStruct->relnatts);
		}
	    else
		td = CreateTemplateTupleDesc(1);
	    
	    ((TupleTableSlot*)retval->funcSlot)->ttc_tupleDescriptor = td;
	}
    else
	retval->funcSlot = (char *)NULL;
    
    nargs = procedureStruct->pronargs;
    retval->nargs = nargs;
    
    if (nargs > 0)
	{
	    Oid *argTypes;
	    
	    retval->nullVect = (bool *)palloc((retval->nargs)*sizeof(bool));
	    
	    if (retval->language == SQLlanguageId)
		{
		    int  i;
		    List *oneArg;
		    
		    retval->argOidVect =
			(Oid *)palloc(retval->nargs*sizeof(Oid));
		    argTypes = procedureStruct->proargtypes;
		    memmove(retval->argOidVect,
			    argTypes,
			    (retval->nargs)*sizeof(Oid));
		    
		    for (i=0, oneArg = lfirst(argList);
			 argList;
			 i++, argList = lnext(argList))
			{
			    if (FuncArgTypeIsDynamic(oneArg))
		retval->argOidVect[i] = GetDynamicFuncArgType((Var*)oneArg,
							      econtext);
			}
		}
	    else
		retval->argOidVect = (Oid *)NULL;
	}
    else
	{
	    retval->argOidVect = (Oid *)NULL;
	    retval->nullVect = (BoolPtr)NULL;
	}
    
    /*
     * XXX this is the first varlena in the struct.  If the order
     *     changes for some reason this will fail.
     */
    if (procedureStruct->prolang == SQLlanguageId)
	{
	    retval->src = textout(&(procedureStruct->prosrc));
	    retval->bin = (char *) NULL;
	}
    else 
	{
	    
	    /*
	     * I'm not sure that we even need to do this at all.
	     */
	    
	    /*
	     * We do for untrusted functions.
	     */
	    
	    if (procedureStruct->proistrusted)
		retval->bin = (char *) NULL;
	    else {
		tmp = (text *)
		    SearchSysCacheGetAttribute(PROOID,
					       Anum_pg_proc_probin,
					       (char *) ObjectIdGetDatum(foid),
					       (char *) NULL, (char *) NULL,
					       (char *) NULL);
		retval->bin = textout(tmp);
	    }
	    retval->src = (char *) NULL;
	}
    
    
    
    
    if (retval->language != SQLlanguageId)
	fmgr_info(foid, &(retval->func), &(retval->nargs));
    else
	retval->func = (func_ptr)NULL;
    
    
    return(retval);
}

void
setFcache(Node *node, Oid foid, List *argList, ExprContext *econtext)
{
    Func *fnode;
    Oper *onode;
    FunctionCachePtr fcache;
    
    fcache = init_fcache(foid, true, argList, econtext);
    
    if (IsA(node,Oper)) {
	onode = (Oper*) node;
	onode->op_fcache = fcache;
    }else if (IsA(node,Func)) {
	fnode = (Func*) node;
	fnode->func_fcache = fcache;
    }else {
	elog(WARN, "init_fcache: node must be Oper or Func!");
    }
}
