/*-------------------------------------------------------------------------
 *
 * fcache.c
 *	  Code for the 'function cache' used in Oper and Func nodes....
 *
 * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /home/projects/pgsql/cvsroot/pgsql/src/backend/utils/cache/fcache.c,v 1.30 2000/04/12 17:15:53 momjian Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "catalog/pg_language.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "parser/parsetree.h"
#include "utils/builtins.h"
#include "utils/fcache2.h"
#include "utils/syscache.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(TYPENAME,
							  PointerGetDatum(relname),
							  0, 0, 0);
	if (!tup)
		elog(ERROR, "Lookup failed on type tuple for class %s",
			 relname);

	return tup->t_data->t_oid;
}

static FunctionCachePtr
init_fcache(Oid foid,
			bool use_syscache,
			List *argList,
			ExprContext *econtext)
{
	HeapTuple	procedureTuple;
	HeapTuple	typeTuple;
	Form_pg_proc procedureStruct;
	Form_pg_type typeStruct;
	FunctionCachePtr retval;
	int			nargs;
	text	   *tmp;
	bool		isNull;

	/* ----------------
	 *	 get the procedure tuple corresponding to the given functionOid
	 * ----------------
	 */
	retval = (FunctionCachePtr) palloc(sizeof(FunctionCache));
	memset(retval, 0, sizeof(FunctionCache));

	if (!use_syscache)
		elog(ERROR, "what the ????, init the fcache without the catalogs?");

	procedureTuple = SearchSysCacheTuple(PROCOID,
										 ObjectIdGetDatum(foid),
										 0, 0, 0);

	if (!HeapTupleIsValid(procedureTuple))
		elog(ERROR, "init_fcache: Cache lookup failed for procedure %u",
			 foid);

	procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);

	/* ----------------
	 *	 get the return type from the procedure tuple
	 * ----------------
	 */
	typeTuple = SearchSysCacheTuple(TYPEOID,
						   ObjectIdGetDatum(procedureStruct->prorettype),
									0, 0, 0);

	if (!HeapTupleIsValid(typeTuple))
		elog(ERROR, "init_fcache: Cache lookup failed for type %u",
			 procedureStruct->prorettype);

	typeStruct = (Form_pg_type) GETSTRUCT(typeTuple);

	/* ----------------
	 *	 get the type length and by-value from the type tuple and
	 *	 save the information in our one element cache.
	 * ----------------
	 */
	retval->typlen = typeStruct->typlen;
	if (typeStruct->typrelid == InvalidOid)
	{
		/* The return type is not a relation, so just use byval */
		retval->typbyval = typeStruct->typbyval;
	}
	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,
								PointerGetDatum(&typeStruct->typname),
								0, 0, 0);

		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;
				 argList;
				 i++, argList = lnext(argList))
			{
				oneArg = lfirst(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;
	}

	if (procedureStruct->prolang == SQLlanguageId)
	{
		tmp = (text *) SysCacheGetAttr(PROCOID,
									   procedureTuple,
									   Anum_pg_proc_prosrc,
									   &isNull);
		if (isNull)
			elog(ERROR, "init_fcache: null prosrc for procedure %u",
				 foid);
		retval->src = textout(tmp);
		retval->bin = (char *) NULL;
	}
	else
	{
		retval->src = (char *) NULL;
		if (procedureStruct->proistrusted)
			retval->bin = (char *) NULL;
		else
		{
			tmp = (text *) SysCacheGetAttr(PROCOID,
										   procedureTuple,
										   Anum_pg_proc_probin,
										   &isNull);
			if (isNull)
				elog(ERROR, "init_fcache: null probin for procedure %u",
					 foid);
			retval->bin = textout(tmp);
		}
	}

	if (retval->language != SQLlanguageId)
	{
		fmgr_info(foid, &(retval->func));
		retval->nargs = retval->func.fn_nargs;
	}
	else
		retval->func.fn_addr = (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(ERROR, "init_fcache: node must be Oper or Func!");
}
