ShardingSphere源码分析2-解析引擎

911 阅读2分钟

整体规划

基于前面的学习,了解了ShardingSphere的数据分片的主要流程:

image.png

以及ShardingSphere-infra的核心模块:

image.png

为了进一步掌握源码分析的技能,接下来的一周里,将按照【解析】->【路由】->【改写】->【执行】->【归并】的顺序,逐一分析各项功能的实现原理。

今天的主题就是【解析】-ShardingSphere-infra-parser。

结合官方文档,快速入门

首先,参考官方介绍,来快速了解parser的大致原理,简单来说,通过3步完成SQL解析:

  1. 词法解析器:拆分(拆解成最小元素)、匹配(该条SQL的DB类型对应的方言库)
  2. 语法解析器:转换成语法树
  3. 遍历器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);
}

流程图

未命名文件.png

总结

教训

  1. 一开始没有明确阅读目标,过程里只关注程序的流向,没有思考每一步的作用是什么,结果不自觉地陷入parser的解析细节里,看不懂,但又没及时抽身,白白浪费了不少时间

收获

  1. 通过源码+文档的形式,初步了解了【SQL解析器】的实现原理
  2. 对如何分析源码和快速掌握只是,有了更深刻的认识