专栏首页开发架构二三事sharding-sphere源码之sql解析

sharding-sphere源码之sql解析

sharding-sephere源码解析之sql解析

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

ShardingPreparedStatement构造方法入参

结构图:

源码:

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

PreparedQueryShardingEngine

类结构图:

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

下面我们来看一看。

PreparedStatementRoutingEngine

看下源码:

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

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

它的代码:

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);
    }
}

它父类的源码:

@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

源代码:

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

源代码:

@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. 生成语法树
ParseTree parseTree = SQLParserFactory.newInstance(databaseType, sql).execute().getChild(0);

SQLParserFactory.newInstance:

/**
     * 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)方法:
@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的实现:

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:

/**
     * 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中加载的:

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:
@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:

@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的路由中去了。

本文分享自微信公众号 - 开发架构二三事(gh_d6f166e26398),作者:两个小灰象

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • disruptor源码分析四之consumer流程

    一般调用disruptor的hadleEventsWith方法添加event handler处理消费到的event。

    开发架构二三事
  • sharding-jdbc源码之读写分离和从库负载

    使用上面的配置,就可以使用shardingjdbc的RandomMasterSlaveLoadBalanceAlgorithm算法进行读写分离和从库负载了。那么...

    开发架构二三事
  • flink的local模式启动全流程源码分析

    这是一个执行WordCount的操作,我们以这个demo为入口来对整个执行流程进行分析记录。

    开发架构二三事
  • 【漫画】腾讯面试,我竟然输给了final关键字

    任何变量前被 final 修饰就是 final 变量,定义的类前被 final 修饰就是 final 类,任何方法前被 final 修饰就是final方法。

    乔戈里
  • Java——四种访问控制权限及Java命名规范

    之前的博文中提到了访问控制权限,本文系统的讲解下。Java中一共定义四种访问控制权限,由小到大的顺序是:private<defult<protected<pub...

    Winter_world
  • 编程思想 之「继承、组合、fianl」

    提起「复用类」三个字,相信我们脑海中浮现的都是「组合」和「继承」,实际上,在 Java 中复用类的方法也确实是这两种。

    CG国斌
  • 时间复杂度和空间复杂度

    这个算法的运行次数函数是f (n) =3。 根据我们推导大0阶的方法,第一步就是把常数项3 改为1。在保留最高阶项时发现,它根本没有最高阶项,所以这个算法的时...

    周三不加班
  • 算法核心——空间复杂度和时间复杂度超详细解析

    其实说白了,算法就是一个计算过程解决问题的方法。我们现在已经知道数据结构表示数据是怎么存储的,而“程序=数据结构+算法”,数据结构是静态的,算法是动态的,它们加...

    泰斗贤若如
  • SourceTree的基本使用

      克隆完成后,得到的是发布后的master源码,如果想要获取最新的正在开发中的源码,需要对项目流进行初始化,点击“Git工作流”

    用户1409099
  • Android——NORDIC nRF5x系列 DFU升级 APP实现

    2、AndroidMainfest.xml中申请BLE的相关权限、读写权限、定位等。

    Winter_world

扫码关注云+社区

领取腾讯云代金券