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

/* $Id: Delete.java,v 1.9 2003/09/24 19:59:34 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.
 */

/**
 * <p>Delete will parse and execute an SQL DELETE query.
 *
 * @author chris.studholme@utoronto.ca
 */
final class Delete implements Query {

  /** Name of table. */
  String tablename=null;
  /** Where clause used to choose rows. */
  Function where=null;

  /** Reader used to fetch rows (set by optimize). */
  TableReader reader=null;
  /** Database table to update (set by optimize). */
  DatabaseTable table=null;

  /** Table manager used to open tables. */
  protected transient DatabaseManager tablemanager;

  /**
   * Contructor to parse query.
   *
   * @param tokenizer StreamTokenizer that SQL tokens should be read from
   * @param manager manager to use when looking up tables
   * @throws SQLException if an error occurs
   * @throws IOException if there is a problem reading the query
   */
  protected Delete(StreamTokenizer tokenizer, DatabaseManager manager) 
    throws SQLException, IOException {
    tablemanager = manager;
    ParseDelete(tokenizer);
  }

  /**
   * Close query and free resources in use.
   */
  public void close() {
  }

  /**
   * Returns human-readable string version of query (with surrounding 
   * brackets).
   *
   * @return String representation of query
   */
  public String toString() {
    return toString(true);
  }

  /**
   * Returns human-readable string version of query.
   *
   * @param with_brackets true to include surrounding brackets
   * @return String representation of query
   */
  public String toString(boolean with_brackets) {
    StringBuffer result = new StringBuffer();
    if (with_brackets) result.append('(');
    result.append("DELETE FROM ");
    result.append(tablename!=null ? tablename : "[UNKNOWN]");
    if (where!=null) {
      result.append(" WHERE "); 
      result.append(where);
    }
    if (with_brackets) result.append(')');
    return result.toString();
  }

  /**
   * Parse delete query of the form:<br>
   *   <blockquote><code>
   *   DELETE FROM tablename [ WHERE condition ]
   *   </code></blockquote>
   *
   * @param tokenizer StreamTokenizer that SQL tokens should be read from
   * @throws SQLException if an error occurs
   * @throws IOException if there is a problem reading the query
   */   
  private void ParseDelete(StreamTokenizer tokenizer)
    throws SQLException, IOException {

    tablename=null;
    where=null;
    
    if ((tokenizer.ttype!=StreamTokenizer.TT_WORD)||
	(!tokenizer.sval.equals("delete")))
      throw new SQLException("'DELETE' expected");
    tokenizer.nextToken();

    if ((tokenizer.ttype!=StreamTokenizer.TT_WORD)||
	(!tokenizer.sval.equals("from")))
      throw new SQLException("'FROM' expected");
    tokenizer.nextToken();

    // table name
    if (tokenizer.ttype!=StreamTokenizer.TT_WORD)
      throw new SQLException("table name expected at or before "+tokenizer);
    tablename=tokenizer.sval;
    tokenizer.nextToken();

    if (tokenizer.ttype==StreamTokenizer.TT_WORD) {
      if (!tokenizer.sval.equals("where"))
	throw new SQLException("'WHERE' expected at or before "+tokenizer);
      tokenizer.nextToken();
      where = Expression.parseExpression(tokenizer,tablemanager);
      // need to CreateRowDefinition() and optimize() here
    }
  }

  /**
   * Optimize query.
   *
   * @throws SQLException if an error occurs
   */
  public void optimize() throws SQLException {
    if (tablename==null)
      throw new SQLException("cannot execute DELETE statement");

    DatabaseTable[] tables = new DatabaseTable[1];
    tables[0] = table = tablemanager.openTable(tablename,false);
    String[] aliases = new String[1];
    reader = new TableReader(tables,aliases,null);
    
    if (where!=null) {
      where.registerWith(reader);
      reader.optimizeParameterOrder(where);
      where.optimize();
      if (where.isAggregate())
	throw new SQLException("WHERE is an aggregate");
    }
  }

  /**
   * Execute the query.
   *
   * @return number of rows deleted
   * @throws SQLException if an error occurs
   */
  public int execute() throws SQLException {
    // read all rows
    Boolean TRUE_VALUE = new Boolean(true);
    int count=0;
    while (true) {
      try {
	// advance to next row
	Object result;
	do {
	  reader.next();
	  if (where==null) break;
	  result = where.evaluate(Function.MATCH_EQU,TRUE_VALUE);
	} while (result==null || ((Boolean)result).booleanValue()!=true);
	reader.readAllTables();  
	reader.deleteRow();
	++count;

      }
      catch (EndOfTable e) {
	if (reader!=e.getReader())
	  throw new SQLExceptionWithCause("SOFTWARE BUG",e);
	if (reader.finishTable())
	  break;
      }
    }
    return count;
  }
};
