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

Lazy query execution support

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