2021SC@SDUSC
总览
本篇为对QueryParserDriver类进行分析的第二篇,主要是对
parse生成逻辑执行计划的过程进行分析。
代码分析
parse
生成逻辑执行计划具体依靠的是LogicalPlanGenerator。 LogicalPlanGenerator.g是一个树分析器文件,antlr生成LogicalPlanGenerator.java文件,实现org.antlr.runtime.tree.TreeParser接口,会对QueryParser.g对应的抽象语法树进行语义处理,用来生成逻辑执行计划。每次调用pigServer.registerQuery方法,注册一个查询语句,Pig都会启动解析、验证步骤,然后调用LogicalPlanGenerator.query()方法,生成该条语句对应的逻辑执行子计划。直到调用pigServer.store方法,才会生成一个完整的逻辑执行计划,触发下一阶段操作。
public LogicalPlan parse(String query) throws ParserException {
LogicalPlan plan = null;
ScriptState ss = ScriptState.get();
CommonTokenStream tokenStream = tokenize(query, ss.getFileName());
Tree ast = parse( tokenStream );
ast = expandMacro( ast );
try{
ast = validateAst( ast );
applyRegisters(ast);
LogicalPlanGenerator planGenerator =
new LogicalPlanGenerator( new CommonTreeNodeStream( ast ), pigContext, scope, fileNameMap );
planGenerator.query();
checkError( planGenerator );
plan = planGenerator.getLogicalPlan();
operators = planGenerator.getOperators();
lastRel = planGenerator.getLastRel();
} catch(RecognitionException ex) {
throw new ParserException( ex );
} catch(Exception ex) {
throw new ParserException( ex.getMessage(), ex );
}
return plan;
}
不同于上述方法,下面的方法生成的是抽象语法树,该抽象语法树记录的是命令语句的各个部分的命令,当在源程序语法分析工作时,是在相应程序设计语言的语法规则指导下进行的。语法规则描述了该语言的各种语法成分的组成结构,通常可以用所谓的前后文无关文法或与之等价的Backus-Naur范式(BNF)将一个程序设计语言的语法规则确切的描述出来。所以需要抽象语法数的建立。
static Tree parse(CommonTokenStream tokens) throws ParserException {
QueryParser parser = QueryParserUtils.createParser(tokens);
QueryParser.query_return result = null;
try {
result = parser.query();
} catch (RecognitionException e) {
String msg = parser.getErrorHeader(e) + " "
+ parser.getErrorMessage(e, parser.getTokenNames());
SourceLocation location = new SourceLocation(null, e.line,e.charPositionInLine);
throw new ParserException(msg, location);
} catch(RuntimeException ex) {
throw new ParserException( ex.getMessage() );
}
Tree ast = (Tree) result.getTree();
checkError(parser);
return ast;
}
applyRegisters
首先判断该抽象语法树的文本是否为register,如果是则首先读取位于子路径0的地址,如果子节点有5个,则在注册时将第三个节点存储的记录为脚本语言,第五个记录为命名域,如果没有5个则直接登记,不标注命名域以及脚本语言;如果不是register则依次登记各个节点。
private void applyRegisters(Tree t) throws ExecException, ParserException {
if (t.getText().equalsIgnoreCase(REGISTER_DEF)) {
String path = t.getChild(0).getText();
path = path.substring(1, path.length()-1);
try {
if (t.getChildCount() == 5) {
new RegisterResolver(getPigServer()).parseRegister(path, t.getChild(2).getText(), t.getChild(4).getText());
} else {
new RegisterResolver(getPigServer()).parseRegister(path, null, null);
}
} catch (IOException ioe) {
throw new ParserException(ioe.getMessage());
}
} else {
for (int i = 0; i < t.getChildCount(); i++) {
applyRegisters(t.getChild(i));
}
}
}
tokenize
读取指令以及其来源,然后通过查询词法分析器对此法进行分析得到对应结果。
static CommonTokenStream tokenize(String query, String source)
throws ParserException {
CharStream input;
try {
input = new QueryParserStringStream(query, source);
} catch (IOException ex) {
throw new ParserException("Unexpected IOException: "
+ ex.getMessage());
}
QueryLexer lexer = new QueryLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
checkError(lexer);
return tokens;
}
总结
本次代码分析分析了逻辑执行计划的生成计划以及生成效果,同时了解了对抽象语法树其中节点的解释读取。