package com.tesora.dve.queryplan;
/*
* #%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.clock.NoopTimingService;
import com.tesora.dve.clock.Timer;
import com.tesora.dve.clock.TimingService;
import com.tesora.dve.exceptions.PESQLStateException;
import com.tesora.dve.singleton.Singletons;
import org.apache.log4j.Logger;
import com.tesora.dve.common.logutil.ExecutionLogger;
import com.tesora.dve.common.logutil.LogSubject;
import com.tesora.dve.db.DBResultConsumer;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.server.connectionmanager.SSConnection;
import com.tesora.dve.sql.transform.execution.ConnectionValuesMap;
import com.tesora.dve.sql.transform.execution.ExecutionPlanOptions;
import com.tesora.dve.sql.transform.execution.LateBindingUpdateCountFilter.RuntimeUpdateCounter;
/**
* <b>QueryPlan</b> lays out the steps required to execute a user's SQL query.
* <p/>
* A <b>QueryPlan</b> is built up of a series of {@link QueryStep}s, with each
* step having one or more operations (setup, execute, cleanup) and zero or more
* dependencies (steps that must be executed before it).
* <p/>
* Each step is executed in sequence by calling {@link QueryPlan#executeStep(SSConnection)}
* and {@link QueryPlan#getMoreResults()}. At each step, the <b>QueryPlan</b> makes
* available any results of executing the step (see {@link QueryPlan#getResultCollector()}).
* <p/>
* See {@link QueryStepBasicTest} for examples of how to build a <b>QueryPlan</b>.
*
*/
public class QueryPlan implements LogSubject {
static Logger logger = Logger.getLogger( QueryPlan.class );
TimingService timingService = Singletons.require(TimingService.class, NoopTimingService.SERVICE);
enum TimingDesc {QUERYPLAN_STEP_ROUND_TRIP, QUERYPLAN_STEP_BEFORE, QUERYPLAN_STEP_AFTER }
QueryStepOperation root;
final ConnectionValuesMap cv;
SSConnection ssCon;
// sometimes the update count from the last step is incorrect, as when
// a multivalue insert has been planned (the last step may have only one value in it)
// if the planner knows the true count, use that instead
Long trueUpdateCount = null;
RuntimeUpdateCounter lateBindingUpdateCounter = null;
// sometimes the actual update count is a row count, i.e. insert into select
boolean useRowCount = false;
// original sql - maybe be only a prefix
String inputStatement;
/**
* Constructs an empty <b>QueryPlan</b>
*/
public QueryPlan(ConnectionValuesMap cv) {
this.cv = cv;
}
// compatibility constructor
public QueryPlan() {
this((ConnectionValuesMap)null);
}
public QueryPlan(QueryStepOperation op) throws PEException {
this();
addStep(op);
}
/**
* Adds a step to the <b>QueryPlan</b>
*
* @param step step to add
* @return itself for inline calling
*/
public QueryPlan addStep(QueryStepOperation op) {
if (root == null)
root = op;
else {
op.addRequirement(root);
root = op;
}
return this;
}
public void setInputStatement(String sql) {
if (sql == null) return;
if (sql.length() > 1024)
inputStatement = sql.substring(0, 1024);
else
inputStatement = sql;
}
public String getInputStatement() {
return inputStatement;
}
public boolean isEmpty() {
return root == null;
}
/**
* Executes the current step in the <b>QueryPlan</b>.
*
* @param ssCon1 {@link SSConnection} for the user's session
* @param resultConsumer
* @return <b>true</b> if the query returned results
* @throws Throwable
*/
public void executeStep(SSConnection ssCon1, DBResultConsumer resultConsumer) throws Throwable {
this.ssCon = ssCon1;
if (root == null)
throw new PEException("Attempt to execute empty (or exhausted) QueryPlan)");
//TODO: There is a lot of similarity between the ExecutionLogger and the Timer service, maybe merge them?
Timer stepTime = timingService.startSubTimer(TimingDesc.QUERYPLAN_STEP_ROUND_TRIP);
Timer preStep = stepTime.newSubTimer(TimingDesc.QUERYPLAN_STEP_BEFORE);
ExecutionLogger beforeLogger = ssCon.getExecutionLogger().getNewLogger("BeforePlanExecute");
if (!ssCon.isAutoCommitMode() && !ssCon.hasActiveTransaction()) {
ssCon.userBeginTransaction();
}
boolean txnNeeded = root.requiresTransaction() && root.hasRequirements();
if (txnNeeded)
ssCon.autoBeginTransaction(false);
beforeLogger.end();
preStep.end();
try {
root.execute(new ExecutionState(ssCon1,cv), resultConsumer);
if (trueUpdateCount != null)
resultConsumer.setNumRowsAffected(trueUpdateCount);
// for certain operations we have to accumulate the update count from dependent steps (i.e. REPLACE INTO)
if (lateBindingUpdateCounter != null)
resultConsumer.setNumRowsAffected(adjustUpdateCount(resultConsumer.getUpdateCount()));
} catch (PEException e) {
ExecutionLogger afterLogger = ssCon.getExecutionLogger().getNewLogger("AfterPlanExecuteOnException");
try {
try {
//TODO: this check is all or none, but apparently 'some do', hence the special casing. -sgossard
if (ssCon.getDBNative().exceptionAbortsTxn(e)) {
ssCon.onTransactionAbortedByJDBC();
txnNeeded = false;
}
if (txnNeeded)
ssCon.autoRollbackTransaction();
Exception root = e.rootCause();
boolean rootCauseIsSQLErrorMessage = (root instanceof PESQLStateException);
if (ssCon.hasActiveXATransaction() && rootCauseIsSQLErrorMessage){
PESQLStateException sqlState = (PESQLStateException)root;
if (sqlState.isXAFailed()){
ssCon.autoRollbackTransaction(true);
}
}
} catch (Exception e1) {
// ignore ?
}
throw e;
} finally {
afterLogger.end();
}
}
Timer afterTime = stepTime.newSubTimer(TimingDesc.QUERYPLAN_STEP_AFTER);
ExecutionLogger afterLogger = ssCon.getExecutionLogger().getNewLogger("AfterPlanExecute");
if (txnNeeded)
ssCon.autoCommitTransaction();
afterLogger.end();
afterTime.end();
stepTime.end();
}
// This method is used to add the dependent step update counts to the final tally
// used by REPLACE INTO (See commit message on PE-852)
private long adjustUpdateCount(long in) {
if (lateBindingUpdateCounter == null) {
return in;
}
if (lateBindingUpdateCounter.getRuntimeUpdateCountAccumulation()) {
return lateBindingUpdateCounter.incrementBy(in);
}
return lateBindingUpdateCounter.getValue();
}
public void setTrueUpdateCount(Long v) {
trueUpdateCount = v;
}
public void setRuntimeUpdateCountAdjustment(final ExecutionPlanOptions options) {
lateBindingUpdateCounter = options.getRuntimeUpdateCounter();
}
public void setUseRowCount() {
useRowCount = true;
}
@Override
public String describeForLog() {
return getInputStatement();
}
public QueryStepOperation getRootOperation() {
return root;
}
}