/*
* Copyright 2004-2013 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.lealone.result;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import org.lealone.engine.Database;
import org.lealone.engine.Session;
import org.lealone.expression.Expression;
import org.lealone.message.DbException;
import org.lealone.result.ResultInterface;
import org.lealone.util.New;
import org.lealone.util.ValueHashMap;
import org.lealone.value.DataType;
import org.lealone.value.Value;
import org.lealone.value.ValueArray;
/**
* A local result set contains all row data of a result set.
* This is the object generated by engine,
* and it is also used directly by the ResultSet class in the embedded mode.
* If the result does not fit in memory, it is written to a temporary file.
*/
public class LocalResult implements ResultInterface, ResultTarget {
private int maxMemoryRows;
private Session session;
private int visibleColumnCount;
private Expression[] expressions;
private int rowId, rowCount;
private ArrayList<Value[]> rows;
private SortOrder sort;
private ValueHashMap<Value[]> distinctRows;
private Value[] currentRow;
private int offset;
private int limit = -1;
private ResultExternal external;
private int diskOffset;
private boolean distinct;
private boolean randomAccess;
private boolean closed;
/**
* Construct a local result object.
*/
public LocalResult() {
// nothing to do
}
/**
* Construct a local result object.
*
* @param session the session
* @param expressions the expression array
* @param visibleColumnCount the number of visible columns
*/
public LocalResult(Session session, Expression[] expressions, int visibleColumnCount) {
this.session = session;
if (session == null) {
this.maxMemoryRows = Integer.MAX_VALUE;
} else {
this.maxMemoryRows = session.getDatabase().getMaxMemoryRows();
}
rows = New.arrayList();
this.visibleColumnCount = visibleColumnCount;
rowId = -1;
this.expressions = expressions;
}
/**
* Construct a local result set by reading all data from a regular result set.
*
* @param session the session
* @param rs the result set
* @param maxrows the maximum number of rows to read (0 for no limit)
* @return the local result set
*/
public static LocalResult read(Session session, ResultSet rs, int maxrows) {
Expression[] cols = Expression.getExpressionColumns(session, rs);
int columnCount = cols.length;
LocalResult result = new LocalResult(session, cols, columnCount);
try {
for (int i = 0; (maxrows == 0 || i < maxrows) && rs.next(); i++) {
Value[] list = new Value[columnCount];
for (int j = 0; j < columnCount; j++) {
int type = result.getColumnType(j);
list[j] = DataType.readValue(session, rs, j + 1, type);
}
result.addRow(list);
}
} catch (SQLException e) {
throw DbException.convert(e);
}
result.done();
return result;
}
/**
* Create a shallow copy of the result set. The data and a temporary table
* (if there is any) is not copied.
*
* @param targetSession the session of the copy
* @return the copy
*/
public LocalResult createShallowCopy(Session targetSession) {
if (external == null && (rows == null || rows.size() < rowCount)) {
return null;
}
ResultExternal e2 = null;
if (external != null) {
e2 = external.createShallowCopy();
if (e2 == null) {
return null;
}
}
LocalResult copy = new LocalResult();
copy.maxMemoryRows = this.maxMemoryRows;
copy.session = targetSession;
copy.visibleColumnCount = this.visibleColumnCount;
copy.expressions = this.expressions;
copy.rowId = -1;
copy.rowCount = this.rowCount;
copy.rows = this.rows;
copy.sort = this.sort;
copy.distinctRows = this.distinctRows;
copy.distinct = distinct;
copy.randomAccess = randomAccess;
copy.currentRow = null;
copy.offset = 0;
copy.limit = -1;
copy.external = e2;
copy.diskOffset = this.diskOffset;
return copy;
}
/**
* Set the sort order.
*
* @param sort the sort order
*/
public void setSortOrder(SortOrder sort) {
this.sort = sort;
}
/**
* Remove duplicate rows.
*/
public void setDistinct() {
distinct = true;
distinctRows = ValueHashMap.newInstance();
}
/**
* Random access is required (containsDistinct).
*/
public void setRandomAccess() {
this.randomAccess = true;
}
/**
* Remove the row from the result set if it exists.
*
* @param values the row
*/
public void removeDistinct(Value[] values) {
if (!distinct) {
DbException.throwInternalError();
}
if (distinctRows != null) {
ValueArray array = ValueArray.get(values);
distinctRows.remove(array);
rowCount = distinctRows.size();
} else {
rowCount = external.removeRow(values);
}
}
/**
* Check if this result set contains the given row.
*
* @param values the row
* @return true if the row exists
*/
public boolean containsDistinct(Value[] values) {
if (external != null) {
return external.contains(values);
}
if (distinctRows == null) {
distinctRows = ValueHashMap.newInstance();
for (Value[] row : rows) {
if (row.length > visibleColumnCount) {
Value[] r2 = new Value[visibleColumnCount];
System.arraycopy(row, 0, r2, 0, visibleColumnCount);
row = r2;
}
ValueArray array = ValueArray.get(row);
distinctRows.put(array, row);
}
}
ValueArray array = ValueArray.get(values);
return distinctRows.get(array) != null;
}
public void reset() {
rowId = -1;
if (external != null) {
external.reset();
if (diskOffset > 0) {
for (int i = 0; i < diskOffset; i++) {
external.next();
}
}
}
}
public Value[] currentRow() {
return currentRow;
}
public boolean next() {
if (rowId < rowCount) {
rowId++;
if (rowId < rowCount) {
if (external != null) {
currentRow = external.next();
} else {
currentRow = rows.get(rowId);
}
return true;
}
currentRow = null;
}
return false;
}
public int getRowId() {
return rowId;
}
/**
* Add a row to this object.
*
* @param values the row to add
*/
public void addRow(Value[] values) {
if (distinct) {
if (distinctRows != null) {
ValueArray array = ValueArray.get(values);
distinctRows.put(array, values);
rowCount = distinctRows.size();
Database db = session.getDatabase();
if (rowCount > db.getSettings().maxMemoryRowsDistinct && db.isPersistent() && !db.isReadOnly()) {
external = new ResultTempTable(session, sort);
rowCount = external.addRows(distinctRows.values());
distinctRows = null;
}
} else {
rowCount = external.addRow(values);
}
return;
}
rows.add(values);
rowCount++;
if (rows.size() > maxMemoryRows && session.getDatabase().isPersistent()) {
if (external == null) {
if (randomAccess) {
Database db = session.getDatabase();
if (!db.isReadOnly()) {
external = new ResultTempTable(session, sort);
}
} else {
external = new ResultDiskBuffer(session, sort, values.length);
}
}
addRowsToDisk();
}
}
private void addRowsToDisk() {
rowCount = external.addRows(rows);
rows.clear();
}
public int getVisibleColumnCount() {
return visibleColumnCount;
}
/**
* This method is called after all rows have been added.
*/
public void done() {
if (distinct) {
if (distinctRows != null) {
rows = distinctRows.values();
} else {
if (external != null && sort != null) {
// external sort
ResultExternal temp = external;
external = null;
temp.reset();
rows = New.arrayList();
// TODO use offset directly if possible
while (true) {
Value[] list = temp.next();
if (list == null) {
break;
}
if (external == null) {
if (randomAccess) {
Database db = session.getDatabase();
if (!db.isReadOnly()) {
external = new ResultTempTable(session, sort);
}
} else {
external = new ResultDiskBuffer(session, sort, list.length);
}
}
rows.add(list);
if (rows.size() > maxMemoryRows) {
rowCount = external.addRows(rows);
rows.clear();
}
}
temp.close();
// the remaining data in rows is written in the following lines
}
}
}
if (external != null) {
addRowsToDisk();
external.done();
} else {
if (sort != null) {
if (offset > 0 || limit > 0) {
sort.sort(rows, offset, limit < 0 ? rows.size() : limit);
} else {
sort.sort(rows);
}
}
}
applyOffset();
applyLimit();
reset();
}
public int getRowCount() {
return rowCount;
}
/**
* Set the number of rows that this result will return at the maximum.
*
* @param limit the limit (-1 means no limit, 0 means no rows)
*/
public void setLimit(int limit) {
this.limit = limit;
}
private void applyLimit() {
if (limit < 0) {
return;
}
if (external == null) {
if (rows.size() > limit) {
rows = New.arrayList(rows.subList(0, limit));
rowCount = limit;
}
} else {
if (limit < rowCount) {
rowCount = limit;
}
}
}
public boolean needToClose() {
return external != null;
}
public void close() {
if (external != null) {
external.close();
external = null;
closed = true;
}
}
public String getAlias(int i) {
return expressions[i].getAlias();
}
public String getTableName(int i) {
return expressions[i].getTableName();
}
public String getSchemaName(int i) {
return expressions[i].getSchemaName();
}
public int getDisplaySize(int i) {
return expressions[i].getDisplaySize();
}
public String getColumnName(int i) {
return expressions[i].getColumnName();
}
public int getColumnType(int i) {
return expressions[i].getType();
}
public long getColumnPrecision(int i) {
return expressions[i].getPrecision();
}
public int getNullable(int i) {
return expressions[i].getNullable();
}
public boolean isAutoIncrement(int i) {
return expressions[i].isAutoIncrement();
}
public int getColumnScale(int i) {
return expressions[i].getScale();
}
/**
* Set the offset of the first row to return.
*
* @param offset the offset
*/
public void setOffset(int offset) {
this.offset = offset;
}
private void applyOffset() {
if (offset <= 0) {
return;
}
if (external == null) {
if (offset >= rows.size()) {
rows.clear();
rowCount = 0;
} else {
// avoid copying the whole array for each row
int remove = Math.min(offset, rows.size());
rows = New.arrayList(rows.subList(remove, rows.size()));
rowCount -= remove;
}
} else {
if (offset >= rowCount) {
rowCount = 0;
} else {
diskOffset = offset;
rowCount -= offset;
}
}
}
public String toString() {
return super.toString() + " columns: " + visibleColumnCount + " rows: " + rowCount + " pos: " + rowId;
}
/**
* Check if this result set is closed.
*
* @return true if it is
*/
public boolean isClosed() {
return closed;
}
public int getFetchSize() {
return 0;
}
public void setFetchSize(int fetchSize) {
// ignore
}
}