🙂🙂🙂关注微信公众号:【芋艿的后端小屋】有福利:
  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群!

  • 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 解析主流程如下:
  1. // AbstractSelectParser.java
  2. publicfinalSelectStatement parse(){
  3.   query();
  4.   parseOrderBy();
  5.   customizedSelect();
  6.   appendDerivedColumns();
  7.   appendDerivedOrderBy();
  8. return selectStatement;
  9. }
  • #parseOrderBy() :对于 MySQL 查询语句解析器无效果,因为已经在 #query() 方法里面已经调用 #parseOrderBy(),因此图中省略该方法。
  • #customizedSelect() :Oracle、SQLServer 查询语句解析器重写了该方法,对于 MySQL 查询解析器是个空方法,进行省略。有兴趣的同学可以单独去研究研究。
Sharding-JDBC 正在收集使用公司名单:传送门。

🙂 你的登记,会让更多人参与和使用 Sharding-JDBC。传送门

Sharding-JDBC 也会因此,能够覆盖更多的业务场景。传送门

登记吧,骚年!传送门
👼 查询语句解析是增删改查里面
最灵活也是最复杂的
,希望大家有耐心看完本文。理解查询语句解析,另外三种语句理解起来简直是 SO EASY。骗人是小狗🐶。

🙂如果对本文有不理解的地方,可以给我的公众号
(芋艿的后端小屋)
留言,我会
逐条认真耐心
回复。骗人是小猪🐷。
OK,不废话啦,开始我们这段痛并快乐的旅途。

2. SelectStatement

🙂 本节只介绍这些类,方便本文下节分析源码实现大家能知道认识它们 🙂
SelectStatement,查询语句解析结果对象。
  1. // SelectStatement.java
  2. publicfinalclassSelectStatementextendsAbstractSQLStatement{
  3. /**
  4.     * 是否行 DISTINCT / DISTINCTROW / UNION
  5.     */
  6. privateboolean distinct;
  7. /**
  8.     * 是否查询所有字段,即 SELECT *
  9.     */
  10. privateboolean containStar;
  11. /**
  12.     * 最后一个查询项下一个 Token 的开始位置
  13.     *
  14.     * @see #items
  15.     */
  16. privateint selectListLastPosition;
  17. /**
  18.     * 最后一个分组项下一个 Token 的开始位置
  19.     */
  20. privateint groupByLastPosition;
  21. /**
  22.     * 查询项
  23.     */
  24. privatefinalList<SelectItem> items =newLinkedList<>();
  25. /**
  26.     * 分组项
  27.     */
  28. privatefinalList<OrderItem> groupByItems =newLinkedList<>();
  29. /**
  30.     * 排序项
  31.     */
  32. privatefinalList<OrderItem> orderByItems =newLinkedList<>();
  33. /**
  34.     * 分页
  35.     */
  36. privateLimit limit;
  37. }
我们对属性按照类型进行归类:
  • 特殊
    • distinct
  • 查询字段
    • containStar
    • items
    • selectListLastPosition
  • 分组条件
    • groupByItems
    • groupByLastPosition
  • 排序条件
    • orderByItems
  • 分页条件
    • limit

2.1 AbstractSQLStatement

增删改查解析结果对象的抽象父类
  1. publicabstractclassAbstractSQLStatementimplementsSQLStatement{
  2. /**
  3.     * SQL 类型
  4.     */
  5. privatefinalSQLType type;
  6. /**
  7.     * 表
  8.     */
  9. privatefinalTables tables =newTables();
  10. /**
  11.     * 过滤条件。
  12.     * 只有对路由结果有影响的条件,才添加进数组
  13.     */
  14. privatefinalConditions conditions =newConditions();
  15. /**
  16.     * SQL标记对象
  17.     */
  18. privatefinalList<SQLToken> sqlTokens =newLinkedList<>();
  19. }

2.2 SQLToken

SQLToken,SQL标记对象接口,SQL 改写时使用到。下面都是它的实现类:
说明
GeneratedKeyToken自增主键标记对象
TableToken表标记对象
ItemsToken选择项标记对象
OffsetToken分页偏移量标记对象
OrderByToken排序标记对象
RowCountToken分页长度标记对象

3. #query()

#query(),查询 SQL 解析。
MySQL SELECT Syntax
  1. // https://dev.mysql.com/doc/refman/5.7/en/select.html
  2. SELECT
  3. [ALL | DISTINCT | DISTINCTROW ]
  4. [HIGH_PRIORITY]
  5. [STRAIGHT_JOIN]
  6. [SQL_SMALL_RESULT][SQL_BIG_RESULT][SQL_BUFFER_RESULT]
  7. [SQL_CACHE | SQL_NO_CACHE][SQL_CALC_FOUND_ROWS]
  8.    select_expr [, select_expr ...]
  9. [FROM table_references
  10. [PARTITION partition_list]
  11. [WHERE where_condition]
  12. [GROUP BY {col_name | expr | position}
  13. [ASC | DESC],...[WITH ROLLUP]]
  14. [HAVING where_condition]
  15. [ORDER BY {col_name | expr | position}
  16. [ASC | DESC],...]
  17. [LIMIT {[offset,] row_count | row_count OFFSET offset}]
  18. [PROCEDURE procedure_name(argument_list)]
  19. [INTO OUTFILE 'file_name'
  20. [CHARACTER SET charset_name]
  21.        export_options
  22. | INTO DUMPFILE 'file_name'
  23. | INTO var_name [, var_name]]
  24. [FOR UPDATE | LOCK IN SHARE MODE]]
大体流程如下:
  1. // MySQLSelectParser.java
  2. publicvoid query(){
  3. if(getSqlParser().equalAny(DefaultKeyword.SELECT)){
  4.       getSqlParser().getLexer().nextToken();
  5.       parseDistinct();
  6.       getSqlParser().skipAll(MySQLKeyword.HIGH_PRIORITY,DefaultKeyword.STRAIGHT_JOIN,MySQLKeyword.SQL_SMALL_RESULT,MySQLKeyword.SQL_BIG_RESULT,MySQLKeyword.SQL_BUFFER_RESULT,
  7. MySQLKeyword.SQL_CACHE,MySQLKeyword.SQL_NO_CACHE,MySQLKeyword.SQL_CALC_FOUND_ROWS);
  8.       parseSelectList();// 解析 查询字段
  9.       skipToFrom();// 跳到 FROM 处
  10. }
  11.   parseFrom();// 解析 表(JOIN ON / FROM 单&多表)
  12.   parseWhere();// 解析 WHERE 条件
  13.   parseGroupBy();// 解析 Group By 和 Having(目前不支持)条件
  14.   parseOrderBy();// 解析 Order By 条件
  15.   parseLimit();// 解析 分页 Limit 条件
  16. // [PROCEDURE] 暂不支持
  17. if(getSqlParser().equalAny(DefaultKeyword.PROCEDURE)){
  18. thrownewSQLParsingUnsupportedException(getSqlParser().getLexer().getCurrentToken().getType());
  19. }
  20.   queryRest();
  21. }

3.1 #parseDistinct()

解析 DISTINCT、DISTINCTROW、UNION 谓语。
核心代码:
  1. // AbstractSelectParser.java
  2. protectedfinalvoid parseDistinct(){
  3. if(sqlParser.equalAny(DefaultKeyword.DISTINCT,DefaultKeyword.DISTINCTROW,DefaultKeyword.UNION)){
  4.       selectStatement.setDistinct(true);
  5.       sqlParser.getLexer().nextToken();
  6. if(hasDistinctOn()&& sqlParser.equalAny(DefaultKeyword.ON)){// PostgreSQL 独有语法: DISTINCT ON
  7.           sqlParser.getLexer().nextToken();
  8.           sqlParser.skipParentheses();
  9. }
  10. }elseif(sqlParser.equalAny(DefaultKeyword.ALL)){
  11.       sqlParser.getLexer().nextToken();
  12. }
  13. }
此处 DISTINCT 和 DISTINCT(字段) 不同,它是针对查询结果做去重,即整行重复。举个例子:
  1. mysql> SELECT item_id, order_id FROM t_order_item;
  2. +---------+----------+
  3. | item_id | order_id |
  4. +---------+----------+
  5. |1|1|
  6. |1|1|
  7. +---------+----------+
  8. 2 rows inset(0.03 sec)
  9. mysql> SELECT DISTINCT item_id, order_id FROM t_order_item;
  10. +---------+----------+
  11. | item_id | order_id |
  12. +---------+----------+
  13. |1|1|
  14. +---------+----------+
  15. 1 rows inset(0.02 sec)

3.2 #parseSelectList()

SELECTo.user_idCOUNT(DISTINCT i.itemid) AS itemcountMAX(i.item_id)FROM
SelectItemSelectItemSelectItem
将 SQL 查询字段 按照逗号( , )切割成多个选择项( SelectItem)。核心代码如下:
  1. // AbstractSelectParser.java
  2. protectedfinalvoid parseSelectList(){
  3. do{
  4. // 解析单个选择项
  5.       parseSelectItem();
  6. }while(sqlParser.skipIfEqual(Symbol.COMMA));
  7. // 设置 最后一个查询项下一个 Token 的开始位置
  8.   selectStatement.setSelectListLastPosition(sqlParser.getLexer().getCurrentToken().getEndPosition()- sqlParser.getLexer().getCurrentToken().getLiterals().length());
  9. }

3.2.1 SelectItem 选择项

SelectItem 接口,属于分片上下文信息,有 2 个实现类:
  • CommonSelectItem :通用选择项
  • AggregationSelectItem :聚合选择项
解析单个 SelectItem 核心代码:
  1. // AbstractSelectParser.java
  2. privatevoid parseSelectItem(){
  3. // 第四种情况,SQL Server 独有
  4. if(isRowNumberSelectItem()){
  5.       selectStatement.getItems().add(parseRowNumberSelectItem());
  6. return;
  7. }
  8.   sqlParser.skipIfEqual(DefaultKeyword.CONNECT_BY_ROOT);// Oracle 独有:https://docs.oracle.com/cd/B19306_01/server.102/b14200/operators004.htm
  9. String literals = sqlParser.getLexer().getCurrentToken().getLiterals();
  10. // 第一种情况,* 通用选择项,SELECT *
  11. if(sqlParser.equalAny(Symbol.STAR)||Symbol.STAR.getLiterals().equals(SQLUtil.getExactlyValue(literals))){
  12.       sqlParser.getLexer().nextToken();
  13.       selectStatement.getItems().add(newCommonSelectItem(Symbol.STAR.getLiterals(), sqlParser.parseAlias()));
  14.       selectStatement.setContainStar(true);
  15. return;
  16. }
  17. // 第二种情况,聚合选择项
  18. if(sqlParser.skipIfEqual(DefaultKeyword.MAX,DefaultKeyword.MIN,DefaultKeyword.SUM,DefaultKeyword.AVG,DefaultKeyword.COUNT)){
  19.       selectStatement.getItems().add(newAggregationSelectItem(AggregationType.valueOf(literals.toUpperCase()), sqlParser.skipParentheses(), sqlParser.parseAlias()));
  20. return;
  21. }
  22. // 第三种情况,非 * 通用选择项
  23. StringBuilder expression =newStringBuilder();
  24. Token lastToken =null;
  25. while(!sqlParser.equalAny(DefaultKeyword.AS)&&!sqlParser.equalAny(Symbol.COMMA)&&!sqlParser.equalAny(DefaultKeyword.FROM)&&!sqlParser.equalAny(Assist.END)){
  26. String value = sqlParser.getLexer().getCurrentToken().getLiterals();
  27. int position = sqlParser.getLexer().getCurrentToken().getEndPosition()- value.length();
  28.       expression.append(value);
  29.       lastToken = sqlParser.getLexer().getCurrentToken();
  30.       sqlParser.getLexer().nextToken();
  31. if(sqlParser.equalAny(Symbol.DOT)){
  32.           selectStatement.getSqlTokens().add(newTableToken(position, value));
  33. }
  34. }
  35. // 不带 AS,并且有别名,并且别名不等于自己(tips:这里重点看。判断这么复杂的原因:防止substring操作截取结果错误)
  36. if(null!= lastToken &&Literals.IDENTIFIER == lastToken.getType()
  37. &&!isSQLPropertyExpression(expression, lastToken)// 过滤掉,别名是自己的情况【1】(例如,SELECT u.user_id u.user_id FROM t_user)
  38. &&!expression.toString().equals(lastToken.getLiterals())){// 过滤掉,无别名的情况【2】(例如,SELECT user_id FROM t_user)
  39.       selectStatement.getItems().add(
  40. newCommonSelectItem(SQLUtil.getExactlyValue(expression.substring(0, expression.lastIndexOf(lastToken.getLiterals()))),Optional.of(lastToken.getLiterals())));
  41. return;
  42. }
  43. // 带 AS(例如,SELECT user_id AS userId) 或者 无别名(例如,SELECT user_id)
  44.   selectStatement.getItems().add(newCommonSelectItem(SQLUtil.getExactlyValue(expression.toString()), sqlParser.parseAlias()));
  45. }
一共分成 4 种大的情况,我们来逐条梳理:
  • 第一种:
    * 通用选择项

    例如, 
    SELECT*FROM t_user
     的 
    *

    为什么要加 
    Symbol.STAR.getLiterals().equals(SQLUtil.getExactlyValue(literals))
     判断呢?
  1. 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 里出现的位置名字
  1. publicfinalclassTableTokenimplementsSQLToken{
  2. /**
  3.     * 开始位置
  4.     */
  5. privatefinalint beginPosition;
  6. /**
  7.     * 表达式
  8.     */
  9. privatefinalString originalLiterals;
  10. /**
  11.     * 获取表名称.
  12.     * @return 表名称
  13.     */
  14. publicString getTableName(){
  15. returnSQLUtil.getExactlyValue(originalLiterals);
  16. }
  17. }
例如上文第三种情况。

3.3 #skipToFrom()

  1. /**
  2. * 跳到 FROM 处
  3. */
  4. privatevoid skipToFrom(){
  5. while(!getSqlParser().equalAny(DefaultKeyword.FROM)&&!getSqlParser().equalAny(Assist.END)){
  6.       getSqlParser().getLexer().nextToken();
  7. }
  8. }

3.4 #parseFrom()

解析表以及表连接关系。这块相对比较复杂,请大家耐心+耐心+耐心。
MySQL JOIN Syntax
  1. // https://dev.mysql.com/doc/refman/5.7/en/join.html
  2. table_references:
  3.    escaped_table_reference [, escaped_table_reference]...
  4. escaped_table_reference:
  5.    table_reference
  6. |{ OJ table_reference }
  7. table_reference:
  8.    table_factor
  9. | join_table
  10. table_factor:
  11.    tbl_name [PARTITION (partition_names)]
  12. [[AS]alias][index_hint_list]
  13. | table_subquery [AS]alias
  14. |( table_references )
  15. join_table:
  16.    table_reference [INNER | CROSS] JOIN table_factor [join_condition]
  17. | table_reference STRAIGHT_JOIN table_factor
  18. | table_reference STRAIGHT_JOIN table_factor ON conditional_expr
  19. | table_reference {LEFT|RIGHT}[OUTER] JOIN table_reference join_condition
  20. | table_reference NATURAL [{LEFT|RIGHT}[OUTER]] JOIN table_factor
  21. join_condition:
  22.    ON conditional_expr
  23. | USING (column_list)
  24. index_hint_list:
  25.    index_hint [, index_hint]...
  26. index_hint:
  27.    USE {INDEX|KEY}
  28. [FOR {JOIN|ORDER BY|GROUP BY}]([index_list])
  29. | IGNORE {INDEX|KEY}
  30. [FOR {JOIN|ORDER BY|GROUP BY}](index_list)
  31. | FORCE {INDEX|KEY}
  32. [FOR {JOIN|ORDER BY|GROUP BY}](index_list)
  33. index_list:
  34.    index_name [, index_name]...

3.4.1 JOIN ON / FROM TABLE

先抛开子查询的情况,只考虑如下两种 SQL 情况。
  1. // JOIN ON : 实际可以继续 JOIN ON 更多表
  2. SELECT * FROM t_order o JOIN t_order_item i ON o.order_id = i.order_id;
  3. // FROM 多表 :实际可以继续 FROM 多更表
  4. SELECT * FROM t_order o, t_order_item i
在看实现代码之前,先一起看下调用顺序图:
看懂上图后,来继续看下实现代码(🙂代码有点多,不要方!):
  1. // AbstractSelectParser.java
  2. /**
  3. * 解析所有表名和表别名
  4. */
  5. publicfinalvoid parseFrom(){
  6. if(sqlParser.skipIfEqual(DefaultKeyword.FROM)){
  7.       parseTable();
  8. }
  9. }
  10. /**
  11. * 解析所有表名和表别名
  12. */
  13. publicvoid parseTable(){
  14. // 解析子查询
  15. if(sqlParser.skipIfEqual(Symbol.LEFT_PAREN)){
  16. if(!selectStatement.getTables().isEmpty()){
  17. thrownewUnsupportedOperationException("Cannot support subquery for nested tables.");
  18. }
  19.       selectStatement.setContainStar(false);
  20.       sqlParser.skipUselessParentheses();// 去掉子查询左括号
  21.       parse();// 解析子查询 SQL
  22.       sqlParser.skipUselessParentheses();// 去掉子查询右括号
  23. //
  24. if(!selectStatement.getTables().isEmpty()){
  25. return;
  26. }
  27. }
  28.   parseTableFactor();// 解析当前表
  29.   parseJoinTable();// 解析下一个表
  30. }
  31. /**
  32. * 解析单个表名和表别名
  33. */
  34. protectedfinalvoid parseTableFactor(){
  35. int beginPosition = sqlParser.getLexer().getCurrentToken().getEndPosition()- sqlParser.getLexer().getCurrentToken().getLiterals().length();
  36. String literals = sqlParser.getLexer().getCurrentToken().getLiterals();
  37.   sqlParser.getLexer().nextToken();
  38. // TODO 包含Schema解析
  39. 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
  40.       sqlParser.getLexer().nextToken();
  41.       sqlParser.parseAlias();
  42. return;
  43. }
  44. // FIXME 根据shardingRule过滤table
  45.   selectStatement.getSqlTokens().add(newTableToken(beginPosition, literals));
  46. // 表 以及 表别名
  47.   selectStatement.getTables().add(newTable(SQLUtil.getExactlyValue(literals), sqlParser.parseAlias()));
  48. }
  49. /**
  50. * 解析 Join Table 或者 FROM 下一张 Table
  51. */
  52. protectedvoid parseJoinTable(){
  53. if(sqlParser.skipJoin()){
  54. // 这里调用 parseJoinTable() 而不是 parseTableFactor() :下一个 Table 可能是子查询
  55. // 例如:SELECT * FROM t_order JOIN (SELECT * FROM t_order_item JOIN t_order_other ON ) .....
  56.       parseTable();
  57. if(sqlParser.skipIfEqual(DefaultKeyword.ON)){// JOIN 表时 ON 条件
  58. do{
  59.               parseTableCondition(sqlParser.getLexer().getCurrentToken().getEndPosition());
  60.               sqlParser.accept(Symbol.EQ);
  61.               parseTableCondition(sqlParser.getLexer().getCurrentToken().getEndPosition()- sqlParser.getLexer().getCurrentToken().getLiterals().length());
  62. }while(sqlParser.skipIfEqual(DefaultKeyword.AND));
  63. }elseif(sqlParser.skipIfEqual(DefaultKeyword.USING)){// JOIN 表时 USING 为使用两表相同字段相同时对 ON 的简化。例如以下两条 SQL 等价:
  64. // SELECT * FROM t_order o JOIN t_order_item i USING (order_id);
  65. // SELECT * FROM t_order o JOIN t_order_item i ON o.order_id = i.order_id
  66.           sqlParser.skipParentheses();
  67. }
  68.       parseJoinTable();// 继续递归
  69. }
  70. }
  71. /**
  72. * 解析 ON 条件里的 TableToken
  73. *
  74. * @param startPosition 开始位置
  75. */
  76. privatevoid parseTableCondition(finalint startPosition){
  77. SQLExpression sqlExpression = sqlParser.parseExpression();
  78. if(!(sqlExpression instanceofSQLPropertyExpression)){
  79. return;
  80. }
  81. SQLPropertyExpression sqlPropertyExpression =(SQLPropertyExpression) sqlExpression;
  82. if(selectStatement.getTables().getTableNames().contains(SQLUtil.getExactlyValue(sqlPropertyExpression.getOwner().getName()))){
  83.       selectStatement.getSqlTokens().add(newTableToken(startPosition, sqlPropertyExpression.getOwner().getName()));
  84. }
  85. }
OK,递归因为平时日常中写的比较少,可能理解起来可能会困难一些,努力看懂!🙂如果真的看不懂,可以加微信公众号(芋艿的后端小屋),我来帮你一起理解。

3.4.2 子查询

Sharding-JDBC 目前支持第一个包含多层级的数据子查询。例如:
  1. SELECT o3.* FROM (SELECT * FROM (SELECT * FROM t_order o) o2) o3;
  2. SELECT o3.* FROM (SELECT * FROM (SELECT * FROM t_order o) o2) o3 JOIN t_order_item i ON o3.order_id = i.order_id;
不支持第二个开始包含多层级的数据子查询。例如:
  1. 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 左右量表颠倒
  2. SELECT COUNT(*) FROM (SELECT * FROM t_order o WHERE o.id IN (SELECT id FROM t_order WHERE status =?))// FROM 官方不支持 SQL 举例
使用第二个开始的子查询会抛出异常,代码如下:
  1. // AbstractSelectParser.java#parseTable()片段
  2. if(!selectStatement.getTables().isEmpty()){
  3. thrownewUnsupportedOperationException("Cannot support subquery for nested tables.");
  4. }
使用子查询,建议认真阅读官方《分页及子查询》文档。

3.4.3 #parseJoinTable()

MySQLSelectParser 重写了 #parseJoinTable() 方法用于解析 USE / IGNORE / FORCE index_hint。具体语法见上文 JOIN Syntax。这里就跳过,有兴趣的同学可以去看看。

3.4.4 Tables 表集合对象

属于分片上下文信息
  1. // Tables.java
  2. publicfinalclassTables{
  3. privatefinalList<Table> tables =newArrayList<>();
  4. }
  5. // Table.java
  6. publicfinalclassTable{
  7. /**
  8.     * 表
  9.     */
  10. privatefinalString name;
  11. /**
  12.     * 别名
  13.     */
  14. privatefinalOptional<String>alias;
  15. }
  16. // AbstractSelectParser.java#parseTableFactor()片段
  17. selectStatement.getTables().add(newTable(SQLUtil.getExactlyValue(literals), sqlParser.parseAlias()));

3.5 #parseWhere()

解析 WHERE 条件。解析代码:《SQL 解析(二)之SQL解析》的#parseWhere()小节。

3.6 #parseGroupBy()

解析分组条件,实现上比较类似 #parseSelectList,会更加简单一些。
  1. // 🙂 抱歉,微信文章篇幅长度限制,该处代码已经“屏蔽”。解决方式:
  2. 1. 点击最下方【阅读原文】
  3. 2. 访问地址:http://www.yunai.me/Sharding-JDBC/sql-parse-3/?mp

3.6.1 OrderItem 排序项

属于分片上下文信息
  1. publicfinalclassOrderItem{
  2. /**
  3.    * 所属表别名
  4.    */
  5. privatefinalOptional<String> owner;
  6. /**
  7.    * 排序字段
  8.    */
  9. privatefinalOptional<String> name;
  10. /**
  11.    * 排序类型
  12.    */
  13. privatefinalOrderType type;
  14. /**
  15.    * 按照第几个查询字段排序
  16.    * ORDER BY 数字 的 数字代表的是第几个字段
  17.    */
  18. @Setter
  19. privateint index =-1;
  20. /**
  21.    * 字段在查询项({@link com.dangdang.ddframe.rdb.sharding.parsing.parser.context.selectitem.SelectItem} 的别名
  22.    */
  23. @Setter
  24. privateOptional<String>alias;
  25. }

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

分页对象。属于分片上下文信息
  1. // Limit.java
  2. publicfinalclassLimit{
  3. /**
  4.     * 是否重写rowCount
  5.     * TODO 待补充:预计和内存分页合并有关
  6.     */
  7. privatefinalboolean rowCountRewriteFlag;
  8. /**
  9.     * offset
  10.     */
  11. privateLimitValue offset;
  12. /**
  13.     * row
  14.     */
  15. privateLimitValue rowCount;
  16. }
  17. // LimitValue.java
  18. publicfinalclassLimitValue{
  19. /**
  20.     * 值
  21.     * 当 value == -1 时,为占位符
  22.     */
  23. privateint value;
  24. /**
  25.     * 第几个占位符
  26.     */
  27. privateint index;
  28. }

3.8.2 OffsetToken RowCountToken

  • OffsetToken:分页偏移量标记对象
  • RowCountToken:分页长度标记对象
只有在对应位置非占位符才有该 SQLToken
  1. // OffsetToken.java
  2. publicfinalclassOffsetTokenimplementsSQLToken{
  3. /**
  4.     * SQL 所在开始位置
  5.     */
  6. privatefinalint beginPosition;
  7. /**
  8.     * 偏移值
  9.     */
  10. privatefinalint offset;
  11. }
  12. // RowCountToken.java
  13. publicfinalclassRowCountTokenimplementsSQLToken{
  14. /**
  15.     * SQL 所在开始位置
  16.     */
  17. privatefinalint beginPosition;
  18. /**
  19.     * 行数
  20.     */
  21. privatefinalint rowCount;
  22. }

3.9 #queryRest()

  1. // AbstractSelectParser.java
  2. protectedvoid queryRest(){
  3. if(sqlParser.equalAny(DefaultKeyword.UNION,DefaultKeyword.EXCEPT,DefaultKeyword.INTERSECT,DefaultKeyword.MINUS)){
  4. thrownewSQLParsingUnsupportedException(sqlParser.getLexer().getCurrentToken().getType());
  5. }
  6. }
不支持 UNION / EXCEPT / INTERSECT / MINUS ,调用会抛出异常。

4. appendDerived等方法

因为 Sharding-JDBC 对表做了分片,在 AVG , GROUP BY , ORDER BY 需要对 SQL 进行一些改写,以达到能在内存里对结果做进一步处理,例如求平均值、分组、排序等。
😈:打起精神,此块是非常有趣的。

4.1 appendAvgDerivedColumns

解决 AVG 查询。
  1. // AbstractSelectParser.java
  2. /**
  3. * 针对 AVG 聚合字段,增加推导字段
  4. * AVG 改写成 SUM + COUNT 查询,内存计算出 AVG 结果。
  5. *
  6. * @param itemsToken 选择项标记对象
  7. */
  8. privatevoid appendAvgDerivedColumns(finalItemsToken itemsToken){
  9. int derivedColumnOffset =0;
  10. for(SelectItem each : selectStatement.getItems()){
  11. if(!(each instanceofAggregationSelectItem)||AggregationType.AVG !=((AggregationSelectItem) each).getType()){
  12. continue;
  13. }
  14. AggregationSelectItem avgItem =(AggregationSelectItem) each;
  15. // COUNT 字段
  16. String countAlias =String.format(DERIVED_COUNT_ALIAS, derivedColumnOffset);
  17. AggregationSelectItem countItem =newAggregationSelectItem(AggregationType.COUNT, avgItem.getInnerExpression(),Optional.of(countAlias));
  18. // SUM 字段
  19. String sumAlias =String.format(DERIVED_SUM_ALIAS, derivedColumnOffset);
  20. AggregationSelectItem sumItem =newAggregationSelectItem(AggregationType.SUM, avgItem.getInnerExpression(),Optional.of(sumAlias));
  21. // AggregationSelectItem 设置
  22.       avgItem.getDerivedAggregationSelectItems().add(countItem);
  23.       avgItem.getDerivedAggregationSelectItems().add(sumItem);
  24. // TODO 将AVG列替换成常数,避免数据库再计算无用的AVG函数
  25. // ItemsToken
  26.       itemsToken.getItems().add(countItem.getExpression()+" AS "+ countAlias +" ");
  27.       itemsToken.getItems().add(sumItem.getExpression()+" AS "+ sumAlias +" ");
  28. //
  29.       derivedColumnOffset++;
  30. }
  31. }

4.2 appendDerivedOrderColumns

解决 GROUP BY , ORDER BY。
  1. // AbstractSelectParser.java
  2. /**
  3. * 针对 GROUP BY 或 ORDER BY 字段,增加推导字段
  4. * 如果该字段不在查询字段里,需要额外查询该字段,这样才能在内存里 GROUP BY 或 ORDER BY
  5. *
  6. * @param itemsToken 选择项标记对象
  7. * @param orderItems 排序字段
  8. * @param aliasPattern 别名模式
  9. */
  10. privatevoid appendDerivedOrderColumns(finalItemsToken itemsToken,finalList<OrderItem> orderItems,finalString aliasPattern){
  11. int derivedColumnOffset =0;
  12. for(OrderItem each : orderItems){
  13. if(!isContainsItem(each)){
  14. Stringalias=String.format(aliasPattern, derivedColumnOffset++);
  15.           each.setAlias(Optional.of(alias));
  16.           itemsToken.getItems().add(each.getQualifiedName().get()+" AS "+alias+" ");
  17. }
  18. }
  19. }
  20. /**
  21. * 查询字段是否包含排序字段
  22. *
  23. * @param orderItem 排序字段
  24. * @return 是否
  25. */
  26. privateboolean isContainsItem(finalOrderItem orderItem){
  27. if(selectStatement.isContainStar()){// SELECT *
  28. returntrue;
  29. }
  30. for(SelectItem each : selectStatement.getItems()){
  31. if(-1!= orderItem.getIndex()){// ORDER BY 使用数字
  32. returntrue;
  33. }
  34. if(each.getAlias().isPresent()&& orderItem.getAlias().isPresent()&& each.getAlias().get().equalsIgnoreCase(orderItem.getAlias().get())){// 字段别名比较
  35. returntrue;
  36. }
  37. if(!each.getAlias().isPresent()&& orderItem.getQualifiedName().isPresent()&& each.getExpression().equalsIgnoreCase(orderItem.getQualifiedName().get())){// 字段原名比较
  38. returntrue;
  39. }
  40. }
  41. returnfalse;
  42. }

4.3 ItemsToken

选择项标记对象,属于分片上下文信息,目前有 3 个情况会创建:
  1. AVG 查询额外 COUNT 和 SUM: #appendAvgDerivedColumns()
  2. GROUP BY 不在 查询字段,额外查询该字段 : #appendDerivedOrderColumns()
  3. ORDER BY 不在 查询字段,额外查询该字段 : #appendDerivedOrderColumns()
  1. publicfinalclassItemsTokenimplementsSQLToken{
  2. /**
  3.     * SQL 开始位置
  4.     */
  5. privatefinalint beginPosition;
  6. /**
  7.     * 字段名数组
  8.     */
  9. privatefinalList<String> items =newLinkedList<>();
  10. }

4.4 appendDerivedOrderBy()

当 SQL 有聚合条件而无排序条件,根据聚合条件进行排序。这是数据库自己的执行规则。
  1. mysql> SELECT order_id FROM t_order GROUP BY order_id;
  2. +----------+
  3. | order_id |
  4. +----------+
  5. |1|
  6. |2|
  7. |3|
  8. +----------+
  9. 3 rows inset(0.05 sec)
  10. mysql> SELECT order_id FROM t_order GROUP BY order_id DESC;
  11. +----------+
  12. | order_id |
  13. +----------+
  14. |3|
  15. |2|
  16. |1|
  17. +----------+
  18. 3 rows inset(0.02 sec)
  1. // AbstractSelectParser.java
  2. /**
  3. * 当无 Order By 条件时,使用 Group By 作为排序条件(数据库本身规则)
  4. */
  5. privatevoid appendDerivedOrderBy(){
  6. if(!getSelectStatement().getGroupByItems().isEmpty()&& getSelectStatement().getOrderByItems().isEmpty()){
  7.       getSelectStatement().getOrderByItems().addAll(getSelectStatement().getGroupByItems());
  8.       getSelectStatement().getSqlTokens().add(newOrderByToken(getSelectStatement().getGroupByLastPosition()));
  9. }
  10. }

4.3.1 OrderByToken

排序标记对象。当无 Order By 条件时,使用 Group By 作为排序条件(数据库本身规则)。
  1. // OrderByToken.java
  2. publicfinalclassOrderByTokenimplementsSQLToken{
  3. /**
  4.     * SQL 所在开始位置
  5.     */
  6. privatefinalint beginPosition;
  7. }

666. 彩蛋

咳咳咳,确实有一些略长。但请相信,INSERT / UPDATE / DELETE 会简单很多很多。考试考的 SQL 最多的是什么?SELECT 语句呀!为啥,难呗。恩,我相信看到此处的你,一定是能看懂的,加油!
🙂如果对本文有不理解的地方,可以关注我的公众号(芋艿的后端小屋)获得微信号,我们来一场,1 对 1 的搞基吧,不不不,是交流交流。
道友,帮我分享一波怎么样?
继续阅读
阅读原文