ShardingSphere 的 SQL parser 是基于 ANTLR 实现的,整体实现还算简单,花了点时间看了这部分代码,记录下来。

基于 SPI 实现扩展

ShardingSphere 源码中,shardingsphere-sql-parser-spi 这个 module 利用 Java 的 SPI 机制定义了 parser 的扩展点,定义了 org.apache.shardingsphere.sql.parser.spi.SQLParserFacade 和 org.apache.shardingsphere.sql.parser.spi.SQLVisitorFacade 两个扩展点,其作用主要是定义不同数据库的 SQL 语言的解析接口,最终转换成 ShardingSphere 自定义的 AST:

  • SQLParserFacade  定义 SQL 解析的扩展点,用于解析文本 SQL,最终返回 antlr 的 AST。
  • SQLVisitorFacade 定义转换 ANTLR 的 AST 到 ShardingSphere 的 AST 的扩展点。比如 ANTLR 解析出来的是一个 ParserTree 的 AST 的数据结构,而 ShardingSphere 自己定义了一套 AST。

SQL Parser Engine 的实现

ShardingSphere SQL 解析的入口在 shardingsphere-infra-parser 这个 module,具体的方法是 org.apache.shardingsphere.infra.parser.ShardingSphereSQLParserEngine#parse ,这个方法入参是 sql 语句字符串(还有一个是否使用缓存的参数,暂时不用关心),返回的是 ShardingSphere 的 AST。 对于 ShardingSphereSQLParserEngine ,在初始化的时候,会获取到真实的 parser engine 对象,代码如下

public ShardingSphereSQLParserEngine(final String databaseTypeName) {
    // sql parser 的核心对象
	sqlStatementParserEngine = SQLStatementParserEngineFactory.getSQLStatementParserEngine(databaseTypeName);
    // 看文法文件,是处理 ShardingSphere 自定义的创建数据源和规则语句的
    distSQLStatementParserEngine = new DistSQLStatementParserEngine();
    // SQL 解析回调
    parsingHookRegistry = ParsingHookRegistry.getInstance();
}

SQLStatementParserEngineFactory.getSQLStatementParserEngine(databaseTypeName) 这行语句就是创建 SQL parser 的核心对象,这个方法中,会根据参数 databaseTypeName  创建出 org.apache.shardingsphere.infra.parser.sql.SQLStatementParserEngine 对象。在该类中有 org.apache.shardingsphere.sql.parser.api.SQLParserEngine 和 org.apache.shardingsphere.sql.parser.api.SQLVisitorEngine,这两个分别负责将 SQL 语句解析成 ANTLR 的 AST 和将 ANTLR 的 AST 转换为 ShardingSphere 定义的 AST。在处理的过程中会根据 databaseTypeName 参数来处理对应的数据库,比如针对 MySQL,在解析的过程中拿到 MySQLParser 的对象来解析 SQL 语句。 在 SQLParserEngine 中,执行解析 SQL 和转换 ANTLR 的 AST 的代码如下。首先由 SQLParserEngine.parse 方法将 SQL 解析成 org.antlr.v4.runtime.tree.ParseTree 对象,再由 org.apache.shardingsphere.sql.parser.api.SQLVisitorEngine#visit  将 ParseTree 的对象转换为 ShardingSphere 的 AST,即 org.apache.shardingsphere.sql.parser.sql.common.statement.SQLStatement 的子类对象。

private SQLStatement parse(final String sql) {
	return visitorEngine.visit(parserEngine.parse(sql, false));
}

SQLParserEngine

org.apache.shardingsphere.sql.parser.api.SQLParserEngine 中维护了一个 ConcurrentMap,key 是 databaseType,标识数据库的类型,value 是 org.apache.shardingsphere.sql.parser.core.parser.SQLParserExecutor,SQL 解析的执行器。在 SQLParserExecutor 中 会创建 SQLParser 对象,比如 MySQLParser,然后执行真正的 SQL 文本解析。

private ParseASTNode twoPhaseParse(final String sql) {
    SQLParser sqlParser = SQLParserFactory.newInstance(databaseType, sql);
    try {
        setPredictionMode((Parser) sqlParser, PredictionMode.SLL);
        return (ParseASTNode) sqlParser.parse();
    } catch (final ParseCancellationException ex) {
        ((Parser) sqlParser).reset();
        setPredictionMode((Parser) sqlParser, PredictionMode.LL);
        return (ParseASTNode) sqlParser.parse();
    }
}

在 ShardingSphere 中,SQLParser 的实现就比较简单了,因为核心逻辑都在 ANTLR 中,ShardingSphere 在 ANTLR 上做了一层封装。比如 org.apache.shardingsphere.sql.parser.mysql.parser.MySQLParser 的实现如下。

/**
 * SQL parser for MySQL.
 */
public final class MySQLParser extends MySQLStatementParser implements SQLParser {
    
    public MySQLParser(final TokenStream input) {
        super(input);
    }
    
    @Override
    public ASTNode parse() {
        return new ParseASTNode(execute());
    }
}

SQLVisitorEngine

SQLVisitorEngine 则将 ANTLR 的 AST 转换为 ShardingSphere 的 AST,整个转换过程基于 Visitor 模式,ANTLR 的 ParserTree 支持 Visit 模式(基于 antlr 的 org.antlr.v4.runtime.tree.ParseTreeVisitor类)。代码如下

public <T> T visit(final ParseTree parseTree) {
    ParseTreeVisitor<T> visitor = SQLVisitorFactory.newInstance(databaseType, visitorType, SQLVisitorRule.valueOf(parseTree.getClass()));
    return parseTree.accept(visitor);
}

SQLVisitorFactory 是一个创建 ParseTreeVisitor 实现对象的工厂,在它的成员变量中持有了各种 ParseTreeVisitor 的实现对象,其 newInstance 方法会根据 databaseType,和 visitorType 返回对应的 ParseTreeVisitor 实现类的实例,需要注意的是第三个参数,其定义了 SQL 操作的类型,比如 SELECT,INSERT,UPDATE 等等。newInstance 的代码如下。

public static <T> ParseTreeVisitor<T> newInstance(final String databaseType, final String visitorType, final SQLVisitorRule visitorRule) {
    SQLVisitorFacade facade = SQLVisitorFacadeRegistry.getInstance().getSQLVisitorFacade(databaseType, visitorType);
    return createParseTreeVisitor(facade, visitorRule.getType());
}
    
@SuppressWarnings("unchecked")
@SneakyThrows(ReflectiveOperationException.class)
private static <T> ParseTreeVisitor<T> createParseTreeVisitor(final SQLVisitorFacade visitorFacade, final SQLStatementType type) {
    switch (type) {
        case DML:
            return (ParseTreeVisitor) visitorFacade.getDMLVisitorClass().getConstructor().newInstance();
        case DDL:
            return (ParseTreeVisitor) visitorFacade.getDDLVisitorClass().getConstructor().newInstance();
        case TCL:
            return (ParseTreeVisitor) visitorFacade.getTCLVisitorClass().getConstructor().newInstance();
        case DCL:
            return (ParseTreeVisitor) visitorFacade.getDCLVisitorClass().getConstructor().newInstance();
        case DAL:
            return (ParseTreeVisitor) visitorFacade.getDALVisitorClass().getConstructor().newInstance();
        case RL:
            return (ParseTreeVisitor) visitorFacade.getRLVisitorClass().getConstructor().newInstance();
        default:
            throw new SQLParsingException("Can not support SQL statement type: `%s`", type);
    }
}

SQLVisitorFacadeRegistry 基于 Java 的 SPI 机制实现了 Visitor 的扩展,可以遍历整个 ANTLR 的 AST,对其做遍历的处理,比如在 ShardingSphere 中将各种不同的数据库的 SQL 语句的 ANTLR 的 AST 转换成 ShardingSphere 定义的 AST,如 MySQLStatementSQLVisitorFacade ,OracleFormatSQLVisitorFacade 等。

总结

这个 SQL Parser 还是比较简单的,主要就是基于 ANTLR 实现了 SQL 解析和 AST 的转换。通过定义 parser 和 visitor 的扩展点,ShardingSphere 就可以方便的扩展出对不同类型的数据库方言的支持。​ShardingSphere 通过定义出一套 AST,后面就可以基于这套 AST 生成关系代数表达式了。

Reference