/*
* 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.command;
import java.io.IOException;
import java.util.ArrayList;
import org.h2.engine.Constants;
import org.h2.engine.SessionRemote;
import org.h2.engine.SysProperties;
import org.h2.expression.ParameterInterface;
import org.h2.expression.ParameterRemote;
import org.h2.message.DbException;
import org.h2.message.Trace;
import org.h2.result.ResultInterface;
import org.h2.result.ResultRemote;
import org.h2.util.New;
import org.h2.value.Transfer;
import org.h2.value.Value;
import org.h2.value.ValueNull;
/**
* Represents the client-side part of a SQL statement.
* This class is not used in embedded mode.
*/
public class CommandRemote implements CommandInterface {
private final ArrayList<Transfer> transferList;
private final ArrayList<ParameterInterface> parameters;
private final Trace trace;
private final String sql;
private final int fetchSize;
private SessionRemote session;
private int id;
private boolean isQuery;
private int cmdType = UNKNOWN;
private boolean readonly;
private final int created;
public CommandRemote(SessionRemote session,
ArrayList<Transfer> transferList, String sql, int fetchSize) {
this.transferList = transferList;
trace = session.getTrace();
this.sql = sql;
parameters = New.arrayList();
prepare(session, true);
// set session late because prepare might fail - in this case we don't
// need to close the object
this.session = session;
this.fetchSize = fetchSize;
created = session.getLastReconnect();
}
private void prepare(SessionRemote s, boolean createParams) {
//虽然getNextId内部是nextId++;
//但是org.h2.engine.SessionRemote.prepareCommand(String, int)是synchronized的,
//
//另外org.h2.command.CommandRemote.prepareIfRequired()也在synchronized (session)中调用
//所以这里没有并发问题
id = s.getNextId(); //这个id会发往server,用于缓存server对应的command。
for (int i = 0, count = 0; i < transferList.size(); i++) {
try {
Transfer transfer = transferList.get(i);
boolean v16 = s.getClientVersion() >= Constants.TCP_PROTOCOL_VERSION_16;
if (createParams) {
s.traceOperation(v16 ? "SESSION_PREPARE_READ_PARAMS2"
: "SESSION_PREPARE_READ_PARAMS", id);
transfer.writeInt(
v16 ? SessionRemote.SESSION_PREPARE_READ_PARAMS2
: SessionRemote.SESSION_PREPARE_READ_PARAMS)
.writeInt(id).writeString(sql);
} else {
s.traceOperation("SESSION_PREPARE", id);
transfer.writeInt(SessionRemote.SESSION_PREPARE).
writeInt(id).writeString(sql);
}
s.done(transfer);
isQuery = transfer.readBoolean();
readonly = transfer.readBoolean();
cmdType = v16 && createParams ? transfer.readInt() : UNKNOWN;
int paramCount = transfer.readInt();
if (createParams) {
parameters.clear();
//prepare阶段每个ParameterRemote只有类型还没有值,会在接下来通过getParameters()传给JdbcPreparedStatement
//然后在JdbcPreparedStatement中设置,如果在executeQuery和executeUpdate中还没有为这些参数设置值,
//那么调用checkParameters时会抛异常
for (int j = 0; j < paramCount; j++) {
ParameterRemote p = new ParameterRemote(j);
p.readMetaData(transfer);
parameters.add(p);
}
}
} catch (IOException e) {
s.removeServer(e, i--, ++count);
}
}
}
@Override
public boolean isQuery() {
return isQuery;
}
@Override
public ArrayList<ParameterInterface> getParameters() {
return parameters;
}
private void prepareIfRequired() {
if (session.getLastReconnect() != created) {
// in this case we need to prepare again in every case
id = Integer.MIN_VALUE;
}
session.checkClosed();
if (id <= session.getCurrentId() - SysProperties.SERVER_CACHED_OBJECTS) {
// object is too old - we need to prepare again
prepare(session, false);
}
}
//这个并不是对应java.sql.ResultSetMetaData,而是在org.h2.jdbc.JdbcPreparedStatement.getMetaData()调用
//等价于ResultSet.getMetaData(),只不过PreparedStatement.getMetaData()不需要事先执行查询
@Override
public ResultInterface getMetaData() {
synchronized (session) {
if (!isQuery) {
return null;
}
int objectId = session.getNextId();
ResultRemote result = null;
for (int i = 0, count = 0; i < transferList.size(); i++) {
prepareIfRequired();
Transfer transfer = transferList.get(i);
try {
session.traceOperation("COMMAND_GET_META_DATA", id);
//这里得到的ResultRemote也会在server端按objectId缓存一个org.h2.result.LocalResult
//但是这个ResultRemote在org.h2.jdbc.JdbcPreparedStatement.getMetaData()中被封装到JdbcResultSetMetaData后
//没有机会调用ResultRemote.close来释放server端的相关东西了
//这个影响有多大?不过server端的cache是有限制的,并且按session来配置,所以不至于造成OOM
//==========================
//上面理解错了,ResultRemote的构造函数中会触发ResultRemote.fetchRows(boolean),
//因为rowCount是0,所以当下就调用sendClose了
transfer.writeInt(SessionRemote.COMMAND_GET_META_DATA).writeInt(id).writeInt(objectId);
session.done(transfer);
int columnCount = transfer.readInt();
result = new ResultRemote(session, transfer, objectId,
columnCount, Integer.MAX_VALUE);
break;
} catch (IOException e) {
session.removeServer(e, i--, ++count);
}
}
session.autoCommitIfCluster();
return result;
}
}
@Override
public ResultInterface executeQuery(int maxRows, boolean scrollable) {
checkParameters();
synchronized (session) {
int objectId = session.getNextId();
ResultRemote result = null;
for (int i = 0, count = 0; i < transferList.size(); i++) {
prepareIfRequired();
Transfer transfer = transferList.get(i);
try {
session.traceOperation("COMMAND_EXECUTE_QUERY", id);
transfer.writeInt(SessionRemote.COMMAND_EXECUTE_QUERY).
writeInt(id).writeInt(objectId).writeInt(maxRows);
int fetch;
if (session.isClustered() || scrollable) {
fetch = Integer.MAX_VALUE;
} else {
fetch = fetchSize;
}
transfer.writeInt(fetch);
//如果是JdbcStatement,没有参数,JdbcPreparedStatement才有
sendParameters(transfer);
session.done(transfer);
int columnCount = transfer.readInt();
if (result != null) {
result.close();
result = null;
}
result = new ResultRemote(session, transfer, objectId, columnCount, fetch);
//对于只读查询只需要从一台server上获得结果就可以了,不需要每台server都查询一次
//select ... for update并不能使readonly为false,
//相反,在where中加上一些NotDeterministic的函数,例如RAND、RANDOM等反而能使readonly为false
//见org.h2.expression.Function.addFunctionNotDeterministic(String, int, int, int)
//例子: select * from SessionRemoteTest where id>? and b=? and id<RAND()
if (readonly) {
break;
}
} catch (IOException e) {
session.removeServer(e, i--, ++count);
}
}
session.autoCommitIfCluster();
session.readSessionState();
return result;
}
}
//注意: transferList.size大于1时,说明是集群环境,但是并不是XA,也就是说可能有一台server更新成功了,可以允许另一台更新不成功
@Override
public int executeUpdate() {
checkParameters();
synchronized (session) {
int updateCount = 0;
boolean autoCommit = false;
for (int i = 0, count = 0; i < transferList.size(); i++) {
prepareIfRequired();
Transfer transfer = transferList.get(i);
try {
session.traceOperation("COMMAND_EXECUTE_UPDATE", id);
transfer.writeInt(SessionRemote.COMMAND_EXECUTE_UPDATE).writeInt(id);
//如果是JdbcStatement,没有参数,JdbcPreparedStatement才有
sendParameters(transfer);
session.done(transfer);
updateCount = transfer.readInt();
autoCommit = transfer.readBoolean();
} catch (IOException e) {
//只有所有server都出错时才尝试重连(不过要取决于各种参数)
session.removeServer(e, i--, ++count);
}
}
session.setAutoCommitFromServer(autoCommit); //如果是集群环境,设为false
session.autoCommitIfCluster(); //如果是集群环境,通知所有server提交事务
session.readSessionState();//当session状态发生改变时,提取INFORMATION_SCHEMA.SESSION_STATE信息,下次可重建session
return updateCount;
}
}
private void checkParameters() {
if (cmdType != EXPLAIN) {
for (ParameterInterface p : parameters) {
p.checkSet();
}
}
}
private void sendParameters(Transfer transfer) throws IOException {
int len = parameters.size();
transfer.writeInt(len);
for (ParameterInterface p : parameters) {
Value pVal = p.getParamValue();
if (pVal == null && cmdType == EXPLAIN) {
pVal = ValueNull.INSTANCE;
}
transfer.writeValue(pVal);
}
}
@Override
public void close() {
if (session == null || session.isClosed()) {
return;
}
synchronized (session) {
session.traceOperation("COMMAND_CLOSE", id);
for (Transfer transfer : transferList) {
try {
//transfer没有立刻flush输出流,可能是考虑到COMMAND_CLOSE命令不需要server端响应,
//也考虑到不立刻flush输出流能一定程度上提高调用close()方法的性能,
//延迟到下一次执行query、update或关闭session时再一并flush出去
transfer.writeInt(SessionRemote.COMMAND_CLOSE).writeInt(id);
} catch (IOException e) {
trace.error(e, "close");
}
}
}
session = null;
try {
for (ParameterInterface p : parameters) {
Value v = p.getParamValue();
if (v != null) {
v.remove(); //只对ValueLob、ValueLobDb有用
}
}
} catch (DbException e) {
trace.error(e, "close");
}
parameters.clear();
}
/**
* Cancel this current statement.
*/
@Override
public void cancel() {
session.cancelStatement(id);
}
@Override
public String toString() {
return sql + Trace.formatParams(getParameters());
}
@Override
public int getCommandType() {
return cmdType;
}
}