/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.query.optimizer.relational.rules; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.teiid.api.exception.query.QueryMetadataException; import org.teiid.api.exception.query.QueryPlannerException; import org.teiid.client.plan.Annotation; import org.teiid.client.plan.Annotation.Priority; import org.teiid.core.TeiidComponentException; import org.teiid.core.id.IDGenerator; import org.teiid.query.analysis.AnalysisRecord; import org.teiid.query.metadata.QueryMetadataInterface; import org.teiid.query.metadata.SupportConstants; import org.teiid.query.optimizer.QueryOptimizer; import org.teiid.query.optimizer.capabilities.CapabilitiesFinder; import org.teiid.query.optimizer.relational.OptimizerRule; import org.teiid.query.optimizer.relational.RuleStack; import org.teiid.query.optimizer.relational.plantree.NodeConstants; import org.teiid.query.optimizer.relational.plantree.NodeEditor; import org.teiid.query.optimizer.relational.plantree.NodeFactory; import org.teiid.query.optimizer.relational.plantree.PlanNode; import org.teiid.query.processor.relational.JoinNode.JoinStrategyType; import org.teiid.query.processor.relational.MergeJoinStrategy.SortOption; import org.teiid.query.processor.relational.RelationalPlan; import org.teiid.query.resolver.util.ResolverUtil; import org.teiid.query.rewriter.QueryRewriter; import org.teiid.query.sql.LanguageObject; import org.teiid.query.sql.lang.*; import org.teiid.query.sql.navigator.DeepPostOrderNavigator; import org.teiid.query.sql.symbol.AggregateSymbol; import org.teiid.query.sql.symbol.AggregateSymbol.Type; import org.teiid.query.sql.symbol.Constant; import org.teiid.query.sql.symbol.ElementSymbol; import org.teiid.query.sql.symbol.Expression; import org.teiid.query.sql.symbol.GroupSymbol; import org.teiid.query.sql.symbol.Reference; import org.teiid.query.sql.symbol.ScalarSubquery; import org.teiid.query.sql.util.SymbolMap; import org.teiid.query.sql.visitor.AggregateSymbolCollectorVisitor; import org.teiid.query.sql.visitor.ExpressionMappingVisitor; import org.teiid.query.sql.visitor.GroupsUsedByElementsVisitor; import org.teiid.query.sql.visitor.ReferenceCollectorVisitor; import org.teiid.query.util.CommandContext; public final class RuleMergeCriteria implements OptimizerRule { /** * Used to replace correlated references */ public static final class ReferenceReplacementVisitor extends ExpressionMappingVisitor { private final SymbolMap refs; private boolean replacedAny; public ReferenceReplacementVisitor(SymbolMap refs) { super(null); this.refs = refs; } public Expression replaceExpression(Expression element) { if (element instanceof Reference) { Reference r = (Reference)element; Expression ex = refs.getMappedExpression(r.getExpression()); if (ex != null) { if (ex instanceof ElementSymbol) { ElementSymbol es = (ElementSymbol) ex.clone(); es.setIsExternalReference(false); ex = es; } replacedAny = true; return ex; } } return element; } } public static class PlannedResult { public List leftExpressions = new LinkedList(); public List rightExpressions = new LinkedList(); public Query query; public boolean not; public List<Criteria> nonEquiJoinCriteria = new LinkedList<Criteria>(); public Criteria additionalCritieria; public Class<?> type; public boolean mergeJoin; public boolean madeDistinct; public boolean makeInd; public void reset() { this.leftExpressions.clear(); this.rightExpressions.clear(); this.query = null; this.not = false; this.nonEquiJoinCriteria.clear(); this.additionalCritieria = null; this.type = null; this.mergeJoin = false; this.madeDistinct = false; this.makeInd = false; } } private IDGenerator idGenerator; private CapabilitiesFinder capFinder; private AnalysisRecord analysisRecord; private CommandContext context; private QueryMetadataInterface metadata; private boolean dependent; public RuleMergeCriteria(IDGenerator idGenerator, CapabilitiesFinder capFinder, AnalysisRecord analysisRecord, CommandContext context, QueryMetadataInterface metadata) { this.idGenerator = idGenerator; this.capFinder = capFinder; this.analysisRecord = analysisRecord; this.context = context; this.metadata = metadata; } /** * @see OptimizerRule#execute(PlanNode, QueryMetadataInterface, RuleStack) */ public PlanNode execute(PlanNode plan, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, RuleStack rules, AnalysisRecord analysisRecord, CommandContext context) throws QueryPlannerException, TeiidComponentException { dependent = false; // Find strings of criteria and merge them, removing duplicates List<PlanNode> criteriaChains = new ArrayList<PlanNode>(); findCriteriaChains(plan, criteriaChains, analysisRecord); // Merge chains for (PlanNode critNode : criteriaChains) { mergeChain(critNode, metadata); } if (dependent) { //rules.push(new RuleAssignOutputElements(true)); rules.push(RuleConstants.PUSH_SELECT_CRITERIA); } return plan; } /** * Walk the tree pre-order, looking for any chains of criteria * @param node Root node to search * @param foundNodes Roots of criteria chains */ void findCriteriaChains(PlanNode root, List<PlanNode> foundNodes, AnalysisRecord analysisRecord) throws QueryPlannerException, TeiidComponentException { PlanNode recurseRoot = root; if(root.getType() == NodeConstants.Types.SELECT) { // Walk to end of the chain and change recurse root while(recurseRoot.getType() == NodeConstants.Types.SELECT) { // Look for opportunities to replace with a semi-join recurseRoot = planMergeJoin(recurseRoot, root); if (root.getChildCount() == 0) { root = recurseRoot.getFirstChild(); if (root.getType() != NodeConstants.Types.SELECT) { root = root.getParent(); } } recurseRoot = recurseRoot.getFirstChild(); } // Ignore trivial 1-node case if(recurseRoot.getParent() != root) { // Found root for chain foundNodes.add(root); } } if (recurseRoot.getType() != NodeConstants.Types.ACCESS) { for (PlanNode child : recurseRoot.getChildren()) { findCriteriaChains(child, foundNodes, analysisRecord); } } } static void mergeChain(PlanNode chainRoot, QueryMetadataInterface metadata) { // Remove all of chain except root, collect crit from each CompoundCriteria critParts = new CompoundCriteria(); LinkedList<Criteria> subqueryCriteria = new LinkedList<Criteria>(); PlanNode current = chainRoot; boolean isDependentSet = false; while(current.getType() == NodeConstants.Types.SELECT) { if (!current.getCorrelatedReferenceElements().isEmpty()) { //add at the end for delayed evaluation subqueryCriteria.add(0, (Criteria)current.getProperty(NodeConstants.Info.SELECT_CRITERIA)); } else { critParts.getCriteria().add(0, (Criteria)current.getProperty(NodeConstants.Info.SELECT_CRITERIA)); } isDependentSet |= current.hasBooleanProperty(NodeConstants.Info.IS_DEPENDENT_SET); // Recurse PlanNode last = current; current = current.getLastChild(); // Remove current if(last != chainRoot) { NodeEditor.removeChildNode(last.getParent(), last); } } critParts.getCriteria().addAll(subqueryCriteria); Criteria combinedCrit = QueryRewriter.optimizeCriteria(critParts, metadata); if (isDependentSet) { chainRoot.setProperty(NodeConstants.Info.IS_DEPENDENT_SET, Boolean.TRUE); } // Replace criteria at root with new combined criteria chainRoot.setProperty(NodeConstants.Info.SELECT_CRITERIA, combinedCrit); // Reset group for node based on combined criteria chainRoot.getGroups().clear(); chainRoot.addGroups(GroupsUsedByElementsVisitor.getGroups(combinedCrit)); chainRoot.addGroups(GroupsUsedByElementsVisitor.getGroups(chainRoot.getCorrelatedReferenceElements())); } /** * Look for: * [NOT] EXISTS ( ) * IN ( ) / SOME ( ) * * and replace with a semi join */ private PlanNode planMergeJoin(PlanNode current, PlanNode root) throws QueryMetadataException, TeiidComponentException { float sourceCost = NewCalculateCostUtil.computeCostForTree(current.getFirstChild(), metadata); Criteria crit = (Criteria)current.getProperty(NodeConstants.Info.SELECT_CRITERIA); PlannedResult plannedResult = findSubquery(crit, true); if (plannedResult.query == null) { return current; } if (sourceCost != NewCalculateCostUtil.UNKNOWN_VALUE && sourceCost < RuleChooseDependent.DEFAULT_INDEPENDENT_CARDINALITY && !plannedResult.mergeJoin) { //TODO: see if a dependent join applies the other direction return current; } RelationalPlan originalPlan = (RelationalPlan)plannedResult.query.getProcessorPlan(); Number originalCardinality = originalPlan.getRootNode().getEstimateNodeCardinality(); if (!plannedResult.mergeJoin && originalCardinality.floatValue() == NewCalculateCostUtil.UNKNOWN_VALUE) { //TODO: this check isn't really accurate - exists and scalarsubqueries will always have cardinality 2/1 //if it's currently unknown, removing criteria won't make it any better return current; } Collection<GroupSymbol> leftGroups = FrameUtil.findJoinSourceNode(current).getGroups(); if (!planQuery(leftGroups, false, plannedResult)) { if (plannedResult.mergeJoin && analysisRecord != null && analysisRecord.recordAnnotations()) { this.analysisRecord.addAnnotation(new Annotation(Annotation.HINTS, "Could not plan as a merge join: " + crit, "ignoring MJ hint", Priority.HIGH)); //$NON-NLS-1$ //$NON-NLS-2$ } return current; } //check if the child is already ordered. TODO: see if the ordering is compatible. PlanNode childSort = NodeEditor.findNodePreOrder(root, NodeConstants.Types.SORT, NodeConstants.Types.SOURCE | NodeConstants.Types.JOIN); if (childSort != null) { if (plannedResult.mergeJoin && analysisRecord != null && analysisRecord.recordAnnotations()) { this.analysisRecord.addAnnotation(new Annotation(Annotation.HINTS, "Could not plan as a merge join since the parent join requires a sort: " + crit, "ignoring MJ hint", Priority.HIGH)); //$NON-NLS-1$ //$NON-NLS-2$ } return current; } //add an order by, which hopefully will get pushed down plannedResult.query.setOrderBy(new OrderBy(plannedResult.rightExpressions).clone()); for (OrderByItem item : plannedResult.query.getOrderBy().getOrderByItems()) { int index = plannedResult.query.getProjectedSymbols().indexOf(item.getSymbol()); if (index >= 0 && !(item.getSymbol() instanceof ElementSymbol)) { item.setSymbol((Expression) plannedResult.query.getProjectedSymbols().get(index).clone()); } item.setExpressionPosition(index); } try { //clone the symbols as they may change during planning List<Expression> projectedSymbols = LanguageObject.Util.deepClone(plannedResult.query.getProjectedSymbols(), Expression.class); //NOTE: we could tap into the relationalplanner at a lower level to get this in a plan node form, //the major benefit would be to reuse the dependent join planning logic if possible. RelationalPlan subPlan = (RelationalPlan)QueryOptimizer.optimizePlan(plannedResult.query, metadata, idGenerator, capFinder, analysisRecord, context); Number planCardinality = subPlan.getRootNode().getEstimateNodeCardinality(); if (!plannedResult.mergeJoin) { //if we don't have a specific hint, then use costing if (planCardinality.floatValue() == NewCalculateCostUtil.UNKNOWN_VALUE || planCardinality.floatValue() > 10000000 || (sourceCost == NewCalculateCostUtil.UNKNOWN_VALUE && planCardinality.floatValue() > 1000) || (sourceCost != NewCalculateCostUtil.UNKNOWN_VALUE && sourceCost * originalCardinality.floatValue() < planCardinality.floatValue() / (100 * Math.log(Math.max(4, sourceCost))))) { //bail-out if both are unknown or the new plan is too large if (analysisRecord != null && analysisRecord.recordDebug()) { current.recordDebugAnnotation("cost of merge join plan was not favorable", null, "semi merge join will not be used", analysisRecord, metadata); //$NON-NLS-1$ //$NON-NLS-2$ } return current; } } //assume dependent if ((sourceCost != NewCalculateCostUtil.UNKNOWN_VALUE && planCardinality.floatValue() != NewCalculateCostUtil.UNKNOWN_VALUE && planCardinality.floatValue() < sourceCost / 8) || (sourceCost == NewCalculateCostUtil.UNKNOWN_VALUE && planCardinality.floatValue() <= 1000)) { plannedResult.makeInd = true; } /*if (plannedResult.makeInd && plannedResult.query.getCorrelatedReferences() == null && !plannedResult.not && plannedResult.leftExpressions.size() == 1) { //TODO: this should just be a dependent criteria node to avoid sorts }*/ current.recordDebugAnnotation("Conditions met (hint or cost)", null, "Converting to a semi merge join", analysisRecord, metadata); //$NON-NLS-1$ //$NON-NLS-2$ PlanNode semiJoin = NodeFactory.getNewNode(NodeConstants.Types.JOIN); semiJoin.addGroups(current.getGroups()); Set<GroupSymbol> groups = GroupsUsedByElementsVisitor.getGroups(plannedResult.rightExpressions); semiJoin.addGroups(groups); semiJoin.setProperty(NodeConstants.Info.JOIN_STRATEGY, JoinStrategyType.MERGE); semiJoin.setProperty(NodeConstants.Info.JOIN_TYPE, plannedResult.not?JoinType.JOIN_ANTI_SEMI:JoinType.JOIN_SEMI); semiJoin.setProperty(NodeConstants.Info.NON_EQUI_JOIN_CRITERIA, plannedResult.nonEquiJoinCriteria); List<Criteria> joinCriteria = new ArrayList<Criteria>(); joinCriteria.addAll(plannedResult.nonEquiJoinCriteria); for (int i = 0; i < plannedResult.leftExpressions.size(); i++) { joinCriteria.add(new CompareCriteria((Expression)plannedResult.rightExpressions.get(i), CompareCriteria.EQ, (Expression)plannedResult.leftExpressions.get(i))); } semiJoin.setProperty(NodeConstants.Info.JOIN_CRITERIA, joinCriteria); //nested subqueries are possibly being promoted, so they need their references updated List<SymbolMap> refMaps = semiJoin.getAllReferences(); SymbolMap parentRefs = plannedResult.query.getCorrelatedReferences(); for (SymbolMap refs : refMaps) { for (Map.Entry<ElementSymbol, Expression> ref : refs.asUpdatableMap().entrySet()) { Expression expr = ref.getValue(); if (expr instanceof ElementSymbol) { Expression convertedExpr = parentRefs.getMappedExpression((ElementSymbol)expr); if (convertedExpr != null) { ref.setValue(convertedExpr); } } semiJoin.getGroups().addAll(GroupsUsedByElementsVisitor.getGroups(ref.getValue())); } } semiJoin.setProperty(NodeConstants.Info.LEFT_EXPRESSIONS, plannedResult.leftExpressions); semiJoin.getGroups().addAll(GroupsUsedByElementsVisitor.getGroups(plannedResult.leftExpressions)); semiJoin.setProperty(NodeConstants.Info.RIGHT_EXPRESSIONS, plannedResult.rightExpressions); semiJoin.getGroups().addAll(GroupsUsedByElementsVisitor.getGroups(plannedResult.rightExpressions)); semiJoin.setProperty(NodeConstants.Info.SORT_RIGHT, SortOption.ALREADY_SORTED); semiJoin.setProperty(NodeConstants.Info.OUTPUT_COLS, root.getProperty(NodeConstants.Info.OUTPUT_COLS)); List childOutput = (List)current.getFirstChild().getProperty(NodeConstants.Info.OUTPUT_COLS); PlanNode toCorrect = root; while (toCorrect != current) { toCorrect.setProperty(NodeConstants.Info.OUTPUT_COLS, childOutput); toCorrect = toCorrect.getFirstChild(); } PlanNode node = NodeFactory.getNewNode(NodeConstants.Types.ACCESS); node.setProperty(NodeConstants.Info.PROCESSOR_PLAN, subPlan); node.setProperty(NodeConstants.Info.OUTPUT_COLS, projectedSymbols); node.setProperty(NodeConstants.Info.EST_CARDINALITY, planCardinality); node.addGroups(groups); root.addAsParent(semiJoin); semiJoin.addLastChild(node); PlanNode result = current.getParent(); NodeEditor.removeChildNode(result, current); RuleImplementJoinStrategy.insertSort(semiJoin.getFirstChild(), (List<Expression>) plannedResult.leftExpressions, semiJoin, metadata, capFinder, true, context); if (plannedResult.makeInd && !plannedResult.not) { //TODO: would like for an enhanced sort merge with the semi dep option to avoid the sorting //this is a little different than a typical dependent join in that the right is the independent side String id = RuleChooseDependent.nextId(); PlanNode dep = RuleChooseDependent.getDependentCriteriaNode(id, plannedResult.rightExpressions, plannedResult.leftExpressions, node, metadata, null, false, null); semiJoin.getFirstChild().addAsParent(dep); semiJoin.setProperty(NodeConstants.Info.DEPENDENT_VALUE_SOURCE, id); this.dependent = true; } return result; } catch (QueryPlannerException e) { //can't be done - probably access patterns - what about dependent return current; } } public PlannedResult findSubquery(Expression expr, boolean unnest, PlannedResult result) { if (expr instanceof ScalarSubquery) { ScalarSubquery scc = (ScalarSubquery)expr; if (scc.getSubqueryHint().isNoUnnest()) { return result; } Query query = (Query)scc.getCommand(); if (!isSingleRow(query)) { return result; } result.type = scc.getClass(); result.mergeJoin = scc.getSubqueryHint().isMergeJoin(); if (!unnest && !result.mergeJoin) { return result; } result.makeInd = scc.getSubqueryHint().isDepJoin(); result.query = query; } return result; } private boolean isSingleRow(Query query) { if (query.hasAggregates() && query.getGroupBy() == null) { return true; } return false; //todo: unique key } public PlannedResult findSubquery(Criteria crit, boolean unnest) throws TeiidComponentException, QueryMetadataException { PlannedResult result = new PlannedResult(); if (crit instanceof SubquerySetCriteria) { //convert to the quantified form SubquerySetCriteria ssc = (SubquerySetCriteria)crit; if (ssc.getSubqueryHint().isNoUnnest()) { return result; } result.not = ssc.isNegated(); result.type = ssc.getClass(); crit = new SubqueryCompareCriteria(ssc.getExpression(), ssc.getCommand(), SubqueryCompareCriteria.EQ, SubqueryCompareCriteria.SOME); ((SubqueryCompareCriteria)crit).setSubqueryHint(ssc.getSubqueryHint()); } else if (crit instanceof CompareCriteria) { CompareCriteria cc = (CompareCriteria)crit; if (cc.getRightExpression() instanceof ScalarSubquery) { ScalarSubquery ss = (ScalarSubquery)cc.getRightExpression(); if (ss.getSubqueryHint().isNoUnnest()) { return result; } result.type = ss.getClass(); //we can only use a semi-join if we know that 1 row will be present if (ss.getCommand() instanceof Query) { Query query = (Query)ss.getCommand(); if (query.getGroupBy() == null && query.hasAggregates()) { crit = new SubqueryCompareCriteria(cc.getLeftExpression(), ss.getCommand(), cc.getOperator(), SubqueryCompareCriteria.SOME); ((SubqueryCompareCriteria)crit).setSubqueryHint(ss.getSubqueryHint()); } } } } if (crit instanceof SubqueryCompareCriteria) { SubqueryCompareCriteria scc = (SubqueryCompareCriteria)crit; if (scc.getSubqueryHint().isNoUnnest()) { return result; } if (scc.getPredicateQuantifier() != SubqueryCompareCriteria.SOME //TODO: could add an inline view if not a query || !(scc.getCommand() instanceof Query)) { return result; } Query query = (Query)scc.getCommand(); Expression rightExpr = SymbolMap.getExpression(query.getProjectedSymbols().get(0)); if (result.not && !isNonNull(query, rightExpr)) { return result; } if (result.type == null) { result.type = scc.getClass(); } result.mergeJoin = scc.getSubqueryHint().isMergeJoin(); if (!unnest && !result.mergeJoin) { return result; } result.makeInd = scc.getSubqueryHint().isDepJoin(); result.query = query; result.additionalCritieria = (Criteria)new CompareCriteria(scc.getLeftExpression(), scc.getOperator(), rightExpr).clone(); } if (crit instanceof ExistsCriteria) { ExistsCriteria exists = (ExistsCriteria)crit; if (exists.getSubqueryHint().isNoUnnest()) { return result; } if (!(exists.getCommand() instanceof Query)) { return result; } result.type = crit.getClass(); result.not = exists.isNegated(); //the correlations can only be in where (if no group by or aggregates) or having result.mergeJoin = exists.getSubqueryHint().isMergeJoin(); result.makeInd = exists.getSubqueryHint().isDepJoin(); if (!unnest && !result.mergeJoin) { return result; } result.query = (Query)exists.getCommand(); } return result; } private boolean isNonNull(Query query, Expression rightExpr) throws TeiidComponentException, QueryMetadataException { if (rightExpr instanceof ElementSymbol) { ElementSymbol es = (ElementSymbol)rightExpr; if (metadata.elementSupports(es.getMetadataID(), SupportConstants.Element.NULL)) { return false; } if (!isSimpleJoin(query)) { return false; } } else if (rightExpr instanceof Constant) { if (((Constant)rightExpr).isNull()) { return false; } } else if (rightExpr instanceof AggregateSymbol) { AggregateSymbol as = (AggregateSymbol)rightExpr; if (as.getAggregateFunction() != Type.COUNT) { return false; } } else { return false; } return true; } private boolean isSimpleJoin(Query query) { if (query.getFrom() != null) { for (FromClause clause : query.getFrom().getClauses()) { if (RuleCollapseSource.hasOuterJoins(clause)) { return false; } } } return true; } public boolean planQuery(Collection<GroupSymbol> leftGroups, boolean requireDistinct, PlannedResult plannedResult) throws QueryMetadataException, TeiidComponentException { if ((plannedResult.query.getLimit() != null && !plannedResult.query.getLimit().isImplicit()) || plannedResult.query.getFrom() == null) { return false; } if ((plannedResult.type == ExistsCriteria.class || plannedResult.type == ScalarSubquery.class) && plannedResult.query.getCorrelatedReferences() == null) { //we can't really improve on this case //TODO: do this check earlier return false; } plannedResult.query = (Query)plannedResult.query.clone(); plannedResult.query.setLimit(null); List<GroupSymbol> rightGroups = plannedResult.query.getFrom().getGroups(); Set<Expression> requiredExpressions = new LinkedHashSet<Expression>(); final SymbolMap refs = plannedResult.query.getCorrelatedReferences(); boolean addGroupBy = false; if (refs != null) { boolean hasAggregates = plannedResult.query.hasAggregates(); Criteria where = plannedResult.query.getCriteria(); if (plannedResult.query.getGroupBy() == null) { plannedResult.query.setCriteria(null); } Criteria having = plannedResult.query.getHaving(); plannedResult.query.setHaving(null); if (hasCorrelatedReferences(plannedResult.query, refs)) { return false; } if (plannedResult.query.getGroupBy() == null) { processCriteria(leftGroups, plannedResult, rightGroups, requiredExpressions, refs, where, null, true); if (hasAggregates) { if (!plannedResult.nonEquiJoinCriteria.isEmpty()) { return false; } addGroupBy = true; } } processCriteria(leftGroups, plannedResult, rightGroups, requiredExpressions, refs, having, plannedResult.query.getGroupBy(), false); } if (plannedResult.additionalCritieria != null) { RuleChooseJoinStrategy.separateCriteria(leftGroups, rightGroups, plannedResult.leftExpressions, plannedResult.rightExpressions, Criteria.separateCriteriaByAnd(plannedResult.additionalCritieria), plannedResult.nonEquiJoinCriteria); } if (plannedResult.leftExpressions.isEmpty()) { return false; } plannedResult.leftExpressions = RuleChooseJoinStrategy.createExpressionSymbols(plannedResult.leftExpressions); plannedResult.rightExpressions = RuleChooseJoinStrategy.createExpressionSymbols(plannedResult.rightExpressions); if (requireDistinct && !addGroupBy) { //ensure that uniqueness applies to the in condition if (plannedResult.rightExpressions.size() > 1 && (plannedResult.type != SubquerySetCriteria.class || !isDistinct(plannedResult.query, plannedResult.rightExpressions.subList(plannedResult.rightExpressions.size() - 1, plannedResult.rightExpressions.size()), metadata))) { return false; } if (!isDistinct(plannedResult.query, plannedResult.rightExpressions, metadata)) { if (plannedResult.type == ExistsCriteria.class) { if (requiredExpressions.size() > plannedResult.leftExpressions.size()) { return false; //not an equi join } } else if (!requiredExpressions.isEmpty() && !isDistinct(plannedResult.query, plannedResult.query.getProjectedSymbols(), metadata)) { return false; } plannedResult.query.getSelect().setDistinct(true); plannedResult.madeDistinct = true; } } //it doesn't matter what the select columns are if (plannedResult.type == ExistsCriteria.class) { plannedResult.query.getSelect().clearSymbols(); } if (addGroupBy) { LinkedHashSet<Expression> groupingSymbols = new LinkedHashSet<Expression>(); for (Expression expr : (List<Expression>)plannedResult.rightExpressions) { AggregateSymbolCollectorVisitor.getAggregates(expr, null, groupingSymbols, null, null, null); } if (!groupingSymbols.isEmpty()) { plannedResult.query.setGroupBy((GroupBy) new GroupBy(new ArrayList<Expression>(groupingSymbols)).clone()); } } HashSet<Expression> projectedSymbols = new HashSet<Expression>(); for (Expression ses : plannedResult.query.getProjectedSymbols()) { projectedSymbols.add(SymbolMap.getExpression(ses)); } for (Expression ses : requiredExpressions) { if (projectedSymbols.add(ses)) { plannedResult.query.getSelect().addSymbol((Expression)ses.clone()); } } for (Expression ses : (List<Expression>)plannedResult.rightExpressions) { if (projectedSymbols.add(SymbolMap.getExpression(ses))) { plannedResult.query.getSelect().addSymbol((Expression)ses.clone()); } } return true; } private void processCriteria(Collection<GroupSymbol> leftGroups, PlannedResult plannedResult, List<GroupSymbol> rightGroups, Set<Expression> requiredExpressions, final SymbolMap refs, Criteria joinCriteria, GroupBy groupBy, boolean where) { if (joinCriteria == null) { return; } List<Criteria> crits = Criteria.separateCriteriaByAnd((Criteria)joinCriteria.clone()); for (Iterator<Criteria> critIter = crits.iterator(); critIter.hasNext();) { Criteria conjunct = critIter.next(); List<Expression> additionalRequired = new LinkedList<Expression>(); AggregateSymbolCollectorVisitor.getAggregates(conjunct, additionalRequired, additionalRequired, additionalRequired, null, groupBy!=null?groupBy.getSymbols():null); ReferenceReplacementVisitor emv = new ReferenceReplacementVisitor(refs); DeepPostOrderNavigator.doVisit(conjunct, emv); if (!emv.replacedAny) { //if not correlated, then leave it on the query critIter.remove(); if (where) { plannedResult.query.setCriteria(Criteria.combineCriteria(plannedResult.query.getCriteria(), conjunct)); } else { plannedResult.query.setHaving(Criteria.combineCriteria(plannedResult.query.getHaving(), conjunct)); } } else { requiredExpressions.addAll(additionalRequired); } } RuleChooseJoinStrategy.separateCriteria(leftGroups, rightGroups, plannedResult.leftExpressions, plannedResult.rightExpressions, crits, plannedResult.nonEquiJoinCriteria); } public static boolean isDistinct(Query query, List<Expression> expressions, QueryMetadataInterface metadata) throws QueryMetadataException, TeiidComponentException { boolean distinct = false; if (query.getGroupBy() != null) { distinct = true; for (Expression groupByExpr : query.getGroupBy().getSymbols()) { if (!expressions.contains(groupByExpr)) { distinct = false; break; } } } if (distinct) { return true; } HashSet<GroupSymbol> keyPreservingGroups = new HashSet<GroupSymbol>(); ResolverUtil.findKeyPreserved(query, keyPreservingGroups, metadata); return NewCalculateCostUtil.usesKey(expressions, keyPreservingGroups, metadata, true); } private boolean hasCorrelatedReferences(LanguageObject object, SymbolMap correlatedReferences) { Collection<Reference> references = ReferenceCollectorVisitor.getReferences(object); for (Reference reference : references) { if (correlatedReferences.asMap().containsKey(reference.getExpression())) { return true; } } return false; } public String toString() { return "MergeCriteria"; //$NON-NLS-1$ } }