写点什么

ShardingSphere LogicSQL 的生成探索

用户头像
关注
发布于: 2 小时前

简介

在上两篇文中,我们探索了 SQLToken 和真实 SQL 的生成的想关代码,本文继续来探索最开始的一个 LogicSQL 的生成,补全这一块拼图

源码探索

继续上面两篇的探索:



其中生成真实的 SQL 有两个要素:


  • 逻辑表名到真实表名的映射:这个在 SQLToken 生成的

  • 拼接 SQL 语句时,对应的 index 位置,这个目前看来是在 LogicSQL 中就生成好了的


目前就还差 index 生成的东西,我们接下来就看看 LogicSQL 的相关代码:

寻找切入点

下面是 LogicSQL 的生成部分:


    private ExecutionContext createExecutionContext() {        LogicSQL logicSQL = createLogicSQL();        SQLCheckEngine.check(logicSQL.getSqlStatementContext().getSqlStatement(), logicSQL.getParameters(),                 metaDataContexts.getMetaData(connection.getSchemaName()).getRuleMetaData().getRules(), connection.getSchemaName(), metaDataContexts.getMetaDataMap(), null);        ExecutionContext result = kernelProcessor.generateExecutionContext(logicSQL, metaDataContexts.getMetaData(connection.getSchemaName()), metaDataContexts.getProps());        findGeneratedKey(result).ifPresent(generatedKey -> generatedValues.addAll(generatedKey.getGeneratedValues()));        return result;    }        private LogicSQL createLogicSQL() {        List<Object> parameters = new ArrayList<>(getParameters());        SQLStatementContext<?> sqlStatementContext = SQLStatementContextFactory.newInstance(metaDataContexts.getMetaDataMap(), parameters, sqlStatement, connection.getSchemaName());        return new LogicSQL(sqlStatementContext, sql, parameters);    }
复制代码


但通过 debug 看到相关的东西其实之前就生成好了:



其中的 preparedStatement 就有了相关的 index 信息,看来是在某一步就初始化好了的,我们找到对应的初始化语句,如下:


# ShardingSpherePreparedStatement.java    private ShardingSpherePreparedStatement(final ShardingSphereConnection connection, final String sql,                                            final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability, final boolean returnGeneratedKeys) throws SQLException {        if (Strings.isNullOrEmpty(sql)) {            throw new SQLException(SQLExceptionConstant.SQL_STRING_NULL_OR_EMPTY);        }        this.connection = connection;        metaDataContexts = connection.getContextManager().getMetaDataContexts();        this.sql = sql;        statements = new ArrayList<>();        parameterSets = new ArrayList<>();        ShardingSphereSQLParserEngine sqlParserEngine = new ShardingSphereSQLParserEngine(                DatabaseTypeRegistry.getTrunkDatabaseTypeName(metaDataContexts.getMetaData(connection.getSchemaName()).getResource().getDatabaseType()));  // 这里进行生成的        sqlStatement = sqlParserEngine.parse(sql, true);        parameterMetaData = new ShardingSphereParameterMetaData(sqlStatement);        statementOption = returnGeneratedKeys ? new StatementOption(true) : new StatementOption(resultSetType, resultSetConcurrency, resultSetHoldability);        JDBCExecutor jdbcExecutor = new JDBCExecutor(metaDataContexts.getExecutorEngine(), connection.isHoldTransaction());        driverJDBCExecutor = new DriverJDBCExecutor(connection.getSchemaName(), metaDataContexts, jdbcExecutor);        rawExecutor = new RawExecutor(metaDataContexts.getExecutorEngine(), connection.isHoldTransaction(), metaDataContexts.getProps());        // TODO Consider FederateRawExecutor        federateExecutor = new FederateJDBCExecutor(connection.getSchemaName(), metaDataContexts.getOptimizeContextFactory(), metaDataContexts.getProps(), jdbcExecutor);        batchPreparedStatementExecutor = new BatchPreparedStatementExecutor(metaDataContexts, jdbcExecutor, connection.getSchemaName());        kernelProcessor = new KernelProcessor();    }
复制代码


我们再往前找找,看到是在 OrderRepositoryImpl.java 中进行触发的:


# OrderRepositoryImpl.java    @Override    public Long insert(final Order order) throws SQLException {        String sql = "INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)";        try (Connection connection = dataSource.getConnection();             PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {            preparedStatement.setInt(1, order.getUserId());            preparedStatement.setLong(2, order.getAddressId());            preparedStatement.setString(3, order.getStatus());            preparedStatement.executeUpdate();            try (ResultSet resultSet = preparedStatement.getGeneratedKeys()) {                if (resultSet.next()) {                    order.setOrderId(resultSet.getLong(1));                }            }        }        return order.getOrderId();    }
复制代码


那我们就继续探索:sqlStatement = sqlParserEngine.parse(sql, true);


我们一直跟着下去,来到一个 SQL 处理的相关类:MySQLStatementParser.java


其给人的第一个感觉是相当的复杂,我们跟着 debug 下去,看到进入到相关的 insert 处理的分支


      case XA:        enterOuterAlt(_localctx, 1);        {        setState(1246);        _errHandler.sync(this);        switch ( getInterpreter().adaptivePredict(_input,0,_ctx) ) {        case 1:          {          setState(1146);          select();          }          break;        case 2:          {          setState(1147);          insert();          }          break;
复制代码


跟着下去,来到 insert 语句处理具体函数:


# MySQLStatementParser.java  public final InsertContext insert() throws RecognitionException {    InsertContext _localctx = new InsertContext(_ctx, getState());    enterRule(_localctx, 2, RULE_insert);    int _la;    try {      enterOuterAlt(_localctx, 1);      {      setState(1258);      // Insert相关处理      match(INSERT);      setState(1259);      insertSpecification();      setState(1261);      _errHandler.sync(this);      _la = _input.LA(1);      if (_la==INTO) {        {        setState(1260);        // into 相关处理        match(INTO);        }      }
setState(1263); // 表名相关处理 tableName(); setState(1265); _errHandler.sync(this); _la = _input.LA(1); if (_la==PARTITION) { { setState(1264); partitionNames(); } }
setState(1270); _errHandler.sync(this); switch ( getInterpreter().adaptivePredict(_input,6,_ctx) ) { case 1: { setState(1267); // values相关处理 insertValuesClause(); } break; case 2: { setState(1268); setAssignmentsClause(); } break; case 3: { setState(1269); insertSelectClause(); } break; } setState(1273); _errHandler.sync(this); _la = _input.LA(1); if (_la==ON) { { setState(1272); onDuplicateKeyClause(); } }
} } catch (RecognitionException re) { _localctx.exception = re; _errHandler.reportError(this, re); _errHandler.recover(this, re); } finally { exitRule(); } return _localctx; }
复制代码


在上面的函数中,我们大意看到几个比较关键的处理函数:


  • Insert 相关处理 : match(INSERT);

  • into 相关处理 : match(INTO);

  • 表名相关处理 : tableName();

  • values 相关处理 : insertValuesClause();


其规则跟下来有点复杂了,有循环和嵌套处理的,目前是梳理不清楚了


但其大意都是得到对应的开始和结束位置之类的,如下图:



最终得到结果如下:



到结果后,相关的返回函数如下:


@RequiredArgsConstructorpublic final class SQLParserExecutor {        private final String databaseType;        /**     * Parse SQL.     *      * @param sql SQL to be parsed     * @return parse tree     */    public ParseTree parse(final String sql) {        ParseASTNode result = twoPhaseParse(sql);        if (result.getRootNode() instanceof ErrorNode) {            throw new SQLParsingException("Unsupported SQL of `%s`", sql);        }        return result.getRootNode();    }}
复制代码


result.getRootNode() 如下:


@RequiredArgsConstructorpublic final class ParseASTNode implements ASTNode {        private final ParseTree parseTree;        /**     * Get root node.     *      * @return root node     */    public ParseTree getRootNode() {        return parseTree.getChild(0);    }}
复制代码


而 result 的结果如下图,getChild(0)就是得到上面我们 Insert 解析后得到的结果


总结

感觉看的迷迷糊糊的,很多地方目前还不能很好的理解


但我们起码通过本次的探索知道了真实 SQL 的关键路径:


  • 通过原始的 LogicSQL 语句,经过的 ShardingSphere 的语法树解析,得到对应的各个部分的元数据,如开始和结束 index

  • 根据语法树解析结果,得到对应的 SQLToken,其中包含了如分库分表中的逻辑表到真实表的映射等关键信息

  • 根据 SQLToken 生成真实的 SQL

发布于: 2 小时前阅读数: 3
用户头像

关注

还未添加个人签名 2018.09.09 加入

代码是门手艺活,也是门艺术活

评论

发布
暂无评论
ShardingSphere LogicSQL 的生成探索