🙂🙂🙂关注微信公众号:【芋艿的后端小屋】有福利:
  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

本文主要基于 Sharding-JDBC 1.5.0 正式版
  • 1. 概述
  • 2. InsertStatement
  • 3. #parse()
    • 3.1 #parseInfo()
    • 3.2 #parseColumns()
    • 3.3 #parseValues()
    • 3.4 #parseCustomizedInsert()
    • 3.5 #appendGenerateKey()
  • 666. 彩蛋

1. 概述

本文前置阅读:
  • 《SQL 解析(一)之词法解析》
  • 《SQL 解析(二)之SQL解析》
本文分享插入SQL解析的源码实现。
不考虑 INSERT SELECT 情况下,插入SQL解析比查询SQL解析复杂度低的多的多。不同数据库在插入SQL语法上也统一的多。本文分享 MySQL 插入SQL解析器 MySQLInsertParser
MySQL INSERT 语法一共有 3 种 :
  • 第一种: INSERT{VALUES|VALUES}
  1. INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY][IGNORE]
  2. [INTO] tbl_name
  3. [PARTITION (partition_name,...)]
  4. [(col_name,...)]
  5. {VALUES | VALUE}({expr | DEFAULT},...),(...),...
  6. [ ON DUPLICATE KEY UPDATE
  7.      col_name=expr
  8. [, col_name=expr]...]
  • 第二种: INSERT SET
  1. INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY][IGNORE]
  2. [INTO] tbl_name
  3. [PARTITION (partition_name,...)]
  4.    SET col_name={expr | DEFAULT},...
  5. [ ON DUPLICATE KEY UPDATE
  6.      col_name=expr
  7. [, col_name=expr]...]
  • 第三种: INSERT SELECT
  1. INSERT [LOW_PRIORITY | HIGH_PRIORITY][IGNORE]
  2. [INTO] tbl_name
  3. [PARTITION (partition_name,...)]
  4. [(col_name,...)]
  5.    SELECT ...
  6. [ ON DUPLICATE KEY UPDATE
  7.      col_name=expr
  8. [, col_name=expr]...]
Sharding-JDBC 目前支持:
  • 第一种: INSERT{VALUES|VALUES}单条记录
  • 第二种: INSERT SET
Sharding-JDBC 插入SQL解析主流程如下:
  1. // AbstractInsertParser.java
  2. publicfinalInsertStatement parse(){
  3.   sqlParser.getLexer().nextToken();// 跳过 INSERT 关键字
  4.   parseInto();// 解析INTO
  5.   parseColumns();// 解析表
  6. if(sqlParser.equalAny(DefaultKeyword.SELECT,Symbol.LEFT_PAREN)){
  7. thrownewUnsupportedOperationException("Cannot support subquery");
  8. }
  9. if(getValuesKeywords().contains(sqlParser.getLexer().getCurrentToken().getType())){// 第一种插入SQL情况
  10.       parseValues();
  11. }elseif(getCustomizedInsertKeywords().contains(sqlParser.getLexer().getCurrentToken().getType())){// 第二种插入SQL情况
  12.       parseCustomizedInsert();
  13. }
  14.   appendGenerateKey();// 自增主键
  15. return insertStatement;
  16. }
Sharding-JDBC 正在收集使用公司名单:传送门。

🙂 你的登记,会让更多人参与和使用 Sharding-JDBC。传送门

Sharding-JDBC 也会因此,能够覆盖更多的业务场景。传送门

登记吧,骚年!传送门

2. InsertStatement

插入SQL 解析结果。
  1. publicfinalclassInsertStatementextendsAbstractSQLStatement{
  2. /**
  3.     * 插入字段
  4.     */
  5. privatefinalCollection<Column> columns =newLinkedList<>();
  6. /**
  7.     *
  8.     */
  9. privateGeneratedKey generatedKey;
  10. /**
  11.     * 插入字段 下一个Token 开始位置
  12.     */
  13. privateint columnsListLastPosition;
  14. /**
  15.     * 值字段 下一个Token 开始位置
  16.     */
  17. privateint valuesListLastPosition;
  18. }
我们来看下 INSERT INTO t_order(uid,nickname)VALUES(?,?)解析结果

3. #parse()

3.1 #parseInto()

解析
  1. // AbstractInsertParser.java
  2. /**
  3. * 解析表
  4. */
  5. privatevoid parseInto(){
  6. // 例如,Oracle,INSERT FIRST/ALL 目前不支持
  7. if(getUnsupportedKeywords().contains(sqlParser.getLexer().getCurrentToken().getType())){
  8. thrownewSQLParsingUnsupportedException(sqlParser.getLexer().getCurrentToken().getType());
  9. }
  10.   sqlParser.skipUntil(DefaultKeyword.INTO);
  11.   sqlParser.getLexer().nextToken();
  12. // 解析表
  13.   sqlParser.parseSingleTable(insertStatement);
  14.   skipBetweenTableAndValues();
  15. }
  16. /**
  17. * 跳过 表 和 插入字段 中间的 Token
  18. * 例如 MySQL :[PARTITION (partition_name,...)]
  19. */
  20. privatevoid skipBetweenTableAndValues(){
  21. while(getSkippedKeywordsBetweenTableAndValues().contains(sqlParser.getLexer().getCurrentToken().getType())){
  22.       sqlParser.getLexer().nextToken();
  23. if(sqlParser.equalAny(Symbol.LEFT_PAREN)){
  24.           sqlParser.skipParentheses();
  25. }
  26. }
  27. }
其中 #parseSingleTable() 请看《SQL 解析(二)之SQL解析》的 #parseSingleTable() 小节。

3.2 #parseColumns()

解析插入字段
  1. // AbstractInsertParser.java
  2. privatevoid parseColumns(){
  3. Collection<Column> result =newLinkedList<>();
  4. if(sqlParser.equalAny(Symbol.LEFT_PAREN)){
  5. String tableName = insertStatement.getTables().getSingleTableName();
  6. Optional<String> generateKeyColumn = shardingRule.getGenerateKeyColumn(tableName);// 自动生成键信息
  7. int count =0;
  8. do{
  9. // Column 插入字段
  10.           sqlParser.getLexer().nextToken();
  11. String columnName =SQLUtil.getExactlyValue(sqlParser.getLexer().getCurrentToken().getLiterals());
  12.           result.add(newColumn(columnName, tableName));
  13.           sqlParser.getLexer().nextToken();
  14. // 自动生成键
  15. if(generateKeyColumn.isPresent()&& generateKeyColumn.get().equalsIgnoreCase(columnName)){
  16.               generateKeyColumnIndex = count;
  17. }
  18.           count++;
  19. }while(!sqlParser.equalAny(Symbol.RIGHT_PAREN)&&!sqlParser.equalAny(Assist.END));
  20. //
  21.       insertStatement.setColumnsListLastPosition(sqlParser.getLexer().getCurrentToken().getEndPosition()- sqlParser.getLexer().getCurrentToken().getLiterals().length());
  22. //
  23.       sqlParser.getLexer().nextToken();
  24. }
  25.   insertStatement.getColumns().addAll(result);
  26. }

3.3 #parseValues()

解析值字段
  1. /**
  2. * 解析值字段
  3. */
  4. privatevoid parseValues(){
  5. boolean parsed =false;
  6. do{
  7. if(parsed){// 只允许INSERT INTO 一条
  8. thrownewUnsupportedOperationException("Cannot support multiple insert");
  9. }
  10.       sqlParser.getLexer().nextToken();
  11.       sqlParser.accept(Symbol.LEFT_PAREN);
  12. // 解析表达式
  13. List<SQLExpression> sqlExpressions =newLinkedList<>();
  14. do{
  15.           sqlExpressions.add(sqlParser.parseExpression());
  16. }while(sqlParser.skipIfEqual(Symbol.COMMA));
  17. //
  18.       insertStatement.setValuesListLastPosition(sqlParser.getLexer().getCurrentToken().getEndPosition()- sqlParser.getLexer().getCurrentToken().getLiterals().length());
  19. // 解析值字段
  20. int count =0;
  21. for(Column each : insertStatement.getColumns()){
  22. SQLExpression sqlExpression = sqlExpressions.get(count);
  23.           insertStatement.getConditions().add(newCondition(each, sqlExpression), shardingRule);
  24. if(generateKeyColumnIndex == count){// 自动生成键
  25.               insertStatement.setGeneratedKey(createGeneratedKey(each, sqlExpression));
  26. }
  27.           count++;
  28. }
  29.       sqlParser.accept(Symbol.RIGHT_PAREN);
  30.       parsed =true;
  31. }
  32. while(sqlParser.equalAny(Symbol.COMMA));// 字段以 "," 分隔
  33. }
  34. /**
  35. * 创建 自动生成键
  36. *
  37. * @param column 字段
  38. * @param sqlExpression 表达式
  39. * @return 自动生成键
  40. */
  41. privateGeneratedKey createGeneratedKey(finalColumn column,finalSQLExpression sqlExpression){
  42. GeneratedKey result;
  43. if(sqlExpression instanceofSQLPlaceholderExpression){// 占位符
  44.       result =newGeneratedKey(column.getName(),((SQLPlaceholderExpression) sqlExpression).getIndex(),null);
  45. }elseif(sqlExpression instanceofSQLNumberExpression){// 数字
  46.       result =newGeneratedKey(column.getName(),-1,((SQLNumberExpression) sqlExpression).getNumber());
  47. }else{
  48. thrownewShardingJdbcException("Generated key only support number.");
  49. }
  50. return result;
  51. }

3.4.1 GeneratedKey

自动生成键,属于分片上下文信息
  1. publicfinalclassGeneratedKey{
  2. /**
  3.     * 字段
  4.     */
  5. privatefinalString column;
  6. /**
  7.     * 第几个占位符
  8.     */
  9. privatefinalint index;
  10. /**
  11.     * 值
  12.     */
  13. privatefinalNumber value;
  14. }

3.4.2 Condition

条件对象,属于分片上下文信息。在插入SQL解析里存储影响分片的值字段。后续《SQL 路由》 会专门分享这块。
  1. publicfinalclassCondition{
  2. /**
  3.     * 字段
  4.     */
  5. @Getter
  6. privatefinalColumn column;
  7. // ... 省略其它属性
  8. }
  9. publicfinalclassColumn{
  10. /**
  11.     * 列名
  12.     */
  13. privatefinalString name;
  14. /**
  15.     * 表名
  16.     */
  17. privatefinalString tableName;
  18. }

3.4 #parseCustomizedInsert()

解析第二种插入SQLINSERT SET。例如:
  1. INSERT INTO test SET id =4  ON DUPLICATE KEY UPDATE name ='doubi', name ='hehe';
  2. INSERT INTO test SET id =4, name ='hehe';
  1. privatevoid parseInsertSet(){
  2. do{
  3.       getSqlParser().getLexer().nextToken();
  4. // 插入字段
  5. Column column =newColumn(SQLUtil.getExactlyValue(getSqlParser().getLexer().getCurrentToken().getLiterals()), getInsertStatement().getTables().getSingleTableName());
  6.       getSqlParser().getLexer().nextToken();
  7. // 等号
  8.       getSqlParser().accept(Symbol.EQ);
  9. // 【值】表达式
  10. SQLExpression sqlExpression;
  11. if(getSqlParser().equalAny(Literals.INT)){
  12.           sqlExpression =newSQLNumberExpression(Integer.parseInt(getSqlParser().getLexer().getCurrentToken().getLiterals()));
  13. }elseif(getSqlParser().equalAny(Literals.FLOAT)){
  14.           sqlExpression =newSQLNumberExpression(Double.parseDouble(getSqlParser().getLexer().getCurrentToken().getLiterals()));
  15. }elseif(getSqlParser().equalAny(Literals.CHARS)){
  16.           sqlExpression =newSQLTextExpression(getSqlParser().getLexer().getCurrentToken().getLiterals());
  17. }elseif(getSqlParser().equalAny(DefaultKeyword.NULL)){
  18.           sqlExpression =newSQLIgnoreExpression();
  19. }elseif(getSqlParser().equalAny(Symbol.QUESTION)){
  20.           sqlExpression =newSQLPlaceholderExpression(getSqlParser().getParametersIndex());
  21.           getSqlParser().increaseParametersIndex();
  22. }else{
  23. thrownewUnsupportedOperationException("");
  24. }
  25.       getSqlParser().getLexer().nextToken();
  26. // Condition
  27. if(getSqlParser().equalAny(Symbol.COMMA,DefaultKeyword.ON,Assist.END)){
  28.           getInsertStatement().getConditions().add(newCondition(column, sqlExpression), getShardingRule());
  29. }else{
  30.           getSqlParser().skipUntil(Symbol.COMMA,DefaultKeyword.ON);
  31. }
  32. }while(getSqlParser().equalAny(Symbol.COMMA));// 字段以 "," 分隔
  33. }

3.5 #appendGenerateKey()

当表设置自动生成键,并且插入SQL写自增字段,增加该字段。例如:
  1. // 主键为user_id
  2. INSERT INTO t_user(nickname, age) VALUES (?,?)
后续 SQL 改写会生成该自增编号,并改写该 SQL。后续《SQL 改写》 会专门分享这块。
  1. privatevoid appendGenerateKey(){
  2. // 当表设置自动生成键,并且插入SQL没写自增字段
  3. String tableName = insertStatement.getTables().getSingleTableName();
  4. Optional<String> generateKeyColumn = shardingRule.getGenerateKeyColumn(tableName);
  5. if(!generateKeyColumn.isPresent()||null!= insertStatement.getGeneratedKey()){
  6. return;
  7. }
  8. // ItemsToken
  9. ItemsToken columnsToken =newItemsToken(insertStatement.getColumnsListLastPosition());
  10.   columnsToken.getItems().add(generateKeyColumn.get());
  11.   insertStatement.getSqlTokens().add(columnsToken);
  12. // GeneratedKeyToken
  13.   insertStatement.getSqlTokens().add(newGeneratedKeyToken(insertStatement.getValuesListLastPosition()));
  14. }

3.5.1 GeneratedKeyToken

自增主键标记对象。
  1. publicfinalclassGeneratedKeyTokenimplementsSQLToken{
  2. /**
  3.     * 开始位置
  4.     */
  5. privatefinalint beginPosition;
  6. }

666. 彩蛋

😈 是不是比《SQL 解析(三)之插入SQL》简单很多。
道友,可否分享一波【本文】到朋友圈
继续加油更新.
继续阅读
阅读原文