整体规划
基于前面的学习,了解了ShardingSphere的数据分片的主要流程:
以及ShardingSphere-infra的核心模块:
为了进一步掌握源码分析的技能,接下来的一周里,将按照【解析】->【路由】->【改写】->【执行】->【归并】的顺序,逐一分析各项功能的实现原理。
今天的主题就是【解析】-ShardingSphere-infra-parser。
结合官方文档,快速入门
首先,参考官方介绍,来快速了解parser的大致原理,简单来说,通过3步完成SQL解析:
- 词法解析器:拆分(拆解成最小元素)、匹配(该条SQL的DB类型对应的方言库)
- 语法解析器:转换成语法树
- 遍历器visitor:遍历,解析成SQLStatement
分析源码
解析入口
private SQLStatement parse0(final String sql, final boolean useCache) {
try {
// 默认解析器解析
return sqlStatementParserEngine.parse(sql, useCache);
} catch (final SQLParsingException | ParseCancellationException originalEx) {
try {
// 异常时,走distSQL解析器解析
return distSQLStatementParserEngine.parse(sql);
} catch (final SQLParsingException ignored) {
throw originalEx;
}
}
}
关于distSQL的介绍:shardingsphere.apache.org/document/cu…
解析成语法树
public ParseTree parse(final String sql) {
ParseASTNode result = twoPhaseParse(sql);
// 由这步抛异常,可以推测:解析的目的是为了检查入参sql是否合法
if (result.getRootNode() instanceof ErrorNode) {
throw new SQLParsingException("Unsupported SQL of `%s`", sql);
}
return result.getRootNode();
}
private ParseASTNode twoPhaseParse(final String sql) {
// 根据DB类型,选择对应的SQL解析器
DatabaseTypedSQLParserFacade sqlParserFacade = DatabaseTypedSQLParserFacadeRegistry.getFacade(databaseType);
// 解析器继承了ANTLR的Parser类
SQLParser sqlParser = SQLParserFactory.newInstance(sql, sqlParserFacade.getLexerClass(), sqlParserFacade.getParserClass());
try {
((Parser) sqlParser).getInterpreter().setPredictionMode(PredictionMode.SLL);
// 这一步完成解析(基于ANTLR实现),返回语法树
return (ParseASTNode) sqlParser.parse();
} catch (final ParseCancellationException ex) {
((Parser) sqlParser).reset();
((Parser) sqlParser).getInterpreter().setPredictionMode(PredictionMode.LL);
try {
return (ParseASTNode) sqlParser.parse();
} catch (final ParseCancellationException e) {
throw new SQLParsingException("You have an error in your SQL syntax");
}
}
}
遍历语法树,得到sqlStatement
public SQLStatement parse(final String sql) {
/**
* parseEngin.parse()返回:解析后的语法树
* visitorEngine.visit()返回:遍历后得到的特定sqlStatement(跟入参sql的类型一致)
*/
return visitorEngine.visit(parserEngine.parse(sql, false));
}
public <T> T visit(final ParseTree parseTree) {
// 构造出对应的visitor
ParseTreeVisitor<T> visitor = SQLVisitorFactory.newInstance(databaseType, visitorType, SQLVisitorRule.valueOf(parseTree.getClass()), props);
// 基于ANTLR实现
return parseTree.accept(visitor);
}
流程图
总结
教训
- 一开始没有明确阅读目标,过程里只关注程序的流向,没有思考每一步的作用是什么,结果不自觉地陷入parser的解析细节里,看不懂,但又没及时抽身,白白浪费了不少时间
收获
- 通过源码+文档的形式,初步了解了【SQL解析器】的实现原理
- 对如何分析源码和快速掌握只是,有了更深刻的认识