Package org.teiid.query.optimizer.relational.rules

Source Code of org.teiid.query.optimizer.relational.rules.RulePushSelectCriteria

/*
* 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.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Stack;

import org.teiid.api.exception.query.QueryMetadataException;
import org.teiid.api.exception.query.QueryPlannerException;
import org.teiid.core.TeiidComponentException;
import org.teiid.query.QueryPlugin;
import org.teiid.query.analysis.AnalysisRecord;
import org.teiid.query.metadata.QueryMetadataInterface;
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.resolver.util.AccessPattern;
import org.teiid.query.sql.lang.CompoundCriteria;
import org.teiid.query.sql.lang.Criteria;
import org.teiid.query.sql.lang.JoinType;
import org.teiid.query.sql.lang.SubqueryContainer;
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.util.SymbolMap;
import org.teiid.query.sql.visitor.AggregateSymbolCollectorVisitor;
import org.teiid.query.sql.visitor.ElementCollectorVisitor;
import org.teiid.query.sql.visitor.ValueIteratorProviderCollectorVisitor;
import org.teiid.query.util.CommandContext;


public final class RulePushSelectCriteria implements OptimizerRule {
 
  private List<PlanNode> createdNodes;
 
  public List<PlanNode> getCreatedNodes() {
    return createdNodes;
  }
 
  public void setCreatedNodes(List<PlanNode> createdNodes) {
    this.createdNodes = createdNodes;
  }
   
  /**
   * Execute the rule as described in the class comments.
   * @param plan Incoming query plan, may be modified during method and may be returned from method
   * @param metadata Metadata source
   * @param rules Rules from optimizer rule stack, may be manipulated during method
   * @return Updated query plan if rule fired, else original query plan
   */
  public PlanNode execute(PlanNode plan, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, RuleStack rules, AnalysisRecord analysisRecord, CommandContext context)
    throws QueryPlannerException, QueryMetadataException, TeiidComponentException {

    // Initialize movedNode to true so the loop will start
    boolean movedAnyNode = true;

    // Create set of nodes that no longer need to be considered
    Set<PlanNode> deadNodes = new HashSet<PlanNode>();

    // Loop while criteria nodes are still being moved
    while(movedAnyNode) {

        // Reset flag to false for this iteration
        movedAnyNode = false;

        // Find criteria nodes that could be pushed
        List<PlanNode> critNodes = NodeEditor.findAllNodes(plan, NodeConstants.Types.SELECT);
        Collections.reverse(critNodes);
        for (PlanNode critNode : critNodes) {
          boolean isPhantom = critNode.hasBooleanProperty(NodeConstants.Info.IS_PHANTOM);
              boolean isCopied = critNode.hasBooleanProperty(NodeConstants.Info.IS_COPIED);
              boolean isPushed = critNode.hasBooleanProperty(NodeConstants.Info.IS_PUSHED);
              if (isPhantom || isCopied || isPushed || deadNodes.contains(critNode)) {
                continue;
              }
             
              PlanNode sourceNode = findOriginatingNode(metadata, capFinder, critNode, analysisRecord);
             
              if(sourceNode == null) {
                    deadNodes.add(critNode);
                  continue;
              }
              pushTowardOriginatingNode(sourceNode, critNode, metadata, capFinder);
             
                boolean moved = false;
               
                if((critNode.getGroups().isEmpty() && critNode.getSubqueryContainers().isEmpty()) || !atBoundary(critNode, sourceNode)) {
                    deadNodes.add(critNode);
                    continue;
                }
              
                switch (sourceNode.getType()) {
                    case NodeConstants.Types.SOURCE:
                    {
                        moved = pushAcrossFrame(sourceNode, critNode, metadata);
                        break;
                    }
                    case NodeConstants.Types.JOIN:
                    {
                //pushing below a join is not necessary under an access node
                if (NodeEditor.findParent(critNode, NodeConstants.Types.ACCESS) == null) {
                            moved = handleJoinCriteria(sourceNode, critNode, metadata);
                            break;
                }
                    }
                }
               
                if (!moved) {
                    deadNodes.add(critNode);
                } else {
                    movedAnyNode = true;
                }
        }
    }

    return plan;
  }

  private PlanNode findOriginatingNode(QueryMetadataInterface metadata,
      CapabilitiesFinder capFinder, PlanNode critNode, AnalysisRecord record)
      throws TeiidComponentException, QueryMetadataException {
    if (critNode.getGroups().isEmpty()) {
          //check to see if pushing may impact cardinality
          PlanNode groupNode = NodeEditor.findNodePreOrder(critNode, NodeConstants.Types.GROUP, NodeConstants.Types.SOURCE);
          if (groupNode != null && !groupNode.hasCollectionProperty(NodeConstants.Info.GROUP_COLS)) {
            return groupNode;
          }

      Object modelId = getSubqueryModelId(metadata, capFinder, critNode, record);
      if (modelId != null) {
        for (PlanNode node : NodeEditor.findAllNodes(critNode, NodeConstants.Types.SOURCE)) {
                GroupSymbol group = node.getGroups().iterator().next();
                Object srcModelID = metadata.getModelID(group.getMetadataID());
                if(CapabilitiesUtil.isSameConnector(srcModelID, modelId, metadata, capFinder)) {
                    return node;
                }
            }
      }
    }
    return FrameUtil.findOriginatingNode(critNode, critNode.getGroups());
  }

  private Object getSubqueryModelId(QueryMetadataInterface metadata,
      CapabilitiesFinder capFinder, PlanNode critNode, AnalysisRecord record)
      throws TeiidComponentException, QueryMetadataException {
    Object modelId = null;
    for (SubqueryContainer subqueryContainer : critNode.getSubqueryContainers()) {
      Object validId = CriteriaCapabilityValidatorVisitor.validateSubqueryPushdown(subqueryContainer, null, metadata, capFinder, record);
      if (validId == null) {
        return null;
      }
      if (modelId == null) {
        modelId = validId;
      } else if (!CapabilitiesUtil.isSameConnector(modelId, validId, metadata, capFinder)) {
        return null;
      }
    }
    return modelId;
  }
   
    /**
     * Handles multi-group criteria originating at the given joinNode
     * 
     * @param joinNode
     * @param critNode
     * @return
     */
    private boolean handleJoinCriteria(PlanNode joinNode, PlanNode critNode, QueryMetadataInterface metadata) {
        JoinType jt = (JoinType)joinNode.getProperty(NodeConstants.Info.JOIN_TYPE);
       
        if (jt == JoinType.JOIN_CROSS || jt == JoinType.JOIN_INNER) {
            if (jt == JoinType.JOIN_CROSS) {
                joinNode.setProperty(NodeConstants.Info.JOIN_TYPE, JoinType.JOIN_INNER);
            }
            moveCriteriaIntoOnClause(critNode, joinNode);
        } else {
            JoinType optimized = JoinUtil.optimizeJoinType(critNode, joinNode, metadata);
           
            if (optimized == JoinType.JOIN_INNER) {
                moveCriteriaIntoOnClause(critNode, joinNode);
                return true; //return true since the join type has changed
            }
        }
        return false;
    }
   
    /**
     * @param critNode
     * @param joinNode
     */
    private void moveCriteriaIntoOnClause(PlanNode critNode,
                                          PlanNode joinNode) {
        List joinCriteria = (List)joinNode.getProperty(NodeConstants.Info.JOIN_CRITERIA);
        Criteria criteria = (Criteria)critNode.getProperty(NodeConstants.Info.SELECT_CRITERIA);
       
        //since the parser uses EMPTY_LIST, check for size 0 also
        if (joinCriteria == null || joinCriteria.size() == 0) {
            joinCriteria = new LinkedList();
            joinNode.setProperty(NodeConstants.Info.JOIN_CRITERIA, joinCriteria);
        }
       
        if (!joinCriteria.contains(criteria)) {
            joinCriteria.add(criteria);
            if(critNode.hasBooleanProperty(NodeConstants.Info.IS_DEPENDENT_SET)) {
                joinNode.setProperty(NodeConstants.Info.IS_DEPENDENT_SET, Boolean.TRUE);
            }
        }
        NodeEditor.removeChildNode(critNode.getParent(), critNode);
    }

    /**
     * 
     * @param critNode
     * @param metadata
     * @param capFinder
     * @throws QueryPlannerException
     * @throws QueryMetadataException
     * @throws TeiidComponentException
     */
    void pushTowardOriginatingNode(PlanNode sourceNode, PlanNode critNode, QueryMetadataInterface metadata, CapabilitiesFinder capFinder)
    throws QueryPlannerException, QueryMetadataException, TeiidComponentException {

      boolean groupSelects = sourceNode.getParent().getType() == NodeConstants.Types.SELECT && sourceNode.getChildCount() == 0;
       
        //to keep a stable criteria ordering, move the sourceNode to the top of the criteria chain
        while (sourceNode.getParent().getType() == NodeConstants.Types.SELECT) {
            sourceNode = sourceNode.getParent();
            if (sourceNode == critNode) {
                return;
            }
        }

    // See how far we can move it towards the SOURCE node
    PlanNode destination = examinePath(critNode, sourceNode, metadata, capFinder);
        NodeEditor.removeChildNode(critNode.getParent(), critNode);
        destination.addAsParent(critNode);
        if (groupSelects && destination == sourceNode) {
          //Help with the detection of composite keys in pushed criteria
          RuleMergeCriteria.mergeChain(critNode, metadata);
        }
  }

    /**
   * Examine the path from crit node to source node to determine how far down a node
   * can be pushed.
   * @return destinationChild
   */
  PlanNode examinePath(PlanNode critNode, PlanNode sourceNode, QueryMetadataInterface metadata, CapabilitiesFinder capFinder)
    throws QueryPlannerException, TeiidComponentException {
       
    // Walk from source node up to critNode to build list of intervening nodes
    Stack<PlanNode> path = new Stack<PlanNode>();
    PlanNode currentNode = sourceNode.getParent();
    while(currentNode != critNode) {
      path.push(currentNode);
      currentNode = currentNode.getParent();
    }

    // Examine path in reverse order (by popping stack)
    while(! path.empty()) {
      currentNode = path.pop();
           
      // Look for situations where we don't allow SELECT to be pushed
      if(currentNode.getType() == NodeConstants.Types.ACCESS) {
                try {
                    if (!RuleRaiseAccess.canRaiseOverSelect(currentNode, metadata, capFinder, critNode, null)) {
                        return currentNode;
                    }
                    if (this.createdNodes == null) {
                      satisfyAccessPatterns(critNode, currentNode);
                    }

                    if (critNode.hasBooleanProperty(NodeConstants.Info.IS_DEPENDENT_SET)) {
                        //once a dependent crit node is pushed, don't bother pushing it further into the command
                        //dependent access node will use this as an assumption for where dependent sets can appear in the command
                        critNode.setProperty(NodeConstants.Info.IS_PUSHED, Boolean.TRUE);
                        currentNode.setProperty(NodeConstants.Info.IS_DEPENDENT_SET, Boolean.TRUE);
                        return currentNode.getFirstChild();
                    }
        } catch(QueryMetadataException e) {
                    throw new QueryPlannerException(e, QueryPlugin.Util.getString("ERR.015.004.0020", currentNode.getGroups())); //$NON-NLS-1$
        }
      } else if(currentNode.getType() == NodeConstants.Types.JOIN) {
        //pushing below a join is not necessary under an access node
        if (this.createdNodes == null && NodeEditor.findParent(currentNode, NodeConstants.Types.ACCESS) != null) {
          return currentNode;
        }
       
                // Check whether this criteria is on the inner side of an outer join. 
                // If so, can't push past the join
                JoinType jt = JoinUtil.getJoinTypePreventingCriteriaOptimization(currentNode, critNode);
               
                if(jt != null) {
                    //if we successfully optimized then this should no longer inhibit the criteria from being pushed
                    //since the criteria must then be on the outer side of an outer join or on either side of an inner join

                    JoinType optimized = JoinUtil.optimizeJoinType(critNode, currentNode, metadata);
                   
                    if (optimized == null || optimized.isOuter()) {
                        return currentNode;
                    }
                } 
           
                satisfyAccessPatterns(critNode, currentNode);
            } else if (FrameUtil.isOrderedLimit(currentNode)) {
                return currentNode;
            } else if (currentNode.getType() == NodeConstants.Types.GROUP && critNode.hasBooleanProperty(NodeConstants.Info.IS_HAVING)) {
                return currentNode;
            }
    }

    return sourceNode;
  }

  boolean pushAcrossFrame(PlanNode sourceNode, PlanNode critNode, QueryMetadataInterface metadata)
    throws QueryPlannerException {
       
        //ensure that the criteria can be pushed further
        if (sourceNode.getChildCount() == 1 && FrameUtil.isOrderedLimit(sourceNode.getFirstChild())) {
            return false;
        }
       
        //check to see if this is a move across a union
        if (sourceNode.getChildCount() > 0) {
            PlanNode child = sourceNode.getFirstChild();
            child = FrameUtil.findOriginatingNode(child, child.getGroups());
            if (child != null && child.getType() == NodeConstants.Types.SET_OP) {
              //only allow criteria without subqueires - node cloning doesn't allow for the proper creation of
              //multiple nodes with the same subqueries
                if (child == sourceNode.getFirstChild() && critNode.getSubqueryContainers().isEmpty()) {
                    return pushAcrossSetOp(critNode, child, metadata);
                }
                //this could be an access node in the middle of the source and set op,
                //it is an odd case that is not supported for now
                return false;
            }
        }
       
    // See if we can move it towards the SOURCE node
        return moveNodeAcrossFrame(critNode, sourceNode, metadata);
  }

  /**
   * All nodes between critNode and sourceNode must be SELECT nodes.
   */
  boolean atBoundary(PlanNode critNode, PlanNode sourceNode) {
    // Walk from source node to critNode to check each intervening node
    PlanNode currentNode = sourceNode.getParent();
    while(currentNode != critNode) {
      if(currentNode.getType() != NodeConstants.Types.SELECT) {
        return false;
      }

      currentNode = currentNode.getParent();
    }

    return true;
  }

  boolean moveNodeAcrossFrame(PlanNode critNode, PlanNode sourceNode, QueryMetadataInterface metadata)
    throws QueryPlannerException {

        // Check that sourceNode has a child to push across
        if(sourceNode.getChildCount() == 0) {
            return false;
        }

        PlanNode projectNode = NodeEditor.findNodePreOrder(sourceNode.getFirstChild(), NodeConstants.Types.PROJECT, NodeConstants.Types.SOURCE);
        if(FrameUtil.isProcedure(projectNode)) {
            return false;
        }
       
        SymbolMap symbolMap = (SymbolMap) sourceNode.getProperty(NodeConstants.Info.SYMBOL_MAP);
       
        if (!createConvertedSelectNode(critNode, sourceNode.getGroups().iterator().next(), projectNode, symbolMap, metadata)) {
            return false;
        }
   
        satisfyAccessPatterns(critNode, sourceNode);
       
    // Mark critNode as a "phantom"
    critNode.setProperty(NodeConstants.Info.IS_PHANTOM, Boolean.TRUE);
   
    return true;
  }

    /**
     * @param critNode
     * @param sourceNode
     */
    static void satisfyAccessPatterns(PlanNode critNode,
                                       PlanNode sourceNode) {
        List aps = (List)sourceNode.getProperty(NodeConstants.Info.ACCESS_PATTERNS);
      
        if (aps == null) {
            return;
        }

        Criteria crit = (Criteria)critNode.getProperty(NodeConstants.Info.SELECT_CRITERIA);
       
        Collection<ElementSymbol> elements = getElementsIncriteria(crit);
                       
        boolean removeAps = satisfyAccessPatterns(aps, elements);
        if (removeAps) {
            sourceNode.removeProperty(NodeConstants.Info.ACCESS_PATTERNS);
            return;
        }
        Collections.sort(aps);
    }
   
    static Collection<ElementSymbol> getElementsIncriteria(Criteria crit) {
        Collection<ElementSymbol> elements = new HashSet<ElementSymbol>();
        boolean first = true;
        if(crit instanceof CompoundCriteria) {
            CompoundCriteria compCrit = (CompoundCriteria) crit;
            for(Criteria subCrit : compCrit.getCriteria()) {
                if(compCrit.getOperator() == CompoundCriteria.AND || first) {
                    first = false;
                    elements.addAll(getElementsIncriteria(subCrit));
                } else {
                    elements.retainAll(getElementsIncriteria(subCrit));
                }
            }
        } else {
            elements.addAll(ElementCollectorVisitor.getElements(crit, true));       
        }
        return elements;
    }
   
    /**
     * @param aps
     * @param elements
     * @return
     */
    static boolean satisfyAccessPatterns(List<AccessPattern> aps, Collection<ElementSymbol> elements) {
      for (AccessPattern ap : aps) {
            ap.getUnsatisfied().removeAll(elements);
            if (ap.getUnsatisfied().isEmpty()) {
                return true;
            }
        }
        return false;
    }

  PlanNode copyNode(PlanNode critNode) {
    // Create new copy node
    PlanNode copyNode = NodeFactory.getNewNode(NodeConstants.Types.SELECT);

    // Copy criteria
    Criteria crit = (Criteria) critNode.getProperty(NodeConstants.Info.SELECT_CRITERIA);
    Criteria copyCrit = (Criteria) crit.clone();
    copyNode.setProperty(NodeConstants.Info.SELECT_CRITERIA, copyCrit);
    copyNode.addGroups(critNode.getGroups());
        if(critNode.hasBooleanProperty(NodeConstants.Info.IS_DEPENDENT_SET)) {
            copyNode.setProperty(NodeConstants.Info.IS_DEPENDENT_SET, Boolean.TRUE);
        }
        if (createdNodes != null) {
          createdNodes.add(copyNode);
        }
      return copyNode;
  }

  boolean pushAcrossSetOp(PlanNode critNode, PlanNode setOp, QueryMetadataInterface metadata)
    throws QueryPlannerException {
       
        // Find source node above union and grab the symbol map
        PlanNode sourceNode = NodeEditor.findParent(setOp, NodeConstants.Types.SOURCE);
        GroupSymbol virtualGroup = sourceNode.getGroups().iterator().next();
        satisfyAccessPatterns(critNode, sourceNode);
       
        SymbolMap symbolMap = (SymbolMap) sourceNode.getProperty(NodeConstants.Info.SYMBOL_MAP);
        SymbolMap childMap = symbolMap;
       
    // Move criteria to first child of union - names are the same, so no symbol mapping
    LinkedList<PlanNode> unionChildren = new LinkedList<PlanNode>();
    collectUnionChildren(setOp, unionChildren);

        int movedCount = 0;

        for (PlanNode planNode : unionChildren) {
          // Find first project node
          PlanNode projectNode = NodeEditor.findNodePreOrder(planNode, NodeConstants.Types.PROJECT);
       
          if (childMap == null) {
            childMap = SymbolMap.createSymbolMap(symbolMap.getKeys(), (List) projectNode.getProperty(NodeConstants.Info.PROJECT_COLS));
          }
       
      // Move the node
      if(createConvertedSelectNode(critNode, virtualGroup, projectNode, childMap, metadata)) {
                movedCount++;
            }
     
      childMap = null; //create a new symbol map for the other children
    }
       
    //TODO - the logic here could be made more intelligent about EXCEPT and INTERSECT.
        if(movedCount == unionChildren.size()) {
            critNode.setProperty(NodeConstants.Info.IS_PHANTOM, Boolean.TRUE);
            return true;
        }
        //otherwise mark it as pushed so we don't consider it again
        critNode.setProperty(NodeConstants.Info.IS_PUSHED, Boolean.TRUE);
        return false;
  }

  static void collectUnionChildren(PlanNode unionNode, List<PlanNode> unionChildren) {
      for (PlanNode child : unionNode.getChildren()) {
          if(child.getType() == NodeConstants.Types.SET_OP) {
              collectUnionChildren(child, unionChildren);
          } else {
              unionChildren.add(child);
          }
        }
  }

    private boolean createConvertedSelectNode(PlanNode critNode,
                     GroupSymbol sourceGroup,
                                   PlanNode projectNode,
                                   SymbolMap symbolMap,
                                   QueryMetadataInterface metadata) throws QueryPlannerException {
        // If projectNode has children, then it is from a SELECT without a FROM and the criteria should not be pushed
        if(projectNode.getChildCount() == 0) {
            return false;
        }

        Criteria crit = (Criteria) critNode.getProperty(NodeConstants.Info.SELECT_CRITERIA);

        Boolean conversionResult = checkConversion(symbolMap, ElementCollectorVisitor.getElements(crit, true));
       
        if (conversionResult == Boolean.FALSE) {
          return false; //not convertable
        }
       
        if (!critNode.getSubqueryContainers().isEmpty()
            && checkConversion(symbolMap, critNode.getCorrelatedReferenceElements()) != null) {
        return false; //not convertable, or has an aggregate for a correlated reference
        }
       
        PlanNode copyNode = copyNode(critNode);

        if (conversionResult == Boolean.TRUE) {
            copyNode.setProperty(NodeConstants.Info.IS_HAVING, Boolean.TRUE);
        }

        FrameUtil.convertNode(copyNode, sourceGroup, null, symbolMap.asMap(), metadata, true)
        PlanNode intermediateParent = NodeEditor.findParent(projectNode, NodeConstants.Types.ACCESS, NodeConstants.Types.SOURCE | NodeConstants.Types.SET_OP);
        if (intermediateParent != null) {
            intermediateParent.addAsParent(copyNode);
        } else {
          projectNode.getFirstChild().addAsParent(copyNode);
        }
    return true;
    }

  private Boolean checkConversion(SymbolMap symbolMap,
      Collection<ElementSymbol> elements) {
    Boolean result = null;
       
        for (ElementSymbol element : elements) {
            Expression converted = symbolMap.getMappedExpression(element);

            if(converted == null) {
                return false;
            }
           
            Collection<SubqueryContainer> scalarSubqueries = ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(converted);
            if (!scalarSubqueries.isEmpty()){
                return false;
            }
           
            if (!AggregateSymbolCollectorVisitor.getAggregates(converted, false).isEmpty()) {
                result = Boolean.TRUE;
            }
        }
    return result;
  }
   
  public String toString() {
    return "PushSelectCriteria"; //$NON-NLS-1$
  }

}
TOP

Related Classes of org.teiid.query.optimizer.relational.rules.RulePushSelectCriteria

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.