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

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

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

import org.teiid.api.exception.query.QueryMetadataException;
import org.teiid.api.exception.query.QueryPlannerException;
import org.teiid.core.TeiidComponentException;
import org.teiid.language.SortSpecification.NullOrdering;
import org.teiid.query.analysis.AnalysisRecord;
import org.teiid.query.metadata.QueryMetadataInterface;
import org.teiid.query.metadata.SupportConstants;
import org.teiid.query.optimizer.capabilities.CapabilitiesFinder;
import org.teiid.query.optimizer.capabilities.SourceCapabilities.Capability;
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.sql.lang.CompareCriteria;
import org.teiid.query.sql.lang.Criteria;
import org.teiid.query.sql.lang.JoinType;
import org.teiid.query.sql.lang.OrderBy;
import org.teiid.query.sql.lang.OrderByItem;
import org.teiid.query.sql.lang.SetQuery.Operation;
import org.teiid.query.sql.symbol.AggregateSymbol;
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.SingleElementSymbol;
import org.teiid.query.sql.util.SymbolMap;
import org.teiid.query.util.CommandContext;
import org.teiid.translator.ExecutionFactory.NullOrder;
import org.teiid.translator.ExecutionFactory.SupportedJoinCriteria;


public final class RuleRaiseAccess implements OptimizerRule {

  public PlanNode execute(PlanNode plan, QueryMetadataInterface metadata, CapabilitiesFinder capFinder, RuleStack rules, AnalysisRecord analysisRecord, CommandContext context)
    throws QueryPlannerException, QueryMetadataException, TeiidComponentException {

        boolean afterJoinPlanning = !rules.contains(RuleConstants.PLAN_JOINS);
       
        // Loop until nothing has been raised - plan is then stable and can be returned
        boolean raisedNode = true;
        while(raisedNode) {
            raisedNode = false;

            for (PlanNode accessNode : NodeEditor.findAllNodes(plan, NodeConstants.Types.ACCESS)) {
                PlanNode newRoot = raiseAccessNode(plan, accessNode, metadata, capFinder, afterJoinPlanning, analysisRecord);
                if(newRoot != null) {
                    raisedNode = true;
                    plan = newRoot;
                }
            }           
        }
       
        return plan;
  }
   
    /**
     * @return null if nothing changed, and a new plan root if something changed
     */
    static PlanNode raiseAccessNode(PlanNode rootNode, PlanNode accessNode, QueryMetadataInterface metadata,
        CapabilitiesFinder capFinder, boolean afterJoinPlanning, AnalysisRecord record)
    throws QueryPlannerException, QueryMetadataException, TeiidComponentException {
       
        PlanNode parentNode = accessNode.getParent();
        if(parentNode == null) {
            // Nothing to raise over
            return null;
        }
        Object modelID = getModelIDFromAccess(accessNode, metadata);
        if(modelID == null) {
            return null;
        }
       
        switch(parentNode.getType()) {
            case NodeConstants.Types.JOIN:
            {
                modelID = canRaiseOverJoin(modelID, parentNode, metadata, capFinder, afterJoinPlanning, record);
                if(modelID != null) {
                    raiseAccessOverJoin(parentNode, modelID, true);                   
                    return rootNode;
                }
                return null;
            }           
            case NodeConstants.Types.PROJECT:
            {        
                // Check that the PROJECT contains only functions that can be pushed                              
                List projectCols = (List) parentNode.getProperty(NodeConstants.Info.PROJECT_COLS);
               
                for (int i = 0; i < projectCols.size(); i++) {
                    SingleElementSymbol symbol = (SingleElementSymbol)projectCols.get(i);
                    if(! canPushSymbol(symbol, true, modelID, metadata, capFinder, record)) {
                        return null;
                    }
                }
               
                /*
                 * TODO: this creates an extraneous project node in many circumstances.
                 * However we don't actually support project in this case, so allowing it to be pushed
                 * causes problems with stored procedures and the assumptions made for proc/relational
                 * planning.
                 */
                if (FrameUtil.isProcedure(parentNode)) {
                  return null;
                }
                               
                return performRaise(rootNode, accessNode, parentNode);               
            }
            case NodeConstants.Types.DUP_REMOVE:
            {    
                // If model supports the support constant parameter, then move access node
                if(!CapabilitiesUtil.supportsSelectDistinct(modelID, metadata, capFinder)) {
                  recordDebug("cannot push dupremove, since distinct is not supported by source", parentNode, record); //$NON-NLS-1$
                  return null;
                }
               
                //TODO: this check is too specific the columns could be used in expressions that are comparable
                if (!CapabilitiesUtil.checkElementsAreSearchable((List)NodeEditor.findNodePreOrder(parentNode, NodeConstants.Types.PROJECT).getProperty(NodeConstants.Info.PROJECT_COLS), metadata, SupportConstants.Element.SEARCHABLE_COMPARE)) {
                  recordDebug("cannot push dupremove, since not all columns are comparable at the source", parentNode, record); //$NON-NLS-1$
                  return null;
                }
               
                return performRaise(rootNode, accessNode, parentNode);
            }
            case NodeConstants.Types.SORT:
            {        
                if (canRaiseOverSort(accessNode, metadata, capFinder, parentNode, record, false)) {
                    return performRaise(rootNode, accessNode, parentNode);
                }
                return null;
            }           
            case NodeConstants.Types.GROUP:           
            {               
                Set<AggregateSymbol> aggregates = RulePushAggregates.collectAggregates(parentNode);
                if (canRaiseOverGroupBy(parentNode, accessNode, aggregates, metadata, capFinder, record)) {
                    return performRaise(rootNode, accessNode, parentNode);
                }
                return null;
            }
            case NodeConstants.Types.SET_OP:
              if (!canRaiseOverSetQuery(parentNode, metadata, capFinder)) {
                return null;
              }

              for (PlanNode node : new ArrayList<PlanNode>(parentNode.getChildren())) {
                if (node == accessNode) {
                  continue;
                }
              NodeEditor.removeChildNode(parentNode, node);
              }
              accessNode.getGroups().clear();
                return performRaise(rootNode, accessNode, parentNode);
            case NodeConstants.Types.SELECT:           
            {
              if (parentNode.hasBooleanProperty(NodeConstants.Info.IS_DEPENDENT_SET)) {
                return null;
              }
              if (canRaiseOverSelect(accessNode, metadata, capFinder, parentNode, record)) {
                    RulePushSelectCriteria.satisfyAccessPatterns(parentNode, accessNode);
                    return performRaise(rootNode, accessNode, parentNode);                     
              }
              //determine if we should push the select back up
              if (parentNode.getParent() == null) {
                return null;
              }
            PlanNode selectRoot = parentNode;
            while (selectRoot.getParent() != null && selectRoot.getParent().getType() == NodeConstants.Types.SELECT) {
              selectRoot = selectRoot.getParent();
            }
            if (selectRoot.getParent() == null || (selectRoot.getParent().getType() & (NodeConstants.Types.PROJECT|NodeConstants.Types.GROUP)) == selectRoot.getParent().getType()) {
              return null;
            }
          PlanNode grandParent = selectRoot.getParent();
          boolean isLeft = false;
        isLeft = grandParent.getFirstChild() == selectRoot;
        if (grandParent.getType() == NodeConstants.Types.JOIN) {
          JoinType jt = (JoinType)grandParent.getProperty(NodeConstants.Info.JOIN_TYPE);
          if (jt == JoinType.JOIN_FULL_OUTER || (jt == JoinType.JOIN_LEFT_OUTER && !isLeft)) {
            return null;
          }
        }
        grandParent.removeChild(selectRoot);
        if (isLeft) {
          grandParent.addFirstChild(accessNode);
        } else {
          grandParent.addLastChild(accessNode);
        }
          PlanNode newParent = grandParent.getParent();
        //TODO: use costing or heuristics instead of always raising
          PlanNode newRoot = raiseAccessNode(rootNode, accessNode, metadata, capFinder, afterJoinPlanning, record);
          if (newRoot == null) {
          //return the tree to its original state
            parentNode.addFirstChild(accessNode);
            if (isLeft) {
              grandParent.addFirstChild(selectRoot);
            } else {
              grandParent.addLastChild(selectRoot);
            }
          } else {
            //attach the select nodes above the access node
            accessNode = grandParent.getParent();
          if (newParent != null) {
              isLeft = newParent.getFirstChild() == accessNode;
              if (isLeft) {
                newParent.addFirstChild(selectRoot);
              } else {
                newParent.addLastChild(selectRoot);
              }
            } else {
              newRoot = selectRoot;
            }
          parentNode.addFirstChild(accessNode);
            return newRoot;
          }
                return null;
            }  
            case NodeConstants.Types.SOURCE:
            {
                //if a source has access patterns that are unsatisfied, then the raise cannot occur
                if (parentNode.hasCollectionProperty(NodeConstants.Info.ACCESS_PATTERNS)) {
                    return null;
                }
               
                SymbolMap references = (SymbolMap)parentNode.getProperty(NodeConstants.Info.CORRELATED_REFERENCES);
                if (references != null) {
                  return null;
                }
               
                //raise only if there is no intervening project into
                PlanNode parentProject = NodeEditor.findParent(parentNode, NodeConstants.Types.PROJECT);
                GroupSymbol intoGroup = (GroupSymbol)parentProject.getProperty(NodeConstants.Info.INTO_GROUP);
                if (intoGroup != null && parentProject.getParent() == null) {
                  if (CapabilitiesUtil.supports(Capability.INSERT_WITH_QUERYEXPRESSION, modelID, metadata, capFinder) && CapabilitiesUtil.isSameConnector(modelID, metadata.getModelID(intoGroup.getMetadataID()), metadata, capFinder)) {
                      rootNode = performRaise(rootNode, accessNode, parentNode);
                      return performRaise(rootNode, accessNode, parentProject);
                  }
                  return null;
                } else if (!CapabilitiesUtil.supportsInlineView(modelID, metadata, capFinder)) {
                  return null;
                }

                //is there another query that will be used with this source
                if (FrameUtil.getNonQueryCommand(accessNode) != null || FrameUtil.getNestedPlan(accessNode) != null) {
                  return null;
                }
                               
                //switch to inline view and change the group on the access to that of the source
              parentNode.setProperty(NodeConstants.Info.INLINE_VIEW, Boolean.TRUE);
              accessNode.getGroups().clear();
              accessNode.addGroups(parentNode.getGroups());
                RulePlaceAccess.copyDependentHints(parentNode, accessNode);
              return performRaise(rootNode, accessNode, parentNode);
            }
            case NodeConstants.Types.TUPLE_LIMIT:
            {
                return RulePushLimit.raiseAccessOverLimit(rootNode, accessNode, metadata, capFinder, parentNode);
            }
            default:
            {
                return null;
            }                     
        }       
    }

    static boolean canRaiseOverGroupBy(PlanNode groupNode,
                                         PlanNode accessNode,
                                         Collection<? extends SingleElementSymbol> aggregates,
                                         QueryMetadataInterface metadata,
                                         CapabilitiesFinder capFinder, AnalysisRecord record) throws QueryMetadataException,
                                                        TeiidComponentException {
        Object modelID = getModelIDFromAccess(accessNode, metadata);
        if(modelID == null) {
            return false;
        }
        List<SingleElementSymbol> groupCols = (List<SingleElementSymbol>)groupNode.getProperty(NodeConstants.Info.GROUP_COLS);
        if(!CapabilitiesUtil.supportsAggregates(groupCols, modelID, metadata, capFinder)) {
          recordDebug("cannot push group by, since group by is not supported by source", groupNode, record); //$NON-NLS-1$
            return false;
        }
        if (groupCols != null) {
            for (SingleElementSymbol singleElementSymbol : groupCols) {
                if (!canPushSymbol(singleElementSymbol, false, modelID, metadata, capFinder, record)) {
                    return false;
                }
            }
        }
        if (aggregates != null) {
            for (SingleElementSymbol aggregateSymbol : aggregates) {
                if(! CriteriaCapabilityValidatorVisitor.canPushLanguageObject(aggregateSymbol, modelID, metadata, capFinder, record)) {
                    return false;
                }
            }
        }
        return CapabilitiesUtil.checkElementsAreSearchable(groupCols, metadata, SupportConstants.Element.SEARCHABLE_COMPARE);
    }

  private static void recordDebug(String message, PlanNode node, AnalysisRecord record) {
    if (record != null && record.recordDebug()) {
      record.println(message + " " + node.nodeToString()); //$NON-NLS-1$
    }
  }
   
    static boolean canRaiseOverSort(PlanNode accessNode,
                                   QueryMetadataInterface metadata,
                                   CapabilitiesFinder capFinder,
                                   PlanNode parentNode, AnalysisRecord record, boolean compensateForUnrelated) throws QueryMetadataException,
                                                       TeiidComponentException {
        // Find the model for this node by getting ACCESS node's model
        Object modelID = getModelIDFromAccess(accessNode, metadata);
        if(modelID == null) {
            // Couldn't determine model ID, so give up
            return false;
        }
       
        List<OrderByItem> sortCols = ((OrderBy)parentNode.getProperty(NodeConstants.Info.SORT_ORDER)).getOrderByItems();
        for (OrderByItem symbol : sortCols) {
            //TODO: this check shouldn't be necessary, since the order by is not introducing new expressions
            if(! canPushSymbol(symbol.getSymbol(), true, modelID, metadata, capFinder, record)) {
                return false;
            }
            boolean supportsNullOrdering = CapabilitiesUtil.supports(Capability.QUERY_ORDERBY_NULL_ORDERING, modelID, metadata, capFinder);
            NullOrder defaultNullOrder = CapabilitiesUtil.getDefaultNullOrder(modelID, metadata, capFinder);
            if (symbol.getNullOrdering() != null) {
            if (!supportsNullOrdering) {
              if (symbol.getNullOrdering() == NullOrdering.FIRST) {
                if (defaultNullOrder != NullOrder.FIRST && !(symbol.isAscending() && defaultNullOrder == NullOrder.LOW)
                    && !(!symbol.isAscending() && defaultNullOrder == NullOrder.HIGH)) {
                  return false;
                }
              } else if (defaultNullOrder != NullOrder.LAST && !(symbol.isAscending() && defaultNullOrder == NullOrder.HIGH)
                  && !(!symbol.isAscending() && defaultNullOrder == NullOrder.LOW)) {
              return false;
            }
            symbol.setNullOrdering(null);
            }
            } else if (supportsNullOrdering && defaultNullOrder != NullOrder.LOW) {
              //try to match the expected default of low
            if (symbol.isAscending()) {
              if (defaultNullOrder != NullOrder.FIRST) {
                symbol.setNullOrdering(NullOrdering.FIRST);
              }
            } else if (defaultNullOrder != NullOrder.LAST) {
              symbol.setNullOrdering(NullOrdering.LAST);
            }
            }
        }
       
        if (accessNode.getLastChild() != null) {
            //check to see if the sort applies to a union
            if (accessNode.getLastChild().getType() == NodeConstants.Types.SET_OP) {
                return CapabilitiesUtil.supportsSetQueryOrderBy(modelID, metadata, capFinder);
            }
            //check to see the plan is not in a consistent state to have a sort applied
            if (accessNode.getLastChild().getType() == NodeConstants.Types.TUPLE_LIMIT) {
                return false;
            }
        }
       
        if (!CapabilitiesUtil.checkElementsAreSearchable(sortCols, metadata, SupportConstants.Element.SEARCHABLE_COMPARE)) {
          return false;
        }
       
        // If model supports the support constant parameter, then move access node
        if (!CapabilitiesUtil.supportsOrderBy(modelID, metadata, capFinder)) {
          return false;
        }
       
        if (parentNode.hasBooleanProperty(NodeConstants.Info.UNRELATED_SORT)
            && !CapabilitiesUtil.supports(Capability.QUERY_ORDERBY_UNRELATED, modelID, metadata, capFinder)
            && NodeEditor.findParent(accessNode, NodeConstants.Types.PROJECT, NodeConstants.Types.SOURCE) == null
            && !compensateForUnrelated) {
          return false;
        }
       
        return true;
    }

    /**
     * @param accessNode
     * @param metadata
     * @param capFinder
     * @param parentNode
     * @return
     * @throws QueryMetadataException
     * @throws TeiidComponentException
     * @throws QueryPlannerException
     */
    static boolean canRaiseOverSelect(PlanNode accessNode,
                                        QueryMetadataInterface metadata,
                                        CapabilitiesFinder capFinder,
                                        PlanNode parentNode, AnalysisRecord record) throws QueryMetadataException,
                                                            TeiidComponentException,
                                                            QueryPlannerException {
        if (parentNode.hasBooleanProperty(NodeConstants.Info.IS_PHANTOM)) {
            return true;
        }
               
        // Find the model for this node by getting ACCESS node's model
        Object modelID = getModelIDFromAccess(accessNode, metadata);
        if(modelID == null) {
            // Couldn't determine model ID, so give up
            return false;
        }
       
        if (parentNode.hasBooleanProperty(NodeConstants.Info.IS_HAVING) && !CapabilitiesUtil.supports(Capability.QUERY_HAVING, modelID, metadata, capFinder)) {
          recordDebug("cannot push having, since having is not supported by source", parentNode, record); //$NON-NLS-1$
          return false;
        }
       
        //don't push criteria into an invalid location above an ordered limit - shouldn't happen
        PlanNode limitNode = NodeEditor.findNodePreOrder(accessNode, NodeConstants.Types.TUPLE_LIMIT, NodeConstants.Types.SOURCE);
        if (limitNode != null && FrameUtil.isOrderedLimit(limitNode)) {
          return false;
        }
       
        Criteria crit = (Criteria) parentNode.getProperty(NodeConstants.Info.SELECT_CRITERIA);
       
        if(!CriteriaCapabilityValidatorVisitor.canPushLanguageObject(crit, modelID, metadata, capFinder, record) ) {
            return false;                       
        }
       
        if (accessNode.getFirstChild() != null && accessNode.getFirstChild().getType() == NodeConstants.Types.SET_OP) {
            return false; //inconsistent select position - RulePushSelectCriteria is too greedy
        }
               
        return true;
   
           
    /**
     * 
     * @param symbol Symbol to check
     * @param inSelectClause True if evaluating in the context of a SELECT clause
     * @param modelID Model
     * @param metadata Metadata
     * @param capFinder Capabilities finder
     * @return True if can push symbol to source
     * @throws TeiidComponentException
     * @throws QueryMetadataException
     * @since 4.1.2
     */
    private static boolean canPushSymbol(SingleElementSymbol symbol, boolean inSelectClause, Object modelID,
        QueryMetadataInterface metadata, CapabilitiesFinder capFinder, AnalysisRecord record)
    throws TeiidComponentException, QueryMetadataException {

        Expression expr = SymbolMap.getExpression(symbol);
       
        // Do the normal checks
        if(! CriteriaCapabilityValidatorVisitor.canPushLanguageObject(expr, modelID, metadata, capFinder, record)) {
            return false;
        }
       
        if(inSelectClause && !(expr instanceof ElementSymbol || expr instanceof AggregateSymbol)
            && !CapabilitiesUtil.supportsSelectExpression(modelID, metadata, capFinder)) {
        return false;
        }               
        
        // By default, no reason we can't push
        return true;
    }
   
    static PlanNode performRaise(PlanNode rootNode, PlanNode accessNode, PlanNode parentNode) {
      accessNode.removeProperty(NodeConstants.Info.EST_CARDINALITY);
        NodeEditor.removeChildNode(parentNode, accessNode);
        parentNode.addAsParent(accessNode);
        PlanNode grandparentNode = accessNode.getParent();
        if(grandparentNode != null) {
            return rootNode;
        }
        return accessNode;
    }

    /**
     * Determine whether an access node can be raised over the specified join node.
     *
     * This method can also be used to determine if a join node "A", parent of another join
     * node "B", will have it's access raised.  This is needed to help determine if node
     * "B" will have access raised over it.  In this scenario, the parameter will be true.
     * When this method is called normally from the "execute" method, that param will be false.
     *  
     * @param joinNode Join node that might be pushed underneath the access node
     * @param metadata Metadata information
     * @param capFinder CapabilitiesFinder
     * @return The modelID if the raise can proceed and what common model these combined
     * nodes will be sent to
     */
  private static Object canRaiseOverJoin(Object modelId, PlanNode joinNode, QueryMetadataInterface metadata,
      CapabilitiesFinder capFinder, boolean afterJoinPlanning, AnalysisRecord record)
    throws QueryMetadataException, TeiidComponentException {
   
        List crits = (List) joinNode.getProperty(NodeConstants.Info.JOIN_CRITERIA);
        JoinType type = (JoinType) joinNode.getProperty(NodeConstants.Info.JOIN_TYPE);
       
        //let ruleplanjoins handle this case
        if (!afterJoinPlanning && type == JoinType.JOIN_CROSS && joinNode.getParent().getType() == NodeConstants.Types.JOIN) {
            JoinType jt = (JoinType)joinNode.getParent().getProperty(NodeConstants.Info.JOIN_TYPE);
            if (!jt.isOuter()) {
                return null;
            }
        }
       
        if (joinNode.getProperty(NodeConstants.Info.DEPENDENT_VALUE_SOURCE) != null) {
            return null;
        }
       
        //if a join has access patterns that are unsatisfied, then the raise cannot occur
        if (joinNode.hasCollectionProperty(NodeConstants.Info.ACCESS_PATTERNS)) {
            return null;
        }
       
        //if I'm on the inner side of an outer join, then and we have a criteria restriction, then I can't be pushed
    if (type.isOuter() && CapabilitiesUtil.getSupportedJoinCriteria(modelId, metadata, capFinder) != SupportedJoinCriteria.ANY) {
      PlanNode critNode = NodeEditor.findNodePreOrder(joinNode.getLastChild(), NodeConstants.Types.SELECT, NodeConstants.Types.SOURCE);
      if (critNode != null) {
        return null;
      }
      if (type == JoinType.JOIN_FULL_OUTER) {
        critNode = NodeEditor.findNodePreOrder(joinNode.getFirstChild(), NodeConstants.Types.SELECT, NodeConstants.Types.SOURCE);
        if (critNode != null) {
          return null;
       
      }
    }
       
        return canRaiseOverJoin(joinNode.getChildren(), metadata, capFinder, crits, type, record);   
  }

    static Object canRaiseOverJoin(List<PlanNode> children,
                                           QueryMetadataInterface metadata,
                                           CapabilitiesFinder capFinder,
                                           List<Criteria> crits,
                                           JoinType type, AnalysisRecord record) throws QueryMetadataException,
                                                         TeiidComponentException {
        //we only want to consider binary joins
        if (children.size() != 2) {
          return null;
        }

      Object modelID = null;
        Set<Object> groupIDs = new HashSet<Object>();
        int groupCount = 0;
    LinkedList<CompareCriteria> thetaCriteria = new LinkedList<CompareCriteria>();
    SupportedJoinCriteria sjc = null;

    for (PlanNode childNode : children) {
      if(childNode.getType() != NodeConstants.Types.ACCESS
          || childNode.hasCollectionProperty(NodeConstants.Info.ACCESS_PATTERNS)) {
                return null;
            }
      Object accessModelID = getModelIDFromAccess(childNode, metadata);
            if(accessModelID == null) {
                return null;
            }
           
            groupCount += childNode.getGroups().size();

      // Add all group metadata IDs to the list but check before each to make
      // sure group hasn't already been seen - if so, bail out - this is a self join
            // Unless model supports self joins, in which case, don't bail out.

            boolean supportsSelfJoins = CapabilitiesUtil.supportsSelfJoins(accessModelID, metadata, capFinder);
           
            if (!supportsSelfJoins) {
                for (GroupSymbol groupSymbol : childNode.getGroups()) {
              Object groupID = groupSymbol.getMetadataID();
              if(!groupIDs.add(groupID)) {
                  // Already seen group - can't raise access over self join
                  return null;
              }
          }
            }
                 
            //check the join criteria now that we know the model
      if(modelID == null) {
               
            if (!CapabilitiesUtil.supportsJoin(accessModelID, type, metadata, capFinder)) {
           return null;
            }
            sjc = CapabilitiesUtil.getSupportedJoinCriteria(accessModelID, metadata, capFinder);
       
            /*
             * Key joins must be left linear
             */
            if (sjc == SupportedJoinCriteria.KEY && children.get(0).getGroups().size() != 1) {
              return null;
            }
           
        if(crits != null && !crits.isEmpty()) {
          for (Criteria crit : crits) {
                if (!isSupportedJoinCriteria(sjc, crit, accessModelID, metadata, capFinder, record)) {
                  if (crit instanceof CompareCriteria) {
                  CompareCriteria cc = (CompareCriteria) crit;
                  if (cc.isOptional()) {
                    cc.setOptional(true);
                    continue;
                  }
                }
                  return null;
                } else if (crit instanceof CompareCriteria) {
                  thetaCriteria.add((CompareCriteria)crit);
                }
          }
          if (sjc == SupportedJoinCriteria.KEY) {
            LinkedList<Expression> leftExpressions = new LinkedList<Expression>();
            LinkedList<Expression> rightExpressions = new LinkedList<Expression>();
            RuleChooseJoinStrategy.separateCriteria(children.get(0).getGroups(), children.get(1).getGroups(), leftExpressions, rightExpressions, crits, new LinkedList<Criteria>());
            ArrayList<Object> leftIds = new ArrayList<Object>(leftExpressions.size());
            ArrayList<Object> rightIds = new ArrayList<Object>(rightExpressions.size());
            for (Expression expr : leftExpressions) {
              if (expr instanceof ElementSymbol) {
                leftIds.add(((ElementSymbol) expr).getMetadataID());
              }
            }
            GroupSymbol rightGroup = null;
            for (Expression expr : rightExpressions) {
              if (expr instanceof ElementSymbol) {
                ElementSymbol es = (ElementSymbol) expr;
                if (rightGroup == null) {
                  rightGroup = es.getGroupSymbol();
                } else if (!rightGroup.equals(es.getGroupSymbol())) {
                  return null;
                }
                rightIds.add(es.getMetadataID());
              }
            }
            if (rightGroup == null) {
              return null;
            }
            if (!matchesForeignKey(metadata, leftIds, rightIds,  children.get(0).getGroups().iterator().next(), true)
                && !matchesForeignKey(metadata, rightIds, leftIds, rightGroup, true)) {
              return null;
            }
          }
                } else if (sjc != SupportedJoinCriteria.ANY) {
                  return null; //cross join not supported
                }
       
        modelID = accessModelID;
       
      } else if(!CapabilitiesUtil.isSameConnector(modelID, accessModelID, metadata, capFinder)) {
        return null;             
      }
    } // end walking through join node's children

    int maxGroups = CapabilitiesUtil.getMaxFromGroups(modelID, metadata, capFinder);
   
    if (maxGroups != -1 && maxGroups < groupCount) {
        return null;
    }
   
    if (sjc == SupportedJoinCriteria.KEY) {
      for (CompareCriteria criteria : thetaCriteria) {
        criteria.setOptional(false);
      }
    } else {
      //TODO: this should be done in a less arbitrary way, and what about composite keys?
      boolean hasCriteria = false;
      for (CompareCriteria criteria : thetaCriteria) {
        if (criteria.getIsOptional() == null || (!hasCriteria && criteria.getIsOptional())) {
          criteria.setOptional(false);
        }
        hasCriteria = true;
      }
    }
   
    return modelID;
    }
   
    /**
     * Checks criteria one predicate at a time.  Only tests up to the equi restriction.
     */
    static boolean isSupportedJoinCriteria(SupportedJoinCriteria sjc, Criteria crit, Object accessModelID,
        QueryMetadataInterface metadata, CapabilitiesFinder capFinder, AnalysisRecord record)
    throws QueryMetadataException, TeiidComponentException {
      if(!CriteriaCapabilityValidatorVisitor.canPushLanguageObject(crit, accessModelID, metadata, capFinder, record) ) {
            return false;                       
        }
        if (sjc == SupportedJoinCriteria.ANY) {
          return true;
        }
        //theta join must be between elements with a compare predicate
      if (!(crit instanceof CompareCriteria)) {
        return false;
      }
      CompareCriteria cc = (CompareCriteria)crit;
      if (!(cc.getLeftExpression() instanceof ElementSymbol)) {
        return false;
      }
      if (!(cc.getRightExpression() instanceof ElementSymbol)) {
        return false;
      }
      if (sjc == SupportedJoinCriteria.THETA) {
        return true;
      }
      //equi must use the equality operator
      if (cc.getOperator() != CompareCriteria.EQ) {
        return false;
      }
    return true;
    }

  public static boolean matchesForeignKey(QueryMetadataInterface metadata,
      Collection<Object> leftIds, Collection<Object> rightIds, GroupSymbol leftGroup, boolean exact)
      throws TeiidComponentException, QueryMetadataException {
    Collection fks = metadata.getForeignKeysInGroup(leftGroup.getMetadataID());
    for (Object fk : fks) {
      List fkColumns = metadata.getElementIDsInKey(fk);
      if ((exact && leftIds.size() != fkColumns.size()) || !leftIds.containsAll(fkColumns)) {
        continue;
      }
      Object pk = metadata.getPrimaryKeyIDForForeignKeyID(fk);
      List pkColumns = metadata.getElementIDsInKey(pk);
      if ((!exact || rightIds.size() == pkColumns.size()) && rightIds.containsAll(pkColumns)) {
        return true;
      }
    }
    return false;
  }
   
    static PlanNode raiseAccessOverJoin(PlanNode joinNode, Object modelID, boolean insert) {
    PlanNode leftAccess = joinNode.getFirstChild();
    PlanNode rightAccess = joinNode.getLastChild();

    // Remove old access nodes - this will automatically add children of access nodes to join node
    NodeEditor.removeChildNode(joinNode, leftAccess);
    NodeEditor.removeChildNode(joinNode, rightAccess);
       
        //Set for later possible use, even though this isn't an access node
        joinNode.setProperty(NodeConstants.Info.MODEL_ID, modelID);

    // Insert new access node above join node
    PlanNode newAccess = NodeFactory.getNewNode(NodeConstants.Types.ACCESS);
    newAccess.setProperty(NodeConstants.Info.MODEL_ID, modelID);
    newAccess.addGroups(rightAccess.getGroups());
    newAccess.addGroups(leftAccess.getGroups());
       
        // Combine hints if necessary
        Object leftHint = leftAccess.getProperty(NodeConstants.Info.MAKE_DEP);
        if(leftHint != null) {
            newAccess.setProperty(NodeConstants.Info.MAKE_DEP, leftHint);
        } else {
            Object rightHint = rightAccess.getProperty(NodeConstants.Info.MAKE_DEP);
            if(rightHint != null) {
                newAccess.setProperty(NodeConstants.Info.MAKE_DEP, rightHint);
            }   
        }
        RulePlaceAccess.copyDependentHints(leftAccess, newAccess);
        RulePlaceAccess.copyDependentHints(rightAccess, newAccess);
        RulePlaceAccess.copyDependentHints(joinNode, newAccess);
       
        if (insert) {
            joinNode.addAsParent(newAccess);
        } else {
            newAccess.addFirstChild(joinNode);
        }
       
        return newAccess;
  }

    /**
     * Get modelID for Access node and cache the result in the Access node.
     * @param accessNode Access node
     * @param metadata Metadata access
     * @return Object Model ID or null if not found.
     * @throws QueryMetadataException
     * @throws TeiidComponentException
     */
    static Object getModelIDFromAccess(PlanNode accessNode, QueryMetadataInterface metadata)
    throws QueryMetadataException, TeiidComponentException {

        Object accessModelID = accessNode.getProperty(NodeConstants.Info.MODEL_ID);
        if(accessModelID == null && accessNode.getGroups().size() > 0) {
            GroupSymbol group = accessNode.getGroups().iterator().next();
            if(metadata.isVirtualGroup(group.getMetadataID())) {
                return null;
            }
            accessModelID = metadata.getModelID(group.getMetadataID());
   
            accessNode.setProperty(NodeConstants.Info.MODEL_ID, accessModelID);
        }
       
        return accessModelID;   
    }
   
    private static boolean canRaiseOverSetQuery(PlanNode setOpNode,
                                     QueryMetadataInterface metadata,
                                     CapabilitiesFinder capFinder) throws QueryMetadataException, TeiidComponentException {
       
        Object modelID = null;
       
        for (PlanNode childNode : setOpNode.getChildren()) {
            if(childNode.getType() != NodeConstants.Types.ACCESS) {
                return false;
            }
           
            if (FrameUtil.getNonQueryCommand(childNode) != null || FrameUtil.getNestedPlan(childNode) != null) {
                return false;
            }
           
            // Get model and check that it exists
            Object accessModelID = getModelIDFromAccess(childNode, metadata);
            if(accessModelID == null) {
                return false;
            }
           
            // Reconcile this access node's model ID with existing                                            
            if(modelID == null) {
                modelID = accessModelID;
               
                if(! CapabilitiesUtil.supportsSetOp(accessModelID, (Operation)setOpNode.getProperty(NodeConstants.Info.SET_OPERATION), metadata, capFinder)) {
                    return false;
                }
            } else if(!CapabilitiesUtil.isSameConnector(modelID, accessModelID, metadata, capFinder)) {
                return false;
            }     
            if (!setOpNode.hasBooleanProperty(NodeConstants.Info.USE_ALL)
                &&  !CapabilitiesUtil.checkElementsAreSearchable((List)NodeEditor.findNodePreOrder(childNode, NodeConstants.Types.PROJECT).getProperty(NodeConstants.Info.PROJECT_COLS), metadata, SupportConstants.Element.SEARCHABLE_COMPARE)) {
              return false;
            }

        }
        return true;
    }
   
  public String toString() {
    return "RaiseAccess"; //$NON-NLS-1$
  }
 
}
TOP

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

TOP
Copyright © 2015 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.