我们先看看AstBuilder中的代码:
class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging { ......其他代码 override def visitQuerySpecification( ctx: QuerySpecificationContext): LogicalPlan = withOrigin(ctx) { val from = OneRowRelation().optional(ctx.fromClause) { //如果有FROM语句,生成对应的Logical Plan visitFromClause(ctx.fromClause) } withQuerySpecification(ctx, from) } ......其他代码代码中会先判断是否有FROM子语句,有的话会去生成对应的Logical Plan,再调用withQuerySpecification()方法,而withQuerySpecification()方法是比较核心的一个方法。它会处理包括SELECT,FILTER,GROUP BY,HAVING等子语句的逻辑。
代码比较长就不贴了,有兴趣的童鞋可以去看看,大意就是使用scala的模式匹配,匹配不同的子语句生成不同的Logical Plan。
然后再来说说最终生成的LogicalPlan,LogicalPlan其实是继承自TreeNode,所以本质上LogicalPlan就是一棵树。
而实际上,LogicalPlan还有多个子类,分别表示不同的SQL子语句。
LeafNode,叶子节点,一般用来表示用户命令
UnaryNode,一元节点,表示FILTER等操作
BinaryNode,二元节点,表示JOIN,GROUP BY等操作
这里一元二元这些都是对应关系代数方面的知识,在学数据库理论的时候肯定有接触过,不过估计都还给老师了吧(/偷笑)。不过一元二元基本上也就是用来区分具体的操作,如上面说的FILTER,或是JOIN等,也不是很复杂。这三个类都位于org.apache.spark.sql.catalyst.plans.logical.LogicalPlan中,有兴趣的童鞋可以看看。而后,这三个类又会有多个子类,用以表示不同的情况,这里就不再赘述。
最后看看用一个测试案例,看看会生成什么吧。示例中简单生成一个临时的view,然后直接select查询这个view。代码如下:
val df = Seq((1, 1)).toDF("key", "value") df.createOrReplaceTempView("src") val queryCaseWhen = sql("select key from src ")最终经过parse SQL后会变成如下的内容:
'Project ['key] +- 'UnresolvedRelation `src`这个Project是UnaryNode的一个子类(SELECT自然是一元节点),表明我们要查询的字段是key。
UnresolvedRelation是一个新的概念,这里顺便说下,我们通过SQL parse生成的这棵树,其实叫Unresolved LogicalPlan,这里的Unresolved的意思说,还不知道src是否存在,或它的元数据是什么样,只有通过Analysis阶段后,才会把Unresolved变成Resolved LogicalPlan。这里的意思可以理解为,读取名为src的表,但这张表的情况未知,有待验证。
总的来说,我们的示例足够简单直接,所以内容会比较少,不过拿来学习是足够了。
下一个阶段是要使用这棵树进行分析验证了,也就是Analysis阶段,这一块留到下篇介绍吧。