ShardingSphere源码解析之SQL解析引擎(二)

上一篇开始我们们讨论ShardingSphere的SQL解析引擎部分的内容,这里补充说明一下,本系列的内容都是基于ShardingSphere的4.0.1版本。

上一篇已经明确SQL解析引擎部分的目的就是生成SQLStatement,今天我们继续讨论如何生成这个对象。在此之前,我们先来回顾一下SQLParseKernel的parse方法:

 public SQLStatement parse() {   

        //利用ANTLR4解析SQLAST,即SQL的抽象语法树

        SQLAST ast = parserEngine.parse();       

        //提取AST中的Token,封装成对应的Segment,如TableSegmentIndexSegment

        Collection<SQLSegment> sqlSegments = extractorEngine.extract(ast);

        Map<ParserRuleContext, Integer> parameterMarkerIndexes = ast.getParameterMarkerIndexes();       

        //填充SQLStatement并返回

        return fillerEngine.fill(sqlSegments, parameterMarkerIndexes.size(), ast.getSqlStatementRule());

    }

今天我们关注于上述流程中的第一步,即如何生成一个SQLAST。我们来到SQLParserEngine的parse方法,如下所示:

public SQLAST parse() {

        SQLParser sqlParser = SQLParserFactory.newInstance(databaseTypeName, sql);       

        //利用ANTLR4解析获取解析树

        ParseTree parseTree;

        try {

            ((Parser) sqlParser).setErrorHandler(new BailErrorStrategy());

            ((Parser) sqlParser).getInterpreter().setPredictionMode(PredictionMode.SLL);

            parseTree = sqlParser.execute().getChild(0);

        } catch (final ParseCancellationException ex) {

            ((Parser) sqlParser).reset();

            ((Parser) sqlParser).setErrorHandler(new DefaultErrorStrategy());

            ((Parser) sqlParser).getInterpreter().setPredictionMode(PredictionMode.LL);

            parseTree = sqlParser.execute().getChild(0);

        }

        if (parseTree instanceof ErrorNode) {

            throw new SQLParsingException(String.format("Unsupported SQL of `%s`", sql));

        }       

        //获取配置文件中的StatementContext

        SQLStatementRule rule = parseRuleRegistry.getSQLStatementRule(databaseTypeName, parseTree.getClass().getSimpleName());

        if (null == rule) {

            throw new SQLParsingException(String.format("Unsupported SQL of `%s`", sql));

        }       

        //封装抽象语法树AST

        return new SQLAST((ParserRuleContext) parseTree, getParameterMarkerIndexes((ParserRuleContext) parseTree), rule);

    }

其中SQLParser负责具体的SQL到AST(Abstract Syntax Tree,抽象语法树)的解析过程,该接口定义如下(请注意该接口位于shardingsphere-sql-parser-spi 工程的org.apache.shardingsphere.sql.parser.api包中):

public interface SQLParser {   

        ParserRuleContext execute();

}

而具体的SQLParser的生成由SQLParserFactory负责,SQLParserFactory定义如下:

public final class SQLParserFactory {   

    public static SQLParser newInstance(final String databaseTypeName, final String sql) {

    //通过SPI机制加载所有扩展

    for (SQLParserEntry each : NewInstanceServiceLoader.newServiceInstances(SQLParserEntry.class)) {       

        //判断数据库类型

        if (each.getDatabaseTypeName().equals(databaseTypeName)) {

                return createSQLParser(sql, each);

            }

        }

        throw new UnsupportedOperationException(String.format("Cannot support database type '%s'", databaseTypeName));

    }

   

    @SneakyThrows

    private static SQLParser createSQLParser(final String sql, final SQLParserEntry parserEntry) {

    //词法分析器

    Lexer lexer = parserEntry.getLexerClass().getConstructor(CharStream.class).newInstance(CharStreams.fromString(sql));       

    //语法分析器

    return parserEntry.getParserClass().getConstructor(TokenStream.class).newInstance(new CommonTokenStream(lexer));

    }

}

这里引入了一个核心接口,即SQLParserEntry,该接口位于shardingsphere-sql-parser-spi 工程的org.apache.shardingsphere.sql.parser.spi包中,定义如下:

public interface SQLParserEntry {   

    String getDatabaseTypeName();   

    Class<? extends Lexer> getLexerClass();   

    Class<? extends SQLParser> getParserClass();

}

ShardingSphere中存在多个SQLParserEntry,类层结构如下所示:

显然,每个数据库都有一个SQLParserEntry实现,至于如何获取具体的SQLParserEntry,ShardingSphere采用了微内核(MicroKernel)模型,关于这块内容我们会有一个专题进行讨论,这里不做展开。今天我们以MySQL为例,来看MySQLParserEntry的具体实现:

public final class MySQLParserEntry implements SQLParserEntry {   

    @Override

    public String getDatabaseTypeName() {

        return "MySQL";

    }   

    @Override

    public Class<? extends Lexer> getLexerClass() {

        return MySQLStatementLexer.class;

    }   

    @Override

    public Class<? extends SQLParser> getParserClass() {

        return MySQLParser.class;

    }

}

而MySQLParser如下所示:

public final class MySQLParser extends MySQLStatementParser implements SQLParser {   

    public MySQLParser(final TokenStream input) {

        super(input);

    }

}

我们在这里看到两个新的类,即MySQLStatementLexer和MySQLStatementParser。我们跳转到这两个类的定义,发现这两个类都是位于org.apache.shardingsphere.sql.parser.autogen包下,属于自动生成的工具类。这里就会依赖于ANTLR4的AST生成机制。当通过SQLParserFactory获取了SQLParser实例之后,在SQLParserEngine中会利用ANTLR4解析获取解析树ParseTree,这部分同样属于ANTLR4的内容。关于ANTLR4相关的实现机制也值得我们后面做一个专题进行讲解,今天也不做展开。

我们继续关注主流程,当获取ParseTree之后,我们需要进一步获取SQLStatementRule,代码如下:

//获取配置文件中的StatementContext

SQLStatementRule rule = parseRuleRegistry.getSQLStatementRule(databaseTypeName, parseTree.getClass().getSimpleName());       

这里需要先介绍ParseRuleRegistry类,从命名上看,该类就是一个规则注册表,保存着各种解析规则信息。ParseRuleRegistry类中的核心变量如下所示:

    private final ExtractorRuleDefinitionEntityLoader extractorRuleLoader = new ExtractorRuleDefinitionEntityLoader();   

    private final FillerRuleDefinitionEntityLoader fillerRuleLoader = new FillerRuleDefinitionEntityLoader();   

    private final SQLStatementRuleDefinitionEntityLoader statementRuleLoader = new SQLStatementRuleDefinitionEntityLoader();   

    private final Map<String, FillerRuleDefinition> fillerRuleDefinitions = new HashMap<>();   

    private final Map<String, SQLStatementRuleDefinition> sqlStatementRuleDefinitions = new HashMap<>();

可以看到解析规则信息包括SQLStatementRule、ExtractorRule和FillerRule这三大类,这里用到的是SQLStatementRule,这些SQLStatementRule位于sql-statement-rule-definition.xml配置文件(以Mysql为例,该配置文件位于META-INF/parsing-rule-definition/mysql目录下)中,截取该配置文件中的部分配置信息如下所示::

<sql-statement-rule-definition>

    <sql-statement-rule context="select" sql-statement-class="org.apache.shardingsphere.sql.parser.sql.statement.dml.SelectStatement" extractor-rule-refs="tableReferences, columns, selectItems, where, predicate, groupBy, orderBy, limit, subqueryPredicate, lock" />

    <sql-statement-rule context="insert" sql-statement-class="org.apache.shardingsphere.sql.parser.sql.statement.dml.InsertStatement" extractor-rule-refs="table, columns, insertColumns, insertValues, setAssignments, onDuplicateKeyColumns" />

    <sql-statement-rule context="update" sql-statement-class="org.apache.shardingsphere.sql.parser.sql.statement.dml.UpdateStatement" extractor-rule-refs="tableReferences, columns, setAssignments, where, predicate" />

    <sql-statement-rule context="delete" sql-statement-class="org.apache.shardingsphere.sql.parser.sql.statement.dml.DeleteStatement" extractor-rule-refs="tables, columns, where, predicate" />

</sql-statement-rule-definition>

对于具体某种数据库类型的每条SQL而言,都会有一个SQLStatementRule对象。一旦获取SQLStatementRule对象,我们就可以构建一个SQLAST对象,该对象定义如下:

public final class SQLAST {   

    private final ParserRuleContext parserRuleContext;   

    private final Map<ParserRuleContext, Integer> parameterMarkerIndexes;   

    private final SQLStatementRule sqlStatementRule;

}

这里的三个变量中还剩下一个parameterMarkerIndexes变量没有介绍,该变量用于获取所有参数占位符。这样SQL的抽象语法树构建的整体流程就完成了,也就是说我们完成了对如下语句的分析:

SQLAST ast = parserEngine.parse();

作为总结,今天的内容关注于SQL的抽象语法树SQLAST的构建。虽然对整个流程有了一个简单的闭环介绍,但还有很多细节没有展开,例如微内核模式和SPI机制、ANTLR4的AST解析、动态获取配置信息等,这些就是我们后面几篇文章的主题。

更多内容可以关注我的公众号:程序员向架构师转型。

发布了92 篇原创文章 · 获赞 9 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/lantian08251/article/details/104487475
今日推荐