/*-------------------------------------------------------------------------
 *
 * setrefs.c
 *	  Routines to change varno/attno entries to contain references
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.54 1999/08/09 00:56:05 tgl Exp $
 *
 *-------------------------------------------------------------------------
 */
#include <sys/types.h>

#include "postgres.h"

#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/planmain.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"

typedef struct {
	List	   *outer_tlist;
	List	   *inner_tlist;
} replace_joinvar_refs_context;

typedef struct {
	Index		subvarno;
	List	   *subplanTargetList;
} replace_vars_with_subplan_refs_context;

typedef struct {
	List	   *groupClause;
	List	   *targetList;
} check_having_for_ungrouped_vars_context;

static void set_join_tlist_references(Join *join);
static void set_nonamescan_tlist_references(SeqScan *nonamescan);
static void set_noname_tlist_references(Noname *noname);
static Node *replace_joinvar_refs(Node *clause,
								  List *outer_tlist,
								  List *inner_tlist);
static Node *replace_joinvar_refs_mutator(Node *node,
					replace_joinvar_refs_context *context);
static List *tlist_noname_references(Oid nonameid, List *tlist);
static void set_result_tlist_references(Result *resultNode);
static void replace_vars_with_subplan_refs(Node *clause,
										   Index subvarno,
										   List *subplanTargetList);
static bool replace_vars_with_subplan_refs_walker(Node *node,
					replace_vars_with_subplan_refs_context *context);
static List *pull_agg_clause(Node *clause);
static bool pull_agg_clause_walker(Node *node, List **listptr);
static bool check_having_for_ungrouped_vars_walker(Node *node,
					check_having_for_ungrouped_vars_context *context);

/*****************************************************************************
 *
 *		SUBPLAN REFERENCES
 *
 *****************************************************************************/

/*
 * set_tlist_references
 *	  Modifies the target list of nodes in a plan to reference target lists
 *	  at lower levels.
 *
 * 'plan' is the plan whose target list and children's target lists will
 *		be modified
 *
 * Returns nothing of interest, but modifies internal fields of nodes.
 *
 */
void
set_tlist_references(Plan *plan)
{
	if (plan == NULL)
		return;

	if (IsA_Join(plan))
		set_join_tlist_references((Join *) plan);
	else if (IsA(plan, SeqScan) &&plan->lefttree &&
			 IsA_Noname(plan->lefttree))
		set_nonamescan_tlist_references((SeqScan *) plan);
	else if (IsA(plan, Sort))
		set_noname_tlist_references((Noname *) plan);
	else if (IsA(plan, Result))
		set_result_tlist_references((Result *) plan);
	else if (IsA(plan, Hash))
		set_tlist_references(plan->lefttree);
}

/*
 * set_join_tlist_references
 *	  Modifies the target list of a join node by setting the varnos and
 *	  varattnos to reference the target list of the outer and inner join
 *	  relations.
 *
 *	  Creates a target list for a join node to contain references by setting
 *	  varno values to OUTER or INNER and setting attno values to the
 *	  result domain number of either the corresponding outer or inner join
 *	  tuple.
 *
 * 'join' is a join plan node
 *
 * Returns nothing of interest, but modifies internal fields of nodes.
 *
 */
static void
set_join_tlist_references(Join *join)
{
	Plan	   *outer = ((Plan *) join)->lefttree;
	Plan	   *inner = ((Plan *) join)->righttree;
	List	   *outer_tlist = ((outer == NULL) ? NIL : outer->targetlist);
	List	   *inner_tlist = ((inner == NULL) ? NIL : inner->targetlist);
	List	   *new_join_targetlist = NIL;
	List	   *qptlist = ((Plan *) join)->targetlist;
	List	   *entry;

	foreach(entry, qptlist)
	{
		TargetEntry *xtl = (TargetEntry *) lfirst(entry);
		Node	   *joinexpr = replace_joinvar_refs(xtl->expr,
													outer_tlist,
													inner_tlist);

		new_join_targetlist = lappend(new_join_targetlist,
									  makeTargetEntry(xtl->resdom, joinexpr));
	}

	((Plan *) join)->targetlist = new_join_targetlist;
	if (outer != NULL)
		set_tlist_references(outer);
	if (inner != NULL)
		set_tlist_references(inner);
}

/*
 * set_nonamescan_tlist_references
 *	  Modifies the target list of a node that scans a noname relation (i.e., a
 *	  sort or hash node) so that the varnos refer to the child noname.
 *
 * 'nonamescan' is a seqscan node
 *
 * Returns nothing of interest, but modifies internal fields of nodes.
 *
 */
static void
set_nonamescan_tlist_references(SeqScan *nonamescan)
{
	Noname	   *noname = (Noname *) ((Plan *) nonamescan)->lefttree;

	((Plan *) nonamescan)->targetlist = tlist_noname_references(noname->nonameid,
									  ((Plan *) nonamescan)->targetlist);
	set_noname_tlist_references(noname);
}

/*
 * set_noname_tlist_references
 *	  The noname's vars are made consistent with (actually, identical to) the
 *	  modified version of the target list of the node from which noname node
 *	  receives its tuples.
 *
 * 'noname' is a noname (e.g., sort, hash) plan node
 *
 * Returns nothing of interest, but modifies internal fields of nodes.
 *
 */
static void
set_noname_tlist_references(Noname *noname)
{
	Plan	   *source = ((Plan *) noname)->lefttree;

	if (source != NULL)
	{
		set_tlist_references(source);
		((Plan *) noname)->targetlist = copy_vars(((Plan *) noname)->targetlist,
												  (source)->targetlist);
	}
	else
		elog(ERROR, "calling set_noname_tlist_references with empty lefttree");
}

/*
 * join_references
 *	   Creates a new set of join clauses by changing the varno/varattno
 *	   values of variables in the clauses to reference target list values
 *	   from the outer and inner join relation target lists.
 *	   This is just an external interface for replace_joinvar_refs.
 *
 * 'clauses' is the list of join clauses
 * 'outer_tlist' is the target list of the outer join relation
 * 'inner_tlist' is the target list of the inner join relation
 *
 * Returns the new join clauses.  The original clause structure is
 * not modified.
 *
 */
List *
join_references(List *clauses,
				List *outer_tlist,
				List *inner_tlist)
{
	return (List *) replace_joinvar_refs((Node *) clauses,
										 outer_tlist,
										 inner_tlist);
}

/*
 * replace_joinvar_refs
 *
 *	  Replaces all variables within a join clause with a new var node
 *	  whose varno/varattno fields contain a reference to a target list
 *	  element from either the outer or inner join relation.
 *
 *	  Returns a suitably modified copy of the join clause;
 *	  the original is not modified (and must not be!)
 *
 *	  Side effect: also runs fix_opids on the modified join clause.
 *	  Really ought to make that happen in a uniform, consistent place...
 *
 * 'clause' is the join clause
 * 'outer_tlist' is the target list of the outer join relation
 * 'inner_tlist' is the target list of the inner join relation
 */
static Node *
replace_joinvar_refs(Node *clause,
					 List *outer_tlist,
					 List *inner_tlist)
{
	replace_joinvar_refs_context context;

	context.outer_tlist = outer_tlist;
	context.inner_tlist = inner_tlist;
	return (Node *) fix_opids((List *)
							  replace_joinvar_refs_mutator(clause, &context));
}

static Node *
replace_joinvar_refs_mutator(Node *node,
							 replace_joinvar_refs_context *context)
{
	if (node == NULL)
		return NULL;
	if (IsA(node, Var))
	{
		Var		   *var = (Var *) node;
		Resdom	   *resdom = tlist_member(var, context->outer_tlist);

		if (resdom != NULL && IsA(resdom, Resdom))
			return (Node *) makeVar(OUTER,
									resdom->resno,
									var->vartype,
									var->vartypmod,
									0,
									var->varnoold,
									var->varoattno);
		resdom = tlist_member(var, context->inner_tlist);
		if (resdom != NULL && IsA(resdom, Resdom))
			return (Node *) makeVar(INNER,
									resdom->resno,
									var->vartype,
									var->vartypmod,
									0,
									var->varnoold,
									var->varoattno);
		/* Var not in either tlist, return an unmodified copy. */
		return copyObject(node);
	}
	return expression_tree_mutator(node,
								   replace_joinvar_refs_mutator,
								   (void *) context);
}

/*
 * tlist_noname_references
 *	  Creates a new target list for a node that scans a noname relation,
 *	  setting the varnos to the id of the noname relation and setting varids
 *	  if necessary (varids are only needed if this is a targetlist internal
 *	  to the tree, in which case the targetlist entry always contains a var
 *	  node, so we can just copy it from the noname).
 *
 * 'nonameid' is the id of the noname relation
 * 'tlist' is the target list to be modified
 *
 * Returns new target list
 *
 */
static List *
tlist_noname_references(Oid nonameid,
						List *tlist)
{
	List	   *t_list = NIL;
	List	   *entry;

	foreach(entry, tlist)
	{
		TargetEntry *xtl = lfirst(entry);
		AttrNumber	oattno;
		TargetEntry *noname;

		if (IsA(get_expr(xtl), Var))
			oattno = ((Var *) xtl->expr)->varoattno;
		else
			oattno = 0;

		noname = makeTargetEntry(xtl->resdom,
								 (Node *) makeVar(nonameid,
												  xtl->resdom->resno,
												  xtl->resdom->restype,
												  xtl->resdom->restypmod,
												  0,
												  nonameid,
												  oattno));

		t_list = lappend(t_list, noname);
	}
	return t_list;
}

/*---------------------------------------------------------
 *
 * set_result_tlist_references
 *
 * Change the target list of a Result node, so that it correctly
 * addresses the tuples returned by its left tree subplan.
 *
 * NOTE:
 *	1) we ignore the right tree! (in the current implementation
 *	   it is always nil)
 *	2) this routine will probably *NOT* work with nested dot
 *	   fields....
 */
static void
set_result_tlist_references(Result *resultNode)
{
	Plan	   *subplan;
	List	   *resultTargetList;
	List	   *subplanTargetList;

	resultTargetList = ((Plan *) resultNode)->targetlist;

	/*
	 * NOTE: we only consider the left tree subplan. This is usually a seq
	 * scan.
	 */
	subplan = ((Plan *) resultNode)->lefttree;
	if (subplan != NULL)
		subplanTargetList = subplan->targetlist;
	else
		subplanTargetList = NIL;

	replace_tlist_with_subplan_refs(resultTargetList,
									(Index) OUTER,
									subplanTargetList);
}

/*---------------------------------------------------------
 *
 * replace_tlist_with_subplan_refs
 *
 * Applies replace_vars_with_subplan_refs() to each entry of a targetlist.
 */
void
replace_tlist_with_subplan_refs(List *tlist,
								Index subvarno,
								List *subplanTargetList)
{
	List	   *t;

	foreach(t, tlist)
	{
		TargetEntry *entry = (TargetEntry *) lfirst(t);

		replace_vars_with_subplan_refs((Node *) get_expr(entry),
									   subvarno, subplanTargetList);
	}
}

/*---------------------------------------------------------
 *
 * replace_vars_with_subplan_refs
 *
 * This routine modifies (destructively!) an expression tree so that all
 * Var nodes reference target nodes of a subplan.  It is used to fix up
 * target expressions of upper-level plan nodes.
 *
 * 'clause': the tree to be fixed
 * 'subvarno': varno to be assigned to all Vars
 * 'subplanTargetList': target list for subplan
 *
 * Afterwards, all Var nodes have varno = subvarno, varattno = resno
 * of corresponding subplan target.
 */
static void
replace_vars_with_subplan_refs(Node *clause,
							   Index subvarno,
							   List *subplanTargetList)
{
	replace_vars_with_subplan_refs_context context;

	context.subvarno = subvarno;
	context.subplanTargetList = subplanTargetList;
	replace_vars_with_subplan_refs_walker(clause, &context);
}

static bool
replace_vars_with_subplan_refs_walker(Node *node,
							 replace_vars_with_subplan_refs_context *context)
{
	if (node == NULL)
		return false;
	if (IsA(node, Var))
	{
		/*
		 * It could be that this varnode has been created by make_groupplan
		 * and is already set up to reference the subplan target list. We
		 * recognize that case by varno = 1, varnoold = -1, varattno =
		 * varoattno, and varlevelsup = 0.	(Probably ought to have an
		 * explicit flag, but this should do for now.)
		 */
		Var		   *var = (Var *) node;
		TargetEntry *subplanVar;

		if (var->varno == (Index) 1 &&
			var->varnoold == ((Index) -1) &&
			var->varattno == var->varoattno &&
			var->varlevelsup == 0)
			return false;		/* OK to leave it alone */

		/* Otherwise it had better be in the subplan list. */
		subplanVar = match_varid(var, context->subplanTargetList);
		if (!subplanVar)
			elog(ERROR, "replace_vars_with_subplan_refs: variable not in target list");

		/*
		 * Change the varno & varattno fields of the var node.
		 */
		var->varno = context->subvarno;
		var->varattno = subplanVar->resdom->resno;
		return false;
	}
	return expression_tree_walker(node,
								  replace_vars_with_subplan_refs_walker,
								  (void *) context);
}

/*****************************************************************************
 *
 *****************************************************************************/

/*---------------------------------------------------------
 *
 * set_agg_tlist_references -
 *	  This routine has several responsibilities:
 *	* Update the target list of an Agg node so that it points to
 *	  the tuples returned by its left tree subplan.
 *	* If there is a qual list (from a HAVING clause), similarly update
 *	  vars in it to point to the subplan target list.
 *	* Generate the aggNode->aggs list of Aggref nodes contained in the Agg.
 *
 * The return value is TRUE if all qual clauses include Aggrefs, or FALSE
 * if any do not (caller may choose to raise an error condition).
 */
bool
set_agg_tlist_references(Agg *aggNode)
{
	List	   *subplanTargetList;
	List	   *tl;
	List	   *ql;
	bool		all_quals_ok;

	subplanTargetList = aggNode->plan.lefttree->targetlist;
	aggNode->aggs = NIL;

	foreach(tl, aggNode->plan.targetlist)
	{
		TargetEntry *tle = lfirst(tl);

		replace_vars_with_subplan_refs(tle->expr,
									   (Index) 0,
									   subplanTargetList);
		aggNode->aggs = nconc(pull_agg_clause(tle->expr), aggNode->aggs);
	}

	all_quals_ok = true;
	foreach(ql, aggNode->plan.qual)
	{
		Node	   *qual = lfirst(ql);
		List	   *qualaggs;

		replace_vars_with_subplan_refs(qual,
									   (Index) 0,
									   subplanTargetList);
		qualaggs = pull_agg_clause(qual);
		if (qualaggs == NIL)
			all_quals_ok = false;		/* this qual clause has no agg
										 * functions! */
		else
			aggNode->aggs = nconc(qualaggs, aggNode->aggs);
	}

	return all_quals_ok;
}

/*
 * pull_agg_clause
 *	  Recursively pulls all Aggref nodes from an expression clause.
 *
 *	  Returns list of Aggref nodes found.  Note the nodes themselves are not
 *	  copied, only referenced.
 */
static List *
pull_agg_clause(Node *clause)
{
	List	   *result = NIL;

	pull_agg_clause_walker(clause, &result);
	return result;
}

static bool
pull_agg_clause_walker(Node *node, List **listptr)
{
	if (node == NULL)
		return false;
	if (IsA(node, Aggref))
	{
		*listptr = lappend(*listptr, node);
		return false;
	}
	return expression_tree_walker(node, pull_agg_clause_walker,
								  (void *) listptr);
}

/*
 * check_having_for_ungrouped_vars takes the havingQual and the list of
 * GROUP BY clauses and checks for subplans in the havingQual that are being
 * passed ungrouped variables as parameters.  In other contexts, ungrouped
 * vars in the havingQual will be detected by the parser (see parse_agg.c,
 * exprIsAggOrGroupCol()).	But that routine currently does not check subplans,
 * because the necessary info is not computed until the planner runs.
 * This ought to be cleaned up someday.
 *
 * NOTE: the havingClause has been cnf-ified, so AND subclauses have been
 * turned into a plain List.  Thus, this routine has to cope with List nodes
 * where the routine above does not...
 */

void
check_having_for_ungrouped_vars(Node *clause, List *groupClause,
								List *targetList)
{
	check_having_for_ungrouped_vars_context context;

	context.groupClause = groupClause;
	context.targetList = targetList;
	check_having_for_ungrouped_vars_walker(clause, &context);
}

static bool
check_having_for_ungrouped_vars_walker(Node *node,
					check_having_for_ungrouped_vars_context *context)
{
	if (node == NULL)
		return false;
	/*
	 * We can ignore Vars other than in subplan args lists,
	 * since the parser already checked 'em.
	 */
	if (is_subplan(node))
	{
		/*
		 * The args list of the subplan node represents attributes from
		 * outside passed into the sublink.
		 */
		List	*t;

		foreach(t, ((Expr *) node)->args)
		{
			Node	   *thisarg = lfirst(t);
			bool		contained_in_group_clause = false;
			List	   *gl;

			foreach(gl, context->groupClause)
			{
				Var	   *groupexpr = get_groupclause_expr(lfirst(gl),
														 context->targetList);

				if (var_equal((Var *) thisarg, groupexpr))
				{
					contained_in_group_clause = true;
					break;
				}
			}

			if (!contained_in_group_clause)
				elog(ERROR, "Sub-SELECT in HAVING clause must use only GROUPed attributes from outer SELECT");
		}
	}
	return expression_tree_walker(node,
								  check_having_for_ungrouped_vars_walker,
								  (void *) context);
}
