ShardingSphere rewrite the source code parsing engine (a)

In the final article describes the Routing Engine "to resolve the ShardingSphere Source Routing Engine (g)" we saw another important concept in ShardingSphere shard method BaseShardingEngine in that SQL rewrite (Rewrite). SQL rewrite located after routing, is also an important aspect of the entire SQL execution process in the framework of sub-library sub-table is usually because developers are facing logic and logic library table writing SQL, is not able to perform in a real database directly , SQL rewrite the logic for SQL rewrite the SQL database can be performed in real right.

Let us look at rewriteAndConvert BaseShardingEngine method for performing rewriting logic:

       private Collection<RouteUnit> rewriteAndConvert(final String sql, final List<Object> parameters, final SQLRouteResult sqlRouteResult) {

        // build SQLRewriteContext

    SQLRewriteContext sqlRewriteContext = new SQLRewriteContext(metaData.getRelationMetas(), sqlRouteResult.getSqlStatementContext(), sql, parameters);

    // build ShardingSQLRewriteContextDecorator to SQLRewriteContext decorated

    new ShardingSQLRewriteContextDecorator(shardingRule, sqlRouteResult).decorate(sqlRewriteContext);

    // determine whether the data desensitization column

    boolean isQueryWithCipherColumn = shardingProperties.<Boolean>getValue(ShardingPropertiesConstant.QUERY_WITH_CIPHER_COLUMN);

        // build EncryptSQLRewriteContextDecorator to SQLRewriteContext decorated

    new EncryptSQLRewriteContextDecorator(shardingRule.getEncryptRule(), isQueryWithCipherColumn).decorate(sqlRewriteContext);

      // generate SQLTokens

    sqlRewriteContext.generateSQLTokens();   

        Collection<RouteUnit> result = new LinkedHashSet<>();

        for (RoutingUnit each : sqlRouteResult.getRoutingResult().getRoutingUnits()) {

        // build ShardingSQLRewriteEngine

            ShardingSQLRewriteEngine sqlRewriteEngine = new ShardingSQLRewriteEngine(shardingRule, sqlRouteResult.getShardingConditions(), each);

            // execute rewrite

            SQLRewriteResult sqlRewriteResult = sqlRewriteEngine.rewrite(sqlRewriteContext);

            // save rewrite results

            result.add(new RouteUnit(each.getDataSourceName(), new SQLUnit(sqlRewriteResult.getSql(), sqlRewriteResult.getParameters())));

        }

        return result;

    }

Although this code is not much content, but they do achieve a complete description of the SQL rewrite the whole process. There involves a lot of core classes, we spent a few articles worth its detailed expansion. Previously, we give an overall configuration of related core classes, as shown below:

For the chart, we found rewrite engine, SQLRewriteContext is a very important class, SQLRewriteEngine, SQLRewriteContextDecorator core interfaces that depend on it. From the naming speaking, SQLRewriteContext is a context object, it is conceivable certainly holds a lot of information related to SQL data rewrite, let's look at its variables are defined as follows:   

    private final RelationMetas relationMetas;   

    private final SQLStatementContext sqlStatementContext;   

    private final String sql;   

    private final List<Object> parameters;   

    private final List<SQLToken> sqlTokens = new LinkedList<>();   

    private final ParameterBuilder parameterBuilder;   

    @Getter(AccessLevel.NONE)

    private final SQLTokenGenerators sqlTokenGenerators = new SQLTokenGenerators();

Here, we see already introduced the SQLStatementContext, also saw new SQLToken and SQLTokenGenerators. With the evolution of content, these objects are introduced one by one. Here, we first clear SQLRewriteContext save the information for SQL rewrite, but the build process this information will vary according to different application scenarios.

Let's look SQLToken objects here, the object high in importance rewrite engine, SQLRewriteEngine is based SQLToken implements SQL rewrite . SQLToken defined as follows:

@RequiredArgsConstructor

@Getter

public abstract class SQLToken implements Comparable<SQLToken> {   

    private final int start index ;   

    @Override

    public final int compareTo(final SQLToken sqlToken) {

        return startIndex - sqlToken.getStartIndex();

    }

}

SQLToken actually be seen is an abstract class, in ShardingSphere, there are a large number of SQLToken subclass, which tier structure is shown below:

Most of these SQLToken rewrite related (package name contains a rewrite) with SQL, and some on the basis of rewriting and later on also want to address the data related to desensitization function (also contains encrypt the package name), the data provided desensitization is ShardingSphere is a very useful feature, we have introduced the topic later. At the same time, some here SQLToken located shardingsphere-rewrite-engine project, while others are located sharding-core-rewrite project, this point also need attention.

SQL rewrite combination of common scenes, some SQLToken the meaning of the above figure we can be understood from the literal meaning.

例如,对于INSERT语句而言,如果使用数据库自增主键,是无需写入主键字段的。但数据库的自增主键是无法满足分布式场景下的主键唯一性,因此ShardingSphere提供了分布式自增主键的生成策略,并且可以通过补列,让使用方无需改动现有代码,即可将分布式自增主键透明的替换数据库现有的自增主键。关于ShardingSphere中的分布式主键的介绍可以回顾《ShardingSphere源码解析之分布式主键》中的内容。举例说明,假设表t_order的主键是order_id,原始的SQL为:

INSERT INTO t_order (`field1`, `field2`) VALUES (10, 1);

可以看到,上述SQL中并未包含自增主键,需要数据库自行填充。ShardingSphere配置自增主键后,SQL将改写为:

INSERT INTO t_order (`field1`, `field2`, order_id) VALUES (10, 1, xxxxx);

改写后的SQL将在INSERT语句的最后部分增加主键列名称以及自动生成的自增主键值。上述SQL中的xxxxx表示自动生成的自增主键值。

从命名上看,GeneratedKeyInsertColumnToken对应上述的自动主键填充的场景,这实际上属于常见的一种SQL改写策略,即补列。GeneratedKeyInsertColumnToken的实现如下所示:

public final class GeneratedKeyInsertColumnToken extends SQLToken implements Attachable {   

    private final String column;   

    public GeneratedKeyInsertColumnToken(final int startIndex, final String column) {

        super(startIndex);

        this.column = column;

    }   

    @Override

    public String toString() {

        return String.format(", %s", column);

    }

}

可以看到这里多了一个column变量用于指定主键的所在列。我们再来跟踪GeneratedKeyInsertColumnToken的构造函数调用情况,发现在GeneratedKeyInsertColumnTokenGenerator中创建了GeneratedKeyInsertColumnToken。顾名思义,GeneratedKeyInsertColumnTokenGenerator是一种TokenGenerator,专门负责生成具体的Token。TokenGenerator接口定义如下:

public interface SQLTokenGenerator {   

    //判断是否要生成SQLToken

    boolean isGenerateSQLToken(SQLStatementContext sqlStatementContext);

}

该接口还有两个子接口,分别是负责生成单个SQLToken的OptionalSQLTokenGenerator和负责生成批量SQLToken的CollectionSQLTokenGenerator,如下所示:

public interface OptionalSQLTokenGenerator extends SQLTokenGenerator {   

    //生成单个SQLToken

    SQLToken generateSQLToken(SQLStatementContext sqlStatementContext);

}

public interface CollectionSQLTokenGenerator extends SQLTokenGenerator {

    //生成批量SQLToken

    Collection<? extends SQLToken> generateSQLTokens(SQLStatementContext sqlStatementContext);

}

在ShardingSphere,和SQLToken一样,TokenGenerator的类层结构也比较复杂,类层结构如下所示:

对于GeneratedKeyInsertColumnTokenGenerator而言,它还有一个抽象的基类,即上图中的BaseGeneratedKeyTokenGenerator,其实现如下所示:

public abstract class BaseGeneratedKeyTokenGenerator implements OptionalSQLTokenGenerator, SQLRouteResultAware {

    private SQLRouteResult sqlRouteResult;   

    @Override

    public final boolean isGenerateSQLToken(final SQLStatementContext sqlStatementContext) {

        return sqlStatementContext instanceof InsertSQLStatementContext && sqlRouteResult.getGeneratedKey().isPresent()

                && sqlRouteResult.getGeneratedKey().get().isGenerated() && isGenerateSQLToken((InsertStatement) sqlStatementContext.getSqlStatement());

    }   

    protected abstract boolean isGenerateSQLToken(InsertStatement insertStatement);   

    @Override

    public final SQLToken generateSQLToken(final SQLStatementContext sqlStatementContext) {

        Preconditions.checkState(sqlRouteResult.getGeneratedKey().isPresent());

        return generateSQLToken(sqlStatementContext, sqlRouteResult.getGeneratedKey().get());

    }   

    protected abstract SQLToken generateSQLToken(SQLStatementContext sqlStatementContext, GeneratedKey generatedKey);

}

这个抽象类留下了两个模板方法isGenerateSQLToken和generateSQLToken交由子类进行实现,在GeneratedKeyInsertColumnTokenGenerator中,这两个方法的实现如下所示:

public final class GeneratedKeyInsertColumnTokenGenerator extends BaseGeneratedKeyTokenGenerator {   

    @Override

    protected boolean isGenerateSQLToken(final InsertStatement insertStatement) {

        Optional<InsertColumnsSegment> sqlSegment = insertStatement.findSQLSegment(InsertColumnsSegment.class);

        return sqlSegment.isPresent() && !sqlSegment.get().getColumns().isEmpty();

    }   

    @Override

    protected GeneratedKeyInsertColumnToken generateSQLToken(final SQLStatementContext sqlStatementContext, final GeneratedKey generatedKey) {

        Optional<InsertColumnsSegment> sqlSegment = sqlStatementContext.getSqlStatement().findSQLSegment(InsertColumnsSegment.class);

        Preconditions.checkState(sqlSegment.isPresent());

        return new GeneratedKeyInsertColumnToken(sqlSegment.get().getStopIndex(), generatedKey.getColumnName());

    }

}

我们看到在上述generateSQLToken方法中,通过利用在SQL解析引擎中获取的InsertColumnsSegment以及从用于生成分布式主键的GeneratedKey中获取对应的主键列,我们就可以构建一个GeneratedKeyInsertColumnToken。

关于SQLToken的基本概念以及它的其中一个实现类GeneratedKeyInsertColumnToken就介绍到这里,关于其他SQLToken实现类我们不会全部展开,部分会在后面的内容中进行穿插介绍。

作为总结,从今天起,我们正式进入到ShardingSphere中关于改写引擎部分的讲解,本文先从整体结构上给出了改写引擎部分的核心类,然后重点分析了改写引擎的上下文对象SQLRewriteContext中与SQLToken相关的内容。

更多内容可以关注我的公众号:程序员向架构师转型。

发布了104 篇原创文章 · 获赞 10 · 访问量 11万+

Guess you like

Origin blog.csdn.net/lantian08251/article/details/104709420