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.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.exceptions.PESQLStateException;
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.expression.TableInstance;
import com.tesora.dve.sql.node.expression.TempTableInstance;
import com.tesora.dve.sql.node.structural.FromTableReference;
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.schema.TempTable;
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.SelectStatement;
import com.tesora.dve.sql.statement.dml.UpdateStatement;
import com.tesora.dve.sql.transform.CopyContext;
import com.tesora.dve.sql.transform.CopyVisitor;
import com.tesora.dve.sql.transform.KeyCollector;
import com.tesora.dve.sql.transform.UniqueKeyCollector;
import com.tesora.dve.sql.transform.strategy.PlannerContext;
import com.tesora.dve.sql.transform.strategy.featureplan.FeaturePlanner;
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.featureplan.RedistributionFlags;
import com.tesora.dve.sql.transform.strategy.nested.NestingStrategy.ScalarCheckResult;
import com.tesora.dve.sql.util.ListSet;
public class HandleScalarSubquery extends StrategyFactory {
Boolean hasAllPK = null;
@Override
public NestingStrategy adapt(SchemaContext sc, EdgeTest location,
DMLStatement enclosing, Subquery sq, ExpressionPath path)
throws PEException {
if ((EngineConstant.PROJECTION == location)
|| (EngineConstant.UPDATECLAUSE == location)
|| (EngineConstant.WHERECLAUSE == location)) {
if (computeScalar(sc, sq.getStatement(), location)) {
return new ScalarHandler(sq,path,location,hasAllPK);
}
}
return null;
}
private boolean computeScalar(SchemaContext sc, ProjectingStatement nested, EdgeTest location) throws PESQLStateException {
final ScalarCheckResult scalarCheck = NestingStrategy.hasScalarResult(sc, nested);
if (scalarCheck.isValid()) {
return true;
} else if (!scalarCheck.isValid() && (scalarCheck != ScalarCheckResult.UNKNOWN)) {
if ((scalarCheck == ScalarCheckResult.HAS_TOO_MANY_COLUMNS) && (EngineConstant.UPDATECLAUSE == location)) {
throw new PESQLStateException(1241, "21000", "Operand should contain 1 column(s)");
}
return false;
}
final ListSet<TableKey> nestedTables = EngineConstant.TABLES_INC_NESTED.getValue(nested, sc);
if (nestedTables.size() != 1){
if (EngineConstant.UPDATECLAUSE == location) {
throw new PESQLStateException(1052, "23000", "Column 'id' in field list is ambiguous");
}
return false;
}
hasAllPK = hasAllPK(sc, nested);
if (Boolean.FALSE.equals(hasAllPK)
&& ((EngineConstant.WHERECLAUSE == location)
|| (EngineConstant.UPDATECLAUSE == location))) {
// we can relax the unique check when the uncorrelated subquery is in the where clause
// also we assume that a subquery in the update expression returns a
// single value
return true;
}
return hasAllPK;
}
private boolean hasAllPK(SchemaContext sc, ProjectingStatement nested) {
//ok, now we know we only have one table in the subquery, so check if all its pk is present.
UniqueKeyCollector uniqueCheck = new UniqueKeyCollector(sc,nested);
ListSet<KeyCollector.Part> pkParts = uniqueCheck.getParts();
if (pkParts.isEmpty()){
return false;
}
boolean haveAllParts = true;
for (KeyCollector.Part part : pkParts){
if (!part.isComplete()){
haveAllParts = false;
break;
}
}
return haveAllParts;
}
public HandleScalarSubquery() {
}
protected static class ScalarHandler extends NestingStrategy {
EdgeTest location;
ExpressionNode expr;
ExpressionPath within;
int offset;
Boolean hasPK = null;
public ScalarHandler(Subquery nested, ExpressionPath pathTo, EdgeTest location, Boolean hasPK) {
super(nested, pathTo);
this.location = location;
this.hasPK = hasPK;
}
@Override
public DMLStatement beforeChildPlanning(SchemaContext sc, DMLStatement orig) throws PEException {
if (location != EngineConstant.PROJECTION) {
return null;
}
SelectStatement ss = (SelectStatement) orig;
offset = -1;
expr = null;
int counter = -1;
for(Iterator<ExpressionNode> iter = ss.getProjectionEdge().iterator(); iter.hasNext();) {
counter++;
ExpressionNode en = iter.next();
if (sq.getStatement().ifAncestor(Collections.singleton(en)) != null) {
offset = counter;
expr = en;
break;
}
}
if (offset == -1)
throw new PEException("Cannot find subquery within projection");
within = ExpressionPath.build(sq, expr);
orig.getDerivedInfo().getLocalNestedQueries().remove(sq.getStatement());
return null;
}
@Override
public DMLStatement afterChildPlanning(PlannerContext pc, DMLStatement orig, DMLStatement preRewrites, FeaturePlanner planner, List<FeatureStep> collector) throws PEException {
if ((preRewrites instanceof UpdateStatement)
|| (preRewrites instanceof DeleteStatement)) {
// Scalar subqueries should already be collocated from
// NestedQueryBroadcastTransformFactory applied on UPDATE and
// DELETE statements.
return orig;
}
SelectStatement ss = (SelectStatement) orig;
if (location == EngineConstant.PROJECTION) {
SelectStatement beforeRewrites = (SelectStatement) preRewrites;
int delta = beforeRewrites.getProjectionEdge().size() - ss.getProjectionEdge().size();
// now remove
List<ExpressionNode> proj = new ArrayList<ExpressionNode>(ss.getProjection());
proj.remove(offset - delta);
ss.setProjection(proj);
return ss;
} else {
RedistFeatureStep rfs =
buildChildBCastTempTableStep(pc, (ProjectingFeatureStep)planned,ss.getStorageGroups(pc.getContext()),planner,
new RedistributionFlags().withEnforceScalarValue());
collector.add(rfs);
setStep(rfs);
TempTable ct = rfs.getTargetTempTable();
// now - we originally had a subquery; now we're going to build a new table instance from
// the temp table and replace the subquery with the table instance
TableInstance ti = new TempTableInstance(pc.getContext(),ct);
// and we need a column instance for the scalar value
ColumnInstance ci = new ColumnInstance(ct.getColumns(pc.getContext()).get(0),ti);
SelectStatement origChild = (SelectStatement) pathWithinEnclosing.apply(orig);
// the parent is a subquery node, so we want the parent of the parent
Subquery sq = (Subquery) origChild.getParent();
sq.getParentEdge().set(ci);
// also add the new table to the from clause
ss.getTablesEdge().add(new FromTableReference(ti));
orig.getDerivedInfo().getLocalNestedQueries().remove(origChild);
orig.getDerivedInfo().addLocalTable(ti.getTableKey());
return orig;
}
}
@Override
public FeatureStep afterParentPlanning(PlannerContext pc, FeatureStep parentStep, FeaturePlanner planner,
List<FeatureStep> collector) throws PEException {
SelectStatement stmt = (SelectStatement)planned.getPlannedStatement();
if (NestingStrategy.hasLimitOne(pc.getContext(), stmt))
parentStep.withCachingFlag(false);
if (location != EngineConstant.PROJECTION)
return null;
RedistFeatureStep rfs =
buildChildBCastTempTableStep(pc,(ProjectingFeatureStep)planned,Collections.singletonList(parentStep.getSourceGroup()),
planner,(RedistributionFlags)null);
collector.add(rfs);
TempTable ct = rfs.getTargetTempTable();
// now - we originally had a subquery; now we're going to build a new table instance from
// the temp table and replace the subquery with the table instance
TableInstance ti = new TempTableInstance(pc.getContext(), ct);
ColumnInstance ci = new ColumnInstance(ct.getColumns(pc.getContext()).get(0),ti);
SelectStatement ss = (SelectStatement) parentStep.getPlannedStatement();
List<ExpressionNode> proj = new ArrayList<ExpressionNode>(ss.getProjection());
ExpressionNode projEntryCopy =
CopyVisitor.copy(expr, new CopyContext("HandleProjectionScalarSubquery.afterParentPlanning"));
within.update(projEntryCopy, ci);
proj.add(offset, projEntryCopy);
ss.setProjection(proj);
// renormalize
ss.normalize(pc.getContext());
ss.getTablesEdge().add(new FromTableReference(ti));
ss.getDerivedInfo().addLocalTable(ti.getTableKey());
return parentStep;
}
}
}