sharding jdbc之解析引擎
1. 解析引擎
解析過程分為 詞法解析 和 語法解析 。 解析引擎在 parsing
包下,包含兩大元件:
- Lexer:詞法解析器。
- Parser:SQL解析器。
詞法解析器用於將 SQL 拆解為不可再分的原子符號,稱為 Token 。並根據不同資料庫方言所提供的字典,將其歸類為關鍵字,表示式,字面量和操作符。 再使用語法解析器將 SQL 轉換為抽象語法樹。例如:
SELECT id, name FROM t_user WHERE status = 'ACTIVE' AND age > 18
解析成的抽象語法樹如:
兩者都是解析器,區別在於 Lexer 只做詞法的解析,不關注上下文,將字串拆解成 N 個分詞。而 Parser 在 Lexer 的基礎上,進一步理解 SQL表示的行為 。
1.1 Lexer 詞法解析器
作用 :順序 解析 SQL,將sql字串分解成 N 個分詞(token)。那麼每個分詞該如何表示呢?
1.1.1 token 和 tokenType
token用於描述當前分解出的詞法,包含3個屬性:
- TokenType type :詞法標記型別
- String literals :當前詞法字面量
- int endPosition : literals 在 SQL 字串中的位置
TokenType 用於描述當前token的型別,分成 4 大類:
- DefaultKeyword :詞法關鍵詞
- Literals :詞法字面量標記
- Symbol :詞法符號標記
- Assist :詞法輔助標記
1.1.2 詞法解析器
由於不同資料庫遵守的 SQL 規範有所不同,所以不同的資料庫對應存在不同的 Lexer,維護了對應的dictionary。Lexer內部根據相應資料庫的dictionary與sql語句生成一個Tokenizer分詞器進行分詞。
public final class Tokenizer { //輸入 private final String input; //字典 private final Dictionary dictionary; //偏移量 private final int offset; }
分詞器具體的api如下:
方法名 | 說明 |
---|---|
int skipWhitespace() | 跳過所有的空格 返回最後的偏移量 |
int skipComment() | 跳過註釋,並返回最終的偏移量 |
Token scanVariable() | 獲取變數,返回分詞Token |
Token scanIdentifier() | 返回關鍵詞分詞 |
Token scanHexDecimal() | 掃描16進位制返回分詞 |
Token scanNumber() | 返回數字分詞 |
Token scanChars() | 返回字串分詞 |
Token scanSymbol() | 返回詞法符號標記分詞 |
所有的分詞結果都是按照TokenType進行標記返回Token,不同的分詞型別,有不同的分詞方法去處理並返回。
核心程式碼如下:
// Lexer.java public final void nextToken() { skipIgnoredToken(); if (isVariableBegin()) { currentToken = new Tokenizer(input, dictionary, offset).scanVariable(); } else if (isNCharBegin()) { currentToken = new Tokenizer(input, dictionary, ++offset).scanChars(); } else if (isIdentifierBegin()) { currentToken = new Tokenizer(input, dictionary, offset).scanIdentifier(); } else if (isHexDecimalBegin()) { currentToken = new Tokenizer(input, dictionary, offset).scanHexDecimal(); } else if (isNumberBegin()) { currentToken = new Tokenizer(input, dictionary, offset).scanNumber(); } else if (isSymbolBegin()) { currentToken = new Tokenizer(input, dictionary, offset).scanSymbol(); } else if (isCharsBegin()) { currentToken = new Tokenizer(input, dictionary, offset).scanChars(); } else if (isEnd()) { currentToken = new Token(Assist.END, "", offset); } else { throw new SQLParsingException(this, Assist.ERROR); } offset = currentToken.getEndPosition(); System.out.println(currentToken.getLiterals() + " | " + currentToken.getType() + " | " + currentToken.getEndPosition() + " |"); }
類繼承圖:
總結:Lexer通過 nextToken()
方法,不斷解析出當前 Token。 Lexer的nextToken()
方法裡,使用 skipIgnoredToken()
方法跳過忽略的 Token,通過 isXxx()
方法判斷好下一個 Token 的型別後,交給 Tokenizer 進行分詞並返回 Token。
1.2 SQLParser 語法解析器
語法解析器的作用是根據不同型別的sql語句在詞法解析器的基礎上,由不同型別的語法解析器解析成SQLStatement,具體語法解析類結構如圖:
可以看到,不同型別的sql,不同廠商的資料庫,存在不同的處理解析器去解析,解析完成之後,會將SQL解析成SQLStatement。
SQLParsingEngine,SQL 解析引擎。其 parse()
方法作為 SQL 解析入口,本身不帶複雜邏輯,通過呼叫對應的 SQLParser 進行 SQL 解析,返回SQLStatement。
@RequiredArgsConstructor public final class SQLParsingEngine { private final DatabaseType dbType; private final String sql; private final ShardingRule shardingRule; private final ShardingTableMetaData shardingTableMetaData; /** * Parse SQL. * * @param useCache use cache or not * @return parsed SQL statement */ public SQLStatement parse(final boolean useCache) { Optional<SQLStatement> cachedSQLStatement = getSQLStatementFromCache(useCache); if (cachedSQLStatement.isPresent()) { return cachedSQLStatement.get(); } LexerEngine lexerEngine = LexerEngineFactory.newInstance(dbType, sql); lexerEngine.nextToken(); SQLStatement result = SQLParserFactory.newInstance(dbType, lexerEngine.getCurrentToken().getType(), shardingRule, lexerEngine, shardingTableMetaData).parse(); if (useCache) { ParsingResultCache.getInstance().put(sql, result); } return result; } }
SQLStatement物件是個超類,具體實現類有很多。按照不同的語句,解析成不同的SQLStatement。
sql語句解析的過程如下圖:
參考:
http://www.iocoder.cn/categories/Sharding-JDBC/
https://www.jianshu.com/u/c6408f5e4b0e