ShardingSphere 语句解析生成初探
简介
在上篇文章中,我们找到了一个逻辑SQL转换到真实SQL的关键路径代码,本篇文中,我们就上篇基础上,来探索语句解析生成的一些细节
源码解析
语句的关键解析生成的代码如下:
@RequiredArgsConstructor
public abstract class AbstractSQLBuilder implements SQLBuilder {private final SQLRewriteContext context;@Overridepublic final String toSQL() {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();}
}
从toSQL函数可以看到,基本都死用context变量里面去获取生成真实SQL的,其内容大致如下:
其他的TableToken比较丰富,包含了很多的信息,可能是逻辑表名需要转成真实表名,所有需要这么多的信息
我们看到context.getSqlTokens()的长度是2,那result的就会经过三次添加:
- 未循环遍历处理前的初始添加:result.append(context.getSql(), 0, context.getSqlTokens().get(0).getStartIndex());
- 第一次循环添加: TableToken
- 第二次循环添加:
- 第三次循环添加
下面我们就仔细跟下这三次添加
初始添加
result.append(context.getSql(), 0, context.getSqlTokens().get(0).getStartIndex());
上面的语句就是从字符串中截取子字符串添加,从原始的SQL截取的,起点是0,结束点是第一个SQLTokens的startIndex
相应的变化如下:
- 原始逻辑SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
- result从空字符串变成了: INSERT INTO
这步感觉对于所有的SQL来说确实是通用的,前面这种语句应该都是不用进行解析变换的(如何获取截取的结束点,这个是SQLToken的生成部分,这次先不看,先跟一下处理流程)
第一次循环添加: TableToken
for (SQLToken each : context.getSqlTokens()) {result.append(each instanceof ComposableSQLToken ? getComposableSQLTokenText((ComposableSQLToken) each) : getSQLTokenText(each));result.append(getConjunctionText(each));}
从上面的语句看出,目前有两种处理方式:
- 组合SQLToken的处理: getComposableSQLTokenText
- 非组合SQLToken的处理: getSQLTokenText
我们跟踪得到直接走的: getSQLTokenText
跟踪进入下面的函数,如果是 RouteUnitAware,还需要进行处理
public final class RouteSQLBuilder extends AbstractSQLBuilder {@Overrideprotected String getSQLTokenText(final SQLToken sqlToken) {if (sqlToken instanceof RouteUnitAware) {return ((RouteUnitAware) sqlToken).toString(routeUnit);}return sqlToken.toString();}
}
看看TableToken是如何ToString的,相关的细节如下
public final class TableToken extends SQLToken implements Substitutable, RouteUnitAware {@Overridepublic String toString(final RouteUnit routeUnit) {// 得到了真实的表名,真实表名从逻辑表到真实表的转换Map中获取的String actualTableName = getLogicAndActualTables(routeUnit).get(tableName.getValue().toLowerCase());// 如果真实表名是null,则获取tableName的值(还能这么写成一句,学到了)// tableName是value值是原始的SQL的 t_orderactualTableName = null == actualTableName ? tableName.getValue().toLowerCase() : actualTableName;return tableName.getQuoteCharacter().wrap(actualTableName);}// 从下面的函数大意就可以看出是构建逻辑表名到真实表的映射转换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());// 为啥还要再加一次,这里没有理解result.putAll(shardingRule.getLogicAndActualTablesFromBindingTable(routeUnit.getDataSourceMapper().getLogicName(), each.getLogicName(), each.getActualName(), tableNames));}return result;}
}
return tableName.getQuoteCharacter().wrap(actualTableName) 核心代码大致如下,额外的加一些东西(关键字保留字段处理之类的?)
@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) {return startDelimiter + value + endDelimiter;}
}
相应的变化如下:
- 原始逻辑SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
- result从空字符串变成了: 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();}
}
大意就是获取开始和结束截取点,算法目前还没领会…,但和SQLToken的关系很大,看后面看SQLToken的时候能不能得到解答
相应的变化如下:
- 原始逻辑SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
- result从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status
第二次循环添加:
我们看看第二次循环添加:GeneratedKeyInsertColumnToken
@Overrideprotected String getSQLTokenText(final SQLToken sqlToken) {if (sqlToken instanceof RouteUnitAware) {return ((RouteUnitAware) sqlToken).toString(routeUnit);}// 直接走的这return sqlToken.toString();}
}public final class extends SQLToken implements Attachable {@Overridepublic String toString() {// 简单粗暴的直接插入一列名return String.format(", %s", column);}
}
从上面的大意看出,就是插入一列名,相应的变化如下:
- 原始逻辑SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
- result从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status, order_id
接着: result.append(getConjunctionText(each)),变成:
- 原始逻辑SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
- result从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status, order_id) VALUES
第三次循环添加:
我们接着看第三次循环添加:ShardingInsertValuesToken
public final class RouteSQLBuilder extends AbstractSQLBuilder {@Overrideprotected String getSQLTokenText(final SQLToken sqlToken) {if (sqlToken instanceof RouteUnitAware) {// 走的这return ((RouteUnitAware) sqlToken).toString(routeUnit);}return sqlToken.toString();}
}public final class ShardingInsertValuesToken extends InsertValuesToken implements RouteUnitAware {public ShardingInsertValuesToken(final int startIndex, final int stopIndex) {super(startIndex, stopIndex);}@Overridepublic String toString(final RouteUnit routeUnit) {StringBuilder result = new StringBuilder();// 这里得到了:(?, ?, ?, ?),appendInsertValue(routeUnit, result);// 然后又变成了:(?, ?, ?, ?)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;}
}
对于这个目前不懂,为啥要经过这个处理,应用场景是啥?
- 原始逻辑SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
- result从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status, order_id) VALUES (?, ?, ?, ?)
接着: result.append(getConjunctionText(each)),变成:
- 原始逻辑SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
- result从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status, order_id) VALUES (?, ?, ?, ?)
到这里就解析生成完毕了
总结
本篇中详细查看了逻辑SQL转真实SQL的处理过程:
- 原始逻辑SQL: INSERT INTO t_order (user_id, address_id, status) VALUES (?, ?, ?)
- result从空字符串变成了: INSERT INTO t_order_0 (user_id, address_id, status, order_id) VALUES (?, ?, ?, ?)
转换中使用了相应的Token类进行对应的处理,组装成真实的SQL
但算法之类的,目前还是不甚了解的,后面找找官方资料,有没有这方面的
下面应该要去看看SQLToken的生成了,通过上面的跟踪发现,真实SQL的生成和SQLToken是息息相关的
ShardingSphere 语句解析生成初探相关推荐
- ShardingSphere LogicSQL 的生成探索
简介 在上两篇文中,我们探索了SQLToken和真实SQL的生成的想关代码,本文继续来探索最开始的一个LogicSQL的生成,补全这一块拼图 源码探索 继续上面两篇的探索: ShardingSpher ...
- Java中如何解析SQL语句、格式化SQL语句、生成SQL语句?
昨天在群里看到有小伙伴问,Java里如何解析SQL语句然后格式化SQL,是否有现成类库可以使用? 之前TJ没有做过这类需求,所以去研究了一下,并找到了一个不过的解决方案,今天推荐给大家,如果您正要做类 ...
- Mybatis动态sql语句的生成
在XMLLanguageDriver的createSQLSource()方法中,可以将已经经过解析的xml节点传入,并且传入相应的参数类型,开始动态sql语句的生成. @Override public ...
- mysql 语句解析_MySQL进阶之语句解析顺序
概述 一条普通的SQL查询语句它在MySQL数据库中是怎么样被解析和执行的呢?下面一起来了解一下,MySQL是如何解析SQL查询语句的,这对理解MySQL的执行计划也大有益处. 解析顺序 下面是一条普 ...
- oracle12测试骤,Oracle中SQL语句解析的步骤
我们都知道在Oracle中每条SQL语句在执行之前都需要经过解析,这里面又分为软解析和硬解析.那么这两种解析有何不同之处呢?它们又分别是如何进行解析呢?Oracle内部解析的步骤又是如何进行的呢?下面 ...
- SAP SD微观研究之销售发票自动生成初探
SAP SD微观研究之销售发票自动生成初探 SAP项目实践中,销售业务里的开票(BILLING)功能,可以根据业务实际设置成自动生成.笔者经历过的项目里,大致有2种不同的实现方式. 一种方式是,将事务 ...
- mxOutlookBarPro中,button是通过for语句动态生成的,如何取得当前单击button的标题呢?...
mxOutlookBarPro中,button是通过for语句动态生成的,如何取得当前单击button的标题呢? VCL组件开发及应用 http://www.delphi2007.net/Delphi ...
- oracle bcp out,SQL Server利用bcp命令把SQL语句结果生成文本文件
这篇文章主要为大家详细介绍了SQL Server利用bcp命令把SQL语句结果生成文本文件,具有一定的参考价值,可以用来参考一下. 感兴趣的小伙伴,下面一起跟随512笔记的小编两巴掌来看看吧!在SQL ...
- mysql offset函数_mysql查询语句解析
原标题:mysql查询语句解析 一.查询基本知识 select 列1,列2,...,列n from 表名 1.条件查询:where ①比较运算符:=.!=.< >.<= .>= ...
最新文章
- SAP SD基础知识之信用范围数据维护
- C#中的Dictionary字典类介绍
- 全面开启线上参会报名!CNCC线上与现场参会者共赴技术盛宴!
- LibSvm python 调试实验
- HTTP权威指南记录 ---- Web服务器
- HTML的相关路径与绝对路径的问题---通过网络搜索整理
- 前端学习(615):变量
- 运行shell脚本时怎么知道jdk路径_Shell写脚本关于ssh执行jar包,需要刷新JDK路径的问题...
- FreeModbus线圈
- Java String、StringBuffer、StringBuilder区别
- 炫炫炫的十六进制编辑器
- 网狐荣耀之微星棋牌搭建ios苹果APP编译
- mysql怎么增加字数_数据库字段如何设置最大字数
- html5 监控,基于 HTML5 的 WebGL 3D 智能楼宇监控系统
- 使用 Fail2ban 防止 ssh 暴力破解攻击
- (转)粒子编辑器Particle designer属性的介绍
- Swift 编程语言教程(官方文档)
- 算法题练习系列之(一):守形数
- Win 98系统启动过程全揭密
- iOS POS之3DES加密
热门文章
- .net web 点击链接在页面指定位置显示DIV的问题
- JavaScript 类型的隐式转换
- Vue+Electron下Vuex的Dispatch没有效果的解决方案
- Spring Boot统一异常处理实践
- Shape Completion using 3D-Encoder-Predictor CNNs and Shape Synthesis 第二部分
- 我从吴恩达课堂演讲中学到的一些建议
- python解释器用什么写的_用 Python 从零开始写一个简单的解释器(3)
- pd生成mysql数据库错误_Err] 1064 - You have an error in your ……pd生成mysql导入失败问题...
- 读书笔记——计算机网络CN
- LeetCode:10.regular-expression-matching(正则式表达)