關於antlr的使用
Hive使用的是antlr來做詞法、語法的解析工作,最終生成一棵有語義的ast數。
關於antlr
1、ANTLR是ANother Tool for Language Recognition的縮寫“又一個語言識別工具”,讀[ 'æntlə ]。從名字上可以看出在ANTLR出現之前已經存在其它語言識別工具了(如LEX1,GCC ,YACC2 )。Antlr通過自己的語法來定義此法規則和語法規則,然后將這些語法規則生成相應的Java/C++代碼,分別是一個EELexer.java(詞法解析器)和EEParser.java(語法解析器),其中EE是EE.g文件的文件名,這兩個文件可以直接拿來使用,具體的demo如下 。
2、Antlr的詞法規則
詞法是一些正則表達式的東東,可以根據規則將一篇文章切分成一個個的“單詞”,又叫token,such 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、HIVE的antlr語法文件(位於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 屬性中, Task的executeTask()方法是可以直接執行的,最終實際的執行也是調用每個task的executeTask方法,依賴以及調度是在上層控制的,Task的繼承關系如下:
Task是一個樹形結構,每個task有一堆child task ,這些child是在執行順序上依賴於自己的task ,rootTasks中存儲的就是整個執行計划中需要最開始執行的task list ,一棵”倒着的執行依賴樹” 。
d) 執行task:Driver.execute()為入口
將可執行的task放入runnable中,初始為root task list ,runnable表示正在運行running的task 。
具體的執行流程如下:
Ø 不斷去遍歷runnable,選出一個執行launchTask(tsk, queryId, noName, running, jobname, jobs, driverCxt) ,在這個方法中,啟動task,其實就是調用task的executeTask() 方法 。
這個里面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:將dml的ast樹翻譯成具體的執行計划的analyzer
5、其它幾個LoadSemanticAnalyzer、ExportSemanticAnalyzer、ImportSemanticAnalyzer較簡單,具體可以關注下相關的代碼。