Calcite 参与对象简介

Calcite 参与对象简介

本文我们来看几个 Calcite 的关键参与对象。

抽象语法树(SqlNode)

SQL 到达 Calcite 后首先会被转换为 SqlNode, 对于语法树的访问我们多依靠 SqlVistor 来进行检查和后续转处理。

sql 到语法树的转换是 parser 的处理逻辑, 而 SqlNode 自身相对比较简单这里不多介绍。

关系表达式(RelNode)

SqlNode 会被转换为 RelNode 关系表达式来进行后续 Planner 逻辑.

1) RelOptNode

RelOptNode 接口, 它代表的是能被 planner 操作的 expression node。

  • getId(): 表达式唯一数字 ID (进程启动后维度)
  • getDigest(): 简洁的描述关系表达式定义唯一性(不会包含当前算子ID但会包含子算子ID)
  • getDescription/toString: 通常是 rel#{id}:{digest} 但多带 ID 信息
  • getTraitSet(): 获取关系表达式的 traits, 这个马上后面会介绍.
  • getInput(): 获取当前关系表达式的 input 表达式们
  • getCluster(): 获取当前关系表达式所属的 cluster

2) RelNode

RelNode 代表一个 relation expression 的 node,他实现了 RelOptNode, 我们熟悉的 sort,join,projection 等都是 RelNode。

前面几篇文章中有提到过, 关系表达式可以选择在自己"出现"时注册新的 Rule, 即实现 register 方法(e.g. Cassandra 的 tableScan 出现时再注册 c* 相关的转换规则)。

每个可被实现的关系表达式都会有有对应的 RelTraitSet 来描述他的物理特性, 每个 TraitSet 中必有一个 Convention 的用于定义当前 Rel 如何将数据传递给消费 Rel, 另外还有一些其他 Trait。

关系表达式特性(RelTrait)

1) RelTraitDef

RelTraitDef 用于定义一类 RelTrait, 后面的几篇文章中会看到对于一个 TraitSet 中是根据 TraitDef 做的去重(也就是在 set 中同一种 TraitDef 只会有一个 Trait) 。

具体的 TraitDef 实现有几个需要关注的待实现方法:

  • convert(): 转换 Rel 为目标 Trait 的表达式(可以修改 Rel), 其实这个方法正想提交者所说更应该叫 shouldConvert
  • canConvert(): 检查当前关系节点的 Trait 是否能直接转换为目标 Trait(不改变 Rel 的情况下)

Calcite 默认实现的 Trait 有: Convention, Collation(排序), Distribution(分布)

这里先用 Collation 作为 RelTraitDef 的简单实现来看看(Distribution 和 Collation 非常像, Convention 稍微有些复杂本文后面有专门一小节说明), CollationTraitDef 代码位于这里

  • Collation 的 canConvert 实现是检查原始排序是否能覆盖目标排序, e.g. 原始 Trait 是"按照a , b 排序", 那可以直接将 Trait 变为"按照 a 排序"也是等价的, 所以如果需要可以直接转。
  • Collation 的 convert 实现则是添加一个 LogicalSort 的 Rel 来替换原始 Rel 达到替换输出 trait 的目的

2) RelTrait

RelTrait 是对应 TraitDef 的具体实例, 需要关注的是可以 register 方法来添加注册相关的 Rule。

Trait 对应的也定义自带有: Convention, Collation, Distribution 三个已有 Trait.

关系表达式等价集(RelSet)

1) RelSet

RelSet 是一组等价关系表达式的集合,集合中的表达式都有相同的语义, 但其中的 Rel 各自会有不同的 Trait。

RelSet 中除了一组等价关系表达式(rels)外, 还维护了:

  • 引用了当前集合中 RelNode 的其他外部 RelNode;
  • 待满足的 Converter
  • 和逻辑上对当前等价集根据不同 TraitSet 进行划分的 RelSubSet。

2) RelSubSet

RelSubSet 引用一个 RelSet,并从 Set 中根据 TraitSet 划分出逻辑上的子集(都是在getRels 才去 lazy 取子集), 此外RelSubSet本身也是一种 RelNode 。

所以在一个 RelSet 中相同的 RelTraitSet 的 RelNode 会在同一个 RelSubSet 内。

添加一个 Rel 到 RelSubSet 会添加 rel 到对应的 RelSet 中。

调用约定 ConventionTraitDef/Convention/Converter/ConverterRule

本节对 Convention 相关的几个概念进行梳理:

0) ConventionTraitDef

Convention 是一种 RelTrait 用于表示 Rel 的调用约定(calling convention),即, Rel 最后应该被如何调用; 而 ConventionTraitDef 则是 Convention 的分类定义(表现在一个 TraitSet 中只会有一个 Convention, 因为 Def 都相同) 。

前面提到过所有 TraitDef 都会实现 canConvertconvert来支持不同 Trait(这里是 Convention) 之间的转换,Convention 的转换相对复杂和之前 Collation 不同, Collation 的转换和判断逻辑都在 TraitDef 中实现(并通过abstractConverter + ExpandConversionRule 驱动执行), 而 Convention 的具体转换是 通过 ConverterRule 来实现, ConventionTraitDef 中的 canConvertconvert 用于支持 rule 的 isGuaranteed 返回 true 是支持连续转换(e.g. 当前是 A 但希望转换成 D, 中间用 a->b, b->c, c->d 三个 ConventerRule), 所以 ConventionTraitDef 会在 Planner 维度维护一个支持转换的的 Graph --- ConversionData 来寻找最短转换路径, 不过从 calcite 仓库的代码看并没有 conveterRule 用的 isGuaranteed 返回 true, 所以从代码看所有 Convention 的转换都是走的正常 rule 匹配的方式, 不过可能有别的项目用了(看 calcite 会发现比较多看上去 dead code 的东西,作为一个被其他项目用的框架有很多未知)

1) Convention

相比普通的 Trait, Convention 多了 2 个需要提供的信息:

  • Class getInterface(): 实现该 Trait 的 rel 需要遵循的接口
  • String getName(): Convention 的名字

所以我们看下几个 Convention 实现:

  • Convention#Impl: 默认的 Convention 实现, 只是提供了接口信息和名称信息, JdbcConvention, 和上篇文章中介绍的 Drill 转换 logic 和 physical rel 都是使用这个默认实现。
  • EnumerableConvention: 能返回 Enumerable 的 Convention 实现, 默认一般用这个。会对实现了 EnumerableRel接口的 rel 执行 impl, 一般是通过 codegen 方式来实现 enumerable 的关系表达式。
  • InterpretableConvention: 能返回 Enumerable 的对象数组, 同样会实现 EnumerableRel 接口和上一个相比通常不依赖于 codegen 而是 interpret 执行。
  • BindableConvention: 也是能返回 Enumerable 的对象数组, 会实现 BindableRel 接口

总结下: Convention 是一种特殊的 trait 定义了,相关 rel 必须实现的接口, 后面会利用这个接口来做 calling.

2) ConverterRule 转换规则

ConverterRule 则是可以用于将一种 Convention 转换为另一种 Convention 的 rule。

相比普通的 rule 他会检查 in/out trait 约束并且实现只需要实现转换的convert方法即可。

3) Converter 转换器

Converter 是一种特殊的 RelNode(注意是Node不是rule), 所以 Converter 理解更像一个 Adaptor 作为一个 RelNode 实现了从 input rel + trait 到 output traitSet 的转换, converter 通常作为 converterRule 的中间结果,之后被进一步 impl。

举个例子: CassandraToEnumerableConverterRule 实现了 CassandraRel#CONVENTION到转 EnumerableConvention的转换,生成了converter CassandraToEnumerableConverter 来充当 EnumerableConvention 的 rel, 后续他会被 impl 并 codegen 出实际执行的 Enumerable。

ps: Converter#getTraitDef会被设置为被转换的 Trait,之后在 ConverterRule 不会重复操作这些是 Converter 类型的 Rel。

4) AbstractConverter

Converter 中有个叫 AbstractConverter 的 Converter 比较奇特,在一些场景比如 root RelSet 中的 input 并不确定, 会先添加一个 AbstractConverter 的 Rel, 然后等待后续用 ExpandConversionRule 来调用 TraitDef 的 convert 来做转换, 根据前面的描述,一般没有 isGuaranteed 为 true 所以 Convention 相关的 rule 并不会被这个触发执行, 但类似 collation 的逻辑会就会被这个先 AbstractConverter + ExpandConversionRule 给执行。

关系表达式变换规则 RelOptRule

Calcite 中存在大量的 rule,Rule 通过指定 Rel'Class, Trait, Rel Predicate 和RelOptRuleOperand树, 来定义规则匹配的 RelNode, 并可以通过自定义 matches 方法来添加一些匹配附加条件, 最后会调用 onMatch 方法进行具体匹配后的规则变换操作。

具体的规则后续文章会针对不同场景分别介绍~

行表达式 RexNode

SqlToRelConverter 在转换 SqlNodeRelNode 的同时, 对于行表达式(SqlOperator)需要转换为 RexNode,每个 Row-Expression 都有一种特定的类型:

  • RexLiteral 普通的 Literal 类型.
  • RexVariable 有一些子类型:
  • RexCorrelVariable nested-loop joins 的关联变量
  • RexInputRef引用输入关系表达式中的一个字段
  • RexCall 对表达式或函数的调用,会被用于表示树中的非叶子节点
  • RexRangeRef引用输入关系表达式中的联系的多个字段(通常只用于 translation 中)

Rex 通常通过 RexBuilder 来构建, 并且同样通过 RexVisitorRexShuttle 来进行操作访问。

元数据信息

Calcite 在查询优化阶段依赖于 RelMetadataQueryRelMetadataProvider 来查询获取统计信息 (如何收集?后面会继续研究哈- -), DefaultRelMetadataProvider 定义了一组各类 RelMd*的 Meta 信息,meta 信息使用者 RelMetadataQuery 来查询特定类型的信息, 这个 query 实现是通过代码生成对 RelMd* 的调用来实现

ps: RelationalExpressionMetadata(Wiki) 值得一看

优化环境信息 RelOptCluster

RelOptCluster 汇集了 Planner, RexBuilder, MetadataQuery 等上面介绍的各种信息, 在 SqlNode 转换 RelNode 前创建,后面一直充当类似上下文的作用。

Planner

RelOptPlanner 通过合理运用 Rule 和 cost 对 RelNode 做语义等价转换,Calcite 中有 Hep 和 Volcano 两个 Planner 实现, 这部分后面会重点介绍~

Program

Program 用来将 planner 和 TraitSet 组合来转换 rel, 另外 Program 自己也是可以通过 Program 组合 Program 来实现对 rel 用不同 planner 不同 traitSet 的多轮处理.

总结下

  • SQL 文本会被 Parse 为 SqlNode
  • SqlNode 转换为 RelNode 和 RexNode
  • RelNode 会有 RelTraitSet, 其中的Trait由 TraitDef 定义
  • RelNode 会被 RelOptRule 匹配并转换
  • 相同语义的 RelNode 会组成 RelSet, set 中 TraitSet 相同的 rel 会归为 RelSubSet, subSet 作为一个 RelNode 会被其他 RelNode 做 input
  • Convention 是一种特殊的 Trait 规定了如何被 impl, ConverterRule 可以用于不同 Convention 的转换, 转换过程中会用特殊的 RelNode --- Converter 来协助
  • 有不同的 Planner 实现, 通过 Program 可以协调组合

本文介绍的概念比较多, 梳理下主要为了后续进一步研究哈,有问题欢迎讨论~~

编辑于 2019-03-17

文章被以下专栏收录