提交 b9dc4892 authored 作者: Sergi Vladykin's avatar Sergi Vladykin 提交者: GitHub

Lazy query execution support

上级 cb88fe35
......@@ -21,7 +21,6 @@ import org.h2.util.MathUtils;
* Represents a SQL statement. This object is only used on the server side.
*/
public abstract class Command implements CommandInterface {
/**
* The session.
*/
......@@ -44,7 +43,7 @@ public abstract class Command implements CommandInterface {
private final String sql;
private boolean canReuse;
private volatile boolean canReuse;
Command(Parser parser, String sql) {
this.session = parser.getSession();
......@@ -147,7 +146,8 @@ public abstract class Command implements CommandInterface {
}
}
private void stop() {
@Override
public void stop() {
session.endStatement();
session.setCurrentCommand(null);
if (!isTransactional()) {
......@@ -198,7 +198,9 @@ public abstract class Command implements CommandInterface {
while (true) {
database.checkPowerOff();
try {
return query(maxrows);
ResultInterface result = query(maxrows);
callStop = !result.isLazy();
return result;
} catch (DbException e) {
start = filterConcurrentUpdate(e, start);
} catch (OutOfMemoryError e) {
......
......@@ -111,7 +111,7 @@ public class CommandContainer extends Command {
start();
prepared.checkParameters();
ResultInterface result = prepared.query(maxrows);
prepared.trace(startTimeNanos, result.getRowCount());
prepared.trace(startTimeNanos, result.isLazy() ? 0 : result.getRowCount());
setProgress(DatabaseEventListener.STATE_STATEMENT_END);
return result;
}
......
......@@ -503,6 +503,11 @@ public interface CommandInterface {
*/
int executeUpdate();
/**
* Stop the command execution, release all locks and resources
*/
void stop();
/**
* Close the statement.
*/
......
......@@ -54,6 +54,12 @@ public class CommandRemote implements CommandInterface {
created = session.getLastReconnect();
}
@Override
public void stop() {
// Must never be called, because remote result is not lazy.
throw DbException.throwInternalError();
}
private void prepare(SessionRemote s, boolean createParams) {
id = s.getNextId();
for (int i = 0, count = 0; i < transferList.size(); i++) {
......
......@@ -224,6 +224,7 @@ public class Parser {
private ArrayList<Parameter> indexedParameterList;
private int orderInFrom;
private ArrayList<Parameter> suppliedParameterList;
private boolean hasRecursive;
public Parser(Session session) {
this.database = session.getDatabase();
......@@ -300,6 +301,9 @@ public class Parser {
}
p.setPrepareAlways(recompileAlways);
p.setParameterList(parameters);
if (hasRecursive && p.isQuery() && p instanceof Query) {
((Query) p).setNeverLazy(true);
}
return p;
}
......@@ -4833,6 +4837,7 @@ public class Parser {
}
private Query parseWith() {
hasRecursive = true;
readIf("RECURSIVE");
String tempViewName = readIdentifierWithSchema();
Schema schema = getSchema();
......
......@@ -38,6 +38,9 @@ public class Explain extends Prepared {
public void setCommand(Prepared command) {
this.command = command;
if (command instanceof Query) {
((Query) command).setNeverLazy(true);
}
}
public Prepared getCommand() {
......
......@@ -19,7 +19,7 @@ import org.h2.expression.ExpressionVisitor;
import org.h2.expression.Parameter;
import org.h2.expression.ValueExpression;
import org.h2.message.DbException;
import org.h2.result.LocalResult;
import org.h2.result.ResultInterface;
import org.h2.result.ResultTarget;
import org.h2.result.SortOrder;
import org.h2.table.ColumnResolver;
......@@ -60,10 +60,12 @@ public abstract class Query extends Prepared {
*/
protected boolean randomAccessResult;
protected boolean neverLazy;
private boolean noCache;
private int lastLimit;
private long lastEvaluated;
private LocalResult lastResult;
private ResultInterface lastResult;
private Value[] lastParameters;
private boolean cacheableChecked;
......@@ -71,6 +73,10 @@ public abstract class Query extends Prepared {
super(session);
}
public void setNeverLazy(boolean b) {
this.neverLazy = b;
}
/**
* Check if this is a UNION query.
*
......@@ -92,9 +98,24 @@ public abstract class Query extends Prepared {
* @param target the target to write results to
* @return the result
*/
protected abstract LocalResult queryWithoutCache(int limit,
protected abstract ResultInterface queryWithoutCache(int limit,
ResultTarget target);
protected final ResultInterface queryWithoutCache0(int limit,
ResultTarget target) {
boolean disableLazy = neverLazy && session.isLazyQueryExecution();
if (disableLazy) {
session.setLazyQueryExecution(false);
}
try {
return queryWithoutCache(limit, target);
} finally {
if (disableLazy) {
session.setLazyQueryExecution(true);
}
}
}
/**
* Initialize the query.
*/
......@@ -305,7 +326,7 @@ public abstract class Query extends Prepared {
}
@Override
public LocalResult query(int maxrows) {
public final ResultInterface query(int maxrows) {
return query(maxrows, null);
}
......@@ -316,10 +337,11 @@ public abstract class Query extends Prepared {
* @param target the target result (null will return the result)
* @return the result set (if the target is not set).
*/
LocalResult query(int limit, ResultTarget target) {
ResultInterface query(int limit, ResultTarget target) {
fireBeforeSelectTriggers();
if (noCache || !session.getDatabase().getOptimizeReuseResults()) {
return queryWithoutCache(limit, target);
if (noCache || !session.getDatabase().getOptimizeReuseResults() ||
session.isLazyQueryExecution()) {
return queryWithoutCache0(limit, target);
}
Value[] params = getParameterValues();
long now = session.getDatabase().getModificationDataId();
......@@ -338,7 +360,7 @@ public abstract class Query extends Prepared {
}
lastParameters = params;
closeLastResult();
LocalResult r = queryWithoutCache(limit, target);
ResultInterface r = queryWithoutCache0(limit, target);
lastResult = r;
this.lastEvaluated = now;
lastLimit = limit;
......@@ -565,5 +587,4 @@ public abstract class Query extends Prepared {
isEverything(visitor);
return visitor.getMaxDataModificationId();
}
}
......@@ -17,6 +17,7 @@ import org.h2.expression.ExpressionVisitor;
import org.h2.expression.Parameter;
import org.h2.expression.ValueExpression;
import org.h2.message.DbException;
import org.h2.result.LazyResult;
import org.h2.result.LocalResult;
import org.h2.result.ResultInterface;
import org.h2.result.ResultTarget;
......@@ -148,7 +149,7 @@ public class SelectUnion extends Query {
}
@Override
protected LocalResult queryWithoutCache(int maxRows, ResultTarget target) {
protected ResultInterface queryWithoutCache(int maxRows, ResultTarget target) {
if (maxRows != 0) {
// maxRows is set (maxRows 0 means no limit)
int l;
......@@ -177,6 +178,25 @@ public class SelectUnion extends Query {
}
}
int columnCount = left.getColumnCount();
if (session.isLazyQueryExecution() && unionType == UNION_ALL && !distinct &&
sort == null && !randomAccessResult && !isForUpdate &&
offsetExpr == null && isReadOnly()) {
int limit = -1;
if (limitExpr != null) {
Value v = limitExpr.getValue(session);
if (v != ValueNull.INSTANCE) {
limit = v.getInt();
}
}
// limit 0 means no rows
if (limit != 0) {
LazyResultUnion lazyResult = new LazyResultUnion(expressionArray, columnCount);
if (limit > 0) {
lazyResult.setLimit(limit);
}
return lazyResult;
}
}
LocalResult result = new LocalResult(session, expressionArray, columnCount);
if (sort != null) {
result.setSortOrder(sort);
......@@ -205,8 +225,8 @@ public class SelectUnion extends Query {
default:
DbException.throwInternalError("type=" + unionType);
}
LocalResult l = left.query(0);
LocalResult r = right.query(0);
ResultInterface l = left.query(0);
ResultInterface r = right.query(0);
l.reset();
r.reset();
switch (unionType) {
......@@ -435,10 +455,10 @@ public class SelectUnion extends Query {
}
@Override
public LocalResult query(int limit, ResultTarget target) {
public ResultInterface query(int limit, ResultTarget target) {
// union doesn't always know the parameter list of the left and right
// queries
return queryWithoutCache(limit, target);
return queryWithoutCache0(limit, target);
}
@Override
......@@ -473,4 +493,75 @@ public class SelectUnion extends Query {
return left.allowGlobalConditions() && right.allowGlobalConditions();
}
/**
* Lazy execution for this union.
*/
private final class LazyResultUnion extends LazyResult {
int columnCount;
ResultInterface l;
ResultInterface r;
boolean leftDone;
boolean rightDone;
LazyResultUnion(Expression[] expressions, int columnCount) {
super(expressions);
this.columnCount = columnCount;
}
@Override
public int getVisibleColumnCount() {
return columnCount;
}
@Override
protected Value[] fetchNextRow() {
if (rightDone) {
return null;
}
if (!leftDone) {
if (l == null) {
l = left.query(0);
l.reset();
}
if (l.next()) {
return l.currentRow();
}
leftDone = true;
}
if (r == null) {
r = right.query(0);
r.reset();
}
if (r.next()) {
return r.currentRow();
}
rightDone = true;
return null;
}
@Override
public void close() {
super.close();
if (l != null) {
l.close();
}
if (r != null) {
r.close();
}
}
@Override
public void reset() {
super.reset();
if (l != null) {
l.reset();
}
if (r != null) {
r.reset();
}
leftDone = false;
rightDone = false;
}
}
}
......@@ -510,11 +510,20 @@ public class Set extends Prepared {
int value = getIntValue();
if (value != 0 && value != 1) {
throw DbException.getInvalidValueException("FORCE_JOIN_ORDER",
getIntValue());
value);
}
session.setForceJoinOrder(value == 1);
break;
}
case SetTypes.LAZY_QUERY_EXECUTION: {
int value = getIntValue();
if (value != 0 && value != 1) {
throw DbException.getInvalidValueException("LAZY_QUERY_EXECUTION",
value);
}
session.setLazyQueryExecution(value == 1);
break;
}
default:
DbException.throwInternalError("type="+type);
}
......
......@@ -214,7 +214,7 @@ public class SetTypes {
public static final int RETENTION_TIME = 40;
/**
* The type of a SET QUERY_STATISTICS_ACTIVE statement.
* The type of a SET QUERY_STATISTICS statement.
*/
public static final int QUERY_STATISTICS = 41;
......@@ -238,6 +238,11 @@ public class SetTypes {
*/
public static final int FORCE_JOIN_ORDER = 45;
/**
* The type of SET LAZY_QUERY_EXECUTION statement.
*/
public static final int LAZY_QUERY_EXECUTION = 46;
private static final ArrayList<String> TYPES = New.arrayList();
private SetTypes() {
......@@ -292,6 +297,7 @@ public class SetTypes {
list.add(ROW_FACTORY, "ROW_FACTORY");
list.add(BATCH_JOINS, "BATCH_JOINS");
list.add(FORCE_JOIN_ORDER, "FORCE_JOIN_ORDER");
list.add(LAZY_QUERY_EXECUTION, "LAZY_QUERY_EXECUTION");
}
/**
......
......@@ -31,7 +31,7 @@ import org.h2.message.TraceSystem;
import org.h2.mvstore.db.MVTable;
import org.h2.mvstore.db.TransactionStore.Change;
import org.h2.mvstore.db.TransactionStore.Transaction;
import org.h2.result.LocalResult;
import org.h2.result.ResultInterface;
import org.h2.result.Row;
import org.h2.result.SortOrder;
import org.h2.schema.Schema;
......@@ -108,7 +108,7 @@ public class Session extends SessionWithState {
private long transactionStart;
private long currentCommandStart;
private HashMap<String, Value> variables;
private HashSet<LocalResult> temporaryResults;
private HashSet<ResultInterface> temporaryResults;
private int queryTimeout;
private boolean commitOrRollbackDisabled;
private Table waitForLock;
......@@ -116,7 +116,7 @@ public class Session extends SessionWithState {
private int modificationId;
private int objectId;
private final int queryCacheSize;
private SmallLRUCache<String, Command> queryCache;
private final SmallLRUCache<String, Command> queryCache;
private long modificationMetaID = -1;
private SubQueryInfo subQueryInfo;
private int parsingView;
......@@ -125,6 +125,7 @@ public class Session extends SessionWithState {
private HashMap<Object, ViewIndex> subQueryIndexCache;
private boolean joinBatchEnabled;
private boolean forceJoinOrder;
private boolean lazyQueryExecution;
/**
* Temporary LOBs from result sets. Those are kept for some time. The
......@@ -148,6 +149,9 @@ public class Session extends SessionWithState {
this.database = database;
this.queryTimeout = database.getSettings().maxQueryTimeout;
this.queryCacheSize = database.getSettings().queryCacheSize;
this.queryCache = queryCacheSize <= 0 ? null :
SmallLRUCache.<String, Command>newInstance(queryCacheSize);
this.modificationMetaID = database.getModificationMetaId();
this.undoLog = new UndoLog(this);
this.user = user;
this.id = id;
......@@ -158,6 +162,14 @@ public class Session extends SessionWithState {
this.currentSchemaName = Constants.SCHEMA_MAIN;
}
public void setLazyQueryExecution(boolean lazyQueryExecution) {
this.lazyQueryExecution = lazyQueryExecution;
}
public boolean isLazyQueryExecution() {
return lazyQueryExecution;
}
public void setForceJoinOrder(boolean forceJoinOrder) {
this.forceJoinOrder = forceJoinOrder;
}
......@@ -542,11 +554,8 @@ public class Session extends SessionWithState {
"session closed");
}
Command command;
if (queryCacheSize > 0) {
if (queryCache == null) {
queryCache = SmallLRUCache.newInstance(queryCacheSize);
modificationMetaID = database.getModificationMetaId();
} else {
if (queryCache != null) {
synchronized (queryCache) {
long newModificationMetaID = database.getModificationMetaId();
if (newModificationMetaID != modificationMetaID) {
queryCache.clear();
......@@ -567,8 +576,8 @@ public class Session extends SessionWithState {
subQueryIndexCache = null;
}
command.prepareJoinBatch();
if (queryCache != null) {
if (command.isCacheable()) {
if (queryCache != null && command.isCacheable()) {
synchronized (queryCache) {
queryCache.put(sql, command);
}
}
......@@ -1469,7 +1478,7 @@ public class Session extends SessionWithState {
*
* @param result the temporary result set
*/
public void addTemporaryResult(LocalResult result) {
public void addTemporaryResult(ResultInterface result) {
if (!result.needToClose()) {
return;
}
......@@ -1484,7 +1493,7 @@ public class Session extends SessionWithState {
private void closeTemporaryResults() {
if (temporaryResults != null) {
for (LocalResult result : temporaryResults) {
for (ResultInterface result : temporaryResults) {
result.close();
}
temporaryResults = null;
......
......@@ -7,7 +7,7 @@ package org.h2.expression;
import org.h2.command.dml.Query;
import org.h2.engine.Session;
import org.h2.result.LocalResult;
import org.h2.result.ResultInterface;
import org.h2.table.ColumnResolver;
import org.h2.table.TableFilter;
import org.h2.util.StringUtils;
......@@ -28,9 +28,9 @@ public class ConditionExists extends Condition {
@Override
public Value getValue(Session session) {
query.setSession(session);
LocalResult result = query.query(1);
ResultInterface result = query.query(1);
session.addTemporaryResult(result);
boolean r = result.getRowCount() > 0;
boolean r = result.hasNext();
return ValueBoolean.get(r);
}
......
......@@ -11,7 +11,7 @@ import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.index.IndexCondition;
import org.h2.message.DbException;
import org.h2.result.LocalResult;
import org.h2.result.ResultInterface;
import org.h2.table.ColumnResolver;
import org.h2.table.TableFilter;
import org.h2.util.StringUtils;
......@@ -38,6 +38,7 @@ public class ConditionInSelect extends Condition {
this.query = query;
this.all = all;
this.compareType = compareType;
query.setNeverLazy(true);
}
@Override
......@@ -46,9 +47,9 @@ public class ConditionInSelect extends Condition {
if (!query.hasOrder()) {
query.setDistinct(true);
}
LocalResult rows = query.query(0);
ResultInterface rows = query.query(0);
Value l = left.getValue(session);
if (rows.getRowCount() == 0) {
if (!rows.hasNext()) {
return ValueBoolean.get(all);
} else if (l == ValueNull.INSTANCE) {
return l;
......@@ -74,7 +75,7 @@ public class ConditionInSelect extends Condition {
return ValueBoolean.get(false);
}
private Value getValueSlow(LocalResult rows, Value l) {
private Value getValueSlow(ResultInterface rows, Value l) {
// this only returns the correct result if the result has at least one
// row, and if l is not null
boolean hasNull = false;
......
......@@ -34,21 +34,19 @@ public class Subquery extends Expression {
public Value getValue(Session session) {
query.setSession(session);
try (ResultInterface result = query.query(2)) {
int rowcount = result.getRowCount();
if (rowcount > 1) {
throw DbException.get(ErrorCode.SCALAR_SUBQUERY_CONTAINS_MORE_THAN_ONE_ROW);
}
Value v;
if (rowcount <= 0) {
if (!result.next()) {
v = ValueNull.INSTANCE;
} else {
result.next();
Value[] values = result.currentRow();
if (result.getVisibleColumnCount() == 1) {
v = values[0];
} else {
v = ValueArray.get(values);
}
if (result.hasNext()) {
throw DbException.get(ErrorCode.SCALAR_SUBQUERY_CONTAINS_MORE_THAN_ONE_ROW);
}
}
return v;
}
......
......@@ -12,7 +12,6 @@ import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.result.LocalResult;
import org.h2.result.ResultInterface;
import org.h2.table.Column;
import org.h2.tools.SimpleResultSet;
import org.h2.util.MathUtils;
......@@ -131,7 +130,7 @@ public class TableFunction extends Function {
return vr;
}
private static SimpleResultSet getSimpleResultSet(ResultInterface rs,
private static SimpleResultSet getSimpleResultSet(LocalResult rs,
int maxrows) {
int columnCount = rs.getVisibleColumnCount();
SimpleResultSet simple = new SimpleResultSet();
......
......@@ -6,7 +6,7 @@
package org.h2.index;
import org.h2.message.DbException;
import org.h2.result.LocalResult;
import org.h2.result.ResultInterface;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.table.Table;
......@@ -20,11 +20,11 @@ public class ViewCursor implements Cursor {
private final Table table;
private final ViewIndex index;
private final LocalResult result;
private final ResultInterface result;
private final SearchRow first, last;
private Row current;
public ViewCursor(ViewIndex index, LocalResult result, SearchRow first,
public ViewCursor(ViewIndex index, ResultInterface result, SearchRow first,
SearchRow last) {
this.table = index.getTable();
this.index = index;
......
......@@ -20,6 +20,7 @@ import org.h2.expression.Comparison;
import org.h2.expression.Parameter;
import org.h2.message.DbException;
import org.h2.result.LocalResult;
import org.h2.result.ResultInterface;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
......@@ -181,7 +182,7 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
private Cursor findRecursive(SearchRow first, SearchRow last) {
assert recursive;
LocalResult recResult = view.getRecursiveResult();
ResultInterface recResult = view.getRecursiveResult();
if (recResult != null) {
recResult.reset();
return new ViewCursor(this, recResult, first, last);
......@@ -191,6 +192,7 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
parser.setRightsChecked(true);
parser.setSuppliedParameterList(originalParameters);
query = (Query) parser.prepare(querySQL);
query.setNeverLazy(true);
}
if (!query.isUnion()) {
throw DbException.get(ErrorCode.SYNTAX_ERROR_2,
......@@ -204,7 +206,7 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
Query left = union.getLeft();
// to ensure the last result is not closed
left.disableCache();
LocalResult r = left.query(0);
ResultInterface r = left.query(0);
LocalResult result = union.getEmptyResult();
// ensure it is not written to disk,
// because it is not closed normally
......@@ -219,7 +221,7 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
right.disableCache();
while (true) {
r = right.query(0);
if (r.getRowCount() == 0) {
if (!r.hasNext()) {
break;
}
while (r.next()) {
......@@ -286,7 +288,7 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
return findRecursive(first, last);
}
setupQueryParameters(session, first, last, intersection);
LocalResult result = query.query(0);
ResultInterface result = query.query(0);
return new ViewCursor(this, result, first, last);
}
......
......@@ -1548,7 +1548,7 @@ public class JdbcConnection extends TraceObject implements Connection,
"SELECT SCOPE_IDENTITY() " +
"WHERE SCOPE_IDENTITY() IS NOT NULL", getGeneratedKeys);
ResultInterface result = getGeneratedKeys.executeQuery(0, false);
ResultSet rs = new JdbcResultSet(this, stat, result, id, false, true, false);
ResultSet rs = new JdbcResultSet(this, stat, getGeneratedKeys, result, id, false, true, false);
return rs;
}
......
......@@ -102,16 +102,18 @@ public class JdbcPreparedStatement extends JdbcStatement implements
synchronized (session) {
checkClosed();
closeOldResultSet();
ResultInterface result;
ResultInterface result = null;
boolean scrollable = resultSetType != ResultSet.TYPE_FORWARD_ONLY;
boolean updatable = resultSetConcurrency == ResultSet.CONCUR_UPDATABLE;
try {
setExecutingStatement(command);
result = command.executeQuery(maxRows, scrollable);
} finally {
setExecutingStatement(null);
if (result == null || !result.isLazy()) {
setExecutingStatement(null);
}
}
resultSet = new JdbcResultSet(conn, this, result, id,
resultSet = new JdbcResultSet(conn, this, command, result, id,
closedByResultSet, scrollable, updatable, cachedColumnLabelMap);
}
return resultSet;
......@@ -186,6 +188,7 @@ public class JdbcPreparedStatement extends JdbcStatement implements
boolean returnsResultSet;
synchronized (conn.getSession()) {
closeOldResultSet();
boolean lazy = false;
try {
setExecutingStatement(command);
if (command.isQuery()) {
......@@ -193,15 +196,18 @@ public class JdbcPreparedStatement extends JdbcStatement implements
boolean scrollable = resultSetType != ResultSet.TYPE_FORWARD_ONLY;
boolean updatable = resultSetConcurrency == ResultSet.CONCUR_UPDATABLE;
ResultInterface result = command.executeQuery(maxRows, scrollable);
resultSet = new JdbcResultSet(conn, this, result,
lazy = result.isLazy();
resultSet = new JdbcResultSet(conn, this, command, result,
id, closedByResultSet, scrollable,
updatable);
updatable, cachedColumnLabelMap);
} else {
returnsResultSet = false;
updateCount = command.executeUpdate();
}
} finally {
setExecutingStatement(null);
if (!lazy) {
setExecutingStatement(null);
}
}
}
return returnsResultSet;
......
......@@ -31,6 +31,7 @@ import java.util.UUID;
import org.h2.api.ErrorCode;
import org.h2.api.TimestampWithTimeZone;
import org.h2.command.CommandInterface;
import org.h2.engine.SysProperties;
import org.h2.message.DbException;
import org.h2.message.TraceObject;
......@@ -91,13 +92,15 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
private HashMap<String, Integer> columnLabelMap;
private HashMap<Integer, Value[]> patchedRows;
private JdbcPreparedStatement preparedStatement;
private CommandInterface command;
JdbcResultSet(JdbcConnection conn, JdbcStatement stat,
JdbcResultSet(JdbcConnection conn, JdbcStatement stat, CommandInterface command,
ResultInterface result, int id, boolean closeStatement,
boolean scrollable, boolean updatable) {
setTrace(conn.getSession().getTrace(), TraceObject.RESULT_SET, id);
this.conn = conn;
this.stat = stat;
this.command = command;
this.result = result;
columnCount = result.getVisibleColumnCount();
this.closeStatement = closeStatement;
......@@ -106,10 +109,10 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
}
JdbcResultSet(JdbcConnection conn, JdbcPreparedStatement preparedStatement,
ResultInterface result, int id, boolean closeStatement,
CommandInterface command, ResultInterface result, int id, boolean closeStatement,
boolean scrollable, boolean updatable,
HashMap<String, Integer> columnLabelMap) {
this(conn, preparedStatement, result, id, closeStatement, scrollable,
this(conn, preparedStatement, command, result, id, closeStatement, scrollable,
updatable);
this.columnLabelMap = columnLabelMap;
this.preparedStatement = preparedStatement;
......@@ -208,6 +211,9 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
void closeInternal() throws SQLException {
if (result != null) {
try {
if (result.isLazy()) {
stat.onLazyResultSetClose(command, preparedStatement == null);
}
result.close();
if (closeStatement && stat != null) {
stat.close();
......@@ -2525,10 +2531,10 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
try {
debugCodeCall("getRow");
checkClosed();
int rowId = result.getRowId();
if (rowId >= result.getRowCount()) {
if (result.isAfterLast()) {
return 0;
}
int rowId = result.getRowId();
return rowId + 1;
} catch (Exception e) {
throw logAndConvert(e);
......@@ -2674,9 +2680,7 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
try {
debugCodeCall("isBeforeFirst");
checkClosed();
int row = result.getRowId();
int count = result.getRowCount();
return count > 0 && row < 0;
return result.getRowId() < 0 && result.hasNext();
} catch (Exception e) {
throw logAndConvert(e);
}
......@@ -2695,9 +2699,7 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
try {
debugCodeCall("isAfterLast");
checkClosed();
int row = result.getRowId();
int count = result.getRowCount();
return count > 0 && row >= count;
return result.getRowId() > 0 && result.isAfterLast();
} catch (Exception e) {
throw logAndConvert(e);
}
......@@ -2715,8 +2717,7 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
try {
debugCodeCall("isFirst");
checkClosed();
int row = result.getRowId();
return row == 0 && row < result.getRowCount();
return result.getRowId() == 0 && !result.isAfterLast();
} catch (Exception e) {
throw logAndConvert(e);
}
......@@ -2734,8 +2735,8 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
try {
debugCodeCall("isLast");
checkClosed();
int row = result.getRowId();
return row >= 0 && row == result.getRowCount() - 1;
int rowId = result.getRowId();
return rowId >= 0 && !result.isAfterLast() && !result.hasNext();
} catch (Exception e) {
throw logAndConvert(e);
}
......@@ -2791,10 +2792,9 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
try {
debugCodeCall("first");
checkClosed();
if (result.getRowId() < 0) {
return nextRow();
if (result.getRowId() >= 0) {
resetResult();
}
resetResult();
return nextRow();
} catch (Exception e) {
throw logAndConvert(e);
......@@ -2812,7 +2812,13 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
try {
debugCodeCall("last");
checkClosed();
return absolute(-1);
if (result.isAfterLast()) {
resetResult();
}
while (result.hasNext()) {
nextRow();
}
return isOnValidRow();
} catch (Exception e) {
throw logAndConvert(e);
}
......@@ -2836,17 +2842,16 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
checkClosed();
if (rowNumber < 0) {
rowNumber = result.getRowCount() + rowNumber + 1;
} else if (rowNumber > result.getRowCount() + 1) {
rowNumber = result.getRowCount() + 1;
}
if (rowNumber <= result.getRowId()) {
if (--rowNumber < result.getRowId()) {
resetResult();
}
while (result.getRowId() + 1 < rowNumber) {
nextRow();
while (result.getRowId() < rowNumber) {
if (!nextRow()) {
return false;
}
}
int row = result.getRowId();
return row >= 0 && row < result.getRowCount();
return isOnValidRow();
} catch (Exception e) {
throw logAndConvert(e);
}
......@@ -2867,13 +2872,16 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
try {
debugCodeCall("relative", rowCount);
checkClosed();
int row = result.getRowId() + 1 + rowCount;
if (row < 0) {
row = 0;
} else if (row > result.getRowCount()) {
row = result.getRowCount() + 1;
if (rowCount < 0) {
rowCount = result.getRowId() + rowCount + 1;
resetResult();
}
return absolute(row);
for (int i = 0; i < rowCount; i++) {
if (!nextRow()) {
return false;
}
}
return isOnValidRow();
} catch (Exception e) {
throw logAndConvert(e);
}
......@@ -3207,8 +3215,12 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
}
}
private boolean isOnValidRow() {
return result.getRowId() >= 0 && !result.isAfterLast();
}
private void checkOnValidRow() {
if (result.getRowId() < 0 || result.getRowId() >= result.getRowCount()) {
if (!isOnValidRow()) {
throw DbException.get(ErrorCode.NO_DATA_AVAILABLE);
}
}
......@@ -3254,6 +3266,9 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
}
private boolean nextRow() {
if (result.isLazy() && stat.isCancelled()) {
throw DbException.get(ErrorCode.STATEMENT_WAS_CANCELED);
}
boolean next = result.next();
if (!next && !scrollable) {
result.close();
......
......@@ -12,6 +12,7 @@ import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.ArrayList;
import org.h2.api.ErrorCode;
import org.h2.command.Command;
import org.h2.command.CommandInterface;
import org.h2.engine.SessionInterface;
import org.h2.engine.SysProperties;
......@@ -38,7 +39,7 @@ public class JdbcStatement extends TraceObject implements Statement, JdbcStateme
private int lastExecutedCommandType;
private ArrayList<String> batchCommands;
private boolean escapeProcessing = true;
private boolean cancelled;
private volatile boolean cancelled;
JdbcStatement(JdbcConnection conn, int id, int resultSetType,
int resultSetConcurrency, boolean closeWithResultSet) {
......@@ -71,17 +72,21 @@ public class JdbcStatement extends TraceObject implements Statement, JdbcStateme
closeOldResultSet();
sql = JdbcConnection.translateSQL(sql, escapeProcessing);
CommandInterface command = conn.prepareCommand(sql, fetchSize);
ResultInterface result;
ResultInterface result = null;
boolean scrollable = resultSetType != ResultSet.TYPE_FORWARD_ONLY;
boolean updatable = resultSetConcurrency == ResultSet.CONCUR_UPDATABLE;
setExecutingStatement(command);
try {
result = command.executeQuery(maxRows, scrollable);
} finally {
setExecutingStatement(null);
if (result == null || !result.isLazy()) {
setExecutingStatement(null);
}
}
command.close();
resultSet = new JdbcResultSet(conn, this, result, id,
if (!result.isLazy()) {
command.close();
}
resultSet = new JdbcResultSet(conn, this, command, result, id,
closedByResultSet, scrollable, updatable);
}
return resultSet;
......@@ -168,6 +173,7 @@ public class JdbcStatement extends TraceObject implements Statement, JdbcStateme
closeOldResultSet();
sql = JdbcConnection.translateSQL(sql, escapeProcessing);
CommandInterface command = conn.prepareCommand(sql, fetchSize);
boolean lazy = false;
boolean returnsResultSet;
synchronized (session) {
setExecutingStatement(command);
......@@ -177,17 +183,22 @@ public class JdbcStatement extends TraceObject implements Statement, JdbcStateme
boolean scrollable = resultSetType != ResultSet.TYPE_FORWARD_ONLY;
boolean updatable = resultSetConcurrency == ResultSet.CONCUR_UPDATABLE;
ResultInterface result = command.executeQuery(maxRows, scrollable);
resultSet = new JdbcResultSet(conn, this, result, id,
lazy = result.isLazy();
resultSet = new JdbcResultSet(conn, this, command, result, id,
closedByResultSet, scrollable, updatable);
} else {
returnsResultSet = false;
updateCount = command.executeUpdate();
}
} finally {
setExecutingStatement(null);
if (!lazy) {
setExecutingStatement(null);
}
}
}
command.close();
if (!lazy) {
command.close();
}
return returnsResultSet;
} finally {
afterWriting();
......@@ -549,7 +560,7 @@ public class JdbcStatement extends TraceObject implements Statement, JdbcStateme
*
* @return true if yes
*/
public boolean wasCancelled() {
public boolean isCancelled() {
return cancelled;
}
......@@ -1031,6 +1042,14 @@ public class JdbcStatement extends TraceObject implements Statement, JdbcStateme
executingCommand = c;
}
void onLazyResultSetClose(CommandInterface command, boolean closeCommand) {
setExecutingStatement(null);
command.stop();
if (closeCommand) {
command.close();
}
}
/**
* INTERNAL.
* Get the command type of the last executed command.
......
......@@ -7,7 +7,7 @@ SELECT [ TOP term ] [ DISTINCT | ALL ] selectExpression [,...]
FROM tableExpression [,...] [ WHERE expression ]
[ GROUP BY expression [,...] ] [ HAVING expression ]
[ { UNION [ ALL ] | MINUS | EXCEPT | INTERSECT } select ] [ ORDER BY order [,...] ]
[ [ LIMIT expression ] [ OFFSET expression ] [ SAMPLE_SIZE rowCountInt ] ]
[ { LIMIT expression [ OFFSET expression ] [ SAMPLE_SIZE rowCountInt ] } | { [ OFFSET expression { ROW | ROWS } ] [ { FETCH { FIRST | NEXT } expression { ROW | ROWS } ONLY } ] } ]
[ FOR UPDATE ]
","
Selects data from a table or multiple tables."
......
/*
* Copyright 2004-2014 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 org.h2.engine.Session;
import org.h2.expression.Expression;
import org.h2.message.DbException;
import org.h2.value.Value;
/**
* Lazy execution support for queries.
*
* @author Sergi Vladykin
*/
public abstract class LazyResult implements ResultInterface {
private Expression[] expressions;
private int rowId = -1;
private Value[] currentRow;
private Value[] nextRow;
private boolean closed;
private boolean afterLast;
private int limit;
public LazyResult(Expression[] expressions) {
this.expressions = expressions;
}
public void setLimit(int limit) {
this.limit = limit;
}
@Override
public boolean isLazy() {
return true;
}
@Override
public void reset() {
if (closed) {
throw DbException.throwInternalError();
}
rowId = -1;
afterLast = false;
currentRow = null;
nextRow = null;
}
@Override
public Value[] currentRow() {
return currentRow;
}
@Override
public boolean next() {
if (hasNext()) {
rowId++;
currentRow = nextRow;
nextRow = null;
return true;
}
if (!afterLast) {
rowId++;
currentRow = null;
afterLast = true;
}
return false;
}
@Override
public boolean hasNext() {
if (closed || afterLast) {
return false;
}
if (nextRow == null && (limit <= 0 || rowId + 1 < limit)) {
nextRow = fetchNextRow();
}
return nextRow != null;
}
/**
* Fetch next row or null if none available.
*
* @return next row or null
*/
protected abstract Value[] fetchNextRow();
@Override
public boolean isAfterLast() {
return afterLast;
}
@Override
public int getRowId() {
return rowId;
}
@Override
public int getRowCount() {
throw DbException.getUnsupportedException("Row count is unknown for lazy result.");
}
@Override
public boolean needToClose() {
return true;
}
@Override
public boolean isClosed() {
return closed;
}
@Override
public void close() {
closed = true;
}
@Override
public String getAlias(int i) {
return expressions[i].getAlias();
}
@Override
public String getSchemaName(int i) {
return expressions[i].getSchemaName();
}
@Override
public String getTableName(int i) {
return expressions[i].getTableName();
}
@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 getColumnScale(int i) {
return expressions[i].getScale();
}
@Override
public int getDisplaySize(int i) {
return expressions[i].getDisplaySize();
}
@Override
public boolean isAutoIncrement(int i) {
return expressions[i].isAutoIncrement();
}
@Override
public int getNullable(int i) {
return expressions[i].getNullable();
}
@Override
public void setFetchSize(int fetchSize) {
// ignore
}
@Override
public int getFetchSize() {
// We always fetch rows one by one.
return 1;
}
@Override
public ResultInterface createShallowCopy(Session targetSession) {
// Copying is impossible with lazy result.
return null;
}
@Override
public boolean containsDistinct(Value[] values) {
// We have to make sure that we do not allow lazy
// evaluation when this call is needed:
// WHERE x IN (SELECT ...).
throw DbException.throwInternalError();
}
}
......@@ -77,6 +77,11 @@ public class LocalResult implements ResultInterface, ResultTarget {
this.expressions = expressions;
}
@Override
public boolean isLazy() {
return false;
}
public void setMaxMemoryRows(int maxValue) {
this.maxMemoryRows = maxValue;
}
......@@ -117,6 +122,7 @@ public class LocalResult implements ResultInterface, ResultTarget {
* @param targetSession the session of the copy
* @return the copy if possible, or null if copying is not possible
*/
@Override
public LocalResult createShallowCopy(Session targetSession) {
if (external == null && (rows == null || rows.size() < rowCount)) {
return null;
......@@ -199,6 +205,7 @@ public class LocalResult implements ResultInterface, ResultTarget {
* @param values the row
* @return true if the row exists
*/
@Override
public boolean containsDistinct(Value[] values) {
if (external != null) {
return external.contains(values);
......@@ -217,6 +224,7 @@ public class LocalResult implements ResultInterface, ResultTarget {
@Override
public void reset() {
rowId = -1;
currentRow = null;
if (external != null) {
external.reset();
if (diskOffset > 0) {
......@@ -254,6 +262,11 @@ public class LocalResult implements ResultInterface, ResultTarget {
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];
......@@ -375,6 +388,11 @@ public class LocalResult implements ResultInterface, ResultTarget {
return rowCount;
}
@Override
public boolean hasNext() {
return !closed && rowId < rowCount - 1;
}
/**
* Set the number of rows that this result will return at the maximum.
*
......@@ -508,6 +526,7 @@ public class LocalResult implements ResultInterface, ResultTarget {
*
* @return true if it is
*/
@Override
public boolean isClosed() {
return closed;
}
......
......@@ -5,6 +5,7 @@
*/
package org.h2.result;
import org.h2.engine.Session;
import org.h2.value.Value;
/**
......@@ -41,6 +42,13 @@ public interface ResultInterface extends AutoCloseable {
*/
int getRowId();
/**
* Check if the current position is after last row.
*
* @return true if after last
*/
boolean isAfterLast();
/**
* Get the number of visible columns.
* More columns may exist internally for sorting or grouping.
......@@ -56,6 +64,13 @@ public interface ResultInterface extends AutoCloseable {
*/
int getRowCount();
/**
* Check if this result has more rows to fetch.
*
* @return true if it has
*/
boolean hasNext();
/**
* Check if this result set should be closed, for example because it is
* buffered using a temporary file.
......@@ -164,4 +179,34 @@ public interface ResultInterface extends AutoCloseable {
*/
int getFetchSize();
/**
* Check if this a lazy execution result.
*
* @return true if it is a lazy result
*/
boolean isLazy();
/**
* Check if this result set is closed.
*
* @return true if it is
*/
boolean isClosed();
/**
* 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
*/
ResultInterface createShallowCopy(Session targetSession);
/**
* Check if this result set contains the given row.
*
* @param values the row
* @return true if the row exists
*/
boolean containsDistinct(Value[] values);
}
......@@ -7,6 +7,7 @@ package org.h2.result;
import java.io.IOException;
import java.util.ArrayList;
import org.h2.engine.Session;
import org.h2.engine.SessionRemote;
import org.h2.engine.SysProperties;
import org.h2.message.DbException;
......@@ -50,6 +51,11 @@ public class ResultRemote implements ResultInterface {
fetchRows(false);
}
@Override
public boolean isLazy() {
return false;
}
@Override
public String getAlias(int i) {
return columns[i].alias;
......@@ -145,6 +151,11 @@ public class ResultRemote implements ResultInterface {
return rowId;
}
@Override
public boolean isAfterLast() {
return rowId >= rowCount;
}
@Override
public int getVisibleColumnCount() {
return columns.length;
......@@ -155,6 +166,11 @@ public class ResultRemote implements ResultInterface {
return rowCount;
}
@Override
public boolean hasNext() {
return rowId < rowCount - 1;
}
private void sendClose() {
if (session == null) {
return;
......@@ -254,4 +270,20 @@ public class ResultRemote implements ResultInterface {
return true;
}
@Override
public ResultInterface createShallowCopy(Session targetSession) {
// The operation is not supported on remote result.
return null;
}
@Override
public boolean isClosed() {
return result == null;
}
@Override
public boolean containsDistinct(Value[] values) {
// We should never do this on remote result.
throw DbException.throwInternalError();
}
}
......@@ -376,7 +376,7 @@ public class PgServerThread implements Runnable {
sendCommandComplete(prep, prep.getUpdateCount());
}
} catch (Exception e) {
if (prep.wasCancelled()) {
if (prep.isCancelled()) {
sendCancelQueryResponse();
} else {
sendErrorResponse(e);
......@@ -423,7 +423,7 @@ public class PgServerThread implements Runnable {
sendCommandComplete(stat, stat.getUpdateCount());
}
} catch (SQLException e) {
if (stat != null && stat.wasCancelled()) {
if (stat != null && stat.isCancelled()) {
sendCancelQueryResponse();
} else {
sendErrorResponse(e);
......
......@@ -21,7 +21,7 @@ import org.h2.index.IndexLookupBatch;
import org.h2.index.ViewCursor;
import org.h2.index.ViewIndex;
import org.h2.message.DbException;
import org.h2.result.LocalResult;
import org.h2.result.ResultInterface;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.util.DoneFuture;
......@@ -738,6 +738,7 @@ public final class JoinBatch {
/**
* Simple singleton list.
* @param <E> Element type.
*/
static final class SingletonList<E> extends AbstractList<E> {
private E element;
......@@ -763,6 +764,7 @@ public final class JoinBatch {
/**
* Base class for SELECT and SELECT UNION view index lookup batches.
* @param <R> Runner type.
*/
private abstract static class ViewIndexLookupBatchBase<R extends QueryRunnerBase>
implements IndexLookupBatch {
......@@ -850,14 +852,15 @@ public final class JoinBatch {
}
/**
* Lazy query runner base.
* Lazy query runner base for subqueries and views.
*/
private abstract static class QueryRunnerBase extends LazyFuture<Cursor> {
protected final ViewIndex viewIndex;
protected SearchRow first;
protected SearchRow last;
private boolean isLazyResult;
public QueryRunnerBase(ViewIndex viewIndex) {
QueryRunnerBase(ViewIndex viewIndex) {
this.viewIndex = viewIndex;
}
......@@ -867,6 +870,9 @@ public final class JoinBatch {
@Override
public final boolean reset() {
if (isLazyResult) {
resetViewTopFutureCursorAfterQuery();
}
if (super.reset()) {
return true;
}
......@@ -875,11 +881,14 @@ public final class JoinBatch {
return false;
}
protected final ViewCursor newCursor(LocalResult localResult) {
protected final ViewCursor newCursor(ResultInterface localResult) {
isLazyResult = localResult.isLazy();
ViewCursor cursor = new ViewCursor(viewIndex, localResult, first, last);
clear();
return cursor;
}
protected abstract void resetViewTopFutureCursorAfterQuery();
}
/**
......@@ -924,12 +933,12 @@ public final class JoinBatch {
}
/**
* Query runner.
* Query runner for SELECT.
*/
private final class QueryRunner extends QueryRunnerBase {
Future<Cursor> topFutureCursor;
public QueryRunner(ViewIndex viewIndex) {
QueryRunner(ViewIndex viewIndex) {
super(viewIndex);
}
......@@ -948,14 +957,21 @@ public final class JoinBatch {
}
viewIndex.setupQueryParameters(viewIndex.getSession(), first, last, null);
JoinBatch.this.viewTopFutureCursor = topFutureCursor;
LocalResult localResult;
ResultInterface localResult = null;
try {
localResult = viewIndex.getQuery().query(0);
} finally {
JoinBatch.this.viewTopFutureCursor = null;
if (localResult == null || !localResult.isLazy()) {
resetViewTopFutureCursorAfterQuery();
}
}
return newCursor(localResult);
}
@Override
protected void resetViewTopFutureCursorAfterQuery() {
JoinBatch.this.viewTopFutureCursor = null;
}
}
/**
......@@ -1056,7 +1072,7 @@ public final class JoinBatch {
private ViewIndexLookupBatchUnion batchUnion;
@SuppressWarnings("unchecked")
public QueryRunnerUnion(ViewIndexLookupBatchUnion batchUnion) {
QueryRunnerUnion(ViewIndexLookupBatchUnion batchUnion) {
super(batchUnion.viewIndex);
this.batchUnion = batchUnion;
topFutureCursors = new Future[batchUnion.filters.size()];
......@@ -1078,16 +1094,27 @@ public final class JoinBatch {
assert topFutureCursors[i] != null;
joinBatches.get(i).viewTopFutureCursor = topFutureCursors[i];
}
LocalResult localResult;
ResultInterface localResult = null;
try {
localResult = viewIndex.getQuery().query(0);
} finally {
for (int i = 0, size = joinBatches.size(); i < size; i++) {
joinBatches.get(i).viewTopFutureCursor = null;
if (localResult == null || !localResult.isLazy()) {
resetViewTopFutureCursorAfterQuery();
}
}
return newCursor(localResult);
}
@Override
protected void resetViewTopFutureCursorAfterQuery() {
ArrayList<JoinBatch> joinBatches = batchUnion.joinBatches;
if (joinBatches == null) {
return;
}
for (int i = 0, size = joinBatches.size(); i < size; i++) {
joinBatches.get(i).viewTopFutureCursor = null;
}
}
}
}
......@@ -26,7 +26,7 @@ import org.h2.index.Index;
import org.h2.index.IndexType;
import org.h2.index.ViewIndex;
import org.h2.message.DbException;
import org.h2.result.LocalResult;
import org.h2.result.ResultInterface;
import org.h2.result.Row;
import org.h2.result.SortOrder;
import org.h2.schema.Schema;
......@@ -55,7 +55,7 @@ public class TableView extends Table {
private long maxDataModificationId;
private User owner;
private Query topQuery;
private LocalResult recursiveResult;
private ResultInterface recursiveResult;
private boolean tableExpression;
public TableView(Schema schema, int id, String name, String querySQL,
......@@ -591,14 +591,14 @@ public class TableView extends Table {
return viewQuery.isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR);
}
public void setRecursiveResult(LocalResult value) {
public void setRecursiveResult(ResultInterface value) {
if (recursiveResult != null) {
recursiveResult.close();
}
this.recursiveResult = value;
}
public LocalResult getRecursiveResult() {
public ResultInterface getRecursiveResult() {
return recursiveResult;
}
......@@ -630,7 +630,7 @@ public class TableView extends Table {
private final int[] masks;
private final TableView view;
public CacheKey(int[] masks, TableView view) {
CacheKey(int[] masks, TableView view) {
this.masks = masks;
this.view = view;
}
......
......@@ -315,6 +315,11 @@ java org.h2.test.TestAll timer
*/
public boolean multiThreaded;
/**
* If lazy queries should be used.
*/
public boolean lazy;
/**
* The cipher to use (null for unencrypted).
*/
......@@ -603,6 +608,13 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
test();
testUnit();
// lazy
lazy = true;
memory = true;
multiThreaded = true;
test();
lazy = false;
// but sometimes race conditions need bigger windows
memory = false;
multiThreaded = true;
......@@ -1063,6 +1075,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
public String toString() {
StringBuilder buff = new StringBuilder();
appendIf(buff, fast, "fast");
appendIf(buff, lazy, "lazy");
appendIf(buff, mvStore, "mvStore");
appendIf(buff, big, "big");
appendIf(buff, networked, "net");
......
......@@ -22,6 +22,7 @@ import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
......@@ -318,6 +319,9 @@ public abstract class TestBase {
if (config.multiThreaded) {
url = addOption(url, "MULTI_THREADED", "TRUE");
}
if (config.lazy) {
url = addOption(url, "LAZY_QUERY_EXECUTION", "1");
}
if (config.cacheType != null && admin) {
url = addOption(url, "CACHE_TYPE", config.cacheType);
}
......@@ -1056,13 +1060,30 @@ public abstract class TestBase {
protected void assertThrows(int expectedErrorCode, Statement stat,
String sql) {
try {
stat.execute(sql);
execute(stat, sql);
fail("Expected error: " + expectedErrorCode);
} catch (SQLException ex) {
assertEquals(expectedErrorCode, ex.getErrorCode());
}
}
protected void execute(PreparedStatement stat) throws SQLException {
execute(stat, null);
}
protected void execute(Statement stat, String sql) throws SQLException {
boolean query = sql == null ? ((PreparedStatement) stat).execute() :
stat.execute(sql);
if (query && config.lazy) {
try (ResultSet rs = stat.getResultSet()) {
while (rs.next()) {
// just loop
}
}
}
}
/**
* Check if the result set meta data is correct.
*
......@@ -1497,8 +1518,9 @@ public abstract class TestBase {
AssertionError ae = new AssertionError(
"Expected an SQLException or DbException with error code "
+ expectedErrorCode
+ ", but got a " + t.getClass().getName() + " exception "
+ " with error code " + errorCode);
+ ", but got a " + (t == null ? "null" :
t.getClass().getName() + " exception "
+ " with error code " + errorCode));
ae.initCause(t);
throw ae;
}
......
......@@ -163,6 +163,9 @@ public class TestLob extends TestBase {
}
private void testRemovedAfterTimeout() throws Exception {
if (config.lazy) {
return;
}
deleteDb("lob");
final String url = getURL("lob;lob_timeout=50", true);
Connection conn = getConnection(url);
......@@ -199,6 +202,9 @@ public class TestLob extends TestBase {
}
private void testConcurrentRemoveRead() throws Exception {
if (config.lazy) {
return;
}
deleteDb("lob");
final String url = getURL("lob", true);
Connection conn = getConnection(url);
......
......@@ -220,6 +220,9 @@ public class TestOptimizations extends TestBase {
}
private void testQueryCacheConcurrentUse() throws Exception {
if (config.lazy) {
return;
}
final Connection conn = getConnection("optimizations");
Statement stat = conn.createStatement();
stat.execute("create table test(id int primary key, data clob)");
......
......@@ -37,9 +37,16 @@ public class TestOutOfMemory extends TestBase {
@Override
public void test() throws SQLException {
testMVStoreUsingInMemoryFileSystem();
testDatabaseUsingInMemoryFileSystem();
testUpdateWhenNearlyOutOfMemory();
try {
System.gc();
testMVStoreUsingInMemoryFileSystem();
System.gc();
testDatabaseUsingInMemoryFileSystem();
System.gc();
testUpdateWhenNearlyOutOfMemory();
} finally {
System.gc();
}
}
private void testMVStoreUsingInMemoryFileSystem() {
......
......@@ -103,7 +103,7 @@ public class TestRecursiveQueries extends TestBase {
prep2.setInt(1, 10);
prep2.setInt(2, 2);
prep2.setInt(3, 14);
prep2.execute();
assertTrue(prep2.executeQuery().next());
rs = prep.executeQuery();
assertTrue(rs.next());
assertEquals(10, rs.getInt(1));
......@@ -116,7 +116,7 @@ public class TestRecursiveQueries extends TestBase {
prep2.setInt(1, 100);
prep2.setInt(2, 3);
prep2.setInt(3, 103);
prep2.execute();
assertTrue(prep2.executeQuery().next());
rs = prep.executeQuery();
assertTrue(rs.next());
assertEquals(100, rs.getInt(1));
......
......@@ -284,12 +284,6 @@ public class TestScript extends TestBase {
ResultSetMetaData meta = rs.getMetaData();
int len = meta.getColumnCount();
int[] max = new int[len];
String[] head = new String[len];
for (int i = 0; i < len; i++) {
String label = formatString(meta.getColumnLabel(i + 1));
max[i] = label.length();
head[i] = label;
}
result.clear();
while (rs.next()) {
String[] row = new String[len];
......@@ -302,6 +296,14 @@ public class TestScript extends TestBase {
}
result.add(row);
}
String[] head = new String[len];
for (int i = 0; i < len; i++) {
String label = formatString(meta.getColumnLabel(i + 1));
if (max[i] < label.length()) {
max[i] = label.length();
}
head[i] = label;
}
rs.close();
writeResult(sql, format(head, max), null);
writeResult(sql, format(null, max), null);
......
......@@ -1200,7 +1200,7 @@ public class TestTableEngines extends TestBase {
}
};
public TreeSetTable(CreateTableData data) {
TreeSetTable(CreateTableData data) {
super(data);
}
......@@ -1602,7 +1602,7 @@ public class TestTableEngines extends TestBase {
Iterator<SearchRow> it;
private Row current;
public IteratorCursor(Iterator<SearchRow> it) {
IteratorCursor(Iterator<SearchRow> it) {
this.it = it;
}
......@@ -1644,12 +1644,12 @@ public class TestTableEngines extends TestBase {
private int[] cols;
private boolean descending;
public RowComparator(int... cols) {
RowComparator(int... cols) {
this.descending = false;
this.cols = cols;
}
public RowComparator(boolean descending, int... cols) {
RowComparator(boolean descending, int... cols) {
this.descending = descending;
this.cols = cols;
}
......
......@@ -90,6 +90,9 @@ public class TestTempTables extends TestBase {
}
private void testTempFileResultSet() throws SQLException {
if (config.lazy) {
return;
}
deleteDb("tempTables");
Connection conn = getConnection("tempTables;MAX_MEMORY_ROWS=10");
ResultSet rs1, rs2;
......@@ -100,10 +103,10 @@ public class TestTempTables extends TestBase {
rs1 = stat1.executeQuery("select * from system_range(1, 20)");
rs2 = stat2.executeQuery("select * from system_range(1, 20)");
for (int i = 0; i < 20; i++) {
rs1.next();
rs2.next();
rs1.getInt(1);
rs2.getInt(1);
assertTrue(rs1.next());
assertTrue(rs2.next());
assertEquals(i + 1, rs1.getInt(1));
assertEquals(i + 1, rs2.getInt(1));
}
rs2.close();
// verify the temp table is not deleted yet
......
......@@ -189,9 +189,8 @@ public class TestCancel extends TestBase {
cancel.start();
try {
Thread.yield();
assertThrows(ErrorCode.STATEMENT_WAS_CANCELED, query).
executeQuery("SELECT VISIT(ID), (SELECT SUM(X) " +
"FROM SYSTEM_RANGE(1, 100000) WHERE X<>ID) FROM TEST ORDER BY ID");
assertThrows(ErrorCode.STATEMENT_WAS_CANCELED, query, "SELECT VISIT(ID), (SELECT SUM(X) " +
"FROM SYSTEM_RANGE(1, 100000) WHERE X<>ID) FROM TEST ORDER BY ID");
} finally {
cancel.stopNow();
cancel.join();
......
......@@ -1256,7 +1256,7 @@ public class TestMetaData extends TestBase {
stat.execute("SET QUERY_STATISTICS TRUE");
int count = 100;
for (int i = 0; i < count; i++) {
stat.execute("select * from test limit 10");
execute(stat, "select * from test limit 10");
}
// The "order by" makes the result set more stable on windows, where the
// timer resolution is not that great
......@@ -1266,7 +1266,7 @@ public class TestMetaData extends TestBase {
assertTrue(rs.next());
assertEquals("select * from test limit 10", rs.getString("SQL_STATEMENT"));
assertEquals(count, rs.getInt("EXECUTION_COUNT"));
assertEquals(10 * count, rs.getInt("CUMULATIVE_ROW_COUNT"));
assertEquals(config.lazy ? 0 : 10 * count, rs.getInt("CUMULATIVE_ROW_COUNT"));
rs.close();
conn.close();
deleteDb("metaData");
......
......@@ -372,11 +372,11 @@ public class TestPreparedStatement extends TestBase {
prepareStatement("SELECT * FROM (SELECT ? FROM DUAL)");
PreparedStatement prep = conn.prepareStatement("SELECT -?");
prep.setInt(1, 1);
prep.execute();
execute(prep);
prep = conn.prepareStatement("SELECT ?-?");
prep.setInt(1, 1);
prep.setInt(2, 2);
prep.execute();
execute(prep);
}
private void testCancelReuse(Connection conn) throws Exception {
......@@ -390,7 +390,7 @@ public class TestPreparedStatement extends TestBase {
Task t = new Task() {
@Override
public void call() throws SQLException {
prep.execute();
TestPreparedStatement.this.execute(prep);
}
};
t.execute();
......
......@@ -266,8 +266,13 @@ public class TestUpdatableResultSet extends TestBase {
assertTrue(rs.absolute(3));
assertEquals(3, rs.getRow());
assertTrue(rs.absolute(-1));
assertEquals(3, rs.getRow());
if (!config.lazy) {
assertTrue(rs.absolute(-1));
assertEquals(3, rs.getRow());
assertTrue(rs.absolute(-2));
assertEquals(2, rs.getRow());
}
assertFalse(rs.absolute(4));
assertTrue(rs.isAfterLast());
......
......@@ -38,9 +38,9 @@ public class TestHaltApp extends TestHalt {
}
}
private void execute(Statement stat, String sql) throws SQLException {
protected void execute(Statement stat, String sql) throws SQLException {
traceOperation("execute: " + sql);
stat.execute(sql);
super.execute(stat, sql);
}
/**
......
......@@ -56,7 +56,7 @@ public class H2Cursor extends AbstractWindowedCursor {
@Override
public int getCount() {
return result.getRowCount();
return result.isLazy() ? -1 : result.getRowCount();
}
/**
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论