🙂🙂🙂關注微信公衆號:【芋艿的後端小屋】有福利: java
- RocketMQ / MyCAT / Sharding-JDBC 全部源碼分析文章列表
- RocketMQ / MyCAT / Sharding-JDBC 中文註釋源碼 GitHub 地址
- 您對於源碼的疑問每條留言都將獲得認真回覆。甚至不知道如何讀源碼也能夠請教噢。
- 新的源碼解析文章實時收到通知。每週更新一篇左右。
- 認真的源碼交流微信羣。
上篇文章《詞法解析》分享了詞法解析器Lexer是如何解析 SQL 裏的詞法。本文分享SQL解析引擎是如何解析與理解 SQL的。由於本文創建在《詞法解析》之上,你須要閱讀它後在開始這段旅程。🙂若是對詞法解析不徹底理解,請給個人公衆號(芋艿的後端小屋)留言,我會逐條認真耐心回覆。git
區別於 Lexer,Parser 理解SQL:github
Parser 有三個組件:sql
SQLParsingEngine 調用 StatementParser 解析 SQL。
StatementParser 調用 SQLParser 解析 SQL 表達式。
SQLParser 調用 Lexer 解析 SQL 詞法。數據庫
😜 是否是以爲 SQLParser 和 StatementParser 看起來很接近?下文爲你揭開這個答案。express
Sharding-JDBC 正在收集使用公司名單:傳送門。
🙂 你的登記,會讓更多人蔘與和使用 Sharding-JDBC。傳送門
Sharding-JDBC 也會所以,可以覆蓋更多的業務場景。傳送門
登記吧,騷年!傳送門後端
SQLParsingEngine,SQL 解析引擎。其 #parse()
方法做爲 SQL 解析入口,自己不帶複雜邏輯,經過調用 SQL 對應的 StatementParser 進行 SQL 解析。微信
核心代碼以下:app
// 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());
}複製代碼
SQLParser,SQL 解析器。和詞法解析器 Lexer 同樣,不一樣數據庫有不一樣的實現。函數
類圖以下(包含全部屬性和方法)(放大圖片):
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;
}複製代碼
咱們來看 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();
}複製代碼
這個類其它方法很重要,邏輯相對簡單,咱們就不佔用篇幅了。你們必定要看喲,後面調用很是很是多。AbstractParser.java 傳送門。👼也能夠關注個人公衆號(芋艿的後端小屋)發送關鍵字【sjdbc】獲取增長方法內註釋的項目地址。
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 塊公用解析。
SQLExpression,SQL表達式接口。目前 6 種實現:
類 | 說明 | 對應Token |
---|---|---|
SQLIdentifierExpression | 標識表達式 | Literals.IDENTIFIER |
SQLPropertyExpression | 屬性表達式 | 無 |
SQLNumberExpression | 數字表達式 | Literals.INT, Literals.HEX |
SQLPlaceholderExpression | 佔位符表達式 | Symbol.QUESTION |
SQLTextExpression | 字符表達式 | Literals.CHARS |
SQLIgnoreExpression | 分片中無需關注的SQL表達式 | 無 |
SELECT * FROM t_order o ORDER BY o.order_id
中的 o.order_id
。SQLPropertyExpression 從 SQLIdentifierExpression 進一步判斷解析而來。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();
}
}複製代碼
解析了 SQLExpression 有什麼用呢?咱們會在《查詢SQL解析》、《插入SQL解析》、《更新SQL解析》、《刪除SQL解析》。留個懸念😈,關注個人公衆號(芋艿的後端小屋),實時收到新文更新通知。
/** * 解析別名.不只僅是字段的別名,也能夠是表的別名。 * * @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();
}複製代碼
/** * 解析單表. * * @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);
}複製代碼
跳過表關聯詞法,支持 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;
}複製代碼
解析 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 查詢條件對路由有影響的條件。《路由》相關的邏輯,會單獨開文章介紹。這裏,咱們先留有映像。
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 進行解析。具體解析過程,另開文章分享。
不一樣 SQL 解析後,返回對應的 SQL 結果,即 Statement。大致結構以下:
Statement 包含兩部分信息:
分片上下文:用於 SQL 路由。
SQL 標記對象:用於 SQL 改寫。
咱們會在後文增刪改查SQL解析的過程當中分享到它們。
Parser | Statement | 分享文章 |
---|---|---|
SelectStatementParser | SelectStatement + AbstractSQLStatement | 《查詢SQL解析》 |
InsertStatementParser | InsertStatement | 《插入SQL解析》 |
UpdateStatementParser | UpdateStatement | 《更新SQL解析》 |
DeleteStatementParser | DeleteStatement | 《刪除SQL解析》 |
老鐵,是否是有丟丟長?
若是有地方錯誤,煩請指出🙂。
若是有地方不是很理解,能夠加個人公衆號(芋艿的後端小屋)留言,我會逐條認真耐心回覆。
若是以爲還湊合,勞駕分享朋友圈或者基佬。
《查詢SQL解析》已經寫了一半,預計很快...