1. 程式人生 > >sharding jdbc之解析引擎

sharding jdbc之解析引擎

圖片 分詞器 span DPoS soft name parsing nchar shu

1. 解析引擎

解析過程分為詞法解析語法解析。 解析引擎在 parsing 包下,包含兩大組件:

  1. Lexer:詞法解析器。
  2. 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 endPositionliterals 在 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

sharding jdbc之解析引擎