前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >sharding-sphere源码之sql解析

sharding-sphere源码之sql解析

作者头像
山行AI
发布2019-08-01 11:13:05
2.9K1
发布2019-08-01 11:13:05
举报
文章被收录于专栏:山行AI

sharding-sephere源码解析之sql解析

这里我们以org.apache.shardingsphere.shardingjdbc.jdbc.core.statement.ShardingPreparedStatement为起点。

ShardingPreparedStatement构造方法入参

结构图:

源码:

sql解析和路由部分主要在属性 private final PreparedQueryShardingEngine shardingEngine;中,这里我们主要分析下这个类,关于其他属性,之后会有专门篇幅来讲。

PreparedQueryShardingEngine

类结构图:

  • 属性PreparedStatementRoutingEngine routingEngine:路由的引擎,是最终执行解析和路由的关键部分。
  • route方法,是执行解析和路由并返回路由结果的方法。
  • shard方法,是根据分片信息对sql获取解析和路由信息。

下面我们来看一看。

PreparedStatementRoutingEngine

看下源码:

代码语言:javascript
复制
public final class PreparedStatementRoutingEngine {
    /**
     * 逻辑sql
     */
    private final String logicSQL;
    /**
     * 分片路由
     */
    private final ShardingRouter shardingRouter;
    /**
     * 主从路由
     */
    private final ShardingMasterSlaveRouter masterSlaveRouter;

    /**
     * sql statement
     */
    private SQLStatement sqlStatement;

    public PreparedStatementRoutingEngine(final String logicSQL, final ShardingRule shardingRule,
                                          final ShardingMetaData shardingMetaData, final DatabaseType databaseType, final ParsingResultCache parsingResultCache) {
        this.logicSQL = logicSQL;
        //获取分片路由
        shardingRouter = ShardingRouterFactory.newInstance(shardingRule, shardingMetaData, databaseType, parsingResultCache);
        //获取主从路由
        masterSlaveRouter = new ShardingMasterSlaveRouter(shardingRule.getMasterSlaveRules());
    }

    /**
     * SQL route.
     *
     * <p>First routing time will parse SQL, after second time will reuse first parsed result.</p>
     *
     * @param parameters parameters of SQL placeholder
     * @return route result
     */
    public SQLRouteResult route(final List<Object> parameters) {
        if (null == sqlStatement) {
            sqlStatement = shardingRouter.parse(logicSQL, true);
        }
        //这里如果masterSlaveRouter的masterSlaveRules属性集合为空,则会直接返回shardingRouter.route(sqlStatement, parameters)的结果
        return masterSlaveRouter.route(shardingRouter.route(sqlStatement, parameters));
    }
}

可以看到,这个类里面主要起作用的是两类router,ShardingRouter和ShardingMasterSlaveRouter。我们从类继承图来分别看下他们的实现。ShardingRouter:

ShardingMasterSlaveRouter:

从PreparedStatementRoutingEngine的route方法可以看出:

  • 当开始进来时,没有缓存,sqlStatement为null,会进入shardingRouter的parse方法。
  • shardingRouter.route(sqlStatement, parameters)方法的入参sqlStatement是sharding.parse之后得到的sqlStatement。
  • 在使用时masterSlaveRouter的route方法的入参是shardingRouter.route(sqlStatement, parameters)的结果。

这里对于其他类型的路由部分我们不作过多解释,主要关注下ShardingRouter的一个实现ParsingSQLRouter中的解析部分,也就是它的parse方法。

org.apache.shardingsphere.core.route.router.sharding.ParsingSQLRouter#parse

代码语言:javascript
复制
public final class ParsingSQLRouter implements ShardingRouter {
    /**
     * 分片规则
     */
    private final ShardingRule shardingRule;

    /**
     * 分片元数据信息
     */
    private final ShardingMetaData shardingMetaData;

    /**
     * 数据库类型
     */
    private final DatabaseType databaseType;

    /**
     * 解析结果的缓存
     */
    private final ParsingResultCache parsingResultCache;

    private final List<Comparable<?>> generatedValues = new LinkedList<>();

    /**
     * 一个用于spi拓展的钩子
     */
    private final ParsingHook parsingHook = new SPIParsingHook();

    @Override
    public SQLStatement parse(final String logicSQL, final boolean useCache) {
        parsingHook.start(logicSQL);
        try {
            //parse方法调用的是ShardingSQLParseEntry的parse方法
            SQLStatement result = new ShardingSQLParseEntry(databaseType, shardingMetaData.getTable(), parsingResultCache).parse(logicSQL, useCache);
            parsingHook.finishSuccess(result, shardingMetaData.getTable());
            return result;
            // CHECKSTYLE:OFF
        } catch (final Exception ex) {
            // CHECKSTYLE:ON
            parsingHook.finishFailure(ex);
            throw ex;
        }
    }

    @Override
    public SQLRouteResult route(final SQLStatement sqlStatement, final List<Object> parameters) {
    ....
    }
 }

可以看到,这里的parse方法实际调用的是ShardingSQLParseEntry的parse方法。我们继续来看ShardingSQLParseEntry的parse方法。

ShardingSQLParseEntry

它的代码:

代码语言:javascript
复制
public final class ShardingSQLParseEntry extends SQLParseEntry {
    /**
     * 数据库类型
     */
    private final DatabaseType databaseType;
    /**
     * 分表元数据信息 这里是解析sql,所以需要分表信息
     */
    private final ShardingTableMetaData shardingTableMetaData;

    public ShardingSQLParseEntry(final DatabaseType databaseType, final ShardingTableMetaData shardingTableMetaData, final ParsingResultCache parsingResultCache) {
        super(parsingResultCache);
        this.databaseType = databaseType;
        this.shardingTableMetaData = shardingTableMetaData;
    }

    @Override
    protected SQLParseEngine getSQLParseEngine(final String sql) {
        //新建SQLParseEngine 也就是sql解析引擎
        return new SQLParseEngine(ShardingParseRuleRegistry.getInstance(), databaseType, sql, shardingTableMetaData);
    }
}

它父类的源码:

代码语言:javascript
复制
@RequiredArgsConstructor
public abstract class SQLParseEntry {

    /**
     * 缓存解析结果的
     */
    private final ParsingResultCache parsingResultCache;

    /**
     * Parse SQL.
     *
     * @param sql SQL
     * @param useCache use cache or not
     * @return SQL statement
     */
    public final SQLStatement parse(final String sql, final boolean useCache) {
        Optional<SQLStatement> cachedSQLStatement = getSQLStatementFromCache(sql, useCache);
        if (cachedSQLStatement.isPresent()) {
            return cachedSQLStatement.get();
        }
        //这里调用的是子类中实现的getSQLParseEngine方法获取sql解析引擎
        SQLStatement result = getSQLParseEngine(sql).parse();
        if (useCache) {
            parsingResultCache.put(sql, result);
        }
        return result;
    }

    private Optional<SQLStatement> getSQLStatementFromCache(final String sql, final boolean useCache) {
        //从缓存中获取,并返回一个Optional对象,避免NPE
        return useCache ? Optional.fromNullable(parsingResultCache.getSQLStatement(sql)) : Optional.<SQLStatement>absent();
    }

    protected abstract SQLParseEngine getSQLParseEngine(String sql);
}

可以看出,在parse方法中调用的是SQLParseEngine的parse方法,我们接着往下看。

SQLParseEngine

源代码:

代码语言:javascript
复制
public final class SQLParseEngine {

    /**
     * sql解析引擎,真正的sql解析引擎
     */
    private final SQLParserEngine parserEngine;

    /**
     * sql片段提取引擎
     */
    private final SQLSegmentsExtractorEngine extractorEngine;

    /**
     * sql statement 过滤引擎
     */
    private final SQLStatementFillerEngine fillerEngine;

    public SQLParseEngine(final ParseRuleRegistry parseRuleRegistry, final DatabaseType databaseType, final String sql, final ShardingTableMetaData shardingTableMetaData) {
        DatabaseType trunkDatabaseType = DatabaseTypes.getTrunkDatabaseType(databaseType.getName());
        parserEngine = new SQLParserEngine(parseRuleRegistry, trunkDatabaseType, sql);
        extractorEngine = new SQLSegmentsExtractorEngine();
        fillerEngine = new SQLStatementFillerEngine(parseRuleRegistry, trunkDatabaseType, sql, shardingTableMetaData);
    }

    /**
     * Parse SQL.
     *
     * @return SQL statement
     */
    public SQLStatement parse() {
        SQLAST ast = parserEngine.parse();
        Collection<SQLSegment> sqlSegments = extractorEngine.extract(ast);
        Map<ParserRuleContext, Integer> parameterMarkerIndexes = ast.getParameterMarkerIndexes();
        return fillerEngine.fill(sqlSegments, parameterMarkerIndexes.size(), ast.getSqlStatementRule());
    }
}
  • SQLParserEngine parserEngine sql真正的解析引擎。
  • SQLSegmentsExtractorEngine extractorEngine 用来指取SQLAST语法书中的sql片段。
  • SQLStatementFillerEngine fillerEngine 是用来生成最后的SQLStatement的。
SQLParserEngine

源代码:

代码语言:javascript
复制
@RequiredArgsConstructor
public final class SQLParserEngine {

    private final ParseRuleRegistry parseRuleRegistry;

    private final DatabaseType databaseType;

    private final String sql;

    /**
     * Parse SQL to abstract syntax tree.
     *
     * @return abstract syntax tree of SQL
     */
    public SQLAST parse() {
        //1. 生成语法树
        ParseTree parseTree = SQLParserFactory.newInstance(databaseType, sql).execute().getChild(0);
        if (parseTree instanceof ErrorNode) {
            throw new SQLParsingException(String.format("Unsupported SQL of `%s`", sql));
        }
        //2. sql statement的规则
        SQLStatementRule sqlStatementRule = parseRuleRegistry.getSQLStatementRule(databaseType, parseTree.getClass().getSimpleName());
        if (null == sqlStatementRule) {
            throw new SQLParsingException(String.format("Unsupported SQL of `%s`", sql));
        }
        //3. sql的ast 语法树
        return new SQLAST((ParserRuleContext) parseTree, getParameterMarkerIndexes((ParserRuleContext) parseTree), sqlStatementRule);
    }

    private Map<ParserRuleContext, Integer> getParameterMarkerIndexes(final ParserRuleContext rootNode) {
        //获取到树节点下所有子节点
        Collection<ParserRuleContext> placeholderNodes = ExtractorUtils.getAllDescendantNodes(rootNode, RuleName.PARAMETER_MARKER);
        Map<ParserRuleContext, Integer> result = new HashMap<>(placeholderNodes.size(), 1);
        int index = 0;
        for (ParserRuleContext each : placeholderNodes) {
            //将节点信息放入result中
            result.put(each, index++);
        }
        return result;
    }
}

上面的过程说明如下:

  1. 生成语法树
代码语言:javascript
复制
ParseTree parseTree = SQLParserFactory.newInstance(databaseType, sql).execute().getChild(0);

SQLParserFactory.newInstance:

代码语言:javascript
复制
/**
     * New instance of SQL parser.
     *
     * @param databaseType database type
     * @param sql SQL
     * @return SQL parser
     */
    public static SQLParser newInstance(final DatabaseType databaseType, final String sql) {
        //根据数据库类型在SPI拓展里面获取SQLParserEntry实例
        for (SQLParserEntry each : NewInstanceServiceLoader.newServiceInstances(SQLParserEntry.class)) {
            if (DatabaseTypes.getActualDatabaseType(each.getDatabaseType()) == databaseType) {
                return createSQLParser(sql, each);
            }
        }
        throw new UnsupportedOperationException(String.format("Cannot support database type '%s'", databaseType));
    }
  • NewInstanceServiceLoader是一个sharding-jdbc的spi拓展点加载器,用于加载SQLParserEntry的实现,可以看下sharding-core-parse-mysql这个module下的:

sharding-core-parse-* 其他类型的数据库中也是类似。

  • 这里使用的SQLParserEntry是:
  • createSQLParser(sql, each)方法:
代码语言:javascript
复制
@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));
}

parserEntry.getParserClass()获取到的是MySQLParser,我们看下MySQLParser的实现:

代码语言:javascript
复制
public final class MySQLParser extends MySQLStatementParser implements SQLParser {

    public MySQLParser(final TokenStream input) {
        super(input);
    }
}

这里需要关注一下MySQLStatementParser,它是通过antlr4自动生成的,仍然以mysql为例,看sharding-core-parse-mysql这个module的配置。pom:

pom中对应的lib中的信息和要生成代码的g4文件的信息:

最终生成的代码文件:

所以这里获取到的parser实际上是antlr通过自己配置的语法解析g4文件来生成的。

MySQLStatementParser.execute:

这个方法返回的是ExecuteContext对象。

ExecuteContext.getChild(0)获取第一个子节点,获取解析树,实际调用的是org.antlr.v4.runtime.ParserRuleContext#getChild(int):

  1. sql statement的规则 根据ParseTree的结果生成SQLStatementRule:

org.apache.shardingsphere.core.parse.rule.registry.ParseRuleRegistry#getSQLStatementRule:

代码语言:javascript
复制
/**
     * Get SQL statement rule.
     *
     * @param databaseType database type
     * @param contextClassName context class name
     * @return SQL statement rule
     */
    public SQLStatementRule getSQLStatementRule(final DatabaseType databaseType, final String contextClassName) {
        return sqlStatementRuleDefinitions.get(databaseType).getSQLStatementRule(contextClassName);
    }

这个sqlStatementRuleDefinitions是在org.apache.shardingsphere.core.parse.rule.registry.ParseRuleRegistry#initParseRuleDefinition中加载的:

代码语言:javascript
复制
public ParseRuleRegistry() {
        initParseRuleDefinition();
    }

    private void initParseRuleDefinition() {
        ExtractorRuleDefinitionEntity generalExtractorRuleEntity = extractorRuleLoader.load(RuleDefinitionFileConstant.getExtractorRuleDefinitionFile());
        FillerRuleDefinitionEntity generalFillerRuleEntity = fillerRuleLoader.load(RuleDefinitionFileConstant.getFillerRuleDefinitionFile());
        FillerRuleDefinitionEntity featureGeneralFillerRuleEntity = fillerRuleLoader.load(RuleDefinitionFileConstant.getFillerRuleDefinitionFile(getType()));
        for (DatabaseType each : SQLParserFactory.getAddOnDatabaseTypes()) {
            fillerRuleDefinitions.put(each, createFillerRuleDefinition(generalFillerRuleEntity, featureGeneralFillerRuleEntity, each));
            sqlStatementRuleDefinitions.put(each, createSQLStatementRuleDefinition(generalExtractorRuleEntity, each));
        }
    }

加载的内容为:

  1. sql的ast 语法树 SQLAST:
代码语言:javascript
复制
@RequiredArgsConstructor
@Getter
public final class SQLAST {

    private final ParserRuleContext parserRuleContext;

    private final Map<ParserRuleContext, Integer> parameterMarkerIndexes;

    private final SQLStatementRule sqlStatementRule;
}

可以看到SQLStatementRule的信息和parserRuleContext都是包括在SQLAST中的。

这时我们再回过头来看一下org.apache.shardingsphere.core.route.router.sharding.ParsingSQLRouter#parse:

代码语言:javascript
复制
@Override
    public SQLStatement parse(final String logicSQL, final boolean useCache) {
        parsingHook.start(logicSQL);
        try {
            //parse方法调用的是ShardingSQLParseEntry的parse方法
            SQLStatement result = new ShardingSQLParseEntry(databaseType, shardingMetaData.getTable(), parsingResultCache).parse(logicSQL, useCache);
            parsingHook.finishSuccess(result, shardingMetaData.getTable());
            return result;
            // CHECKSTYLE:OFF
        } catch (final Exception ex) {
            // CHECKSTYLE:ON
            parsingHook.finishFailure(ex);
            throw ex;
        }
    }

通过下面一系列的解析之后,这里就可以在ParsingSQLRouter的parse方法中获取到SQLStatement然后用于到sql的路由中去了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开发架构二三事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ShardingPreparedStatement构造方法入参
  • PreparedQueryShardingEngine
    • PreparedStatementRoutingEngine
      • org.apache.shardingsphere.core.route.router.sharding.ParsingSQLRouter#parse
        • ShardingSQLParseEntry
          • SQLParseEngine
            • SQLParserEngine
        相关产品与服务
        云数据库 SQL Server
        腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档