/*-------------------------------------------------------------------------
 *
 * execJunk.c
 *	  Junk attribute support stuff....
 *
 * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $PostgreSQL: pgsql/src/backend/executor/execJunk.c,v 1.44 2004/10/07 18:38:49 tgl Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/heapam.h"
#include "executor/executor.h"
#include "nodes/makefuncs.h"

/*-------------------------------------------------------------------------
 *		XXX this stuff should be rewritten to take advantage
 *			of ExecProject() and the ProjectionInfo node.
 *			-cim 6/3/91
 *
 * An attribute of a tuple living inside the executor, can be
 * either a normal attribute or a "junk" attribute. "junk" attributes
 * never make it out of the executor, i.e. they are never printed,
 * returned or stored in disk. Their only purpose in life is to
 * store some information useful only to the executor, mainly the values
 * of some system attributes like "ctid" or rule locks.
 *
 * The general idea is the following: A target list consists of a list of
 * Resdom nodes & expression pairs. Each Resdom node has an attribute
 * called 'resjunk'. If the value of this attribute is true then the
 * corresponding attribute is a "junk" attribute.
 *
 * When we initialize a plan we call 'ExecInitJunkFilter' to create
 * and store the appropriate information in the 'es_junkFilter' attribute of
 * EState.
 *
 * We then execute the plan ignoring the "resjunk" attributes.
 *
 * Finally, when at the top level we get back a tuple, we can call
 * 'ExecGetJunkAttribute' to retrieve the value of the junk attributes we
 * are interested in, and 'ExecRemoveJunk' to remove all the junk attributes
 * from a tuple. This new "clean" tuple is then printed, replaced, deleted
 * or inserted.
 *
 *-------------------------------------------------------------------------
 */

/*
 * ExecInitJunkFilter
 *
 * Initialize the Junk filter.
 *
 * 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, bool hasoid, TupleTableSlot *slot)
{
	JunkFilter *junkfilter;
	TupleDesc	cleanTupType;
	int			cleanLength;
	AttrNumber *cleanMap;
	ListCell   *t;
	AttrNumber	cleanResno;

	/*
	 * Compute the tuple descriptor for the cleaned tuple.
	 */
	cleanTupType = ExecCleanTypeFromTL(targetList, hasoid);

	/*
	 * 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)
	{
		cleanMap = (AttrNumber *) palloc(cleanLength * sizeof(AttrNumber));
		cleanResno = 1;
		foreach(t, targetList)
		{
			TargetEntry *tle = lfirst(t);
			Resdom	   *resdom = tle->resdom;

			if (!resdom->resjunk)
			{
				cleanMap[cleanResno - 1] = resdom->resno;
				cleanResno++;
			}
		}
	}
	else
		cleanMap = NULL;

	/*
	 * Finally create and initialize the JunkFilter struct.
	 */
	junkfilter = makeNode(JunkFilter);

	junkfilter->jf_targetList = targetList;
	junkfilter->jf_cleanTupType = cleanTupType;
	junkfilter->jf_cleanMap = cleanMap;
	junkfilter->jf_resultSlot = slot;

	if (slot)
		ExecSetSlotDescriptor(slot, cleanTupType, false);

	return junkfilter;
}

/*
 * ExecInitJunkFilterConversion
 *
 * Initialize a JunkFilter for rowtype conversions.
 *
 * Here, we are given the target "clean" tuple descriptor rather than
 * inferring it from the targetlist.  The target descriptor can contain
 * deleted columns.  It is assumed that the caller has checked that the
 * non-deleted columns match up with the non-junk columns of the targetlist.
 */
JunkFilter *
ExecInitJunkFilterConversion(List *targetList,
							 TupleDesc cleanTupType,
							 TupleTableSlot *slot)
{
	JunkFilter *junkfilter;
	int			cleanLength;
	AttrNumber *cleanMap;
	ListCell   *t;
	int			i;

	/*
	 * Calculate the mapping between the original tuple's attributes and
	 * the "clean" tuple's attributes.
	 *
	 * The "map" is an array of "cleanLength" attribute numbers, i.e. one
	 * entry for every attribute of the "clean" tuple. The value of this
	 * entry is the attribute number of the corresponding attribute of the
	 * "original" tuple.  We store zero for any deleted attributes, marking
	 * that a NULL is needed in the output tuple.
	 */
	cleanLength = cleanTupType->natts;
	if (cleanLength > 0)
	{
		cleanMap = (AttrNumber *) palloc0(cleanLength * sizeof(AttrNumber));
		t = list_head(targetList);
		for (i = 0; i < cleanLength; i++)
		{
			if (cleanTupType->attrs[i]->attisdropped)
				continue;		/* map entry is already zero */
			for (;;)
			{
				TargetEntry *tle = lfirst(t);
				Resdom	   *resdom = tle->resdom;

				t = lnext(t);
				if (!resdom->resjunk)
				{
					cleanMap[i] = resdom->resno;
					break;
				}
			}
		}
	}
	else
		cleanMap = NULL;

	/*
	 * Finally create and initialize the JunkFilter struct.
	 */
	junkfilter = makeNode(JunkFilter);

	junkfilter->jf_targetList = targetList;
	junkfilter->jf_cleanTupType = cleanTupType;
	junkfilter->jf_cleanMap = cleanMap;
	junkfilter->jf_resultSlot = slot;

	if (slot)
		ExecSetSlotDescriptor(slot, cleanTupType, false);

	return junkfilter;
}

/*
 * ExecGetJunkAttribute
 *
 * Given a tuple (slot), the junk filter and a junk attribute's name,
 * extract & return the value and isNull flag of this attribute.
 *
 * It returns false iff no junk attribute with such name was found.
 */
bool
ExecGetJunkAttribute(JunkFilter *junkfilter,
					 TupleTableSlot *slot,
					 char *attrName,
					 Datum *value,
					 bool *isNull)
{
	List	   *targetList;
	ListCell   *t;
	AttrNumber	resno;
	TupleDesc	tupType;
	HeapTuple	tuple;

	/*
	 * first look in the junkfilter's target list for an attribute with
	 * the given name
	 */
	resno = InvalidAttrNumber;
	targetList = junkfilter->jf_targetList;

	foreach(t, targetList)
	{
		TargetEntry *tle = lfirst(t);
		Resdom	   *resdom = tle->resdom;

		if (resdom->resjunk && resdom->resname &&
			(strcmp(resdom->resname, attrName) == 0))
		{
			/* We found it ! */
			resno = resdom->resno;
			break;
		}
	}

	if (resno == InvalidAttrNumber)
	{
		/* Ooops! We couldn't find this attribute... */
		return false;
	}

	/*
	 * Now extract the attribute value from the tuple.
	 */
	tuple = slot->val;
	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.
 *
 * 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[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 */

	cleanTupType = junkfilter->jf_cleanTupType;
	cleanLength = cleanTupType->natts;
	cleanMap = junkfilter->jf_cleanMap;

	/*
	 * Create the arrays that will hold the attribute values and the null
	 * information for the old tuple and new "clean" tuple.
	 *
	 * 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.
	 */
	if (cleanLength > PREALLOC_SIZE)
	{
		values = (Datum *) palloc(cleanLength * sizeof(Datum));
		nulls = (char *) palloc(cleanLength * sizeof(char));
	}
	else
	{
		values = values_array;
		nulls = nulls_array;
	}
	if (oldLength > PREALLOC_SIZE)
	{
		old_values = (Datum *) palloc(oldLength * sizeof(Datum));
		old_nulls = (char *) palloc(oldLength * sizeof(char));
	}
	else
	{
		old_values = old_values_array;
		old_nulls = old_nulls_array;
	}

	/*
	 * 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 + 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];

		values[i] = old_values[j];
		nulls[i] = old_nulls[j];
	}

	/*
	 * Now form the new tuple.
	 */
	cleanTuple = heap_formtuple(cleanTupType, values, nulls);

	/*
	 * We are done.  Free any space allocated for 'values' and 'nulls' and
	 * return the new tuple.
	 */
	if (values != values_array)
	{
		pfree(values);
		pfree(nulls);
	}
	if (old_values != old_values_array)
	{
		pfree(old_values);
		pfree(old_nulls);
	}

	return cleanTuple;
}
