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

/* $Id: SelectTable.java,v 1.12 2004/01/03 17:37:57 cvs Exp $
 *
 * Copyright (c) 2004 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.
 */

/**
 * <p>Wrapper for a Select object to make it look like a DatabaseTable.
 * This class is used when a SELECT statement appears as a subquery in
 * the FROM section of some other SELECT statement.
 *
 * @see Select
 * @author chris.studholme@utoronto.ca
 */
class SelectTable implements DatabaseTable {

  /** Query that generated the table. */
  private Table query=null;
  /** Name of table. */
  private String name=null;

  /** Are we before the first row? */
  private boolean beforefirst=true;
  /** Are we after the last row? */
  private boolean afterlast=false;
  /** Current row number. */
  private long rownum=0;

  /**
   * Query should already be optimized before using this constructor.
   *
   * @param query optimized query to generate table
   * @param name name of table
   */
  public SelectTable(Table query, String name) {
    this.query=query;
    this.name=name;
  }

  /**
   * Close table.  Just calls close() below.
   */
  protected void finalize() {
    close();
  }


  //======================================================================
  // Methods for manipulating table
  //======================================================================

  /**
   * Close the query.
   */
  public final void close() {
    if (query!=null && query instanceof Select)
      ((Select)query).close();
    query=null;
  }

  /**
   * Open an index on the table.  Indices are not supported for tables
   * generated from a query so this method just returns null.
   *
   * @param column open index for desired column
   * @return null
   */
  public DatabaseIndex openIndex(int column) {
    return null;
  }


  //======================================================================
  // Methods for accessing table metadata
  //======================================================================

  /**
   * Is an index available?  Indices are not supported for tables
   * generated from a query so this method just returns null.
   *
   * @param column desired index
   * @return false
   */
  public boolean isIndexAvailable(int column) {
    return false;
  }

  /**
   * Is table readonly?
   *
   * @return true
   */
  public boolean isReadOnly() {
    return true;
  }

  /**
   * Size of table in bytes.
   *
   * @return -1 (unknown)
   */
  public long getTableSize() {
    return -1;
    //return query.getColumnCount()*query.precomputedResults.size();
  }

  /**
   * Table signature.
   *
   * @return 0
   */
  public long getTableSignature() {
    return 0;
  }

  /**
   * Name of table.
   *
   * @return table name
   */
  public String getTableName() {
    return name;
  }

  /**
   * Number of columns in table.
   *
   * @return number of columns
   * @throws DatabaseException is the query fails
   */
  public int getColumnCount() throws DatabaseException {
    try {
      return query.getColumnCount();
    }
    catch (SQLException e) {
      throw new DatabaseException("error in SELECT query",e);
    }
  }

  /**
   * Number of rows.
   * 
   * @return -1 (unknown)
   */
  public long getRowCount() {
    return -1;
  }

  /**
   * Human readable description of table.
   *
   * @return the query
   */
  public String toString() {
    return query.toString();
  }

  //======================================================================
  // Methods for accessing column metadata
  //======================================================================

  /**
   * Find a column with the specified name.
   * @param name name of column to find
   * @return index of column (-1 if not found)
   * @throws DatabaseException if there is a problem with the query
   */
  public int findColumn(String name) throws DatabaseException {
    for (int i=1; i<=getColumnCount(); ++i)
      if (getColumnName(i).equalsIgnoreCase(name))
	return i;
    return -1;
  }
  
  /**
   * Get name of column.
   * @param column index of column
   * @return name of column
   * @throws DatabaseException if there is a problem with the query
   */
  public String getColumnName(int column) throws DatabaseException {
    if ((column<=0)||(column>getColumnCount()))
      return null;
    try {
      String result = query.getColumnName(column-1);
      if (result==null)
	return "Column"+column;
      // remove "tablename." prefix
      int lastdot = result.lastIndexOf('.');
      if (lastdot>0)
	result = result.substring(lastdot+1);
      return result;
    }
    catch (SQLException e) {
      throw new DatabaseException("error in SELECT query",e);
    }
  }
  
  /**
   * Get column label.  This method is the same as getColumnName() above.
   * @param column index of column
   * @return label for column
   * @throws DatabaseException if there is a problem with the query
   */
  public String getColumnLabel(int column) throws DatabaseException {
    return getColumnName(column);
  }
  
  /**
   * Get column type.
   * @param column index of column
   * @return SQL type of column
   * @throws DatabaseException if there is a problem with the query
   */
  public int getColumnType(int column) throws DatabaseException {
    if ((column<=0)||(column>getColumnCount()))
      return Types.NULL;
    try {
      return query.getSQLType(column-1);
    }
    catch (SQLException e) {
      throw new DatabaseException("error in SELECT query",e);
    }
  }

  /**
   * Get maximum size of column.
   * @param column index of column
   * @return maximum size of column in characters
   * @throws DatabaseException if there is a problem with the query
   */
  public int getColumnDisplaySize(int column) throws DatabaseException {
    if ((column<=0)||(column>getColumnCount()))
      return -1;
    try {
      return query.getMaxResultSize(column-1);
    }
    catch (SQLException e) {
      throw new DatabaseException("error in SELECT query",e);
    }
  }

  /**
   * Add a new column.
   * @throws DatabaseException always since table is read-only
   */
  public void addColumn(String name, int type, int maxlen) 
    throws DatabaseException {
    throw new DatabaseException("table is read-only");
  }

  //======================================================================
  // Methods for changing current row
  //======================================================================

  /**
   * Advance to next row in table.
   *
   * @return true if a valid row was found
   * @throws DatabaseException if there is a problem with the query
   */
  public boolean next() throws DatabaseException {
    if (afterlast)
      return false;

    beforefirst=false;
    try {
      if (!query.next()) {
	afterlast=true;
	return false;
      }
      ++rownum;
      return true;
    }
    catch (SQLException e) {
      throw new DatabaseException("error in SELECT query",e);
    }
    catch (EndOfTable e) {
      throw new DatabaseException("SOFTWARE BUG",e);
    }
  }

  /**
   * Delete current row.
   * @throws DatabaseException always since table is read-only
   */
  public void deleteRow() throws DatabaseException {
    throw new DatabaseException("table is read-only");
  }

  /**
   * Add a new row.
   * @throws DatabaseException always since table is read-only
   */
  public void addRow() throws DatabaseException {
    throw new DatabaseException("table is read-only");
  }

  /**
   * Are we before the first row?
   * @return true if table is before the first row
   */
  public boolean isBeforeFirst() {
    return beforefirst;
  }

  /**
   * Are we after the last row?
   * @return true if table is after the last row
   */
  public boolean isAfterLast() {
    return afterlast;
  }

  /**
   * Reset current row to before the first row in table.
   * @throws DatabaseException if there is a problem with the query
   */
  public void beforeFirst() throws DatabaseException {
    try {
      query.beforeFirst();
      beforefirst=true;
      afterlast=false;
      rownum=-1;
    }
    catch (SQLException e) {
      throw new DatabaseException("error in SELECT query",e);
    }
  }

  /**
   * Set current row to after last row in table.
   */
  public void afterLast() {
    beforefirst=false;
    afterlast=true;
  }

  /**
   * Returns the rowid for the current row.
   *
   * @return rowid object
   */
  public Object getRowId() {
    return new Long(rownum);
  }

  /**
   * Move to an absolute rowid in the table.
   * @param rowid id of row to seek
   * @return true if row was found, false otherwise
   * @throws DatabaseException always since this method is not supported
   */
  public boolean absolute(Object id) throws DatabaseException {
    throw new DatabaseException("cannot use absolute() with SelectTable");
  }


  //======================================================================
  // Methods for accessing column data
  //======================================================================

  /**
   * Get the value of a column in the current row as a Java object.
   *
   * @param column index of column
   * @return an Object holding the column value
   * @throws DatabaseException if there is a problem with the query
   */
  public Object getObject(int column) throws DatabaseException {
    try {
      return query.getObject(column-1);
    }
    catch (SQLException e) {
      throw new DatabaseException("error in SELECT query",e);
    }
    catch (EndOfTable e) {
      throw new DatabaseException("SOFTWARE BUG",e);
    }
  }

  /**
   * Update value of column.
   * @param column index of column to update
   * @param x new column value
   * @throws DatabaseException always since table is read-only
   */
  public void updateObject(int column, Object x)
    throws DatabaseException {
    throw new DatabaseException("table is read-only");
  }

  /**
   * Commit column updates.
   * @throws DatabaseException always since table is read-only
   */
  public void commitUpdates() throws DatabaseException {
    throw new DatabaseException("table is read-only");
  }
};


