package com.tesora.dve.sql.transform.strategy.nested;
/*
* #%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.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.sql.expression.ExpressionPath;
import com.tesora.dve.sql.node.EdgeName;
import com.tesora.dve.sql.node.expression.Subquery;
import com.tesora.dve.sql.node.test.EdgeTest;
import com.tesora.dve.sql.node.test.EngineConstant;
import com.tesora.dve.sql.schema.SchemaContext;
import com.tesora.dve.sql.statement.dml.DMLStatement;
import com.tesora.dve.sql.statement.dml.DeleteStatement;
import com.tesora.dve.sql.statement.dml.ProjectingStatement;
import com.tesora.dve.sql.statement.dml.UpdateStatement;
import com.tesora.dve.sql.transform.CopyVisitor;
import com.tesora.dve.sql.transform.behaviors.ComplexFeaturePlannerFilter;
import com.tesora.dve.sql.transform.behaviors.defaults.DefaultFeaturePlannerFilter;
import com.tesora.dve.sql.transform.strategy.FeaturePlannerIdentifier;
import com.tesora.dve.sql.transform.strategy.PlannerContext;
import com.tesora.dve.sql.transform.strategy.SingleSiteStorageGroupTransformFactory;
import com.tesora.dve.sql.transform.strategy.TransformFactory;
import com.tesora.dve.sql.transform.strategy.featureplan.FeatureStep;
import com.tesora.dve.sql.util.ListSet;
/*
* Handles uncorrelated subqueries on the projection, from or where clauses, or in update expressions.
* The general strategy is to either rewrite the subquery as a join and plan the result, or else
* to convert the subquery to a temp table, paste back into the parent query, and plan that.
*/
public class NestedQueryRewriteTransformFactory extends TransformFactory {
private static final EdgeTest[] supportedLocations =
new EdgeTest[] { EngineConstant.FROMCLAUSE, EngineConstant.UPDATECLAUSE, EngineConstant.WHERECLAUSE, EngineConstant.PROJECTION };
private static final StrategyFactory[] strategies = new StrategyFactory[] {
new HandleWhereClauseSubqueryAsJoin(),
new HandleFromClauseSubquery(),
new HandleScalarSubquery()
};
private static boolean applies(SchemaContext sc, DMLStatement stmt) throws PEException {
if ((stmt instanceof ProjectingStatement)
|| (stmt instanceof UpdateStatement)
|| (stmt instanceof DeleteStatement)) {
// nested queries are ok if they are on a single site - just push
// down then
if (SingleSiteStorageGroupTransformFactory.isSingleSite(sc, stmt, true))
return false;
ListSet<ProjectingStatement> any = EngineConstant.NESTED.getValue(stmt, sc);
if (any.isEmpty() || (any.size() == 1 && any.get(0) == stmt))
return false;// no subqueries provided.
// statement has one or more nested queries that can't be pushed
// down as is, choose a strategy to handle it, but only if the query
// is not correlated.
boolean nc = false;
for (ProjectingStatement ps : any) {
if (ps.getDerivedInfo().getCorrelatedColumns().isEmpty()) {
nc = true;
break;
}
}
return nc;
}
return false;
}
// the role of this method is not only to find queries that this instance of this transform can handle, but to
// figure how they can be handled
private void classifySubqueries(SchemaContext sc, DMLStatement in, ListSet<NestingStrategy> subs) throws PEException {
ListSet<ProjectingStatement> nested = EngineConstant.NESTED.getValue(in,sc);
// first pass, determine ancestry chains
LinkedHashMap<ProjectingStatement, DMLStatement> ancestry = new LinkedHashMap<ProjectingStatement, DMLStatement>();
for(ProjectingStatement ss : nested) {
DMLStatement firstAncestor = ss.getParent().getEnclosing(DMLStatement.class, null);
if (firstAncestor == null) continue;
ancestry.put(ss,firstAncestor);
}
// now, we only want to look at those queries for which firstAncestor==in
for (Map.Entry<ProjectingStatement, DMLStatement> me : ancestry.entrySet()) {
if (me.getValue() != in) continue; // the ancestor of this select is not the stmt we were given
ProjectingStatement ns = me.getKey();
if (!ns.getDerivedInfo().getCorrelatedColumns().isEmpty()) continue;
NestingStrategy adapted = null;
String message = null;
if (ns.getParentEdge().getName().matches(EdgeName.SUBQUERY)) {
ExpressionPath ep = ExpressionPath.build(ns, in);
EdgeTest locet = null;
for(EdgeTest et : supportedLocations) {
if (ep.has(et)) {
locet = et;
break;
}
}
if (locet == null) {
throw new PEException("Unable to classify subquery " + ns + " of statement " + in);
}
for(StrategyFactory sf : strategies) {
adapted = sf.adapt(sc,locet, in, (Subquery)ns.getParent(), ep);
if (adapted != null)
break;
}
if (adapted == null)
message = "No nesting handler for subquery ";
}
if (message == null && adapted == null)
message = "No support for subquery ";
if (message != null)
throw new PEException(message + ns.getSQL(sc) + " used in query " + in.getSQL(sc));
else
subs.add(adapted);
}
}
@Override
public FeaturePlannerIdentifier getFeaturePlannerID() {
return FeaturePlannerIdentifier.NESTED_QUERY;
}
@Override
public FeatureStep plan(DMLStatement stmt, PlannerContext incontext)
throws PEException {
if (!applies(incontext.getContext(),stmt))
return null;
PlannerContext context = incontext.withTransform(getFeaturePlannerID());
final SchemaContext sc = context.getContext();
DMLStatement copy = (DMLStatement) CopyVisitor.copy(stmt);
ListSet<NestingStrategy> handlers = new ListSet<NestingStrategy>();
// change of plans, so to speak
// we should:
// [1] classify - but only do preplan rewrites at this point
// [2] execute preplan rewrites - go back to [1]
// [3] if no remaining rewrites plan with no further rewrites
// [4] remaining rewrites - mod parent as necessary
// [5] plan children as necessary
// [6] plan parent if necessary
// [7] fold in children if necessary
// [8] replan parent if necessary
boolean modified = false;
do {
modified = false;
classifySubqueries(sc, copy, handlers);
for(NestingStrategy ns : handlers) {
String beforeHand = null, afterwards = null;
if (emitting())
beforeHand = copy.getSQL(sc);
DMLStatement out = (DMLStatement) ns.beforeChildPlanning(sc,copy);
if (emitting())
afterwards = copy.getSQL(sc);
if (out != null) {
if (emitting()) {
emit("pre before child planning: " + beforeHand);
emit("post before child planning: " + afterwards);
}
copy = out;
copy.getBlock().clear();
modified = true;
break;
}
}
if (modified == true)
// run the whole analysis again
handlers.clear();
} while(modified == true);
if (handlers.isEmpty()) {
return buildPlan(copy,context,DefaultFeaturePlannerFilter.INSTANCE);
}
PlannerContext childContext = context.withCosting();
// for everything that's left, plan it separately
for(NestingStrategy ns : handlers) {
FeatureStep fs = buildPlan(ns.getSubquery().getStatement(),childContext,
new ComplexFeaturePlannerFilter(Collections.<FeaturePlannerIdentifier> emptySet(), Collections.singleton(getFeaturePlannerID())));
ns.setStep(fs);
}
DMLStatement nss = CopyVisitor.copy(copy);
if (emitting())
emit("pre paste: " + nss.getSQL(sc));
ListSet<FeatureStep> childSteps = new ListSet<FeatureStep>();
for(NestingStrategy ns : handlers) {
DMLStatement mod = ns.afterChildPlanning(context,nss, copy, this, childSteps);
if (mod != null) nss = mod;
if (emitting())
emit("post paste: " + nss.getSQL(sc));
}
// now that we have an adjusted select statement, plan that as normal
FeatureStep current = buildPlan(nss,context,DefaultFeaturePlannerFilter.INSTANCE);
// we have to rewrite the children to add anything that the handlers did.
for(NestingStrategy ns : handlers) {
FeatureStep sub = ns.afterParentPlanning(context,current,this, childSteps);
if (sub != null) current = sub;
}
current.prefixChildren(childSteps);
return current;
}
}