/*****************************************************************************
* Copyright (C) 2008 EnterpriseDB Corporation.
* Copyright (C) 2011 Stado Global Development Group.
*
* This file is part of Stado.
*
* Stado is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Stado 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Stado. If not, see <http://www.gnu.org/licenses/>.
*
* You can find Stado at http://www.stado.us
*
****************************************************************************/
/*
* ExecutableRequest.java
*
*
*/
package org.postgresql.stado.engine;
import java.sql.Statement;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.postgresql.stado.common.CommandLog;
import org.postgresql.stado.common.util.XLevel;
import org.postgresql.stado.common.util.XLogger;
import org.postgresql.stado.exception.XDBServerException;
import org.postgresql.stado.metadata.DBNode;
import org.postgresql.stado.metadata.scheduler.BatchCost;
import org.postgresql.stado.metadata.scheduler.ILockCost;
import org.postgresql.stado.metadata.scheduler.RequestCost;
import org.postgresql.stado.misc.Timer;
import org.postgresql.stado.parser.IXDBSql;
import org.postgresql.stado.parser.SqlBeginTransaction;
import org.postgresql.stado.parser.SqlBulkInsert;
import org.postgresql.stado.parser.SqlCommitTransaction;
import org.postgresql.stado.parser.SqlModifyTable;
import org.postgresql.stado.parser.SqlRollbackTransaction;
import org.postgresql.stado.parser.SqlSelect;
/**
*
*/
public class ExecutableRequest {
private static final XLogger logger = XLogger
.getLogger(ExecutableRequest.class);
public static final char STATUS_QUEUED = 'Q';
public static final char STATUS_PREPARED = 'P';
public static final char STATUS_EXECUTING = 'E';
public static final char STATUS_DYING = 'D';
// BUILD_CUT_START
public static Timer requestTimer = new Timer();
public static Timer batchTimer = new Timer();
// BUILD_CUT_END
private static int SYS_REQUEST_ID = 0;
private int requestID;
private long submitTime;
private int fetchSize;
private RequestCost cost;
private ILockCost[] subRequests;
private String originalCommand;
private volatile char status = STATUS_QUEUED;
private static long nextStatementId = 0;
private synchronized static long getNextStatementId() {
if (nextStatementId >= Long.MAX_VALUE) {
nextStatementId = 0;
}
return ++nextStatementId;
}
private long statementId = getNextStatementId();
/**
*
* @param command
*/
public ExecutableRequest(String command) {
requestID = SYS_REQUEST_ID == Integer.MAX_VALUE ? SYS_REQUEST_ID = 0
: ++SYS_REQUEST_ID;
submitTime = System.currentTimeMillis();
subRequests = null;
originalCommand = command;
}
/**
*
* @return
*/
public int getRequestID() {
return requestID;
}
/**
* @return Returns the cost. Value returned is never null.
*/
public RequestCost getCost() {
if (cost == null) {
cost = new RequestCost(null);
}
return cost;
}
/**
* @return Returns the submitTime.
*/
public long getSubmitTime() {
return submitTime;
}
/**
* @return Returns the statement.
*/
public String getStatement() {
return originalCommand;
}
/**
* @return Returns the fetchSize.
*/
public int getFetchSize() {
return fetchSize;
}
/**
*
* @return
*/
public char getStatus() {
return status;
}
/**
* @param fetchSize
* the fetchSize.
*/
public void setFetchSize(int fetchSize) {
this.fetchSize = fetchSize;
}
/**
*
* @param sqlObject
* @throws org.postgresql.stado.exception.XDBServerException
*/
public void setSQLObject(ILockCost sqlObject) throws XDBServerException {
if (sqlObject instanceof IPreparable) {
try {
((IPreparable) sqlObject).prepare();
} catch (Exception e) {
throw new XDBServerException("Can not prepare request: "
+ e.getMessage(), e);
}
}
cost = new RequestCost(sqlObject);
status = STATUS_PREPARED;
}
/**
* @return Returns the subRequests.
*/
public ILockCost[] getSubRequests() {
return subRequests;
}
/**
*
* @param subRequests
* The subRequests to set.
* @throws org.postgresql.stado.exception.XDBServerException
*/
public void setSubRequests(ILockCost[] subRequests)
throws XDBServerException {
BatchCost bc = new BatchCost();
for (ILockCost element : subRequests) {
if (element instanceof SqlModifyTable) {
((SqlModifyTable) element).setBatchMode();
}
if (element instanceof IPreparable) {
try {
((IPreparable) element).prepare();
} catch (Exception e) {
throw new XDBServerException(
"Can not add request to batch: " + e.getMessage());
}
}
bc.addElement(element);
}
cost = new RequestCost(bc);
this.subRequests = subRequests;
status = STATUS_PREPARED;
}
/**
*
* @param engine
* @param client
* @return
*/
ExecutionResult execute(Engine engine, XDBSessionContext client) {
try {
try {
status = STATUS_EXECUTING;
client.setStatementId(statementId);
ILockCost sqlObject = cost.getSqlObject();
if (sqlObject instanceof IExecutable) {
Collection<DBNode> nodeList = null;
if (sqlObject instanceof IXDBSql) {
nodeList = ((IXDBSql) sqlObject).getNodeList();
}
boolean goPersistent = !(sqlObject instanceof SqlSelect)
&& !(sqlObject instanceof SqlBulkInsert)
&& !(sqlObject instanceof SqlBeginTransaction)
&& !(sqlObject instanceof SqlCommitTransaction)
&& !(sqlObject instanceof SqlRollbackTransaction);
if (goPersistent) {
client.setPersistent(true);
}
try {
ExecutionResult result = null;
Timer execTimer = new Timer();
try {
// BUILD_CUT_START
requestTimer.startTimer();
// BUILD_CUT_END
execTimer.startTimer();
result = ((IExecutable) sqlObject).execute(engine);
execTimer.stopTimer();
// BUILD_CUT_START
requestTimer.stopTimer();
// BUILD_CUT_END
} catch (XDBServerException e) {
if (!client.isInTransaction()
&& !client.isInSubTransaction()) {
// Clean up underlying connections before
// executing next command
engine.rollbackTransaction(client, nodeList);
}
// else client will explicitly rollback
throw e;
}
if (goPersistent && nodeList != null
&& nodeList.size() > 1
&& !result.hasResultSet()
&& !client.isInTransaction()
&& !client.isInSubTransaction()
&& !client.hasActiveResultSets()) {
engine.commitTransaction(client, nodeList);
}
// See if this was a long query
CommandLog.checkLongQuery(originalCommand, execTimer
.getDurationSeconds(), client);
return result;
} catch (Exception e) {
logger.catching(e);
if (!client.isInTransaction()
&& !client.isInSubTransaction()) {
engine.rollbackTransaction(client, nodeList);
}
logger.throwing(e);
throw e;
} finally {
if (goPersistent) {
client.setPersistent(false);
}
}
} else if (subRequests != null) {
boolean explicitTransaction = !client.isInTransaction();
if (explicitTransaction) {
engine.beginTransaction(client, null);
}
// BUILD_CUT_START
batchTimer.startTimer();
// BUILD_CUT_END
try {
Map<Integer,ExecutionResult> responses = new HashMap<Integer,ExecutionResult>(
subRequests.length);
int i = 0;
for (ILockCost sqlObjectItem : subRequests) {
// Only INSERT, UPDATE and DELETE are supported
if (sqlObjectItem instanceof SqlModifyTable) {
SqlModifyTable statement = (SqlModifyTable) sqlObjectItem;
try {
responses.put(i++,
statement.execute(engine));
} catch (Exception e) {
logger.catching(e);
responses.put(
i++,
ExecutionResult.createRowCountResult(
statement.getResultType(),
Statement.EXECUTE_FAILED));
}
} else {
logger.warn("Only INSERT, UPDATE and DELETE are allowed in batches");
responses.put(
i++,
ExecutionResult.createRowCountResult(
ExecutionResult.COMMAND_UNKNOWN,
Statement.EXECUTE_FAILED));
}
}
if (explicitTransaction) {
Collection<DBNode> empty = Collections.emptyList();
engine.commitTransaction(client, empty);
}
return ExecutionResult.createMultipleResult(
ExecutionResult.COMMAND_BATCH_EXEC, responses);
} catch (Exception e) {
logger.catching(e);
// Transaction could be terminated if session was closed
if (explicitTransaction && client.isInTransaction()) {
Collection<DBNode> empty = Collections.emptyList();
engine.rollbackTransaction(client, empty);
}
logger.throwing(e);
throw e;
// BUILD_CUT_START
} finally {
batchTimer.stopTimer();
// BUILD_CUT_END
}
} else {
throw new XDBServerException("Empty request");
}
} catch (Exception ex) {
logger.log(XLevel.TRACE, "Request failed, request: %0%",
new Object[] { client });
logger.catching(ex);
return ExecutionResult.createErrorResult(ex);
}
} finally {
status = STATUS_PREPARED;
}
}
public void cancel() {
status = STATUS_DYING;
}
/**
*
* @return
*/
public boolean cancelled() {
return status == STATUS_DYING;
}
/**
*
* @return the current statement id
*/
public long getStatementId() {
return statementId;
}
}