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
PrepareAbstract base for classes that implement the process of preparing and executing SQL expressions.
---
title: Calcite Schema
---
classDiagram
Prepare <|-- CalcitePreparingStmt
CalcitePreparingStmt <|-- CalciteMaterializer
CatalogReader
CatalogReaderInterface 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中的含义
RelrelationalOptoptimizerClusterenviroment
在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的,否则将永远得不到等价节点。traitSetRelTraitSet represents anorderedset of RelTraits. 允许编辑 traitSet,但是不允许在优化期间编辑;desc返回当前节点的描述,相对于digest,desc包含了id信息;inputs当前节点的输入,不为null;cluster返回当前节点所归属的cluster。
1 | /** |
RelNode
- A
RelNodeis 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
ConverterA 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.
JdbcToEnumerableConverterRelational 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定义了节点的物理属性,以指导优化器选择适合的物理操作和执行计划。RelMultipleTraitRelMultipleTrait是一种特殊的关系特征,用于描述关系的多个特征。它可以包含多个RelTrait,例如RelCollation和RelDistribution等;RelCollationRelCollation用于描述关系的排序要求。它定义了关系中的行的排序顺序,可以指定一个或多个列以升序或降序进行排序;RelDistributionRelDistribution 定义了关系在分布式环境中的数据分布方式,例如,是否分布在多个节点上、是否进行分区等。
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。他内部包含了大量的自身的实现类,例如:
ConverterRulesInstruction that executes converter rules.MatchLimitInstruction 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 DirectedGraph<HepRelVertex, DefaultEdge>
User -->> HepPlanner : setRoot(RelNode)
HepPlanner -->> HepPlanner : addRelToGraph(RelNode)
HepPlanner -->> RelNode : getInputs()
RelNode -->> HepPlanner : List<RelNode>
loop List<RelNode>
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 介绍