YON CISE

Calcite RelMetadataQuery 源码阅读指南

Calcite 运行时通过 RelMetadataQuery 来获取一些元信息来辅助进行 SQL 优化。例如获取 Rel 节点的输出行数和执行的 cost。但是 RelMetadataQuery 是通过 JaninoRelMetadataProvider 来提供具体逻辑的,JaninoRelMetadataProvider 的具体实现是动态生成代码再编译的,导致 Debug 和看源码都不是很方便。简单记录下看 RelMetadataQuery 具体功能实现的方法。

首先,RelMetadataProvider 是设置在一个 ThreadLocal 里的,代码见:org.apache.calcite.plan.RelOptCluster#setMetadataProvider

运行时第一次获取元信息会触发对应元信息的 Provider 的代码生成和编译,例如获取 Rel 节点输出行数:

public /* @Nullable: CALCITE-4263 */ Double getRowCount(RelNode rel) {
    for (;;) {
        try {
            // 第一次调用会抛出异常
            Double result = rowCountHandler.getRowCount(rel, this);
            return RelMdUtil.validateResult(castNonNull(result));
        } catch (MetadataHandlerProvider.NoHandler e) {
            // revise 里会进行代码生成和编译
            rowCountHandler = revise(BuiltInMetadata.RowCount.Handler.class);
        }
	}
}

最后会在 Guava cache 里调用 org.apache.calcite.rel.metadata.JaninoRelMetadataProvider#generateCompileAndInstantiate 方法,代码生成的逻辑就是从 org.apache.calcite.rel.metadata.DefaultRelMetadataProvider#DefaultRelMetadataProvider 里找到对应的 Handler,然后根据 Handler 里的方法名和入参类型生成代码。

例如,为了获取节点在优化过程中的累加 Cost,那么会找到 RelMdPercentageOriginalRows,然后获取累加 Cost 的方法名是 getCumulativeCost,那么生成代码的时候就会基于 RelMdPercentageOriginalRows里的所用名称叫 getCumulativeCost 的方法来生成代码,最终会生成

package org.apache.calcite.rel.metadata.janino;

public final class GeneratedMetadata_CumulativeCostHandler
  implements org.apache.calcite.rel.metadata.BuiltInMetadata.CumulativeCost.Handler {
  private final Object methodKey0 =
      new org.apache.calcite.rel.metadata.janino.DescriptiveCacheKey("RelOptCost Handler.getCumulativeCost()");
  public final org.apache.calcite.rel.metadata.RelMdPercentageOriginalRows$RelMdCumulativeCost provider0;
  public GeneratedMetadata_CumulativeCostHandler(
      org.apache.calcite.rel.metadata.RelMdPercentageOriginalRows$RelMdCumulativeCost provider0) {
    this.provider0 = provider0;
  }
  public org.apache.calcite.rel.metadata.MetadataDef getDef() {
    return provider0.getDef();
  }
  public org.apache.calcite.plan.RelOptCost getCumulativeCost(
      org.apache.calcite.rel.RelNode r,
      org.apache.calcite.rel.metadata.RelMetadataQuery mq) {
    while (r instanceof org.apache.calcite.rel.metadata.DelegatingMetadataRel) {
      r = ((org.apache.calcite.rel.metadata.DelegatingMetadataRel) r).getMetadataDelegateRel();
    }
    final Object key;
    key = methodKey0;
    // 缓存相关代码
    final Object v = mq.map.get(r, key);
    if (v != null) {
      if (v == org.apache.calcite.rel.metadata.NullSentinel.ACTIVE) {
        throw new org.apache.calcite.rel.metadata.CyclicMetadataException();
      }
      if (v == org.apache.calcite.rel.metadata.NullSentinel.INSTANCE) {
        return null;
      }
      return (org.apache.calcite.plan.RelOptCost) v;
    }
    mq.map.put(r, key,org.apache.calcite.rel.metadata.NullSentinel.ACTIVE);
    try {
      final org.apache.calcite.plan.RelOptCost x = getCumulativeCost_(r, mq);
      mq.map.put(r, key, org.apache.calcite.rel.metadata.NullSentinel.mask(x));
      return x;
    } catch (java.lang.Exception e) {
      mq.map.row(r).clear();
      throw e;
    }
  }

  private org.apache.calcite.plan.RelOptCost getCumulativeCost_(
      org.apache.calcite.rel.RelNode r,
      org.apache.calcite.rel.metadata.RelMetadataQuery mq) {
    // 根据入参类型来进行调用
    if (r instanceof org.apache.calcite.adapter.enumerable.EnumerableInterpreter) {
      return provider0.getCumulativeCost((org.apache.calcite.adapter.enumerable.EnumerableInterpreter) r, mq);
    } else if (r instanceof org.apache.calcite.rel.RelNode) {
      return provider0.getCumulativeCost((org.apache.calcite.rel.RelNode) r, mq);
    } else {
            throw new java.lang.IllegalArgumentException("No handler for method [public abstract org.apache.calcite.plan.RelOptCost org.apache.calcite.rel.metadata.BuiltInMetadata$CumulativeCost$Handler.getCumulativeCost(org.apache.calcite.rel.RelNode,org.apache.calcite.rel.metadata.RelMetadataQuery)] applied to argument of type [" + r.getClass() + "]; we recommend you create a catch-all (RelNode) handler");
    }
  }

}

所以剩下来就是看具体的 Handler 里的对应方法名的实现了。