Sharding-JDBC 原始碼分析 —— SQL 解析(二)之SQL解析
- 1. 概述
- 2. SQLParsingEngine
- 3. SQLParser SQL解析器
- 3.2.1 #parseExpression() 和 SQLExpression
- 3.2.2 #parseAlias()
- 3.2.3 #parseSingleTable()
- 3.2.4 #skipJoin()
- 3.2.5 #parseWhere()
- 3.1 AbstractParser
- 3.2 SQLParser
- 4. StatementParser SQL語句解析器
- 4.1 StatementParser
- 4.2 Statement
1. 概述
區別於 Lexer,Parser 理解SQL
- 提煉分片上下文
- 標記需要SQL改寫的部分
Parser 有三個元件:
- SQLParsingEngine :SQL 解析引擎
- SQLParser :SQL 解析器
- StatementParser :SQL語句解析器
SQLParsingEngine 呼叫 StatementParser 解析 SQL。 StatementParser 呼叫 SQLParser 解析 SQL 表示式。 SQLParser 呼叫 Lexer 解析 SQL 詞法。
? 是不是覺得 SQLParser 和 StatementParser 看起來很接近?下文為你揭開這個答案。
2. SQLParsingEngine
SQLParsingEngine,SQL 解析引擎。其 #parse()
方法作為 SQL 解析入口,本身不帶複雜邏輯,通過呼叫 SQL 對應的 StatementParser 進行 SQL 解析。
核心程式碼如下:
// SQLParsingEngine.java public SQLStatement parse() { // 獲取 SQL解析器 SQLParser sqlParser = getSQLParser(); // sqlParser.skipIfEqual(Symbol.SEMI); // 跳過 ";" if (sqlParser.equalAny(DefaultKeyword.WITH)) { // WITH Syntax skipWith(sqlParser); } // 獲取對應 SQL語句解析器 解析SQL if (sqlParser.equalAny(DefaultKeyword.SELECT)) { return SelectParserFactory.newInstance(sqlParser).parse(); } if (sqlParser.equalAny(DefaultKeyword.INSERT)) { return InsertParserFactory.newInstance(shardingRule, sqlParser).parse(); } if (sqlParser.equalAny(DefaultKeyword.UPDATE)) { return UpdateParserFactory.newInstance(sqlParser).parse(); } if (sqlParser.equalAny(DefaultKeyword.DELETE)) { return DeleteParserFactory.newInstance(sqlParser).parse(); } throw new SQLParsingUnsupportedException(sqlParser.getLexer().getCurrentToken().getType()); }
3. SQLParser SQL解析器
SQLParser,SQL 解析器。和詞法解析器 Lexer 一樣,不同資料庫有不同的實現。
類圖如下(包含所有屬性和方法)(放大圖片):
3.1 AbstractParser
AbstractParser,SQLParser 的抽象父類,對 Lexer 簡單封裝。例如:
-
#skipIfEqual()
:判斷當前詞法標記型別是否與其中一個傳入值相等 -
#equalAny()
:判斷當前詞法標記型別是否與其中一個傳入值相等
這裡有一點我們需要注意,SQLParser 並不是等 Lexer 解析完詞法( Token ),再根據詞法去理解 SQL。而是,在理解 SQL 的過程中,呼叫 Lexer 進行分詞。
// SQLParsingEngine.java#parse()片段
if (sqlParser.equalAny(DefaultKeyword.SELECT)) {
return SelectParserFactory.newInstance(sqlParser).parse();
}
// AbstractParser.java
public final boolean equalAny(final TokenType... tokenTypes) {
for (TokenType each : tokenTypes) {
if (each == lexer.getCurrentToken().getType()) {
return true;
}
}
return false;
}
- ↑↑↑ 判斷當前詞法是否為 SELECT。實際 AbstractParser 只知道當前詞法,並不知道後面還有哪些詞法,也不知道之前有哪些詞法。
我們來看 AbstractParser 裡比較複雜的方法 #skipParentheses()
幫助大家再理解下。請認真看程式碼註釋噢。
// AbstractParser.java
/**
* 跳過小括號內所有的詞法標記.
*
* @return 小括號內所有的詞法標記
*/
public final String skipParentheses() {
StringBuilder result = new StringBuilder("");
int count = 0;
if (Symbol.LEFT_PAREN == getLexer().getCurrentToken().getType()) {
final int beginPosition = getLexer().getCurrentToken().getEndPosition();
result.append(Symbol.LEFT_PAREN.getLiterals());
getLexer().nextToken();
while (true) {
if (equalAny(Symbol.QUESTION)) {
increaseParametersIndex();
}
// 到達結尾 或者 匹配合適數的)右括號
if (Assist.END == getLexer().getCurrentToken().getType() || (Symbol.RIGHT_PAREN == getLexer().getCurrentToken().getType() && 0 == count)) {
break;
}
// 處理裡面有多個括號的情況,例如:SELECT COUNT(DISTINCT(order_id) FROM t_order
if (Symbol.LEFT_PAREN == getLexer().getCurrentToken().getType()) {
count++;
} else if (Symbol.RIGHT_PAREN == getLexer().getCurrentToken().getType()) {
count--;
}
// 下一個詞法
getLexer().nextToken();
}
// 獲得括號內的內容
result.append(getLexer().getInput().substring(beginPosition, getLexer().getCurrentToken().getEndPosition()));
// 下一個詞法
getLexer().nextToken();
}
return result.toString();
}
3.2 SQLParser
SQLParser,SQL 解析器,主要提供只考慮 SQL 塊的解析方法,不考慮 SQL 上下文。下文即將提到的 StatementParser 將 SQL 拆成對應的塊,呼叫 SQLParser 進行解析。? 這麼說,可能會有些抽象,我們下面來一起看。
SQLParser 看起來方法特別多,合併下一共 5 種:
方法 |
說明 |
---|---|
#parseExpression() |
解析表示式 |
#parseAlias() |
解析別名 |
#parseSingleTable() |
解析單表 |
#skipJoin() |
跳過表關聯詞法 |
#parseWhere() |
解析查詢條件 |
看了這 5 個方法是否有點理解了?SQLParser 不考慮 SQL 是 SELECT / INSERT / UPDATE / DELETE ,它考慮的是,給我的是 WHERE 處解析查詢條件,或是 INSERT INTO 解析單表 等,提供 SELECT / INSERT / UPDATE / DELETE 需要的 SQL 塊公用解析。
3.2.1 #parseExpression() 和 SQLExpression
SQLExpression,SQL表示式介面。目前 6 種實現:
類 |
說明 |
對應Token |
---|---|---|
SQLIdentifierExpression |
標識表示式 |
Literals.IDENTIFIER |
SQLPropertyExpression |
屬性表示式 |
無 |
SQLNumberExpression |
數字表達式 |
Literals.INT, Literals.HEX |
SQLPlaceholderExpression |
佔位符表示式 |
Symbol.QUESTION |
SQLTextExpression |
字元表示式 |
Literals.CHARS |
SQLIgnoreExpression |
分片中無需關注的SQL表示式 |
無 |
- SQLPropertyExpression 例如:
SELECT*FROM t_order o ORDER BY o.order_id
中的o.order_id
。SQLPropertyExpression 從 SQLIdentifierExpression 進一步判斷解析而來。
- SQLIgnoreExpression 例如:
SELECT*FROM t_order o ORDER BY o.order_id%2
中的o.order_id%2
。複合表示式都會解析成 SQLIgnoreExpression。
解析 SQLExpression 核心程式碼如下:
// SQLParser.java
/**
* 解析表示式.
*
* @return 表示式
*/
// TODO 完善Expression解析的各種場景
public final SQLExpression parseExpression() {
// 解析表示式
String literals = getLexer().getCurrentToken().getLiterals();
final SQLExpression expression = getExpression(literals);
// SQLIdentifierExpression 需要特殊處理。考慮自定義函式,表名.屬性情況。
if (skipIfEqual(Literals.IDENTIFIER)) {
if (skipIfEqual(Symbol.DOT)) { // 例如,ORDER BY o.uid 中的 "o.uid"
String property = getLexer().getCurrentToken().getLiterals();
getLexer().nextToken();
return skipIfCompositeExpression() ? new SQLIgnoreExpression() : new SQLPropertyExpression(new SQLIdentifierExpression(literals), property);
}
if (equalAny(Symbol.LEFT_PAREN)) { // 例如,GROUP BY DATE(create_time) 中的 "DATE(create_time)"
skipParentheses();
skipRestCompositeExpression();
return new SQLIgnoreExpression();
}
return skipIfCompositeExpression() ? new SQLIgnoreExpression() : expression;
}
getLexer().nextToken();
return skipIfCompositeExpression() ? new SQLIgnoreExpression() : expression;
}
/**
* 獲得 詞法Token 對應的 SQLExpression
*
* @param literals 詞法字面量標記
* @return SQLExpression
*/
private SQLExpression getExpression(final String literals) {
if (equalAny(Symbol.QUESTION)) {
increaseParametersIndex();
return new SQLPlaceholderExpression(getParametersIndex() - 1);
}
if (equalAny(Literals.CHARS)) {
return new SQLTextExpression(literals);
}
// TODO 考慮long的情況
if (equalAny(Literals.INT)) {
return new SQLNumberExpression(Integer.parseInt(literals));
}
if (equalAny(Literals.FLOAT)) {
return new SQLNumberExpression(Double.parseDouble(literals));
}
// TODO 考慮long的情況
if (equalAny(Literals.HEX)) {
return new SQLNumberExpression(Integer.parseInt(literals, 16));
}
if (equalAny(Literals.IDENTIFIER)) {
return new SQLIdentifierExpression(SQLUtil.getExactlyValue(literals));
}
return new SQLIgnoreExpression();
}
/**
* 如果是 複合表示式,跳過。
*
* @return 是否跳過
*/
private boolean skipIfCompositeExpression() {
if (equalAny(Symbol.PLUS, Symbol.SUB, Symbol.STAR, Symbol.SLASH, Symbol.PERCENT, Symbol.AMP, Symbol.BAR, Symbol.DOUBLE_AMP, Symbol.DOUBLE_BAR, Symbol.CARET, Symbol.DOT, Symbol.LEFT_PAREN)) {
skipParentheses();
skipRestCompositeExpression();
return true;
}
return false;
}
/**
* 跳過剩餘複合表示式
*/
private void skipRestCompositeExpression() {
while (skipIfEqual(Symbol.PLUS, Symbol.SUB, Symbol.STAR, Symbol.SLASH, Symbol.PERCENT, Symbol.AMP, Symbol.BAR, Symbol.DOUBLE_AMP, Symbol.DOUBLE_BAR, Symbol.CARET, Symbol.DOT)) {
if (equalAny(Symbol.QUESTION)) {
increaseParametersIndex();
}
getLexer().nextToken();
skipParentheses();
}
}
3.2.2 #parseAlias()
/**
* 解析別名.不僅僅是欄位的別名,也可以是表的別名。
*
* @return 別名
*/
public Optional<String> parseAlias() {
// 解析帶 AS 情況
if (skipIfEqual(DefaultKeyword.AS)) {
if (equalAny(Symbol.values())) {
return Optional.absent();
}
String result = SQLUtil.getExactlyValue(getLexer().getCurrentToken().getLiterals());
getLexer().nextToken();
return Optional.of(result);
}
// 解析別名
// TODO 增加哪些資料庫識別哪些關鍵字作為別名的配置
if (equalAny(Literals.IDENTIFIER, Literals.CHARS, DefaultKeyword.USER, DefaultKeyword.END, DefaultKeyword.CASE, DefaultKeyword.KEY, DefaultKeyword.INTERVAL, DefaultKeyword.CONSTRAINT)) {
String result = SQLUtil.getExactlyValue(getLexer().getCurrentToken().getLiterals());
getLexer().nextToken();
return Optional.of(result);
}
return Optional.absent();
}
3.2.3 #parseSingleTable()
/**
* 解析單表.
*
* @param sqlStatement SQL語句物件
*/
public final void parseSingleTable(final SQLStatement sqlStatement) {
boolean hasParentheses = false;
if (skipIfEqual(Symbol.LEFT_PAREN)) {
if (equalAny(DefaultKeyword.SELECT)) { // multiple-update 或者 multiple-delete
throw new UnsupportedOperationException("Cannot support subquery");
}
hasParentheses = true;
}
Table table;
final int beginPosition = getLexer().getCurrentToken().getEndPosition() - getLexer().getCurrentToken().getLiterals().length();
String literals = getLexer().getCurrentToken().getLiterals();
getLexer().nextToken();
if (skipIfEqual(Symbol.DOT)) {
getLexer().nextToken();
if (hasParentheses) {
accept(Symbol.RIGHT_PAREN);
}
table = new Table(SQLUtil.getExactlyValue(literals), parseAlias());
} else {
if (hasParentheses) {
accept(Symbol.RIGHT_PAREN);
}
table = new Table(SQLUtil.getExactlyValue(literals), parseAlias());
}
if (skipJoin()) { // multiple-update 或者 multiple-delete
throw new UnsupportedOperationException("Cannot support Multiple-Table.");
}
sqlStatement.getSqlTokens().add(new TableToken(beginPosition, literals));
sqlStatement.getTables().add(table);
}
3.2.4 #skipJoin()
跳過表關聯詞法,支援 SELECT*FROM t_user,t_order WHERE...
, SELECT*FROM t_user JOIN t_order ON...
。下篇《查詢SQL解析》解析表會用到這個方法。
// SQLParser.java
/**
* 跳過表關聯詞法.
*
* @return 是否表關聯.
*/
public final boolean skipJoin() {
if (skipIfEqual(DefaultKeyword.LEFT, DefaultKeyword.RIGHT, DefaultKeyword.FULL)) {
skipIfEqual(DefaultKeyword.OUTER);
accept(DefaultKeyword.JOIN);
return true;
} else if (skipIfEqual(DefaultKeyword.INNER)) {
accept(DefaultKeyword.JOIN);
return true;
} else if (skipIfEqual(DefaultKeyword.JOIN, Symbol.COMMA, DefaultKeyword.STRAIGHT_JOIN)) {
return true;
} else if (skipIfEqual(DefaultKeyword.CROSS)) {
if (skipIfEqual(DefaultKeyword.JOIN, DefaultKeyword.APPLY)) {
return true;
}
} else if (skipIfEqual(DefaultKeyword.OUTER)) {
if (skipIfEqual(DefaultKeyword.APPLY)) {
return true;
}
}
return false;
}
3.2.5 #parseWhere()
解析 WHERE 查詢條件。目前支援 AND 條件,不支援 OR 條件。近期 OR 條件支援的可能性比較低。另外條件這塊對括號解析需要繼續優化,實際使用請勿寫冗餘的括號。例如: SELECT*FROM tbl_name1 WHERE((val1=?)AND(val2=?))AND val3=?
。
根據不同的運算操作符,分成如下情況:
運算子 |
附加條件 |
方法 |
---|---|---|
= |
#parseEqualCondition() |
|
IN |
#parseInCondition() |
|
BETWEEN |
#parseBetweenCondition() |
|
<, <=, >, >= |
Oracle 或 SQLServer 分頁 |
#parseRowNumberCondition() |
<, <=, >, >= |
#parseOtherCondition() |
|
LIKE |
parseOtherCondition |
程式碼如下:
// SQLParser.java
/**
* 解析所有查詢條件。
* 目前不支援 OR 條件。
*
* @param sqlStatement SQL
*/
private void parseConditions(final SQLStatement sqlStatement) {
// AND 查詢
do {
parseComparisonCondition(sqlStatement);
} while (skipIfEqual(DefaultKeyword.AND));
// 目前不支援 OR 條件
if (equalAny(DefaultKeyword.OR)) {
throw new SQLParsingUnsupportedException(getLexer().getCurrentToken().getType());
}
}
// TODO 解析組合expr
/**
* 解析單個查詢條件
*
* @param sqlStatement SQL
*/
public final void parseComparisonCondition(final SQLStatement sqlStatement) {
skipIfEqual(Symbol.LEFT_PAREN);
SQLExpression left = parseExpression(sqlStatement);
if (equalAny(Symbol.EQ)) {
parseEqualCondition(sqlStatement, left);
skipIfEqual(Symbol.RIGHT_PAREN);
return;
}
if (equalAny(DefaultKeyword.IN)) {
parseInCondition(sqlStatement, left);
skipIfEqual(Symbol.RIGHT_PAREN);
return;
}
if (equalAny(DefaultKeyword.BETWEEN)) {
parseBetweenCondition(sqlStatement, left);
skipIfEqual(Symbol.RIGHT_PAREN);
return;
}
if (equalAny(Symbol.LT, Symbol.GT, Symbol.LT_EQ, Symbol.GT_EQ)) {
if (left instanceof SQLIdentifierExpression && sqlStatement instanceof SelectStatement
&& isRowNumberCondition((SelectStatement) sqlStatement, ((SQLIdentifierExpression) left).getName())) {
parseRowNumberCondition((SelectStatement) sqlStatement);
} else if (left instanceof SQLPropertyExpression && sqlStatement instanceof SelectStatement
&& isRowNumberCondition((SelectStatement) sqlStatement, ((SQLPropertyExpression) left).getName())) {
parseRowNumberCondition((SelectStatement) sqlStatement);
} else {
parseOtherCondition(sqlStatement);
}
} else if (equalAny(DefaultKeyword.LIKE)) {
parseOtherCondition(sqlStatement);
}
skipIfEqual(Symbol.RIGHT_PAREN);
}
#parseComparisonCondition()
解析到 左SQL表示式(left)
和 運算子,呼叫相應方法進一步處理。我們選擇 #parseEqualCondition()
看下,其他方法有興趣跳轉 SQLParser 檢視。
// SQLParser.java
/**
* 解析 = 條件
*
* @param sqlStatement SQL
* @param left 左SQLExpression
*/
private void parseEqualCondition(final SQLStatement sqlStatement, final SQLExpression left) {
getLexer().nextToken();
SQLExpression right = parseExpression(sqlStatement);
// 新增列
// TODO 如果有多表,且找不到column是哪個表的,則不加入condition,以後需要解析binding table
if ((sqlStatement.getTables().isSingleTable() || left instanceof SQLPropertyExpression)
// 只有對路由結果有影響的才會新增到 conditions。SQLPropertyExpression 和 SQLIdentifierExpression 無法判斷,所以未加入 conditions
&& (right instanceof SQLNumberExpression || right instanceof SQLTextExpression || right instanceof SQLPlaceholderExpression)) {
Optional<Column> column = find(sqlStatement.getTables(), left);
if (column.isPresent()) {
sqlStatement.getConditions().add(new Condition(column.get(), right), shardingRule);
}
}
}
#parseEqualCondition()
解析到 右SQL表示式(right)
,並判斷 左右SQL表示式
與路由邏輯是否有影響,如果有,則加入到 Condition。這個就是 #parseWhere()
的目的:解析 WHERE 查詢條件對路由有影響的條件。《路由》相關的邏輯,會單獨開文章介紹。這裡,我們先留有映像。
4. StatementParser SQL語句解析器
4.1 StatementParser
StatementParser,SQL語句解析器。每種 SQL,都有相應的 SQL語句解析器實現。不同資料庫,繼承這些 SQL語句解析器,實現各自 SQL 上的差異。大體結構如下:
SQLParsingEngine 根據不同 SQL 呼叫對應工廠建立 StatementParser。核心程式碼如下:
public final class SelectParserFactory {
/**
* 建立Select語句解析器.
*
* @param sqlParser SQL解析器
* @return Select語句解析器
*/
public static AbstractSelectParser newInstance(final SQLParser sqlParser) {
if (sqlParser instanceof MySQLParser) {
return new MySQLSelectParser(sqlParser);
}
if (sqlParser instanceof OracleParser) {
return new OracleSelectParser(sqlParser);
}
if (sqlParser instanceof SQLServerParser) {
return new SQLServerSelectParser(sqlParser);
}
if (sqlParser instanceof PostgreSQLParser) {
return new PostgreSQLSelectParser(sqlParser);
}
throw new UnsupportedOperationException(String.format("Cannot support sqlParser class [%s].", sqlParser.getClass()));
}
}
呼叫 StatementParser#parse()
實現方法,對 SQL 進行解析。具體解析過程,另開文章分享。
4.2 Statement
不同 SQL 解析後,返回對應的 SQL 結果,即 Statement。大體結構如下:
Statement 包含兩部分資訊:
- 分片上下文:用於 SQL 路由。
- SQL 標記物件:用於 SQL 改寫。
我們會在後文增刪改查SQL解析的過程中分享到它們。
4.3 預告
Parser |
Statement |
分享文章 |
---|---|---|
SelectStatementParser |
SelectStatement + AbstractSQLStatement |
《查詢SQL解析》 |
InsertStatementParser |
InsertStatement |
《插入SQL解析》 |
UpdateStatementParser |
UpdateStatement |
《更新SQL解析》 |
DeleteStatementParser |
DeleteStatement |
《刪除SQL解析》 |