/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/db/sql/SelectQuery.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.db.sql;

import com.caucho.db.Database;
import com.caucho.db.table.TableIterator;
import com.caucho.db.xa.DbTransaction;
import com.caucho.sql.SQLExceptionWrapper;
import com.caucho.util.CharBuffer;

import java.io.IOException;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.logging.*;

public class SelectQuery extends Query {
  private static final Logger log
    = Logger.getLogger(SelectQuery.class.getName());

  private Expr []_results;
  private String []_resultNames;

  private boolean []_groupFields;

  private Order _order;
  private int _limit = Integer.MAX_VALUE / 2;

  SelectQuery(Database db, String sql)
    throws SQLException
  {
    super(db, sql);
  }

  SelectQuery(Database db, String sql, FromItem []fromItems)
    throws SQLException
  {
    super(db, sql, fromItems);
  }

  void setResults(Expr []resultExprs)
    throws SQLException
  {
    _results = new Expr[resultExprs.length];

    for (int i = 0; i < resultExprs.length; i++) {
      _results[i] = resultExprs[i];
    }

    setDataFields(resultExprs.length);
  }

  Expr []getResults()
  {
    return _results;
  }

  /**
   * Sets the result item as group.
   */
  public void setGroupResult(int index)
  {
    if (_groupFields == null)
      _groupFields = new boolean[_results.length];

    _groupFields[index] = true;
  }

  @Override
  protected void bind()
    throws SQLException
  {
    super.bind();

    for (int i = 0; i < _results.length; i++) {
      _results[i] = _results[i].bind(this);
    }

    if (isGroup()) {
      for (int i = 0; i < _results.length; i++) {
        if (isGroup() && ! (_results[i] instanceof GroupExpr))
          _results[i] = new GroupResultExpr(i, _results[i]);
      }
    }

    for (int i = 0; i < _results.length; i++) {
      _results[i] = _results[i].bind(this);
    }

    _resultNames = new String[_results.length];

    for (int i = 0; i < _resultNames.length; i++)
      _resultNames[i] = _results[i].getName();
  }

  void setOrder(Order order)
  {
    _order = order;
  }

  @Override
  public void setLimit(int limit)
  {
    _limit = limit;
  }

  /**
   * Returns true for select queries.
   */
  @Override
  public boolean isSelect()
  {
    return true;
  }

  /**
   * Returns the type of the child.
   */
  Class<?> getType()
  {
    if (_results.length == 1)
      return _results[0].getType();
    else
      return Object.class;
  }

  /**
   * Executes the query.
   */
  @Override
  public void execute(QueryContext context, DbTransaction xa)
    throws SQLException
  {
    SelectResult result = SelectResult.create(_results, _order);
    FromItem []fromItems = getFromItems();
    TableIterator []rows = null;

    try {
      rows = result.initRows(fromItems);
      context.init(xa, rows, isReadOnly());

      if (isGroup())
        executeGroup(result, rows, context, xa);
      else
        execute(result, rows, context, xa);

      result.initRead();

      context.setResult(result);
    } catch (IOException e) {
      throw new SQLExceptionWrapper(e);
    } finally {
      // autoCommitRead must be before freeRows in case freeRows
      // throws an exception
      try {
        context.close();
      } catch (Exception e) {
        log.log(Level.WARNING, e.toString(), e);
      }

      if (rows != null)
        freeRows(rows, rows.length);
    }
  }

  /**
   * Executes the query.
   */
  private void execute(SelectResult result,
                       TableIterator []rows,
                       QueryContext context,
                       DbTransaction xa)
    throws SQLException, IOException
  {
    FromItem []fromItems = getFromItems();
    int rowLength = fromItems.length;

    int limit = _limit;
    int contextLimit = context.getLimit();
    if (contextLimit > 0)
      limit = contextLimit;

    if (start(rows, rowLength, context, xa)) {
      do {
        result.startRow();

        for (int i = 0; i < _results.length; i++) {
          _results[i].evalToResult(context, result);
        }
      } while (nextTuple(rows, rowLength, context, xa) && --limit > 0);
    }
  }

  private void executeGroup(SelectResult result,
                            TableIterator []rows,
                            QueryContext context,
                            DbTransaction transaction)
    throws SQLException, IOException
  {
    FromItem []fromItems = getFromItems();
    int rowLength = fromItems.length;

    Expr []results = _results;
    int resultsLength = results.length;

    /*
    for (int i = 0; i < results.length; i++)
      results[i].initGroup(context);
    */

    if (_groupFields == null)
      _groupFields = new boolean[0];

    boolean []groupByFields = _groupFields;
    int groupByLength = _groupFields.length;

    if (start(rows, rowLength, context, transaction)) {
      do {
        context.initGroup(getDataFields(), _groupFields);

        for (int i = 0; i < groupByLength; i++) {
          if (groupByFields[i])
            results[i].evalGroup(context);
        }

        context.selectGroup();

        for (int i = 0; i < resultsLength; i++) {
          if (! (i < groupByLength && groupByFields[i]))
            results[i].evalGroup(context);
        }
      } while (nextTuple(rows, rowLength, context, transaction));
    }

    Iterator<GroupItem> groupIter = context.groupResults();

    while (groupIter.hasNext()) {
      GroupItem item = groupIter.next();

      context.setGroupItem(item);

      result.startRow();
      for (int i = 0; i < results.length; i++) {
        results[i].evalToResult(context, result);
      }
    }
  }

  /**
   * Executes the query.
   */
  @Override
  public SelectCursor executeCursor(QueryContext context, DbTransaction xa)
    throws SQLException
  {
    if (isGroup())
      throw new IllegalStateException();
    if (_order != null)
      throw new IllegalStateException();
    
    FromItem []fromItems = getFromItems();
    TableIterator []rows = null;

    SelectCursor cursor = new SelectCursor(_results, this, context);

    rows = cursor.initRows(fromItems);
    context.init(xa, rows, isReadOnly());

    int rowLength = fromItems.length;

    if (start(rows, rowLength, context, xa)) {
      context.unlock();
      
      return cursor;
    }
    else
      return null;
  }

  /**
   * Executes the query.
   */
  public final boolean nextCursor(TableIterator []rows,
                                  QueryContext context,
                                  DbTransaction xa)
    throws SQLException
  {
    try {
      FromItem []fromItems = getFromItems();
      int rowLength = fromItems.length;

      boolean value = nextTuple(rows, rowLength, context, xa);

      return value;
    } catch (IOException e) {
      throw new SQLExceptionWrapper(e);
    }
  }

  public String toString()
  {
    CharBuffer cb = CharBuffer.allocate();
    cb.append("SelectQuery[");
    cb.append("SELECT ");
    for (int i = 0; i < _results.length; i++) {
      if (i != 0)
        cb.append(",");
      cb.append(_results[i]);
    }
    cb.append(" FROM ");

    FromItem []fromItems = getFromItems();
    for (int i = 0; i < fromItems.length; i++) {
      if (i != 0)
        cb.append(",");
      cb.append(fromItems[i]);
    }

    if (_whereExpr != null) {
      cb.append(" WHERE " + _whereExpr);
    }
    cb.append("]");

    return cb.close();
  }
}
