package com.tesora.dve.server.messaging;
/*
* #%L
* Tesora Inc.
* Database Virtualization Engine
* %%
* Copyright (C) 2011 - 2014 Tesora Inc.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import com.tesora.dve.concurrent.CompletionHandle;
import com.tesora.dve.db.CommandChannel;
import com.tesora.dve.db.GroupDispatch;
import com.tesora.dve.db.mysql.DelegatingResultsProcessor;
import com.tesora.dve.db.mysql.MysqlMessage;
import com.tesora.dve.db.mysql.PassFailProcessor;
import com.tesora.dve.db.mysql.portal.protocol.MSPComQueryRequestMessage;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.exceptions.PESQLException;
import com.tesora.dve.server.connectionmanager.PerHostConnectionManager;
import com.tesora.dve.worker.StatementManager;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.CharsetUtil;
import org.apache.commons.lang.StringUtils;
import com.tesora.dve.comms.client.messages.RequestMessage;
import com.tesora.dve.server.connectionmanager.SSContext;
import com.tesora.dve.server.statistics.manager.LogSiteStatisticRequest;
import com.tesora.dve.worker.Worker;
/**
* WorkerRequest is the base class of any requests to be executed in the context of a worker. Since the message
* will be executed on several workers in parallel, any member variables of classes which derive from WorkerRequest
* should be final (otherwise, one worker could change the parameters seen by another worker, which would
* cause concurrency issues).
*
*/
@SuppressWarnings("serial")
public abstract class WorkerRequest extends RequestMessage implements GroupDispatch {
GroupDispatch groupDispatch;
final SSContext connectionContext;
boolean autoTransact = true;
public WorkerRequest withGroupDispatch(GroupDispatch groupDispatch) {
this.groupDispatch = groupDispatch;
return this;
}
@Override
public void setSenderCount(int senderCount) {
this.groupDispatch.setSenderCount(senderCount);
}
@Override
public Bundle getDispatchBundle(CommandChannel connection, SQLCommand sql, CompletionHandle<Boolean> promise) {
return this.groupDispatch.getDispatchBundle(connection, sql, promise);
}
public WorkerRequest(SSContext ctx) {
connectionContext = ctx;
}
public boolean isAutoTransact() {
return autoTransact && !StringUtils.isEmpty(connectionContext.getTransId());
}
public void setAutoTransact(boolean autoTransact) {
this.autoTransact = autoTransact;
}
public WorkerRequest forDDL() {
autoTransact = false;
return this;
}
public SSContext getContext() {
return connectionContext;
}
public String getTransId() {
return connectionContext.getTransId();
}
public abstract void executeRequest(Worker w, CompletionHandle<Boolean> promise);
public void execute(final Worker targetWorker, final SQLCommand sql, CompletionHandle<Boolean> promise) {
//NOTE: all worker requests that need to execute statements should come through here to make things easy to refactor.
final int connectionId = getConnectionId();
final GroupDispatch resultConsumer = this;
final Worker.SingleDirectStatement statement;
try {
statement = targetWorker.getStatement();
CommandChannel dbConnection = statement.dbConnection;
Long referenceTime = null;
if (sql.hasReferenceTime())
referenceTime = sql.getReferenceTime();
SQLCommand resolvedSQL = sql.getResolvedCommand(targetWorker);
Bundle dispatchBundle = resultConsumer.getDispatchBundle(dbConnection, resolvedSQL, promise);
MysqlMessage outboundMessage = dispatchBundle.outboundMessage;
final DelegatingResultsProcessor statementInterceptor = new DelegatingResultsProcessor(dispatchBundle.resultsProcessor){
boolean ended = false;
@Override
public void active(ChannelHandlerContext ctx) {
statementStart(connectionId, sql, statement);
super.active(ctx);
}
@Override
public void end(ChannelHandlerContext ctx) {
if (!ended) {
statementEnd(connectionId, statement);
super.end(ctx);
ended = true;
}
}
@Override
public void failure(Exception e) {
if (!ended) {
statementEnd(connectionId, statement);
PESQLException psqlError = new PESQLException(e.getMessage(), new PESQLException("On statement: " + sql.getDisplayForLog(), e));
super.failure(psqlError);
ended = true;
}
}
};
//TODO: this is one of the null promises that gets deferred. -sgossard
if (referenceTime != null) {
CompletionHandle<Boolean> deferred = dbConnection.getExceptionDeferringPromise();
String setTimestampSQL = "SET TIMESTAMP=" + referenceTime + ";";
MysqlMessage message = MSPComQueryRequestMessage.newMessage(new SQLCommand(CharsetUtil.UTF_8, setTimestampSQL).getBytes());
dbConnection.write(message, new PassFailProcessor(deferred));//don't flush, since we are writing another packet right behind it.dve
}
targetWorker.writeAndFlush(dbConnection, outboundMessage, statementInterceptor);
} catch (PEException pe){
promise.failure(pe);
}
}
@Override
public String toString() {
return new StringBuffer(getClass()
.getSimpleName()+"{type=").append(getMessageType())
.append(", globalId=").append(connectionContext.getTransId())
.append(", autoTrans=").append(autoTransact).append("}"
).toString();
}
public int getConnectionId() {
return connectionContext.getConnectionId();
}
public abstract LogSiteStatisticRequest getStatisticsNotice();
public static void statementEnd(int connectionId, Worker.SingleDirectStatement executingStatement) {
StatementManager.INSTANCE.unregisterStatement(connectionId, executingStatement);
PerHostConnectionManager.INSTANCE.resetConnectionState(connectionId);
}
public static void statementStart(int connectionId, SQLCommand sql, Worker.SingleDirectStatement executingStatement) {
StatementManager.INSTANCE.registerStatement(connectionId, executingStatement);
PerHostConnectionManager.INSTANCE.changeConnectionState(
connectionId, "Query", "", (sql == null) ? "Null Query" : sql.getRawSQL());
}
}