package com.tesora.dve.sql.transform.strategy; /* * #%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.HashMap; import java.util.Map; import com.tesora.dve.exceptions.PEException; import com.tesora.dve.sql.SchemaException; import com.tesora.dve.sql.ParserException.Pass; import com.tesora.dve.sql.expression.ExpressionPath; import com.tesora.dve.sql.expression.ExpressionUtils; import com.tesora.dve.sql.expression.RewriteKey; import com.tesora.dve.sql.node.LanguageNode; import com.tesora.dve.sql.node.Traversal; import com.tesora.dve.sql.node.expression.AliasInstance; 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.test.EngineConstant; import com.tesora.dve.sql.schema.SchemaContext; import com.tesora.dve.sql.statement.dml.DMLStatement; import com.tesora.dve.sql.statement.dml.SelectStatement; import com.tesora.dve.sql.transform.AggFunCollector; import com.tesora.dve.sql.transform.CopyVisitor; import com.tesora.dve.sql.transform.behaviors.defaults.DefaultFeaturePlannerFilter; import com.tesora.dve.sql.transform.strategy.featureplan.FeatureStep; import com.tesora.dve.sql.transform.strategy.featureplan.ProjectingFeatureStep; import com.tesora.dve.sql.util.ListOfPairs; import com.tesora.dve.sql.util.ListSet; import com.tesora.dve.sql.util.Pair; /* * Applies if the query has a having clause */ public class HavingRewriteTransformFactory extends TransformFactory { @Override public FeaturePlannerIdentifier getFeaturePlannerID() { return FeaturePlannerIdentifier.HAVING; } private static class HavingState extends Traversal { private ExpressionNode originalHavingClause; private ListOfPairs<ExpressionNode, ExpressionPath> paths; private ListOfPairs<ExpressionNode, Integer> offsets; private ListSet<ExpressionNode> found; private int origProjSize; public HavingState(SchemaContext sc, ExpressionNode orig, SelectStatement ss) { super(Order.PREORDER, ExecStyle.ONCE); originalHavingClause = orig; found = new ListSet<ExpressionNode>(); paths = new ListOfPairs<ExpressionNode, ExpressionPath>(); offsets = new ListOfPairs<ExpressionNode, Integer>(); origProjSize = ss.getProjectionEdge().size(); traverse(orig); Map<RewriteKey,ExpressionNode> projEntryByKey = ExpressionUtils.buildRewriteMap(ss.getProjection()); boolean mustNormalize = false; for(ExpressionNode en : found) { ExpressionNode actual = null; boolean isAlias = false; if (en instanceof AliasInstance) { AliasInstance ai = (AliasInstance) en; actual = ai.getTarget(); isAlias = true; } else { actual = en; } ExpressionPath path = ExpressionPath.build(en, originalHavingClause); RewriteKey rk = actual.getRewriteKey(); int offset = -1; ExpressionNode inplace = projEntryByKey.get(rk); if (inplace == null) { // must be added if (isAlias) throw new SchemaException(Pass.PLANNER, "Unable to find alias within projection"); offset = ss.getProjectionEdge().size(); ss.getProjectionEdge().add((ExpressionNode)en.copy(null)); mustNormalize = true; } else { for(int i = 0; i < ss.getProjectionEdge().size(); i++) { ExpressionAlias pen = (ExpressionAlias) ss.getProjectionEdge().get(i); if (pen.getRewriteKey().equals(rk)) { offset = i; break; } else if (pen.getTarget().getRewriteKey().equals(rk)) { offset = i; break; } } if (offset == -1) throw new SchemaException(Pass.PLANNER, "Unable to find offset within projection"); } paths.add(en,path); offsets.add(en,offset); } if (mustNormalize) ss.normalize(sc); } @Override public LanguageNode action(LanguageNode in) { if (EngineConstant.AGGFUN.has(in) || EngineConstant.COLUMN.has(in) || EngineConstant.ALIAS_INSTANCE.has(in)) { // don't add if any parent is already in the added (recall this is preorder) boolean any = false; for(LanguageNode n : getPath()) { if (found.contains(n)) { any = true; break; } } if (!any) found.add((ExpressionNode)in); } return in; } public void adjust(SelectStatement ss) { // so, first pull off anything that was add by us - that by definition is everything after the // original proj offset HashMap<ExpressionNode, ExpressionNode> byOrig = new HashMap<ExpressionNode,ExpressionNode>(); // any synthetic entries we're going to remove. for(Pair<ExpressionNode, Integer> po : offsets) { Integer off = po.getSecond(); ExpressionAlias ith = (ExpressionAlias) ss.getProjectionEdge().get(off.intValue()); if (off.intValue() >= origProjSize) { byOrig.put(po.getFirst(), ith.getTarget()); } else { byOrig.put(po.getFirst(), ith.buildAliasInstance()); } } // truncate the projection while(ss.getProjectionEdge().size() > origProjSize) ss.getProjectionEdge().remove(ss.getProjectionEdge().size() - 1); // the having clause might have used aliases, so we're going to modify the original for(Pair<ExpressionNode, ExpressionPath> pa : paths) { ExpressionNode updated = byOrig.get(pa.getFirst()); ExpressionPath path = pa.getSecond(); path.update(originalHavingClause, updated); } ss.setHavingExpression(originalHavingClause); } } private static class AliasExpander extends Traversal { public AliasExpander() { super(Order.POSTORDER, ExecStyle.ONCE); } @Override public LanguageNode action(LanguageNode in) { if (EngineConstant.ALIAS_INSTANCE.has(in)) { AliasInstance ai = (AliasInstance) in; ExpressionAlias targ = ai.getTarget(); return (LanguageNode) targ.getTarget().copy(null); } return in; } } @Override public FeatureStep plan(DMLStatement statement, PlannerContext ipc) throws PEException { if (!EngineConstant.HAVINGCLAUSE.has(statement)) return null; PlannerContext context = ipc.withTransform(getFeaturePlannerID()); SelectStatement in = (SelectStatement) statement; SelectStatement copy = (SelectStatement)CopyVisitor.copy(in); // if this is a having clause with no agg funs in the projection or in the having expression, // convert to a where clause. ListSet<FunctionCall> projAggFun = EngineConstant.PROJ_AGGREGATE_FUNCTIONS.getValue(copy,context.getContext()); ListSet<FunctionCall> havingAggFuns = AggFunCollector.collectAggFuns(copy.getHavingExpression()); if ((projAggFun == null || projAggFun.isEmpty()) && (havingAggFuns == null || havingAggFuns.isEmpty())) { // where clause case // decompose the where clause, add the having clause as a clause ListSet<ExpressionNode> parts = ExpressionUtils.decomposeAndClause(copy.getWhereClause()); ExpressionNode hc = copy.getHavingExpression(); hc = (ExpressionNode) new AliasExpander().traverse(hc); parts.add(copy.getHavingExpression()); copy.getHavingEdge().clear(); if (parts.size() < 2) copy.setWhereClause(parts.get(0)); else copy.setWhereClause(ExpressionUtils.buildAnd(parts)); // plan the child as normal return buildPlan(copy, context.withTransform(getFeaturePlannerID()), DefaultFeaturePlannerFilter.INSTANCE); } // normal case HavingState hs = new HavingState(context.getContext(),copy.getHavingExpression(), copy); copy.getHavingEdge().clear(); ProjectingFeatureStep childPlan = (ProjectingFeatureStep) buildPlan(copy,context.withTransform(getFeaturePlannerID()),DefaultFeaturePlannerFilter.INSTANCE); SelectStatement ss = (SelectStatement) childPlan.getPlannedStatement(); hs.adjust(ss); return childPlan; } }