调研系列第五篇:antlr以及hive的parse执行入口

关于antlr的使用

Hive使用的是antlr来做词法、语法的解析工作,最终生成一棵有语义的ast数。

关于antlr  

1、ANTLRANother Tool for Language Recognition的缩写又一个语言识别工具,读[ 'æntlə ]。从名字上可以看出在ANTLR出现之前已经存在其它语言识别工具了(如LEX1GCC ,YACC2 )。Antlr通过自己的语法来定义此法规则和语法规则,然后将这些语法规则生成相应的Java/C++代码,分别是一个EELexer.java(词法解析器)和EEParser.java(语法解析器),其中EEEE.g文件的文件名,这两个文件可以直接拿来使用,具体的demo如下 

2、Antlr的词法规则

词法是一些正则表达式的东东,可以根据规则将一篇文章切分成一个个的“单词”,又叫tokensuch  as 

Identifier : ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*;

STRING : '\'' (~'\'')* '\'';

INT : '0'..'9'+;

WS :   ( ' ' | '\t' | '\r' | '\n' )+ { Skip(); } ;

其中WS也是一个词法规则,但是会被过滤掉,可以看作是词之间的分隔符 

词法规则使用大写来表示,一般写在antlr **.g文件的末尾  

3、Antlr的语法规则

语法规则是将一系列词法规则组合起来用的,语法规则可以嵌套语法规则,但是有个最顶层的语法规则,最终对于输入要生成这个语法规则,不然会报错 

语法树重写:

insertStatement : insertClause selectClause fromClause whereClause?

         -> ^(INSERT_STATEMENT insertClause selectClause fromClause whereClause?);

 

这种叫做语法重写,是根据相应的识别相应的语法规则,然后重写这个规则的语法树,主要是为了使得结果的语法树更清晰 。

^(*)表示需要有根节点、叶子节点的语法树,如果不加这个标志的,则解析后的每个token都是一个叶子节点。

其中的第一个token是这棵树的根节点 。

4、一个sql  Demo

对于一个StormQ1.g文件  

grammar StormQl;
options { language=Java; output=AST;}
tokens {
  DDL_STATEMEMT;
  INSERT_STATEMENT;
  SELECT_STATEMENT;
  INSERT_TABLE;
  SELECT_LIST;
  TABLE_LIST;
  WHERE_CONDITION;
  FIELD_NAME;
  COMPARE_ITEM;
  CONSTANT_STR;
} 
dmlStatement  :    insertStatement | selectStatement ;
insertStatement : insertClause selectClause fromClause whereClause? 
         -> ^(INSERT_STATEMENT insertClause selectClause fromClause whereClause?);
selectStatement : selectClause fromClause whereClause? 
         -> ^(SELECT_STATEMENT selectClause fromClause whereClause?);
insertClause : 'INSERT''INTO'tableName ->^(INSERT_TABLE tableName);
selectClause : 'SELECT' ('*' -> ^(SELECT_LIST '*')
           | fieldName (',' fieldName)* -> ^(SELECT_LIST fieldName+)
            );
fromClause : 'FROM' tableSource (',' tableSource)* 
       -> ^(TABLE_LIST tableSource+);
whereClause : 'WHERE' searchCondition -> ^(WHERE_CONDITION searchCondition);
searchCondition    : searchItem ('AND' searchItem)*;
searchItem : expression (
                               (p='=' | p='>' | p='<' | p='<>') expression -> ^(COMPARE_ITEM expression $p expression) | 
                               'IS' (null_c='NOT NULL'|null_c='NULL')-> ^(COMPARE_ITEM expression 'IS' ^(CONSTANT_STR $null_c))
                         );
expression : fieldName | STRING | INT;
tableSource : tableName | '(' selectStatement ')' 'AS' tableName
              -> ^(tableName selectStatement);
fieldName : Identifier ('.' Identifier)* 
       -> ^(FIELD_NAME Identifier ('.' Identifier)*);
tableName : Identifier;
Identifier : ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')*;
STRING : '\'' (~'\'')* '\'';
INT : '0'..'9'+;
WS :   ( ' ' | '\t' | '\r' | '\n' )+ { Skip(); } ;

 

可以使用antlrworks-1.4.jar打开 

antlrwork中,可以使用Generate—>Genterate Code 来生成相应的Java代码,具体生成的文件如下:

StormQ1Lexer.java(词法分析器,将sql解析成一个个的单词)、StormQ1Parser.java(语法解析器,将前一个生成的词法Token List转换成一棵AST树)

PS:由于antlrwork的一些bug,生成的Java代码需要改动一些东西  ,修改一个方法的大小写,然后就可以直接用这两个类了 

利用Java调用的demo如下 

         String sql="INSERT INTO desttable SELECT   aa , bb ,cc FROM log_alb_sum WHERE cc>1000  AND  bb=dd AND ss IS NULL AND cc IS   NOT NULL";

        InputStream in =new ByteArrayInputStream(sql.getBytes());

        ANTLRInputStream input = new ANTLRInputStream(in);

        Lexer lexer = new StormQlLexer(input);

        CommonTokenStream tokens = new CommonTokenStream(lexer);

        StormQlParser parser = new StormQlParser(tokens);

        StormQlParser.dmlStatement_return r =parser.dmlStatement();

        Token start=r.start;

        Token end=r.stop;

        BaseTree tree=(BaseTree)r.tree;

 

r是解析后返回的一个值,其中最终要的属性值r.tree ,是解析后的语法树的根节点,其结构如下:

主要是children子树  

Token是表示每个词,其中主要属性是其文本,开始、结束位置以及type,主要是在语法报错时候会用到,type的值在生成的StormQl.tokens会有定义,表示某个token对应的值是多少,主要可以用在后面语法解析的时候,用这个值找到自己定义的token(主要是重写为子树的根节点),然后实现自己的语法解析,一般这种token是用户自己的语法关键点,语义分析也都是遍历这些关键token点(可以参考hive的语法中的token

 

 

Hive中的antlr以及词法语法解析

1、HIVEantlr语法文件(位于ql\src\java\org\apache\hadoop\hive\ql\parse目录下)

IdentifiersParser.g

HiveLexer.g

FromClauseParser.g

SelectClauseParser.g

HiveParser.g

几个文件位于,里面是antlr的语法,有兴趣可以看下。

      

2、Hive中整词法语法解析(未用antlr的)的步骤

整个sql语句编译、执行的步骤,执行的入口方法是在Driver.run(String command)方法中,执行的参数也就是一个sql字符串 ,主要的方法是:

int ret = compile(command);   //编译,主要是将sql字符串翻译成ast树,然后翻译成可执行的task ,然后再优化执行树

ret = execute();               //执行所有的task  

a)   Hive中调用antlr类的代码org.apache.hadoop.hive.ql.parse.ParseDriver     返回的HiveParser.statement_return和上面一样,是棵ast的语法树 ,具体语法树的接口可以参见相应的HiveParse.g文件 

 

b)   得到语法树之后,会根据语法树根节点的类型来选择相应的SemanticAnalyzer

主要是根据根节点的语法树类型来选择相应的analyzer,具体的选择analyzer代码如下:

 

对于DDL操所,得到的就是DDLSemanticAnalyzer ,对于一般的insert(hive中存select语句会被翻译成一个insert tmpDirectory的语句)得到的就是SemanticAnalyzer 

c)   然后调用SemanticAnalyzer.analyze(tree,ctx)来将语法树翻译成可执行的执行计划

 

可执行的计划存储在  protected List<Task<? extends Serializable>> rootTasks 属性中, TaskexecuteTask()方法是可以直接执行的,最终实际的执行也是调用每个taskexecuteTask方法,依赖以及调度是在上层控制的,Task的继承关系如下:

 

Task是一个树形结构,每个task有一堆child task ,这些child是在执行顺序上依赖于自己的task rootTasks中存储的就是整个执行计划中需要最开始执行的task list ,一棵倒着的执行依赖树” 

d)   执行task:Driver.execute()为入口

 

 

将可执行的task放入runnable中,初始为root task list  runnable表示正在运行runningtask 

具体的执行流程如下:

Ø  不断去遍历runnable,选出一个执行launchTask(tsk, queryId, noName, running, jobname, jobs, driverCxt) ,在这个方法中,启动task,其实就是调用taskexecuteTask() 方法 

 

 

这个里面hive是支持并发执行task的,若是需要并发的话每个task被封装成一个Thread的子类,然后自行启动。

Ø  找出执行完成的task,然后遍历该task的子task,选出可执行(pre task已经执行完)task 放入runnable ,然后重复上一个步骤 

 

 

对于一些有多个pre task child  task,会在最后一个pre task执行完后被启动,所以在这会被在child中过掉。

Ø  待补充

 

e)   待补充

3、关于DDLSemanticAnalyzer:解析ddl语句并生成相应执行DDLTask来执行

 

 

根据相应的ast树类型,生成一个执行该ddl需要信息的对象DDLWork DDLWork是一个union的数据结构,里面有各种操作信息的引用,但是只有一个有用的 。随后DDLWork封装到一个DDLTask中,DDLTask执行具体execute方法的时候,在根据DDLWork中得到的值判断具体该执行那一种操作(那种操作需要的信息的引用!=null,则执行哪种引用),具体的执行其实是调用Hive对象相应的方法 

 

 

4、SemanticAnalyzer:dmlast树翻译成具体的执行计划的analyzer 

 

5、其它几个LoadSemanticAnalyzerExportSemanticAnalyzerImportSemanticAnalyzer简单,具体可以关注下相关的代码。

 

posted @ 2014-05-20 08:59  xiao晓  阅读(6697)  评论(0编辑  收藏  举报