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 java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import com.tesora.dve.clock.NoopTimingService;
import com.tesora.dve.clock.Timer;
import com.tesora.dve.clock.TimingService;
import com.tesora.dve.errmap.AvailableErrors;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.exceptions.PESQLException;
import com.tesora.dve.groupmanager.CacheInvalidationMessage;
import com.tesora.dve.membership.GroupTopicPublisher;
import com.tesora.dve.server.connectionmanager.SSConnection;
import com.tesora.dve.singleton.Singletons;
import com.tesora.dve.sql.SchemaException;
import com.tesora.dve.sql.parser.InputState;
import com.tesora.dve.sql.parser.InvokeParser;
import com.tesora.dve.sql.parser.PlanningResult;
import com.tesora.dve.sql.parser.PreparePlanningResult;
import com.tesora.dve.sql.parser.SqlStatistics;
import com.tesora.dve.sql.schema.QualifiedName;
import com.tesora.dve.sql.schema.SchemaContext;
import com.tesora.dve.sql.schema.UnqualifiedName;
import com.tesora.dve.sql.schema.cache.CacheInvalidationRecord;
import com.tesora.dve.sql.schema.cache.CachedPreparedStatement;
import com.tesora.dve.sql.schema.cache.PlanCacheUtils;
import com.tesora.dve.sql.statement.StatementType;
import com.tesora.dve.sql.transform.execution.ConnectionValuesMap;
import com.tesora.dve.sql.transform.execution.ExecutionPlan;
import com.tesora.dve.sql.transform.execution.RootExecutionPlan;
import com.tesora.dve.sql.transform.execution.ExecutionPlanOptions;
import com.tesora.dve.sql.util.Pair;
public class QueryPlanner {
private static final boolean noisyErrors = false;
enum PlannerTime {PLANNER_BUILDPLAN}
static public Pair<QueryPlan,InputState> computeQueryPlan(final byte[] sqlCommand, final Charset cs, SSConnection connMgr) throws PEException {
final SchemaContext sc = connMgr.getSchemaContext();
SchemaContext.setThreadContext(sc);
sc.refresh();
try {
PlanningResult results = buildPlanningResult(new Callable<PlanningResult>() {
@Override
public PlanningResult call() throws Exception {
return InvokeParser.buildPlan(sc, sqlCommand, cs, null);
}
}, connMgr);
return new Pair<QueryPlan,InputState>(buildPlan(results,connMgr,sc),(results == null ? null : results.getInputState()));
} finally {
SchemaContext.clearThreadContext();
}
}
static public Pair<QueryPlan,InputState> computeQueryPlan(final InputState sqlCommand, SSConnection connMgr) throws PEException {
final SchemaContext sc = connMgr.getSchemaContext();
SchemaContext.setThreadContext(sc);
sc.refresh();
try {
PlanningResult results = buildPlanningResult(new Callable<PlanningResult>() {
@Override
public PlanningResult call() throws Exception {
return InvokeParser.buildPlan(sc,sqlCommand);
}
}, connMgr);
return new Pair<QueryPlan,InputState>(buildPlan(results, connMgr, sc), (results == null ? null : results.getInputState()));
} finally {
SchemaContext.clearThreadContext();
}
}
public static PreparedPlan prepareStatement(final byte[] sqlCommand, final Charset cs, SSConnection connMgr, final String stmtID) throws PEException {
final SchemaContext sc = connMgr.getSchemaContext();
SchemaContext.setThreadContext(sc);
sc.refresh();
try {
PlanningResult results = buildPlanningResult(new Callable<PlanningResult>() {
@Override
public PlanningResult call() throws Exception {
return InvokeParser.buildPlan(sc, sqlCommand, cs, stmtID);
}
}, connMgr);
PreparePlanningResult ppr = (PreparePlanningResult) results;
return new PreparedPlan(buildPlan(results, connMgr, sc),
ppr.getPrepareSQL(),
ppr.getCachedPlan());
} finally {
SchemaContext.clearThreadContext();
}
}
public static void registerPreparedStatement(SSConnection connMgr, String stmtID, PreparedPlan prepd) throws PEException {
// store the original stmt on the connection in case we need to rebuild it
// and then cache the plan in the pstmt cache
PlanCacheUtils.registerPreparedStatementPlan(connMgr.getSchemaContext(), (CachedPreparedStatement) prepd.getUnboundPlan(), prepd.getOriginalSQL(), connMgr.getConnectionId(), stmtID, false);
}
public static void destroyPreparedStatement(SSConnection connMgr, String stmtID) {
SqlStatistics.incrementCounter(StatementType.CLOSE_PREPARE);
PlanCacheUtils.destroyPreparedStatement(connMgr.getSchemaContext(),stmtID);
}
public static QueryPlan buildPreparedPlan(SSConnection connMgr, String stmtID, List<Object> params) throws PEException {
Timer buildPlanTime = startPlanTimer(PlannerTime.PLANNER_BUILDPLAN);
SchemaContext cntxt = connMgr.getSchemaContext();
SchemaContext.setThreadContext(cntxt);
cntxt.refresh();
try {
PlanningResult result = InvokeParser.bindPreparedStatement(cntxt, stmtID, params);
SqlStatistics.incrementCounter(StatementType.EXEC_PREPARE);
return buildPlan(result,connMgr,cntxt);
} finally {
buildPlanTime.end();
SchemaContext.clearThreadContext();
}
}
static PlanningResult buildPlanningResult(Callable<PlanningResult> toCall, SSConnection connMgr) throws PEException {
Timer buildPlanTime = startPlanTimer(PlannerTime.PLANNER_BUILDPLAN);
try {
return toCall.call();
} catch (PEException pe) {
if (noisyErrors) pe.printStackTrace();
throw pe;
} catch (Throwable t) {
if (isFiltered(t,connMgr))
return null;
if (noisyErrors) t.printStackTrace();
if (t instanceof SchemaException) {
SchemaException se = (SchemaException) t;
if (se.getErrorInfo() != null)
throw se;
}
throw new PESQLException("Unable to build plan - " + t.getMessage(), t);
} finally {
buildPlanTime.end();
}
}
@SuppressWarnings("rawtypes")
private static Timer startPlanTimer(Enum cat) {
return Singletons.require(TimingService.class, NoopTimingService.SERVICE).startSubTimer(cat);
}
static private boolean isFiltered(Throwable t, SSConnection connMgr) {
if (!connMgr.getConnectionContext().hasFilter())
return false;
if (t instanceof SchemaException) {
SchemaException se = (SchemaException) t;
if (se.getErrorInfo().getCode() == AvailableErrors.TABLE_DNE) {
List<UnqualifiedName> names = new ArrayList<UnqualifiedName>();
for(Object o : se.getErrorInfo().getParams()) {
names.add(new UnqualifiedName((String)o));
}
return connMgr.getConnectionContext().isFilteredTable(new QualifiedName(names));
}
}
return false;
}
public static void invalidateCache(CacheInvalidationRecord cir) {
if (cir == null)
return;
Singletons.require(GroupTopicPublisher.class).publish(new CacheInvalidationMessage(cir));
}
private static QueryPlan buildPlan(PlanningResult planningResult,
SSConnection connMgr, SchemaContext sc) throws PEException {
if (planningResult == null) {
return new QueryPlan((ConnectionValuesMap)null);
}
final List<ExecutionPlan> plans = planningResult.getPlans();
final QueryPlan plan = new QueryPlan(planningResult.getValues());
plan.setInputStatement(planningResult.getOriginalSQL());
final ExecutionPlanOptions opts = new ExecutionPlanOptions();
final int lastExecutionPlanIndex = plans.size() - 1;
Long accUpdateCount = null;
for (int epIdx = 0; epIdx <= lastExecutionPlanIndex; ++epIdx) {
final RootExecutionPlan ep = (RootExecutionPlan) plans.get(epIdx);
ep.logPlan(sc, planningResult.getValues(),"on conn " + connMgr.getName(), null);
final List<QueryStepOperation> steps = ep.schedule(opts, connMgr, sc,planningResult.getValues());
final int numQuerySteps = steps.size();
for (int qsIdx = 0; qsIdx < numQuerySteps; ++qsIdx) {
final QueryStepOperation qs = steps.get(qsIdx);
plan.addStep(qs);
}
Long anyUpdate = ep.getUpdateCount(sc,planningResult.getValues().getRootValues());
if (anyUpdate != null)
accUpdateCount = anyUpdate;
if (epIdx == lastExecutionPlanIndex) {
if (ep.useRowCount()) {
plan.setUseRowCount();
}
}
}
plan.setTrueUpdateCount(accUpdateCount);
plan.setRuntimeUpdateCountAdjustment(opts);
// the query plan is created - do any cleanup on the context
sc.cleanupPostPlanning();
return plan;
}
}