package ModSQL;
import java.sql.*;

/* $Id: ColumnValue.java,v 1.13 2003/05/29 04:28:11 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.
 */

/**
 * This object is used to retrieve the value of specific of a database table.
 *
 * @author chris.studholme@utoronto.ca
 */
public class ColumnValue implements Function {

  /** TableReader managing the table we get data from. */
  private TableReader reader;
  
  /** Index of table in TableReader. */
  protected int table;
  /** Index of column within table. */
  protected int column;
  /** Set to true if an index was searched for. */
  private boolean index_checked;
  /** Set to true if this column is indexed. */
  private boolean indexed;

  /** Set to true if the value is valid. */
  private boolean value_valid=false;
  /** Value of column (valid if value_valid==true). */
  private Object value;
  /** Column type (one of java.sql.Types values).*/
  private int type;

  /**
   * Constructor.
   *
   * @param reader TableReader managing table
   * @param table index of table in reader
   * @param column index of column in table
   */
  public ColumnValue(TableReader reader, int table, int column) {
    this.reader=reader;
    this.table=table;
    this.column=column;
    index_checked=false;
    type=Types.NULL;
  }


  /**
   * Number of parameters.  ColumnValue does not support parameters so this
   * method always returns 0.
   *
   * @return 0
   */
  public int getParameterCount() {
    return 0;
  }
  
  /**
   * Get a particular parameter.  ColumnValue does not support parameters.
   *
   * @param index index of parameter to get
   * @return parameter (function)
   * @throws IndexOutOfBoundsException in all cases
   */
  public Function getParameter(int index) {
    throw new IndexOutOfBoundsException("parameters not supported");
  }

  /**
   * Adds a parameter to the list of parameters.  ColumnValue does not 
   * support parameters.
   *
   * @param item function to add
   * @throws SQLException in all cases
   */
  public void addParameter(Function item) throws SQLException {
    throw new SQLException("parameters not supported");
  }

  /**
   * Specify the order in which the parameters should be evaluated.  
   * ColumnValue does not support parameters.
   *
   * @param index index of parameter
   * @param order number indicating order in which parameter is evaluated
   * @throws IndexOutOfBoundsException in all cases
   */
  public void evaluateOrder(int index, int order) {
    throw new IndexOutOfBoundsException("parameters not supported");
  }

  /**
   * <p>This method is used by the TableReader object when ordering
   * parameters.  The TableReader object will call this method with a
   * TableReader.TableAccess object to find out which columns are accessed
   * by each parameter and whether those columns are indexed or not.  
   *
   * <p>Objects of any other type are ignored.
   *
   * @param o object to register with
   * @throws SQLException if an error occurs
   */
  public void registerWith(Object o) throws SQLException {
    if (o instanceof TableReader.TableAccess) {
      TableReader.TableAccess ta = (TableReader.TableAccess)o;
      // make sure this column belongs to the reader associated with ta
      if (ta.forReader(reader)) {
	ta.table_needed.set(table);
	// check if column is indexed
	if (!index_checked) {
	  indexed = reader.isIndexAvailable(table,column);
	  index_checked=true;
	}
	if (indexed)
	  ta.index_available.set(table);
      }
    }
  }

  /**
   * This implementation only sets the type variable.
   *
   * @throws SQLException if an error occurs
   */
  public void optimize() throws SQLException {
    type = reader.getDatabaseTable(table).getColumnType(column);
  }


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

  /**
   * Determine if this function returns a constant value.  ColumnValues are
   * never constant.
   *
   * @return false
   */
  public boolean isConstant() {
    return false;
  }

  /**
   * Determine if this function will return a value that is an aggregate of
   * many database rows.  ColumnValues are never aggregate.
   *
   * @return false
   */
  public boolean isAggregate() {
    return false;
  }

  /**
   * Return the SQL type of the value that this function expects to return.
   * The data returned by the function must either be of this type, or null.
   *
   * @return SQL type of data to be returned
   * @throws SQLException if an error occurs
   */
  public int getSQLType() throws SQLException {
    return type = reader.getDatabaseTable(table).getColumnType(column);
  }

  /**
   * Return the maximum number of characters that this function expects to
   * return in a String object.  If the function does not expect to return
   * a String object or if it cannot guess a maximum value, -1 should be
   * returned.
   *
   * @return maximum size of String returned or -1 if unknown
   * @throws SQLException if an error occurs
   */
  public int getMaxResultSize() throws SQLException {
    return reader.getDatabaseTable(table).getColumnDisplaySize(column);
  }

  /**
   * Returns "table_alias.column_name".  If the table does not have an alias,
   * "table_name.column_name" is returned.
   *
   * @return human readable description of column
   */
  public String toString() {
    try {
      DatabaseTable t = reader.getDatabaseTable(table);
      String table_name = reader.getTableAlias(table);
      if (table_name==null) table_name = t.getTableName();
      return table_name+'.'+t.getColumnName(column);
    }
    catch (DatabaseException e) {
      return "[UNKNOWN]";
    }
  }


  /****************  evaluation methods  ****************/

  /**
   * Invalidate cached value.
   */
  public void invalidate() {
    value_valid=false;
    value=null;
  }

  /**
   * Set cached value and mark the value as valid.
   *
   * @param v value to set
   */
  public void setValue(Object v) {
    value=v;
    value_valid=true;
  }

  /**
   * Reset aggregate function.  This method does nothing.
   */
  public void reset() {
  }

  /**
   * <p>Return the value of the column in the current row.  If the value
   * of the column has been cached and is valid, that value is returned.
   * Otherwise, a value is read from the TableReader, and if the TableReader
   * is forced to advance to the next row in the database, EndOfTable may
   * be thrown.
   *
   * <p>If aggregate is true, an exception is thrown.
   *
   * @param aggregate true to return final aggregate value
   * @return result object
   * @throws SQLException if an error occurs
   * @throws EndOfTable if the end of the table is reached
   */
  public Object evaluate(boolean aggregate) throws SQLException, EndOfTable {
    if (!value_valid) {
      if (aggregate)
	throw new SQLException("column value is not an aggregate");
      value = reader.readColumnValue(table,column);
      value_valid = true;
    }
    return value;
  }

  /**
   * <p>Return the value of the column in the current row.  If the value
   * of the column has been cached and is valid, that value is returned.
   * Otherwise, a value is read from the TableReader, and if the TableReader
   * is forced to advance to the next row in the database, EndOfTable may
   * be thrown.
   *
   * <p>If the TableReader is read from and match_op is MATCH_EQU, an
   * attempt is made to find a row in the database where the column constains
   * match_value.  An index may be used to speed up this search.  This
   * method makes no guarantee that such a value can be found and may return
   * some other value (even in cases where the desired row exists).
   *
   * @param match_op how desired value is matched
   * @param match_value value to match to
   * @return result object
   * @throws SQLException if an error occurs
   * @throws EndOfTable if the end of the table is reached
   */
  public Object evaluate(int match_op, Object match_value)
    throws SQLException, EndOfTable {
    if (!value_valid) {
      if (match_op==MATCH_EQU) {
	match_value = AbstractFunction.convertToSQLType(match_value,type);
	value = reader.readColumnValue(table,column,match_value);
      }
      else
	value = reader.readColumnValue(table,column);
      value_valid = true;
    }
    return value;
  }
};



