Calcite RelMetadataQuery 源码阅读指南
11 May 2023Calcite 运行时通过 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 里的对应方法名的实现了。