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; //偏移量 privatefinal int offset; }
分詞器具體的api如下:
方法名 | 說明 |
---|---|
int skipWhitespace() | 跳過所有的空格 返回最後的偏移量 |
int skipComment() | 跳過註釋,並返回最終的偏移量 |
Token scanVariable() | 獲取變量,返回分詞Token |
Token scanIdentifier() | 返回關鍵詞分詞 |
Token scanHexDecimal() | 掃描16進制返回分詞 |
Token scanNumber() | 返回數字分詞 |
Token scanChars() | 返回字符串分詞 |
Token scanSymbol() | 返回詞法符號標記分詞 |
核心代碼如下:
// 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
sharding jdbc之解析引擎