Hive代码分析报告(九):语义分析④

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,HashMap<String, ASTNode> aggregations, List<ASTNode> 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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值