package ModSQL;
import java.sql.*;
import java.util.*;

/* $Id: AbstractAggregate.java,v 1.8 2003/05/29 04:28:21 cvs Exp $
 *
 * Copyright (c) 2003 Chris Studholme <chris.studholme@utoronto.ca>
 *
 * May be copied or modified under the terms of the GNU General Public
 * License.  See COPYING for more information.
 */

/**
 * Abstract implementation of Aggregate to simplify the implementation
 * of real aggregate functions.
 *
 * @author chris.studholme@utoronto.ca
 */
public abstract class AbstractAggregate extends AbstractFunction 
  implements Aggregate {

  /** True if the aggregate should eliminate duplicate values before
   * aggregating. */
  protected boolean distinct=false;

  /** Hash table used to eliminate duplicate values. */
  protected HashSet distinct_set=null;
  
  /**
   * Specify that this aggregate should eliminate duplicate values before
   * aggregating rows.
   *
   * @throws SQLException if DISTINCT is not allowed
   */
  public void setDistinct() throws SQLException {
    distinct = true;
  }

  /**
   * Returns "DISTINCT" if distinct was requested, or "*" if the parameter
   * list is empty and optimize() has already been called.
   *
   * @return "DISTINCT ", "*", or "" as needed
   */
  public String prefixParameters() {
    if (distinct)
      return "DISTINCT ";
    else if (parameters!=null && parameters.length==0)
      return "*";
    return "";
  }

  /**
   * This implementation blocks the registerWith() call if the supplied
   * object is of type GroupByList to prevent ColumnValue's that appear
   * in the GROUP BY list from mistakenly being labled as aggregate.
   * Otherwise, super.registerWith() is used.
   *
   * @param o object to register with
   * @throws SQLException if an error occurs
   */
  public void registerWith(Object o) throws SQLException {
    if (!(o instanceof Select.GroupByList))
      super.registerWith(o);
  }


  /****************  result meta-data  ****************/

  /**
   * Determine if this function returns a constant value.  Aggregate functions
   * are rarely constant (even if all of their parameters are constant) so
   * this method always returns false.
   *
   * @return false
   * @throws SQLException if an error occurs
   */
  public boolean isConstant() throws SQLException {
    return false;
  }

  /**
   * Aggregate functions will always return true when isAggregate() is
   * called, unless one of the parameters is an aggregate.  In this latter
   * case, an exception is thrown as aggregates within aggregates are not
   * allowed in SQL.
   *
   * @return true unless exception
   * @throws SQLException if any parameters are aggregates
   */
  public boolean isAggregate() throws SQLException {
    if (parameters==null)
      throw new SQLException("cannot call isAggregate before optimize");
    for (int i=0; i<parameters.length; ++i)
      if (parameters[i].isAggregate())
	throw new SQLException("aggregate function with aggregate parameters");
    return true;
  }

  /****************  evaluate methods  ****************/

  /**
   * <p>This implemenation calls reset() for all parameters and clears the
   * distinct_set hash table.  Derived classes should override this method
   * (but still call it) to reset its state.
   *
   * @throws SQLException if an error occurs
   */
  public void reset() throws SQLException {
    super.reset();
    if (distinct_set!=null)
      distinct_set.clear();
  }

  /**
   * Add a value to the set of distinct values.  This method initializes
   * distinct_set if null.
   *
   * @return true if the value was not already in the distinct set
   */
  public boolean addDistinctValue(Object value) {
    if (distinct_set==null)
      distinct_set = new HashSet(DriverConfig.getHashTableSize());
    return distinct_set.add(value);
  }

  /**
   * Get the size of the set of distinct values.
   *
   * @return number of distinct values in hash table
   */
  public int getDistinctSetSize() {
    return distinct_set!=null ? distinct_set.size() : 0;
  }

};
