package com.tesora.dve.sql.transform;
/*
* #%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 com.tesora.dve.sql.SchemaException;
import com.tesora.dve.sql.node.LanguageNode;
import com.tesora.dve.sql.node.Traversal;
import com.tesora.dve.sql.node.expression.ColumnInstance;
import com.tesora.dve.sql.node.expression.ExpressionAlias;
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.expression.TableInstance;
import com.tesora.dve.sql.node.expression.Wildcard;
import com.tesora.dve.sql.node.structural.LimitSpecification;
import com.tesora.dve.sql.node.test.EngineConstant;
import com.tesora.dve.sql.schema.PEKey;
import com.tesora.dve.sql.schema.PETable;
import com.tesora.dve.sql.schema.SchemaContext;
import com.tesora.dve.sql.statement.Statement;
import com.tesora.dve.sql.statement.dml.ProjectingStatement;
import com.tesora.dve.sql.statement.dml.SelectStatement;
import com.tesora.dve.sql.statement.dml.UnionStatement;
import com.tesora.dve.sql.util.ListSet;
import com.tesora.dve.variables.KnownVariables;
public class PrePlanner {
public static Statement transform(SchemaContext sc, Statement in) throws SchemaException {
if (in == null) return in;
Statement c = in;
if (EngineConstant.WHERECLAUSE.has(c)) {
// in the where clause, convert 1 = a to a = 1
// also restructure and/or to be broader
new FilterOrder().traverse(EngineConstant.WHERECLAUSE.getEdge(c));
}
c = maybeRewriteNested(sc, c);
c = maybeAddLimitClause(sc, c);
return c;
}
private static Statement maybeAddLimitClause(SchemaContext sc, Statement c) {
if (c instanceof SelectStatement) {
SelectStatement ss = (SelectStatement) c;
if (ss.getLimitEdge().has())
return c;
Long limitByOtherMeans =
KnownVariables.SELECT_LIMIT.getValue(sc.getConnection().getVariableSource());
if (limitByOtherMeans == null)
return c;
ss.setLimit(new LimitSpecification(LiteralExpression.makeLongLiteral(limitByOtherMeans.longValue()),
LiteralExpression.makeAutoIncrLiteral(0L)));
}
return c;
}
// rewrite select count(*) as expression from (select 1 as expression from .... ) subquery
// to be select count(*) as expression from .....
private static Statement maybeRewriteNested(SchemaContext sc, Statement c) {
if (!(c instanceof SelectStatement)) return c;
ListSet<ProjectingStatement> chilluns = EngineConstant.NESTED.getValue(c,sc);
if (chilluns == null || chilluns.isEmpty() || chilluns.size() > 1) return c;
SelectStatement outer = (SelectStatement) c;
if (outer.getWhereClause() != null || outer.getLimit() != null || outer.getOrderBysEdge().has() || outer.getGroupBysEdge().has())
return c;
ProjectingStatement ps = chilluns.get(0);
if (ps instanceof UnionStatement) return c;
SelectStatement inner = (SelectStatement) ps;
if (outer.getProjectionEdge().size() == 1 && inner.getProjectionEdge().size() == 1) {
ExpressionNode innerProj = inner.getProjectionEdge().get(0);
ExpressionNode outerProj = outer.getProjectionEdge().get(0);
ExpressionNode ip = innerProj;
ExpressionNode op = outerProj;
if (op instanceof ExpressionAlias)
op = ((ExpressionAlias)op).getTarget();
if (ip instanceof ExpressionAlias)
ip = ((ExpressionAlias)ip).getTarget();
if (ip instanceof LiteralExpression && EngineConstant.FUNCTION.has(op, EngineConstant.COUNT)) {
FunctionCall fcp = (FunctionCall) op;
if (fcp.getParametersEdge().size() == 1 && fcp.getParametersEdge().get(0) instanceof Wildcard) {
// we're just going to replace the literal with the funcall by replacing ip with op and returning the
// inner select, but we have to preserve the aliases, so let's swap the eas
innerProj.getParentEdge().set(outerProj);
// disconnect inner from the parent - otherwise we'll match nested again
inner.setParent(null);
inner.setExplain(outer.getExplain());
// additionally, we're going to replace count(*) with count(pk), or at least the first
// column of the pk
TableInstance firstTable = inner.getTablesEdge().get(0).getBaseTable();
if (firstTable != null && firstTable.getAbstractTable().isTable()) {
PETable pet = firstTable.getAbstractTable().asTable();
PEKey k = pet.getPrimaryKey(sc);
if (k != null) {
ColumnInstance nci = new ColumnInstance(k.getColumns(sc).get(0),firstTable);
fcp.getParametersEdge().set(nci);
}
}
return inner;
}
}
}
return c;
}
private static class FilterOrder extends Traversal {
public FilterOrder() {
super(Order.POSTORDER, ExecStyle.ONCE);
}
@Override
public LanguageNode action(LanguageNode in) {
if (EngineConstant.FUNCTION.has(in, EngineConstant.EQUALS)) {
FunctionCall fc = (FunctionCall) in;
if (EngineConstant.CONSTANT.has(fc.getParametersEdge().get(0)) &&
EngineConstant.COLUMN.has(fc.getParametersEdge().get(1))) {
ExpressionNode lhs = fc.getParametersEdge().get(0);
ExpressionNode rhs = fc.getParametersEdge().get(1);
fc.getParametersEdge().getEdge(1).set(lhs);
fc.getParametersEdge().getEdge(0).set(rhs);
}
}
return in;
}
}
}