點選上方“Java技術驛站”,選擇“置頂公眾號”。
有內涵、有價值的文章第一時間送達!
精品專欄
INSERT語法
分析insert解析之前,首先看一下mysql官方對insert語法的定義,因為SQL解析跟語法息息相關:
INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name
[PARTITION (partition_name [, partition_name] ...)]
[(col_name [, col_name] ...)]
{VALUES | VALUE} (value_list) [, (value_list)] ...
[ON DUPLICATE KEY UPDATE assignment_list]
INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name
[PARTITION (partition_name [, partition_name] ...)]
SET assignment_list
[ON DUPLICATE KEY UPDATE assignment_list]
INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name
[PARTITION (partition_name [, partition_name] ...)]
[(col_name [, col_name] ...)]
SELECT ...
[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解析器後,執行AbstractInsertParser中parse()方法解析insert sql,核心原始碼如下:
@Override
public final DMLStatement parse() {
lexerEngine.nextToken();
InsertStatement result = new InsertStatement();
insertClauseParserFacade.getInsertIntoClauseParser().parse(result);
insertClauseParserFacade.getInsertColumnsClauseParser().parse(result);
if (lexerEngine.equalAny(DefaultKeyword.SELECT, Symbol.LEFT_PAREN)) {
throw new UnsupportedOperationException("Cannot INSERT SELECT");
}
insertClauseParserFacade.getInsertValuesClauseParser().parse(result);
insertClauseParserFacade.getInsertSetClauseParser().parse(result);
appendGenerateKey(result);
return result;
}
對應的泳道圖如下所示:
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後面的表名,封裝到InsertStatement的table屬性中,核心原始碼如下所示:
-
檢查是否有不支援的關鍵詞(跳過兩個token後,只有Oracle有兩個不支援的關鍵詞:ALL和FIRST);
-
跳到INTO(因為INSERT和INTO之間還有其他關鍵詞,在此之前即使呼叫了兩次nextToken(),當前token也不一定就是INTO);
-
獲取下一個token即表名(根據insert語法可知,INTO後肯定是表名。表名token值:Token(type=IDENTIFIER, literals=
t_user
, endPosition=27)); -
解析表名賦值給InsertStatement;
-
如果表名和VALUES關鍵詞之間有其他關鍵詞則跳過(例如MySQL的分割槽insert語法:
INSERT[LOW_PRIORITY|HIGH_PRIORITY][IGNORE][INTO]tbl_name[PARTITION(partition_name[,partition_name]......
)
public void parse(final InsertStatement insertStatement) {
// step1
lexerEngine.unsupportedIfEqual(getUnsupportedKeywordsBeforeInto());
// step2
lexerEngine.skipUntil(DefaultKeyword.INTO);
// step3
lexerEngine.nextToken();
// step4
tableReferencesClauseParser.parse(insertStatement, true);
// step5
skipBetweenTableAndValues(insertStatement);
}
第3步-InsertColumnsClauseParser.parse()
由parse()原始碼可知,insert解析第3步就是呼叫 insertClauseParserFacade.getInsertColumnsClauseParser().parse(result);
,即解析insert into t_user後面的列,封裝到InsertStatement的columns屬性中,核心原始碼如下所示:
public void parse(final InsertStatement insertStatement) {
Collection<Column> result = new LinkedList<>();
// 如果當前token是(,即左括號,那麼嘗試解析括號裡的insert的列名
if (lexerEngine.equalAny(Symbol.LEFT_PAREN)) {
// 得到insert的標的表名
String tableName = insertStatement.getTables().getSingleTableName();
Optional<String> generateKeyColumn = shardingRule.getGenerateKeyColumn(tableName);
int count = 0;
do {
// 呼叫nextToken()解析到insert的某一列
lexerEngine.nextToken();
// 得到列名
String columnName = SQLUtil.getExactlyValue(lexerEngine.getCurrentToken().getLiterals());
// 封裝到Column中新增到最後的結果中
result.add(new Column(columnName, tableName));
// 呼叫nextToken()進行解析(如果還沒到最後就是都好,否則就是右括號)
lexerEngine.nextToken();
// 如果存在主鍵(例如id),並且當前解析的列剛好是主鍵列,那麼記錄下主鍵列的位置(generateKeyColumnIndex)
if (generateKeyColumn.isPresent() && generateKeyColumn.get().equalsIgnoreCase(columnName)) {
insertStatement.setGenerateKeyColumnIndex(count);
}
count++;
// 如果遍歷到右括號,或者SQL最後,就停止解析
} while (!lexerEngine.equalAny(Symbol.RIGHT_PAREN) && !lexerEngine.equalAny(Assist.END));
// 記錄下insert最後一列的位置,即右括號前的位置,例如"insert ignore into `t_user`(user_id, status) values(? , ?)"這個SQL的status所在位置
insertStatement.setColumnsListLastPosition(lexerEngine.getCurrentToken().getEndPosition() - lexerEngine.getCurrentToken().getLiterals().length());
lexerEngine.nextToken();
}
// 將遍歷得到的所有insert的列賦值給InsertStatement中的columns屬性
insertStatement.getColumns().addAll(result);
}
第4步-不支援的語法檢查
由parse()原始碼可知,insert解析第4步就是呼叫如下程式碼,即檢查是否有sharding-jdbc不支援的 insert...select...
語法(例如insert ignore into tuser(userid, status) select 18, 'VALID' from dual),如果有就丟擲UnsupportedOperationException異常:
if (lexerEngine.equalAny(DefaultKeyword.SELECT, Symbol.LEFT_PAREN)) {
throw new UnsupportedOperationException("Cannot INSERT SELECT");
}
第5步-InsertValuesClauseParser.parse()
由parse()原始碼可知,insert解析第5步就是呼叫 insertClauseParserFacade.getInsertValuesClauseParser().parse(result);
,即解析insert into sql中的value集合,封裝到InsertStatement的conditions屬性中,透過Conditions.add()原始碼可知,只會新增sharding列的值,例如 insert ignoreintot_user(id,user_id,status)values(?,?,?)
只有user_id是sharding列,所以只會新增它:
public void add(final Condition condition, final ShardingRule shardingRule) {
if (shardingRule.isShardingColumn(condition.getColumn())) {
conditions.put(condition.getColumn(), condition);
}
}
第6步-InsertSetClauseParser.parse()
由parse()原始碼可知,insert解析第6步就是呼叫 insertClauseParserFacade.getInsertSetClauseParser().parse(result);
,即解析insert into ... set ...這種語法的SQL,例如: insertintot_usersetid=24,user_id=24,status='NEW'
;核心原始碼如下所示:
public void parse(final InsertStatement insertStatement) {
// 即如果不是**insert into ... set ...**這種語法,那麼return,不需要繼續往下解析
if (!lexerEngine.skipIfEqual(new Keyword[] {DefaultKeyword.SET})) {
return;
}
do {
Column column = new Column(SQLUtil.getExactlyValue(lexerEngine.getCurrentToken().getLiterals()), insertStatement.getTables().getSingleTableName());
lexerEngine.nextToken();
lexerEngine.accept(Symbol.EQ);
SQLExpression sqlExpression;
// 分析token, 根據不同型別得到不用的SQLExpression
if (lexerEngine.equalAny(Literals.INT)) {
sqlExpression = new SQLNumberExpression(Integer.parseInt(lexerEngine.getCurrentToken().getLiterals()));
} else if (lexerEngine.equalAny(Literals.FLOAT)) {
sqlExpression = new SQLNumberExpression(Double.parseDouble(lexerEngine.getCurrentToken().getLiterals()));
} else if (lexerEngine.equalAny(Literals.CHARS)) {
sqlExpression = new SQLTextExpression(lexerEngine.getCurrentToken().getLiterals());
} else if (lexerEngine.equalAny(DefaultKeyword.NULL)) {
sqlExpression = new SQLIgnoreExpression(DefaultKeyword.NULL.name());
} else if (lexerEngine.equalAny(Symbol.QUESTION)) {
sqlExpression = new SQLPlaceholderExpression(insertStatement.getParametersIndex());
insertStatement.increaseParametersIndex();
} else {
throw new UnsupportedOperationException("");
}
lexerEngine.nextToken();
if (lexerEngine.equalAny(Symbol.COMMA, DefaultKeyword.ON, Assist.END)) {
// 說明,這裡只會新增資料庫或者表的sharding列
insertStatement.getConditions().add(new Condition(column, sqlExpression), shardingRule);
} else {
lexerEngine.skipUntil(Symbol.COMMA, DefaultKeyword.ON);
}
} while (lexerEngine.skipIfEqual(Symbol.COMMA));
}
第7步-appendGenerateKey(result)
由parse()原始碼可知,insert解析第7步就是呼叫 appendGenerateKey(result)
,即如果TableRule申明瞭 .generateKeyColumn("id",MyKeyGenerator.class)
,並且SQL中沒有主鍵列,那麼InsertStatement的sqlTokens還需要增加兩個token:ItemsToken和GeneratedKeyToken,核心原始碼如下:
private void appendGenerateKey(final InsertStatement insertStatement) {
String tableName = insertStatement.getTables().getSingleTableName();
Optional<String> generateKeyColumn = shardingRule.getGenerateKeyColumn(tableName);
// generateKeyColumn存在,即generateKeyColumn.isPresent(),即TableRule定義時指定了.generateKeyColumn();例如TableRule.generateKeyColumn("id", MyKeyGenerator.class)
if (!generateKeyColumn.isPresent()) {
return;
}
// Insert SQL陳述句中沒有TableRule定義時TableRule.generateKeyColumn("id")指定的列,例如id;
if (null != insertStatement.getGeneratedKey()) {
return;
}
ItemsToken columnsToken = new ItemsToken(insertStatement.getColumnsListLastPosition());
columnsToken.getItems().add(generateKeyColumn.get());
insertStatement.getSqlTokens().add(columnsToken);
insertStatement.getSqlTokens().add(new GeneratedKeyToken(insertStatement.getValuesListLastPosition()));
}
ItemsToken和GeneratedKeyToken這兩個token用於後面的SQL重寫,例如將insert ignore into tuser(userid, status) values(? , ?)這樣的SQL重寫為insert ignore into tuser(userid, status, id) values(? , ?, ?)