2021SC@SDUSC
目录
概述&&回顾
代码分析:生成QB的方法
小结:
概述&&回顾
关于语义解析AST生成QB,前面一直专注于代码阅读,内容比较散乱分裂,现在,在回顾一下HIVE编译器的流程图,我们看到,语义解析主要是把AST Tree转化为QueryBlock,那为什么要转成QueryBlock呢?从之前的分析,我们可以看到AST Tree 还是很抽象,并且也不携带表、字段相关的信息,进行语义解析,就是为了可以将AST Tree分模块存入QueryBlock 并携带对应的元数据信息,为生成逻辑执行计划做准备。
前面,我们分析到,进入sql编译之前,编译器会先判断是不是设置了hive.semantic.analyzer.hook参数,从而实现一些方法来对语句做预判,之后调用编译模块sem.analyze(tree, ctx):
其中,sem是由baseSemanticAnalyzer sem = SemanticAnalyzerFactory.get(queryState, tree) 获取的。
针对不同功能的sql,hive有多种编译方式。
比如:
Explain采用ExplainSemanticAnalyzer
DDL采用DDLSemanticAnalyzer
Load采用LoadSemanticAnalyzer等
工厂类的设置可以使这些不同的功能隔离开,增加了可扩展性,比如某天需要再添加个import数据的编译过程,开发个importSemanticAnalyzer类 在SemanticAnalyzerFactory工厂里注册一下就ok了。
然而,我们更多的是使用query,这次的源码分析也是围绕query展开,因此,我们就进入了default 选项, 此时编译主要就是用到 SemanticAnalyzer.analyzeInternal 方法。
在上文中的分析中,我们知道analyzeInternal函数最终会调用到genResolvedParseTree函数,上次只是简单看了一下它的结构,这次仔细看一下其中关于生成QB的逻辑。
代码分析:生成QB的方法经过上次的整体浏览,我们可以将生成QB的代码逻辑聚焦在下面的代码中:
Phase1Ctx ctx_1 = initPhase1Ctx(); preProcessForInsert(child, qb); if (!doPhase1(child, qb, ctx_1, plannerCtx)) { return false; } LOG.info("Completed phase 1 of Semantic Analysis");
上次说到,这部分代码主要的作用是把ASTTree 分解存入对应的QB,如果 phase1Result 错误返回false,这个结论从何得出?
代码第二行,preProcessForInsert(child, qb);调用插入前的预处理,此时qb位初始化的空白QB,很明显代码第四行:如果doPhase1执行成功那么就会得到一个QB。
下面,进入doPhase1方法:
@SuppressWarnings({"fallthrough", "nls"}) public boolean doPhase1(ASTNode ast, QB qb, Phase1Ctx ctx_1, PlannerContext plannerCtx) throws SemanticException { 。。。。。。。。。。。。。。。。略。。。。。。。。
//select类型的token
case HiveParser.TOK_SELECT:
//对qb做标记
qb.countSel(); qbp.setSelExprForClause(ctx_1.dest, ast); 。。。。。。。。。。。。。。。。。略。。。。。。
//where类型token
case HiveParser.TOK_WHERe:
//对where的孩子进行处理,之所以ast.getChild(0),因为这个是和之前的HiveParser.g结构相辅相成的。
qbp.setWhrExprForClause(ctx_1.dest, ast); if (!SubQueryUtils.findSubQueries((ASTNode) ast.getChild(0)).isEmpty()) queryProperties.setFilterWithSubQuery(true); break; 。。。。。。。。。。。。。。。。略。。。。。。。。
//下面的代码同上,匹配不同类型的token
case HiveParser.TOK_GROUPBY:
case HiveParser.TOK_ROLLUP_GROUPBY:
case HiveParser.TOK_CUBE_GROUPBY:
case HiveParser.TOK_GROUPING_SETS:
。。。。。。。。。。。。略。。。。。。。。
//遍历AST树,这里采用了递归进行调用doPhase1()函数,目的是,递归终止于于树的叶子
if (!skipRecursion) { //调用AST树的getChildCount()获取孩子节点的个数 int child_count = ast.getChildCount(); //递归调用doPhase1,遍历AST for (int child_pos = 0; child_pos < child_count && phase1Result; ++child_pos) { phase1Result = phase1Result && doPhase1( (ASTNode)ast.getChild(child_pos), qb, ctx_1, plannerCtx); } } 。。。。。。。。。。。。。。。略。。。。。。。。。
由于篇幅过多,这里省略了递归遍历时从树中信息的详细代码,下面先列举两个函数进行分析
首先是doPhase1GetColumnAliasesFromSelect()函数,该函数在doPhase1()遍历树过程中的主要作用是获取列的别名:
①private void doPhase1GetColumnAliasesFromSelect( ASTNode selectExpr, QBParseInfo qbp, String dest) throws SemanticException { if (isInsertInto(qbp, dest)) { ASTNode tblAst = qbp.getDestForClause(dest); String tableName = getUnescapedName((ASTNode) tblAst.getChild(0)); Table targetTable = null;
//条件判断,子句是否为select语句,毕竟select语句才会用到别名
try { if (isValueClause(selectExpr)) { targetTable = db.getTable(tableName, false); replaceDefaultKeyword((ASTNode) selectExpr.getChild(0).getChild(0).getChild(1), targetTable, qbp.getDestSchemaForClause(dest)); } else if (updating(dest)) { targetTable = db.getTable(tableName, false); replaceDefaultKeywordForUpdate(selectExpr, targetTable); } } catch (Exception e) { if (e instanceof SemanticException) { throw (SemanticException) e; } else { throw (new RuntimeException(e)); } } }
//获取表达式树的孩子节点计数,循环并判断相关条件
for (int i = 0; i < selectExpr.getChildCount(); ++i) { ASTNode selExpr = (ASTNode) selectExpr.getChild(i); if ((selExpr.getToken().getType() == HiveParser.TOK_SELEXPR) && (selExpr.getChildCount() == 2)) {
//获取别名,并载入QB
String columnAlias = unescapeIdentifier(selExpr.getChild(1).getText()); qbp.setExprToColumnAlias((ASTNode) selExpr.getChild(0), columnAlias); } } }
②doPhase1GetAllAggregations函数:用来获取聚合子句信息
private void doPhase1GetAllAggregations(ASTNode expressionTree,HashMapaggregations, List wdwFns, ASTNode wndParent) throws SemanticException {
//因为现在我们有了标量子查询,所以我们可以在“having”中得到子查询表达式,同时一般地,不希望在子查询中包含聚合
int exprTokenType = expressionTree.getToken().getType(); if(exprTokenType == HiveParser.TOK_SUBQUERY_EXPR) { ……… return; }小结:
本文我们主要进入doPhase1()函数,这是第一阶段的语义分析,因为我们的主要目标是将AST的数据载入QB,doPhase1这一阶段主要思想是递归地遍历AST,建立一些必要的映射关系,从而将一些关键信息传给QB,如表、子查询的别名信息、内部子句的名字、聚合 *** 作信息等,进而上面所有这些映射关系都保存在QB/QBParseInfo 中。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)