package com.tesora.dve.sql.transform.strategy.joinsimplification;
/*
* #%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.exceptions.PEException;
import com.tesora.dve.sql.expression.ColumnKey;
import com.tesora.dve.sql.expression.ExpressionUtils;
import com.tesora.dve.sql.expression.RewriteKey;
import com.tesora.dve.sql.expression.TableKey;
import com.tesora.dve.sql.jg.DPart;
import com.tesora.dve.sql.jg.UncollapsedJoinGraph;
import com.tesora.dve.sql.node.Edge;
import com.tesora.dve.sql.node.LanguageNode;
import com.tesora.dve.sql.node.expression.ColumnInstance;
import com.tesora.dve.sql.node.expression.ExpressionNode;
import com.tesora.dve.sql.node.expression.FunctionCall;
import com.tesora.dve.sql.node.expression.LiteralExpression;
import com.tesora.dve.sql.node.test.EngineConstant;
import com.tesora.dve.sql.schema.FunctionName;
import com.tesora.dve.sql.schema.SchemaContext;
import com.tesora.dve.sql.statement.dml.DMLStatement;
import com.tesora.dve.sql.statement.dml.MultiTableDMLStatement;
import com.tesora.dve.sql.transform.CopyContext;
import com.tesora.dve.sql.transform.FunCollector;
import com.tesora.dve.sql.util.ListSet;
/*
* For a filter such as
* where p.a = 1 and p.a = q.a
* we transform this to
* where p.a = 1 and q.a = 1
*/
public class ConstantFoldingSimplifier extends Simplifier {
@Override
public boolean applies(SchemaContext sc, DMLStatement dmls) throws PEException {
return dmls instanceof MultiTableDMLStatement && EngineConstant.WHERECLAUSE.has(dmls);
}
@Override
public DMLStatement simplify(SchemaContext sc, DMLStatement in, JoinSimplificationTransformFactory parent) throws PEException {
String before = (parent.emitting() ? in.getSQL(sc) : null);
CopyContext cc = in.getMapper().getCopyContext();
ExpressionNode wc = (ExpressionNode) EngineConstant.WHERECLAUSE.get(in);
List<ExpressionNode> andClauses = ExpressionUtils.decomposeAndClause(wc);
// so for each and clause in turn, look for eq funs; also or clauses
// build up a map of column=constant and column=column, and note column=column
// at the end see if there column=constant for one side of those
// if we find an or clause in one of the branches, ignore the whole branch.
MultiMap<ColumnKey,RewriteKey> matching = new MultiMap<ColumnKey,RewriteKey>();
List<FunctionCall> eqjs = new ArrayList<FunctionCall>();
for(ExpressionNode c : andClauses) {
ListSet<FunctionCall> calls = FunCollector.collectFuns(c);
if (calls == null || calls.isEmpty()) continue;
boolean bad = false;
for(LanguageNode ln : calls) {
FunctionCall fc = (FunctionCall) ln;
if (fc.getFunctionName().isOr() || fc.getFunctionName().isNot()) {
bad = true;
break;
}
}
if (bad) continue;
for(LanguageNode ln : calls) {
FunctionCall fc = (FunctionCall) ln;
if (fc.getFunctionName().isEquals()) {
ExpressionNode lhs = fc.getParametersEdge().get(0);
ExpressionNode rhs = fc.getParametersEdge().get(1);
ColumnKey lk = null;
if (lhs instanceof ColumnInstance) {
ColumnInstance ci = (ColumnInstance) lhs;
lk = ci.getColumnKey();
}
if (lk != null) {
RewriteKey rk = null;
if (rhs instanceof ColumnInstance) {
rk = rhs.getRewriteKey();
eqjs.add(fc);
matching.put(lk,rk);
matching.put((ColumnKey)rk, lk);
} else if (rhs instanceof LiteralExpression) {
rk = rhs.getRewriteKey();
matching.put(lk,rk);
}
}
}
}
}
if (eqjs.isEmpty())
return null;
// we have equijoins in the where clause, go figure out the raw joins
UncollapsedJoinGraph ujg = new UncollapsedJoinGraph(sc,(MultiTableDMLStatement) in);
boolean any = false;
for(FunctionCall fc : eqjs) {
ColumnInstance rc = (ColumnInstance) fc.getParametersEdge().get(1);
// figure out if the rhs is mapped to something else
ColumnKey lookup = rc.getColumnKey();
Collection<RewriteKey> sub = matching.get(lookup);
if (sub == null || sub.isEmpty())
continue;
// look for a constant
for(RewriteKey rk : sub) {
if (rk instanceof ColumnKey) continue;
// at this point we know we could possibly rewrite the expression - so let's make sure we actually can
// we can if for both sides removing the edge that this eq embodies doesn't completely disconnect the partition
// from the graph. if it would completely disconnect the partition - then we will replace p.a = q.a with
// p.a = <constant> and p.a = q.a
// this should produce better partition queries
ColumnInstance lc = (ColumnInstance) fc.getParametersEdge().get(0);
TableKey ltk = lc.getTableInstance().getTableKey();
TableKey rtk = rc.getTableInstance().getTableKey();
DPart lpart = ujg.getPartitionFor(ltk);
DPart rpart = ujg.getPartitionFor(rtk);
boolean additive = (lpart.getEdges().size() == 1 || rpart.getEdges().size() == 1);
FunctionCall fcopy = null;
if (additive)
fcopy = (FunctionCall)fc.copy(cc);
ExpressionNode orig = (ExpressionNode) rk.toInstance();
// make a copy of the node
ExpressionNode copy = (ExpressionNode) orig.copy(cc);
// replace
rc.getParentEdge().set(copy);
if (additive) {
Edge<?, LanguageNode> parentEdge = fc.getParentEdge();
FunctionCall both = new FunctionCall(FunctionName.makeAnd(),fcopy,fc);
parentEdge.set(both);
}
any = true;
fc.getBlock().clear();
}
}
if (any) {
if (parent.emitting()) {
parent.emit(" CF in: " + before);
parent.emit(" CF out: " + in.getSQL(sc));
}
wc.getBlock().clear();
return in;
}
return null;
}
}