3.3算子DAG的生成
在第一章1.4节我们提到Hive首先将抽象语法树AST转换成查询块(QueryBlock),然后在将QueryBlock转化成算子DAG。
3.3.1QueryBlock生成
1) QB数据结构
QueryBlock数据结构定义在QB类中,QB的主要成员变量如下:
HashMap<String, String> aliasToTabs;
HashMap<String, QBExpr> aliasToSubq;
HashMap<String, Map<String, String>> aliasToProps;
List<String> aliases;
QBParseInfo qbp;
QBMetaData qbm;
QBJoinTree qbjoin;
aliasToTabs: 表别名和表名映射关系。
aliasToSubq:子查询语句别名和子查询语句映射关系。其中QBExpr表示子查询语句的集合运算,Hive中定义了以下几种运算类型:
(1) NULLOP:表示单个子查询语句,没有任何运算。其QBExpr#qb字段表示子查询语句的QB。
(2) UNION:表示union运算,包含两个QBExpr对象,分别表示参与运算的两个子查询语句。
(3) INTERSECT:表示表的交集运算,目前尚未实现。
(4) EXCEPT:表示表的差集运算,目前尚未实现。
aliasToProps:表别名和表属性映射关系。
aliases:语句中所有表和子语句的别名。
qbp:QBParseInfo类型,记录该语句相关的信息。主要包括该语句是否为子查询语句、表别名、join语句语法树、where语句语法树、select语句语法树以及body语句的语法树(groupby、where,limit,clusterby,order by等语句)。insert语句还包含insert 目的数据源表名称列表。注意:由于HiveQL insert语句中可以包含多个insert分支(参考第二章insert语法树部分的介绍),每个分支包含select语句以及body语句,因此QBParseInfo使用hash表存放每个分支的select语句语法树以及body语句语法树。其中,hash表的key是分支的名称(Hive称之为dest,即insert的目的地名称),value为语法树。
qbm:QBMetaData类型,存放语句中表和分区的元数据信息。
qbjoin:QBJoinTree类型,记录Join树结构信息。第二章介绍过,join树是一颗左型树,QBJoinTree与之对应也是一棵左型树。关于QBJoinTree的具体介绍,参见下文。
2)QB的生成
Hive通过遍历抽象语法树(AST)生成QB对象,这一部分的核心代码位于SemanticAnalyzer#doPhase1方法中,伪代码如下:
doPhase1(ASTNode ast,dest,QB qb){
QBParseInfo qbb=qb.getParseInfo()//存放语句相关信息
Boolean recursion=false;//ast节点是否需要递归处理
switch(ast.nodeType) {
case TOK_DESTINATION:
dest=newInsertName(); //生成新分支名称
qpb.setDest(dest,ast) //保存当前insert分支的目标数据源
case TOK_SELECT:
//保存当前insert分支的select表达式
qpb.setSelExpr(dest,ast)
//保存当前insert分支的聚合函数表达式
qpb.setAggregationExpr(dest,getAggFunc(ast))
case TOK_WHERE:
//保存当前insert分支的where表达式
qbp.setWhereExpr(dest,ast)
case TOK_GROUPBY:
//保存当前insert分支的group by表达式
qbp.setGroupByExpr(dest,ast)
case TOK_LIMIT:
//保存目标insert分支的limit语句
qbp.setDestLimit(dest,ast)
case 其他body语句节点(sortby,orderby等):
保存相应内容到当前的insert分支下,这里省略。
case TOK_FROM:
fromSrc=ast.getChild(0)
if(fromSrc==TOK_TABREF) {
processTable(qb,fromSrc)
} else if(fromSrc==TOK_SUBQUERY) {
processSubQuery(qb,fromSrc)
} else if(isJoin(fromSrc) {
processJoin(qb,frm)
qb.setJoinExpr(fromSrc)//保存join AST树
} else 其他fromSrc …
case 其他节点:
//其他节点递归处理
recursion=true
}
if(recursion)
for(child:ast.getChildren()) {
doPhase1(child,dest,qb)
}
}
doPhase1方法根据ast节点的类型提取相应节点的信息,保存到qb对象中。如对于TOK_SELECT节点,保存select表达式以及聚合函数信息。遇到TOK_DESTINATION节点时,生成新分支名称,同时保存目标数据源信息(目标数据源表名称,临时文件路径等)。后续TOK_SELECT节点和body语句节点(TOK_GROUPBY,TOK_LIMIT等)的信息保存在该Insert分支下面。
对于TOK_FROM节点的处理比较复杂。第二章讲过,from的数据源包含多种类型,