从上一篇开始我们们讨论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解析、动态获取配置信息等,这些就是我们后面几篇文章的主题。
更多内容可以关注我的公众号:程序员向架构师转型。