Introduction to the

In the previous article, we explored an overview of the process performed by the ShardingSphere JDBC Mybatis sample, found the key nodes for SQL processing, and looked at the key points for a logical SQL to become real SQL

The source code parsing

Note: some exploration down, logical SQL to achieve real database processing is a bit complex, it takes a lot of time to debug, put in a article is too long, so split, so simple point

SQL parsing generation entry

Continue with the example from the previous article: ShardingSphere JDBC statement execution

ExecutionContext is a very important variable throughout the whole article. After debugging, we found that it already has a real statement to execute, as shown in the following figure:

The key code generated by real SQL is as follows, which is also the part of Mybatis from above

# ShardingSpherePreparedStatement.java
    @Override
    public boolean execute(a) throws SQLException {
        try {
            clearPrevious();
	    // At this point, real SQL and other statements have been generated
            executionContext = createExecutionContext();
            if (metaDataContexts.getMetaData(connection.getSchemaName()).getRuleMetaData().getRules().stream().anyMatch(each -> each instanceof RawExecutionRule)) {
                // TODO process getStatement
                Collection<ExecuteResult> executeResults = rawExecutor.execute(createRawExecutionGroupContext(), executionContext.getLogicSQL(), new RawSQLExecutorCallback());
                return executeResults.iterator().next() instanceof QueryResult;
            }
            if (executionContext.getRouteContext().isFederated()) {
                List<QueryResult> queryResults = executeFederatedQuery();
                return! queryResults.isEmpty(); } ExecutionGroupContext<JDBCExecutionUnit> executionGroupContext = createExecutionGroupContext(); cacheStatements(executionGroupContext.getInputGroups());return driverJDBCExecutor.execute(executionGroupContext,
                    executionContext.getLogicSQL(), executionContext.getRouteContext().getRouteUnits(), createExecuteCallback());
        } finally{ clearBatch(); }}Copy the code

The parse generation path of SQL

As above, let’s start tracking the generated statement: executionContext = createExecutionContext();

    private ExecutionContext createExecutionContext(a) {
        LogicSQL logicSQL = createLogicSQL();
        SQLCheckEngine.check(logicSQL.getSqlStatementContext().getSqlStatement(), logicSQL.getParameters(), 
                metaDataContexts.getMetaData(connection.getSchemaName()).getRuleMetaData().getRules(), connection.getSchemaName(), metaDataContexts.getMetaDataMap(), null);
	// This generates real SQL
        ExecutionContext result = kernelProcessor.generateExecutionContext(logicSQL, metaDataContexts.getMetaData(connection.getSchemaName()), metaDataContexts.getProps());
        findGeneratedKey(result).ifPresent(generatedKey -> generatedValues.addAll(generatedKey.getGeneratedValues()));
        return result;
    }
Copy the code

The trace goes to the key class below, and through annotations we see that it holds the original SQL, associated configuration information, and generates the execution context we want

public final class KernelProcessor {
    
    /**
     * Generate execution context.
     *
     * @param logicSQL logic SQL
     * @param metaData ShardingSphere meta data
     * @param props configuration properties
     * @return execution context
     */
    public ExecutionContext generateExecutionContext(final LogicSQL logicSQL, final ShardingSphereMetaData metaData, final ConfigurationProperties props) {
        RouteContext routeContext = route(logicSQL, metaData, props);
        SQLRewriteResult rewriteResult = rewrite(logicSQL, metaData, props, routeContext);
        ExecutionContext result = createExecutionContext(logicSQL, metaData, routeContext, rewriteResult);
        logSQL(logicSQL, props, result);
        returnresult; }}Copy the code

The above function is very, very core. Let’s look at its three key variables: routeContext, rewriteResult, result

RouteContext saves routing information from logical SQL to real SQL, as shown in the following figure:

As you can see from the red above, it already has database and table routing information, which is critical

RewriteResult rewrites the SQL statement to get the real SQL statement, as shown in the following figure:

Result assemble routeContext, and rewriteResult gets something that runs through the entire ShardingSphere statement, which was covered in the previous article and in the beginning of this article, but won’t be covered here

RewriteResult = rewrite(logicSQL, metaData, props, routeContext)

public final class SQLRewriteEntry {
    
    /**
     * Rewrite.
     * 
     * @param sql SQL
     * @param parameters SQL parameters
     * @param sqlStatementContext SQL statement context
     * @param routeContext route context
     * @return route unit and SQL rewrite result map
     */
    public SQLRewriteResult rewrite(final String sql, final List<Object> parameters, finalSQLStatementContext<? > sqlStatementContext,final RouteContext routeContext) {
        SQLRewriteContext sqlRewriteContext = createSQLRewriteContext(sql, parameters, sqlStatementContext, routeContext);
        return routeContext.getRouteUnits().isEmpty()
                ? new GenericSQLRewriteEngine().rewrite(sqlRewriteContext) : new RouteSQLRewriteEngine().rewrite(sqlRewriteContext, routeContext);
    }
    
    private SQLRewriteContext createSQLRewriteContext(final String sql, final List<Object> parameters, finalSQLStatementContext<? > sqlStatementContext,final RouteContext routeContext) {
        SQLRewriteContext result = new SQLRewriteContext(schema, sqlStatementContext, sql, parameters);
        decorate(decorators, result, routeContext);
        result.generateSQLTokens();
        returnresult; }}Copy the code

In the above: createSQLRewriteContext function, the actual SQL is not generated yet, but it generates key meta-information that is critical for the actual SQL that follows, which is not covered here but will be explored later

New RouteSQLRewriteEngine().rewrite(sqlRewriteContext, routeContext)

Rewrite (sqlRewriteContext) : new GenericSQLRewriteEngine(). Mark one here, let’s continue to follow the main line:

public final class RouteSQLRewriteEngine {
    
    /**
     * Rewrite SQL and parameters.
     *
     * @param sqlRewriteContext SQL rewrite context
     * @param routeContext route context
     * @return SQL rewrite result
     */
    public RouteSQLRewriteResult rewrite(final SQLRewriteContext sqlRewriteContext, final RouteContext routeContext) {
        Map<RouteUnit, SQLRewriteUnit> result = new LinkedHashMap<>(routeContext.getRouteUnits().size(), 1);
        for (RouteUnit each : routeContext.getRouteUnits()) {
            result.put(each, new SQLRewriteUnit(new RouteSQLBuilder(sqlRewriteContext, each).toSQL(), getParameters(sqlRewriteContext.getParameterBuilder(), routeContext, each)));
        }
        return newRouteSQLRewriteResult(result); }}Copy the code

In the above code, class SQLRewriteUnit is directly generated by a key variable: SQL. This is the real SQL we want, which is generated by the final core code: New SQLRewriteUnit(new RouteSQLBuilder(sqlRewriteContext, each).toSQL(), whose toSQL function is where the real SQL is generated:

public abstract class AbstractSQLBuilder implements SQLBuilder {
    
    private final SQLRewriteContext context;
    
    @Override
    public final String toSQL(a) {
        if (context.getSqlTokens().isEmpty()) {
            return context.getSql();
        }
        Collections.sort(context.getSqlTokens());
        StringBuilder result = new StringBuilder();
        result.append(context.getSql(), 0, context.getSqlTokens().get(0).getStartIndex());
        for (SQLToken each : context.getSqlTokens()) {
            result.append(each instanceof ComposableSQLToken ? getComposableSQLTokenText((ComposableSQLToken) each) : getSQLTokenText(each));
            result.append(getConjunctionText(each));
        }
        return result.toString();
    }
    
    protected abstract String getSQLTokenText(SQLToken sqlToken);

    private String getComposableSQLTokenText(final ComposableSQLToken composableSQLToken) {
        StringBuilder result = new StringBuilder();
        for (SQLToken each : composableSQLToken.getSqlTokens()) {
            result.append(getSQLTokenText(each));
            result.append(getConjunctionText(each));
        }
        return result.toString();
    }

    private String getConjunctionText(final SQLToken sqlToken) {
        returncontext.getSql().substring(getStartIndex(sqlToken), getStopIndex(sqlToken)); }}Copy the code

Through debug, we gradually see the variable result, which gradually becomes the real SQL we want. By now, we have basically found the path

There is still a bit more logic here, which has not been analyzed, and will be left to the next chapter to decompose

conclusion

Summarize the key points of the real SQL experience we explored here

  • ShardingSpherePreparedStatement. Java: — an optional executionContext = createExecutionContext ();
  • ShardingSpherePreparedStatement. Java: ExecutionContext result = kernelProcessor.generateExecutionContext(logicSQL, metaDataContexts.getMetaData(connection.getSchemaName()), metaDataContexts.getProps());
  • KernelProcessor.java: generateExecutionContext
    • RouteContext routeContext = route(logicSQL, metaData, props);
    • SQLRewriteResult rewriteResult = rewrite(logicSQL, metaData, props, routeContext);
      • SQLRewriteEntry: rewrite
        • new GenericSQLRewriteEngine().rewrite(sqlRewriteContext)
        • new RouteSQLRewriteEngine().rewrite(sqlRewriteContext, routeContext)
    • ExecutionContext result = createExecutionContext(logicSQL, metaData, routeContext, rewriteResult)

In fact, this time to locate the specific real SQL key code, logic comb is still more time-consuming, so I will leave to the next chapter