calcite 中rule的分析 FilterIntoJoinRule(上)

从test 中构造这样一个sql 来分析这条rule 入口进入

      
      
     String sql = "select u.id as user_id, u.name as user_name, j.company as user_company, u.age as user_age" +
                " from users u join jobs j on u.name=j.name" +
                " where u.age > 30 and j.id>10" +
                " order by user_id";
      
      
      1. SqlNode = sqlParser.parse(sql)
      2. validate(sqlNode) 
      3. sqlNode -> relNode 
      4. optimize 
      
      /*******************************
         * Optimizer(RelNode-->RelNode)*
         *******************************/
        HepProgramBuilder optBuilder = new HepProgramBuilder();
        optBuilder.addRuleInstance(FilterJoinRule.FilterIntoJoinRule.FILTER_ON_JOIN);
        HepPlanner optPlanner = new HepPlanner(optBuilder.build());
        optPlanner.setRoot(relNode);
        //应用rule ,优化sql 逻辑
        relNode = optPlanner.findBestExp();

在findBestExp 的过程中,主要的调用是在executeProgram(mainProgram);

mainProgram 这个类是HepProgram , 类里维护了一个HepInstruction 的列表。

  final ImmutableList<HepInstruction> instructions;


HepInstruction 是一个抽象类, 内部有很多的静态实现, 主要的方法是

abstract void execute(HepPlanner planner);

自己在内部有多个继承他的实现类, 有单条rule 的, 只需要继承他的接口execute ,接受一个SqlPlanner 作为入参,这里其实隐含了一个访问者模式, 这个HepInstruction 的execute 方法其实是被访问者的accept 方法,只需要接受visitor 的访问, 然后传入自己, 在visitor 的方法里面让visitor 去根据方法重载, 自动选择不同的实现。

  /** Instruction that executes a given rule. */
  static class RuleInstance extends HepInstruction {
    /**
     * Description to look for, or null if rule specified explicitly.
     */
    String ruleDescription;

    /**
     * Explicitly specified rule, or rule looked up by planner from
     * description.
     */
    RelOptRule rule;

    void initialize(boolean clearCache) {
      if (!clearCache) {
        return;
      }

      if (ruleDescription != null) {
        // Look up anew each run.
        rule = null;
      }
    }

    void execute(HepPlanner planner) {
      planner.executeInstruction(this);
    }
  }

除了RuleInstance 的实现,还有rule集合的实现

  /** Instruction that executes all rules in a given collection. */
  static class RuleCollection extends HepInstruction {
    /**
     * Collection of rules to apply.
     */
    Collection<RelOptRule> rules;

    void execute(HepPlanner planner) {
      planner.executeInstruction(this);
    }
  }

还有

  /** Instruction that executes converter rules. */
  static class ConverterRules extends HepInstruction {
    boolean guaranteed;

    /**
     * Actual rule set instantiated during planning by filtering all of the
     * planner's rules, looking for the desired converters.
     */
    Set<RelOptRule> ruleSet;

    void execute(HepPlanner planner) {
      planner.executeInstruction(this);
    }
  }

而对应的访问者也就是上面说的HepPlanner ,他的重载方法我们以一个RuleInstance 的传入为例

  void executeInstruction(
      HepInstruction.RuleInstance instruction) { // istruction 里面只对应一个rule 的instance 
    if (skippingGroup()) {
      return;
    }
    if (instruction.rule == null) {
      assert instruction.ruleDescription != null;
      instruction.rule =
          getRuleByDescription(instruction.ruleDescription);
      LOGGER.trace("Looking up rule with description {}, found {}",
          instruction.ruleDescription, instruction.rule);
    }
    if (instruction.rule != null) {
      applyRules(
          Collections.singleton(instruction.rule), //调用applyRules 
          true);
    }
  }

applyRules 里面是对rule 的集合做一个遍历, 里面具体还是调用applyRule ,我们直接看applyRule 的逻辑 ,外圈的遍历是遍历rel,内圈的遍历是遍历rule ,一一匹配,任何一个rel 匹配到任何一个rule ,即fireRule 。

 Iterator<HepRelVertex> iter = getGraphIterator(root); // 将rel --> relVertex 
      while (iter.hasNext()) { // 遍历relVertex , 对每一relVertex
        HepRelVertex vertex = iter.next();
        for (RelOptRule rule : rules) {
          HepRelVertex newVertex =
              applyRule(rule, vertex, forceConversions); // 具体的应用每一个rule  
          if (newVertex == null || newVertex == vertex) {
            continue;
          }

在一开始时候我们注册了一个rule : FILTER_ON_JOIN

具体来看这个rule 的应用情况, 具体过程可以debug 看。

onMatch 的过程

applyRule 中有两个if 具体特殊的目前的案例没有涉及到,我们直接看看主流程

   applyRule: 
     // 看当前的relVertex 是否匹配具体的rule ,
           // 如果不匹配,下面return null 
           // 如果匹配,  下面 fireRule -- 里面具体调用
   
    boolean match =
        matchOperands(
            rule.getOperand(),
            vertex.getCurrentRel(),
            bindings,
            nodeChildren); 

    if (!match) {
      return null;
    }

    HepRuleCall call =
        new HepRuleCall(
            this,
            rule.getOperand(),
            bindings.toArray(new RelNode[0]),
            nodeChildren,
            parents);

    // Allow the rule to apply its own side-conditions.
    if (!rule.matches(call)) {
      return null;
    }

    fireRule(call);

fireRule 的核心逻辑:

ruleCall.getRule().onMatch(ruleCall);

由于我们此处具体调用的 是FilterToJoinRule ,可以看到这个rule的具体实现 。 可以发现这个Rule 的onMatch 方法也是一个类似观察者者模式。rule 是visitor ,我们直接进入FilterJoinRule.java 看他对onMatch 的实现。

    @Override public void onMatch(RelOptRuleCall call) {
      Filter filter = call.rel(0);
      Join join = call.rel(1);
      perform(call, filter, join);
    }
  }
  

matchOprands 这个方法有一点复杂,其实也是一个递归的过程, 将当前节点去match ,同时也要取出来他的children ,往下看是否match ,如果有任何一个不match ,即return false 。

  private boolean matchOperands(
      RelOptRuleOperand operand,
      RelNode rel,
      List<RelNode> bindings,
      Map<RelNode, List<RelNode>> nodeChildren) {
    if (!operand.matches(rel)) {
      return false;
    }
    bindings.add(rel);
    @SuppressWarnings("unchecked")
    List<HepRelVertex> childRels = (List) rel.getInputs();  // 他的child就是他的输入。
    switch (operand.childPolicy) {
    case ANY:
      return true;
    case UNORDERED: /
      // For each operand, at least one child must match. If
      // matchAnyChildren, usually there's just one operand.
      for (RelOptRuleOperand childOperand : operand.getChildOperands()) {
        boolean match = false;
        for (HepRelVertex childRel : childRels) { // 可以发现,他在遍历他的children ,去做一个match 的动作
          match =
              matchOperands( 
                  childOperand, // 对operand 取他的child 
                  childRel.getCurrentRel(),  // 对rel 也要取child 
                  bindings,
                  nodeChildren);
          if (match) { // 如果match  , 就break ,也就是说针对一个operand ,任何一个儿子是match 的,就是符合的 ,而不要求所有的child 都match
            break;
          }
        }
        if (!match) {
          return false;
        }
      }
      final List<RelNode> children = new ArrayList<>(childRels.size());
      for (HepRelVertex childRel : childRels) {
        children.add(childRel.getCurrentRel());
      }
      nodeChildren.put(rel, children);
      return true;
    default:  //FilterToJoinRule 会走到这里, Filter的childPolicy=SOME 
      int n = operand.getChildOperands().size();
      if (childRels.size() < n) {
        return false;
      }
      for (Pair<HepRelVertex, RelOptRuleOperand> pair  // 也就是匹配到LogicalSort 之后 ,由于rule 是FilterIntoJoinRule, filter的child 会是join
          : Pair.zip(childRels, operand.getChildOperands())) {  // 将(childRel,ChildOprand) 1:1 打一个pair list ,具体可以参考
        boolean match =
            matchOperands(  // 对childRel 和 childOprand 敌对进行调用matchOperands
                pair.right,
                pair.left.getCurrentRel(),
                bindings,
                nodeChildren);
        if (!match) {
          return false;
        }
      }
      return true;
    }
  }

在具体的operand.matches(rel) 这个方法中具体是怎么操作:

在debug 的过程中,当前调用这个方法的operand 是 RelOptRuleOperand, 我们这个sql , 其实在打印的过程中会发现他的结构

LogicalSort(sort0=[$0], dir0=[ASC])
  LogicalProject(USER_ID=[$0], USER_NAME=[$1], USER_COMPANY=[$5], USER_AGE=[$2])
    LogicalFilter(condition=[AND(>($2, 30), >($3, 10))])
      LogicalJoin(condition=[=($1, $4)], joinType=[inner])
        EnumerableTableScan(table=[[USERS]])
        EnumerableTableScan(table=[[JOBS]])

对于这样一个rel ,从top 往下, 可以看到第一个传入LogicalProject , 搜索这个类,可以发现class LogicalProject extends Project 对于这个方法来说就是

  public boolean matches(RelNode rel) { // 
    if (!clazz.isInstance(rel)) {  
      // class LogicalProject extends Project 的情况: 不符合
            clazz: org.apache.calcite.rel.core.Filter
            rel  : LogicalProject(input=HepRelVertex#8,ID=$0,NAME=$1)
      // 第二次进来的是:       LogicalFilter : class LogicalFilter extends Filter 
            clazz: class org.apache.calcite.rel.core.Filter
            rel :  rel#7:LogicalFilter(input=HepRelVertex#6,condition=AND(<($0, 5), =(CAST($1):VARCHAR CHARACTER SET "UTF-8" NOT NULL, 'zhang'))) 
            // 此处可以发现是rel 是clazz 的一个instance ,就是match 上了,
           
         
      
      return false;
    }
    if ((trait != null) && !rel.getTraitSet().contains(trait)) {
      return false;
    }
    return predicate.test(rel);
  }

对于下一次, FilterIntoJoinRule 的child = Join ,而LogicalFilter 的input 是 LogicJoin ,child 和child 互相匹配,最终onMatch 。可以fireRule 。

具体应用rule

创建HepRuleCall ,对于上面的FilterIntoJoinRule 的对sql 的一圈遍历以后,我们可以看到, fireRule 的输入: hepRuleCall 的结构

FilterIntoJoinRule 中

    @Override public void onMatch(RelOptRuleCall call) {
      Filter filter = call.rel(0);
      Join join = call.rel(1);
      perform(call, filter, join);
    }
    
    

具体的perform 逻辑

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值