/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/db/sql/SelectResultSetImpl.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 SoftwareFoundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.db.sql;

import com.caucho.db.blob.BlobInputStream;
import com.caucho.db.block.BlockStore;
import com.caucho.db.table.Column;
import com.caucho.db.table.TableIterator;
import com.caucho.db.table.Column.ColumnType;
import com.caucho.sql.SQLExceptionWrapper;
import com.caucho.util.CharBuffer;
import com.caucho.util.FreeList;
import com.caucho.util.L10N;
import com.caucho.util.QDate;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.TempBuffer;
import com.caucho.vfs.TempStream;
import com.caucho.vfs.WriteStream;

import java.io.IOException;
import java.sql.SQLException;

public class SelectResultSetImpl extends ResultSetImpl {
  private static final L10N L = new L10N(SelectResultSetImpl.class);
  
  private static final FreeList<SelectResultSetImpl> _freeList =
    new FreeList<SelectResultSetImpl>(16);
  
  private final WriteStream _ws;
  private final TempBuffer _buf;
  private final ReadStream _rs;
  private final byte []_buffer;
  private final CharBuffer _cb;
  private final TempStream _ts;

  private static QDate _date = new QDate();
  
  private Expr []_exprs;
  private ColumnType []_types = new ColumnType[32];
  private int []_offsets = new int[32];
  private int []_lengths = new int[32];
  private BlockStore []_stores = new BlockStore[32];

  private TableIterator []_rows;

  private int _lastColumn;

  private SelectResultSetImpl()
  {
    _ws = new WriteStream();
    _ws.setReuseBuffer(true);
    _ts = new TempStream();
    _rs = new ReadStream();
    _rs.setReuseBuffer(true);
    _buf = TempBuffer.allocate();
    _buffer = _buf.getBuffer();
    _cb = new CharBuffer();

    _rows = new TableIterator[0];
  }

  public static SelectResultSetImpl create(Expr []exprs)
  {
    SelectResultSetImpl rs = _freeList.allocate();

    if (rs == null)
      rs = new SelectResultSetImpl();

    rs.init(exprs);

    return rs;
  }

  TableIterator []initRows(FromItem []fromItems)
  {
    if (_rows.length < fromItems.length)
      _rows = new TableIterator[fromItems.length];
      
    for (int i = 0; i < fromItems.length; i++) {
      if (_rows[i] == null)
        _rows[i] = new TableIterator();
      _rows[i].init(fromItems[i].getTable());
    }

    return _rows;
  }
    

  private void init(Expr []exprs)
  {
    _exprs = exprs;
    
    if (_offsets.length < _exprs.length) {
      _offsets = new int[exprs.length];
      _lengths = new int[exprs.length];
      _types = new ColumnType[exprs.length];
      _stores = new BlockStore[exprs.length];
    }

    for (int i = 0; i < exprs.length; i++) {
      _stores[i] = exprs[i].getTable();
    }
  }

  void initRead()
    throws IOException
  {
    _ts.openRead(_rs);
  }

  WriteStream getWriteStream()
  {
    _ts.openWrite();
    _ws.init(_ts);
    
    return _ws;
  }
  
  public boolean next()
    throws SQLException
  {
    try {
      ReadStream rs = _rs;

      _lastColumn = 0;
      
      int hasData = rs.read();
      if (hasData <= 0)
        return false;

      int length = 0;
      int fields = _exprs.length;

      byte []buffer = _buffer;
      for (int i = 0; i < fields; i++) {
        int type = rs.read();

        int sublen = 0;

        if (type < 0)
          return false;

        ColumnType cType = ColumnType.values()[type];

        switch (cType) {
        case NONE:
          sublen = -1;
          break;

        case VARCHAR:
          int l0 = rs.read();
          int l1 = rs.read();
          int l2 = rs.read();
          int l3 = rs.read();

          sublen = ((l0 << 24) +
                    (l1 << 16) +
                    (l2 << 8) +
                    (l3));
          break;

        case INT:
          sublen = 4;
          break;
        case LONG:
        case DOUBLE:
        case DATE:
          sublen = 8;
          break;

        case BLOB:
          sublen = 128;
          break;

        default:
          throw new SQLException("Unknown column: " + type);
        }

        _types[i] = cType;
        _offsets[i] = length;
        _lengths[i] = sublen;

        if (sublen > 0) {
          rs.read(buffer, length, sublen);

          length += sublen;
        }
      }

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

  /**
   * Returns the column index with the given name.
   */
  public int findColumnIndex(String name)
    throws SQLException
  {
    for (int i = 0; i < _exprs.length; i++) {
      if (_exprs[i].getName().equals(name))
        return i + 1;
    }

    throw new SQLException(L.l("column `{0}' does not exist.", name));
  }

  /**
   * Returns the string value of the given index.
   */
  public String getString(int index)
    throws SQLException
  {
    _lastColumn = index;
    
    byte []buffer = _buffer;
    int offset = _offsets[index];
    int length = _lengths[index];
    
    switch (_types[index]) {
    case NONE:
      return null;
      
    case INT:
      {
        int value = (((buffer[offset] & 0xff) << 24) +
                     ((buffer[offset + 1] & 0xff) << 16) +
                     ((buffer[offset + 2] & 0xff) << 8) +
                     ((buffer[offset + 3] & 0xff)));

        return String.valueOf(value);
      }
      
    case LONG:
      {
        long value = (((buffer[offset + 0] & 0xffL) << 56) +
                      ((buffer[offset + 1] & 0xffL) << 48) +
                      ((buffer[offset + 2] & 0xffL) << 40) +
                      ((buffer[offset + 3] & 0xffL) << 32) +
                      ((buffer[offset + 4] & 0xffL) << 24) +
                      ((buffer[offset + 5] & 0xffL) << 16) +
                      ((buffer[offset + 6] & 0xffL) << 8) +
                      ((buffer[offset + 7] & 0xffL)));
        return String.valueOf(value);
      }
      
    case DOUBLE:
      {
        long value = (((buffer[offset + 0] & 0xffL) << 56) +
                      ((buffer[offset + 1] & 0xffL) << 48) +
                      ((buffer[offset + 2] & 0xffL) << 40) +
                      ((buffer[offset + 3] & 0xffL) << 32) +
                      ((buffer[offset + 4] & 0xffL) << 24) +
                      ((buffer[offset + 5] & 0xffL) << 16) +
                      ((buffer[offset + 6] & 0xffL) << 8) +
                      ((buffer[offset + 7] & 0xffL)));
        return String.valueOf(Double.longBitsToDouble(value));
      }
      
    case DATE:
      {
        long value = (((buffer[offset + 0] & 0xffL) << 56) +
                      ((buffer[offset + 1] & 0xffL) << 48) +
                      ((buffer[offset + 2] & 0xffL) << 40) +
                      ((buffer[offset + 3] & 0xffL) << 32) +
                      ((buffer[offset + 4] & 0xffL) << 24) +
                      ((buffer[offset + 5] & 0xffL) << 16) +
                      ((buffer[offset + 6] & 0xffL) << 8) +
                      ((buffer[offset + 7] & 0xffL)));
        return QDate.formatGMT(value);
      }

    case VARCHAR:
      return getStringValue(index);

    case BLOB:
      return getBlobString(index);

    default:
      return null;
    }
  }

  /**
   * Returns the string value for the result set.
   */
  private String getStringValue(int index)
    throws SQLException
  {
    _lastColumn = index;
    
    int length = _lengths[index];
    int offset = _offsets[index];
    
    if (length < 0)
      return null;

    CharBuffer cb = _cb;
    cb.clear();

    byte []buffer = _buffer;
    for (; length > 0; length--) {
      cb.append((char) buffer[offset++]);
    }

    return cb.toString();
  }

  /**
   * Returns the string value for the result set.
   */
  private String getBlobString(int index)
    throws SQLException
  {
    _lastColumn = index;
    
    int offset = _offsets[index];

    CharBuffer cb = _cb;
    cb.clear();

    BlobInputStream is = null;
    try {
      is = new BlobInputStream(_stores[index], _buffer, offset);

      int ch;
      while ((ch = is.read()) >= 0) {
        if (ch < 0x80)
          cb.append((char) ch);
      }
    } catch (IOException e) {
      throw new SQLExceptionWrapper(e);
    }

    return cb.toString();
  }

  public int getInt(int index)
    throws SQLException
  {
    _lastColumn = index;
    
    byte []buffer = _buffer;
    int offset = _offsets[index];
    int length = _lengths[index];
    
    switch (_types[index]) {
    case NONE:
      return 0;
      
    case INT:
      return (((buffer[offset + 0] & 0xff) << 24) +
              ((buffer[offset + 1] & 0xff) << 16) +
              ((buffer[offset + 2] & 0xff) << 8) +
              ((buffer[offset + 3] & 0xff)));
      
    case LONG:
      {
        long value = (((buffer[offset + 0] & 0xffL) << 56) +
                      ((buffer[offset + 1] & 0xffL) << 48) +
                      ((buffer[offset + 2] & 0xffL) << 40) +
                      ((buffer[offset + 3] & 0xffL) << 32) +
                      ((buffer[offset + 4] & 0xffL) << 24) +
                      ((buffer[offset + 5] & 0xffL) << 16) +
                      ((buffer[offset + 6] & 0xffL) << 8) +
                      ((buffer[offset + 7] & 0xffL)));
        return (int) value;
      }
      
    case DOUBLE:
      {
        long value = (((buffer[offset + 0] & 0xffL) << 56) +
                      ((buffer[offset + 1] & 0xffL) << 48) +
                      ((buffer[offset + 2] & 0xffL) << 40) +
                      ((buffer[offset + 3] & 0xffL) << 32) +
                      ((buffer[offset + 4] & 0xffL) << 24) +
                      ((buffer[offset + 5] & 0xffL) << 16) +
                      ((buffer[offset + 6] & 0xffL) << 8) +
                      ((buffer[offset + 7] & 0xffL)));
        return (int) Double.longBitsToDouble(value);
      }

    case VARCHAR:
      return Integer.parseInt(getString(index));

    default:
      return 0;
    }
  }

  public long getLong(int index)
    throws SQLException
  {
    _lastColumn = index;
    
    byte []buffer = _buffer;
    int offset = _offsets[index];
    int length = _lengths[index];
    
    switch (_types[index]) {
    case NONE:
      return 0;
      
    case INT:
      return (((buffer[offset] & 0xff) << 24) +
              ((buffer[offset + 1] & 0xff) << 16) +
              ((buffer[offset + 2] & 0xff) << 8) +
              ((buffer[offset + 3] & 0xff)));
      
    case LONG:
    case DATE:
      {
        long value = (((buffer[offset + 0] & 0xffL) << 56) +
                      ((buffer[offset + 1] & 0xffL) << 48) +
                      ((buffer[offset + 2] & 0xffL) << 40) +
                      ((buffer[offset + 3] & 0xffL) << 32) +
                      ((buffer[offset + 4] & 0xffL) << 24) +
                      ((buffer[offset + 5] & 0xffL) << 16) +
                      ((buffer[offset + 6] & 0xffL) << 8) +
                      ((buffer[offset + 7] & 0xffL)));
        return value;
      }
      
    case DOUBLE:
      {
        long value = (((buffer[offset + 0] & 0xffL) << 56) +
                      ((buffer[offset + 1] & 0xffL) << 48) +
                      ((buffer[offset + 2] & 0xffL) << 40) +
                      ((buffer[offset + 3] & 0xffL) << 32) +
                      ((buffer[offset + 4] & 0xffL) << 24) +
                      ((buffer[offset + 5] & 0xffL) << 16) +
                      ((buffer[offset + 6] & 0xffL) << 8) +
                      ((buffer[offset + 7] & 0xffL)));
        return (long) Double.longBitsToDouble(value);
      }

    case VARCHAR:
      return Long.parseLong(getString(index));

    default:
      return 0;
    }
  }

  /**
   * Returns a double value from this column.
   */
  public double getDouble(int index)
    throws SQLException
  {
    _lastColumn = index;
    
    byte []buffer = _buffer;
    int offset = _offsets[index];
    int length = _lengths[index];
    
    switch (_types[index]) {
    case NONE:
      return 0;
      
    case INT:
      return (((buffer[offset + 0] & 0xff) << 24) +
              ((buffer[offset + 1] & 0xff) << 16) +
              ((buffer[offset + 2] & 0xff) << 8) +
              ((buffer[offset + 3] & 0xff)));
      
    case LONG:
      {
        long value = (((buffer[offset + 0] & 0xffL) << 56) +
                      ((buffer[offset + 1] & 0xffL) << 48) +
                      ((buffer[offset + 2] & 0xffL) << 40) +
                      ((buffer[offset + 3] & 0xffL) << 32) +
                      ((buffer[offset + 4] & 0xffL) << 24) +
                      ((buffer[offset + 5] & 0xffL) << 16) +
                      ((buffer[offset + 6] & 0xffL) << 8) +
                      ((buffer[offset + 7] & 0xffL)));
        return value;
      }
      
    case DOUBLE:
      {
        long value = (((buffer[offset + 0] & 0xffL) << 56) +
                      ((buffer[offset + 1] & 0xffL) << 48) +
                      ((buffer[offset + 2] & 0xffL) << 40) +
                      ((buffer[offset + 3] & 0xffL) << 32) +
                      ((buffer[offset + 4] & 0xffL) << 24) +
                      ((buffer[offset + 5] & 0xffL) << 16) +
                      ((buffer[offset + 6] & 0xffL) << 8) +
                      ((buffer[offset + 7] & 0xffL)));
        return Double.longBitsToDouble(value);
      }

    case VARCHAR:
      return Double.parseDouble(getString(index));

    default:
      return 0;
    }
  }

  public long getDate(int index)
    throws SQLException
  {
    _lastColumn = index;
    
    byte []buffer = _buffer;
    int offset = _offsets[index];
    int length = _lengths[index];
    
    switch (_types[index]) {
    case NONE:
      return 0;
      
    case LONG:
    case DATE:
      {
        long value = (((buffer[offset + 0] & 0xffL) << 56) +
                      ((buffer[offset + 1] & 0xffL) << 48) +
                      ((buffer[offset + 2] & 0xffL) << 40) +
                      ((buffer[offset + 3] & 0xffL) << 32) +
                      ((buffer[offset + 4] & 0xffL) << 24) +
                      ((buffer[offset + 5] & 0xffL) << 16) +
                      ((buffer[offset + 6] & 0xffL) << 8) +
                      ((buffer[offset + 7] & 0xffL)));
        return value;
      }

    case VARCHAR:
    case BLOB:
      {
        String value = getString(index);

        if (value == null)
          return 0;

        synchronized (_date) {
          try {
            return _date.parseDate(value);
          } catch (Exception e) {
            throw new SQLExceptionWrapper(e);
          }
        }
      }

    default:
      throw new SQLException("unknown type:" + _types[index]);
    }
  }

  /**
   * Returns true if the last column read was null.
   */
  public boolean wasNull()
  {
    if (_lastColumn < 0)
      return false;
    else
      return _lengths[_lastColumn] < 0;
  }
  
  public void close()
  {
    _freeList.free(this);
  }
}
