sharding-sphere之SQL解析insert

以mysql为例,官网说明insert语句的写法如下:

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]

value:
    {expr | DEFAULT}

value_list:
    value [, value] ...

assignment:
    col_name = value

assignment_list:
    assignment [, assignment] ...

简单来讲,支持一下三种:

--第一种Insert into values
insert into table(column1,column2,column3...) values (data1,data2,data3...)
--第二种Insert into set
insert into table set column1=data1,column2=data2,column3=data3...
--第三种
insert into table select ...

目前来讲,sharding-sphere并不支持第三种。原因在这里:

 //如果词法解析器解析表名后面为select关键字,则抛异常。
 if (lexerEngine.equalAny(DefaultKeyword.SELECT, Symbol.LEFT_PAREN)) {
     throw new UnsupportedOperationException("Cannot INSERT SELECT");
 }

接下来,就以第一种为例,介绍一下整个的语法解析过程:
解析sql:

INSERT INTO `table` (`field1`, `field2`) VALUES (10, 1)

语法解析器会将sql最终解析成SqlStatement,sql的解析都会被Sql语法解析引擎SQLParsingEngine去解析,从SQL解析引擎看起,看这条insert sql都经历了些什么故事?

@Test
public void insertValuesTest() {
    ShardingRule shardingRule = createShardingRuleByLocalHost();
    String insertSQL="INSERT INTO `table` (`field1`, `field2`) VALUES (10, 1)";
    //初始化一个语法解析器
    SQLParsingEngine statementParser = new SQLParsingEngine(DatabaseType.MySQL,insertSQL, shardingRule, null);
    //语法解析器解析
    InsertStatement insertStatement = (InsertStatement) statementParser.parse(false);
    System.out.println(insertStatement);
}

SQLParsingEngine解析过程中,首先判断是否需要读缓存,如果需要则从缓存中取,如果不需要,则初始化一个词法解析器LexerEngine,获取第一个分词,根据分词,数据库类型获取真实的SqlParser,这里为MySQLInsertParser,具体过程在链接:
词法解析器

public SQLStatement parse(final boolean useCache) {
    //是否读缓存
    Optional<SQLStatement> cachedSQLStatement = getSQLStatementFromCache(useCache);
    if (cachedSQLStatement.isPresent()) {
        return cachedSQLStatement.get();
    }
    //词法解析引擎初始化
    LexerEngine lexerEngine = LexerEngineFactory.newInstance(dbType, sql);
    //分词,获取第一个分词,此处为insert
    lexerEngine.nextToken(); //此处为insert分词
    //获取语法解析器,并解析
    SQLStatement result = SQLParserFactory.newInstance(dbType, lexerEngine.getCurrentToken().getType(), shardingRule, lexerEngine, shardingMetaData).parse();
    if (useCache) {
        ParsingResultCache.getInstance().put(sql, result);
    }
    return result;
}

MySQLInsertParser解析过程中,首先获取insert之后的下一个分词,然后交由下一个从句解析器去解析,比如:insert之后,会解析into表名,交由InsertIntoClauseParser去解析,然后解析(,)里的字段信息,交由InsertColumnsClauseParser去解析,然后判断是否是insert...select语句,如果是,则抛异常,表示不支持,否则,当做insert..values语句处理,交由InsertValuesClauseParser处理,若不是insert..values语句,则交由InsertSetClauseParser,当做insert...set语句处理。

@Override
      public final DMLStatement parse() {
        //获取下一分词
        lexerEngine.nextToken();
        //初始化返回的结果
        InsertStatement result = new InsertStatement();
        //InsertIntoClauseParser insertInto从句解析器解析结果
        insertClauseParserFacade.getInsertIntoClauseParser().parse(result);
        //解析()及里面的字段信息
        insertClauseParserFacade.getInsertColumnsClauseParser().parse(result, shardingMetaData);
        //如果是insert select 语句,则不支持
        if (lexerEngine.equalAny(DefaultKeyword.SELECT, Symbol.LEFT_PAREN)) {
            throw new UnsupportedOperationException("Cannot INSERT SELECT");
        }
        //InsertValuesClauseParser解析insert value信息
        insertClauseParserFacade.getInsertValuesClauseParser().parse(result, shardingMetaData);
        //InsertSetClauseParser 解析insert...set信息
        insertClauseParserFacade.getInsertSetClauseParser().parse(result);
        //ON DUPLICATE KEY UPDATE 解析
        insertClauseParserFacade.getInsertDuplicateKeyUpdateClauseParser().parse(result);
        //处理需要自动生成值的列
        processGeneratedKey(result);
        return result;
    }

InsertIntoClauseParser的处理流程:

public void parse(final InsertStatement insertStatement) {
    //如果出现不支持的词在into之前,则终止解析    
    lexerEngine.unsupportedIfEqual(getUnsupportedKeywordsBeforeInto());
    //跳过所有字符,直到出现into
    lexerEngine.skipUntil(DefaultKeyword.INTO);
    //获取into后的下一分词,当然这里就是表名了
    lexerEngine.nextToken();
    //解析表引用关系
    tableReferencesClauseParser.parse(insertStatement, true);
    skipBetweenTableAndValues(insertStatement);
}

而在解析表引用关系时,实质是使用MySQLTableReferencesClauseParser,mysql的table从句解析器解析,具体如下:

@Override
protected void parseTableReference(final SQLStatement sqlStatement, final boolean isSingleTableOnly) {
    //解析表
    parseTableFactor(sqlStatement, isSingleTableOnly);
    //PARTITION 解析
    parsePartition();
    parseIndexHint(sqlStatement);
}  

protected final void parseTableFactor(final SQLStatement sqlStatement, final boolean isSingleTableOnly) {
    final int beginPosition = lexerEngine.getCurrentToken().getEndPosition() - lexerEngine.getCurrentToken().getLiterals().length();
    String literals = lexerEngine.getCurrentToken().getLiterals();
    int skippedSchemaNameLength = 0;
    //获取下一分词
    lexerEngine.nextToken();
    //跳过如果是. 则在点之前的是schema的名称,跳过.
    if (lexerEngine.skipIfEqual(Symbol.DOT)) {
         //跳过schema的长度为skippedSchemaNameLength
        skippedSchemaNameLength = literals.length() + Symbol.DOT.getLiterals().length();
        //表名为当前分词
        literals = lexerEngine.getCurrentToken().getLiterals();
    }
    //获取表名 如果为`tablename`,则返回tablename
    String tableName = SQLUtil.getExactlyValue(literals);
    if (Strings.isNullOrEmpty(tableName)) {
        return;
    }
    //解析别名 
    Optional<String> alias = aliasExpressionParser.parseTableAlias();
    //如果是单表,或者能根据逻辑实体表名获取到表规则等其他条件
    if (isSingleTableOnly || shardingRule.tryFindTableRuleByLogicTable(tableName).isPresent() || shardingRule.findBindingTableRule(tableName).isPresent()
            || shardingRule.getShardingDataSourceNames().getDataSourceNames().contains(shardingRule.getShardingDataSourceNames().getDefaultDataSourceName())) {
        //表的信息记录在返回值里,有数据库的schema名称,表名称,开始位置坐标
        sqlStatement.getSqlTokens().add(new TableToken(beginPosition, skippedSchemaNameLength, literals));
        //表名和别名记录下来
        sqlStatement.getTables().add(new Table(tableName, alias));
    }
    //解析是否强制索引,insert语句直接跳过,不会使用
    parseForceIndex(tableName, sqlStatement);
    //表关联解析,直接跳过
    parseJoinTable(sqlStatement);
    //如果参数是单表,且解析结果不是单表,则直接抛异常
    if (isSingleTableOnly && !sqlStatement.getTables().isSingleTable()) {
        throw new UnsupportedOperationException("Cannot support Multiple-Table.");
    }
}

insert into table具体如何解析结束看完了,接下来,到解析表字段了,InsertColumnsClauseParser出场了。在InsertColumnsClauseParser处理逻辑中,从(开始,解析一个又一个的分词,直到碰到)或者结束分词。如果没有(,则获取全部字段。

public void parse(final InsertStatement insertStatement, final ShardingMetaData shardingMetaData) {
        Collection<Column> result = new LinkedList<>();
        //获取表名
        String tableName = insertStatement.getTables().getSingleTableName();
        //获取需要自动生成字段的值
        Optional<Column> generateKeyColumn = shardingRule.getGenerateKeyColumn(tableName);
        int count = 0;
        //如果是`(`
        if (lexerEngine.equalAny(Symbol.LEFT_PAREN)) {
            do {
                //获取下一个分词,就是获取insert into table(field1,filed2...)中`(`后面的fileld1,或者是`,`后面的field2.
                lexerEngine.nextToken();
                //具体解析里面的字段,返回值name为具体属性
                SQLExpression sqlExpression = basicExpressionParser.parse(insertStatement);
                //根据不同类型,获取列属性名称
                String columnName = null;
                if (sqlExpression instanceof SQLPropertyExpression) {
                    columnName = SQLUtil.getExactlyValue(((SQLPropertyExpression) sqlExpression).getName());
                }
                if (sqlExpression instanceof SQLIdentifierExpression) {
                    columnName = SQLUtil.getExactlyValue(((SQLIdentifierExpression) sqlExpression).getName());
                }
                if (sqlExpression instanceof SQLIgnoreExpression) {
                    columnName = SQLUtil.getExactlyValue(((SQLIgnoreExpression) sqlExpression).getExpression());
                }
                //返回值添加列信息
                result.add(new Column(columnName, tableName));
                if (generateKeyColumn.isPresent() && generateKeyColumn.get().getName().equalsIgnoreCase(columnName)) {
                    //如果有需要自动生成列信息的字段,记录字段位置
                    insertStatement.setGenerateKeyColumnIndex(count);
                }
                count++;
            } while (!lexerEngine.equalAny(Symbol.RIGHT_PAREN) && !lexerEngine.equalAny(Assist.END));
            insertStatement.setColumnsListLastPosition(lexerEngine.getCurrentToken().getEndPosition() - lexerEngine.getCurrentToken().getLiterals().length());
            lexerEngine.nextToken();
        } else {
          //忽略若干代码
        }
        insertStatement.getColumns().addAll(result);
}

接下来要解析values后面的值了,惯例是InsertValuesClauseParser,该从句首先判断是否是values分词的语句,如果是,则解析,如果不是则直接跳过,等待后面insert ...set处理。

public void parse(final InsertStatement insertStatement, final ShardingMetaData shardingMetaData) {
    Collection<Keyword> valueKeywords = new LinkedList<>();
    valueKeywords.add(DefaultKeyword.VALUES);
    valueKeywords.addAll(Arrays.asList(getSynonymousKeywordsForValues()));
    //是否是value,或者是values的关键字,如果是则解析,如果不是,则跳过
    if (lexerEngine.skipIfEqual(valueKeywords.toArray(new Keyword[valueKeywords.size()]))) {
        //解析value值
        parseValues(insertStatement);
    }
}

private void parseValues(final InsertStatement insertStatement) {
    int beginPosition = lexerEngine.getCurrentToken().getEndPosition() - lexerEngine.getCurrentToken().getLiterals().length();
    int endPosition;
    insertStatement.getSqlTokens().add(new InsertValuesToken(beginPosition, insertStatement.getTables().getSingleTableName()));
    do {
        beginPosition = lexerEngine.getCurrentToken().getEndPosition() - lexerEngine.getCurrentToken().getLiterals().length();
        //分词器解析必须是`(`开头
        lexerEngine.accept(Symbol.LEFT_PAREN);
        List<SQLExpression> sqlExpressions = new LinkedList<>();
        int columnsCount = 0;
        do {
            //解析每一个分词sql后面的具体值,对应sql中的INSERT INTO `table` (`field1`, `field2`) VALUES (10, 1)的10和1
            sqlExpressions.add(basicExpressionParser.parse(insertStatement));
            //跳过::
            skipsDoubleColon();
            //列个数+1
            columnsCount++;
            //当解析器遇到的分词为,号时,继续解析
        } while (lexerEngine.skipIfEqual(Symbol.COMMA));
        //删除自动生成的键
        removeGenerateKeyColumn(insertStatement, columnsCount);
        columnsCount = 0;
        int parametersCount = 0;
        AndCondition andCondition = new AndCondition();
        for (Column each : insertStatement.getColumns()) {
            SQLExpression sqlExpression = sqlExpressions.get(columnsCount);
            //如果是分片项,则添加Condition
            if (shardingRule.isShardingColumn(each)) {
                andCondition.getConditions().add(new Condition(each, sqlExpression));
            }
            //如果是自动生成的列
            if (insertStatement.getGenerateKeyColumnIndex() == columnsCount) {
                //生成Condition
                insertStatement.getGeneratedKeyConditions().add(createGeneratedKeyCondition(each, sqlExpression));
            }
            //列个数+1
            columnsCount++;
            if (sqlExpression instanceof SQLPlaceholderExpression) {
                parametersCount++;
            }
        }
        endPosition = lexerEngine.getCurrentToken().getEndPosition();
        //分词解析器必须以)结束
        lexerEngine.accept(Symbol.RIGHT_PAREN);
        //组装数据
        insertStatement.getInsertValues().getInsertValues().add(new InsertValue(DefaultKeyword.VALUES, lexerEngine.getInput().substring(beginPosition, endPosition), parametersCount));
        insertStatement.getConditions().getOrCondition().getAndConditions().add(andCondition);
    } while (lexerEngine.skipIfEqual(Symbol.COMMA));
    insertStatement.setInsertValuesListLastPosition(endPosition);
}

如果是insert...set语句,则会走到InsertSetClauseParser,去解析set语句,如果发现不是set开头的语句,直接return。

public void parse(final InsertStatement insertStatement) {
    //判断是否是set,如不是,直接返回
    if (!lexerEngine.skipIfEqual(getCustomizedInsertKeywords())) {
        return;
    }
    //删除无用的Token
    removeUnnecessaryToken(insertStatement);
    int beginPosition = lexerEngine.getCurrentToken().getEndPosition() - lexerEngine.getCurrentToken().getLiterals().length();
    insertStatement.getSqlTokens().add(new InsertValuesToken(beginPosition, insertStatement.getTables().getSingleTableName()));
    int parametersCount = 0;
    do {
        //解析set之后的数据库列名称
        SQLExpression sqlExpression = basicExpressionParser.parse(insertStatement);
        Column column = null;
        if (sqlExpression instanceof SQLPropertyExpression) {
            column = new Column(SQLUtil.getExactlyValue(((SQLPropertyExpression) sqlExpression).getName()), insertStatement.getTables().getSingleTableName());
        }
        if (sqlExpression instanceof SQLIdentifierExpression) {
            column = new Column(SQLUtil.getExactlyValue(((SQLIdentifierExpression) sqlExpression).getName()), insertStatement.getTables().getSingleTableName());
        }
        if (sqlExpression instanceof SQLIgnoreExpression) {
            column = new Column(SQLUtil.getExactlyValue(((SQLIgnoreExpression) sqlExpression).getExpression()), insertStatement.getTables().getSingleTableName());
        }
        //列属性后面必须紧跟=号
        lexerEngine.accept(Symbol.EQ);
        //获取=号下一分词类型,根据类型组装
        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();
            parametersCount++;
        } else {
            throw new UnsupportedOperationException("");
        }
        //获取下一分词
        lexerEngine.nextToken();
        if (lexerEngine.equalAny(Symbol.COMMA, DefaultKeyword.ON, Assist.END)) {
            //组装Condition
            insertStatement.getConditions().add(new Condition(column, sqlExpression), shardingRule);
        } else {
            //跳过,直到出现,on
            lexerEngine.skipUntil(Symbol.COMMA, DefaultKeyword.ON);
        }
    //如果是,结尾,继续循环,解析其他的字段
    } while (lexerEngine.skipIfEqual(Symbol.COMMA));
    //组装返回数据
    int endPosition = lexerEngine.getCurrentToken().getEndPosition() - lexerEngine.getCurrentToken().getLiterals().length();
    insertStatement.getInsertValues().getInsertValues().add(new InsertValue(DefaultKeyword.VALUES, lexerEngine.getInput().substring(beginPosition, endPosition), parametersCount));
    insertStatement.setInsertValuesListLastPosition(endPosition);
}

insert语句解析过程结束,解析过程复杂,能用如此简洁的代码写完,代码功底深厚令人折服。大写的服。但其实还是有一些能优化的点。
最后,再看一下InsertStatement的信息,解析之后,该对象组装了些啥。

InsertStatement.png

对应sql

INSERT INTO `table` (`field1`, `field2`) VALUES (10, 1)
public final class InsertStatement extends DMLStatement {
    //列属性信息
    private final Collection<Column> columns = new LinkedList<>();
    //自动生成的表字段信息
    private List<GeneratedKeyCondition> generatedKeyConditions = new LinkedList<>();
    //插入的值
    private final InsertValues insertValues = new InsertValues();
    //数据库字段属性最后结束的位置
    private int columnsListLastPosition;
    //自动生成键的位置
    private int generateKeyColumnIndex = -1;
    //insert values最后结束的位置
    private int insertValuesListLastPosition;
}

public abstract class AbstractSQLStatement implements SQLStatement {
    //sql类型
    private final SQLType type;
    //表信息
    private final Tables tables = new Tables();
    //分片信息
    private final Conditions conditions = new Conditions();
    //sql分词信息
    private final List<SQLToken> sqlTokens = new LinkedList<>();
    
    private int parametersIndex;

}
InsertStatement.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容