/*-------------------------------------------------------------------------
 *
 * preptlist.c
 *	  Routines to preprocess the parse tree target list
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.20 1999/05/17 17:03:18 momjian Exp $
 *
 *-------------------------------------------------------------------------
 */
#include <string.h>
#include "postgres.h"

#include "catalog/pg_type.h"
#include "nodes/pg_list.h"
#include "nodes/relation.h"
#include "nodes/primnodes.h"
#include "nodes/parsenodes.h"

#include "nodes/makefuncs.h"

#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/palloc.h"
#include "parser/parse_type.h"

#include "parser/parsetree.h"	/* for getrelid() */

#include "optimizer/internal.h"
#include "optimizer/prep.h"
#include "optimizer/clauses.h"
#include "optimizer/tlist.h"

static List *expand_targetlist(List *tlist, Oid relid, int command_type,
				  Index result_relation);
static List *replace_matching_resname(List *new_tlist,
						 List *old_tlist);
static List *new_relation_targetlist(Oid relid, Index rt_index,
						NodeTag node_type);


/*
 * preprocess_targetlist
 *	  Driver for preprocessing the parse tree targetlist.
 *
 *	  1. Deal with appends and replaces by filling missing attributes
 *		 in the target list.
 *	  2. Reset operator OIDs to the appropriate regproc ids.
 *
 *	  Returns the new targetlist.
 */
List *
preprocess_targetlist(List *tlist,
					  int command_type,
					  Index result_relation,
					  List *range_table)
{
	List	   *expanded_tlist = NIL;
	Oid			relid = InvalidOid;
	List	   *t_list = NIL;
	List	   *temp = NIL;

	if (result_relation >= 1 && command_type != CMD_SELECT)
		relid = getrelid(result_relation, range_table);

	/*
	 * for heap_formtuple to work, the targetlist must match the exact
	 * order of the attributes. We also need to fill in the missing
	 * attributes here.							-ay 10/94
	 */
	expanded_tlist = expand_targetlist(tlist, relid, command_type, result_relation);

	/* XXX should the fix-opids be this early?? */
	/* was mapCAR  */
	foreach(temp, expanded_tlist)
	{
		TargetEntry *tle = lfirst(temp);

		if (tle->expr)
			fix_opid(tle->expr);
	}
	t_list = copyObject(expanded_tlist);

	/* ------------------
	 *	for "replace" or "delete" queries, add ctid of the result
	 *	relation into the target list so that the ctid can get
	 *	propogate through the execution and in the end ExecReplace()
	 *	will find the right tuple to replace or delete.  This
	 *	extra field will be removed in ExecReplace().
	 *	For convinient, we append this extra field to the end of
	 *	the target list.
	 * ------------------
	 */
	if (command_type == CMD_UPDATE || command_type == CMD_DELETE)
	{
		TargetEntry *ctid;
		Resdom	   *resdom;
		Var		   *var;

		resdom = makeResdom(length(t_list) + 1,
							TIDOID,
							-1,
							"ctid",
							0,
							0,
							true);

		var = makeVar(result_relation, -1, TIDOID, -1, 0, result_relation, -1);

		ctid = makeTargetEntry(resdom, (Node *) var);
		t_list = lappend(t_list, ctid);
	}

	return t_list;
}

/*****************************************************************************
 *
 *		TARGETLIST EXPANSION
 *
 *****************************************************************************/

/*
 * expand_targetlist
 *	  Given a target list as generated by the parser and a result relation,
 *	  add targetlist entries for the attributes which have not been used.
 *
 *	  XXX This code is only supposed to work with unnested relations.
 *
 *	  'tlist' is the original target list
 *	  'relid' is the relid of the result relation
 *	  'command' is the update command
 *
 * Returns the expanded target list, sorted in resno order.
 */
static List *
expand_targetlist(List *tlist,
				  Oid relid,
				  int command_type,
				  Index result_relation)
{
	NodeTag		node_type = T_Invalid;

	switch (command_type)
	{
		case CMD_INSERT:
			node_type = (NodeTag) T_Const;
			break;
		case CMD_UPDATE:
			node_type = (NodeTag) T_Var;
			break;
	}

	if (node_type != T_Invalid)
	{
		List	   *ntlist = new_relation_targetlist(relid,
													 result_relation,
													 node_type);

		return replace_matching_resname(ntlist, tlist);
	}
	else
		return tlist;

}


static List *
replace_matching_resname(List *new_tlist, List *old_tlist)
{
	List	   *temp,
			   *i;
	List	   *t_list = NIL;

	foreach(i, new_tlist)
	{
		TargetEntry *new_tle = (TargetEntry *) lfirst(i);
		TargetEntry *matching_old_tl = NULL;

		foreach(temp, old_tlist)
		{
			TargetEntry *old_tle = (TargetEntry *) lfirst(temp);

			old_tle = lfirst(temp);
			if (!strcmp(old_tle->resdom->resname,
						new_tle->resdom->resname))
			{
				matching_old_tl = old_tle;
				break;
			}
		}

		if (matching_old_tl)
		{
			matching_old_tl->resdom->resno = new_tle->resdom->resno;
			t_list = lappend(t_list, matching_old_tl);
		}
		else
			t_list = lappend(t_list, new_tle);
	}

	/*
	 * It is possible that 'old_tlist' has some negative attributes (i.e.
	 * negative resnos). This only happens if this is a replace/append
	 * command and we explicitly specify a system attribute. Of course
	 * this is not a very good idea if this is a user query, but on the
	 * other hand the rule manager uses this mechanism to replace rule
	 * locks.
	 *
	 * So, copy all these entries to the end of the target list and set their
	 * 'resjunk' value to true to show that these are special attributes and
	 * have to be treated specially by the executor!
	 */
	foreach(temp, old_tlist)
	{
		TargetEntry *old_tle,
				   *new_tl;
		Resdom	   *newresno;

		old_tle = lfirst(temp);
		if (old_tle->resdom->resno < 0)
		{
			newresno = (Resdom *) copyObject((Node *) old_tle->resdom);
			newresno->resno = length(t_list) + 1;
			newresno->resjunk = true;
			new_tl = makeTargetEntry(newresno, old_tle->expr);
			t_list = lappend(t_list, new_tl);
		}

		/*
		 * Also it is possible that the parser or rewriter added
		 * some junk attributes to hold GROUP BY expressions which
		 * are not part of the result attributes.
		 * We can simply identify them by looking at the resgroupref
		 * in the TLE's resdom, which is a unique number telling which
		 * TLE belongs to which GroupClause.
		 */
		if (old_tle->resdom->resgroupref > 0)
		{
			bool			already_there = FALSE;
			TargetEntry 	*new_tle;
			Resdom	   		*newresno;

			/*
			 * Check if the tle is already in the new list
			 */
			foreach(i, t_list)
			{
				new_tle = (TargetEntry *)lfirst(i);

				if (new_tle->resdom->resgroupref == 
									old_tle->resdom->resgroupref)
				{
					already_there = TRUE;
					break;
				}

			}

			/*
			 * If not, add it and make sure it is now a junk attribute
			 */
			if (!already_there)
			{
				newresno = (Resdom *) copyObject((Node *) old_tle->resdom);
				newresno->resno = length(t_list) + 1;
				newresno->resjunk = true;
				new_tl = makeTargetEntry(newresno, old_tle->expr);
				t_list = lappend(t_list, new_tl);
			}
		}
	}

	return t_list;
}

/*
 * new_relation_targetlist
 *	  Generate a targetlist for the relation with relation OID 'relid'
 *	  and rangetable index 'rt_index'.
 *
 *	  Returns the new targetlist.
 */
static List *
new_relation_targetlist(Oid relid, Index rt_index, NodeTag node_type)
{
	AttrNumber	attno;
	List	   *t_list = NIL;
	char	   *attname;
	int			typlen;
	Oid			atttype = 0;
	bool		attisset = false;

	for (attno = 1; attno <= get_relnatts(relid); attno++)
	{
		attname = get_attname(relid, attno);
		atttype = get_atttype(relid, attno);

		/*
		 * Since this is an append or replace, the size of any set
		 * attribute is the size of the OID used to represent it.
		 */
		attisset = get_attisset(relid, attname);
		if (attisset)
			typlen = typeLen(typeidType(OIDOID));
		else
			typlen = get_typlen(atttype);

		switch (node_type)
		{
			case T_Const:
				{
					struct varlena *typedefault = get_typdefault(atttype);
					int			temp = 0;
					Const	   *temp2 = (Const *) NULL;
					TargetEntry *temp3 = (TargetEntry *) NULL;

					if (typedefault == NULL)
						temp = 0;
					else
						temp = typlen;

					temp2 = makeConst(atttype,
									  temp,
									  (Datum) typedefault,
								(typedefault == (struct varlena *) NULL),
					/* XXX ? */
									  false,
									  false,	/* not a set */
									  false);

					temp3 = makeTargetEntry(makeResdom(attno,
													   atttype,
													   -1,
													   attname,
													   0,
													   (Oid) 0,
													   false),
											(Node *) temp2);
					t_list = lappend(t_list, temp3);
					break;
				}
			case T_Var:
				{
					Var		   *temp_var = (Var *) NULL;
					TargetEntry *temp_list = NULL;

					temp_var = makeVar(rt_index, attno, atttype,
								get_atttypmod(relid, attno),
								0, rt_index, attno);

					temp_list = makeTargetEntry(makeResdom(attno,
														   atttype,
											 get_atttypmod(relid, attno),
														   attname,
														   0,
														   (Oid) 0,
														   false),
												(Node *) temp_var);
					t_list = lappend(t_list, temp_list);
					break;
				}
			default:			/* do nothing */
				break;
		}
	}

	return t_list;
}
