/*-------------------------------------------------------------------------
 *
 * clausesel.c--
 *    Routines to compute and set clause selectivities 
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    $Header: /usr/local/cvsroot/postgres95/src/backend/optimizer/path/clausesel.c,v 1.1.1.1 1996/07/09 06:21:35 scrappy Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "nodes/pg_list.h"
#include "nodes/relation.h"
#include "nodes/primnodes.h"

#include "optimizer/internal.h"
#include "optimizer/clauses.h"
#include "optimizer/clauseinfo.h"
#include "optimizer/cost.h"
#include "optimizer/plancat.h"

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

#include "catalog/pg_proc.h"
#include "catalog/pg_operator.h"

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

static Cost compute_selec(Query *root, List *clauses, List *or_selectivities);

/****************************************************************************
 * 	ROUTINES TO SET CLAUSE SELECTIVITIES  
 ****************************************************************************/

/*    
 * set_clause_selectivities -
 *    Sets the selectivity field for each of clause in 'clauseinfo-list'
 *    to 'new-selectivity'.  If the selectivity has already been set, reset 
 *    it only if the new one is better.
 *    
 * Returns nothing of interest.
 *
 */
void
set_clause_selectivities(List *clauseinfo_list, Cost new_selectivity)
{
    List *temp;
    CInfo *clausenode;
    Cost cost_clause;

    foreach (temp,clauseinfo_list) {
	clausenode = (CInfo*)lfirst(temp);
	cost_clause = clausenode->selectivity;
	if ( FLOAT_IS_ZERO(cost_clause) || new_selectivity < cost_clause) {
	    clausenode->selectivity = new_selectivity;
	}
    }
}

/*    
 * product_selec -
 *    Multiplies the selectivities of each clause in 'clauseinfo-list'.
 *    
 * Returns a flonum corresponding to the selectivity of 'clauseinfo-list'.
 */
Cost
product_selec(List *clauseinfo_list)
{
    Cost result = 1.0;
    if (clauseinfo_list!=NIL) {
	List *xclausenode = NIL;
	Cost temp;

	foreach(xclausenode,clauseinfo_list) {
	    temp = ((CInfo *)lfirst(xclausenode))->selectivity;
	    result = result * temp;
	}
    }
    return(result);
}

/*    
 * set_rest_relselec -
 *    Scans through clauses on each relation and assigns a selectivity to
 *    those clauses that haven't been assigned a selectivity by an index.
 *    
 * Returns nothing of interest.
 * MODIFIES: selectivities of the various rel's clauseinfo
 *	  slots. 
 */
void
set_rest_relselec(Query *root, List *rel_list)
{
    Rel *rel;
    List *x;

    foreach (x,rel_list) {
	rel = (Rel*)lfirst(x);
	set_rest_selec(root, rel->clauseinfo);
    }
}

/*    
 * set_rest_selec -
 *    Sets the selectivity fields for those clauses within a single
 *    relation's 'clauseinfo-list' that haven't already been set.
 *    
 * Returns nothing of interest.
 *    
 */
void
set_rest_selec(Query *root, List *clauseinfo_list)
{
    List *temp = NIL;
    CInfo *clausenode = (CInfo*)NULL;
    Cost cost_clause;
    
    foreach (temp,clauseinfo_list) {
	clausenode = (CInfo*)lfirst(temp);
	cost_clause = clausenode->selectivity;

	/*
	 * Check to see if the selectivity of this clause or any 'or'
	 * subclauses (if any) haven't been set yet.
	 */
	if (valid_or_clause(clausenode) || FLOAT_IS_ZERO(cost_clause)) {
	    clausenode->selectivity =
		compute_clause_selec(root, 
				     (Node*)clausenode->clause,
				     lcons(makeFloat(cost_clause), NIL));
	}
    }
}

/****************************************************************************
 *	ROUTINES TO COMPUTE SELECTIVITIES
 ****************************************************************************/

/*    
 * compute_clause_selec -
 *    Given a clause, this routine will compute the selectivity of the
 *    clause by calling 'compute_selec' with the appropriate parameters
 *    and possibly use that return value to compute the real selectivity
 *    of a clause.
 *    
 * 'or-selectivities' are selectivities that have already been assigned
 * 	to subclauses of an 'or' clause.
 *    
 * Returns a flonum corresponding to the clause selectivity.
 *    
 */
Cost
compute_clause_selec(Query *root, Node *clause, List *or_selectivities)
{
    if (!is_opclause (clause)) {
	/* if it's not an operator clause, then it is a boolean clause -jolly*/
	/*
	 * Boolean variables get a selectivity of 1/2.
	 */
	return(0.1);
    } else if (not_clause (clause)) {
	/*
	 * 'not' gets "1.0 - selectivity-of-inner-clause".
	 */
	return (1.000000 - compute_selec(root,
					 lcons(get_notclausearg((Expr*)clause),
					      NIL),
					 or_selectivities));
    } else if (or_clause(clause)) {
	/*
	 * Both 'or' and 'and' clauses are evaluated as described in 
	 *    (compute_selec). 
	 */
	return (compute_selec(root,
			      ((Expr*)clause)->args, or_selectivities));
    } else {
	return(compute_selec(root,
			     lcons(clause,NIL),or_selectivities));
    } 
}

/*    
 * compute_selec - 
 *    Computes the selectivity of a clause.
 *    
 *    If there is more than one clause in the argument 'clauses', then the
 *    desired selectivity is that of an 'or' clause.  Selectivities for an
 *    'or' clause such as (OR a b) are computed by finding the selectivity
 *    of a (s1) and b (s2) and computing s1+s2 - s1*s2.
 *    
 *    In addition, if the clause is an 'or' clause, individual selectivities
 *    may have already been assigned by indices to subclauses.  These values
 *    are contained in the list 'or-selectivities'.
 *    
 * Returns the clause selectivity as a flonum.
 *    
 */
static Cost
compute_selec(Query *root, List *clauses, List *or_selectivities)
{
    Cost s1 = 0;
    List *clause = lfirst(clauses);

    if (clauses==NULL) {
	s1 = 1.0;
    } else if (IsA(clause,Param)) {
	/* XXX How're we handling this before?? -ay */
	s1 = 1.0;
    } else if (IsA(clause,Const)) {
	s1 = ((bool) ((Const*) clause)->constvalue) ? 1.0 : 0.0;
    } else if (IsA(clause,Var)) {
	Oid relid = getrelid(((Var*)clause)->varno,
			     root->rtable);

	/*
	 * we have a bool Var.  This is exactly equivalent to the clause:
	 *	reln.attribute = 't'
	 * so we compute the selectivity as if that is what we have. The
	 * magic #define constants are a hack.  I didn't want to have to
	 * do system cache look ups to find out all of that info.
	 */

	s1 = restriction_selectivity(EqualSelectivityProcedure,
				     BooleanEqualOperator,
				     relid,
				     ((Var*)clause)->varoattno,
				     "t",
				     _SELEC_CONSTANT_RIGHT_);
    } else if (or_selectivities) {
	/* If s1 has already been assigned by an index, use that value. */ 
	List *this_sel = lfirst(or_selectivities);

	s1 = floatVal(this_sel);
    } else if (is_funcclause((Node*)clause)) {
	/* this isn't an Oper, it's a Func!! */
	/*
	 ** This is not an operator, so we guess at the selectivity.  
	 ** THIS IS A HACK TO GET V4 OUT THE DOOR.  FUNCS SHOULD BE
	 ** ABLE TO HAVE SELECTIVITIES THEMSELVES.
	 **     -- JMH 7/9/92
	 */
	s1 = 0.1;
    } else if (NumRelids((Node*) clause) == 1) {
	/* ...otherwise, calculate s1 from 'clauses'. 
	 *    The clause is not a join clause, since there is 
	 *    only one relid in the clause.  The clause 
	 *    selectivity will be based on the operator 
	 *    selectivity and operand values. 
	 */
	Oid opno = ((Oper*)((Expr*)clause)->oper)->opno;
	RegProcedure oprrest = get_oprrest(opno);
	Oid relid;
	int relidx;
	AttrNumber attno;
	Datum constval;
	int flag;

	get_relattval((Node*)clause, &relidx, &attno, &constval, &flag);
	relid = getrelid(relidx, root->rtable); 
	
	/* if the oprrest procedure is missing for whatever reason,
	   use a selectivity of 0.5*/
	if (!oprrest)
	    s1 = (Cost) (0.5);
	else
	    if (attno == InvalidAttrNumber)  {
		/* attno can be Invalid if the clause had a function in it,
		   i.e.   WHERE myFunc(f) = 10 */
		/* this should be FIXED somehow to use function selectivity */
		s1 = (Cost) (0.5);
	    } else
		s1 = (Cost) restriction_selectivity(oprrest,
						opno,
						relid,
						attno,
						(char *)constval,
						flag);

    } else {
	/*    The clause must be a join clause.  The clause 
	 *    selectivity will be based on the relations to be 
	 *    scanned and the attributes they are to be joined 
	 *    on. 
	 */
	Oid opno = ((Oper*)((Expr*)clause)->oper)->opno;
	RegProcedure oprjoin = get_oprjoin (opno);
	int relid1, relid2;
	AttrNumber attno1, attno2;

	get_rels_atts((Node*)clause, &relid1, &attno1, &relid2, &attno2);
	relid1 = getrelid(relid1, root->rtable);
	relid2 = getrelid(relid2, root->rtable);

	/* if the oprjoin procedure is missing for whatever reason,
	   use a selectivity of 0.5*/
	if (!oprjoin)
	    s1 = (Cost) (0.5);
	else
	    s1 = (Cost) join_selectivity(oprjoin,
					 opno,
					 relid1,
					 attno1,
					 relid2,
					 attno2);
    }
    
    /*    A null clause list eliminates no tuples, so return a selectivity 
     *    of 1.0.  If there is only one clause, the selectivity is not 
     *    that of an 'or' clause, but rather that of the single clause.
     */
    
    if (length (clauses) < 2) {
	return(s1);
    } else {
	/* Compute selectivity of the 'or'ed subclauses. */
	/* Added check for taking lnext(NIL).  -- JMH 3/9/92 */
	Cost s2;

	if (or_selectivities != NIL)
	    s2 = compute_selec(root, lnext(clauses), lnext(or_selectivities));
	else
	    s2 = compute_selec(root, lnext(clauses), NIL);
	return(s1 + s2 - s1 * s2);
    }
}

