Unverified 提交 75da7235 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov 提交者: GitHub

Merge pull request #1424 from tledkov-gridgain/localresult-interface

Introduce LocalResult factory (Issue #1405)
...@@ -12,6 +12,7 @@ import org.h2.engine.Session; ...@@ -12,6 +12,7 @@ import org.h2.engine.Session;
import org.h2.expression.Expression; import org.h2.expression.Expression;
import org.h2.expression.ExpressionVisitor; import org.h2.expression.ExpressionVisitor;
import org.h2.result.LocalResult; import org.h2.result.LocalResult;
import org.h2.result.LocalResultFactory;
import org.h2.result.ResultInterface; import org.h2.result.ResultInterface;
import org.h2.value.Value; import org.h2.value.Value;
...@@ -34,9 +35,9 @@ public class Call extends Prepared { ...@@ -34,9 +35,9 @@ public class Call extends Prepared {
LocalResult result; LocalResult result;
if (isResultSet) { if (isResultSet) {
Expression[] expr = expression.getExpressionColumns(session); Expression[] expr = expression.getExpressionColumns(session);
result = new LocalResult(session, expr, expr.length); result = session.getDatabase().getResultFactory().create(session, expr, expr.length);
} else { } else {
result = new LocalResult(session, expressions, 1); result = session.getDatabase().getResultFactory().create(session, expressions, 1);
} }
result.done(); result.done();
return result; return result;
...@@ -66,9 +67,9 @@ public class Call extends Prepared { ...@@ -66,9 +67,9 @@ public class Call extends Prepared {
if (isResultSet) { if (isResultSet) {
v = v.convertTo(Value.RESULT_SET); v = v.convertTo(Value.RESULT_SET);
ResultSet rs = v.getResultSet(); ResultSet rs = v.getResultSet();
return LocalResult.read(session, rs, maxrows); return LocalResultFactory.read(session, rs, maxrows);
} }
LocalResult result = new LocalResult(session, expressions, 1); LocalResult result = session.getDatabase().getResultFactory().create(session, expressions, 1);
Value[] row = { v }; Value[] row = { v };
result.addRow(row); result.addRow(row);
result.done(); result.done();
......
...@@ -72,7 +72,7 @@ public class Explain extends Prepared { ...@@ -72,7 +72,7 @@ public class Explain extends Prepared {
Database db = session.getDatabase(); Database db = session.getDatabase();
ExpressionColumn expr = new ExpressionColumn(db, column); ExpressionColumn expr = new ExpressionColumn(db, column);
Expression[] expressions = { expr }; Expression[] expressions = { expr };
result = new LocalResult(session, expressions, 1); result = db.getResultFactory().create(session, expressions, 1);
if (maxrows >= 0) { if (maxrows >= 0) {
String plan; String plan;
if (executeCommand) { if (executeCommand) {
......
...@@ -137,7 +137,7 @@ public class ScriptCommand extends ScriptBase { ...@@ -137,7 +137,7 @@ public class ScriptCommand extends ScriptBase {
private LocalResult createResult() { private LocalResult createResult() {
Expression[] expressions = { new ExpressionColumn( Expression[] expressions = { new ExpressionColumn(
session.getDatabase(), new Column("SCRIPT", Value.STRING)) }; session.getDatabase(), new Column("SCRIPT", Value.STRING)) };
return new LocalResult(session, expressions, 1); return session.getDatabase().getResultFactory().create(session, expressions, 1);
} }
@Override @Override
......
...@@ -687,7 +687,7 @@ public class Select extends Query { ...@@ -687,7 +687,7 @@ public class Select extends Query {
@Override @Override
public ResultInterface queryMeta() { public ResultInterface queryMeta() {
LocalResult result = new LocalResult(session, expressionArray, LocalResult result = session.getDatabase().getResultFactory().create(session, expressionArray,
visibleColumnCount); visibleColumnCount);
result.done(); result.done();
return result; return result;
...@@ -858,12 +858,13 @@ public class Select extends Query { ...@@ -858,12 +858,13 @@ public class Select extends Query {
} }
private LocalResult createLocalResult(LocalResult old) { private LocalResult createLocalResult(LocalResult old) {
return old != null ? old : new LocalResult(session, expressionArray, return old != null ? old : session.getDatabase().getResultFactory().create(session, expressionArray,
visibleColumnCount); visibleColumnCount);
} }
private LocalResult convertToDistinct(ResultInterface result) { private LocalResult convertToDistinct(ResultInterface result) {
LocalResult distinctResult = new LocalResult(session, expressionArray, visibleColumnCount); LocalResult distinctResult = session.getDatabase().getResultFactory().create(session,
expressionArray, visibleColumnCount);
distinctResult.setDistinct(); distinctResult.setDistinct();
result.reset(); result.reset();
while (result.next()) { while (result.next()) {
......
...@@ -130,14 +130,14 @@ public class SelectUnion extends Query { ...@@ -130,14 +130,14 @@ public class SelectUnion extends Query {
@Override @Override
public ResultInterface queryMeta() { public ResultInterface queryMeta() {
int columnCount = left.getColumnCount(); int columnCount = left.getColumnCount();
LocalResult result = new LocalResult(session, expressionArray, columnCount); LocalResult result = session.getDatabase().getResultFactory().create(session, expressionArray, columnCount);
result.done(); result.done();
return result; return result;
} }
public LocalResult getEmptyResult() { public LocalResult getEmptyResult() {
int columnCount = left.getColumnCount(); int columnCount = left.getColumnCount();
return new LocalResult(session, expressionArray, columnCount); return session.getDatabase().getResultFactory().create(session, expressionArray, columnCount);
} }
@Override @Override
...@@ -189,7 +189,7 @@ public class SelectUnion extends Query { ...@@ -189,7 +189,7 @@ public class SelectUnion extends Query {
return lazyResult; return lazyResult;
} }
} }
LocalResult result = new LocalResult(session, expressionArray, columnCount); LocalResult result = session.getDatabase().getResultFactory().create(session, expressionArray, columnCount);
if (sort != null) { if (sort != null) {
result.setSortOrder(sort); result.setSortOrder(sort);
} }
...@@ -239,7 +239,7 @@ public class SelectUnion extends Query { ...@@ -239,7 +239,7 @@ public class SelectUnion extends Query {
break; break;
} }
case INTERSECT: { case INTERSECT: {
LocalResult temp = new LocalResult(session, expressionArray, columnCount); LocalResult temp = session.getDatabase().getResultFactory().create(session, expressionArray, columnCount);
temp.setDistinct(); temp.setDistinct();
while (l.next()) { while (l.next()) {
temp.addRow(convert(l.currentRow(), columnCount)); temp.addRow(convert(l.currentRow(), columnCount));
......
...@@ -19,6 +19,7 @@ import org.h2.expression.Expression; ...@@ -19,6 +19,7 @@ import org.h2.expression.Expression;
import org.h2.expression.ValueExpression; import org.h2.expression.ValueExpression;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.message.Trace; import org.h2.message.Trace;
import org.h2.result.LocalResultFactory;
import org.h2.result.ResultInterface; import org.h2.result.ResultInterface;
import org.h2.result.RowFactory; import org.h2.result.RowFactory;
import org.h2.schema.Schema; import org.h2.schema.Schema;
...@@ -558,6 +559,19 @@ public class Set extends Prepared { ...@@ -558,6 +559,19 @@ public class Set extends Prepared {
} }
break; break;
} }
case SetTypes.LOCAL_RESULT_FACTORY: {
session.getUser().checkAdmin();
String localResultFactoryName = expression.getColumnName();
Class<LocalResultFactory> localResultFactoryClass = JdbcUtils.loadUserClass(localResultFactoryName);
LocalResultFactory localResultFactory;
try {
localResultFactory = localResultFactoryClass.getDeclaredConstructor().newInstance();
database.setResultFactory(localResultFactory);
} catch (Exception e) {
throw DbException.convert(e);
}
break;
}
default: default:
DbException.throwInternalError("type="+type); DbException.throwInternalError("type="+type);
} }
......
...@@ -252,7 +252,12 @@ public class SetTypes { ...@@ -252,7 +252,12 @@ public class SetTypes {
*/ */
public static final int AUTHENTICATOR = 48; public static final int AUTHENTICATOR = 48;
private static final int COUNT = AUTHENTICATOR + 1; /**
* The type of a SET LOCAL_RESULT_FACTORY statement.
*/
public static final int LOCAL_RESULT_FACTORY = 49;
private static final int COUNT = LOCAL_RESULT_FACTORY + 1;
private static final ArrayList<String> TYPES; private static final ArrayList<String> TYPES;
...@@ -311,6 +316,7 @@ public class SetTypes { ...@@ -311,6 +316,7 @@ public class SetTypes {
list.add(BUILTIN_ALIAS_OVERRIDE, "BUILTIN_ALIAS_OVERRIDE"); list.add(BUILTIN_ALIAS_OVERRIDE, "BUILTIN_ALIAS_OVERRIDE");
list.add(COLUMN_NAME_RULES, "COLUMN_NAME_RULES"); list.add(COLUMN_NAME_RULES, "COLUMN_NAME_RULES");
list.add(AUTHENTICATOR, "AUTHENTICATOR"); list.add(AUTHENTICATOR, "AUTHENTICATOR");
list.add(LOCAL_RESULT_FACTORY, "LOCAL_RESULT_FACTORY");
TYPES = list; TYPES = list;
} }
......
...@@ -41,6 +41,7 @@ import org.h2.message.Trace; ...@@ -41,6 +41,7 @@ import org.h2.message.Trace;
import org.h2.message.TraceSystem; import org.h2.message.TraceSystem;
import org.h2.mvstore.MVStore; import org.h2.mvstore.MVStore;
import org.h2.mvstore.db.MVTableEngine; import org.h2.mvstore.db.MVTableEngine;
import org.h2.result.LocalResultFactory;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.result.RowFactory; import org.h2.result.RowFactory;
import org.h2.result.SearchRow; import org.h2.result.SearchRow;
...@@ -230,6 +231,7 @@ public class Database implements DataHandler { ...@@ -230,6 +231,7 @@ public class Database implements DataHandler {
private int queryStatisticsMaxEntries = Constants.QUERY_STATISTICS_MAX_ENTRIES; private int queryStatisticsMaxEntries = Constants.QUERY_STATISTICS_MAX_ENTRIES;
private QueryStatisticsData queryStatisticsData; private QueryStatisticsData queryStatisticsData;
private RowFactory rowFactory = RowFactory.DEFAULT; private RowFactory rowFactory = RowFactory.DEFAULT;
private LocalResultFactory resultFactory = LocalResultFactory.DEFAULT;
private Authenticator authenticator; private Authenticator authenticator;
...@@ -369,6 +371,14 @@ public class Database implements DataHandler { ...@@ -369,6 +371,14 @@ public class Database implements DataHandler {
this.rowFactory = rowFactory; this.rowFactory = rowFactory;
} }
public LocalResultFactory getResultFactory() {
return resultFactory;
}
public void setResultFactory(LocalResultFactory resultFactory) {
this.resultFactory = resultFactory;
}
public static void setInitialPowerOffCount(int count) { public static void setInitialPowerOffCount(int count) {
initialPowerOffCount = count; initialPowerOffCount = count;
} }
......
...@@ -128,7 +128,7 @@ public final class GeneratedKeys { ...@@ -128,7 +128,7 @@ public final class GeneratedKeys {
Database db = session.getDatabase(); Database db = session.getDatabase();
if (Boolean.FALSE.equals(generatedKeysRequest)) { if (Boolean.FALSE.equals(generatedKeysRequest)) {
clear(null); clear(null);
return new LocalResult(); return db.getResultFactory().create();
} }
ArrayList<ExpressionColumn> expressionColumns; ArrayList<ExpressionColumn> expressionColumns;
if (Boolean.TRUE.equals(generatedKeysRequest)) { if (Boolean.TRUE.equals(generatedKeysRequest)) {
...@@ -152,7 +152,7 @@ public final class GeneratedKeys { ...@@ -152,7 +152,7 @@ public final class GeneratedKeys {
} }
} else { } else {
clear(null); clear(null);
return new LocalResult(); return db.getResultFactory().create();
} }
} else if (generatedKeysRequest instanceof String[]) { } else if (generatedKeysRequest instanceof String[]) {
if (table != null) { if (table != null) {
...@@ -182,18 +182,19 @@ public final class GeneratedKeys { ...@@ -182,18 +182,19 @@ public final class GeneratedKeys {
} }
} else { } else {
clear(null); clear(null);
return new LocalResult(); return db.getResultFactory().create();
} }
} else { } else {
clear(null); clear(null);
return new LocalResult(); return db.getResultFactory().create();
} }
int columnCount = expressionColumns.size(); int columnCount = expressionColumns.size();
if (columnCount == 0) { if (columnCount == 0) {
clear(null); clear(null);
return new LocalResult(); return db.getResultFactory().create();
} }
LocalResult result = new LocalResult(session, expressionColumns.toArray(new Expression[0]), columnCount); LocalResult result = db.getResultFactory().create(session,
expressionColumns.toArray(new Expression[0]), columnCount);
for (Map<Column, Value> map : data) { for (Map<Column, Value> map : data) {
Value[] row = new Value[columnCount]; Value[] row = new Value[columnCount];
for (Map.Entry<Column, Value> entry : map.entrySet()) { for (Map.Entry<Column, Value> entry : map.entrySet()) {
......
...@@ -86,7 +86,7 @@ public class TableFunction extends Function { ...@@ -86,7 +86,7 @@ public class TableFunction extends Function {
ExpressionColumn col = new ExpressionColumn(db, c); ExpressionColumn col = new ExpressionColumn(db, c);
header[i] = col; header[i] = col;
} }
LocalResult result = new LocalResult(session, header, len); LocalResult result = db.getResultFactory().create(session, header, len);
if (distinctRows) { if (distinctRows) {
result.setDistinct(); result.setDistinct();
} }
......
...@@ -5,604 +5,75 @@ ...@@ -5,604 +5,75 @@
*/ */
package org.h2.result; package org.h2.result;
import java.sql.ResultSet; import org.h2.engine.SysProperties;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.engine.SessionInterface;
import org.h2.expression.Expression;
import org.h2.message.DbException;
import org.h2.mvstore.db.MVTempResult;
import org.h2.util.Utils;
import org.h2.util.ValueHashMap;
import org.h2.value.DataType;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueArray;
/** /**
* A local result set contains all row data of a result set. * A local result set contains all row data of a result set.
* This is the object generated by engine, * The object is generated by {@link LocalResultFactory},
* and it is also used directly by the ResultSet class in the embedded mode. * 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. * The memory usage and other policies are defined by implementation.
*/
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 boolean fetchPercent;
private boolean withTies;
private boolean limitsWereApplied;
private ResultExternal external;
private boolean distinct;
private int[] distinctIndexes;
private boolean closed;
private boolean containsLobs;
/**
* 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, public interface LocalResult extends ResultInterface, ResultTarget {
int visibleColumnCount) {
this.session = session;
if (session == null) {
this.maxMemoryRows = Integer.MAX_VALUE;
} else {
Database db = session.getDatabase();
if (db.isPersistent() && !db.isReadOnly()) {
this.maxMemoryRows = session.getDatabase().getMaxMemoryRows();
} else {
this.maxMemoryRows = Integer.MAX_VALUE;
}
}
rows = Utils.newSmallArrayList();
this.visibleColumnCount = visibleColumnCount;
rowId = -1;
this.expressions = expressions;
}
@Override
public boolean isLazy() {
return false;
}
public void setMaxMemoryRows(int maxValue) {
this.maxMemoryRows = maxValue;
}
/** /**
* Construct a local result set by reading all data from a regular result * Redefine count of maximum rows holds in memory for the result.
* set.
* *
* @param session the session * @param maxValue Maximum rows count in memory.
* @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 * @see SysProperties#MAX_MEMORY_ROWS
* @return the copy if possible, or null if copying is not possible
*/ */
@Override public void setMaxMemoryRows(int maxValue);
public LocalResult createShallowCopy(SessionInterface targetSession) {
if (external == null && (rows == null || rows.size() < rowCount)) {
return null;
}
if (containsLobs) {
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 = (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.distinctIndexes = distinctIndexes;
copy.currentRow = null;
copy.offset = 0;
copy.limit = -1;
copy.external = e2;
return copy;
}
/** /**
* Set the sort order. * @param sort Sort order.
*
* @param sort the sort order
*/ */
public void setSortOrder(SortOrder sort) { public void setSortOrder(SortOrder sort);
this.sort = sort;
}
/** /**
* Remove duplicate rows. * Remove duplicate rows.
*/ */
public void setDistinct() { public void setDistinct();
assert distinctIndexes == null;
distinct = true;
distinctRows = ValueHashMap.newInstance();
}
/** /**
* Remove rows with duplicates in columns with specified indexes. * Remove rows with duplicates in columns with specified indexes.
* *
* @param distinctIndexes distinct indexes * @param distinctIndexes distinct indexes
*/ */
public void setDistinct(int[] distinctIndexes) { public void setDistinct(int[] distinctIndexes);
assert !distinct;
this.distinctIndexes = distinctIndexes;
distinctRows = ValueHashMap.newInstance();
}
/**
* @return whether this result is a distinct result
*/
public boolean isAnyDistinct() {
return distinct || distinctIndexes != null;
}
/** /**
* Remove the row from the result set if it exists. * Remove the row from the result set if it exists.
* *
* @param values the row * @param values the row
*/ */
public void removeDistinct(Value[] values) { public void removeDistinct(Value[] values);
if (!distinct) {
DbException.throwInternalError();
}
assert values.length == visibleColumnCount;
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
*/
@Override
public boolean containsDistinct(Value[] values) {
assert values.length == visibleColumnCount;
if (external != null) {
return external.contains(values);
}
if (distinctRows == null) {
distinctRows = ValueHashMap.newInstance();
for (Value[] row : rows) {
ValueArray array = getArrayOfDistinct(row);
distinctRows.put(array, array.getList());
}
}
ValueArray array = ValueArray.get(values);
return distinctRows.get(array) != null;
}
@Override
public void reset() {
rowId = -1;
currentRow = null;
if (external != null) {
external.reset();
}
}
@Override
public Value[] currentRow() {
return currentRow;
}
@Override
public boolean next() {
if (!closed && rowId < rowCount) {
rowId++;
if (rowId < rowCount) {
if (external != null) {
currentRow = external.next();
} else {
currentRow = rows.get(rowId);
}
return true;
}
currentRow = null;
}
return false;
}
@Override
public int getRowId() {
return rowId;
}
@Override
public boolean isAfterLast() {
return rowId >= rowCount;
}
private void cloneLobs(Value[] values) {
for (int i = 0; i < values.length; i++) {
Value v = values[i];
Value v2 = v.copyToResult();
if (v2 != v) {
containsLobs = true;
session.addTemporaryLob(v2);
values[i] = v2;
}
}
}
private ValueArray getArrayOfDistinct(Value[] values) {
if (distinctIndexes != null) {
int cnt = distinctIndexes.length;
Value[] newValues = new Value[cnt];
for (int i = 0; i < cnt; i++) {
newValues[i] = values[distinctIndexes[i]];
}
values = newValues;
} else if (values.length > visibleColumnCount) {
values = Arrays.copyOf(values, visibleColumnCount);
}
return ValueArray.get(values);
}
private void createExternalResult() {
Database database = session.getDatabase();
external = database.isMVStore()
|| /* not supported by ResultTempTable */ distinct && expressions.length != visibleColumnCount
|| distinctIndexes != null
? MVTempResult.of(database, expressions, distinct, distinctIndexes, visibleColumnCount, sort)
: new ResultTempTable(session, expressions, distinct, sort);
}
/**
* Add a row to this object.
*
* @param values the row to add
*/
@Override
public void addRow(Value[] values) {
cloneLobs(values);
if (isAnyDistinct()) {
if (distinctRows != null) {
ValueArray array = getArrayOfDistinct(values);
distinctRows.putIfAbsent(array, values);
rowCount = distinctRows.size();
if (rowCount > maxMemoryRows) {
createExternalResult();
rowCount = external.addRows(distinctRows.values());
distinctRows = null;
}
} else {
rowCount = external.addRow(values);
}
} else {
rows.add(values);
rowCount++;
if (rows.size() > maxMemoryRows) {
addRowsToDisk();
}
}
}
private void addRowsToDisk() {
if (external == null) {
createExternalResult();
}
rowCount = external.addRows(rows);
rows.clear();
}
@Override
public int getVisibleColumnCount() {
return visibleColumnCount;
}
/** /**
* This method is called after all rows have been added. * This method is called after all rows have been added.
*/ */
public void done() { public void done();
if (external != null) {
addRowsToDisk();
} else {
if (isAnyDistinct()) {
rows = distinctRows.values();
}
if (sort != null && limit != 0) {
boolean withLimit = limit > 0 && !withTies;
if (offset > 0 || withLimit) {
sort.sort(rows, offset, withLimit ? limit : rows.size());
} else {
sort.sort(rows);
}
}
}
applyOffsetAndLimit();
reset();
}
private void applyOffsetAndLimit() {
if (limitsWereApplied) {
return;
}
int offset = Math.max(this.offset, 0);
int limit = this.limit;
if (offset == 0 && limit < 0 && !fetchPercent || rowCount == 0) {
return;
}
if (fetchPercent) {
if (limit < 0 || limit > 100) {
throw DbException.getInvalidValueException("FETCH PERCENT", limit);
}
// Oracle rounds percent up, do the same for now
limit = (int) (((long) limit * rowCount + 99) / 100);
}
boolean clearAll = offset >= rowCount || limit == 0;
if (!clearAll) {
int remaining = rowCount - offset;
limit = limit < 0 ? remaining : Math.min(remaining, limit);
if (offset == 0 && remaining <= limit) {
return;
}
} else {
limit = 0;
}
distinctRows = null;
rowCount = limit;
if (external == null) {
if (clearAll) {
rows.clear();
return;
}
int to = offset + limit;
if (withTies && sort != null) {
Value[] expected = rows.get(to - 1);
while (to < rows.size() && sort.compare(expected, rows.get(to)) == 0) {
to++;
rowCount++;
}
}
if (offset != 0 || to != rows.size()) {
// avoid copying the whole array for each row
rows = new ArrayList<>(rows.subList(offset, to));
}
} else {
if (clearAll) {
external.close();
external = null;
return;
}
trimExternal(offset, limit);
}
}
private void trimExternal(int offset, int limit) {
ResultExternal temp = external;
external = null;
temp.reset();
while (--offset >= 0) {
temp.next();
}
Value[] row = null;
while (--limit >= 0) {
row = temp.next();
rows.add(row);
if (rows.size() > maxMemoryRows) {
addRowsToDisk();
}
}
if (withTies && sort != null && row != null) {
Value[] expected = row;
while ((row = temp.next()) != null && sort.compare(expected, row) == 0) {
rows.add(row);
rowCount++;
if (rows.size() > maxMemoryRows) {
addRowsToDisk();
}
}
}
if (external != null) {
addRowsToDisk();
}
temp.close();
}
@Override
public int getRowCount() {
return rowCount;
}
@Override
public void limitsWereApplied() {
this.limitsWereApplied = true;
}
@Override
public boolean hasNext() {
return !closed && rowId < rowCount - 1;
}
/** /**
* Set the number of rows that this result will return at the maximum. * 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) * @param limit the limit (-1 means no limit, 0 means no rows)
*/ */
public void setLimit(int limit) { public void setLimit(int limit);
this.limit = limit;
}
/** /**
* @param fetchPercent whether limit expression specifies percentage of rows * @param fetchPercent whether limit expression specifies percentage of rows
*/ */
public void setFetchPercent(boolean fetchPercent) { public void setFetchPercent(boolean fetchPercent);
this.fetchPercent = fetchPercent;
}
/** /**
* @param withTies whether tied rows should be included in result too * @param withTies whether tied rows should be included in result too
*/ */
public void setWithTies(boolean withTies) { public void setWithTies(boolean withTies);
this.withTies = withTies;
}
@Override
public boolean needToClose() {
return external != null;
}
@Override
public void close() {
if (external != null) {
external.close();
external = null;
closed = true;
}
}
@Override
public String getAlias(int i) {
return expressions[i].getAlias();
}
@Override
public String getTableName(int i) {
return expressions[i].getTableName();
}
@Override
public String getSchemaName(int i) {
return expressions[i].getSchemaName();
}
@Override
public int getDisplaySize(int i) {
return expressions[i].getDisplaySize();
}
@Override
public String getColumnName(int i) {
return expressions[i].getColumnName();
}
@Override
public int getColumnType(int i) {
return expressions[i].getType();
}
@Override
public long getColumnPrecision(int i) {
return expressions[i].getPrecision();
}
@Override
public int getNullable(int i) {
return expressions[i].getNullable();
}
@Override
public boolean isAutoIncrement(int i) {
return expressions[i].isAutoIncrement();
}
@Override
public int getColumnScale(int i) {
return expressions[i].getScale();
}
/** /**
* Set the offset of the first row to return. * Set the offset of the first row to return.
* *
* @param offset the offset * @param offset the offset
*/ */
public void setOffset(int offset) { public void setOffset(int offset);
this.offset = offset;
}
@Override
public String toString() {
return super.toString() + " columns: " + visibleColumnCount +
" rows: " + rowCount + " pos: " + rowId;
}
/**
* Check if this result set is closed.
*
* @return true if it is
*/
@Override
public boolean isClosed() {
return closed;
}
@Override
public int getFetchSize() {
return 0;
}
@Override
public void setFetchSize(int fetchSize) {
// ignore
}
} }
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.result;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.h2.engine.Session;
import org.h2.expression.Expression;
import org.h2.message.DbException;
import org.h2.value.DataType;
import org.h2.value.Value;
/**
* Creates local result.
*/
public abstract class LocalResultFactory {
/**
* Default implementation of local result factory.
*/
public static final LocalResultFactory DEFAULT = new DefaultLocalResultFactory();
/**
* Create a local result object.
*
* @param session the session
* @param expressions the expression array
* @param visibleColumnCount the number of visible columns
* @return object to collect local result.
*/
public abstract LocalResult create(Session session, Expression[] expressions, int visibleColumnCount);
/**
* Create a local result object.
* @return object to collect local result.
*/
public abstract LocalResult create();
/**
* 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 = session.getDatabase().getResultFactory().create(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;
}
/**
* Default implementation of local result factory.
*/
private static final class DefaultLocalResultFactory extends LocalResultFactory {
/**
*
*/
DefaultLocalResultFactory() {
//No-op.
}
@Override
public LocalResult create(Session session, Expression[] expressions, int visibleColumnCount) {
return new LocalResultImpl(session, expressions, visibleColumnCount);
}
@Override
public LocalResult create() {
return new LocalResultImpl();
}
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.result;
import java.util.ArrayList;
import java.util.Arrays;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.engine.SessionInterface;
import org.h2.expression.Expression;
import org.h2.message.DbException;
import org.h2.mvstore.db.MVTempResult;
import org.h2.util.Utils;
import org.h2.util.ValueHashMap;
import org.h2.value.Value;
import org.h2.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 LocalResultImpl implements LocalResult {
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 boolean fetchPercent;
private boolean withTies;
private boolean limitsWereApplied;
private ResultExternal external;
private boolean distinct;
private int[] distinctIndexes;
private boolean closed;
private boolean containsLobs;
/**
* Construct a local result object.
*/
public LocalResultImpl() {
// 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 LocalResultImpl(Session session, Expression[] expressions,
int visibleColumnCount) {
this.session = session;
if (session == null) {
this.maxMemoryRows = Integer.MAX_VALUE;
} else {
Database db = session.getDatabase();
if (db.isPersistent() && !db.isReadOnly()) {
this.maxMemoryRows = session.getDatabase().getMaxMemoryRows();
} else {
this.maxMemoryRows = Integer.MAX_VALUE;
}
}
rows = Utils.newSmallArrayList();
this.visibleColumnCount = visibleColumnCount;
rowId = -1;
this.expressions = expressions;
}
@Override
public boolean isLazy() {
return false;
}
public void setMaxMemoryRows(int maxValue) {
this.maxMemoryRows = maxValue;
}
/**
* 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 if possible, or null if copying is not possible
*/
@Override
public LocalResultImpl createShallowCopy(SessionInterface targetSession) {
if (external == null && (rows == null || rows.size() < rowCount)) {
return null;
}
if (containsLobs) {
return null;
}
ResultExternal e2 = null;
if (external != null) {
e2 = external.createShallowCopy();
if (e2 == null) {
return null;
}
}
LocalResultImpl copy = new LocalResultImpl();
copy.maxMemoryRows = this.maxMemoryRows;
copy.session = (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.distinctIndexes = distinctIndexes;
copy.currentRow = null;
copy.offset = 0;
copy.limit = -1;
copy.external = e2;
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() {
assert distinctIndexes == null;
distinct = true;
distinctRows = ValueHashMap.newInstance();
}
/**
* Remove rows with duplicates in columns with specified indexes.
*
* @param distinctIndexes distinct indexes
*/
public void setDistinct(int[] distinctIndexes) {
assert !distinct;
this.distinctIndexes = distinctIndexes;
distinctRows = ValueHashMap.newInstance();
}
/**
* @return whether this result is a distinct result
*/
private boolean isAnyDistinct() {
return distinct || distinctIndexes != null;
}
/**
* Remove the row from the result set if it exists.
*
* @param values the row
*/
public void removeDistinct(Value[] values) {
if (!distinct) {
DbException.throwInternalError();
}
assert values.length == visibleColumnCount;
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
*/
@Override
public boolean containsDistinct(Value[] values) {
assert values.length == visibleColumnCount;
if (external != null) {
return external.contains(values);
}
if (distinctRows == null) {
distinctRows = ValueHashMap.newInstance();
for (Value[] row : rows) {
ValueArray array = getArrayOfDistinct(row);
distinctRows.put(array, array.getList());
}
}
ValueArray array = ValueArray.get(values);
return distinctRows.get(array) != null;
}
@Override
public void reset() {
rowId = -1;
currentRow = null;
if (external != null) {
external.reset();
}
}
@Override
public Value[] currentRow() {
return currentRow;
}
@Override
public boolean next() {
if (!closed && rowId < rowCount) {
rowId++;
if (rowId < rowCount) {
if (external != null) {
currentRow = external.next();
} else {
currentRow = rows.get(rowId);
}
return true;
}
currentRow = null;
}
return false;
}
@Override
public int getRowId() {
return rowId;
}
@Override
public boolean isAfterLast() {
return rowId >= rowCount;
}
private void cloneLobs(Value[] values) {
for (int i = 0; i < values.length; i++) {
Value v = values[i];
Value v2 = v.copyToResult();
if (v2 != v) {
containsLobs = true;
session.addTemporaryLob(v2);
values[i] = v2;
}
}
}
private ValueArray getArrayOfDistinct(Value[] values) {
if (distinctIndexes != null) {
int cnt = distinctIndexes.length;
Value[] newValues = new Value[cnt];
for (int i = 0; i < cnt; i++) {
newValues[i] = values[distinctIndexes[i]];
}
values = newValues;
} else if (values.length > visibleColumnCount) {
values = Arrays.copyOf(values, visibleColumnCount);
}
return ValueArray.get(values);
}
private void createExternalResult() {
Database database = session.getDatabase();
external = database.isMVStore()
|| /* not supported by ResultTempTable */ distinct && expressions.length != visibleColumnCount
|| distinctIndexes != null
? MVTempResult.of(database, expressions, distinct, distinctIndexes, visibleColumnCount, sort)
: new ResultTempTable(session, expressions, distinct, sort);
}
/**
* Add a row to this object.
*
* @param values the row to add
*/
@Override
public void addRow(Value[] values) {
cloneLobs(values);
if (isAnyDistinct()) {
if (distinctRows != null) {
ValueArray array = getArrayOfDistinct(values);
distinctRows.putIfAbsent(array, values);
rowCount = distinctRows.size();
if (rowCount > maxMemoryRows) {
createExternalResult();
rowCount = external.addRows(distinctRows.values());
distinctRows = null;
}
} else {
rowCount = external.addRow(values);
}
} else {
rows.add(values);
rowCount++;
if (rows.size() > maxMemoryRows) {
addRowsToDisk();
}
}
}
private void addRowsToDisk() {
if (external == null) {
createExternalResult();
}
rowCount = external.addRows(rows);
rows.clear();
}
@Override
public int getVisibleColumnCount() {
return visibleColumnCount;
}
/**
* This method is called after all rows have been added.
*/
public void done() {
if (external != null) {
addRowsToDisk();
} else {
if (isAnyDistinct()) {
rows = distinctRows.values();
}
if (sort != null && limit != 0) {
boolean withLimit = limit > 0 && !withTies;
if (offset > 0 || withLimit) {
sort.sort(rows, offset, withLimit ? limit : rows.size());
} else {
sort.sort(rows);
}
}
}
applyOffsetAndLimit();
reset();
}
private void applyOffsetAndLimit() {
if (limitsWereApplied) {
return;
}
int offset = Math.max(this.offset, 0);
int limit = this.limit;
if (offset == 0 && limit < 0 && !fetchPercent || rowCount == 0) {
return;
}
if (fetchPercent) {
if (limit < 0 || limit > 100) {
throw DbException.getInvalidValueException("FETCH PERCENT", limit);
}
// Oracle rounds percent up, do the same for now
limit = (int) (((long) limit * rowCount + 99) / 100);
}
boolean clearAll = offset >= rowCount || limit == 0;
if (!clearAll) {
int remaining = rowCount - offset;
limit = limit < 0 ? remaining : Math.min(remaining, limit);
if (offset == 0 && remaining <= limit) {
return;
}
} else {
limit = 0;
}
distinctRows = null;
rowCount = limit;
if (external == null) {
if (clearAll) {
rows.clear();
return;
}
int to = offset + limit;
if (withTies && sort != null) {
Value[] expected = rows.get(to - 1);
while (to < rows.size() && sort.compare(expected, rows.get(to)) == 0) {
to++;
rowCount++;
}
}
if (offset != 0 || to != rows.size()) {
// avoid copying the whole array for each row
rows = new ArrayList<>(rows.subList(offset, to));
}
} else {
if (clearAll) {
external.close();
external = null;
return;
}
trimExternal(offset, limit);
}
}
private void trimExternal(int offset, int limit) {
ResultExternal temp = external;
external = null;
temp.reset();
while (--offset >= 0) {
temp.next();
}
Value[] row = null;
while (--limit >= 0) {
row = temp.next();
rows.add(row);
if (rows.size() > maxMemoryRows) {
addRowsToDisk();
}
}
if (withTies && sort != null && row != null) {
Value[] expected = row;
while ((row = temp.next()) != null && sort.compare(expected, row) == 0) {
rows.add(row);
rowCount++;
if (rows.size() > maxMemoryRows) {
addRowsToDisk();
}
}
}
if (external != null) {
addRowsToDisk();
}
temp.close();
}
@Override
public int getRowCount() {
return rowCount;
}
@Override
public void limitsWereApplied() {
this.limitsWereApplied = true;
}
@Override
public boolean hasNext() {
return !closed && rowId < rowCount - 1;
}
/**
* 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;
}
/**
* @param fetchPercent whether limit expression specifies percentage of rows
*/
public void setFetchPercent(boolean fetchPercent) {
this.fetchPercent = fetchPercent;
}
/**
* @param withTies whether tied rows should be included in result too
*/
public void setWithTies(boolean withTies) {
this.withTies = withTies;
}
@Override
public boolean needToClose() {
return external != null;
}
@Override
public void close() {
if (external != null) {
external.close();
external = null;
closed = true;
}
}
@Override
public String getAlias(int i) {
return expressions[i].getAlias();
}
@Override
public String getTableName(int i) {
return expressions[i].getTableName();
}
@Override
public String getSchemaName(int i) {
return expressions[i].getSchemaName();
}
@Override
public int getDisplaySize(int i) {
return expressions[i].getDisplaySize();
}
@Override
public String getColumnName(int i) {
return expressions[i].getColumnName();
}
@Override
public int getColumnType(int i) {
return expressions[i].getType();
}
@Override
public long getColumnPrecision(int i) {
return expressions[i].getPrecision();
}
@Override
public int getNullable(int i) {
return expressions[i].getNullable();
}
@Override
public boolean isAutoIncrement(int i) {
return expressions[i].isAutoIncrement();
}
@Override
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;
}
@Override
public String toString() {
return super.toString() + " columns: " + visibleColumnCount +
" rows: " + rowCount + " pos: " + rowId;
}
/**
* Check if this result set is closed.
*
* @return true if it is
*/
@Override
public boolean isClosed() {
return closed;
}
@Override
public int getFetchSize() {
return 0;
}
@Override
public void setFetchSize(int fetchSize) {
// ignore
}
}
...@@ -19,6 +19,7 @@ import org.h2.index.Index; ...@@ -19,6 +19,7 @@ import org.h2.index.Index;
import org.h2.index.IndexType; import org.h2.index.IndexType;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.result.LocalResult; import org.h2.result.LocalResult;
import org.h2.result.LocalResultFactory;
import org.h2.result.ResultInterface; import org.h2.result.ResultInterface;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.schema.Schema; import org.h2.schema.Schema;
...@@ -194,7 +195,7 @@ public class FunctionTable extends Table { ...@@ -194,7 +195,7 @@ public class FunctionTable extends Table {
cachedResult.reset(); cachedResult.reset();
return cachedResult; return cachedResult;
} }
LocalResult result = LocalResult.read(session, v.getResultSet(), 0); LocalResult result = LocalResultFactory.read(session, v.getResultSet(), 0);
if (function.isDeterministic()) { if (function.isDeterministic()) {
cachedResult = result; cachedResult = result;
cachedValue = v; cachedValue = v;
......
...@@ -193,6 +193,7 @@ import org.h2.test.unit.TestIntIntHashMap; ...@@ -193,6 +193,7 @@ import org.h2.test.unit.TestIntIntHashMap;
import org.h2.test.unit.TestIntPerfectHash; import org.h2.test.unit.TestIntPerfectHash;
import org.h2.test.unit.TestInterval; import org.h2.test.unit.TestInterval;
import org.h2.test.unit.TestJmx; import org.h2.test.unit.TestJmx;
import org.h2.test.unit.TestLocalResultFactory;
import org.h2.test.unit.TestLocale; import org.h2.test.unit.TestLocale;
import org.h2.test.unit.TestMathUtils; import org.h2.test.unit.TestMathUtils;
import org.h2.test.unit.TestMemoryUnmapper; import org.h2.test.unit.TestMemoryUnmapper;
...@@ -980,6 +981,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -980,6 +981,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
addTest(new TestTraceSystem()); addTest(new TestTraceSystem());
addTest(new TestUtils()); addTest(new TestUtils());
addTest(new TestValueHashMap()); addTest(new TestValueHashMap());
addTest(new TestLocalResultFactory());
runAddedTests(); runAddedTests();
......
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.unit;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.concurrent.atomic.AtomicInteger;
import org.h2.engine.Session;
import org.h2.expression.Expression;
import org.h2.result.LocalResult;
import org.h2.result.LocalResultFactory;
import org.h2.test.TestBase;
/**
* Test {@link LocalResultFactory} setting.
*/
public class TestLocalResultFactory extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String[] a) throws Exception {
TestBase.createCaller().init().test();
}
@Override
public void test() throws Exception {
try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:localResultFactory;LOCAL_RESULT_FACTORY=\""
+ MyTestLocalResultFactory.class.getName() + '"')) {
Statement stat = conn.createStatement();
stat.execute("create table t1(id int, name varchar)");
for (int i = 0; i < 1000; i++) {
stat.execute("insert into t1 values(" + i + ", 'name')");
}
assertEquals(MyTestLocalResultFactory.COUNTER.get(), 0);
stat.execute("select * from t1");
assertEquals(MyTestLocalResultFactory.COUNTER.get(), 1);
}
}
/**
* Test local result factory.
*/
public static class MyTestLocalResultFactory extends LocalResultFactory {
static final AtomicInteger COUNTER = new AtomicInteger();
@Override public LocalResult create(Session session, Expression[] expressions, int visibleColumnCount) {
COUNTER.incrementAndGet();
return LocalResultFactory.DEFAULT.create(session, expressions, visibleColumnCount);
}
@Override public LocalResult create() {
COUNTER.incrementAndGet();
return LocalResultFactory.DEFAULT.create();
}
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论