歡迎光臨
每天分享高質量文章

【死磕Sharding-jdbc】—SQL解析-INSERT解析

點選上方“Java技術驛站”,選擇“置頂公眾號”。

有內涵、有價值的文章第一時間送達!

精品專欄

 

INSERT語法

分析insert解析之前,首先看一下mysql官方對insert語法的定義,因為SQL解析跟語法息息相關:

  1. INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]

  2.    [INTO] tbl_name

  3.    [PARTITION (partition_name [, partition_name] ...)]

  4.    [(col_name [, col_name] ...)]

  5.    {VALUES | VALUE} (value_list) [, (value_list)] ...

  6.    [ON DUPLICATE KEY UPDATE assignment_list]

  7. INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]

  8.    [INTO] tbl_name

  9.    [PARTITION (partition_name [, partition_name] ...)]

  10.    SET assignment_list

  11.    [ON DUPLICATE KEY UPDATE assignment_list]

  12. INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]

  13.    [INTO] tbl_name

  14.    [PARTITION (partition_name [, partition_name] ...)]

  15.    [(col_name [, col_name] ...)]

  16.    SELECT ...

  17.    [ON DUPLICATE KEY UPDATE assignment_list]

摘自https://dev.mysql.com/doc/refman/8.0/en/insert.html

INSERT解析

接下來分析sharding-jdbc是如何解析insert型別的SQL陳述句的,透過 SQLStatementresult=sqlParser.parse();得到SQL解析器後,執行AbstractInsertParserparse()方法解析insert sql,核心原始碼如下:

  1. @Override

  2. public final DMLStatement parse() {

  3.    lexerEngine.nextToken();

  4.    InsertStatement result = new InsertStatement();

  5.    insertClauseParserFacade.getInsertIntoClauseParser().parse(result);

  6.    insertClauseParserFacade.getInsertColumnsClauseParser().parse(result);

  7.    if (lexerEngine.equalAny(DefaultKeyword.SELECT, Symbol.LEFT_PAREN)) {

  8.        throw new UnsupportedOperationException("Cannot INSERT SELECT");

  9.    }

  10.    insertClauseParserFacade.getInsertValuesClauseParser().parse(result);

  11.    insertClauseParserFacade.getInsertSetClauseParser().parse(result);

  12.    appendGenerateKey(result);

  13.    return result;

  14. }

對應的泳道圖如下所示:

INSERT解析泳道圖

第1步-lexerEngine.nextToken()

由parse()原始碼可知,insert解析第1步就是呼叫 lexerEngine.nextToken(),nextToken()在之前的文章已經分析過(戳連結),即跳到下一個token,由於任意SQL解析都會在SQLParsingEngine中呼叫lexerEngine.nextToken(),這裡再呼叫lexerEngine.nextToken(),所以總計已經跳過兩個token。

為什麼要一開始就呼叫nextToken()呢?回到insert的語法: INSERT[LOW_PRIORITY|DELAYED|HIGH_PRIORITY][IGNORE][INTO]tbl_name~~~INSERT INTO一定會有, LOWPRIORITY,DELAYED ,HIGHPRIORITY和IGNORE幾個關鍵詞可選,即在表名之前,至少有兩個token。所以跳過兩個token後再進行下一步操作(SQLParsingEngine.parse()中呼叫了一次nextToken(),這裡再呼叫一次nextToken());

第2步-InsertIntoClauseParser.parse(result)

由parse()原始碼可知,insert解析第2步就是呼叫 insertClauseParserFacade.getInsertIntoClauseParser().parse(result);,即解析insert into後面的表名,封裝到InsertStatementtable屬性中,核心原始碼如下所示:

  1. 檢查是否有不支援的關鍵詞(跳過兩個token後,只有Oracle有兩個不支援的關鍵詞:ALL和FIRST);

  2. 跳到INTO(因為INSERT和INTO之間還有其他關鍵詞,在此之前即使呼叫了兩次nextToken(),當前token也不一定就是INTO);

  3. 獲取下一個token即表名(根據insert語法可知,INTO後肯定是表名。表名token值:Token(type=IDENTIFIER, literals= t_user, endPosition=27));

  4. 解析表名賦值給InsertStatement

  5. 如果表名和VALUES關鍵詞之間有其他關鍵詞則跳過(例如MySQL的分割槽insert語法: INSERT[LOW_PRIORITY|HIGH_PRIORITY][IGNORE][INTO]tbl_name[PARTITION(partition_name[,partition_name]......

  1. public void parse(final InsertStatement insertStatement) {

  2.    // step1

  3.    lexerEngine.unsupportedIfEqual(getUnsupportedKeywordsBeforeInto());

  4.    // step2

  5.    lexerEngine.skipUntil(DefaultKeyword.INTO);

  6.    // step3

  7.    lexerEngine.nextToken();

  8.    // step4

  9.    tableReferencesClauseParser.parse(insertStatement, true);

  10.    // step5

  11.    skipBetweenTableAndValues(insertStatement);

  12. }

第3步-InsertColumnsClauseParser.parse()

由parse()原始碼可知,insert解析第3步就是呼叫 insertClauseParserFacade.getInsertColumnsClauseParser().parse(result);,即解析insert into t_user後面的列,封裝到InsertStatementcolumns屬性中,核心原始碼如下所示:

  1. public void parse(final InsertStatement insertStatement) {

  2.    Collection<Column> result = new LinkedList<>();

  3.    // 如果當前token是(,即左括號,那麼嘗試解析括號裡的insert的列名

  4.    if (lexerEngine.equalAny(Symbol.LEFT_PAREN)) {

  5.        // 得到insert的標的表名

  6.        String tableName = insertStatement.getTables().getSingleTableName();

  7.        Optional<String> generateKeyColumn = shardingRule.getGenerateKeyColumn(tableName);

  8.        int count = 0;

  9.        do {

  10.            // 呼叫nextToken()解析到insert的某一列

  11.            lexerEngine.nextToken();

  12.            // 得到列名

  13.            String columnName = SQLUtil.getExactlyValue(lexerEngine.getCurrentToken().getLiterals());

  14.            // 封裝到Column中新增到最後的結果中

  15.            result.add(new Column(columnName, tableName));

  16.            // 呼叫nextToken()進行解析(如果還沒到最後就是都好,否則就是右括號)

  17.            lexerEngine.nextToken();

  18.            // 如果存在主鍵(例如id),並且當前解析的列剛好是主鍵列,那麼記錄下主鍵列的位置(generateKeyColumnIndex)

  19.            if (generateKeyColumn.isPresent() && generateKeyColumn.get().equalsIgnoreCase(columnName)) {

  20.                insertStatement.setGenerateKeyColumnIndex(count);

  21.            }

  22.            count++;

  23.            // 如果遍歷到右括號,或者SQL最後,就停止解析

  24.        } while (!lexerEngine.equalAny(Symbol.RIGHT_PAREN) && !lexerEngine.equalAny(Assist.END));

  25.        // 記錄下insert最後一列的位置,即右括號前的位置,例如"insert ignore into `t_user`(user_id, status) values(? , ?)"這個SQL的status所在位置

  26.        insertStatement.setColumnsListLastPosition(lexerEngine.getCurrentToken().getEndPosition() - lexerEngine.getCurrentToken().getLiterals().length());

  27.        lexerEngine.nextToken();

  28.    }

  29.    // 將遍歷得到的所有insert的列賦值給InsertStatement中的columns屬性

  30.    insertStatement.getColumns().addAll(result);

  31. }

第4步-不支援的語法檢查

由parse()原始碼可知,insert解析第4步就是呼叫如下程式碼,即檢查是否有sharding-jdbc不支援的 insert...select...語法(例如insert ignore into tuser(userid, status) select 18, 'VALID' from dual),如果有就丟擲UnsupportedOperationException異常:

  1. if (lexerEngine.equalAny(DefaultKeyword.SELECT, Symbol.LEFT_PAREN)) {

  2.    throw new UnsupportedOperationException("Cannot INSERT SELECT");

  3. }

第5步-InsertValuesClauseParser.parse()

由parse()原始碼可知,insert解析第5步就是呼叫 insertClauseParserFacade.getInsertValuesClauseParser().parse(result);,即解析insert into sql中的value集合,封裝到InsertStatementconditions屬性中,透過Conditions.add()原始碼可知,只會新增sharding列的值,例如 insert ignoreintot_user(id,user_id,status)values(?,?,?)只有user_id是sharding列,所以只會新增它:

  1. public void add(final Condition condition, final ShardingRule shardingRule) {

  2.    if (shardingRule.isShardingColumn(condition.getColumn())) {

  3.        conditions.put(condition.getColumn(), condition);

  4.    }

  5. }

第6步-InsertSetClauseParser.parse()

由parse()原始碼可知,insert解析第6步就是呼叫 insertClauseParserFacade.getInsertSetClauseParser().parse(result);,即解析insert into ... set ...這種語法的SQL,例如: insertintot_usersetid=24,user_id=24,status='NEW';核心原始碼如下所示:

  1. public void parse(final InsertStatement insertStatement) {

  2.    // 即如果不是**insert into ... set ...**這種語法,那麼return,不需要繼續往下解析

  3.    if (!lexerEngine.skipIfEqual(new Keyword[] {DefaultKeyword.SET})) {

  4.        return;

  5.    }

  6.    do {

  7.        Column column = new Column(SQLUtil.getExactlyValue(lexerEngine.getCurrentToken().getLiterals()), insertStatement.getTables().getSingleTableName());

  8.        lexerEngine.nextToken();

  9.        lexerEngine.accept(Symbol.EQ);

  10.        SQLExpression sqlExpression;

  11.        // 分析token, 根據不同型別得到不用的SQLExpression

  12.        if (lexerEngine.equalAny(Literals.INT)) {

  13.            sqlExpression = new SQLNumberExpression(Integer.parseInt(lexerEngine.getCurrentToken().getLiterals()));

  14.        } else if (lexerEngine.equalAny(Literals.FLOAT)) {

  15.            sqlExpression = new SQLNumberExpression(Double.parseDouble(lexerEngine.getCurrentToken().getLiterals()));

  16.        } else if (lexerEngine.equalAny(Literals.CHARS)) {

  17.            sqlExpression = new SQLTextExpression(lexerEngine.getCurrentToken().getLiterals());

  18.        } else if (lexerEngine.equalAny(DefaultKeyword.NULL)) {

  19.            sqlExpression = new SQLIgnoreExpression(DefaultKeyword.NULL.name());

  20.        } else if (lexerEngine.equalAny(Symbol.QUESTION)) {

  21.            sqlExpression = new SQLPlaceholderExpression(insertStatement.getParametersIndex());

  22.            insertStatement.increaseParametersIndex();

  23.        } else {

  24.            throw new UnsupportedOperationException("");

  25.        }

  26.        lexerEngine.nextToken();

  27.        if (lexerEngine.equalAny(Symbol.COMMA, DefaultKeyword.ON, Assist.END)) {

  28.            // 說明,這裡只會新增資料庫或者表的sharding列

  29.            insertStatement.getConditions().add(new Condition(column, sqlExpression), shardingRule);

  30.        } else {

  31.            lexerEngine.skipUntil(Symbol.COMMA, DefaultKeyword.ON);

  32.        }

  33.    } while (lexerEngine.skipIfEqual(Symbol.COMMA));

  34. }

第7步-appendGenerateKey(result)

由parse()原始碼可知,insert解析第7步就是呼叫 appendGenerateKey(result),即如果TableRule申明瞭 .generateKeyColumn("id",MyKeyGenerator.class),並且SQL中沒有主鍵列,那麼InsertStatementsqlTokens還需要增加兩個token:ItemsTokenGeneratedKeyToken,核心原始碼如下:

  1. private void appendGenerateKey(final InsertStatement insertStatement) {

  2.    String tableName = insertStatement.getTables().getSingleTableName();

  3.    Optional<String> generateKeyColumn = shardingRule.getGenerateKeyColumn(tableName);

  4.    // generateKeyColumn存在,即generateKeyColumn.isPresent(),即TableRule定義時指定了.generateKeyColumn();例如TableRule.generateKeyColumn("id", MyKeyGenerator.class)

  5.    if (!generateKeyColumn.isPresent()) {

  6.        return;

  7.    }

  8.    // Insert SQL陳述句中沒有TableRule定義時TableRule.generateKeyColumn("id")指定的列,例如id;

  9.    if (null != insertStatement.getGeneratedKey()) {

  10.        return;

  11.    }

  12.    ItemsToken columnsToken = new ItemsToken(insertStatement.getColumnsListLastPosition());

  13.    columnsToken.getItems().add(generateKeyColumn.get());

  14.    insertStatement.getSqlTokens().add(columnsToken);

  15.    insertStatement.getSqlTokens().add(new GeneratedKeyToken(insertStatement.getValuesListLastPosition()));

  16. }

ItemsTokenGeneratedKeyToken這兩個token用於後面的SQL重寫,例如將insert ignore into tuser(userid, status) values(? , ?)這樣的SQL重寫為insert ignore into tuser(userid, status, id) values(? , ?, ?)

贊(0)

分享創造快樂

© 2024 知識星球   網站地圖