数据库中间件 Sharding-JDBC 源码分析 —— SQL 解析(三)之查询SQL
🙂🙂🙂关注微信公众号:【芋艿的后端小屋】有福利:
RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表 RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。 新的源码解析文章实时收到通知。每周更新一篇左右。 认真的源码交流微信群!
- 1. 概述
- 2. SelectStatement
- 2.1 AbstractSQLStatement
- 2.2 SQLToken
- 3. #query()
- 3.1 #parseDistinct()
- 3.2 #parseSelectList()
- 3.3 #skipToFrom()
- 3.4 #parseFrom()
- 3.5 #parseWhere()
- 3.6 #parseGroupBy()
- 3.7 #parseOrderBy()
- 3.8 #parseLimit()
- 3.9 #queryRest()
- 4. appendDerived等方法
- 4.1 appendAvgDerivedColumns
- 4.2 appendDerivedOrderColumns
- 4.3 ItemsToken
- 4.4 appendDerivedOrderBy()
- 666. 彩蛋
1. 概述
本文前置阅读:
- 《SQL 解析(一)之词法解析》
- 《SQL 解析(二)之SQL解析》
本文分享插入SQL解析的源码实现。
由于每个数据库在遵守 SQL 语法规范的同时,又有各自独特的语法。因此,在 Sharding-JDBC 里每个数据库都有自己的 SELECT 语句的解析器实现方式,当然绝大部分逻辑是相同的。本文主要分享笔者最常用的 MySQL 查询。
查询 SQL 解析主流程如下:
// AbstractSelectParser.java
publicfinalSelectStatement parse(){
query();
parseOrderBy();
customizedSelect();
appendDerivedColumns();
appendDerivedOrderBy();
return selectStatement;
}
#parseOrderBy()
:对于 MySQL 查询语句解析器无效果,因为已经在#query()
方法里面已经调用#parseOrderBy()
,因此图中省略该方法。#customizedSelect()
:Oracle、SQLServer 查询语句解析器重写了该方法,对于 MySQL 查询解析器是个空方法,进行省略。有兴趣的同学可以单独去研究研究。
Sharding-JDBC 正在收集使用公司名单:传送门。🙂 你的登记,会让更多人参与和使用 Sharding-JDBC。传送门Sharding-JDBC 也会因此,能够覆盖更多的业务场景。传送门登记吧,骚年!传送门
👼 查询语句解析是增删改查里面
最灵活也是最复杂的,希望大家有耐心看完本文。理解查询语句解析,另外三种语句理解起来简直是 SO EASY。骗人是小狗🐶。
🙂如果对本文有不理解的地方,可以给我的公众号
(芋艿的后端小屋)留言,我会
逐条认真耐心回复。骗人是小猪🐷。
OK,不废话啦,开始我们这段痛并快乐的旅途。
2. SelectStatement
🙂 本节只介绍这些类,方便本文下节分析源码实现大家能知道认识它们 🙂
SelectStatement,查询语句解析结果对象。
// SelectStatement.java
publicfinalclassSelectStatementextendsAbstractSQLStatement{
/**
* 是否行 DISTINCT / DISTINCTROW / UNION
*/
privateboolean distinct;
/**
* 是否查询所有字段,即 SELECT *
*/
privateboolean containStar;
/**
* 最后一个查询项下一个 Token 的开始位置
*
* @see #items
*/
privateint selectListLastPosition;
/**
* 最后一个分组项下一个 Token 的开始位置
*/
privateint groupByLastPosition;
/**
* 查询项
*/
privatefinalList<SelectItem> items =newLinkedList<>();
/**
* 分组项
*/
privatefinalList<OrderItem> groupByItems =newLinkedList<>();
/**
* 排序项
*/
privatefinalList<OrderItem> orderByItems =newLinkedList<>();
/**
* 分页
*/
privateLimit limit;
}
我们对属性按照类型进行归类:
- 特殊
- distinct
- 查询字段
- containStar
- items
- selectListLastPosition
- 分组条件
- groupByItems
- groupByLastPosition
- 排序条件
- orderByItems
- 分页条件
- limit
2.1 AbstractSQLStatement
增删改查解析结果对象的抽象父类。
publicabstractclassAbstractSQLStatementimplementsSQLStatement{
/**
* SQL 类型
*/
privatefinalSQLType type;
/**
* 表
*/
privatefinalTables tables =newTables();
/**
* 过滤条件。
* 只有对路由结果有影响的条件,才添加进数组
*/
privatefinalConditions conditions =newConditions();
/**
* SQL标记对象
*/
privatefinalList<SQLToken> sqlTokens =newLinkedList<>();
}
2.2 SQLToken
SQLToken,SQL标记对象接口,SQL 改写时使用到。下面都是它的实现类:
类 | 说明 |
---|---|
GeneratedKeyToken | 自增主键标记对象 |
TableToken | 表标记对象 |
ItemsToken | 选择项标记对象 |
OffsetToken | 分页偏移量标记对象 |
OrderByToken | 排序标记对象 |
RowCountToken | 分页长度标记对象 |
3. #query()
#query()
,查询 SQL 解析。MySQL SELECT Syntax:
// https://dev.mysql.com/doc/refman/5.7/en/select.html
SELECT
[ALL | DISTINCT | DISTINCTROW ]
[HIGH_PRIORITY]
[STRAIGHT_JOIN]
[SQL_SMALL_RESULT][SQL_BIG_RESULT][SQL_BUFFER_RESULT]
[SQL_CACHE | SQL_NO_CACHE][SQL_CALC_FOUND_ROWS]
select_expr [, select_expr ...]
[FROM table_references
[PARTITION partition_list]
[WHERE where_condition]
[GROUP BY {col_name | expr | position}
[ASC | DESC],...[WITH ROLLUP]]
[HAVING where_condition]
[ORDER BY {col_name | expr | position}
[ASC | DESC],...]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
[PROCEDURE procedure_name(argument_list)]
[INTO OUTFILE 'file_name'
[CHARACTER SET charset_name]
export_options
| INTO DUMPFILE 'file_name'
| INTO var_name [, var_name]]
[FOR UPDATE | LOCK IN SHARE MODE]]
大体流程如下:
// MySQLSelectParser.java
publicvoid query(){
if(getSqlParser().equalAny(DefaultKeyword.SELECT)){
getSqlParser().getLexer().nextToken();
parseDistinct();
getSqlParser().skipAll(MySQLKeyword.HIGH_PRIORITY,DefaultKeyword.STRAIGHT_JOIN,MySQLKeyword.SQL_SMALL_RESULT,MySQLKeyword.SQL_BIG_RESULT,MySQLKeyword.SQL_BUFFER_RESULT,
MySQLKeyword.SQL_CACHE,MySQLKeyword.SQL_NO_CACHE,MySQLKeyword.SQL_CALC_FOUND_ROWS);
parseSelectList();// 解析 查询字段
skipToFrom();// 跳到 FROM 处
}
parseFrom();// 解析 表(JOIN ON / FROM 单&多表)
parseWhere();// 解析 WHERE 条件
parseGroupBy();// 解析 Group By 和 Having(目前不支持)条件
parseOrderBy();// 解析 Order By 条件
parseLimit();// 解析 分页 Limit 条件
// [PROCEDURE] 暂不支持
if(getSqlParser().equalAny(DefaultKeyword.PROCEDURE)){
thrownewSQLParsingUnsupportedException(getSqlParser().getLexer().getCurrentToken().getType());
}
queryRest();
}
3.1 #parseDistinct()
解析 DISTINCT、DISTINCTROW、UNION 谓语。
核心代码:
// AbstractSelectParser.java
protectedfinalvoid parseDistinct(){
if(sqlParser.equalAny(DefaultKeyword.DISTINCT,DefaultKeyword.DISTINCTROW,DefaultKeyword.UNION)){
selectStatement.setDistinct(true);
sqlParser.getLexer().nextToken();
if(hasDistinctOn()&& sqlParser.equalAny(DefaultKeyword.ON)){// PostgreSQL 独有语法: DISTINCT ON
sqlParser.getLexer().nextToken();
sqlParser.skipParentheses();
}
}elseif(sqlParser.equalAny(DefaultKeyword.ALL)){
sqlParser.getLexer().nextToken();
}
}
此处 DISTINCT 和 DISTINCT(字段) 不同,它是针对查询结果做去重,即整行重复。举个例子:
mysql> SELECT item_id, order_id FROM t_order_item;
+---------+----------+
| item_id | order_id |
+---------+----------+
|1|1|
|1|1|
+---------+----------+
2 rows inset(0.03 sec)
mysql> SELECT DISTINCT item_id, order_id FROM t_order_item;
+---------+----------+
| item_id | order_id |
+---------+----------+
|1|1|
+---------+----------+
1 rows inset(0.02 sec)
3.2 #parseSelectList()
SELECT | o.user_id | COUNT(DISTINCT i.itemid) AS itemcount | MAX(i.item_id) | FROM |
---|---|---|---|---|
SelectItem | SelectItem | SelectItem |
将 SQL 查询字段 按照逗号( , )切割成多个选择项( SelectItem)。核心代码如下:
// AbstractSelectParser.java
protectedfinalvoid parseSelectList(){
do{
// 解析单个选择项
parseSelectItem();
}while(sqlParser.skipIfEqual(Symbol.COMMA));
// 设置 最后一个查询项下一个 Token 的开始位置
selectStatement.setSelectListLastPosition(sqlParser.getLexer().getCurrentToken().getEndPosition()- sqlParser.getLexer().getCurrentToken().getLiterals().length());
}
3.2.1 SelectItem 选择项
SelectItem 接口,属于分片上下文信息,有 2 个实现类:
- CommonSelectItem :通用选择项
- AggregationSelectItem :聚合选择项
解析单个 SelectItem 核心代码:
// AbstractSelectParser.java
privatevoid parseSelectItem(){
// 第四种情况,SQL Server 独有
if(isRowNumberSelectItem()){
selectStatement.getItems().add(parseRowNumberSelectItem());
return;
}
sqlParser.skipIfEqual(DefaultKeyword.CONNECT_BY_ROOT);// Oracle 独有:https://docs.oracle.com/cd/B19306_01/server.102/b14200/operators004.htm
String literals = sqlParser.getLexer().getCurrentToken().getLiterals();
// 第一种情况,* 通用选择项,SELECT *
if(sqlParser.equalAny(Symbol.STAR)||Symbol.STAR.getLiterals().equals(SQLUtil.getExactlyValue(literals))){
sqlParser.getLexer().nextToken();
selectStatement.getItems().add(newCommonSelectItem(Symbol.STAR.getLiterals(), sqlParser.parseAlias()));
selectStatement.setContainStar(true);
return;
}
// 第二种情况,聚合选择项
if(sqlParser.skipIfEqual(DefaultKeyword.MAX,DefaultKeyword.MIN,DefaultKeyword.SUM,DefaultKeyword.AVG,DefaultKeyword.COUNT)){
selectStatement.getItems().add(newAggregationSelectItem(AggregationType.valueOf(literals.toUpperCase()), sqlParser.skipParentheses(), sqlParser.parseAlias()));
return;
}
// 第三种情况,非 * 通用选择项
StringBuilder expression =newStringBuilder();
Token lastToken =null;
while(!sqlParser.equalAny(DefaultKeyword.AS)&&!sqlParser.equalAny(Symbol.COMMA)&&!sqlParser.equalAny(DefaultKeyword.FROM)&&!sqlParser.equalAny(Assist.END)){
String value = sqlParser.getLexer().getCurrentToken().getLiterals();
int position = sqlParser.getLexer().getCurrentToken().getEndPosition()- value.length();
expression.append(value);
lastToken = sqlParser.getLexer().getCurrentToken();
sqlParser.getLexer().nextToken();
if(sqlParser.equalAny(Symbol.DOT)){
selectStatement.getSqlTokens().add(newTableToken(position, value));
}
}
// 不带 AS,并且有别名,并且别名不等于自己(tips:这里重点看。判断这么复杂的原因:防止substring操作截取结果错误)
if(null!= lastToken &&Literals.IDENTIFIER == lastToken.getType()
&&!isSQLPropertyExpression(expression, lastToken)// 过滤掉,别名是自己的情况【1】(例如,SELECT u.user_id u.user_id FROM t_user)
&&!expression.toString().equals(lastToken.getLiterals())){// 过滤掉,无别名的情况【2】(例如,SELECT user_id FROM t_user)
selectStatement.getItems().add(
newCommonSelectItem(SQLUtil.getExactlyValue(expression.substring(0, expression.lastIndexOf(lastToken.getLiterals()))),Optional.of(lastToken.getLiterals())));
return;
}
// 带 AS(例如,SELECT user_id AS userId) 或者 无别名(例如,SELECT user_id)
selectStatement.getItems().add(newCommonSelectItem(SQLUtil.getExactlyValue(expression.toString()), sqlParser.parseAlias()));
}
一共分成 4 种大的情况,我们来逐条梳理:
- 第一种:
*
通用选择项:例如,SELECT*FROM t_user
的*
。为什么要加Symbol.STAR.getLiterals().equals(SQLUtil.getExactlyValue(literals))
判断呢?
SELECT `*` FROM t_user;// 也能达到查询所有字段的效果
- 第二种:聚合选择项:例如,
SELECT COUNT(user_id)FROM t_user
的COUNT(user_id)
。
解析结果 AggregationSelectItem:
sqlParser.skipParentheses()
解析见《SQL 解析(二)之SQL解析》的AbstractParser小节。- 第三种:非
*
通用选择项:
例如,
SELECT user_id FROM t_user
。从实现上,逻辑会复杂很多。相比第一种,可以根据
*
做字段判断;相比第二种,可以使用 (
和 )
做字段判断。能够判断一个包含别名的 SelectItem 结束有 4 种 Token,根据结束方式我们分成 2 种:- DefaultKeyword.AS :能够接触出 SelectItem 字段,即不包含别名。例如,
SELECT user_id AS uid FROM t_user
,能够直接解析出user_id
。 - Symbol.COMMA / DefaultKeyword.FROM / Assist.END :包含别名。例如,
SELECT user_id uid FROM t_user
,解析结果为user_id uid
。
基于这个在配合上面的代码注释,大家再重新理解下第三种情况的实现。
- 第四种:SQLServer ROW_NUMBER:
ROW_NUMBER 是 SQLServer 独有的。由于本文大部分的读者使用的 MySQL / Oracle,就跳过了。有兴趣的同学可以看 SQLServerSelectParser#parseRowNumberSelectItem() 方法。
3.2.2 #parseAlias() 解析别名
解析别名,分成是否带
AS
两种情况。解析代码:《SQL 解析(二)之SQL解析》的#parseAlias()小节。3.2.3 TableToken 表标记对象
TableToken,记录表名在 SQL 里出现的位置和名字。
publicfinalclassTableTokenimplementsSQLToken{
/**
* 开始位置
*/
privatefinalint beginPosition;
/**
* 表达式
*/
privatefinalString originalLiterals;
/**
* 获取表名称.
* @return 表名称
*/
publicString getTableName(){
returnSQLUtil.getExactlyValue(originalLiterals);
}
}
例如上文第三种情况。
3.3 #skipToFrom()
/**
* 跳到 FROM 处
*/
privatevoid skipToFrom(){
while(!getSqlParser().equalAny(DefaultKeyword.FROM)&&!getSqlParser().equalAny(Assist.END)){
getSqlParser().getLexer().nextToken();
}
}
3.4 #parseFrom()
解析表以及表连接关系。这块相对比较复杂,请大家耐心+耐心+耐心。
MySQL JOIN Syntax:
// https://dev.mysql.com/doc/refman/5.7/en/join.html
table_references:
escaped_table_reference [, escaped_table_reference]...
escaped_table_reference:
table_reference
|{ OJ table_reference }
table_reference:
table_factor
| join_table
table_factor:
tbl_name [PARTITION (partition_names)]
[[AS]alias][index_hint_list]
| table_subquery [AS]alias
|( table_references )
join_table:
table_reference [INNER | CROSS] JOIN table_factor [join_condition]
| table_reference STRAIGHT_JOIN table_factor
| table_reference STRAIGHT_JOIN table_factor ON conditional_expr
| table_reference {LEFT|RIGHT}[OUTER] JOIN table_reference join_condition
| table_reference NATURAL [{LEFT|RIGHT}[OUTER]] JOIN table_factor
join_condition:
ON conditional_expr
| USING (column_list)
index_hint_list:
index_hint [, index_hint]...
index_hint:
USE {INDEX|KEY}
[FOR {JOIN|ORDER BY|GROUP BY}]([index_list])
| IGNORE {INDEX|KEY}
[FOR {JOIN|ORDER BY|GROUP BY}](index_list)
| FORCE {INDEX|KEY}
[FOR {JOIN|ORDER BY|GROUP BY}](index_list)
index_list:
index_name [, index_name]...
3.4.1 JOIN ON / FROM TABLE
先抛开子查询的情况,只考虑如下两种 SQL 情况。
// JOIN ON : 实际可以继续 JOIN ON 更多表
SELECT * FROM t_order o JOIN t_order_item i ON o.order_id = i.order_id;
// FROM 多表 :实际可以继续 FROM 多更表
SELECT * FROM t_order o, t_order_item i
在看实现代码之前,先一起看下调用顺序图:
看懂上图后,来继续看下实现代码(🙂代码有点多,不要方!):
// AbstractSelectParser.java
/**
* 解析所有表名和表别名
*/
publicfinalvoid parseFrom(){
if(sqlParser.skipIfEqual(DefaultKeyword.FROM)){
parseTable();
}
}
/**
* 解析所有表名和表别名
*/
publicvoid parseTable(){
// 解析子查询
if(sqlParser.skipIfEqual(Symbol.LEFT_PAREN)){
if(!selectStatement.getTables().isEmpty()){
thrownewUnsupportedOperationException("Cannot support subquery for nested tables.");
}
selectStatement.setContainStar(false);
sqlParser.skipUselessParentheses();// 去掉子查询左括号
parse();// 解析子查询 SQL
sqlParser.skipUselessParentheses();// 去掉子查询右括号
//
if(!selectStatement.getTables().isEmpty()){
return;
}
}
parseTableFactor();// 解析当前表
parseJoinTable();// 解析下一个表
}
/**
* 解析单个表名和表别名
*/
protectedfinalvoid parseTableFactor(){
int beginPosition = sqlParser.getLexer().getCurrentToken().getEndPosition()- sqlParser.getLexer().getCurrentToken().getLiterals().length();
String literals = sqlParser.getLexer().getCurrentToken().getLiterals();
sqlParser.getLexer().nextToken();
// TODO 包含Schema解析
if(sqlParser.skipIfEqual(Symbol.DOT)){// https://dev.mysql.com/doc/refman/5.7/en/information-schema.html :SELECT table_name, table_type, engine FROM information_schema.tables
sqlParser.getLexer().nextToken();
sqlParser.parseAlias();
return;
}
// FIXME 根据shardingRule过滤table
selectStatement.getSqlTokens().add(newTableToken(beginPosition, literals));
// 表 以及 表别名
selectStatement.getTables().add(newTable(SQLUtil.getExactlyValue(literals), sqlParser.parseAlias()));
}
/**
* 解析 Join Table 或者 FROM 下一张 Table
*/
protectedvoid parseJoinTable(){
if(sqlParser.skipJoin()){
// 这里调用 parseJoinTable() 而不是 parseTableFactor() :下一个 Table 可能是子查询
// 例如:SELECT * FROM t_order JOIN (SELECT * FROM t_order_item JOIN t_order_other ON ) .....
parseTable();
if(sqlParser.skipIfEqual(DefaultKeyword.ON)){// JOIN 表时 ON 条件
do{
parseTableCondition(sqlParser.getLexer().getCurrentToken().getEndPosition());
sqlParser.accept(Symbol.EQ);
parseTableCondition(sqlParser.getLexer().getCurrentToken().getEndPosition()- sqlParser.getLexer().getCurrentToken().getLiterals().length());
}while(sqlParser.skipIfEqual(DefaultKeyword.AND));
}elseif(sqlParser.skipIfEqual(DefaultKeyword.USING)){// JOIN 表时 USING 为使用两表相同字段相同时对 ON 的简化。例如以下两条 SQL 等价:
// SELECT * FROM t_order o JOIN t_order_item i USING (order_id);
// SELECT * FROM t_order o JOIN t_order_item i ON o.order_id = i.order_id
sqlParser.skipParentheses();
}
parseJoinTable();// 继续递归
}
}
/**
* 解析 ON 条件里的 TableToken
*
* @param startPosition 开始位置
*/
privatevoid parseTableCondition(finalint startPosition){
SQLExpression sqlExpression = sqlParser.parseExpression();
if(!(sqlExpression instanceofSQLPropertyExpression)){
return;
}
SQLPropertyExpression sqlPropertyExpression =(SQLPropertyExpression) sqlExpression;
if(selectStatement.getTables().getTableNames().contains(SQLUtil.getExactlyValue(sqlPropertyExpression.getOwner().getName()))){
selectStatement.getSqlTokens().add(newTableToken(startPosition, sqlPropertyExpression.getOwner().getName()));
}
}
OK,递归因为平时日常中写的比较少,可能理解起来可能会困难一些,努力看懂!🙂如果真的看不懂,可以加微信公众号(芋艿的后端小屋),我来帮你一起理解。
3.4.2 子查询
Sharding-JDBC 目前支持第一个包含多层级的数据子查询。例如:
SELECT o3.* FROM (SELECT * FROM (SELECT * FROM t_order o) o2) o3;
SELECT o3.* FROM (SELECT * FROM (SELECT * FROM t_order o) o2) o3 JOIN t_order_item i ON o3.order_id = i.order_id;
不支持第二个开始包含多层级的数据子查询。例如:
SELECT o3.* FROM t_order_item i JOIN (SELECT * FROM (SELECT * FROM t_order o) o2) o3 ON o3.order_id = i.order_id;// 此条 SQL 是上面第二条 SQL 左右量表颠倒
SELECT COUNT(*) FROM (SELECT * FROM t_order o WHERE o.id IN (SELECT id FROM t_order WHERE status =?))// FROM 官方不支持 SQL 举例
使用第二个开始的子查询会抛出异常,代码如下:
// AbstractSelectParser.java#parseTable()片段
if(!selectStatement.getTables().isEmpty()){
thrownewUnsupportedOperationException("Cannot support subquery for nested tables.");
}
使用子查询,建议认真阅读官方《分页及子查询》文档。
3.4.3 #parseJoinTable()
MySQLSelectParser 重写了
#parseJoinTable()
方法用于解析 USE / IGNORE / FORCE index_hint。具体语法见上文 JOIN Syntax。这里就跳过,有兴趣的同学可以去看看。3.4.4 Tables 表集合对象
属于分片上下文信息
// Tables.java
publicfinalclassTables{
privatefinalList<Table> tables =newArrayList<>();
}
// Table.java
publicfinalclassTable{
/**
* 表
*/
privatefinalString name;
/**
* 别名
*/
privatefinalOptional<String>alias;
}
// AbstractSelectParser.java#parseTableFactor()片段
selectStatement.getTables().add(newTable(SQLUtil.getExactlyValue(literals), sqlParser.parseAlias()));
3.5 #parseWhere()
解析 WHERE 条件。解析代码:《SQL 解析(二)之SQL解析》的#parseWhere()小节。
3.6 #parseGroupBy()
解析分组条件,实现上比较类似
#parseSelectList
,会更加简单一些。// 🙂 抱歉,微信文章篇幅长度限制,该处代码已经“屏蔽”。解决方式:
1. 点击最下方【阅读原文】
2. 访问地址:http://www.yunai.me/Sharding-JDBC/sql-parse-3/?mp
3.6.1 OrderItem 排序项
属于分片上下文信息
publicfinalclassOrderItem{
/**
* 所属表别名
*/
privatefinalOptional<String> owner;
/**
* 排序字段
*/
privatefinalOptional<String> name;
/**
* 排序类型
*/
privatefinalOrderType type;
/**
* 按照第几个查询字段排序
* ORDER BY 数字 的 数字代表的是第几个字段
*/
@Setter
privateint index =-1;
/**
* 字段在查询项({@link com.dangdang.ddframe.rdb.sharding.parsing.parser.context.selectitem.SelectItem} 的别名
*/
@Setter
privateOptional<String>alias;
}
3.7 #parseOrderBy()
解析排序条件。实现逻辑类似
#parseGroupBy()
,这里就跳过,有兴趣的同学可以去看看。3.8 #parseLimit()
解析分页 Limit 条件。相对简单,这里就跳过,有兴趣的同学可以去看看。注意下,分成 3 种情况:
- LIMIT row_count
- LIMIT offset, row_count
- LIMIT row_count OFFSET offset
3.8.1 Limit
分页对象。属于分片上下文信息。
// Limit.java
publicfinalclassLimit{
/**
* 是否重写rowCount
* TODO 待补充:预计和内存分页合并有关
*/
privatefinalboolean rowCountRewriteFlag;
/**
* offset
*/
privateLimitValue offset;
/**
* row
*/
privateLimitValue rowCount;
}
// LimitValue.java
publicfinalclassLimitValue{
/**
* 值
* 当 value == -1 时,为占位符
*/
privateint value;
/**
* 第几个占位符
*/
privateint index;
}
3.8.2 OffsetToken RowCountToken
- OffsetToken:分页偏移量标记对象
- RowCountToken:分页长度标记对象
只有在对应位置非占位符才有该 SQLToken。
// OffsetToken.java
publicfinalclassOffsetTokenimplementsSQLToken{
/**
* SQL 所在开始位置
*/
privatefinalint beginPosition;
/**
* 偏移值
*/
privatefinalint offset;
}
// RowCountToken.java
publicfinalclassRowCountTokenimplementsSQLToken{
/**
* SQL 所在开始位置
*/
privatefinalint beginPosition;
/**
* 行数
*/
privatefinalint rowCount;
}
3.9 #queryRest()
// AbstractSelectParser.java
protectedvoid queryRest(){
if(sqlParser.equalAny(DefaultKeyword.UNION,DefaultKeyword.EXCEPT,DefaultKeyword.INTERSECT,DefaultKeyword.MINUS)){
thrownewSQLParsingUnsupportedException(sqlParser.getLexer().getCurrentToken().getType());
}
}
不支持 UNION / EXCEPT / INTERSECT / MINUS ,调用会抛出异常。
4. appendDerived等方法
因为 Sharding-JDBC 对表做了分片,在 AVG , GROUP BY , ORDER BY 需要对 SQL 进行一些改写,以达到能在内存里对结果做进一步处理,例如求平均值、分组、排序等。
😈:打起精神,此块是非常有趣的。
4.1 appendAvgDerivedColumns
解决 AVG 查询。
// AbstractSelectParser.java
/**
* 针对 AVG 聚合字段,增加推导字段
* AVG 改写成 SUM + COUNT 查询,内存计算出 AVG 结果。
*
* @param itemsToken 选择项标记对象
*/
privatevoid appendAvgDerivedColumns(finalItemsToken itemsToken){
int derivedColumnOffset =0;
for(SelectItem each : selectStatement.getItems()){
if(!(each instanceofAggregationSelectItem)||AggregationType.AVG !=((AggregationSelectItem) each).getType()){
continue;
}
AggregationSelectItem avgItem =(AggregationSelectItem) each;
// COUNT 字段
String countAlias =String.format(DERIVED_COUNT_ALIAS, derivedColumnOffset);
AggregationSelectItem countItem =newAggregationSelectItem(AggregationType.COUNT, avgItem.getInnerExpression(),Optional.of(countAlias));
// SUM 字段
String sumAlias =String.format(DERIVED_SUM_ALIAS, derivedColumnOffset);
AggregationSelectItem sumItem =newAggregationSelectItem(AggregationType.SUM, avgItem.getInnerExpression(),Optional.of(sumAlias));
// AggregationSelectItem 设置
avgItem.getDerivedAggregationSelectItems().add(countItem);
avgItem.getDerivedAggregationSelectItems().add(sumItem);
// TODO 将AVG列替换成常数,避免数据库再计算无用的AVG函数
// ItemsToken
itemsToken.getItems().add(countItem.getExpression()+" AS "+ countAlias +" ");
itemsToken.getItems().add(sumItem.getExpression()+" AS "+ sumAlias +" ");
//
derivedColumnOffset++;
}
}
4.2 appendDerivedOrderColumns
解决 GROUP BY , ORDER BY。
// AbstractSelectParser.java
/**
* 针对 GROUP BY 或 ORDER BY 字段,增加推导字段
* 如果该字段不在查询字段里,需要额外查询该字段,这样才能在内存里 GROUP BY 或 ORDER BY
*
* @param itemsToken 选择项标记对象
* @param orderItems 排序字段
* @param aliasPattern 别名模式
*/
privatevoid appendDerivedOrderColumns(finalItemsToken itemsToken,finalList<OrderItem> orderItems,finalString aliasPattern){
int derivedColumnOffset =0;
for(OrderItem each : orderItems){
if(!isContainsItem(each)){
Stringalias=String.format(aliasPattern, derivedColumnOffset++);
each.setAlias(Optional.of(alias));
itemsToken.getItems().add(each.getQualifiedName().get()+" AS "+alias+" ");
}
}
}
/**
* 查询字段是否包含排序字段
*
* @param orderItem 排序字段
* @return 是否
*/
privateboolean isContainsItem(finalOrderItem orderItem){
if(selectStatement.isContainStar()){// SELECT *
returntrue;
}
for(SelectItem each : selectStatement.getItems()){
if(-1!= orderItem.getIndex()){// ORDER BY 使用数字
returntrue;
}
if(each.getAlias().isPresent()&& orderItem.getAlias().isPresent()&& each.getAlias().get().equalsIgnoreCase(orderItem.getAlias().get())){// 字段别名比较
returntrue;
}
if(!each.getAlias().isPresent()&& orderItem.getQualifiedName().isPresent()&& each.getExpression().equalsIgnoreCase(orderItem.getQualifiedName().get())){// 字段原名比较
returntrue;
}
}
returnfalse;
}
4.3 ItemsToken
选择项标记对象,属于分片上下文信息,目前有 3 个情况会创建:
AVG
查询额外 COUNT 和 SUM:#appendAvgDerivedColumns()
GROUP BY
不在 查询字段,额外查询该字段 :#appendDerivedOrderColumns()
ORDER BY
不在 查询字段,额外查询该字段 :#appendDerivedOrderColumns()
publicfinalclassItemsTokenimplementsSQLToken{
/**
* SQL 开始位置
*/
privatefinalint beginPosition;
/**
* 字段名数组
*/
privatefinalList<String> items =newLinkedList<>();
}
4.4 appendDerivedOrderBy()
当 SQL 有聚合条件而无排序条件,根据聚合条件进行排序。这是数据库自己的执行规则。
mysql> SELECT order_id FROM t_order GROUP BY order_id;
+----------+
| order_id |
+----------+
|1|
|2|
|3|
+----------+
3 rows inset(0.05 sec)
mysql> SELECT order_id FROM t_order GROUP BY order_id DESC;
+----------+
| order_id |
+----------+
|3|
|2|
|1|
+----------+
3 rows inset(0.02 sec)
// AbstractSelectParser.java
/**
* 当无 Order By 条件时,使用 Group By 作为排序条件(数据库本身规则)
*/
privatevoid appendDerivedOrderBy(){
if(!getSelectStatement().getGroupByItems().isEmpty()&& getSelectStatement().getOrderByItems().isEmpty()){
getSelectStatement().getOrderByItems().addAll(getSelectStatement().getGroupByItems());
getSelectStatement().getSqlTokens().add(newOrderByToken(getSelectStatement().getGroupByLastPosition()));
}
}
4.3.1 OrderByToken
排序标记对象。当无 Order By 条件时,使用 Group By 作为排序条件(数据库本身规则)。
// OrderByToken.java
publicfinalclassOrderByTokenimplementsSQLToken{
/**
* SQL 所在开始位置
*/
privatefinalint beginPosition;
}
666. 彩蛋
咳咳咳,确实有一些略长。但请相信,INSERT / UPDATE / DELETE 会简单很多很多。考试考的 SQL 最多的是什么?SELECT 语句呀!为啥,难呗。恩,我相信看到此处的你,一定是能看懂的,加油!
🙂如果对本文有不理解的地方,可以关注我的公众号(芋艿的后端小屋)获得微信号,我们来一场,1 对 1 的搞基吧,不不不,是交流交流。
道友,帮我分享一波怎么样?
阅读原文 最新评论
推荐文章
作者最新文章
你可能感兴趣的文章
Copyright Disclaimer: The copyright of contents (including texts, images, videos and audios) posted above belong to the User who shared or the third-party website which the User shared from. If you found your copyright have been infringed, please send a DMCA takedown notice to [email protected]. For more detail of the source, please click on the button "Read Original Post" below. For other communications, please send to [email protected].
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。