calcite入门--RBO模型和CBO模型
calcite入门--RBO模型和CBO模型
由于最近接手了公司的一些血缘解析,SQL优化相关的任务,同时也涉及到了一些DSL的开发,在开发的过程中涉及到大量的calcite规则(Rule)定制,用此文章记录一下calcite的学习心得。
解析
sql parse 的过程
flowchart LR subgraph 模版 direction LR 模板Parser.jj compoundIdentifier.ftl parserImpls.ftl config.fmpp default_config.fmpp end 模版 --> FMPP --> Parser.jj:::underline --> JavaCC --> SqlParserImpl:::underline classDef underline fill:#f9f,stroke:#333,stroke-width:4px;
我们通过 freemarker
以及 javacc
生成了一个
SalParserImpl
类,该类就是我们实际用于解析sql的类,内部包含了一个
SqlParserImplFactory
:
1 | /** |
我们可以如下使用该类
1 | SqlParser.Config c = SqlParser.config() |
Parser.jj
Parser.jj
是 calcite 的 parser 的核心定义类,会和一些
freemarker 的模板共同作为输入生成一个可以作为 JavaCC
输入的
Parser.jj
文件。
1 | options { |
可以看到,在
sqlParser.parseStmt()
中,最终是调用了parser.parseSqlStmtEof()
,也就是对应了Parser.jj
中的parseSqlStmtEof()
方法。
1 | public SqlNode parseSqlStmtEof() throws Exception { |
整体的调用链路如下所示:
flowchart TD SqlStmtEof -->|Parses an SQL statement followed by the end-of-file symbol.| SqlStmtm SqlStmtm --> SqlAlter SqlStmtm --> OrderedQueryOrExpr SqlStmtm --> Other[...] OrderedQueryOrExpr -->|expression or query with out order by| QueryOrExpr OrderedQueryOrExpr -->|optional order by, fetch, and so on| OrderByLimitOpt QueryOrExpr --> LeafQueryOrExpr LeafQueryOrExpr -->|SELECT, VALUES OR TABLE| LeafQuery LeafQueryOrExpr -->|expression| Expression LeafQuery -->|SELECT| SqlSelect LeafQuery -->|VALUES| TableConstructor LeafQuery -->|TABLE| ExplicitTable
最终到达我们的 SELECT 解析方法。
1 | /** |
validator相关
--- title: Schema --- classDiagram Schema <|-- SchemaPlus Schema <|-- AbstractSchema Schema <|-- DelegatingSchema Schema <|--JdbcSchema AbstractSchema <|-- FileSchema AbstractSchema <|-- JdbcCatalogSchema AbstractSchema <|-- MetadataSchema
Schema
A namespace for tables and functions.
A schema can also contain sub-schemas, to any level of nesting. Most providers have a limited number of levels; for example, most JDBC databases have either one level ("schemas") or two levels ("database" and "catalog").
1 | public interface Schema { |
SchemaPlus
SchemaPlus
是针对于Schema
的一些扩展;用户自定义的 Schema 不需要实现
SchemaPlus
接口,但是当Schema被传递到user-defined schema或者user-defined table时,会被包装成SchemaPlus
;SchemaPlus is intended to be used by users but not instantiated by them;
相对于
Schema
,SchemaPlus 增加了许多的add
方法用于添加 Schema、Table、Function;
1 | public interface SchemaPlus extends Schema { |
CalciteSchema
CalciteSchema
: Wrapper around user-defined schema used internally.
1
2
3
4
5
6
7
8 /** Defines a table within this schema. */
public TableEntry add(String tableName, Table table,
ImmutableList<String> sqls) {
final TableEntryImpl entry =
new TableEntryImpl(this, tableName, table, sqls);
tableMap.put(tableName, entry);
return entry;
}
--- title: Calcite Schema --- classDiagram CalciteSchema <|-- CachingCalciteSchema CalciteSchema <|-- SimpleCalciteSchema
Prepare
Prepare
Abstract base for classes that implement the process of preparing and executing SQL expressions.
--- title: Calcite Schema --- classDiagram Prepare <|-- CalcitePreparingStmt CalcitePreparingStmt <|-- CalciteMaterializer
CatalogReader
CatalogReader
Interface by which validator and planner can read table metadata.
--- title: CatalogReader --- classDiagram Wrapper <|-- SqlValidatorCatalogReader SqlValidatorCatalogReader <|-- CatalogReader RelOptSchema <|-- CatalogReader SqlOperatorTable <|-- CatalogReader
joins

sql to RelNode

calcite的数据流转
在整个过程中,我们的数据经历了从 string
->
SqlNode
-> RexNode
->
RelNode
的过程,其中 SqlNode
->
RexNode
和 RexNode
-> RelNode
都是在 SqlToRelConverter
中发生。
sequenceDiagram participant Input participant SqlParser participant SqlValidator participant SqlToRelConverter participant Planner Input -->> SqlParser : string SqlParser ->> SqlValidator : SqlNode SqlValidator -->> SqlValidator : validate(SqlNode) SqlValidator ->> SqlToRelConverter : SqlNode SqlToRelConverter -->> SqlToRelConverter : convertQuery(SqlNode) NOTE OVER SqlToRelConverter : RexBuilder && SqlNodeToRexConverter convert SqlNode to RexNode SqlToRelConverter -->> Planner : RelNode
RelNode 和 SqlNode 的区别是什么
在 Calcite 中,RelNode
和 SqlNode
,分别表示关系代数树中的节点和 SQL 解析树中的节点。
RelNode
:RelNode
表示逻辑计划的一部分。RelNode
用于表示关系代数操作,例如Scan
、Project
、Filter
、Join
等,用于优化和执行逻辑计划。SqlNode
:SqlNode
表示 SQL 查询或语句的结构,例如 SELECT 子句、FROM 子句、WHERE 子句等。SqlNode
的每个节点都对应 SQL 语法中的一个语句或表达式。SqlNode
用于解析和分析 SQL 查询,生成对应的RelNode
逻辑计划。
SqlNode 是 validator 进行校验之后,通过 Converter 生成,并提供给 Planner 使用:
1 | // parse sql node |
对于如下SQL
1 | SELECT u.id AS user_id, |
它的SqlNode可能如下(省略了大部分节点),其中每个节点都是 SqlNode 的子类。
flowchart LR SqlOrderBy --> SqlSelect SqlOrderBy --> SqlNodeList[SqlNodeList for Order by] SqlSelect --> SqlNodeList1[SqlNodeList for selectList] SqlSelect --> SqlJoin[SqlJoin for from] SqlSelect --> SqlBasicCall[SqlBasicCall for where]
它的RelNode则可能如下(展示了所有的节点),每个节点都是
RelNode
的子类HepRelVertex
flowchart TD LogicalSort["LogicalSort(sort0=[$0], dir0=[ASC])"] --> LogicalProject["LogicalProject(USER_ID=[$0], USER_NAME=[$1], USER_COMPANY=[$5], USER_AGE=[$2])"] LogicalProject --> LogicalFilter["LogicalFilter(condition=[AND(>($2, 30), >($3, 10))])"] LogicalFilter --> LogicalJoin1["LogicalJoin(condition=[=($0, $3)], joinType=[inner])"] LogicalJoin1 --> LogicalJoin2["LogicalJoin(condition=[=($0, $3)], joinType=[inner])"] LogicalJoin1 --> EnumerableTableScan3["EnumerableTableScan(table=[[USERS]])"] LogicalJoin2 --> EnumerableTableScan1["EnumerableTableScan(table=[[USERS]])"] LogicalJoin2 --> EnumerableTableScan2["EnumerableTableScan(table=[[JOBS]])"]
有一个很重要的特征是,SqlNode 树的节点非常多,这也是为什么我们只画了一部分节点,而逻辑执行计划的节点相对则非常少。而在逻辑执行计划中,多个条件可能会在一个节点中。
RelOptCluster在calcite中的含义
Rel
relationalOpt
optimizerCluster
enviroment
在calicte中,Cluster 一般用于指代 enviroment
,这里是
calcite
中一个很容易让人误解的命名(可能是由于历史因素导致)。同时,calcite也有额外的
Context
类。
1 | /** |
逻辑执行计划的节点
--- title: Logical Planner Node --- classDiagram RelOptNode <|-- RelNode Cloneable <|-- RelNode RelNode <|-- AbstractRelNode AbstractRelNode <|-- SingleRel AbstractRelNode <|-- BiRel SingleRel <|-- Filter Filter <|-- LogicalFilter Filter <|-- EnumerableFilter Filter <|-- BindableFilter SingleRel <|-- Sort Sort <|-- LogicalSort SingleRel <|-- Project Project <|-- LogicalProject BiRel <|-- Join Join <|-- LogicalJoin Join <|-- EquiJoin
RelOptNode
RelOptNode
是 planner 中的基本节点对应的
interface,所有的节点都是 RelOptNode 的子类,通过
RelOptNode
可以获取到许多 planner 需要的重要信息:
id
唯一ID,在服务开始时被创建;digest
返回一个 relational expression 的摘要
,如果两个 RelOptNode 的摘要相同,则可以视为两个节点等价(假设我们的子节点已经被标准化)。注意,digest是不能包含当前RelOptNode的id的,否则将永远得不到等价节点。traitSet
RelTraitSet represents anordered
set of RelTraits. 允许编辑 traitSet,但是不允许在优化期间编辑;desc
返回当前节点的描述,相对于digest
,desc包含了id信息;inputs
当前节点的输入,不为null;cluster
返回当前节点所归属的cluster
。
1 | /** |
RelNode
- A
RelNode
is a relational expression. Relational expressions process data, so their names are typically verbs: Sort, Join, Project, Filter, Scan, Sample. RelNode
是一个 relational expression,不同于 scalar expression(由RexNode
表示)。relational expression 通常执行结果是一个二维的表格(也就是SQL执行的返回值),而 scalar expression 表达式的执行结果通常是一个单个的值;
RelNode
也包含了很多的方法:
Convention
返回traitSet
的 calling convention trait;RelNode getInput(int i)
获取第i个输入RelNode;double estimateRowCount(RelMetadataQuery mq)
估算一个查询可能返回的行数;
AbstractRelNode
AbstractRelNode
是所有 relational expression
的基类,之所以保留 RelNode
是因为所有的 interface
都必须从另一个 interface 导出。
SingleRel
--- title: Abstract base class for relational expressions with a single input. --- classDiagram SingleRel <|-- Sort SingleRel <|-- Filter SingleRel <|-- Project SingleRel <|-- Window SingleRel <|-- Calc SingleRel <|-- Aggregate Sort <|-- LogicalSort Filter <|-- LogicalFilter Project <|-- LogicalProject Window <|-- LogicalWindow Calc <|-- LogicalCalc Aggregate <|-- LogicalAggregate
Converter
Converter
A relational expression implements the interface Converter to indicate that it converts a physical attribute, or trait, of a relational expression from one value to another.
JdbcToEnumerableConverter
Relational expression representing a scan of a table in a JDBC data source.

RelTrait
在 Calcite 中,RelTrait
是一个用于
描述节点特征的
接口
,例如节点是否排序,数据类型等。使用
RelTrait
我们可以知道优化器选择合适的执行计划:例如,如果我们的数据本身就是有序的,那么我们可以直接优化掉SQL中的
order by(如何排序和实际排序一致的话)。
在我们的实际使用中,RelTrait
只是定义特征的接口,实际的实现是由 RelTraitDef
来实现的。从某种角度来说,RelTrait 和 RelTraitDef 的关系类似于
java 里的 class
和
instance
。RelTrait作为一个接口,定义了特征的基本行为和方法,例如
getTraitDef()
用于获取trait定义、satisfies()
用于比较trait。而RelTraitDef作为一个抽象类,用于定义 trait
的规范和操作,例如 convert()
用于转换一个RelNode到另外一个RelNod。
RelTrait的结构如下所示。
--- title: RelTrait 表示关系表达式特征在特征定义中的表现。 --- classDiagram RelTrait <|-- Convention RelTrait <|-- RelMultipleTrait RelMultipleTrait <|-- RelDistribution RelMultipleTrait <|-- RelCollation

Convention
:Convention
表示关系代数树中节点的约定或物理实现方式。它描述了节点的数据布局、执行引擎、数据传输方式等。Convention
定义了节点的物理属性,以指导优化器选择适合的物理操作和执行计划。RelMultipleTrait
RelMultipleTrait是一种特殊的关系特征,用于描述关系的多个特征。它可以包含多个RelTrait,例如RelCollation和RelDistribution等;RelCollation
RelCollation用于描述关系的排序要求。它定义了关系中的行的排序顺序,可以指定一个或多个列以升序或降序进行排序;RelDistribution
RelDistribution 定义了关系在分布式环境中的数据分布方式,例如,是否分布在多个节点上、是否进行分区等。
Calling Convention
在RelNode
相关的代码注释中经常会看到Calling
Convention这个词,
从字面上理解它表示的是RelNode
该如何被调用.
这个”调用”实际上应该理解为”转换”,
一般情况下是指从逻辑算子到物理算子的转换,
如从LogicalTableScan
到EnumerableTableScan
的转换.
转化特征(Convention):属于 Trait 的子类,用于转化 RelNode 到具体平台实现(可以将下文提到的 Planner 注册到 Convention 中). 例如 JdbcConvention,FlinkConventions.DATASTREAM 等。同一个关系表达式的输入必须来自单个数据源,各表达式之间通过 Converter 生成的 Bridge 来连接。
从上文的介绍中也可以知道,
每个RelNode
都会有一个RelTraitSet
类型的变量来描述其当前的物理属性,
其中包含Convention
. 对于逻辑算子,
其Convention
一般是默认实现, 即Convention.Impl
,
而物理算子则有其对应的Convention
属性,
如EnumerableRel
包含EnumerableConvention
.
RelBuilder
RelBuilder
是Calcite中的关系算子生成器,
它提供的方法可用于快速生成绝大多数关系算子. 从整体上来看,
RelBuilder
所提供的方法可分为以下几类:
- 一类是生成
RelNode
的方法, 如scan()
,filter()
,project()
等. - 一类是生成
RexNode
的方法, 如field()
,and()
,equals()
等. - 还有其他一些辅助方法,
如生成
GROUP BY
字段的groupKey()
方法.
1 | /** |
SqlToRelConverter
初始化 SqlToRelConverter
1 | private static void initConverter() { |
以以下SQL作为输入
1
2 SELECT *
FROM `USERS`在经过 validator 验证之后,会被改写为:
1
2 SELECT `USERS`.`id`, `USERS`.`name`, `USERS`.`age`
FROM `x`.`USERS` AS `USERS`改写后的SQL会作为 SqlToRelConverter 的输入
--- title: SqlToRelConverter --- flowchart TD convertQuery --> convertQueryRecursive -->|SELECT| convertSelect convertSelect -->|getWhereScope| SqlValidatorScope convertSelect -->|createBlackboard| Blackboard convertSelect --> convertSelectImpl convertSelectImpl -->|1| convertFrom convertSelectImpl -->|2| convertSelectList convertFrom -->|1. REMOVE AS| convertFrom convertFrom -->|2| convertIdentifier convertIdentifier --> setRoot convertSelectList --> RelBuilder.projectNamed
优化
RBO
规则的定义需要Pattern
和Substitution
,
其中Pattern表示规则可匹配的查询子树,
而Substitution表示与Pattern等价的关系表达式
Column Pruning

predict pushdown

CBO
基于成本的优化有两个核心依赖, 即:
- 统计信息, 例如表的行数, 列索引上唯一键的数量等.
- 成本模型, 在单机环境下一般考虑IO, CPU, 内存成本; 在分布式环境下还需考虑网络成本.
Calcite查询优化器实现
--- title: Calcite查询优化器实现 --- classDiagram RelOptPlanner <|-- AbstractRelOptPlanner AbstractRelOptPlanner <|-- HepPlanner AbstractRelOptPlanner <|-- VolcanoPlanner
查询优化器相关概念和数据结构
无论是RBO还是CBO, 实际都是由规则驱动的.
Calcite中已经实现了上百种优化规则, 从优化内容来看可以分为以下两种类型:
TransformationRule
: 用于将一种逻辑表达式转换为另一种等价的逻辑表达式. 对应Cascades中的Transformation Rule, 是一个独立的接口, 并不继承于RelOptRule
. 需要注意的是TransformationRule
接口仅在VolcanoPlanner中有效, HepPlanner会直接忽略这一接口.ConverterRule
: 用于将一种Calling Convention的表达式转换为另一种Calling Convention的表达式, 可用于将逻辑表达式转换为物理表达式. 对应Cascades中的Implementation Rule,ConverterRule
继承于RelOptRule
.
定义一个规则至少需要两部分信息, 即Pattern和Sustitution, 在Calcite中:
- Pattern由
RelOptRuleOperand
实现, 用于表示该规则可匹配的表达式结构. - Substitution表示该规则可产生的逻辑等价表达式,
在函数
onMatch()
中实现.
FilterMergeRule
FilterMergeRule
是一个简单的TransformationRule
实例,它将查询树种的上下两个Filter
算子合并为一个。> --- title: FilterMergeRule --- flowchart LR beforeOpt --> afterOpt subgraph beforeOpt Filter0[topFilter] --> Filter1[bottomFilter] --> anyInputs end subgraph afterOpt Filter2Input[anyInputs] --> Filter2["topFilter.condition()"] Filter2Input[anyInputs] --> Filter3["bottomFilter.condition()"] end可以看到,我们的代码有如下特点:
- 在
matches
中匹配,在onMatch
中转换;FilterMergeRule
的构造函数为protected
,只能通过FilterMergeRule#Config#DEFAULT.toRule()
构造;FilterMergeRule#Config#withOperandSupplier
有一个default
方法,通过它可以指定具体匹配的节点类型。
1 | /* |
ConverterRule
ConverterRule
可用于在逻辑算子和物理算子之间进行转换, 基类的onMatch()
方法会调用子类的convert()
方法实现转换.
1 | /** |
EnumerableFilterRule
EnumerableFilter
是一种 对EnumerableConvention
调用约定的Filter
操作的实现。在 Calcite 中,Enumerable 是一种计算模型和调用约定,它将 关系代数操作映射到迭代器模型,以便在内存中进行计算。在 Enumerable 调用约定中,查询计划将转换为一系列迭代器操作,每个操作都返回一个迭代器,用于按需获取结果。
Filter(过滤)操作是关系代数中的一种基本操作,用于根据给定的谓词条件从输入行集中选择满足条件的行。
而 Implementation of Filter in enumerable calling convention 指的是在基于 Enumerable 调用约定的计算模型中,对 Filter 操作的具体实现方式。
在下面的代码中,EnumerableFilterRule 通过
DEFAULT_CONFIG
声明了一个如下的ConverterRule
。> --- title: EnumerableFilterRule --- flowchart TD from --> to subgraph from direction LR InTraits --> IntraitsValue[Convention.NONE] OutTraits --> OutTraitsValue[EnumerableConvention.INSTANCE] OperandSupplier --> LogicalFilter -->|with predict| PredictValue["f -> !f.containsOver()"] end subgraph to EnumerableFilter end
1 | /** |
withConversion
1 | default <R extends RelNode> Config withConversion(Class<R> clazz, |
HepRelVertex
在
HepPlanner
内部, 会将待优化的RelNode
算子树转化为一个有向无环图(DAG)
1 | public class HepRelVertex extends AbstractRelNode implements DelegatingMetadataRel { |
HepInstruction && HepState
HepInstruction
HepInstruction represents one instruction in a HepProgram. The actual instruction set is defined here via inner classes; if these grow too big, they should be moved out to top-level classes
1 | abstract class HepInstruction { |
HepState
Able to execute an instruction or program, and contains all mutable state for that instruction.
The goal is that programs are re-entrant. they can be used by more than one thread at a time. We achieve this by making instructions and programs immutable. All mutable state is held in the state objects.
1 | abstract class HepState { |
Summary
--- title: Hep Instruction Sets --- classDiagram HepInstruction <|-- HepProgram HepInstruction <|-- RuleCollection HepInstruction <|-- ConverterRules HepInstruction <|-- MatchOrder HepInstruction <|-- MatchLimit HepInstruction <|-- BeginGroup
HepInstruction
表示HepProgram
中的一条指令,实际指令集在HepInstruction.java
中通过内部类定义,主要用于optimizer的优化规则。
HepInstruction
是一个抽象类,他只包含了一个方法prepare()
用于初始化 instruction 的 runtime state。他内部包含了大量的自身的实现类,例如:
ConverterRules
Instruction that executes converter rules.MatchLimit
Instruction that sets match order.- 而且,一般来说,每个 HepInstruction 的实现类,都会在内部实现一个
HepState
接口用来管理自身的 runtime state。HepInstruction 内部还有一个
PrepareContext
,这个类的实例是State prepare(PrepareContext)
方法的参数,很明显是用来初始化 State 的,内部也只有三个成员变量:
- HepPlanner
- HepProgram.State
- EndGroup.State
Example
在我们的实际应用中,我们不会直接使用类似于
new MatchLimit(10)
之类的方式来初始化一个 HepInstrction,而是使用 HepProgramBuilder() 来初始化:
1
2
3
4
5
6
7
8
9
10
11
12
13 /**
* Adds an instruction to change the order of pattern matching for
* subsequent instructions. The new order will take effect for the rest of
* the program (not counting subprograms) or until another match order
* instruction is encountered.
*
* @param order new match direction to set
*/
public HepProgramBuilder addMatchOrder(HepMatchOrder order) {
checkArgument(group < 0);
return addInstruction(new HepInstruction.MatchOrder(order));
}
1 | private static final String UNION_TREE = |
HepProgram && HepProgramBuilder
HepProgram specifies the order in which rules should be attempted by HepPlanner. Use HepProgramBuilder to create a new instance of HepProgram.
Note that the structure of a program is immutable, but the planner uses it as read/ write during planning, so a program can only be in use by a single planner at a time.
1 | public class HepProgram extends HepInstruction { |
HepPlanner
Example
1 | HepProgramBuilder hepProgramBuilder = new HepProgramBuilder(); |
HepPlanner 的优化分为两步
sequenceDiagram participant HepPlanner HepPlanner -->> HepPlanner : setRoot() HepPlanner -->> HepPlanner : findBestExp()
setRoot()
sequenceDiagram participant User participant HepPlanner participant RelNode participant HepRelVertex participant DirectedGraph as DirectedGraphUser -->> HepPlanner : setRoot(RelNode) HepPlanner -->> HepPlanner : addRelToGraph(RelNode) HepPlanner -->> RelNode : getInputs() RelNode -->> HepPlanner : List loop List NOTE OVER HepPlanner : depth first HepPlanner -->> HepPlanner: addRelToGraph(RelNode)! end HepPlanner -->> RelNode : recomputeDigest() HepPlanner -->> HepRelVertex : new HepRelVertex -->> HepPlanner : HepRelVertex HepPlanner -->> DirectedGraph : addVertex(HepRelVertex) HepPlanner -->> HepPlanner : updateVertex(newVertex, oldRel) NOTE OVER HepPlanner : notify discard && replace old rel by new rel User -->> HepPlanner : findBestExp() HepPlanner -->> User : RelNode
findBestExp()
1 | public RelNode findBestExp() { |
随后进入我们核心的
executeProgram
方法,这个方法中我们会初始化HepState
,并基于HepState
执行HepInstruction
。在这里,我们再回忆一下在HepState中提到:
The goal is that programs are re-entrant - they can be used by more than one thread at a time. We achieve this by making instructions and programs immutable. All mutable state is held in the state objects.
1 | private void executeProgram(HepProgram program) { |
program.prepare(px)
使用HepProgramBUilder
时添加的 HepInstruction 构造了一个 HepState,将所有的HepInstruction转换为了对应的HepState便于后续的执行。
1 | class State extends HepState { |
state.execute()
逻辑非常简单,就是单纯的遍历 instructionState 并执行对应的execute()
1 | void executeProgram(HepProgram instruction, HepProgram.State state) { |
所以对于我们
Example
中的代码,执行逻辑就很简单了
1
2
3
4
5
6
7
8
9
10
11
12 HepProgramBuilder hepProgramBuilder = new HepProgramBuilder();
// 添加优化规则
hepProgramBuilder.addRuleInstance(CoreRules.FILTER_TO_CALC);
hepProgramBuilder.addRuleInstance(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
hepProgramBuilder.addRuleInstance(EnumerableRules.ENUMERABLE_CALC_RULE);
// 1. 构建HepPlanner
HepPlanner planner = new HepPlanner(hepProgramBuilder.build());
// 2. 构建DAG
planner.setRoot(root);
// 3. 执行优化
RelNode optimizedRoot = planner.findBestExp();整个过程就是:
- HepProgramBuilder 添加 HepInstruction,并生成 HepProgram;
- 使用 HepProgram 生成 HepPlanner;
- HepPlanner 调用 findBestExp() -> executeProgram(program);这里的program就是刚才传进来的 HepProgram;
- HepProgram 初始化
HepProgram#State
,HepProgram#State
内部将所有 <1> 中添加的 HepInstruction 转换为 HepState;- 调用
HepProgram#State#execute()
->HepPlanner.executeProgram()
执行完毕。
sequenceDiagram participant HepProgramBuilder participant HepProgram participant HepPlanner participant HepProgram#State HepProgramBuilder -->> HepProgramBuilder : addRuleInstance/addMatchLimit/... NOTE OVER HepProgramBuilder : HepInstrucionts HepProgramBuilder -->> HepProgram : build() HepProgram -->> HepPlanner : new() NOTE OVER HepPlanner : HepInstrucionts NOTE OVER HepPlanner : findBestExp() NOTE OVER HepPlanner : executeProgram() HepPlanner -->> HepProgram#State : prepare(HepPlanner) loop HepInstructions HepProgram#State -->> HepInstruction: prepare() HepInstruction -->> HepProgram#State : HepState end NOTE OVER HepProgram#State : HepStates NOTE OVER HepProgram#State : execte() HepProgram#State -->> HepPlanner : executeProgram(HepProgram.this, this) loop HepStates HepPlanner -->> HepPlanner : instructionState.execute() end
相对来说,如果代码写成如下形式将更加直观:
1 | public RelNode findBestExp() { |
以RuleInstance作为实例说明HepInstruction的优化规则
从前面的代码中,我们发现RBO的优化其实逻辑非常简单,就是为每个规则生成一个HepInstruction并遍历执行,为了保证HepInstruction的可重入性(re-entrant),我们增加了一个额外的HepState来存储状态。所以,实际的优化其实是定义在HepInstruction中的。
1 | /** Rule that combines two {@link LogicalFilter}s. */ |
在以上代码中,我们添加了 FilterMergeRule
并执行,它是
RelOptRule
的子类,基于 matches()
和
onMatch()
方法将一个epxression转换为另外的expression。
而RuleInstance#State
的execute()方法,最终会执行到HepPlanner.applyRules()
。
1 | private void applyRules(HepProgram.State programState, |
引用
- https://github.com/0x822a5b87/test-calcite
- calcite-demo : calcite adapter and calcite optimizer and so on
- Apache Calcite 处理流程详解(一)
- Apache Calcite 优化器详解(二)
- SQL 查询优化原理与 Volcano Optimizer 介绍
- Apche Calcite查询优化概述
- Apache Calcite关系代数
- Apache Calcite查询优化器之HepPlanner
- Apache Calcite查询优化器之VolcanoPlanner
- SQL 查询优化原理与 Volcano Optimizer 介绍