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解析、動態獲取配置信息等,這些就是我們後面幾篇文章的主題。

更多內容可以關注我的公衆號:程序員向架構師轉型。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章