從上一篇開始我們們討論ShardingSphere的SQL解析引擎部分的內容,這裏補充說明一下,本系列的內容都是基於ShardingSphere的4.0.1版本。
上一篇已經明確SQL解析引擎部分的目的就是生成SQLStatement,今天我們繼續討論如何生成這個對象。在此之前,我們先來回顧一下SQLParseKernel的parse方法:
public SQLStatement parse() {
//利用ANTLR4解析SQLAST,即SQL的抽象語法樹
SQLAST ast = parserEngine.parse();
//提取AST中的Token,封裝成對應的Segment,如TableSegment、IndexSegment
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解析、動態獲取配置信息等,這些就是我們後面幾篇文章的主題。
更多內容可以關注我的公衆號:程序員向架構師轉型。