Calcite sql2rel 过程
sql2rel的过程是将SqlNode 转化成RelNode的过程
在 SqlToRelConverterTest
中添加样例测试
@Test void testScan() {
String sql = "SELECT * FROM EMP WHERE empno < 10";
sql(sql).ok();
}
会生成如下的relnode tree.
LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
LogicalFilter(condition=[<($0, 10)])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
Validator
registerQuery
主要实现类是在SqlValidatorImpl
org.apache.calcite.sql.validate.SqlValidatorImpl#registerQuery()
主要做的事就是遍历这个query将各个部分的SqlNode对应的SqlValidatorScope
以及SqlValidatorNamespace
保存起来,供后续validate和sql2rel阶段使用。
这两个概念看起来有点重复,看注解的意思应该是scope主要负责name-resolution,将一个name 解析成相应的namespace来
* Name-resolution scope. Represents any position in a parse tree than an
* expression can be, or anything in the parse tree which has columns.
*
* <p>When validating an expression, say "foo"."bar", you first use the
* {@link #resolve} method of the scope where the expression is defined to
* locate "foo". If successful, this returns a
* {@link SqlValidatorNamespace namespace} describing the type of the resulting
* object.
而namespace描述了SqlNode所对应的relation,如返回类型
* A namespace describes the relation returned by a section of a SQL query.
在convertQuery完成后就注册了整个Query的各个SqlNode的映射关系
validate
注册完成后会执行validate阶段,这部分会涉及语义,类型等校验,比如校验select的where条件
- condition中没有aggregate或window aggregate
- condition Node 自身的校验逻辑(最终还是会路由到SqlValidator的实现上)
- 校验返回类型是boolean的
校验完成之后就会调用下面的接口开始进行toRel的转化
SqlToRel
转化的入口
org.apache.calcite.sql2rel.SqlToRelConverter#convertQuery
这里面最重要的就是通过 convertQueryRecursive
递归的进行转化
convertSelect
SELECT `EMP`.`EMPNO`, `EMP`.`ENAME`, `EMP`.`JOB`, `EMP`.`MGR`, `EMP`.`HIREDATE`, `EMP`.`SAL`, `EMP`.`COMM`, `EMP`.`DEPTNO`, `EMP`.`SLACKER`
FROM `CATALOG`.`SALES`.`EMP` AS `EMP`
WHERE `EMP`.`EMPNO` < 10
我们这个query的主体是一个SqlSelect
所以会通过convertSelect来转化
public RelNode convertSelect(SqlSelect select, boolean top) {
final SqlValidatorScope selectScope = validator().getWhereScope(select);
final Blackboard bb = createBlackboard(selectScope, null, top);
convertSelectImpl(bb, select);
return castNonNull(bb.root);
}
封装了两个对象 SqlValidatorScope 和 Blackboard。最后我们转化完成的RelNode的root会保存在blackboard中,也就是bb.root.
convertFrom
`CATALOG`.`SALES`.`EMP` AS `EMP`
convertSelect的第一步就是先转化From子句, from 子句有很多种可能, 例如以下这些
a single table ("SALES.EMP"),
an aliased table ("EMP AS E"),
a list of tables ("EMP, DEPT"),
an ANSI Join expression ("EMP JOIN DEPT ON EMP.DEPTNO = DEPT.DEPTNO"),
a VALUES clause ("VALUES ('Fred', 20)"),
a query ("(SELECT * FROM EMP WHERE GENDER = 'F')"),
or any combination of the above.
这个例子里是一个 CATALOG.SALES.EMP
的 SqlIdentifer 然后就会根据这个identifier通过CatalogReader查找这个identifier所表示的table。然后会基于此创建出LogicalTableScan
并将该RelNode设置到 BlackBoard中.
convertWhere
`EMP`.`EMPNO` < 10
通过Blackboard#convertExpression
将上述的SqlBasicCall转化成RexCall
(where条件中的是一个rex表达式)
然后会基于Blackboard的当前的root作为input创建一个LogicalFilter
节点
final RelNode filter =
filterFactory.createFilter(bb.root(), convertedWhere2, ImmutableSet.of());
final RelNode r;
final CorrelationUse p = getCorrelationUse(bb, filter);
if (p != null) {
assert p.r instanceof Filter;
Filter f = (Filter) p.r;
r =
LogicalFilter.create(f.getInput(), f.getCondition(),
ImmutableSet.of(p.id));
} else {
r = filter;
}
bb.setRoot(r, false);
这里会先基于前面创建的RexCall创建出filter节点,但是还会看一下这个filter中是否有 correlation variables. 如果有的话需要将这个CorrelationId 添加到Filter中, 这个样例中并没有使用。
到这里RelNode tree 已经是
LogicalFilter(condition=[<($0, 10)])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
Correlation
@Test void testCorrelationInWithSubQuery() {
String sql = "select deptno\n"
+ "from emp\n"
+ "where deptno in (select deptno\n"
+ " from dept\n"
+ " where emp.deptno = dept.deptno\n"
+ " and emp.deptno = dept.deptno)";
sql(sql).withExpand(false).withDecorrelate(false).ok();
}
上面提到在convertWhere产生Filter的时候需要处理是否有Correlation variable的场景,而这段sql就会产生这种case。correlation其实就是关联的意思,而这里where in 子句中引用了外层的emp表,所以是一个关联子查询
在WHERE的 in 的子查询中 from 只有 dept表,但是 条件引用了外层查询的emp表,所以会产生一个关联的语义。而后续可以查到Filter条件中引用了Correlation Variable的。
创建关联关系的阶段在解析SqlIdentifier
时,当此identifier所对应的inputs不为空,并且identifier对应的scope不是parent scope的话namespace就意味着是对某个表的引用,而其他情况就意味着这个表可能还没有toRel,表明是from了一个correlated的item
所以创建了一个 DefferredLookup
LogicalProject(DEPTNO=[$7])
LogicalFilter(condition=[IN($7, {
LogicalProject(DEPTNO=[$0])
LogicalFilter(condition=[AND(=($cor0.DEPTNO, $0), =($cor0.DEPTNO, $0))])
LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
})], variablesSet=[[$cor0]])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
convertSelectList
中间还有convert Orderby,convert Aggregate的操作,不过这个样例中没有,最后就是转化selectList,selectList会被转成Project
LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
LogicalFilter(condition=[<($0, 10)])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
这样通过不断递归convert,完成了SqlNode到RelNode的转化,中间会借助RelBuilder
来创建各个RelNode 并通过BlackBoard维护当前的root节点,用于构建整个tree的结构
本文来自博客园,作者:Aitozi,转载请注明原文链接:https://www.cnblogs.com/Aitozi/p/17487816.html