/*-------------------------------------------------------------------------
 *
 * subselect.c--
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "catalog/pg_type.h"

#include "nodes/pg_list.h"
#include "nodes/plannodes.h"
#include "nodes/parsenodes.h"
#include "nodes/relation.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"

#include "optimizer/subselect.h"
#include "optimizer/planner.h"
#include "optimizer/planmain.h"
#include "optimizer/internal.h"
#include "optimizer/paths.h"
#include "optimizer/clauses.h"
#include "optimizer/keys.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "optimizer/cost.h"

int			PlannerQueryLevel;	/* level of current query */
List	   *PlannerVarParam;	/* correlation Vars to Param mapper */
List	   *PlannerParamVar;	/* to get Var from Param->paramid */
List	   *PlannerInitPlan;	/* init subplans for current query */
int			PlannerPlanId;


static int
_new_param(Var *var, int varlevel)
{
	List	   *last;
	int			i = 0;

	if (PlannerParamVar == NULL)
		last = PlannerParamVar = makeNode(List);
	else
	{
		for (last = PlannerParamVar;;)
		{
			i++;
			if (lnext(last) == NULL)
				break;
			last = lnext(last);
		}
		lnext(last) = makeNode(List);
		last = lnext(last);
	}

	lnext(last) = NULL;
	lfirst(last) = makeVar(var->varno, var->varattno, var->vartype,
				var->vartypmod, varlevel, var->varnoold, var->varoattno);

	return (i);
}

static Param *
_replace_var(Var *var)
{
	List	  **rt = (List **) nth(var->varlevelsup, PlannerVarParam);
	List	   *vpe = rt[var->varno - 1];
	Param	   *retval;
	int			i;

	if (vpe == NULL)
	{
		vpe = rt[var->varno - 1] = makeNode(List);
		lfirsti(vpe) = -1;
		lnext(vpe) = NULL;
	}

	for (i = 1;; i++)
	{
		if (i == var->varattno)
			break;
		if (lnext(vpe) == NULL)
		{
			lnext(vpe) = makeNode(List);
			vpe = lnext(vpe);
			lfirsti(vpe) = -1;
			lnext(vpe) = NULL;
		}
		else
			vpe = lnext(vpe);
	}

	if ((i = lfirsti(vpe)) < 0) /* parameter is not assigned */
		i = _new_param(var, PlannerQueryLevel - var->varlevelsup);

	retval = makeNode(Param);
	retval->paramkind = PARAM_EXEC;
	retval->paramid = (AttrNumber) i;
	retval->paramtype = var->vartype;

	return (retval);
}

static Node *
_make_subplan(SubLink *slink)
{
	SubPlan    *node = makeNode(SubPlan);
	Plan	   *plan;
	List	   *lst;
	Node	   *result;
	List	   *saved_ip = PlannerInitPlan;

	PlannerInitPlan = NULL;

	PlannerQueryLevel++;		/* we becomes child */

	node->plan = plan = union_planner((Query *) slink->subselect);

	/*
	 * Assign subPlan, extParam and locParam to plan nodes. At the moment,
	 * SS_finalize_plan doesn't handle initPlan-s and so we assigne them
	 * to the topmost plan node and take care about its extParam too.
	 */
	(void) SS_finalize_plan(plan);
	plan->initPlan = PlannerInitPlan;

	/* Get extParam from InitPlan-s */
	foreach(lst, PlannerInitPlan)
	{
		List	   *lp;

		foreach(lp, ((SubPlan *) lfirst(lst))->plan->extParam)
		{
			if (!intMember(lfirsti(lp), plan->extParam))
				plan->extParam = lappendi(plan->extParam, lfirsti(lp));
		}
	}

	/* and now we are parent again */
	PlannerInitPlan = saved_ip;
	PlannerQueryLevel--;

	node->plan_id = PlannerPlanId++;
	node->rtable = ((Query *) slink->subselect)->rtable;
	node->sublink = slink;
	slink->subselect = NULL;	/* cool ?! */

	/* make parParam list */
	foreach(lst, plan->extParam)
	{
		Var		   *var = nth(lfirsti(lst), PlannerParamVar);

		if (var->varlevelsup == PlannerQueryLevel)
			node->parParam = lappendi(node->parParam, lfirsti(lst));
	}

	/*
	 * Un-correlated or undirect correlated plans of EXISTS or EXPR types
	 * can be used as initPlans...
	 */
	if (node->parParam == NULL && slink->subLinkType == EXPR_SUBLINK)
	{
		int			i = 0;

		/* transform right side of all sublink Oper-s into Param */
		foreach(lst, slink->oper)
		{
			List	   *rside = lnext(((Expr *) lfirst(lst))->args);
			TargetEntry *te = nth(i, plan->targetlist);
			Var		   *var = makeVar(0, 0, te->resdom->restype,
									  te->resdom->restypmod,
									  PlannerQueryLevel, 0, 0);
			Param	   *prm = makeNode(Param);

			prm->paramkind = PARAM_EXEC;
			prm->paramid = (AttrNumber) _new_param(var, PlannerQueryLevel);
			prm->paramtype = var->vartype;
			lfirst(rside) = prm;
			node->setParam = lappendi(node->setParam, prm->paramid);
			pfree(var);
			i++;
		}
		PlannerInitPlan = lappend(PlannerInitPlan, node);
		if (i > 1)
			result = (Node *) ((slink->useor) ? make_orclause(slink->oper) :
							   make_andclause(slink->oper));
		else
			result = (Node *) lfirst(slink->oper);
	}
	else if (node->parParam == NULL && slink->subLinkType == EXISTS_SUBLINK)
	{
		Var		   *var = makeVar(0, 0, BOOLOID, -1, PlannerQueryLevel, 0, 0);
		Param	   *prm = makeNode(Param);

		prm->paramkind = PARAM_EXEC;
		prm->paramid = (AttrNumber) _new_param(var, PlannerQueryLevel);
		prm->paramtype = var->vartype;
		node->setParam = lappendi(node->setParam, prm->paramid);
		pfree(var);
		PlannerInitPlan = lappend(PlannerInitPlan, node);
		result = (Node *) prm;
	}
	else
/* make expression of SUBPLAN type */
	{
		Expr	   *expr = makeNode(Expr);
		List	   *args = NULL;
		int			i = 0;

		expr->typeOid = BOOLOID;
		expr->opType = SUBPLAN_EXPR;
		expr->oper = (Node *) node;

		/*
		 * Make expr->args from parParam. Left sides of sublink Oper-s are
		 * handled by optimizer directly... Also, transform right side of
		 * sublink Oper-s into Const.
		 */
		foreach(lst, node->parParam)
		{
			Var		   *var = nth(lfirsti(lst), PlannerParamVar);

			var = (Var *) copyObject(var);
			var->varlevelsup = 0;
			args = lappend(args, var);
		}
		foreach(lst, slink->oper)
		{
			List	   *rside = lnext(((Expr *) lfirst(lst))->args);
			TargetEntry *te = nth(i, plan->targetlist);
			Const	   *con = makeConst(te->resdom->restype,
										0, 0, true, 0, 0, 0);

			lfirst(rside) = con;
			i++;
		}
		expr->args = args;
		result = (Node *) expr;
	}

	return (result);

}

static List *
set_unioni(List *l1, List *l2)
{
	if (l1 == NULL)
		return (l2);
	if (l2 == NULL)
		return (l1);

	return (nconc(l1, set_differencei(l2, l1)));
}

static List *
_finalize_primnode(void *expr, List **subplan)
{
	List	   *result = NULL;

	if (expr == NULL)
		return (NULL);

	if (IsA(expr, Param))
	{
		if (((Param *) expr)->paramkind == PARAM_EXEC)
			return (lconsi(((Param *) expr)->paramid, (List *) NULL));
	}
	else if (single_node(expr))
		return (NULL);
	else if (IsA(expr, List))
	{
		List	   *le;

		foreach(le, (List *) expr)
			result = set_unioni(result,
								_finalize_primnode(lfirst(le), subplan));
	}
	else if (IsA(expr, Iter))
		return (_finalize_primnode(((Iter *) expr)->iterexpr, subplan));
	else if (or_clause(expr) || and_clause(expr) || is_opclause(expr) ||
			 not_clause(expr) || is_funcclause(expr))
		return (_finalize_primnode(((Expr *) expr)->args, subplan));
	else if (IsA(expr, Aggreg))
		return (_finalize_primnode(((Aggreg *) expr)->target, subplan));
	else if (IsA(expr, ArrayRef))
	{
		result = _finalize_primnode(((ArrayRef *) expr)->refupperindexpr, subplan);
		result = set_unioni(result,
							_finalize_primnode(((ArrayRef *) expr)->reflowerindexpr, subplan));
		result = set_unioni(result,
			  _finalize_primnode(((ArrayRef *) expr)->refexpr, subplan));
		result = set_unioni(result,
		 _finalize_primnode(((ArrayRef *) expr)->refassgnexpr, subplan));
	}
	else if (IsA(expr, TargetEntry))
		return (_finalize_primnode(((TargetEntry *) expr)->expr, subplan));
	else if (is_subplan(expr))
	{
		List	   *lst;

		*subplan = lappend(*subplan, ((Expr *) expr)->oper);
		foreach(lst, ((SubPlan *) ((Expr *) expr)->oper)->plan->extParam)
		{
			Var		   *var = nth(lfirsti(lst), PlannerParamVar);

			if (var->varlevelsup < PlannerQueryLevel &&
				!intMember(lfirsti(lst), result))
				result = lappendi(result, lfirsti(lst));
		}
	}
	else
		elog(ERROR, "_finalize_primnode: can't handle node %d",
			 nodeTag(expr));

	return (result);
}

Node *
SS_replace_correlation_vars(Node *expr)
{

	if (expr == NULL)
		return (NULL);
	if (IsA(expr, List))
	{
		List	   *le;

		foreach(le, (List *) expr)
			lfirst(le) = SS_replace_correlation_vars((Node *) lfirst(le));
	}
	else if (IsA(expr, Var))
	{
		if (((Var *) expr)->varlevelsup > 0)
		{
			Assert(((Var *) expr)->varlevelsup < PlannerQueryLevel);
			expr = (Node *) _replace_var((Var *) expr);
		}
	}
	else if (IsA(expr, Iter))
	{
		((Iter *) expr)->iterexpr =
			SS_replace_correlation_vars(((Iter *) expr)->iterexpr);
	}
	else if (single_node(expr))
		return (expr);
	else if (or_clause(expr) || and_clause(expr) || is_opclause(expr) ||
			 not_clause(expr) || is_funcclause(expr))
		((Expr *) expr)->args = (List *)
			SS_replace_correlation_vars((Node *) ((Expr *) expr)->args);
	else if (IsA(expr, Aggreg))
		((Aggreg *) expr)->target =
			SS_replace_correlation_vars((Node *) ((Aggreg *) expr)->target);
	else if (IsA(expr, ArrayRef))
	{
		((ArrayRef *) expr)->refupperindexpr = (List *)
			SS_replace_correlation_vars((Node *) ((ArrayRef *) expr)->refupperindexpr);
		((ArrayRef *) expr)->reflowerindexpr = (List *)
			SS_replace_correlation_vars((Node *) ((ArrayRef *) expr)->reflowerindexpr);
		((ArrayRef *) expr)->refexpr =
			SS_replace_correlation_vars((Node *) ((ArrayRef *) expr)->refexpr);
		((ArrayRef *) expr)->refassgnexpr =
			SS_replace_correlation_vars(((ArrayRef *) expr)->refassgnexpr);
	}
	else if (IsA(expr, TargetEntry))
		((TargetEntry *) expr)->expr =
			SS_replace_correlation_vars((Node *) ((TargetEntry *) expr)->expr);
	else if (IsA(expr, SubLink))
	{
		List	   *le;

		foreach(le, ((SubLink *) expr)->oper)	/* left sides only */
		{
			List	   *oparg = ((Expr *) lfirst(le))->args;

			lfirst(oparg) = (List *)
				SS_replace_correlation_vars((Node *) lfirst(oparg));
		}
		((SubLink *) expr)->lefthand = (List *)
			SS_replace_correlation_vars((Node *) ((SubLink *) expr)->lefthand);
	}
	else
		elog(NOTICE, "SS_replace_correlation_vars: can't handle node %d",
			 nodeTag(expr));

	return (expr);
}

Node *
SS_process_sublinks(Node *expr)
{
	if (expr == NULL)
		return (NULL);
	if (IsA(expr, List))
	{
		List	   *le;

		foreach(le, (List *) expr)
			lfirst(le) = SS_process_sublinks((Node *) lfirst(le));
	}
	else if (or_clause(expr) || and_clause(expr) || is_opclause(expr) ||
			 not_clause(expr) || is_funcclause(expr))
		((Expr *) expr)->args = (List *)
			SS_process_sublinks((Node *) ((Expr *) expr)->args);
	else if (IsA(expr, SubLink))/* got it! */
	{
          /* Hack to make sure expr->oper->args points to the same VAR node
           * as expr->lefthand does. Needed for subselects in the havingQual
           * when used on views.
           * Otherwise aggregate functions will fail later on (at execution 
           * time!) Reason: The rewite System makes several copies of the
           * VAR nodes and in this case it should not do so :-( */
	    if(((SubLink *) expr)->lefthand != NULL)
		{
			lfirst(((Expr *) lfirst(((SubLink *)expr)->oper))->args) = 
			lfirst(((SubLink *)expr)->lefthand);
		}
	    expr = _make_subplan((SubLink *) expr);
	}
	
	return (expr);
}

List *
SS_finalize_plan(Plan *plan)
{
	List	   *extParam = NULL;
	List	   *locParam = NULL;
	List	   *subPlan = NULL;
	List	   *param_list;
	List	   *lst;

	if (plan == NULL)
		return (NULL);

	param_list = _finalize_primnode(plan->targetlist, &subPlan);
	Assert(subPlan == NULL);

	switch (nodeTag(plan))
	{
		case T_Result:
			param_list = set_unioni(param_list,
									_finalize_primnode(((Result *) plan)->resconstantqual, &subPlan));
			break;

		case T_Append:
			foreach(lst, ((Append *) plan)->appendplans)
				param_list = set_unioni(param_list,
								 SS_finalize_plan((Plan *) lfirst(lst)));
			break;

		case T_IndexScan:
			param_list = set_unioni(param_list,
			_finalize_primnode(((IndexScan *) plan)->indxqual, &subPlan));
			Assert(subPlan == NULL);
			break;

		case T_MergeJoin:
			param_list = set_unioni(param_list,
									_finalize_primnode(((MergeJoin *) plan)->mergeclauses, &subPlan));
			Assert(subPlan == NULL);
			break;

		case T_HashJoin:
			param_list = set_unioni(param_list,
									_finalize_primnode(((HashJoin *) plan)->hashclauses, &subPlan));
			Assert(subPlan == NULL);
			break;

		case T_Hash:
			param_list = set_unioni(param_list,
				 _finalize_primnode(((Hash *) plan)->hashkey, &subPlan));
			Assert(subPlan == NULL);
			break;

		case T_Agg:
			param_list = set_unioni(param_list,
					 _finalize_primnode(((Agg *) plan)->aggs, &subPlan));
			Assert(subPlan == NULL);
			break;

		case T_SeqScan:
		case T_NestLoop:
		case T_Material:
		case T_Sort:
		case T_Unique:
		case T_Group:
			break;
		default:
			elog(ERROR, "SS_finalize_plan: node %d unsupported", nodeTag(plan));
			return (NULL);
	}

	param_list = set_unioni(param_list, _finalize_primnode(plan->qual, &subPlan));
	param_list = set_unioni(param_list, SS_finalize_plan(plan->lefttree));
	param_list = set_unioni(param_list, SS_finalize_plan(plan->righttree));

	foreach(lst, param_list)
	{
		Var		   *var = nth(lfirsti(lst), PlannerParamVar);

		if (var->varlevelsup < PlannerQueryLevel)
			extParam = lappendi(extParam, lfirsti(lst));
		else if (var->varlevelsup > PlannerQueryLevel)
			elog(ERROR, "SS_finalize_plan: plan shouldn't reference subplan' variable");
		else
		{
			Assert(var->varno == 0 && var->varattno == 0);
			locParam = lappendi(locParam, lfirsti(lst));
		}
	}

	plan->extParam = extParam;
	plan->locParam = locParam;
	plan->subPlan = subPlan;

	return (param_list);

}

List	   *SS_pull_subplan(void *expr);

List *
SS_pull_subplan(void *expr)
{
	List	   *result = NULL;

	if (expr == NULL || single_node(expr))
		return (NULL);

	if (IsA(expr, List))
	{
		List	   *le;

		foreach(le, (List *) expr)
			result = nconc(result, SS_pull_subplan(lfirst(le)));
	}
	else if (IsA(expr, Iter))
		return (SS_pull_subplan(((Iter *) expr)->iterexpr));
	else if (or_clause(expr) || and_clause(expr) || is_opclause(expr) ||
			 not_clause(expr) || is_funcclause(expr))
		return (SS_pull_subplan(((Expr *) expr)->args));
	else if (IsA(expr, Aggreg))
		return (SS_pull_subplan(((Aggreg *) expr)->target));
	else if (IsA(expr, ArrayRef))
	{
		result = SS_pull_subplan(((ArrayRef *) expr)->refupperindexpr);
		result = nconc(result,
				  SS_pull_subplan(((ArrayRef *) expr)->reflowerindexpr));
		result = nconc(result,
					   SS_pull_subplan(((ArrayRef *) expr)->refexpr));
		result = nconc(result,
					 SS_pull_subplan(((ArrayRef *) expr)->refassgnexpr));
	}
	else if (IsA(expr, TargetEntry))
		return (SS_pull_subplan(((TargetEntry *) expr)->expr));
	else if (is_subplan(expr))
		return (lcons(((Expr *) expr)->oper, NULL));
	else
		elog(ERROR, "SS_pull_subplan: can't handle node %d",
			 nodeTag(expr));

	return (result);
}
