/*-------------------------------------------------------------------------
 *
 * 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! */
		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)->unionplans)
				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);
}
