2021SC@SDUSC
目录
概述
上一篇文章中,我分析了doPhase1()函数,这是语义分析的起始阶段,程序的最终目标是将AST的数据载入QB,doPhase1这一阶段主要思想是递归地遍历AST,建立一些必要的映射关系,从而将一些关键信息传给QB,如表、子查询的别名信息、内部子句的名字、聚合操作信息等,进而上面所有这些映射关系都保存在QB/QBParseInfo 中。
补充说明doPhase1()
再来看一次此处代码的大致结构和工作流程
public boolean doPhase1(ASTNode ast, QB qb, Phase1Ctx ctx_1, PlannerContext plannerCtx)
throws SemanticException {
。。。。。。。。。。。。。。。。略。。。。。。。。
case HiveParser.TOK_SELECT://select类型的token
qb.countSel();//对qb做标记
qbp.setSelExprForClause(ctx_1.dest, ast);
。。。。。。。。。。。。。。。。。略。。。。。。
case HiveParser.TOK_WHERE://where类型token
//对where的孩子进行处理,为什么是ast.getChild(0)?这个是和之前的HiveParser.g结构相辅相成的。
qbp.setWhrExprForClause(ctx_1.dest, ast);
if (!SubQueryUtils.findSubQueries((ASTNode) ast.getChild(0)).isEmpty())
queryProperties.setFilterWithSubQuery(true);
break;
。。。。。。。。。。。。。。。。略。。。。。。。。
case HiveParser.TOK_GROUPBY:
case HiveParser.TOK_ROLLUP_GROUPBY:
case HiveParser.TOK_CUBE_GROUPBY:
case HiveParser.TOK_GROUPING_SETS:
。。。。。。。。。。。。略。。。。。。。。
if (!skipRecursion) {
// Iterate over the rest of the children
int child_count = ast.getChildCount();
for (int child_pos = 0; child_pos < child_count && phase1Result; ++child_pos) {
// Recurse
phase1Result = phase1Result && doPhase1(
(ASTNode)ast.getChild(child_pos), qb, ctx_1, plannerCtx);
}
}
。。。。。。。。。。。。。。。略。。。。。。。。。
大体过程:
doPhase1对ASTTree中的每个元素的TOK类型进行case,针对于不同的case对节点数据进行填充。for遍历整棵ASTTree,中间对每个元素递归调用doPhase1,这种方式是一种深度优先搜索的算法。
经过一轮深度优先遍历,不带元数据的QB树就生成了。
getMetaData(QB, ReadEntity)分析
doPhase1执行完毕之后得到QB,QB里边的只是一些关键字还有一些表的名字,但是和hdfs的文件路径对应不起来,所以需要元数据metaData映射关系,之后在SemanticAnalyzer中调用了 getMetaData()函数。
private void getMetaData(QB qb, ReadEntity parentInput)
throws HiveException {
LOG.info("Get metadata for source tables");
根据日志信息,可以知道,该函数的目标是从源头表中获取元数据
List<String> tabAliases = new ArrayList<String>(qb.getTabAliases());
上面这个列表记录了别名,因为别名可能在中间被修改
Map<String, ObjectPair<String, ReadEntity>> aliasToViewInfo =
new HashMap<String, ObjectPair<String, ReadEntity>>();
上面这个MAP的作用,我的理解是:
跟踪视图别名,查看名称和读取实体
例如:对于像'select * from V3'的查询,其中V3 -> V2, V2 -> V1, V1 -> T
map用于跟踪输入的依赖项及其父类。
Map<String, String> sqAliasToCTEName = new HashMap<String, String>();
for (String alias : tabAliases) {
String tabName = qb.getTabNameForAlias(alias);
String cteName = tabName.toLowerCase();
从tabNameToTabObject缓存中获取表的详细信息:
Table tab = getTableObjectByName(tabName, false);
if (tab != null) {
// do a deep copy, in case downstream changes it.
tab = new Table(tab.getTTable().deepCopy());
}
if (tab == null ||
tab.getDbName().equals(SessionState.get().getCurrentDatabase())) {
Table materializedTab = ctx.getMaterializedTable(cteName);
if (materializedTab == null) {
// we first look for this alias from CTE, and then from catalog.
CTEClause cte = findCTEFromName(qb, cteName);
if (cte != null) {
if (!cte.materialize) {
addCTEAsSubQuery(qb, cteName, alias);
sqAliasToCTEName.put(alias, cteName);
continue;
}
tab = materializeCTE(cteName, cte);
}
} else {
tab = materializedTab;
}
}
throw new SemanticException(e.getMessage(), e);
}
}
这个函数多次出现词CTE,而且程序中有很多涉及CTE的函数,这个CTE是做什么的?查阅资料了解到,CTE是很多数据库系统中常用的一种公用表达式。
公用表表达式(CTE)可以被认为是在单个SELECT,INSERT,UPDATE,DELETE或CREATE VIEW语句的执行范围内定义的临时结果集。CTE类似于派生表,因为它不作为对象存储,并且仅在查询期间持续。与派生表不同,CTE可以是自引用的,并且可以在同一查询中多次引用。
CTE由表示CTE的表达式名称,AS关键字和SELECT语句组成。定义CTE后,可以在SELECT,INSERT,UPDATE或DELETE语句中像表或视图一样引用它。CTE也可以在CREATE VIEW语句中用作其定义SELECT语句的一部分。
小结:
getMetaData():获取源表、目标表、的元数据(主要是schema 等信息)
获取的元数据同样存储在QB/QBParseInfo 中。
①获取source table 的元数据,如果一个table 实际上是一个view,将其重写为view 的定义;
②递归的为每个子查询中的源表获得元数据;
③获取所有destination table/dir/local dir 的元数据;
至此,才把比较完整的信息传给QB