package com.tesora.dve.sql.transform.strategy.correlated;
/*
* #%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.ArrayList;
import java.util.Collection;
import java.util.List;
import com.tesora.dve.common.MultiMap;
import com.tesora.dve.common.TreeMapFactory;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.sql.expression.ColumnKey;
import com.tesora.dve.sql.expression.ExpressionPath;
import com.tesora.dve.sql.expression.TableKey;
import com.tesora.dve.sql.node.expression.ColumnInstance;
import com.tesora.dve.sql.node.expression.ExpressionNode;
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.DistributionVector.Model;
import com.tesora.dve.sql.schema.PEStorageGroup;
import com.tesora.dve.sql.schema.SchemaContext;
import com.tesora.dve.sql.schema.TempTable;
import com.tesora.dve.sql.schema.TempTableCreateOptions;
import com.tesora.dve.sql.statement.dml.DMLStatement;
import com.tesora.dve.sql.statement.dml.ProjectingStatement;
import com.tesora.dve.sql.statement.dml.SelectStatement;
import com.tesora.dve.sql.transform.ColumnInstanceCollector;
import com.tesora.dve.sql.transform.CopyVisitor;
import com.tesora.dve.sql.transform.behaviors.defaults.DefaultFeaturePlannerFilter;
import com.tesora.dve.sql.transform.execution.DMLExplainReason;
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.transform.strategy.featureplan.ProjectingFeatureStep;
import com.tesora.dve.sql.transform.strategy.featureplan.RedistFeatureStep;
import com.tesora.dve.sql.transform.strategy.nested.NestingStrategy;
import com.tesora.dve.sql.transform.strategy.nested.NestingStrategy.ScalarCheckResult;
import com.tesora.dve.sql.util.ListSet;
abstract class CorrelatedSubqueryTransformFactory extends TransformFactory {
private static final EdgeTest[] supported =
new EdgeTest[] { EngineConstant.WHERECLAUSE, EngineConstant.PROJECTION };
protected abstract EdgeTest getMatchLocation();
protected abstract FeatureStep buildSteps(PlannerContext pc,List<CorrelatedSubquery> subs,
SelectStatement origQuery,SelectStatement workingCopy) throws PEException;
protected abstract CorrelatedSubquery buildSubquery(SchemaContext sc, ProjectingStatement subq,
SelectStatement enclosing,
ListSet<ColumnKey> outerColumns)
throws PEException;
protected boolean applies(SchemaContext sc, final DMLStatement stmt) throws PEException {
if (SingleSiteStorageGroupTransformFactory.isSingleSite(sc, stmt,true)) return false;
// we apply when there are nested queries and they have tables from the parent
final ListSet<ProjectingStatement> correlated = new ListSet<ProjectingStatement>();
findCorrelated(sc, stmt, new SubqueryCollector() {
@Override
public void onCorrelatedSubquery(SchemaContext sc, ProjectingStatement subq,
ListSet<ColumnKey> outerColumns,
ListSet<ColumnKey> innerColumns) throws PEException {
ExpressionPath ep = ExpressionPath.build(subq,stmt);
// we only support scalar results right now
final ScalarCheckResult scalarCheck = NestingStrategy.hasScalarResult(sc, subq);
if (!innerColumns.isEmpty() && !scalarCheck.isValid())
throw new PEException("Unsupported correlated subquery - result not scalar (" + scalarCheck.getDescribtion() + ")");
EdgeTest which = null;
for(EdgeTest et : supported)
if (ep.has(et)) {
which = et;
break;
}
if (which == null)
throw new PEException("Unsupported correlated subquery - unknown location");
if (which == getMatchLocation())
correlated.add(subq);
}
});
return !correlated.isEmpty();
}
@Override
public FeatureStep plan(DMLStatement stmt, PlannerContext context)
throws PEException {
if (!applies(context.getContext(),stmt))
return null;
return buildStep(context,stmt);
}
protected static void findCorrelated(SchemaContext sc, DMLStatement stmt, SubqueryCollector coll) throws PEException {
ListSet<ProjectingStatement> nested = stmt.getDerivedInfo().getLocalNestedQueries();
ListSet<TableKey> enclosingTabs = EngineConstant.TABLES.getValue(stmt, sc);
for(ProjectingStatement ps : nested) {
ProjectingStatement parent = ps.getParent().getEnclosing(ProjectingStatement.class, null);
// one level at a time
if (parent != stmt) continue;
ListSet<ColumnKey> correlatedOuterColumns = new ListSet<ColumnKey>();
ListSet<ColumnKey> innerColumns = new ListSet<ColumnKey>();
for(ColumnInstance ci : ColumnInstanceCollector.getColumnInstances(ps)) {
ColumnKey ck = ci.getColumnKey();
if (enclosingTabs.contains(ck.getTableKey())) {
correlatedOuterColumns.add(ck);
} else {
innerColumns.add(ck);
}
}
if (correlatedOuterColumns.isEmpty())
continue;
coll.onCorrelatedSubquery(sc, ps, correlatedOuterColumns,innerColumns);
}
}
interface SubqueryCollector {
// return true if we're done
public void onCorrelatedSubquery(SchemaContext sc, ProjectingStatement subq, ListSet<ColumnKey> outerColumns, ListSet<ColumnKey> innerColumns) throws PEException;
}
private boolean orderQueries(SchemaContext sc, final SelectStatement copy, final MultiMap<Integer,CorrelatedSubquery> corsubs,
final ListSet<ProjectingStatement> notActuallyCorrelated) throws PEException {
findCorrelated(sc, copy, new SubqueryCollector() {
@Override
public void onCorrelatedSubquery(SchemaContext sc, ProjectingStatement subq,
ListSet<ColumnKey> outerColumns,
ListSet<ColumnKey> innerColumns) throws PEException {
if (innerColumns.isEmpty()) {
notActuallyCorrelated.add(subq);
return;
}
final ScalarCheckResult scalarCheck = NestingStrategy.hasScalarResult(sc, subq);
if (!scalarCheck.isValid())
throw new PEException("Unsupported correlated subquery - result not scalar (" + scalarCheck.getDescribtion() + ")");
ExpressionPath ep = ExpressionPath.build(subq,copy);
if (ep.has(getMatchLocation())) {
CorrelatedSubquery csq = buildSubquery(sc,subq,copy,outerColumns);
corsubs.put(csq.getOffset(), csq);
}
}
});
boolean any = false;
for(CorrelatedSubquery c : corsubs.values()) {
if (c.simplify(sc,this))
any = true;
}
return any;
}
protected FeatureStep buildStep(PlannerContext ipc, DMLStatement stmt) throws PEException {
SelectStatement ss = (SelectStatement) stmt;
final SelectStatement copy = (SelectStatement) CopyVisitor.copy(stmt);
PlannerContext pc = ipc.withTransform(getFeaturePlannerID());
if (emitting())
emit("in " + copy.getSQL(pc.getContext(), " "));
ListSet<ProjectingStatement> notActuallyCorrelated = new ListSet<ProjectingStatement>();
MultiMap<Integer,CorrelatedSubquery> corsubs =
new MultiMap<Integer, CorrelatedSubquery>(new TreeMapFactory<Integer, Collection<CorrelatedSubquery>>());
while(orderQueries(pc.getContext(), copy, corsubs, notActuallyCorrelated)) {
corsubs.clear();
notActuallyCorrelated.clear();
}
// if not actually correlated, rewrite in the usual way
for(ProjectingStatement ps : notActuallyCorrelated) {
if (ps.getProjections().get(0).size() > 1)
throw new PEException("Invalid noncorrelated subquery - returns more than one column");
Subquery sq = ps.getParent().getEnclosing(Subquery.class, ProjectingStatement.class);
if (sq == null)
throw new PEException("Malformed statement - nested query not a subquery");
sq.getParentEdge().set(ps.getProjections().get(0).get(0));
}
// build the ordered list of correlated subqueries
ListSet<CorrelatedSubquery> subq = new ListSet<CorrelatedSubquery>();
for(Integer i : corsubs.keySet()) {
Collection<CorrelatedSubquery> csubqs = corsubs.get(i);
subq.addAll(csubqs);
}
if (!subq.isEmpty()) {
PlannerContext subcontext = pc.withCosting();
return buildSteps(subcontext,subq,ss,copy);
} else {
return buildPlan(copy,pc,DefaultFeaturePlannerFilter.INSTANCE);
}
}
protected RedistFeatureStep redistToAggSite(PlannerContext pc, ProjectingFeatureStep srcStep) throws PEException {
return srcStep.redist(pc, this,
new TempTableCreateOptions(Model.STATIC,pc.getTempGroupManager().getGroup(true))
.withRowCount(srcStep.getCost().getRowCount()),
null,
null);
}
protected RedistFeatureStep buildLookupTable(PlannerContext pc, RedistFeatureStep srcTab,
ListSet<ColumnKey> allOuterColumns, PEStorageGroup group)
throws PEException {
ProjectingFeatureStep pfs = srcTab.buildNewProjectingStep(pc, this, null, null);
SelectStatement q = (SelectStatement) pfs.getPlannedStatement();
List<ExpressionNode> proj = new ArrayList<ExpressionNode>();
for(ColumnKey ck : allOuterColumns) {
ColumnKey tck = q.getMapper().copyColumnKeyForward(ck);
proj.add(tck.toInstance());
}
q.setProjection(proj);
q.normalize(pc.getContext());
RedistFeatureStep out =
pfs.redist(pc, this,
new TempTableCreateOptions(Model.BROADCAST,group)
.withRowCount(srcTab.getTargetTempTable().getTableSizeEstimate(pc.getContext())),
null,
DMLExplainReason.CORRELATED_SUBQUERY_LOOKUP_TABLE.makeRecord());
TempTable tt = out.getTargetTempTable();
tt.noteJoinedColumns(pc.getContext(), tt.getColumns(pc.getContext()));
return out;
}
}