查看原文
其他

数据库[分库分表]中间件 Sharding-JDBC 源码分析 —— SQL 解析(四)之插入SQL

2017-08-12 王文斌(芋艿) 芋道源码

🙂🙂🙂关注微信公众号:【芋艿的后端小屋】有福利:

  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. public final InsertStatement parse() {

  3.   sqlParser.getLexer().nextToken(); // 跳过 INSERT 关键字

  4.   parseInto(); // 解析INTO

  5.   parseColumns(); // 解析表

  6.   if (sqlParser.equalAny(DefaultKeyword.SELECT, Symbol.LEFT_PAREN)) {

  7.       throw new UnsupportedOperationException("Cannot support subquery");

  8.   }

  9.   if (getValuesKeywords().contains(sqlParser.getLexer().getCurrentToken().getType())) { // 第一种插入SQL情况

  10.       parseValues();

  11.   } else if (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. public final class InsertStatement extends AbstractSQLStatement {

  2.    /**

  3.     * 插入字段

  4.     */

  5.    private final Collection<Column> columns = new LinkedList<>();

  6.    /**

  7.     *

  8.     */

  9.    private GeneratedKey generatedKey;

  10.    /**

  11.     * 插入字段 下一个Token 开始位置

  12.     */

  13.    private int columnsListLastPosition;

  14.    /**

  15.     * 值字段 下一个Token 开始位置

  16.     */

  17.    private int valuesListLastPosition;

  18. }

我们来看下 INSERT INTO t_order(uid,nickname)VALUES(?,?)解析结果

3. #parse()

3.1 #parseInto()

解析

  1. // AbstractInsertParser.java

  2. /**

  3. * 解析表

  4. */

  5. private void parseInto() {

  6.   // 例如,Oracle,INSERT FIRST/ALL 目前不支持

  7.   if (getUnsupportedKeywords().contains(sqlParser.getLexer().getCurrentToken().getType())) {

  8.       throw new SQLParsingUnsupportedException(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. private void 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. private void parseColumns() {

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

  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(new Column(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. private void parseValues() {

  5.   boolean parsed = false;

  6.   do {

  7.       if (parsed) { // 只允许INSERT INTO 一条

  8.           throw new UnsupportedOperationException("Cannot support multiple insert");

  9.       }

  10.       sqlParser.getLexer().nextToken();

  11.       sqlParser.accept(Symbol.LEFT_PAREN);

  12.       // 解析表达式

  13.       List<SQLExpression> sqlExpressions = new LinkedList<>();

  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(new Condition(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. private GeneratedKey createGeneratedKey(final Column column, final SQLExpression sqlExpression) {

  42.   GeneratedKey result;

  43.   if (sqlExpression instanceof SQLPlaceholderExpression) { // 占位符

  44.       result = new GeneratedKey(column.getName(), ((SQLPlaceholderExpression) sqlExpression).getIndex(), null);

  45.   } else if (sqlExpression instanceof SQLNumberExpression) { // 数字

  46.       result = new GeneratedKey(column.getName(), -1, ((SQLNumberExpression) sqlExpression).getNumber());

  47.   } else {

  48.       throw new ShardingJdbcException("Generated key only support number.");

  49.   }

  50.   return result;

  51. }

3.4.1 GeneratedKey

自动生成键,属于分片上下文信息

  1. public final class GeneratedKey {

  2.    /**

  3.     * 字段

  4.     */

  5.    private final String column;

  6.    /**

  7.     * 第几个占位符

  8.     */

  9.    private final int index;

  10.    /**

  11.     * 值

  12.     */

  13.    private final Number value;

  14. }

3.4.2 Condition

条件对象,属于分片上下文信息。在插入SQL解析里存储影响分片的值字段。后续《SQL 路由》 会专门分享这块。

  1. public final class Condition {

  2.    /**

  3.     * 字段

  4.     */

  5.    @Getter

  6.    private final Column column;

  7.    // ... 省略其它属性

  8. }

  9. public final class Column {

  10.    /**

  11.     * 列名

  12.     */

  13.    private final String name;

  14.    /**

  15.     * 表名

  16.     */

  17.    private final String 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. private void parseInsertSet() {

  2.   do {

  3.       getSqlParser().getLexer().nextToken();

  4.       // 插入字段

  5.       Column column = new Column(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 = new SQLNumberExpression(Integer.parseInt(getSqlParser().getLexer().getCurrentToken().getLiterals()));

  13.       } else if (getSqlParser().equalAny(Literals.FLOAT)) {

  14.           sqlExpression = new SQLNumberExpression(Double.parseDouble(getSqlParser().getLexer().getCurrentToken().getLiterals()));

  15.       } else if (getSqlParser().equalAny(Literals.CHARS)) {

  16.           sqlExpression = new SQLTextExpression(getSqlParser().getLexer().getCurrentToken().getLiterals());

  17.       } else if (getSqlParser().equalAny(DefaultKeyword.NULL)) {

  18.           sqlExpression = new SQLIgnoreExpression();

  19.       } else if (getSqlParser().equalAny(Symbol.QUESTION)) {

  20.           sqlExpression = new SQLPlaceholderExpression(getSqlParser().getParametersIndex());

  21.           getSqlParser().increaseParametersIndex();

  22.       } else {

  23.           throw new UnsupportedOperationException("");

  24.       }

  25.       getSqlParser().getLexer().nextToken();

  26.       // Condition

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

  28.           getInsertStatement().getConditions().add(new Condition(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. private void 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 = new ItemsToken(insertStatement.getColumnsListLastPosition());

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

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

  12.   // GeneratedKeyToken

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

  14. }

3.5.1 GeneratedKeyToken

自增主键标记对象。

  1. public final class GeneratedKeyToken implements SQLToken {

  2.    /**

  3.     * 开始位置

  4.     */

  5.    private final int beginPosition;

  6. }

666. 彩蛋

😈 是不是比《SQL 解析(三)之插入SQL》简单很多。

道友,可否分享一波【本文】到朋友圈

继续加油更新.


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存