資料庫中介軟體 Sharding-JDBC 原始碼分析 —— SQL 解析(三)之查詢SQL
������關注微信公眾號:【芋艿的後端小屋】有福利:
1. RocketMQ / MyCAT / Sharding-JDBC 所有原始碼分析文章列表
2. RocketMQ / MyCAT / Sharding-JDBC 中文註釋原始碼 GitHub 地址
3. 您對於原始碼的疑問每條留言都將得到認真回覆。甚至不知道如何讀原始碼也可以請教噢。
4. 新的原始碼解析文章實時收到通知。每週更新一篇左右。
5. 認真的原始碼交流微信群。
1. 概述
本文前置閱讀:
本文分享插入SQL解析的原始碼實現。
由於每個資料庫在遵守 SQL 語法規範的同時,又有各自獨特的語法。因此,在 Sharding-JDBC 裡每個資料庫都有自己的 SELECT 語句的解析器實現方式,當然絕大部分邏輯是相同的。本文主要分享筆者最常用的 MySQL 查詢
查詢 SQL 解析主流程如下:
// AbstractSelectParser.java
public final SelectStatement 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
public final class SelectStatement extends AbstractSQLStatement {
/**
* 是否行 DISTINCT / DISTINCTROW / UNION
*/
private boolean distinct;
/**
* 是否查詢所有欄位,即 SELECT *
*/
private boolean containStar;
/**
* 最後一個查詢項下一個 Token 的開始位置
*
* @see #items
*/
private int selectListLastPosition;
/**
* 最後一個分組項下一個 Token 的開始位置
*/
private int groupByLastPosition;
/**
* 查詢項
*/
private final List<SelectItem> items = new LinkedList<>();
/**
* 分組項
*/
private final List<OrderItem> groupByItems = new LinkedList<>();
/**
* 排序項
*/
private final List<OrderItem> orderByItems = new LinkedList<>();
/**
* 分頁
*/
private Limit limit;
}
我們對屬性按照型別進行歸類:
- 特殊
- distinct
- 查詢欄位
- containStar
- items
- selectListLastPosition
- 分組條件
- groupByItems
- groupByLastPosition
- 排序條件
- orderByItems
- 分頁條件
- limit
2.1 AbstractSQLStatement
增刪改查解析結果物件的抽象父類。
public abstract class AbstractSQLStatement implements SQLStatement {
/**
* SQL 型別
*/
private final SQLType type;
/**
* 表
*/
private final Tables tables = new Tables();
/**
* 過濾條件。
* 只有對路由結果有影響的條件,才新增進陣列
*/
private final Conditions conditions = new Conditions();
/**
* SQL標記物件
*/
private final List<SQLToken> sqlTokens = new LinkedList<>();
}
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
public void 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)) {
throw new SQLParsingUnsupportedException(getSqlParser().getLexer().getCurrentToken().getType());
}
queryRest();
}
3.1 #parseDistinct()
解析 DISTINCT、DISTINCTROW、UNION 謂語。
核心程式碼:
// AbstractSelectParser.java
protected final void 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();
}
} else if (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 in set (0.03 sec)
mysql> SELECT DISTINCT item_id, order_id FROM t_order_item;
+---------+----------+
| item_id | order_id |
+---------+----------+
| 1 | 1 |
+---------+----------+
1 rows in set (0.02 sec)
3.2 #parseSelectList()
SELECT | o.user_id | COUNT(DISTINCT i.item_id) AS item_count | MAX(i.item_id) | FROM |
---|---|---|---|---|
SelectItem | SelectItem | SelectItem |
將 SQL 查詢欄位 按照逗號( , )切割成多個選擇項( SelectItem)。核心程式碼如下:
// AbstractSelectParser.java
protected final void 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
private void 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(new CommonSelectItem(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(new AggregationSelectItem(AggregationType.valueOf(literals.toUpperCase()), sqlParser.skipParentheses(), sqlParser.parseAlias()));
return;
}
// 第三種情況,非 * 通用選擇項
StringBuilder expression = new StringBuilder();
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(new TableToken(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(
new CommonSelectItem(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(new CommonSelectItem(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:
- 第三種:非
*
通用選擇項:
例如,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:
3.2.2 #parseAlias() 解析別名
3.2.3 TableToken 表標記物件
TableToken,記錄表名在 SQL 裡出現的位置和名字。
public final class TableToken implements SQLToken {
/**
* 開始位置
*/
private final int beginPosition;
/**
* 表示式
*/
private final String originalLiterals;
/**
* 獲取表名稱.
* @return 表名稱
*/
public String getTableName() {
return SQLUtil.getExactlyValue(originalLiterals);
}
}
例如上文第三種情況。
3.3 #skipToFrom()
/**
* 跳到 FROM 處
*/
private void 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
/**
* 解析所有表名和表別名
*/
public final void parseFrom() {
if (sqlParser.skipIfEqual(DefaultKeyword.FROM)) {
parseTable();
}
}
/**
* 解析所有表名和表別名
*/
public void parseTable() {
// 解析子查詢
if (sqlParser.skipIfEqual(Symbol.LEFT_PAREN)) {
if (!selectStatement.getTables().isEmpty()) {
throw new UnsupportedOperationException("Cannot support subquery for nested tables.");
}
selectStatement.setContainStar(false);
sqlParser.skipUselessParentheses(); // 去掉子查詢左括號
parse(); // 解析子查詢 SQL
sqlParser.skipUselessParentheses(); // 去掉子查詢右括號
//
if (!selectStatement.getTables().isEmpty()) {
return;
}
}
parseTableFactor(); // 解析當前表
parseJoinTable(); // 解析下一個表
}
/**
* 解析單個表名和表別名
*/
protected final void 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(new TableToken(beginPosition, literals));
// 表 以及 表別名
selectStatement.getTables().add(new Table(SQLUtil.getExactlyValue(literals), sqlParser.parseAlias()));
}
/**
* 解析 Join Table 或者 FROM 下一張 Table
*/
protected void 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));
} else if (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 開始位置
*/
private void parseTableCondition(final int startPosition) {
SQLExpression sqlExpression = sqlParser.parseExpression();
if (!(sqlExpression instanceof SQLPropertyExpression)) {
return;
}
SQLPropertyExpression sqlPropertyExpression = (SQLPropertyExpression) sqlExpression;
if (selectStatement.getTables().getTableNames().contains(SQLUtil.getExactlyValue(sqlPropertyExpression.getOwner().getName()))) {
selectStatement.getSqlTokens().add(new TableToken(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()) {
throw new UnsupportedOperationException("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
public final class Tables {
private final List<Table> tables = new ArrayList<>();
}
// Table.java
public final class Table {
/**
* 表
*/
private final String name;
/**
* 別名
*/
private final Optional<String> alias;
}
// AbstractSelectParser.java#parseTableFactor()片段
selectStatement.getTables().add(new Table(SQLUtil.getExactlyValue(literals), sqlParser.parseAlias()));
3.5 #parseWhere()
3.6 #parseGroupBy()
解析分組條件,實現上比較類似 #parseSelectList
,會更加簡單一些。
// AbstractSelectParser.java
/**
* 解析 Group By 和 Having(暫時不支援)
*/
protected void parseGroupBy() {
if (sqlParser.skipIfEqual(DefaultKeyword.GROUP)) {
sqlParser.accept(DefaultKeyword.BY);
// 解析 Group By 每個欄位
while (true) {
addGroupByItem(sqlParser.parseExpression(selectStatement));
if (!sqlParser.equalAny(Symbol.COMMA)) {
break;
}
sqlParser.getLexer().nextToken();
}
while (sqlParser.equalAny(DefaultKeyword.WITH) || sqlParser.getLexer().getCurrentToken().getLiterals().equalsIgnoreCase("ROLLUP")) {
sqlParser.getLexer().nextToken();
}
// Having(暫時不支援)
if (sqlParser.skipIfEqual(DefaultKeyword.HAVING)) {
throw new UnsupportedOperationException("Cannot support Having");
}
selectStatement.setGroupByLastPosition(sqlParser.getLexer().getCurrentToken().getEndPosition());
} else if (sqlParser.skipIfEqual(DefaultKeyword.HAVING)) {
throw new UnsupportedOperationException("Cannot support Having");
}
}
/**
* 解析 Group By 單個欄位
* Group By 條件是帶有排序功能,預設ASC
*
* @param sqlExpression 表示式
*/
protected final void addGroupByItem(final SQLExpression sqlExpression) {
// Group By 欄位 DESC / ASC / ;預設是 ASC。
OrderType orderByType = OrderType.ASC;
if (sqlParser.equalAny(DefaultKeyword.ASC)) {
sqlParser.getLexer().nextToken();
} else if (sqlParser.skipIfEqual(DefaultKeyword.DESC)) {
orderByType = OrderType.DESC;
}
// 解析 OrderItem
OrderItem orderItem;
if (sqlExpression instanceof SQLPropertyExpression) {
SQLPropertyExpression sqlPropertyExpression = (SQLPropertyExpression) sqlExpression;
orderItem = new OrderItem(SQLUtil.getExactlyValue(sqlPropertyExpression.getOwner().getName()), SQLUtil.getExactlyValue(sqlPropertyExpression.getName()), orderByType,
getAlias(SQLUtil.getExactlyValue(sqlPropertyExpression.getOwner() + "." + SQLUtil.getExactlyValue(sqlPropertyExpression.getName()))));
} else if (sqlExpression instanceof SQLIdentifierExpression) {
SQLIdentifierExpression sqlIdentifierExpression = (SQLIdentifierExpression) sqlExpression;
orderItem = new OrderItem(SQLUtil.getExactlyValue(sqlIdentifierExpression.getName()), orderByType, getAlias(SQLUtil.getExactlyValue(sqlIdentifierExpression.getName())));
} else {
return;
}
selectStatement.getGroupByItems().add(orderItem);
}
/**
* 欄位在查詢項裡的別名
*
* @param name 欄位
* @return 別名
*/
private Optional<String> getAlias(final String name) {
if (selectStatement.isContainStar()) {
return Optional.absent();
}
String rawName = SQLUtil.getExactlyValue(name);
for (SelectItem each : selectStatement.getItems()) {
if (rawName.equalsIgnoreCase(SQLUtil.getExactlyValue(each.getExpression()))) {
return each.getAlias();
}
if (rawName.equalsIgnoreCase(each.getAlias().orNull())) {
return Optional.of(rawName);
}
}
return Optional.absent();
}
3.6.1 OrderItem 排序項
屬於分片上下文資訊
public final class OrderItem {
/**
* 所屬表別名
*/
private final Optional<String> owner;
/**
* 排序欄位
*/
private final Optional<String> name;
/**
* 排序型別
*/
private final OrderType type;
/**
* 按照第幾個查詢欄位排序
* ORDER BY 數字 的 數字代表的是第幾個欄位
*/
@Setter
private int index = -1;
/**
* 欄位在查詢項({@link com.dangdang.ddframe.rdb.sharding.parsing.parser.context.selectitem.SelectItem} 的別名
*/
@Setter
private Optional<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
public final class Limit {
/**
* 是否重寫rowCount
* TODO 待補充:預計和記憶體分頁合併有關
*/
private final boolean rowCountRewriteFlag;
/**
* offset
*/
private LimitValue offset;
/**
* row
*/
private LimitValue rowCount;
}
// LimitValue.java
public final class LimitValue {
/**
* 值
* 當 value == -1 時,為佔位符
*/
private int value;
/**
* 第幾個佔位符
*/
private int index;
}
3.8.2 OffsetToken RowCountToken
- OffsetToken:分頁偏移量標記物件
- RowCountToken:分頁長度標記物件
只有在對應位置非佔位符才有該 SQLToken。
// OffsetToken.java
public final class OffsetToken implements SQLToken {
/**
* SQL 所在開始位置
*/
private final int beginPosition;
/**
* 偏移值
*/
private final int offset;
}
// RowCountToken.java
public final class RowCountToken implements SQLToken {
/**
* SQL 所在開始位置
*/
private final int beginPosition;
/**
* 行數
*/
private final int rowCount;
}
3.9 #queryRest()
// AbstractSelectParser.java
protected void queryRest() {
if (sqlParser.equalAny(DefaultKeyword.UNION, DefaultKeyword.EXCEPT, DefaultKeyword.INTERSECT, DefaultKeyword.MINUS)) {
throw new SQLParsingUnsupportedException(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 選擇項標記物件
*/
private void appendAvgDerivedColumns(final ItemsToken itemsToken) {
int derivedColumnOffset = 0;
for (SelectItem each : selectStatement.getItems()) {
if (!(each instanceof AggregationSelectItem) || AggregationType.AVG != ((AggregationSelectItem) each).getType()) {
continue;
}
AggregationSelectItem avgItem = (AggregationSelectItem) each;
// COUNT 欄位
String countAlias = String.format(DERIVED_COUNT_ALIAS, derivedColumnOffset);
AggregationSelectItem countItem = new AggregationSelectItem(AggregationType.COUNT, avgItem.getInnerExpression(), Optional.of(countAlias));
// SUM 欄位
String sumAlias = String.format(DERIVED_SUM_ALIAS, derivedColumnOffset);
AggregationSelectItem sumItem = new AggregationSelectItem(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 別名模式
*/
private void appendDerivedOrderColumns(final ItemsToken itemsToken, final List<OrderItem> orderItems, final String aliasPattern) {
int derivedColumnOffset = 0;
for (OrderItem each : orderItems) {
if (!isContainsItem(each)) {
String alias = String.format(aliasPattern, derivedColumnOffset++);
each.setAlias(Optional.of(alias));
itemsToken.getItems().add(each.getQualifiedName().get() + " AS " + alias + " ");
}
}
}
/**
* 查詢欄位是否包含排序欄位
*
* @param orderItem 排序欄位
* @return 是否
*/
private boolean isContainsItem(final OrderItem orderItem) {
if (selectStatement.isContainStar()) { // SELECT *
return true;
}
for (SelectItem each : selectStatement.getItems()) {
if (-1 != orderItem.getIndex()) { // ORDER BY 使用數字
return true;
}
if (each.getAlias().isPresent() && orderItem.getAlias().isPresent() && each.getAlias().get().equalsIgnoreCase(orderItem.getAlias().get())) { // 欄位別名比較
return true;
}
if (!each.getAlias().isPresent() && orderItem.getQualifiedName().isPresent() && each.getExpression().equalsIgnoreCase(orderItem.getQualifiedName().get())) { // 欄位原名比較
return true;
}
}
return false;
}
4.3 ItemsToken
選擇項標記物件,屬於分片上下文資訊,目前有 3 個情況會建立:
AVG
查詢額外 COUNT 和 SUM:#appendAvgDerivedColumns()
GROUP BY
不在 查詢欄位,額外查詢該欄位 :#appendDerivedOrderColumns()
ORDER BY
不在 查詢欄位,額外查詢該欄位 :#appendDerivedOrderColumns()
public final class ItemsToken implements SQLToken {
/**
* SQL 開始位置
*/
private final int beginPosition;
/**
* 欄位名陣列
*/
private final List<String> items = new LinkedList<>();
}
4.4 appendDerivedOrderBy()
當 SQL 有聚合條件而無排序條件,根據聚合條件進行排序。這是資料庫自己的執行規則。
mysql> SELECT order_id FROM t_order GROUP BY order_id;
+----------+
| order_id |
+----------+
| 1 |
| 2 |
| 3 |
+----------+
3 rows in set (0.05 sec)
mysql> SELECT order_id FROM t_order GROUP BY order_id DESC;
+----------+
| order_id |
+----------+
| 3 |
| 2 |
| 1 |
+----------+
3 rows in set (0.02 sec)
// AbstractSelectParser.java
/**
* 當無 Order By 條件時,使用 Group By 作為排序條件(資料庫本身規則)
*/
private void appendDerivedOrderBy() {
if (!getSelectStatement().getGroupByItems().isEmpty() && getSelectStatement().getOrderByItems().isEmpty()) {
getSelectStatement().getOrderByItems().addAll(getSelectStatement().getGroupByItems());
getSelectStatement().getSqlTokens().add(new OrderByToken(getSelectStatement().getGroupByLastPosition()));
}
}
4.3.1 OrderByToken
排序標記物件。當無 Order By 條件時,使用 Group By 作為排序條件(資料庫本身規則)。
// OrderByToken.java
public final class OrderByToken implements SQLToken {
/**
* SQL 所在開始位置
*/
private final int beginPosition;
}
666. 彩蛋
咳咳咳,確實有一些略長。但請相信,INSERT / UPDATE / DELETE 會簡單很多很多。考試考的 SQL 最多的是什麼?SELECT 語句呀!為啥,難唄。恩,我相信看到此處的你,一定是能看懂的,加油!
��如果對本文有不理解的地方,可以關注我的公眾號獲得微訊號,我們來一場,1 對 1 的搞基吧,不不不,是交流交流。
道友,幫我分享一波怎麼樣?
相關推薦
資料庫中介軟體 Sharding-JDBC 原始碼分析 —— SQL 解析(三)之查詢SQL
������關注微信公眾號:【芋艿的後端小屋】有福利: 1. RocketMQ / MyCAT / Sharding-JDBC 所有原始碼分析文章列表 2. RocketMQ / MyCAT / Sharding-JDBC 中文註釋原始碼
資料庫中介軟體 Sharding-JDBC 原始碼分析 —— SQL 解析(一)之詞法解析
本文主要基於 Sharding-JDBC 1.5.0 正式版 ������關注微信公眾號:【芋道原始碼】有福利: 1. RocketMQ / MyCAT / Sharding-JDBC 所有原始碼分析文章列表 2. Roc
資料庫中介軟體 Sharding-JDBC 原始碼分析 —— SQL 解析(六)之刪除SQL
本文主要基於 Sharding-JDBC 1.5.0 正式版 ������關注微信公眾號:【芋道原始碼】有福利: 1. RocketMQ / MyCAT / Sharding-JDBC 所有原始碼分析文章列表 2. Roc
資料庫分庫分表中介軟體 Sharding-JDBC 原始碼分析 —— 分散式主鍵
������關注微信公眾號:【芋道原始碼】有福利: 1. RocketMQ / MyCAT / Sharding-JDBC 所有原始碼分析文章列表 2. RocketMQ / MyCAT / Sharding-JDBC 中文註釋原始碼 Gi
數據庫分庫分表中間件 Sharding-JDBC 源碼分析 —— SQL 解析(六)之刪除SQL
java 後端 架構 數據庫 中間件關註微信公眾號:【芋道源碼】有福利:RocketMQ / MyCAT / Sharding-JDBC 所有源碼分析文章列表RocketMQ / MyCAT / Sharding-JDBC 中文註釋源碼 GitHub 地址您對於源碼的疑問每條留言都將得到認真回復。甚至不知道如
精盡Spring MVC原始碼分析 - HandlerMapping 元件(三)之 AbstractHandlerMethodMapping
> 該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 [Spring MVC 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring
精盡Spring MVC原始碼分析 - HandlerAdapter 元件(三)之 HandlerMethodArgumentResolver
> 該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 [Spring MVC 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring
數據庫中間件 Sharding-JDBC 源碼分析 —— SQL 解析(一)之語法解析
sharding-jdbc關註微信公眾號:【芋艿的後端小屋】有福利:RocketMQ / MyCAT / Sharding-JDBC 所有源碼分析文章列表RocketMQ / MyCAT / Sharding-JDBC 中文註釋源碼 GitHub 地址您對於源碼的疑問每條留言都將得到認真回復。甚至不知道如何讀
redis原始碼分析與思考(三)——字典中鍵的兩種hash演算法
在Redis字典中,得到鍵的hash值顯得尤為重要,因為這個不僅關乎到是否字典能做到負載均衡,以及在效能上優勢是否突出,一個良好的hash演算法在此時就能發揮出巨大的作用。而一個良好的has
Memcached原始碼分析-命令解析(3)
#1 流程圖 2 流程說明 1 當進入到conn_read狀態後,會呼叫try_read_network(),將socket資料讀取到conn的rbuf中。 例如:char *rbuf = ‘set key 0 0 4\r\nget name\r\n’。 2
Zebra-VTYSH原始碼分析和改造(三):新增定製命令
一 檢視介紹 由上面幾篇文章分析可見,所有的命令都是包含在node中的,根據Cisco或者H3常見路由器或者交換機的CLI格式可見,一個node就對應著一個檢視(View)。常用的檢視包括:普通檢視,管理檢視,檔案系統檢視,配置檢視,以及介面配置檢視和VLAN檢視等。
Spring 原始碼分析(四) ——MVC(三)原始碼入口
測試程式碼 框架配置 現在就從 SpringMVC 的網站開發開始。當然,最先是用 Mavan 下載所需的 jar。下面是 pom.xml 配置: <project xmlns="http://maven.apache.org/
Netty原始碼分析--建立Channel(三)
先看一下我Netty的啟動類 private void start() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1);
精盡Spring MVC原始碼分析 - HandlerMapping 元件(一)之 AbstractHandlerMapping
> 該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 [Spring MVC 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring
精盡Spring MVC原始碼分析 - HandlerMapping 元件(二)之 HandlerInterceptor 攔截器
> 該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 [Spring MVC 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring
精盡Spring MVC原始碼分析 - HandlerMapping 元件(四)之 AbstractUrlHandlerMapping
> 該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 [Spring MVC 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring
精盡Spring MVC原始碼分析 - HandlerAdapter 元件(一)之 HandlerAdapter
> 該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 [Spring MVC 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring
精盡Spring MVC原始碼分析 - HandlerAdapter 元件(二)之 ServletInvocableHandlerMethod
> 該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 [Spring MVC 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring
精盡Spring MVC原始碼分析 - HandlerAdapter 元件(四)之 HandlerMethodReturnValueHandler
> 該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 [Spring MVC 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring
精盡Spring MVC原始碼分析 - HandlerAdapter 元件(五)之 HttpMessageConverter
> 該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 [Spring MVC 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring