All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.tajo.plan.rewrite.rules.CommonConditionReduceRule Maven / Gradle / Ivy

The newest version!
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.tajo.plan.rewrite.rules;

import org.apache.tajo.exception.TajoException;
import org.apache.tajo.plan.LogicalPlan;
import org.apache.tajo.plan.expr.*;
import org.apache.tajo.plan.logical.JoinNode;
import org.apache.tajo.plan.logical.LogicalNode;
import org.apache.tajo.plan.logical.NodeType;
import org.apache.tajo.plan.logical.SelectionNode;
import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRule;
import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRuleContext;
import org.apache.tajo.plan.visitor.BasicLogicalPlanVisitor;
import org.apache.tajo.util.TUtil;

import java.util.Set;
import java.util.Stack;

/**
 * Condition reduce rule reduces the query predicate based on distributivity.
 * For example, the query
 *
 * SELECT *
 * FROM t
 * WHERE (t.a = 1 OR t.b = 10) AND (t.a = 1 OR t.c = 100)
 *
 * is converted into
 *
 * SELECT *
 * FROM t
 * WHERE t.a = 1 OR (t.b = 10 AND t.c = 100).
 *
 */
public class CommonConditionReduceRule implements LogicalPlanRewriteRule {
  private Rewriter rewriter;

  @Override
  public String getName() {
    return "CommonConditionReduceRule";
  }

  @Override
  public boolean isEligible(LogicalPlanRewriteRuleContext context) {
    for (LogicalPlan.QueryBlock block : context.getPlan().getQueryBlocks()) {
      if (block.hasNode(NodeType.SELECTION) || block.hasNode(NodeType.JOIN)) {
        rewriter = new Rewriter(context.getPlan());
        return true;
      }
    }
    return false;
  }

  @Override
  public LogicalPlan rewrite(LogicalPlanRewriteRuleContext context) throws TajoException {
    rewriter.visit(null, context.getPlan(), context.getPlan().getRootBlock());
    return context.getPlan();
  }

  /**
   * Rewriter simply triggers rewriting evals while visiting logical nodes.
   */
  private final static class Rewriter extends BasicLogicalPlanVisitor {
    EvalRewriter evalRewriter;

    public Rewriter(LogicalPlan plan) {
      evalRewriter = new EvalRewriter(plan);
    }

    @Override
    public LogicalNode visitFilter(Object context, LogicalPlan plan, LogicalPlan.QueryBlock block,
                                   SelectionNode selNode, Stack stack) throws TajoException {
      selNode.setQual(evalRewriter.visit(null, selNode.getQual(), new Stack()));
      return null;
    }

    @Override
    public LogicalNode visitJoin(Object context, LogicalPlan plan, LogicalPlan.QueryBlock block,
                                 JoinNode joinNode, Stack stack) throws TajoException {
      if (joinNode.hasJoinQual()) {
        joinNode.setJoinQual(evalRewriter.visit(null, joinNode.getJoinQual(), new Stack()));
      }
      return null;
    }
  }

  /**
   * EvalRewriter is responsible for rewriting evals based on distributivity.
   */
  private static class EvalRewriter extends SimpleEvalNodeVisitor {
    final LogicalPlan plan;

    public EvalRewriter(LogicalPlan plan) {
      this.plan = plan;
    }

    @Override
    protected EvalNode visitUnaryEval(Object context, UnaryEval unaryEval, Stack stack) {
      stack.push(unaryEval);
      EvalNode child = unaryEval.getChild();
      visit(context, child, stack);
      if (child.getType() == EvalType.AND || child.getType() == EvalType.OR) {
        unaryEval.setChild(rewrite((BinaryEval) child));
      }
      stack.pop();
      return unaryEval;
    }

    @Override
    protected EvalNode visitBinaryEval(Object context, Stack stack, BinaryEval binaryEval) {
      stack.push(binaryEval);
      EvalNode child = binaryEval.getLeftExpr();
      visit(context, child, stack);
      if (child.getType() == EvalType.AND || child.getType() == EvalType.OR) {
        binaryEval.setLeftExpr(rewrite((BinaryEval) child));
      }

      child = binaryEval.getRightExpr();
      visit(context, child, stack);
      if (child.getType() == EvalType.AND || child.getType() == EvalType.OR) {
        binaryEval.setRightExpr(rewrite((BinaryEval) child));
      }

      EvalNode result = rewrite(binaryEval);

      stack.pop();
      return result;
    }

    @Override
    protected EvalNode visitDefaultFunctionEval(Object context, Stack stack, FunctionEval functionEval) {
      stack.push(functionEval);
      if (functionEval.getArgs() != null) {
        EvalNode [] args = functionEval.getArgs();
        for (int i = 0; i < args.length; i++) {
          visit(context, args[i], stack);
          if (args[i].getType() == EvalType.AND || args[i].getType() == EvalType.OR) {
            functionEval.setArg(i, rewrite((BinaryEval) args[i]));
          }
        }
      }
      stack.pop();
      return functionEval;
    }

    private EvalNode rewrite(BinaryEval evalNode) {
      // Example qual: ( a OR b ) AND ( a OR c )
      EvalType outerType = evalNode.getType(); // type of the outer operation. ex) AND

      EvalNode finalQual = evalNode;
      if ((evalNode.getLeftExpr().getType() == EvalType.AND || evalNode.getLeftExpr().getType() == EvalType.OR) &&
          evalNode.getLeftExpr().getType() == evalNode.getRightExpr().getType()) {
        EvalNode leftChild = evalNode.getLeftExpr();
        EvalNode rightChild = evalNode.getRightExpr();

        EvalType innerType = leftChild.getType();

        // Find common quals from the left and right children.
        Set commonQuals = TUtil.newHashSet();
        Set leftChildSplits = innerType == EvalType.AND ?
            TUtil.newHashSet(AlgebraicUtil.toConjunctiveNormalFormArray(leftChild)) :
            TUtil.newHashSet(AlgebraicUtil.toDisjunctiveNormalFormArray(leftChild));
        Set rightChildSplits = innerType == EvalType.AND ?
            TUtil.newHashSet(AlgebraicUtil.toConjunctiveNormalFormArray(rightChild)) :
            TUtil.newHashSet(AlgebraicUtil.toDisjunctiveNormalFormArray(rightChild));

        for (EvalNode eachLeftChildSplit : leftChildSplits) {
          if (rightChildSplits.contains(eachLeftChildSplit)) {
            commonQuals.add(eachLeftChildSplit);
          }
        }

        if (leftChildSplits.size() == rightChildSplits.size() &&
            commonQuals.size() == leftChildSplits.size()) {
          // Ex) ( a OR b ) AND ( a OR b )
          // Current binary eval has the same left and right children, so it is useless.
          // Connect the parent of the current eval and one of the children directly.
          finalQual = leftChild;
        } else if (commonQuals.size() == leftChildSplits.size()) {
          // Ex) ( a OR b ) AND ( a OR b OR c )
          finalQual = rightChild;
        } else if (commonQuals.size() == rightChildSplits.size()) {
          // Ex) ( a OR b OR c ) AND ( a OR b )
          finalQual = leftChild;
        } else if (commonQuals.size() > 0) {
          // Common quals are found.
          // ( a OR b ) AND ( a OR c ) -> a OR (b AND c)

          // Find non-common quals.
          leftChildSplits.removeAll(commonQuals);
          rightChildSplits.removeAll(commonQuals);

          // Recreate both children using non-common quals.
          EvalNode commonQual;
          if (innerType == EvalType.AND) {
            leftChild = AlgebraicUtil.createSingletonExprFromCNF(leftChildSplits);
            rightChild = AlgebraicUtil.createSingletonExprFromCNF(rightChildSplits);
            commonQual = AlgebraicUtil.createSingletonExprFromCNF(commonQuals);
          } else {
            leftChild = AlgebraicUtil.createSingletonExprFromDNF(leftChildSplits);
            rightChild = AlgebraicUtil.createSingletonExprFromDNF(rightChildSplits);
            commonQual = AlgebraicUtil.createSingletonExprFromDNF(commonQuals);
          }

          finalQual = new BinaryEval(innerType, commonQual, new BinaryEval(outerType, leftChild, rightChild));
        }
      } else if (evalNode.getLeftExpr().equals(evalNode.getRightExpr())) {
        finalQual = evalNode.getLeftExpr();
      }

      // Just compare that finalQual and evalNode is the same instance.
      if (finalQual != evalNode) {
        plan.addHistory("Common condition is reduced.");
      }
      return finalQual;
    }

  }
}