/*-------------------------------------------------------------------------
 *
 * clauses.c
 *	  routines to manipulate qualification clauses
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.33 1999/05/10 00:45:24 momjian Exp $
 *
 * HISTORY
 *	  AUTHOR			DATE			MAJOR EVENT
 *	  Andrew Yu			Nov 3, 1994		clause.c and clauses.c combined
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <catalog/pg_operator.h>
#include "nodes/primnodes.h"
#include "nodes/relation.h"
#include "nodes/parsenodes.h"
#include "nodes/plannodes.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"

#include "catalog/pg_aggregate.h"

#include "utils/syscache.h"
#include "utils/lsyscache.h"

#include "optimizer/clauses.h"
#include "optimizer/internal.h"
#include "optimizer/var.h"

static bool agg_clause(Node *clause);


Expr *
make_clause(int type, Node *oper, List *args)
{
	if (type == AND_EXPR || type == OR_EXPR || type == NOT_EXPR ||
		type == OP_EXPR || type == FUNC_EXPR)
	{
		Expr	   *expr = makeNode(Expr);

		/*
		 * assume type checking already done and we don't need the type of
		 * the expr any more.
		 */
		expr->typeOid = InvalidOid;
		expr->opType = type;
		expr->oper = oper;		/* ignored for AND, OR, NOT */
		expr->args = args;
		return expr;
	}
	else
	{
		elog(ERROR, "make_clause: unsupported type %d", type);
		/* will this ever happen? translated from lispy C code - ay 10/94 */
		return (Expr *) args;
	}
}


/*****************************************************************************
 *		OPERATOR clause functions
 *****************************************************************************/


/*
 * is_opclause
 *
 * Returns t iff the clause is an operator clause:
 *				(op expr expr) or (op expr).
 *
 * [historical note: is_clause has the exact functionality and is used
 *		throughout the code. They're renamed to is_opclause for clarity.
 *												- ay 10/94.]
 */
bool
is_opclause(Node *clause)
{
	return (clause != NULL &&
	 nodeTag(clause) == T_Expr && ((Expr *) clause)->opType == OP_EXPR);
}

/*
 * make_opclause
 *	  Creates a clause given its operator left operand and right
 *	  operand (if it is non-null).
 *
 */
Expr *
make_opclause(Oper *op, Var *leftop, Var *rightop)
{
	Expr	   *expr = makeNode(Expr);

	expr->typeOid = InvalidOid; /* assume type checking done */
	expr->opType = OP_EXPR;
	expr->oper = (Node *) op;
	if (rightop)
		expr->args = lcons(leftop, lcons(rightop, NIL));
	else
		expr->args = lcons(leftop, NIL);
	return expr;
}

/*
 * get_leftop
 *
 * Returns the left operand of a clause of the form (op expr expr)
 *		or (op expr)
 * NB: it is assumed (for now) that all expr must be Var nodes
 */
Var *
get_leftop(Expr *clause)
{
	if (clause->args != NULL)
		return lfirst(clause->args);
	else
		return NULL;
}

/*
 * get_rightop
 *
 * Returns the right operand in a clause of the form (op expr expr).
 * NB: result will be NULL if applied to a unary op clause.
 */
Var *
get_rightop(Expr *clause)
{
	if (clause->args != NULL && lnext(clause->args) != NULL)
		return lfirst(lnext(clause->args));
	else
		return NULL;
}

/*****************************************************************************
 *		AGG clause functions
 *****************************************************************************/

static bool
agg_clause(Node *clause)
{
	return (clause != NULL && nodeTag(clause) == T_Aggref);
}

/*****************************************************************************
 *		FUNC clause functions
 *****************************************************************************/

/*
 * is_funcclause
 *
 * Returns t iff the clause is a function clause: (func { expr }).
 *
 */
bool
is_funcclause(Node *clause)
{
	return (clause != NULL &&
	 nodeTag(clause) == T_Expr && ((Expr *) clause)->opType == FUNC_EXPR);
}

/*
 * make_funcclause
 *
 * Creates a function clause given the FUNC node and the functional
 * arguments.
 *
 */
Expr *
make_funcclause(Func *func, List *funcargs)
{
	Expr	   *expr = makeNode(Expr);

	expr->typeOid = InvalidOid; /* assume type checking done */
	expr->opType = FUNC_EXPR;
	expr->oper = (Node *) func;
	expr->args = funcargs;
	return expr;
}

/*****************************************************************************
 *		OR clause functions
 *****************************************************************************/

/*
 * or_clause
 *
 * Returns t iff the clause is an 'or' clause: (OR { expr }).
 *
 */
bool
or_clause(Node *clause)
{
	return clause != NULL &&
	nodeTag(clause) == T_Expr &&
	((Expr *) clause)->opType == OR_EXPR;
}

/*
 * make_orclause
 *
 * Creates an 'or' clause given a list of its subclauses.
 *
 */
Expr *
make_orclause(List *orclauses)
{
	Expr	   *expr = makeNode(Expr);

	expr->typeOid = InvalidOid; /* assume type checking done */
	expr->opType = OR_EXPR;
	expr->oper = NULL;
	expr->args = orclauses;
	return expr;
}

/*****************************************************************************
 *		NOT clause functions
 *****************************************************************************/

/*
 * not_clause
 *
 * Returns t iff this is a 'not' clause: (NOT expr).
 *
 */
bool
not_clause(Node *clause)
{
	return (clause != NULL &&
	 nodeTag(clause) == T_Expr && ((Expr *) clause)->opType == NOT_EXPR);
}

/*
 * make_notclause
 *
 * Create a 'not' clause given the expression to be negated.
 *
 */
Expr *
make_notclause(Expr *notclause)
{
	Expr	   *expr = makeNode(Expr);

	expr->typeOid = InvalidOid; /* assume type checking done */
	expr->opType = NOT_EXPR;
	expr->oper = NULL;
	expr->args = lcons(notclause, NIL);
	return expr;
}

/*
 * get_notclausearg
 *
 * Retrieve the clause within a 'not' clause
 *
 */
Expr *
get_notclausearg(Expr *notclause)
{
	return lfirst(notclause->args);
}

/*****************************************************************************
 *		AND clause functions
 *****************************************************************************/


/*
 * and_clause
 *
 * Returns t iff its argument is an 'and' clause: (AND { expr }).
 *
 */
bool
and_clause(Node *clause)
{
	return (clause != NULL &&
	 nodeTag(clause) == T_Expr && ((Expr *) clause)->opType == AND_EXPR);
}

/*
 * make_andclause
 *
 * Create an 'and' clause given its arguments in a list.
 *
 */
Expr *
make_andclause(List *andclauses)
{
	Expr	   *expr = makeNode(Expr);

	expr->typeOid = InvalidOid; /* assume type checking done */
	expr->opType = AND_EXPR;
	expr->oper = NULL;
	expr->args = andclauses;
	return expr;
}


/*****************************************************************************
 *		CASE clause functions
 *****************************************************************************/


/*
 * case_clause
 *
 * Returns t iff its argument is a 'case' clause: (CASE { expr }).
 *
 */
bool
case_clause(Node *clause)
{
	return (clause != NULL &&
	 nodeTag(clause) == T_CaseExpr);
}

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


/*
 * pull_constant_clauses
 *	  Scans through a list of qualifications and find those that
 *	  contain no variables.
 *
 * Returns a list of the constant clauses in constantQual and the remaining
 * quals as the return value.
 *
 */
List *
pull_constant_clauses(List *quals, List **constantQual)
{
	List	   *q;
	List	   *constqual = NIL;
	List	   *restqual = NIL;

	foreach(q, quals)
	{
		if (!contain_var_clause(lfirst(q)))
			constqual = lcons(lfirst(q), constqual);
		else
			restqual = lcons(lfirst(q), restqual);
	}
	freeList(quals);
	*constantQual = constqual;
	return restqual;
}

/*
 * clause_relids_vars
 *	  Retrieves relids and vars appearing within a clause.
 *	  Returns ((relid1 relid2 ... relidn) (var1 var2 ... varm)) where
 *	  vars appear in the clause  this is done by recursively searching
 *	  through the left and right operands of a clause.
 *
 * Returns the list of relids and vars.
 *
 */
void
clause_get_relids_vars(Node *clause, Relids *relids, List **vars)
{
	List	   *clvars = pull_var_clause(clause);
	List	   *var_list = NIL;
	List	   *varno_list = NIL;
	List	   *i = NIL;

	foreach(i, clvars)
	{
		Var		   *var = (Var *) lfirst(i);
		List	   *vi;

		if (!intMember(var->varno, varno_list))
			varno_list = lappendi(varno_list, var->varno);
		foreach(vi, var_list)
		{
			Var		   *in_list = (Var *) lfirst(vi);

			Assert(var->varlevelsup == 0);
			if (in_list->varno == var->varno &&
				in_list->varattno == var->varattno)
				break;
		}
		if (vi == NIL)
			var_list = lappend(var_list, var);
	}

	*relids = varno_list;
	*vars = var_list;
	return;
}

/*
 * NumRelids
 *		(formerly clause_relids)
 *
 * Returns the number of different relations referenced in 'clause'.
 */
int
NumRelids(Node *clause)
{
	List	   *vars = pull_var_clause(clause);
	List	   *i = NIL;
	List	   *var_list = NIL;

	foreach(i, vars)
	{
		Var		   *var = (Var *) lfirst(i);

		if (!intMember(var->varno, var_list))
			var_list = lconsi(var->varno, var_list);
	}

	return length(var_list);
}

/*
 * contains_not
 *
 * Returns t iff the clause is a 'not' clause or if any of the
 * subclauses within an 'or' clause contain 'not's.
 *
 */
bool
contains_not(Node *clause)
{
	if (single_node(clause))
		return false;

	if (not_clause(clause))
		return true;

	if (or_clause(clause) || and_clause(clause))
	{
		List	   *a;

		foreach(a, ((Expr *) clause)->args)
		{
			if (contains_not(lfirst(a)))
				return true;
		}
	}

	return false;
}

/*
 * is_joinable
 *
 * Returns t iff 'clause' is a valid join clause.
 *
 */
bool
is_joinable(Node *clause)
{
	Node	   *leftop,
			   *rightop;

	if (!is_opclause(clause))
		return false;

	leftop = (Node *) get_leftop((Expr *) clause);
	rightop = (Node *) get_rightop((Expr *) clause);

	if (!rightop)
		return false;			/* unary opclauses need not apply */

	/*
	 * One side of the clause (i.e. left or right operands) must either be
	 * a var node ...
	 */
	if (IsA(leftop, Var) ||IsA(rightop, Var))
		return true;

	/*
	 * ... or a func node.
	 */
	if (is_funcclause(leftop) || is_funcclause(rightop))
		return true;

	return false;
}

/*
 * qual_clause_p
 *
 * Returns t iff 'clause' is a valid qualification clause.
 *
 * For now we accept only "var op const" or "const op var".
 */
bool
qual_clause_p(Node *clause)
{
	Node	   *leftop,
			   *rightop;

	if (!is_opclause(clause))
		return false;

	leftop = (Node *) get_leftop((Expr *) clause);
	rightop = (Node *) get_rightop((Expr *) clause);

	if (!rightop)
		return false;			/* unary opclauses need not apply */

	/* How about Param-s ?	- vadim 02/03/98 */
	if (IsA(leftop, Var) && IsA(rightop, Const))
		return true;
	if (IsA(rightop, Var) && IsA(leftop, Const))
		return true;
	return false;
}

/*
 * fix_opid
 *	  Calculate the opfid from the opno...
 *
 * Returns nothing.
 *
 */
void
fix_opid(Node *clause)
{
	if (clause == NULL || single_node(clause))
		;
	else if (or_clause(clause) || and_clause(clause))
		fix_opids(((Expr *) clause)->args);
	else if (is_funcclause(clause))
		fix_opids(((Expr *) clause)->args);
	else if (IsA(clause, ArrayRef))
	{
		ArrayRef   *aref = (ArrayRef *) clause;

		fix_opids(aref->refupperindexpr);
		fix_opids(aref->reflowerindexpr);
		fix_opid(aref->refexpr);
		fix_opid(aref->refassgnexpr);
	}
	else if (not_clause(clause))
		fix_opid((Node *) get_notclausearg((Expr *) clause));
	else if (is_opclause(clause))
	{
		replace_opid((Oper *) ((Expr *) clause)->oper);
		fix_opid((Node *) get_leftop((Expr *) clause));
		fix_opid((Node *) get_rightop((Expr *) clause));
	}
	else if (agg_clause(clause))
		fix_opid(((Aggref *) clause)->target);
	else if (is_subplan(clause) &&
			 ((SubPlan *) ((Expr *) clause)->oper)->sublink->subLinkType != EXISTS_SUBLINK)
	{
		List	   *lst;

		foreach(lst, ((SubPlan *) ((Expr *) clause)->oper)->sublink->oper)
		{
			replace_opid((Oper *) ((Expr *) lfirst(lst))->oper);
			fix_opid((Node *) get_leftop((Expr *) lfirst(lst)));
		}
	}
	else if (case_clause(clause))
	{
		List	   *lst;

		fix_opid(((CaseExpr *) clause)->defresult);

		/* Run through the WHEN clauses... */
		foreach(lst, ((CaseExpr *) clause)->args)
		{
			fix_opid(((CaseWhen *) lfirst(lst))->expr);
			fix_opid(((CaseWhen *) lfirst(lst))->result);
		}
	}

}

/*
 * fix_opids
 *	  Calculate the opfid from the opno for all the clauses...
 *
 * Returns its argument.
 *
 */
List *
fix_opids(List *clauses)
{
	List	   *clause;

	foreach(clause, clauses)
		fix_opid(lfirst(clause));

	return clauses;
}

/*
 * get_relattval
 *		For a non-join clause, returns a list consisting of the
 *				relid,
 *				attno,
 *				value of the CONST node (if any), and a
 *				flag indicating whether the value appears on the left or right
 *						of the operator and whether the value varied.
 *
 * OLD OBSOLETE COMMENT FOLLOWS:
 *		If 'clause' is not of the format (op var node) or (op node var),
 *		or if the var refers to a nested attribute, then -1's are returned for
 *		everything but the value  a blank string "" (pointer to \0) is
 *		returned for the value if it is unknown or null.
 * END OF OLD OBSOLETE COMMENT.
 * NEW COMMENT:
 * when defining rules one of the attributes of the operator can
 * be a Param node (which is supposed to be treated as a constant).
 * However as there is no value specified for a parameter until run time
 * this routine used to return "" as value, which caused 'compute_selec'
 * to bomb (because it was expecting a lisp integer and got back a lisp
 * string). Now the code returns a plain old good "lispInteger(0)".
 */
void
get_relattval(Node *clause,
			  int *relid,
			  AttrNumber *attno,
			  Datum *constval,
			  int *flag)
{
	Var		   *left,
			   *right;

	/* Careful; the passed clause might not be a binary operator at all */

	if (!is_opclause(clause))
		goto default_results;

	left = get_leftop((Expr *) clause);
	right = get_rightop((Expr *) clause);

	if (!right)
		goto default_results;

	if (IsA(left, Var) && IsA(right, Const))
	{
		*relid = left->varno;
		*attno = left->varattno;
		*constval = ((Const *) right)->constvalue;
		*flag = (_SELEC_CONSTANT_RIGHT_ | _SELEC_IS_CONSTANT_);
	}
	else if (IsA(left, Var) && IsA(right, Param))
	{
		*relid = left->varno;
		*attno = left->varattno;
		*constval = 0;
		*flag = (_SELEC_NOT_CONSTANT_);
	}
	else if (is_funcclause((Node *) left) && IsA(right, Const))
	{
		List	   *vars = pull_var_clause((Node *) left);

		*relid = ((Var *) lfirst(vars))->varno;
		*attno = InvalidAttrNumber;
		*constval = ((Const *) right)->constvalue;
		*flag = (_SELEC_CONSTANT_RIGHT_ | _SELEC_IS_CONSTANT_);
	}
	else if (IsA(right, Var) && IsA(left, Const))
	{
		*relid = right->varno;
		*attno = right->varattno;
		*constval = ((Const *) left)->constvalue;
		*flag = (_SELEC_IS_CONSTANT_);
	}
	else if (IsA(right, Var) &&IsA(left, Param))
	{
		*relid = right->varno;
		*attno = right->varattno;
		*constval = 0;
		*flag = (_SELEC_NOT_CONSTANT_);
	}
	else if (is_funcclause((Node *) right) && IsA(left, Const))
	{
		List	   *vars = pull_var_clause((Node *) right);

		*relid = ((Var *) lfirst(vars))->varno;
		*attno = InvalidAttrNumber;
		*constval = ((Const *) left)->constvalue;
		*flag = (_SELEC_IS_CONSTANT_);
	}
	else
	{
		/* Duh, it's too complicated for me... */
default_results:
		*relid = _SELEC_VALUE_UNKNOWN_;
		*attno = _SELEC_VALUE_UNKNOWN_;
		*constval = 0;
		*flag = (_SELEC_NOT_CONSTANT_);
	}
}

/*
 * get_relsatts
 *
 * Returns a list
 *				( relid1 attno1 relid2 attno2 )
 *		for a joinclause.
 *
 * If the clause is not of the form (op var var) or if any of the vars
 * refer to nested attributes, then -1's are returned.
 *
 */
void
get_rels_atts(Node *clause,
			  int *relid1,
			  AttrNumber *attno1,
			  int *relid2,
			  AttrNumber *attno2)
{
	if (is_opclause(clause))
	{
		Var	   *left = get_leftop((Expr *) clause);
		Var	   *right = get_rightop((Expr *) clause);

		if (left && right)
		{
			bool	var_left = IsA(left, Var);
			bool	var_right = IsA(right, Var);
			bool	varexpr_left = (bool) ((IsA(left, Func) || IsA(left, Oper)) &&
									   contain_var_clause((Node *) left));
			bool	varexpr_right = (bool) ((IsA(right, Func) || IsA(right, Oper)) &&
									 contain_var_clause((Node *) right));

			if (var_left && var_right)
			{

				*relid1 = left->varno;
				*attno1 = left->varoattno;
				*relid2 = right->varno;
				*attno2 = right->varoattno;
				return;
			}
			if (var_left && varexpr_right)
			{

				*relid1 = left->varno;
				*attno1 = left->varoattno;
				*relid2 = _SELEC_VALUE_UNKNOWN_;
				*attno2 = _SELEC_VALUE_UNKNOWN_;
				return;
			}
			if (varexpr_left && var_right)
			{

				*relid1 = _SELEC_VALUE_UNKNOWN_;
				*attno1 = _SELEC_VALUE_UNKNOWN_;
				*relid2 = right->varno;
				*attno2 = right->varoattno;
				return;
			}
		}
	}

	*relid1 = _SELEC_VALUE_UNKNOWN_;
	*attno1 = _SELEC_VALUE_UNKNOWN_;
	*relid2 = _SELEC_VALUE_UNKNOWN_;
	*attno2 = _SELEC_VALUE_UNKNOWN_;
}

void
CommuteClause(Node *clause)
{
	Node	   *temp;
	Oper	   *commu;
	Form_pg_operator commuTup;
	HeapTuple	heapTup;

	if (!is_opclause(clause))
		elog(ERROR, "CommuteClause: applied to non-operator clause");

	heapTup = (HeapTuple)
		get_operator_tuple(get_commutator(((Oper *) ((Expr *) clause)->oper)->opno));

	if (heapTup == (HeapTuple) NULL)
		elog(ERROR, "CommuteClause: no commutator for operator %u",
			 ((Oper *) ((Expr *) clause)->oper)->opno);

	commuTup = (Form_pg_operator) GETSTRUCT(heapTup);

	commu = makeOper(heapTup->t_data->t_oid,
					 commuTup->oprcode,
					 commuTup->oprresult,
					 ((Oper *) ((Expr *) clause)->oper)->opsize,
					 NULL);

	/*
	 * re-form the clause in-place!
	 */
	((Expr *) clause)->oper = (Node *) commu;
	temp = lfirst(((Expr *) clause)->args);
	lfirst(((Expr *) clause)->args) = lsecond(((Expr *) clause)->args);
	lsecond(((Expr *) clause)->args) = temp;
}
