从test 中构造这样一个sql 来分析这条rule 入口进入
String sql = "select u.id as user_id, u.name as user_name, j.company as user_company, u.age as user_age" +
" from users u join jobs j on u.name=j.name" +
" where u.age > 30 and j.id>10" +
" order by user_id";
1. SqlNode = sqlParser.parse(sql)
2. validate(sqlNode)
3. sqlNode -> relNode
4. optimize
/*******************************
* Optimizer(RelNode-->RelNode)*
*******************************/
HepProgramBuilder optBuilder = new HepProgramBuilder();
optBuilder.addRuleInstance(FilterJoinRule.FilterIntoJoinRule.FILTER_ON_JOIN);
HepPlanner optPlanner = new HepPlanner(optBuilder.build());
optPlanner.setRoot(relNode);
//应用rule ,优化sql 逻辑
relNode = optPlanner.findBestExp();
在findBestExp 的过程中,主要的调用是在executeProgram(mainProgram);
mainProgram 这个类是HepProgram , 类里维护了一个HepInstruction 的列表。
final ImmutableList<HepInstruction> instructions;
HepInstruction 是一个抽象类, 内部有很多的静态实现, 主要的方法是
abstract void execute(HepPlanner planner);
自己在内部有多个继承他的实现类, 有单条rule 的, 只需要继承他的接口execute ,接受一个SqlPlanner 作为入参,这里其实隐含了一个访问者模式, 这个HepInstruction 的execute 方法其实是被访问者的accept 方法,只需要接受visitor 的访问, 然后传入自己, 在visitor 的方法里面让visitor 去根据方法重载, 自动选择不同的实现。
/** Instruction that executes a given rule. */
static class RuleInstance extends HepInstruction {
/**
* Description to look for, or null if rule specified explicitly.
*/
String ruleDescription;
/**
* Explicitly specified rule, or rule looked up by planner from
* description.
*/
RelOptRule rule;
void initialize(boolean clearCache) {
if (!clearCache) {
return;
}
if (ruleDescription != null) {
// Look up anew each run.
rule = null;
}
}
void execute(HepPlanner planner) {
planner.executeInstruction(this);
}
}
除了RuleInstance 的实现,还有rule集合的实现
/** Instruction that executes all rules in a given collection. */
static class RuleCollection extends HepInstruction {
/**
* Collection of rules to apply.
*/
Collection<RelOptRule> rules;
void execute(HepPlanner planner) {
planner.executeInstruction(this);
}
}
还有
/** Instruction that executes converter rules. */
static class ConverterRules extends HepInstruction {
boolean guaranteed;
/**
* Actual rule set instantiated during planning by filtering all of the
* planner's rules, looking for the desired converters.
*/
Set<RelOptRule> ruleSet;
void execute(HepPlanner planner) {
planner.executeInstruction(this);
}
}
而对应的访问者也就是上面说的HepPlanner ,他的重载方法我们以一个RuleInstance 的传入为例
void executeInstruction(
HepInstruction.RuleInstance instruction) { // istruction 里面只对应一个rule 的instance
if (skippingGroup()) {
return;
}
if (instruction.rule == null) {
assert instruction.ruleDescription != null;
instruction.rule =
getRuleByDescription(instruction.ruleDescription);
LOGGER.trace("Looking up rule with description {}, found {}",
instruction.ruleDescription, instruction.rule);
}
if (instruction.rule != null) {
applyRules(
Collections.singleton(instruction.rule), //调用applyRules
true);
}
}
applyRules 里面是对rule 的集合做一个遍历, 里面具体还是调用applyRule ,我们直接看applyRule 的逻辑 ,外圈的遍历是遍历rel,内圈的遍历是遍历rule ,一一匹配,任何一个rel 匹配到任何一个rule ,即fireRule 。
Iterator<HepRelVertex> iter = getGraphIterator(root); // 将rel --> relVertex
while (iter.hasNext()) { // 遍历relVertex , 对每一relVertex
HepRelVertex vertex = iter.next();
for (RelOptRule rule : rules) {
HepRelVertex newVertex =
applyRule(rule, vertex, forceConversions); // 具体的应用每一个rule
if (newVertex == null || newVertex == vertex) {
continue;
}
在一开始时候我们注册了一个rule : FILTER_ON_JOIN
具体来看这个rule 的应用情况, 具体过程可以debug 看。
onMatch 的过程
applyRule 中有两个if 具体特殊的目前的案例没有涉及到,我们直接看看主流程
applyRule:
// 看当前的relVertex 是否匹配具体的rule ,
// 如果不匹配,下面return null
// 如果匹配, 下面 fireRule -- 里面具体调用
boolean match =
matchOperands(
rule.getOperand(),
vertex.getCurrentRel(),
bindings,
nodeChildren);
if (!match) {
return null;
}
HepRuleCall call =
new HepRuleCall(
this,
rule.getOperand(),
bindings.toArray(new RelNode[0]),
nodeChildren,
parents);
// Allow the rule to apply its own side-conditions.
if (!rule.matches(call)) {
return null;
}
fireRule(call);
fireRule 的核心逻辑:
ruleCall.getRule().onMatch(ruleCall);
由于我们此处具体调用的 是FilterToJoinRule ,可以看到这个rule的具体实现 。 可以发现这个Rule 的onMatch 方法也是一个类似观察者者模式。rule 是visitor ,我们直接进入FilterJoinRule.java 看他对onMatch 的实现。
@Override public void onMatch(RelOptRuleCall call) {
Filter filter = call.rel(0);
Join join = call.rel(1);
perform(call, filter, join);
}
}
matchOprands 这个方法有一点复杂,其实也是一个递归的过程, 将当前节点去match ,同时也要取出来他的children ,往下看是否match ,如果有任何一个不match ,即return false 。
private boolean matchOperands(
RelOptRuleOperand operand,
RelNode rel,
List<RelNode> bindings,
Map<RelNode, List<RelNode>> nodeChildren) {
if (!operand.matches(rel)) {
return false;
}
bindings.add(rel);
@SuppressWarnings("unchecked")
List<HepRelVertex> childRels = (List) rel.getInputs(); // 他的child就是他的输入。
switch (operand.childPolicy) {
case ANY:
return true;
case UNORDERED: /
// For each operand, at least one child must match. If
// matchAnyChildren, usually there's just one operand.
for (RelOptRuleOperand childOperand : operand.getChildOperands()) {
boolean match = false;
for (HepRelVertex childRel : childRels) { // 可以发现,他在遍历他的children ,去做一个match 的动作
match =
matchOperands(
childOperand, // 对operand 取他的child
childRel.getCurrentRel(), // 对rel 也要取child
bindings,
nodeChildren);
if (match) { // 如果match , 就break ,也就是说针对一个operand ,任何一个儿子是match 的,就是符合的 ,而不要求所有的child 都match
break;
}
}
if (!match) {
return false;
}
}
final List<RelNode> children = new ArrayList<>(childRels.size());
for (HepRelVertex childRel : childRels) {
children.add(childRel.getCurrentRel());
}
nodeChildren.put(rel, children);
return true;
default: //FilterToJoinRule 会走到这里, Filter的childPolicy=SOME
int n = operand.getChildOperands().size();
if (childRels.size() < n) {
return false;
}
for (Pair<HepRelVertex, RelOptRuleOperand> pair // 也就是匹配到LogicalSort 之后 ,由于rule 是FilterIntoJoinRule, filter的child 会是join
: Pair.zip(childRels, operand.getChildOperands())) { // 将(childRel,ChildOprand) 1:1 打一个pair list ,具体可以参考
boolean match =
matchOperands( // 对childRel 和 childOprand 敌对进行调用matchOperands
pair.right,
pair.left.getCurrentRel(),
bindings,
nodeChildren);
if (!match) {
return false;
}
}
return true;
}
}
在具体的operand.matches(rel) 这个方法中具体是怎么操作:
在debug 的过程中,当前调用这个方法的operand 是 RelOptRuleOperand, 我们这个sql , 其实在打印的过程中会发现他的结构
LogicalSort(sort0=[$0], dir0=[ASC])
LogicalProject(USER_ID=[$0], USER_NAME=[$1], USER_COMPANY=[$5], USER_AGE=[$2])
LogicalFilter(condition=[AND(>($2, 30), >($3, 10))])
LogicalJoin(condition=[=($1, $4)], joinType=[inner])
EnumerableTableScan(table=[[USERS]])
EnumerableTableScan(table=[[JOBS]])
对于这样一个rel ,从top 往下, 可以看到第一个传入LogicalProject , 搜索这个类,可以发现class LogicalProject extends Project 对于这个方法来说就是
public boolean matches(RelNode rel) { //
if (!clazz.isInstance(rel)) {
// class LogicalProject extends Project 的情况: 不符合
clazz: org.apache.calcite.rel.core.Filter
rel : LogicalProject(input=HepRelVertex#8,ID=$0,NAME=$1)
// 第二次进来的是: LogicalFilter : class LogicalFilter extends Filter
clazz: class org.apache.calcite.rel.core.Filter
rel : rel#7:LogicalFilter(input=HepRelVertex#6,condition=AND(<($0, 5), =(CAST($1):VARCHAR CHARACTER SET "UTF-8" NOT NULL, 'zhang')))
// 此处可以发现是rel 是clazz 的一个instance ,就是match 上了,
return false;
}
if ((trait != null) && !rel.getTraitSet().contains(trait)) {
return false;
}
return predicate.test(rel);
}
对于下一次, FilterIntoJoinRule 的child = Join ,而LogicalFilter 的input 是 LogicJoin ,child 和child 互相匹配,最终onMatch 。可以fireRule 。
具体应用rule
创建HepRuleCall ,对于上面的FilterIntoJoinRule 的对sql 的一圈遍历以后,我们可以看到, fireRule 的输入: hepRuleCall 的结构
FilterIntoJoinRule 中
@Override public void onMatch(RelOptRuleCall call) {
Filter filter = call.rel(0);
Join join = call.rel(1);
perform(call, filter, join);
}
具体的perform 逻辑