初始SQL经历了解析和路由之后需要将SQL改写,根据分片规则指定需要对哪个库哪个表执行相应的SQL,即正确性改写:在包含分表的场景中,需要将分表配置中的逻辑表名称改写为路由之后所获取的真实表名称。仅分库则不需要表名称的改写。除此之外,还包括补列和分页信息修正等内容。
改写引擎的整体结构划分如下图所示:
上图中的整体结构能够清晰的看到SQL改写的类型,下面即从源码中分析各种改写的原理。
public final class SQLRewriteEngine {
//分片规则
private final ShardingRule shardingRule;
//初始SQL
private final String originalSQL;
//数据库类型
private final DatabaseType databaseType;
//SQL描述
private final SQLStatement sqlStatement;
//SQL的关键字Token
private final List<SQLToken> sqlTokens;
//分片条件
private final ShardingConditions shardingConditions;
//参数
private final List<Object> parameters;
/**
* Constructs SQL rewrite engine.
*
* @param shardingRule databases and tables sharding rule
* @param originalSQL original SQL
* @param databaseType database type
* @param sqlStatement SQL statement
* @param shardingConditions sharding conditions
* @param parameters parameters
*/
public SQLRewriteEngine(final ShardingRule shardingRule, final String originalSQL, final DatabaseType databaseType,
final SQLStatement sqlStatement, final ShardingConditions shardingConditions, final List<Object> parameters) {
this.shardingRule = shardingRule;
this.originalSQL = originalSQL;
this.databaseType = databaseType;
this.sqlStatement = sqlStatement;
sqlTokens = sqlStatement.getSQLTokens();
this.shardingConditions = shardingConditions;
this.parameters = parameters;
}
/**
* rewrite SQL.
*
* @param isSingleRouting is rewrite
* @return SQL builder
*/
public SQLBuilder rewrite(final boolean isSingleRouting) {
SQLBuilder result = new SQLBuilder(parameters);
//如果初始SQL中没有token则返回原生的SQL
if (sqlTokens.isEmpty()) {
//单节点优化
return appendOriginalLiterals(result);
}
//isSingleRouting通过路由引擎执行后路由的路径是否唯一
//拼接非token的sql
appendInitialLiterals(!isSingleRouting, result);
//拼接(重写)token和占位符
appendTokensAndPlaceholders(!isSingleRouting, result);
return result;
}
private SQLBuilder appendOriginalLiterals(final SQLBuilder sqlBuilder) {
sqlBuilder.appendLiterals(originalSQL);
return sqlBuilder;
}
private void appendInitialLiterals(final boolean isRewrite, final SQLBuilder sqlBuilder) {
if (isRewrite && isContainsAggregationDistinctToken()) {
//拼接Distinct聚合函数的关键字
appendAggregationDistinctLiteral(sqlBuilder);
} else {
//拼接第一个token之前的原生SQL
sqlBuilder.appendLiterals(originalSQL.substring(0, sqlTokens.get(0).getBeginPosition()));
}
}
//判断原生SQL中是否包含Distinct关键字
private boolean isContainsAggregationDistinctToken() {
return Iterators.tryFind(sqlTokens.iterator(), new Predicate<SQLToken>() {
@Override
public boolean apply(final SQLToken input) {
return input instanceof AggregationDistinctToken;
}
}).isPresent();
}
private void appendAggregationDistinctLiteral(final SQLBuilder sqlBuilder) {
//定位Distinct关键字的坐标位置
int firstSelectItemStartPosition = ((SelectStatement) sqlStatement).getFirstSelectItemStartPosition();
//拼接关键字之前的SQL
sqlBuilder.appendLiterals(originalSQL.substring(0, firstSelectItemStartPosition));
//拼接执行时需要的关键字DISTINCT
sqlBuilder.appendLiterals("DISTINCT ");
//拼接DISTINCT到第一个token之间的SQL
sqlBuilder.appendLiterals(originalSQL.substring(firstSelectItemStartPosition, sqlTokens.get(0).getBeginPosition()));
}
//重写token和占位符
private void appendTokensAndPlaceholders(final boolean isRewrite, final SQLBuilder sqlBuilder) {
int count = 0;
for (SQLToken each : sqlTokens) {
if (each instanceof TableToken) {
// 表名改写
appendTablePlaceholder(sqlBuilder, (TableToken) each, count);
} else if (each instanceof SchemaToken) {
//schema改写
appendSchemaPlaceholder(sqlBuilder, (SchemaToken) each, count);
} else if (each instanceof IndexToken) {
//索引改写
appendIndexPlaceholder(sqlBuilder, (IndexToken) each, count);
} else if (each instanceof ItemsToken) {
//列改写
appendItemsToken(sqlBuilder, (ItemsToken) each, count, isRewrite);
} else if (each instanceof InsertValuesToken) {
//批量插入拆分改写
appendInsertValuesToken(sqlBuilder, (InsertValuesToken) each, count);
} else if (each instanceof RowCountToken) {
//分页大写修正
appendLimitRowCount(sqlBuilder, (RowCountToken) each, count, isRewrite);
} else if (each instanceof OffsetToken) {
//分页偏移量修正
appendLimitOffsetToken(sqlBuilder, (OffsetToken) each, count, isRewrite);
} else if (each instanceof OrderByToken) {
//排序改写
appendOrderByToken(sqlBuilder, count, isRewrite);
} else if (each instanceof InsertColumnToken) {
//插入列名改写
appendSymbolToken(sqlBuilder, (InsertColumnToken) each, count);
} else if (each instanceof AggregationDistinctToken) {
//Distinct占位符改写
appendAggregationDistinctPlaceholder(sqlBuilder, (AggregationDistinctToken) each, count, isRewrite);
} else if (each instanceof RemoveToken) {
appendRest(sqlBuilder, count, ((RemoveToken) each).getEndPosition());
}
count++;
}
}
private void appendTablePlaceholder(final SQLBuilder sqlBuilder, final TableToken tableToken, final int count) {
// 拼接重写表的占位符
sqlBuilder.appendPlaceholder(new TablePlaceholder(tableToken.getTableName().toLowerCase(), tableToken.getOriginalLiterals()));
// 表名在SQL中的起始位置
int beginPosition = tableToken.getBeginPosition() + tableToken.getSkippedSchemaNameLength() + tableToken.getOriginalLiterals().length();
//拼接表名
appendRest(sqlBuilder, count, beginPosition);
}
private void appendSchemaPlaceholder(final SQLBuilder sqlBuilder, final SchemaToken schemaToken, final int count) {
//拼接schema占位符
sqlBuilder.appendPlaceholder(new SchemaPlaceholder(schemaToken.getSchemaName().toLowerCase(), schemaToken.getTableName().toLowerCase()));
int beginPosition = schemaToken.getBeginPosition() + schemaToken.getOriginalLiterals().length();
appendRest(sqlBuilder, count, beginPosition);
}
private void appendIndexPlaceholder(final SQLBuilder sqlBuilder, final IndexToken indexToken, final int count) {
String indexName = indexToken.getIndexName().toLowerCase();
String logicTableName = indexToken.getTableName().toLowerCase();
if (Strings.isNullOrEmpty(logicTableName)) {
logicTableName = shardingRule.getLogicTableName(indexName);
}
sqlBuilder.appendPlaceholder(new IndexPlaceholder(indexName, logicTableName));
int beginPosition = indexToken.getBeginPosition() + indexToken.getOriginalLiterals().length();
appendRest(sqlBuilder, count, beginPosition);
}
private void appendItemsToken(final SQLBuilder sqlBuilder, final ItemsToken itemsToken, final int count, final boolean isRewrite) {
boolean isRewriteItem = isRewrite || sqlStatement instanceof InsertStatement;
for (int i = 0; i < itemsToken.getItems().size() && isRewriteItem; i++) {
if (itemsToken.isFirstOfItemsSpecial() && 0 == i) {
sqlBuilder.appendLiterals(SQLUtil.getOriginalValue(itemsToken.getItems().get(i), databaseType));
} else {
sqlBuilder.appendLiterals(", ");
sqlBuilder.appendLiterals(SQLUtil.getOriginalValue(itemsToken.getItems().get(i), databaseType));
}
}
appendRest(sqlBuilder, count, itemsToken.getBeginPosition());
}
private void appendInsertValuesToken(final SQLBuilder sqlBuilder, final InsertValuesToken insertValuesToken, final int count) {
sqlBuilder.appendPlaceholder(new InsertValuesPlaceholder(insertValuesToken.getTableName().toLowerCase(), shardingConditions));
appendRest(sqlBuilder, count, ((InsertStatement) sqlStatement).getInsertValuesListLastPosition());
}
private void appendLimitRowCount(final SQLBuilder sqlBuilder, final RowCountToken rowCountToken, final int count, final boolean isRewrite) {
SelectStatement selectStatement = (SelectStatement) sqlStatement;
Limit limit = selectStatement.getLimit();
if (!isRewrite) {
sqlBuilder.appendLiterals(String.valueOf(rowCountToken.getRowCount()));
} else if ((!selectStatement.getGroupByItems().isEmpty() || !selectStatement.getAggregationSelectItems().isEmpty()) && !selectStatement.isSameGroupByAndOrderByItems()) {
sqlBuilder.appendLiterals(String.valueOf(Integer.MAX_VALUE));
} else {
sqlBuilder.appendLiterals(String.valueOf(limit.isNeedRewriteRowCount(databaseType) ? rowCountToken.getRowCount() + limit.getOffsetValue() : rowCountToken.getRowCount()));
}
int beginPosition = rowCountToken.getBeginPosition() + String.valueOf(rowCountToken.getRowCount()).length();
appendRest(sqlBuilder, count, beginPosition);
}
private void appendLimitOffsetToken(final SQLBuilder sqlBuilder, final OffsetToken offsetToken, final int count, final boolean isRewrite) {
sqlBuilder.appendLiterals(isRewrite ? "0" : String.valueOf(offsetToken.getOffset()));
int beginPosition = offsetToken.getBeginPosition() + String.valueOf(offsetToken.getOffset()).length();
appendRest(sqlBuilder, count, beginPosition);
}
//排序语句的重写
private void appendOrderByToken(final SQLBuilder sqlBuilder, final int count, final boolean isRewrite) {
SelectStatement selectStatement = (SelectStatement) sqlStatement;
if (isRewrite) {
StringBuilder orderByLiterals = new StringBuilder();
//拼接order by关键字
orderByLiterals.append(" ").append(DefaultKeyword.ORDER).append(" ").append(DefaultKeyword.BY).append(" ");
int i = 0;
//拼接排序的字段
for (OrderItem each : selectStatement.getOrderByItems()) {
String columnLabel = Strings.isNullOrEmpty(each.getColumnLabel()) ? String.valueOf(each.getIndex())
: SQLUtil.getOriginalValue(each.getColumnLabel(), databaseType);
if (0 == i) {
orderByLiterals.append(columnLabel).append(" ").append(each.getOrderDirection().name());
} else {
orderByLiterals.append(",").append(columnLabel).append(" ").append(each.getOrderDirection().name());
}
i++;
}
orderByLiterals.append(" ");
sqlBuilder.appendLiterals(orderByLiterals.toString());
}
//确定分组语句关键字的位置
int beginPosition = selectStatement.getGroupByLastPosition();
appendRest(sqlBuilder, count, beginPosition);
}
private void appendSymbolToken(final SQLBuilder sqlBuilder, final InsertColumnToken insertColumnToken, final int count) {
sqlBuilder.appendLiterals(insertColumnToken.getColumnName());
appendRest(sqlBuilder, count, insertColumnToken.getBeginPosition());
}
private void appendAggregationDistinctPlaceholder(final SQLBuilder sqlBuilder, final AggregationDistinctToken distinctToken, final int count, final boolean isRewrite) {
if (!isRewrite) {
sqlBuilder.appendLiterals(distinctToken.getOriginalLiterals());
} else {
sqlBuilder.appendPlaceholder(new AggregationDistinctPlaceholder(distinctToken.getColumnName().toLowerCase(), null, distinctToken.getAlias()));
}
appendRest(sqlBuilder, count, distinctToken.getBeginPosition() + distinctToken.getOriginalLiterals().length());
}
private void appendRest(final SQLBuilder sqlBuilder, final int count, final int beginPosition) {
int endPosition = sqlTokens.size() - 1 == count ? originalSQL.length() : sqlTokens.get(count + 1).getBeginPosition();
sqlBuilder.appendLiterals(originalSQL.substring(beginPosition, endPosition));
}
/**
* 生成SQL字符串
*
* @param tableUnit route table unit
* @param sqlBuilder SQL builder
* @param shardingDataSourceMetaData sharding data source meta data
* @return SQL unit
*/
public SQLUnit generateSQL(final TableUnit tableUnit, final SQLBuilder sqlBuilder, final ShardingDataSourceMetaData shardingDataSourceMetaData) {
//sqlBuilder中实现SQL转换为SQLUnit ,逻辑相对简单就不做详细说明
return sqlBuilder.toSQL(tableUnit, getTableTokens(tableUnit), shardingRule, shardingDataSourceMetaData);
}
//获取表的token
private Map<String, String> getTableTokens(final TableUnit tableUnit) {
Map<String, String> result = new HashMap<>();
for (RoutingTable each : tableUnit.getRoutingTables()) {
String logicTableName = each.getLogicTableName().toLowerCase();
result.put(logicTableName, each.getActualTableName());
Optional<BindingTableRule> bindingTableRule = shardingRule.findBindingTableRule(logicTableName);
if (bindingTableRule.isPresent()) {
result.putAll(getBindingTableTokens(tableUnit.getDataSourceName(), each, bindingTableRule.get()));
}
}
return result;
}
//获取绑定表的token
private Map<String, String> getBindingTableTokens(final String dataSourceName, final RoutingTable routingTable, final BindingTableRule bindingTableRule) {
Map<String, String> result = new HashMap<>();
for (String each : sqlStatement.getTables().getTableNames()) {
String tableName = each.toLowerCase();
if (!tableName.equals(routingTable.getLogicTableName().toLowerCase()) && bindingTableRule.hasLogicTable(tableName)) {
result.put(tableName, bindingTableRule.getBindingActualTable(dataSourceName, tableName, routingTable.getActualTableName()));
}
}
return result;
}
}
流式归并优化
它仅为包含GROUP BY的SQL增加ORDER BY以及和分组项相同的排序项和排序顺序,用于将内存归并转化为流式归并。 在结果归并的部分中,将对流式归并和内存归并进行详细说明。
改写后的SQL当然是需要执行的,在sharding-jdbc之执行引擎中对sql执行原理进行分析。