Druid SQL解析器的解析过程

发表于:2017-1-23 09:53

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:SegmentFault    来源:51Testing软件测试网采编

#
SQL
分享:
  这篇文尝试近距离地探究 Druid SQL 解析器如何工作
  Demo 代码
  以这份代码为例
/**
*
*
* @author beanlam
* @date 2017年1月10日 下午11:06:26
* @version 1.0
*
*/
public class ParserMain {
public static void main(String[] args) {
String sql = "select * from user order by id";
// 新建 MySQL Parser
SQLStatementParser parser = new MySqlStatementParser(sql);
// 使用Parser解析生成AST,这里SQLStatement就是AST
SQLStatement statement = parser.parseStatement();
// 使用visitor来访问AST
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
statement.accept(visitor);
System.out.println(visitor.getColumns());
System.out.println(visitor.getOrderByColumns());
}
}
  一开始,需要初始化一个 Parser,在这里 SQLStatementParser 是一个父类,真正解析 SQL 语句的 Parser 实现是 MySqlStatementParser 。
  Parser 的解析结果是一个 SQLStatement ,这是一个内部维护了树状逻辑结构的类。
  词法分析
  Druid 的代码里,代表 语法分析 和 词法分析 的类分别是 SQLParser 和 Lexer 。并且, Parser 拥有一个 Lexer。
public class SQLParser {
protected final Lexer lexer;
protected String      dbType;
public SQLParser(String sql, String dbType){
this(new Lexer(sql), dbType);
this.lexer.nextToken();
}
public SQLParser(String sql){
this(sql, null);
}
public SQLParser(Lexer lexer){
this(lexer, null);
}
public SQLParser(Lexer lexer, String dbType){
this.lexer = lexer;
this.dbType = dbType;
}
}
  经过瘦身后的 Druid 代码,其 Lexer 只有两个,分别是 Lexer ,以及它的子类 MySqlLexer
  Lexer 作为词法分析器,必然拥有其词汇表,在Lexer里,以 Keywords 表示。
  protected Keywords keywods = Keywords.DEFAULT_KEYWORDS;
  Keywords 实际上是 key 为单词,value 为 Token 的字典型结构,其中 Token 是单词的类型,比如说,“select” 的 Token 类型就是 Select Token,而 “abc” 的 Token 类型,则是标识符,也表示为 Identifier Token。
  而 MySqlLexer 类,除了沿用其父类的 Keywords 外,自己还有自己的 Keywords。可以理解为 Lexer 所维护的关键字集合,是通用的;而 MySqlLexer 除了有通用的关键字集合,也有属于 MySQL 数据库 SQL 方言的关键字集合。
  Parser 是 Lexer 的使用者,站在 Parser 的角度看,它会怎么去使用 Lexer,或者说,Lexer 应该具备怎样的功能,才能满足 Parser 的使用需求。
  Lexer 应该具备一个函数,能让使用者命令它解析一个单词,并且 Lexer 还必须提供一个函数,供使用者获取 Lexer 上一次解析到的单词以及单词的类型。
  在 Lexer 中, nextToken() 这个方法提供了第一个需求,只要被调用,它就按顺序从 SQL 语句的开头到结尾,解析出下一个单词; token() 方法,则返回了上一次解析的单词的 Token 类型,如果 Token 类型是标识符(Identifier),Lexer 还提供了一个 stringVal() 方法,让使用者能拿到标识符的值。
  走进 Lexer 的 nextToken() 方法,可以发现它的代码充斥着 if 语句和 switch 语句,因为解析单词的时候,是一个字符一个字符地解析,这就意味着,这个方法每次扫描一个字符,都必须判断单词是否结束,应该用什么方式来验证这个单词等等。这个过程,就是一个 状态机 运作的过程,每解析到一个字符,都要判断当前的状态,以决定应该进入下一个什么状态。
  Select 语法分析
  有了 Lexer 这样的犀利工具,接下来就是 Parser 发挥的时候了,从 Demo 代码里可以看到,解析的开始,在于调用 parser.parseStatement() 方法。进到这个方法看看,发现清一色是形似如下格式的代码:
if (lexer.token() == Token.xxx) {
// 这里解析 xxx 类型
return;
}
if (lexer.token() == Token.aaa) {
// 这里解析 aaa 类型
return;
}
  显然,如果是分析对 Select 类型的语句的解析,那么应该关注以下的代码:
  if (lexer.token() == Token.SELECT) {
  statementList.add(parseSelect());
  continue;
  }
  重点是 parseSelect() 方法, MySqlStatementParser 重载了它的父类的这个方法,因此这个方法实际上的实现细节是这样的
@Override
public SQLStatement parseSelect() {
MySqlSelectParser selectParser = new MySqlSelectParser(this.exprParser);
SQLSelect select = selectParser.select();
if (selectParser.returningFlag) {
return selectParser.updateStmt;
}
return new SQLSelectStatement(select, JdbcConstants.MYSQL);
}
  初始化一个针对 MySQL Select 语句的 Parser,然后调用 select() 方法进行解析,把返回结果 SQLSelect 放到 SQLSelectStatement 里,而这个 SQLSelectStatement ,便是我最关心的 AST 抽象语法树,SQLSelect 是它的第一个子节点。
  抛开解析的细节不谈,实际上我会非常关心这棵 AST 的层次结构。
21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号