YON Blog

Calcite Project 下沉

很符合直觉的一种优化手段,就像谓词下推一样把 Filter 下沉到 TableScan 节点以获得更好的性能。

但是 Project 的下沉在实现上却有点不一样,它不是在优化阶段做的,而是在 SqlNode 构建出 RelNode 后,优化阶段前做的。为什么不是像谓词下推一样在优化阶段做呢?

原因是优化阶段对节点的匹配是基于子节点来匹配的,也就是说你写优化规则的时候只能基于一个节点本身的特征以及它子节点的特征来匹配。但是 Project 的下沉是需要基于一个节点的父节点使用了哪些字段来决定是不是增加 Project 节点来进行字段过滤,这个原因导致无法在优化节点进行 Project 下沉这一优化。

Calcite Project 下沉代码的入口是:

// org.apache.calcite.prepare.Prepare#prepareSql(org.apache.calcite.sql.SqlNode, org.apache.calcite.sql.SqlNode, java.lang.Class, org.apache.calcite.sql.validate.SqlValidator, boolean)
public PreparedResult prepareSql(
  SqlNode sqlQuery,
  SqlNode sqlNodeOriginal,
  Class runtimeContextClass,
  SqlValidator validator,
  boolean needsValidation) {
    // ...
    // Trim unused fields.
    root = trimUnusedFields(root);
    // ...
}       

最终具体逻辑在 org.apache.calcite.sql2rel.RelFieldTrimmer#trim 里。

最新的 1.32 的版本里,对 Sort 节点(limit、offset 属于 Sort 节点)的 Project 处理中是不支持动态参数的,所以如果你的 limit 和 offset 的具体值是通过 JDBC 的参数传递的,那么 Calcite 就不会进行 Project 下沉的优化,不过看了下代码,之所以不支持只是一个历史遗留问题,所以给社区提了个 issue [1],本来想自己改的,不过已经有国人提交 PR 了,挺好。

// org.apache.calcite.sql2rel.RelFieldTrimmer#trimFields(org.apache.calcite.rel.core.Sort, org.apache.calcite.util.ImmutableBitSet, java.util.Set<org.apache.calcite.rel.type.RelDataTypeField>)

// leave the Sort unchanged in case we have dynamic limits
if (sort.offset instanceof RexDynamicParam
    || sort.fetch instanceof RexDynamicParam) {
  return result(sort, inputMapping);
}

[1] RelFieldTrimmer support Sort with dynamic param