preface

This chapter examines the core step of ShardingJDBC: rewriting.

SQL rewrite entry

Return to BasePrepareEngine#prepare, and finally get RouteContext after routing processing, and enter the executeRewrite rewrite process.

public ExecutionContext prepare(final String sql, final List<Object> parameters) { List<Object> clonedParameters = cloneParameters(parameters); RouteContext = executeRoute(SQL, clonedParameters); RouteContext = executeRoute(SQL, clonedParameters); ExecutionContext result = new ExecutionContext(routeContext.getSqlStatementContext()); // Rewrite Collection<ExecutionUnit> executionUnits = executeRewrite(SQL, clonedParameters, routeContext); result.getExecutionUnits().addAll(executionUnits); / / print SQL if (properties. Boolean > getValue (ConfigurationPropertyKey. SQL_SHOW)) {SQLLogger. LogSQL (SQL, properties.<Boolean>getValue(ConfigurationPropertyKey.SQL_SIMPLE), result.getSqlStatementContext(), result.getExecutionUnits()); } return result; }Copy the code

BasePrepareEngine#executeRewrite Rewrite process is divided into three steps:

1. Registered SQLRewriteContextDecorator to SQLRewriteEntry.

2. SQLRewriteEntry creates SQLRewriteContext, overwrites the parameter list, and creates SQLToken.

3. Run SQLRouteRewriteEngine to rewrite SQL and assemble the parameter list.

private Collection<ExecutionUnit> executeRewrite(final String sql, final List<Object> parameters, Final RouteContext RouteContext) {/ / registered BaseRule (ShardingRule) and the corresponding SQL SQLRewriteContextDecorator rewrite the processing class Go to SQLRewriteEntry (rewriter) registerRewriteDecorator(); // Create SQLRewriteContext, rewrite the parameter list, Create SQLToken SQLRewriteContext SQLRewriteContext = rewriter. CreateSQLRewriteContext (SQL, the parameters, routeContext.getSqlStatementContext(), routeContext); If (routeContext. GetRouteResult (.) getRouteUnits (). The isEmpty ()) {/ / routing result is empty Such as go ShardingIgnoreRoutingEngine (use XXX) return  rewrite(sqlRewriteContext); Return rewrite(routeContext, sqlRewriteContext); // Rewrite (routeContext, sqlRewriteContext); }}Copy the code

Second, the registered SQLRewriteContextDecorator

BasePrepareEngine# executeRewrite first step, is to register to SQLRewriteEntry SQLRewriteContextDecorator. This step is similar to the BasePrepareEngine#registerRouteDecorator in the routing process to register the RouteDecorator to the DataNodeRouter.

private void registerRewriteDecorator() { for (Class<? extends SQLRewriteContextDecorator> each : OrderedRegistry.getRegisteredClasses(SQLRewriteContextDecorator.class)) { SQLRewriteContextDecorator rewriteContextDecorator = each.newInstance(); Class<? > ruleClass = (Class<? >) rewriteContextDecorator.getType(); rules.stream().filter(rule -> rule.getClass() == ruleClass || rule.getClass().getSuperclass() == RuleClass).collect(Collectors. ToList ()) // Map of SQLRewriteEntry <BaseRule, SQLRewriteContextDecorator> .forEach(rule -> rewriter.registerDecorator(rule, rewriteContextDecorator)); }}Copy the code

Third, SQLRewriteEntry

SQLRewriteEntry is responsible for creating the SQLRewriteContextsql rewrite context, rewriting the parameter list, and creating the SQLToken.

Public Final class SQLRewriteEntry {// Metadata information of the table private Final SchemaMetaData SchemaMetaData; // Private final ConfigurationProperties properties; // BaseRule - SQLRewriteContextDecorator private final Map<BaseRule, SQLRewriteContextDecorator> decorators = new LinkedHashMap<>(); }Copy the code

Two public methods are exposed:

  1. SQLRewriteContextDecorator registerDecorator methods: registration, in the BasePrepareEngine# executeRewrite the first step in the execution.
private final Map<BaseRule, SQLRewriteContextDecorator> decorators = new LinkedHashMap<>();
public void registerDecorator(final BaseRule rule, final SQLRewriteContextDecorator decorator) {
  decorators.put(rule, decorator);
}
Copy the code
  1. CreateSQLRewriteContext method: Create SQLRewriteContext and perform all SQLRewriteContextDecorator, create SQLToken, this is BasePrepareEngine# executeRewrite second step.
public SQLRewriteContext createSQLRewriteContext(final String sql, final List<Object> parameters, final SQLStatementContext sqlStatementContext, final RouteContext routeContext) { SQLRewriteContext result = new SQLRewriteContext(schemaMetaData, sqlStatementContext, sql, parameters); / / perform all SQLRewriteContextDecorator, rewrite the argument list decorate (decorators, result, routeContext); // Create SQLToken result.generatesQLTokens (); return result; } private void decorate(final Map<BaseRule, SQLRewriteContextDecorator> decorators, final SQLRewriteContext sqlRewriteContext, final RouteContext routeContext) { for (Entry<BaseRule, SQLRewriteContextDecorator> entry : decorators.entrySet()) { BaseRule rule = entry.getKey(); SQLRewriteContextDecorator decorator = entry.getValue(); if (decorator instanceof RouteContextAware) { ((RouteContextAware) decorator).setRouteContext(routeContext); } decorator.decorate(rule, properties, sqlRewriteContext); }}Copy the code

SQLRewriteContextDecorator

SQLRewriteContextDecorator, usually to do two things:

  • ParameterRewriter, which executes the ParameterRewriter collection and saves the rewrite information to SQLRewriteContext#parameterBuilder
  • Create a collection of sqlTokenGenerators and store it in SQLRewriteContext#sqlTokenGenerators

There are three implementation: SQLRewriteContextDecorator

  • EncryptSQLRewriteContextDecorator desensitization is responsible for the data.
  • Shadow ShadowSQLRewriteContextDecorator is responsible for the database.
  • ShardingSQLRewriteContextDecorator responsible for standard SQLRewriteContext decoration.

Here focus on ShardingSQLRewriteContextDecorator decorate method.

public final class ShardingSQLRewriteContextDecorator implements SQLRewriteContextDecorator<ShardingRule>, RouteContextAware { private RouteContext routeContext; @Override public void decorate(final ShardingRule shardingRule, final ConfigurationProperties properties, final SQLRewriteContext sqlRewriteContext) { // 1. Through ShardingParameterRewriterBuilder tectonic ParameterRewriter collection ShardingParameterRewriterBuilder rewriterBuilder = new ShardingParameterRewriterBuilder(shardingRule, routeContext); Collection<ParameterRewriter> parameterRewriters = rewriterBuilder.getParameterRewriters(sqlRewriteContext.getSchemaMetaData()); Execute all parameterRewriters for (ParameterRewriter each: parameterRewriters) {if (! sqlRewriteContext.getParameters().isEmpty() && each.isNeedRewrite(sqlRewriteContext.getSqlStatementContext())) { each.rewrite(sqlRewriteContext.getParameterBuilder(), sqlRewriteContext.getSqlStatementContext(), sqlRewriteContext.getParameters()); }} / / 3. Create SQLTokenGenerators join SQLRewriteContext ShardingTokenGenerateBuilder tokenGenerateBuilder = new ShardingTokenGenerateBuilder(shardingRule, routeContext); sqlRewriteContext.addSQLTokenGenerators(tokenGenerateBuilder.getSQLTokenGenerators()); }}Copy the code

Parameters to rewrite

First look at the ShardingParameterRewriterBuilder which ParameterRewriter obtained.

public final class ShardingParameterRewriterBuilder implements ParameterRewriterBuilder { private final ShardingRule shardingRule; private final RouteContext routeContext; @Override public Collection<ParameterRewriter> getParameterRewriters(final SchemaMetaData schemaMetaData) { // Get all ParameterRewriter Collection<ParameterRewriter> result = getParameterRewriters(); For (ParameterRewriter each: result) {// Implement setter methods of Aware, dependency injection setUpParameterRewriters(each, schemaMetaData); } return result; } private static Collection<ParameterRewriter> getParameterRewriters() { Collection<ParameterRewriter> result = new LinkedList<>(); result.add(new ShardingGeneratedKeyInsertValueParameterRewriter()); result.add(new ShardingPaginationParameterRewriter()); return result; } private void setUpParameterRewriters(final ParameterRewriter parameterRewriter, final SchemaMetaData schemaMetaData) { if (parameterRewriter instanceof SchemaMetaDataAware) { ((SchemaMetaDataAware) parameterRewriter).setSchemaMetaData(schemaMetaData); } if (parameterRewriter instanceof ShardingRuleAware) { ((ShardingRuleAware) parameterRewriter).setShardingRule(shardingRule); } if (parameterRewriter instanceof RouteContextAware) { ((RouteContextAware) parameterRewriter).setRouteContext(routeContext); }}}Copy the code

Primary key argument overridden

Rewrite ShardingGeneratedKeyInsertValueParameterRewriter responsible for primary key parameters. The isNeedRewrite method determines that a primary key is generated only if it exists in the generate primary key context in the SQL context. The rewrite method puts the generated primary key into the addedIndexAndParameters member variable of StandardParameterBuilder. All parameterReWriters reconstruct parameter lists with the help of ParameterBuilder.

@Setter public final class ShardingGeneratedKeyInsertValueParameterRewriter implements ParameterRewriter<InsertStatementContext> {// Only insert statements, @override public Boolean isNeedRewrite(final SQLStatementContext SQLStatementContext) {return sqlStatementContext instanceof InsertStatementContext && ((InsertStatementContext) sqlStatementContext).getGeneratedKeyContext().isPresent() && ((InsertStatementContext) sqlStatementContext).getGeneratedKeyContext().get().isGenerated(); } @Override public void rewrite(final ParameterBuilder parameterBuilder, final InsertStatementContext insertStatementContext, final List<Object> parameters) { Preconditions.checkState(insertStatementContext.getGeneratedKeyContext().isPresent()); ((GroupedParameterBuilder) parameterBuilder).setDerivedColumnName(insertStatementContext.getGeneratedKeyContext().get().getColumnName()); Iterator<Comparable<? >> generatedValues = insertStatementContext.getGeneratedKeyContext().get().getGeneratedValues().descendingIterator(); int count = 0; int parametersCount = 0; // Insert the primary key into params for each group. insertStatementContext.getGroupedParameters()) { parametersCount += insertStatementContext.getInsertValueContexts().get(count).getParametersCount(); Comparable<? > generatedValue = generatedValues.next(); if (! each.isEmpty()) { StandardParameterBuilder standardParameterBuilder = ((GroupedParameterBuilder) parameterBuilder).getParameterBuilders().get(count); // Insert Map<Integer into StandardParameterBuilder, Collection<Object>> addedIndexAndParameters standardParameterBuilder.addAddedParameters(parametersCount, Lists.newArrayList(generatedValue)); } count++; }}}Copy the code

Page parameter rewrite

Rewrite ShardingPaginationParameterRewriter is responsible for the paging parameters. IsNeedRewrite determines that only paging queries with more than one RouteUnit need to be rewritten. The rewrite method overrides offset and rowCount, and eventually goes into ParameterBuilder as well. The focus is on the PaginationContext#getRevisedOffset method and the PaginationContext#getRevisedRowCount method.

@Setter public final class ShardingPaginationParameterRewriter implements ParameterRewriter<SelectStatementContext>, RouteContextAware { private RouteContext routeContext; @Override public boolean isNeedRewrite(final SQLStatementContext sqlStatementContext) { return sqlStatementContext instanceof SelectStatementContext && ((SelectStatementContext) sqlStatementContext).getPaginationContext().isHasPagination() && ! routeContext.getRouteResult().isSingleRouting(); } @Override public void rewrite(final ParameterBuilder parameterBuilder, final SelectStatementContext selectStatementContext, final List<Object> parameters) { PaginationContext pagination = selectStatementContext.getPaginationContext(); / / rewrite offset pagination. GetOffsetParameterIndex () ifPresent (offsetParameterIndex - > rewriteOffset (pagination, offsetParameterIndex, (StandardParameterBuilder) parameterBuilder)); / / rewrite the rowCount pagination. GetRowCountParameterIndex () ifPresent (rowCountParameterIndex - > rewriteRowCount (pagination, rowCountParameterIndex, (StandardParameterBuilder) parameterBuilder, selectStatementContext)); } private void rewriteOffset(final PaginationContext pagination, final int offsetParameterIndex, final StandardParameterBuilder parameterBuilder) { parameterBuilder.addReplacedParameters(offsetParameterIndex, pagination.getRevisedOffset()); } private void rewriteRowCount(final PaginationContext pagination, final int rowCountParameterIndex, final StandardParameterBuilder parameterBuilder, final SQLStatementContext sqlStatementContext) { parameterBuilder.addReplacedParameters(rowCountParameterIndex, pagination.getRevisedRowCount((SelectStatementContext) sqlStatementContext)); }}Copy the code

The PaginationContext#getRevisedOffset method returns a fixed value of 0, which means that any paging query parameter whose RouteUnit exceeds one must have its offset overwritten to 0.

public long getRevisedOffset() {
  return 0L;
}
Copy the code

PaginationContext# getRevisedRowCount method. If the isMaxRowCount method determines that a certain condition is met, the rowCount is overwritten as Integer.MAX_VALUE, followed by x+y in the case of limit x or y, and the original rowCount in all other cases.

public long getRevisedRowCount(final SelectStatementContext shardingStatement) { if (isMaxRowCount(shardingStatement)) {  return Integer.MAX_VALUE; } return rowCountSegment instanceof LimitValueSegment ? actualOffset + actualRowCount : actualRowCount; } private boolean isMaxRowCount(final SelectStatementContext shardingStatement) { return (! shardingStatement.getGroupByContext().getItems().isEmpty() || ! shardingStatement.getProjectionsContext().getAggregationProjections().isEmpty()) && ! shardingStatement.isSameGroupByAndOrderByItems(); }Copy the code

IsMaxRowCount indicates that if there are group by or aggregate functions (sum and count) and group BY is inconsistent with Order BY, set rowCount to integer.max_value. Then focus is isSameGroupByAndOrderByItems method.

public boolean isSameGroupByAndOrderByItems() { return ! groupByContext.getItems().isEmpty() && groupByContext.getItems().equals(orderByContext.getItems()); }Copy the code

IsSameGroupByAndOrderByItems method, when the group by item and the order by a consistent, returns true, Focus on the Equals method of OrderByItem (the items property of GroupByContext and OrderByContext are OrderByItem collections).

@Getter @Setter @EqualsAndHashCode public final class OrderByItem { private final OrderByItemSegment segment; private int index; public OrderByItem(OrderByItemSegment segment) { this.segment = segment; } @Override public boolean equals(final Object obj) { if (null == obj || ! (obj instanceof OrderByItem)) { return false; } OrderByItem orderByItem = (OrderByItem) obj; / / lifting sequence is the same And the same index return segment. GetOrderDirection () = = orderByItem. GetSegment () getOrderDirection () && index = = orderByItem.getIndex(); }}Copy the code

Returns true if OrderDirection rises or falls in the same order and index is the same.

Select * from t_order group by user_id order by user_id desc limit 1. ,? .

? ,? It’s going to be rewritten as 0, 2147483647. Because there is no the where condition, which leads to the shardingConditions is empty, cause ShardingStandardRoutingEngine will choose all of the available data sources. If there is more than one RouteUnit, offset must be 0. The equals method of OrderByItem returns false, which satisfies isMaxRowCount, so rowCount is 2147483647. (By default, group by will be sorted in ascending order, common optimization group by XXX order by NULL).

Select * from t_order limit? ,? .

? ,? It’s going to be rewritten as zero, right? +? .

Select count(*) from t_order where user_id = 2 and order_id = 7 group by user_id order by user_id desc limit 1. ,? .

If user_id = 2 and order_id = 7 is routed to the single data source table, here ShardingPaginationParameterRewriter# isNeedRewrite returns false, not into the paging parameters rewriting logic.

Create the SQLTokenGenerator collection

Back to ShardingSQLRewriteContextDecorator decorate method, and finally a collection of logic is to create SQLTokenGenerator join SQLRewriteContext.

public final class ShardingSQLRewriteContextDecorator implements SQLRewriteContextDecorator<ShardingRule>, RouteContextAware { private RouteContext routeContext; @Override public void decorate(final ShardingRule shardingRule, final ConfigurationProperties properties, final SQLRewriteContext sqlRewriteContext) { // ... / / 3. Create SQLTokenGenerators join SQLRewriteContext ShardingTokenGenerateBuilder tokenGenerateBuilder = new ShardingTokenGenerateBuilder(shardingRule, routeContext); sqlRewriteContext.addSQLTokenGenerators(tokenGenerateBuilder.getSQLTokenGenerators()); }}Copy the code

Look at the ShardingTokenGenerateBuilder getSQLTokenGenerators method.

public final class ShardingTokenGenerateBuilder implements SQLTokenGeneratorBuilder { private final ShardingRule shardingRule; private final RouteContext routeContext; @Override public Collection<SQLTokenGenerator> getSQLTokenGenerators() { Collection<SQLTokenGenerator> result = buildSQLTokenGenerators(); for (SQLTokenGenerator each : result) { if (each instanceof ShardingRuleAware) { ((ShardingRuleAware) each).setShardingRule(shardingRule); } if (each instanceof RouteContextAware) { ((RouteContextAware) each).setRouteContext(routeContext); } } return result; } private Collection<SQLTokenGenerator> buildSQLTokenGenerators() { Collection<SQLTokenGenerator> result = new LinkedList<>(); addSQLTokenGenerator(result, new TableTokenGenerator()); addSQLTokenGenerator(result, new DistinctProjectionPrefixTokenGenerator()); addSQLTokenGenerator(result, new ProjectionsTokenGenerator()); addSQLTokenGenerator(result, new OrderByTokenGenerator()); addSQLTokenGenerator(result, new AggregationDistinctTokenGenerator()); addSQLTokenGenerator(result, new IndexTokenGenerator()); addSQLTokenGenerator(result, new OffsetTokenGenerator()); addSQLTokenGenerator(result, new RowCountTokenGenerator()); addSQLTokenGenerator(result, new GeneratedKeyInsertColumnTokenGenerator()); addSQLTokenGenerator(result, new GeneratedKeyForUseDefaultInsertColumnsTokenGenerator()); addSQLTokenGenerator(result, new GeneratedKeyAssignmentTokenGenerator()); addSQLTokenGenerator(result, new ShardingInsertValuesTokenGenerator()); addSQLTokenGenerator(result, new GeneratedKeyInsertValuesTokenGenerator()); return result; } private void addSQLTokenGenerator(final Collection<SQLTokenGenerator> sqlTokenGenerators, final SQLTokenGenerator toBeAddedSQLTokenGenerator) { if (toBeAddedSQLTokenGenerator instanceof IgnoreForSingleRoute && routeContext.getRouteResult().isSingleRouting()) { return; } sqlTokenGenerators.add(toBeAddedSQLTokenGenerator); }}Copy the code

Actually ShardingTokenGenerateBuilder is assembled a bunch of SQLTokenGenerator collections, and perform the Aware method into attributes. Finally, these sqltokengenerators are put into the SQL rewrite context and executed in the next step of SQLRewriteEntry#createSQLRewriteContext to create the SQLToken. At this point SQLRewriteContextDecorator work is over.

Generate SQLToken

Return to SQLRewriteEntry#createSQLRewriteContext. The last step is to execute SQLRewriteContext#generateSQLTokens to generateSQLToken.

private final SQLTokenGenerators sqlTokenGenerators = new SQLTokenGenerators();
public void generateSQLTokens() {
    List<SQLToken> sqlTokens = sqlTokenGenerators.generateSQLTokens(sqlStatementContext, parameters, schemaMetaData);
    this.sqlTokens.addAll(sqlTokens);
}
Copy the code

SQLTokenGenerators execution is SQLRewriteContextDecorator into SQL rewriting in the context of each SQLTokenGenerator.

public final class SQLTokenGenerators { private final Collection<SQLTokenGenerator> sqlTokenGenerators = new LinkedList<>(); public List<SQLToken> generateSQLTokens(final SQLStatementContext sqlStatementContext, final List<Object> parameters, final SchemaMetaData schemaMetaData) { List<SQLToken> result = new LinkedList<>(); for (SQLTokenGenerator each : // Execute Aware method setUpSQLTokenGenerator(each, parameters, schemaMetaData, result); // Execute Aware method setUpSQLTokenGenerator(each, parameters, schemaMetaData, result); // The generator determines if it needs to generate SQLToken if (! each.isGenerateSQLToken(sqlStatementContext)) { continue; } // Optional Token generator, As long as the result set with this SQLToken don't need to join the if the result set (each instanceof OptionalSQLTokenGenerator) {SQLToken SQLToken = ((OptionalSQLTokenGenerator) each).generateSQLToken(sqlStatementContext); if (! result.contains(sqlToken)) { result.add(sqlToken); }} // Set Token generator, Generate batch SQLToken else if (each instanceof CollectionSQLTokenGenerator) {result. AddAll (((CollectionSQLTokenGenerator) each).generateSQLTokens(sqlStatementContext)); } } return result; }}Copy the code

What is SQLToken?

@RequiredArgsConstructor @Getter public abstract class SQLToken implements Comparable<SQLToken> { private final int startIndex; @Override public final int compareTo(final SQLToken sqlToken) { return startIndex - sqlToken.getStartIndex(); }}Copy the code

SQLToken encapsulates only a startIndex attribute and implements the Comparable interface with startIndex. The startIndex represents the starting index of an SQL word. SQLToken is a word abstraction in an SQL string that needs to be overridden.

For example, to rewrite a logical table as a real table, it is important to know the position of the logical table in SQL, such as the start index, end index, so that it is easy to replace. For example, for select * from T_ORDER, t_order is the table name, the implementation of SQLToken is TableToken, and its startIndex is 14. See TableTokenGenerator for the logic.

For example, in the group aggregation scenario, avG average calculation is performed from different tables of different data sources, and the result set needs to be merged, so the sum and count of each SQL must be obtained. Finally, AVG = total sum/total count. To do this, you must add two query fields (sum and count) corresponding to an SQLToken (ProjectionsToken). See ProjectionsTokenGenerator specific logic.

Four, SQLRouteRewriteEngine

BasePrepareEngine#rewrite is the third step in the rewrite process, executing the rewrite engine, rewriting SQL, and assembling the argument list. The rewrite method mainly executes the SQLRouteRewriteEngine#rewrite method, followed by the assembly of ExecutionUnit.

private Collection<ExecutionUnit> rewrite(final RouteContext routeContext, final SQLRewriteContext sqlRewriteContext) { Collection<ExecutionUnit> result = new LinkedHashSet<>(); SQLRouteRewriteEngine rewriteEngine = new SQLRouteRewriteEngine(); // SQLRouteRewriteEngine rewrite SQL Map<RouteUnit, SQLRewriteResult> rewrite = rewriteEngine.rewrite(sqlRewriteContext, routeContext.getRouteResult()); for (Entry<RouteUnit, SQLRewriteResult> entry : rewrite.entrySet()) { // SQLRewriteResult -> SQLUnit SQLUnit sqlUnit = new SQLUnit(entry.getValue().getSql(), entry.getValue().getParameters()); // DataSourceName + sqlUnit -> ExecutionUnit ExecutionUnit executionUnit = new ExecutionUnit(entry.getKey().getDataSourceMapper().getActualName(), sqlUnit); result.add(executionUnit); } return result; }Copy the code

Let’s look at what SQLRewriteResult is.

@RequiredArgsConstructor
@Getter
public final class SQLRewriteResult {
    
    private final String sql;
    
    private final List<Object> parameters;
}
Copy the code

SQLRewriteResult is the product of SQL rewriting. SQL property is the SQL statement with placeholder after rewriting, and parameters property is the list of parameters. With SQLRewriteResult, you can actually execute SQL, but the Sharding-JDBC code has a very clear hierarchy and is assembled into the ExecutionUnit into the SQL execution engine. SQLRewriteResult has two properties that are exactly the same as SQLUnit, and BasePrepareEngine#rewrite makes it easy to assemble ExecutionUnit later.

SQLRouteRewriteEngine

As you can see from the rewrite method of SQLRouteRewriteEngine below, SQL rewriting is done for each RouteUnit. A RouteUnit corresponds to a dataSource and n tables, which correspond to an SQL.

public final class SQLRouteRewriteEngine { public Map<RouteUnit, SQLRewriteResult> rewrite(final SQLRewriteContext sqlRewriteContext, final RouteResult routeResult) { Map<RouteUnit, SQLRewriteResult> result = new LinkedHashMap<>(routeResult.getRouteUnits().size(), 1); for (RouteUnit each : RouteResult. GetRouteUnits ()) {/ / rewrite the SQL String SQL = new RouteSQLBuilder (sqlRewriteContext, each). ToSQL (); / / assemble the new params List < Object > parameters = getParameters (sqlRewriteContext. GetParameterBuilder (), routeResult, each). result.put(each, new SQLRewriteResult(sql, parameters)); } return result; }}Copy the code

Rewrite the SQL

First, the RouteSQLBuilder subclass AbstractSQLBuilder toSQL method is used to rewrite THE SQL. For ordinary SQL, the logical table name is replaced by the actual table name. For select * from T_order where user_id = 1, the order of concatenation is shown in the following code.

public abstract class AbstractSQLBuilder implements SQLBuilder { private final SQLRewriteContext context; @override public final String toSQL() {Override public final String toSQL() { If (context.getsqltokens ().isempty ()) {return context.getsQL (); } Collections.sort(context.getSqlTokens()); StringBuilder result = new StringBuilder(); // 1. select * from result.append(context.getSql().substring(0, context.getSqlTokens().get(0).getStartIndex())); for (SQLToken each : Context.getsqltokens ()) {// 2. Select * from t_order_0 SQLToken result.append(getSQLTokenText(each)); Select * from t_order_0 where user_id = 1 result.append(getConjunctionText(each)); } return result.toString(); }}Copy the code

The getSQLTokenText method is implemented by subclasses, and the RouteSQLBuilder is implemented by calling SQLToken’s toString method.

public final class RouteSQLBuilder extends AbstractSQLBuilder {
    
    private final RouteUnit routeUnit;
    
    public RouteSQLBuilder(final SQLRewriteContext context, final RouteUnit routeUnit) {
        super(context);
        this.routeUnit = routeUnit;
    }
    
    @Override
    protected String getSQLTokenText(final SQLToken sqlToken) {
        if (sqlToken instanceof RouteUnitAware) {
            return ((RouteUnitAware) sqlToken).toString(routeUnit);
        }
        return sqlToken.toString();
    }
}
Copy the code

Assembly parameter list

The SQLRouteRewriteEngine#getParameters method calls the getParameters method of ParameterBuilder to assemble and return the previously existing overridden parameters in ParameterBuilder. For a normal StandardParameterBuilder, take the rewritten params parameter list directly from it; For a complex GroupedParameterBuilder, you need to loop through the actual Datanodes and assemble the parameter list.

public final class SQLRouteRewriteEngine { private List<Object> getParameters(final ParameterBuilder parameterBuilder, Final RouteResult RouteResult, Final RouteUnit RouteUnit) {// For normal StandardParameterBuilder, Obtained directly from its member variable parameter list if (parameterBuilder instanceof StandardParameterBuilder | | RouteResult. GetOriginalDataNodes (). The isEmpty () | | parameterBuilder. GetParameters (). The isEmpty ()) {/ / from the builder before rewriting params return parameterBuilder.getParameters(); List<Object> result = new LinkedList<>(); int count = 0; for (Collection<DataNode> each : RouteResult. GetOriginalDataNodes ()) {/ / find matching with actual DataNode RouteUnit routing results if (isInSameDataNode (each, RouteUnit) {// Add parameters from GroupedParameterBuilder to result.addAll(((GroupedParameterBuilder)) parameterBuilder).getParameters(count)); } count++; } return result; }}Copy the code

StandardParameterBuilder assembles the previously cached override parameter information back through the getParameters method.

Public final class StandardParameterBuilder implements ParameterBuilder {// Private final List<Object> originalParameters; Private final Map<Integer, Collection<Object>> addedIndexAndParameters = new TreeMap<>(); / / the parameters of the index - need to be replaced private final Map < Integer, Object > replacedIndexAndParameters = new LinkedHashMap < > (); Private Final List<Integer> removeIndexAndParameters = new ArrayList<>(); @Override public List<Object> getParameters() { List<Object> result = new LinkedList<>(originalParameters); For (Entry<Integer, Object> Entry: replacedIndexAndParameters.entrySet()) { result.set(entry.getKey(), entry.getValue()); } // Add subscript values for (Entry<Integer, Collection<Object>> Entry: ((TreeMap<Integer, Collection<Object>>) addedIndexAndParameters).descendingMap().entrySet()) { if (entry.getKey() > result.size()) { result.addAll(entry.getValue()); } else { result.addAll(entry.getKey(), entry.getValue()); }} for (int index: removeIndexAndParameters) {result.remove(index); } return result; }}Copy the code

conclusion

  • BasePrepareEngine#executeRewrite is the main process entry for SQL rewrite.
  • SQLRewriteEntry# createSQLRewriteContext create SQL to rewrite the context, the implementation of all SQLRewriteContextDecorator rewrite the parameter list in ParameterBuilder, Create the SQLTokenGenerator collection and perform SQLToken generation.
  • Sqlrouterewriteengineer #rewrite (AbstractSQLBuilder#toSQL); SQLToken (ParameterBuilder#getParameters);