Analysis and generation of ShardingSphere statements


Introduction to the

In this article, we explore the details of statement parsing generation, building on the previous article where we found the critical path code for converting logical SQL to real SQL

The source code parsing

The key parsing of the statement produces the following code:

@RequiredArgsConstructor
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) {
        return context.getSql().substring(getStartIndex(sqlToken), getStopIndex(sqlToken));
    }
    
    private int getStartIndex(final SQLToken sqlToken) {
        int startIndex = sqlToken instanceof Substitutable ? ((Substitutable) sqlToken).getStopIndex() + 1 : sqlToken.getStartIndex();
        return Math.min(startIndex, context.getSql().length());
    }
    
    private int getStopIndex(final SQLToken sqlToken) {
        int currentSQLTokenIndex = context.getSqlTokens().indexOf(sqlToken);
        return context.getSqlTokens().size() - 1 == currentSQLTokenIndex ? context.getSql().length() : context.getSqlTokens().get(currentSQLTokenIndex + 1).getStartIndex(); }}Copy the code

ToSQL = context; toSQL = context; toSQL = context;

Other TableTokens are rich and contain a lot of information. Maybe the logical table name needs to be converted into the real table name, so so much information is required

Context. GetSqlTokens () has a length of 2 and result is added three times:

  • Result.append (context.getsQL (), 0, context.getsQLTokens ().get(0).getStartIndex());
  • The first loop adds: TableToken
  • For the second loop add:
  • The third loop is added

Let’s follow these three additions carefully

The initial add

result.append(context.getSql(), 0, context.getSqlTokens().get(0).getStartIndex());
Copy the code

SQL > insert substring into SQLTokens; insert substring into SQLTokens; insert substring into SQLTokens

The corresponding changes are as follows:

  • SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (? ,? ,?)
  • Result changes from an empty string to INSERT INTO

This step is true for all SQL statements, such as the preceding statement should not be parsed transformation (how to obtain the end of the intercection point, this is the SQLToken generation part, this will not see, first follow the processing process).

The first loop adds: TableToken

        for (SQLToken each : context.getSqlTokens()) {
            result.append(each instanceof ComposableSQLToken ? getComposableSQLTokenText((ComposableSQLToken) each) : getSQLTokenText(each));
            result.append(getConjunctionText(each));
        }
Copy the code

As you can see from the above statement, there are currently two types of processing:

  • The processing of composite SQLToken: getComposableSQLTokenText
  • Processing of non-combined SQLToken: getSQLTokenText

We trace to get a direct go: getSQLTokenText

The trace goes to the following function, which, if RouteUnitAware, needs to be processed

public final class RouteSQLBuilder extends AbstractSQLBuilder {
    
    @Override
    protected String getSQLTokenText(final SQLToken sqlToken) {
        if (sqlToken instanceof RouteUnitAware) {
            return ((RouteUnitAware) sqlToken).toString(routeUnit);
        }
        returnsqlToken.toString(); }}Copy the code

How is the TableToken ToString

public final class TableToken extends SQLToken implements Substitutable.RouteUnitAware {
    
    @Override
    public String toString(final RouteUnit routeUnit) {
	// Get the real table name from the logical table to the real table conversion Map
        String actualTableName = getLogicAndActualTables(routeUnit).get(tableName.getValue().toLowerCase());
	// if the real tableName is null, get the value of tableName.
	// tableName is the value of the original SQL t_ORDER
        actualTableName = null == actualTableName ? tableName.getValue().toLowerCase() : actualTableName;
        return tableName.getQuoteCharacter().wrap(actualTableName);
    }
    
    SQL > create table name Map (); // Create table name Map ()
    private Map<String, String> getLogicAndActualTables(final RouteUnit routeUnit) {
        Collection<String> tableNames = sqlStatementContext.getTablesContext().getTableNames();
        Map<String, String> result = new HashMap<>(tableNames.size(), 1);
        for (RouteMapper each : routeUnit.getTableMappers()) {
            result.put(each.getLogicName().toLowerCase(), each.getActualName());
	    // Why do I need to add it again
            result.putAll(shardingRule.getLogicAndActualTablesFromBindingTable(routeUnit.getDataSourceMapper().getLogicName(), each.getLogicName(), each.getActualName(), tableNames));
        }
        returnresult; }}Copy the code

Return tableName. GetQuoteCharacter (). Wrap (actualTableName) core code roughly as follows, additional add something (processing such as key keep field?

@Getter
public enum QuoteCharacter {
    
    BACK_QUOTE("`"."`"),
    
    SINGLE_QUOTE("'"."'"),
    
    QUOTE("\" "."\" "),
    
    BRACKETS("["."]"),
    
    NONE(""."");
    
    private final String startDelimiter;
    
    private final String endDelimiter;
    
    /**
     * Wrap value with quote character.
     * 
     * @param value value to be wrapped
     * @return wrapped value
     */
    public String wrap(final String value) {
        returnstartDelimiter + value + endDelimiter; }}Copy the code

The corresponding changes are as follows:

  • SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (? ,? ,?)
  • Result changes from an empty string to: INSERT INTO T_order_0

Result.append (getConjunctionText(each));

public abstract class AbstractSQLBuilder implements SQLBuilder {
    private String getConjunctionText(final SQLToken sqlToken) {
        return context.getSql().substring(getStartIndex(sqlToken), getStopIndex(sqlToken));
    }
    
    private int getStartIndex(final SQLToken sqlToken) {
        int startIndex = sqlToken instanceof Substitutable ? ((Substitutable) sqlToken).getStopIndex() + 1 : sqlToken.getStartIndex();
        return Math.min(startIndex, context.getSql().length());
    }
    
    private int getStopIndex(final SQLToken sqlToken) {
        int currentSQLTokenIndex = context.getSqlTokens().indexOf(sqlToken);
        return context.getSqlTokens().size() - 1 == currentSQLTokenIndex ? context.getSql().length() : context.getSqlTokens().get(currentSQLTokenIndex + 1).getStartIndex(); }}Copy the code

Get start and end interception points. The algorithm does not yet understand…… SQLToken, SQLToken, SQLToken, SQLToken, SQLToken

The corresponding changes are as follows:

  • SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (? ,? ,?)
  • Result changed from an empty string to: INSERT INTO T_order_0 (user_id, address_id, status)

For the second loop add:

Let’s take a look at the second loop to add: GeneratedKeyInsertColumnToken

@Override
    protected String getSQLTokenText(final SQLToken sqlToken) {
        if (sqlToken instanceof RouteUnitAware) {
            return ((RouteUnitAware) sqlToken).toString(routeUnit);
        }
	// Go straight here
        returnsqlToken.toString(); }}public final class  extends SQLToken implements Attachable {
    
    @Override
    public String toString(a) {
	// Simply insert a column name
        return String.format(", %s", column); }}Copy the code

Insert a column of names as follows:

  • SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (? ,? ,?)
  • Result changes from empty string to: INSERT INTO T_order_0 (user_id, address_id, status, order_id)

Then: result.append(getConjunctionText(each)), becomes:

  • SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (? ,? ,?)
  • INSERT INTO T_order_0 (user_id, address_id, status, order_id) VALUES

For the third loop add:

We then look at the third cycle add: ShardingInsertValuesToken

public final class RouteSQLBuilder extends AbstractSQLBuilder {
    
    @Override
    protected String getSQLTokenText(final SQLToken sqlToken) {
        if (sqlToken instanceof RouteUnitAware) {
	    / / this
            return ((RouteUnitAware) sqlToken).toString(routeUnit);
        }
        returnsqlToken.toString(); }}public final class ShardingInsertValuesToken extends InsertValuesToken implements RouteUnitAware {
    
    public ShardingInsertValuesToken(final int startIndex, final int stopIndex) {
        super(startIndex, stopIndex);
    }
    
    @Override
    public String toString(final RouteUnit routeUnit) {
        StringBuilder result = new StringBuilder();
	// get :(? ,? ,? ,?) .
        appendInsertValue(routeUnit, result);
	// then it becomes :(? ,? ,? ,?)
        result.delete(result.length() - 2, result.length());
        return result.toString();
    }
    
    private void appendInsertValue(final RouteUnit routeUnit, final StringBuilder stringBuilder) {
        for (InsertValue each : getInsertValues()) {
            if (isAppend(routeUnit, (ShardingInsertValue) each)) {
                stringBuilder.append(each).append(","); }}}private boolean isAppend(final RouteUnit routeUnit, final ShardingInsertValue insertValueToken) {
        if (insertValueToken.getDataNodes().isEmpty() || null == routeUnit) {
            return true;
        }
        for (DataNode each : insertValueToken.getDataNodes()) {
            if (routeUnit.findTableMapper(each.getDataSourceName(), each.getTableName()).isPresent()) {
                return true; }}return false; }}Copy the code

I don’t understand this at present. Why should I go through this process? What is the application scenario?

  • SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (? ,? ,?)
  • INSERT INTO t_order_0 (user_id, address_id, status, order_id) VALUES (? ,? ,? ,?)

Then: result.append(getConjunctionText(each)), becomes:

  • SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (? ,? ,?)
  • INSERT INTO t_order_0 (user_id, address_id, status, order_id) VALUES (? ,? ,? ,?)

This is where the parsing is done

conclusion

This article looks at the processing of logical SQL into real SQL in detail:

  • SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (? ,? ,?)
  • INSERT INTO t_order_0 (user_id, address_id, status, order_id) VALUES (? ,? ,? ,?)

The corresponding Token class is used in the conversion for corresponding processing, and the real SQL is assembled

But algorithm and so on, still do not know very much at present, seek official data behind, have this respect

SQLToken generation: SQLToken generation: SQLToken generation: SQLToken generation: SQLToken generation