/* * 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 java.io.ByteArrayOutputStream; import java.io.IOException; import java.sql.Blob; import java.sql.Clob; import java.sql.SQLException; import com.caucho.db.blob.BlobInputStream; import com.caucho.db.block.BlockStore; 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.IntArray; import com.caucho.util.L10N; import com.caucho.util.QDate; import com.caucho.vfs.TempBuffer; public class SelectResult { private static final L10N L = new L10N(SelectResult.class); private static final FreeList<SelectResult> _freeList = new FreeList<SelectResult>(32); private static final int SIZE = TempBuffer.SIZE; private static QDate _date = new QDate(); private CharBuffer _cb = new CharBuffer(); private byte []_blob = new byte[128]; private Expr []_exprs; private BlockStore []_stores = new BlockStore[32]; private TableIterator []_rows = new TableIterator[16]; private Order _order; private IntArray _orderIndex; private TempBuffer []_tempBuffers = new TempBuffer[128]; private byte [][]_buffers = new byte[128][]; private int _length; private int _rowCount; private int _row; private int _offset; private int _rowOffset; private int _columnOffset; private int _column; private boolean _wasNull; private SelectResult() { } public static SelectResult create(Expr []exprs, Order order) { SelectResult rs = _freeList.allocate(); if (rs == null) rs = new SelectResult(); rs.init(exprs, order); return rs; } /** * Initialize the iterator. */ 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; } /** * Initialize based on the exprs. */ private void init(Expr []exprs, Order order) { _exprs = exprs; _order = order; if (order != null) _orderIndex = new IntArray(); if (_stores.length < _exprs.length) { _stores = new BlockStore[exprs.length]; } for (int i = 0; i < exprs.length; i++) _stores[i] = exprs[i].getTable(); _length = 0; _rowCount = 0; } void initRead() throws SQLException { if (_order != null) _order.sort(this, _orderIndex); _row = -1; _offset = 0; _column = 0; _rowOffset = 0; _columnOffset = 0; } /** * Moves to the next row, returning true if the row has data. */ public boolean next() throws SQLException { if (++_row < _rowCount) { if (_orderIndex != null) { _offset = _orderIndex.get(_row); } else if (_row != 0) { _offset = _columnOffset; skipColumns(_exprs.length - _column); } _column = 0; _rowOffset = _offset; _columnOffset = _rowOffset; return true; } else return false; } /** * Returns the expressions. */ public Expr []getExprs() { return _exprs; } /** * 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 { _wasNull = false; setColumn(index); int type = read(); if (type < 0) return null; ColumnType cType = ColumnType.values()[type]; switch (cType) { case NONE: _wasNull = true; return null; case SHORT: { int value = (short) ((read() << 8) + (read())); return String.valueOf(value); } case INT: { int value = ((read() << 24) + (read() << 16) + (read() << 8) + (read())); return String.valueOf(value); } case LONG: { long value = (((long) read() << 56) + ((long) read() << 48) + ((long) read() << 40) + ((long) read() << 32) + ((long) read() << 24) + ((long) read() << 16) + ((long) read() << 8) + ((long) read())); return String.valueOf(value); } case DOUBLE: { long value = (((long) read() << 56) + ((long) read() << 48) + ((long) read() << 40) + ((long) read() << 32) + ((long) read() << 24) + ((long) read() << 16) + ((long) read() << 8) + ((long) read())); return String.valueOf(Double.longBitsToDouble(value)); } case DATE: { long value = (((long) read() << 56) + ((long) read() << 48) + ((long) read() << 40) + ((long) read() << 32) + ((long) read() << 24) + ((long) read() << 16) + ((long) read() << 8) + ((long) read())); return QDate.formatISO8601(value); } case VARCHAR: return readString(); case BLOB: return readBlobString(); case BINARY: { int len = read(); char []chars = new char[len]; for (int i = 0; i < len; i++) { chars[i] = (char) (read() & 0xff); } return new String(chars); } default: throw new RuntimeException("unknown column type:" + type + " column:" + index); } } /** * Returns the string value of the given index. */ public byte [] getBytes(int index) throws SQLException { _wasNull = false; setColumn(index); int type = read(); if (type < 0) return null; ColumnType cType = ColumnType.values()[type]; switch (cType) { case NONE: _wasNull = true; return null; case BINARY: { int len = read(); byte []bytes = new byte[len]; read(bytes, 0, len); return bytes; } case BLOB: return readBlobBytes(); default: throw new RuntimeException("unknown column type:" + type + " column:" + index); } } /** * Returns the integer value of the column. */ public int getInt(int index) throws SQLException { _wasNull = false; setColumn(index); int type = read(); if (type < 0) return 0; ColumnType cType = ColumnType.values()[type]; switch (cType) { case NONE: _wasNull = true; return 0; case SHORT: { int value = (short) ((read() << 8) + (read())); return value; } case INT: { int value = ((read() << 24) + (read() << 16) + (read() << 8) + (read())); return value; } case LONG: case DATE: { long value = (((long) read() << 56) + ((long) read() << 48) + ((long) read() << 40) + ((long) read() << 32) + ((long) read() << 24) + ((long) read() << 16) + ((long) read() << 8) + ((long) read())); return (int) value; } case DOUBLE: { long value = (((long) read() << 56) + ((long) read() << 48) + ((long) read() << 40) + ((long) read() << 32) + ((long) read() << 24) + ((long) read() << 16) + ((long) read() << 8) + ((long) read())); return (int) Double.longBitsToDouble(value); } case VARCHAR: return Integer.parseInt(readString()); case BLOB: return Integer.parseInt(readBlobString()); default: return 0; } } /** * Returns the long value of the column. */ public long getLong(int index) throws SQLException { _wasNull = false; setColumn(index); int type = read(); if (type < 0) return 0; ColumnType cType = ColumnType.values()[type]; switch (cType) { case NONE: _wasNull = true; return 0; case SHORT: { int value = (short) ((read() << 8) + (read())); return value; } case INT: { int value = ((read() << 24) + (read() << 16) + (read() << 8) + (read())); return value; } case LONG: case DATE: { long value = (((long) read() << 56) + ((long) read() << 48) + ((long) read() << 40) + ((long) read() << 32) + ((long) read() << 24) + ((long) read() << 16) + ((long) read() << 8) + ((long) read())); return value; } case DOUBLE: { long value = (((long) read() << 56) + ((long) read() << 48) + ((long) read() << 40) + ((long) read() << 32) + ((long) read() << 24) + ((long) read() << 16) + ((long) read() << 8) + ((long) read())); return (long) Double.longBitsToDouble(value); } case VARCHAR: return Long.parseLong(readString()); case BLOB: return Long.parseLong(readBlobString()); default: return 0; } } /** * Returns a double value from this column. */ public double getDouble(int index) throws SQLException { _wasNull = false; setColumn(index); int type = read(); if (type < 0) return 0; ColumnType cType = ColumnType.values()[type]; switch (cType) { case NONE: _wasNull = true; return 0; case SHORT: { int value = (short) ((read() << 8) + (read())); return value; } case INT: { int value = ((read() << 24) + (read() << 16) + (read() << 8) + (read())); return value; } case LONG: case DATE: { long value = (((long) read() << 56) + ((long) read() << 48) + ((long) read() << 40) + ((long) read() << 32) + ((long) read() << 24) + ((long) read() << 16) + ((long) read() << 8) + ((long) read())); return value; } case DOUBLE: { long value = (((long) read() << 56) + ((long) read() << 48) + ((long) read() << 40) + ((long) read() << 32) + ((long) read() << 24) + ((long) read() << 16) + ((long) read() << 8) + ((long) read())); return Double.longBitsToDouble(value); } case VARCHAR: return Double.parseDouble(readString()); case BLOB: return Double.parseDouble(readBlobString()); default: return 0; } } public long getDate(int index) throws SQLException { _wasNull = false; setColumn(index); int type = read(); if (type < 0) return 0; ColumnType cType = ColumnType.values()[type]; switch (cType) { case NONE: _wasNull = true; return 0; case LONG: case DATE: { long value = (((long) read() << 56) + ((long) read() << 48) + ((long) read() << 40) + ((long) read() << 32) + ((long) read() << 24) + ((long) read() << 16) + ((long) read() << 8) + ((long) read())); return value; } case INT: { long value = (((long) read() << 24) + ((long) read() << 16) + ((long) read() << 8) + ((long) read())); return value; } case VARCHAR: { String value = readString(); synchronized (_date) { try { return _date.parseDate(value); } catch (Exception e) { throw new SQLExceptionWrapper(e); } } } case BLOB: { String value = readBlobString(); synchronized (_date) { try { return _date.parseDate(value); } catch (Exception e) { throw new SQLExceptionWrapper(e); } } } default: throw new SQLException("unknown type: " + type); } } /** * Returns the blob value of the given index. */ public Blob getBlob(int index) throws SQLException { _wasNull = false; setColumn(index); int type = read(); if (type < 0) return null; ColumnType cType = ColumnType.values()[type]; switch (cType) { case NONE: _wasNull = true; return null; case BLOB: return getBlob(); default: throw new RuntimeException("column can't be retrieved as a blob:" + type + " column:" + index); } } /** * Returns the clob value of the given index. */ public Clob getClob(int index) throws SQLException { _wasNull = false; setColumn(index); int type = read(); if (type < 0) return null; ColumnType cType = ColumnType.values()[type]; switch (cType) { case NONE: _wasNull = true; return null; case BLOB: return getClob(); default: throw new RuntimeException("column can't be retrieved as a clob:" + type + " column:" + index); } } /** * Returns true if the last column read was null. */ public boolean wasNull() { return _wasNull; } /** * Returns the string value for the result set. */ private String readString() throws SQLException { int length = ((read() << 24) + (read() << 16) + (read() << 8) + (read())); int len = length >> 1; CharBuffer cb = _cb; cb.ensureCapacity(len); char []cBuf = cb.getBuffer(); int cLen = 0; for (; len > 0; len--) { int ch1 = read(); int ch2 = read(); cBuf[cLen++] = (char) (((ch1 & 0xff) << 8) + (ch2 & 0xff)); } return new String(cBuf, 0, cLen); } /** * Returns the blob value for the result set. */ private Blob getBlob() throws SQLException { BlobImpl blob = new BlobImpl(); blob.setStore(_stores[_column]); byte []inode = blob.getInode(); read(inode, 0, 128); return blob; } /** * Returns the clob value for the result set. */ private Clob getClob() throws SQLException { ClobImpl clob = new ClobImpl(); clob.setStore(_stores[_column]); byte []inode = clob.getInode(); read(inode, 0, 128); return clob; } /** * Returns the string value for the result set. */ private String readBlobString() throws SQLException { read(_blob, 0, 128); CharBuffer cb = _cb; cb.clear(); BlobInputStream is = null; try { is = new BlobInputStream(_stores[_column], _blob, 0); int ch; while ((ch = is.read()) >= 0) { if (ch < 0x80) cb.append((char) ch); } } catch (IOException e) { throw new SQLExceptionWrapper(e); } return cb.toString(); } /** * Returns the string value for the result set. */ private byte []readBlobBytes() throws SQLException { read(_blob, 0, 128); ByteArrayOutputStream bos = new ByteArrayOutputStream(); BlobInputStream is = null; try { is = new BlobInputStream(_stores[_column], _blob, 0); int ch; while ((ch = is.read()) >= 0) { bos.write(ch); } } catch (IOException e) { throw new SQLExceptionWrapper(e); } return bos.toByteArray(); } /** * Set the column in the current row. */ private void setColumn(int column) { if (column < _column) { _offset = _rowOffset; skipColumns(column); } else { _offset = _columnOffset; skipColumns(column - _column); } _column = column; _columnOffset = _offset; } /** * Set the column in the current row. */ void setRow(int rowOffset) { _rowOffset = rowOffset; _offset = rowOffset; _column = 0; _columnOffset = rowOffset; } /** * Skips the specified number of columns. */ private void skipColumns(int count) { for (; count > 0; count--) { int type = read(); if (type < 0) return; ColumnType cType = ColumnType.values()[type]; int sublen; switch (cType) { case NONE: break; case VARCHAR: int l0 = read(); int l1 = read(); int l2 = read(); int l3 = read(); sublen = ((l0 << 24) + (l1 << 16) + (l2 << 8) + (l3)); _offset += sublen; break; case BINARY: sublen = read(); _offset += sublen; break; case SHORT: _offset += 2; break; case INT: _offset += 4; break; case LONG: case DOUBLE: case DATE: _offset += 8; break; case BLOB: _offset += 128; break; default: throw new RuntimeException("Unknown column type: " + type); } } } /** * Starts a row */ public void startRow() { if (_orderIndex != null) _orderIndex.add(_length); _rowCount++; } /** * Writes a null. */ public void writeNull() { write(ColumnType.NONE.ordinal()); } /** * Writes a string. */ public void writeString(String s) { write(ColumnType.VARCHAR.ordinal()); int stringLength = s.length(); int length = 2 * stringLength; write(length >> 24); write(length >> 16); write(length >> 8); write(length); for (int i = 0; i < stringLength; i++) { char ch = s.charAt(i); write(ch << 8); write(ch); } } /** * Writes a string. */ public void writeString(byte []buffer, int offset, int stringLength) { int rLength = _length; int rOffset = rLength % SIZE; int rBlockId = rLength / SIZE; if (_buffers[rBlockId] == null) { TempBuffer tempBuffer = TempBuffer.allocate(); _tempBuffers[rBlockId] = tempBuffer; _buffers[rBlockId] = tempBuffer.getBuffer(); } byte []rBuffer = _buffers[rBlockId]; rBuffer[rOffset] = (byte) ColumnType.VARCHAR.ordinal(); int length = 2 * stringLength; if (rOffset + 5 < rBuffer.length) { rBuffer[rOffset + 1] = (byte) (length >> 24); rBuffer[rOffset + 2] = (byte) (length >> 16); rBuffer[rOffset + 3] = (byte) (length >> 8); rBuffer[rOffset + 4] = (byte) length; if (rOffset + 5 + length < SIZE) { System.arraycopy(buffer, offset, rBuffer, rOffset + 5, length); _length = rLength + 5 + length; } else { _length = rLength + 5; write(buffer, offset, length); } } else { _length = rLength + 1; write(length >> 24); write(length >> 16); write(length >> 8); write(length); write(buffer, offset, length); } } /** * Writes a binary. */ public void writeBinary(byte []buffer, int offset, int length) { write(ColumnType.BINARY.ordinal()); write(length); write(buffer, offset, length); } /** * Writes a string. */ public void writeBlock(int code, byte []buffer, int offset, int length) { write(code); write(buffer, offset, length); } /** * Writes a double. */ public void writeDouble(double dValue) { write(ColumnType.DOUBLE.ordinal()); long value = Double.doubleToLongBits(dValue); write((int) (value >> 56)); write((int) (value >> 48)); write((int) (value >> 40)); write((int) (value >> 32)); write((int) (value >> 24)); write((int) (value >> 16)); write((int) (value >> 8)); write((int) value); } /** * Writes a long. */ public void writeLong(long value) { write(ColumnType.LONG.ordinal()); write((int) (value >> 56)); write((int) (value >> 48)); write((int) (value >> 40)); write((int) (value >> 32)); write((int) (value >> 24)); write((int) (value >> 16)); write((int) (value >> 8)); write((int) value); } /** * Writes a date. */ public void writeDate(long value) { write(ColumnType.DATE.ordinal()); write((int) (value >> 56)); write((int) (value >> 48)); write((int) (value >> 40)); write((int) (value >> 32)); write((int) (value >> 24)); write((int) (value >> 16)); write((int) (value >> 8)); write((int) value); } /** * Writes an long. */ public void writeInt(int value) { write(ColumnType.INT.ordinal()); write(value >> 24); write(value >> 16); write(value >> 8); write(value); } /** * Writes a short */ public void writeShort(int value) { write(ColumnType.SHORT.ordinal()); write(value >> 8); write(value); } /** * Writes a blob. */ public void writeBlob(byte []buffer, int offset) { write(ColumnType.BLOB.ordinal()); write(buffer, offset, 128); } /** * Reads the next byte. */ private int read() { int offset = _offset; if (_length <= offset) return -1; _offset = offset + 1; byte []buf = _buffers[offset / SIZE]; return buf[offset % SIZE] & 0xff; } /** * Reads the next byte. */ private int read(byte []buffer, int bufOffset, int bufLength) { int offset = _offset; int length = _length; byte [][]buffers = _buffers; for (int i = bufLength; i > 0; i--) { if (length <= offset) { _offset = offset; return -1; } byte []buf = buffers[offset / SIZE]; buffer[bufOffset] = buf[offset % SIZE]; offset++; bufOffset++; } _offset = offset; return bufLength; } /** * Writes the next byte. */ public void write(int value) { int length = _length; int rOffset = length % SIZE; int blockId = length / SIZE; while (_buffers.length <= blockId) { byte [][]newBuffers = new byte[2 * _buffers.length][]; System.arraycopy(_buffers, 0, newBuffers, 0, _buffers.length); _buffers = newBuffers; TempBuffer []newTempBuffers = new TempBuffer[newBuffers.length]; System.arraycopy(_tempBuffers, 0, newTempBuffers, 0, _tempBuffers.length); _tempBuffers = newTempBuffers; } byte []buffer = _buffers[blockId]; if (buffer == null) { TempBuffer tempBuffer = TempBuffer.allocate(); _tempBuffers[blockId] = tempBuffer; _buffers[blockId] = tempBuffer.getBuffer(); buffer = _buffers[blockId]; } buffer[rOffset] = (byte) value; _length = length + 1; } /** * Writes a buffer */ public void write(byte []buffer, int offset, int length) { int rLength = _length; while (length > 0) { int rOffset = rLength % SIZE; int rBufferId = rLength / SIZE; if (rOffset == 0) { TempBuffer tempBuffer = TempBuffer.allocate(); if (_tempBuffers.length <= rBufferId) { int len = _tempBuffers.length; TempBuffer []newTempBuffers = new TempBuffer[len + 32]; System.arraycopy(_tempBuffers, 0, newTempBuffers, 0, len); _tempBuffers = newTempBuffers; byte [][]newBuffers = new byte[len + 32][]; System.arraycopy(_buffers, 0, newBuffers, 0, len); _buffers = newBuffers; } _tempBuffers[rBufferId] = tempBuffer; _buffers[rBufferId] = tempBuffer.getBuffer(); } byte []rBuffer = _buffers[rBufferId]; int sublen = rBuffer.length - rOffset; if (length < sublen) sublen = length; System.arraycopy(buffer, offset, rBuffer, rOffset, sublen); length -= sublen; offset += sublen; rLength += sublen; } _length = rLength; } public void close() { for (int i = 0; i < _buffers.length; i++) { TempBuffer buffer = _tempBuffers[i]; if (buffer != null) TempBuffer.free(buffer); _tempBuffers[i] = null; _buffers[i] = null; } for (int i = _stores.length - 1; i >= 0; i--) { _stores[i] = null; } _order = null; _orderIndex = null; _freeList.free(this); } }