转载

Shading - jdbc 源码分析(三) - sql 解析之 Select

上一篇文章我们分析了SQL解析中会涉及到的一些类,以及用到的一些方法,今天我们分析 Select 语句,来看看sharding-jdbc是如何解析的。

SELECT o.order_id FROM order o WHERE o.user_id = XXXX

我们以上面的查询语句来分析。

SQLParsingEngine#parse() 解析入口:

public SQLStatement parse() {
        //1、获取SQLParser
        SQLParser sqlParser = getSQLParser();
        sqlParser.skipIfEqual(Symbol.SEMI);
        if (sqlParser.equalAny(DefaultKeyword.WITH)) {
            skipWith(sqlParser);
        }
        //2、根据不同的SQL 实例化相应的SQLStatementParser,并调用parse()方法;
        if (sqlParser.equalAny(DefaultKeyword.SELECT)) {
            return SelectParserFactory.newInstance(sqlParser).parse();
        }
        if (sqlParser.equalAny(DefaultKeyword.INSERT)) {
            return InsertParserFactory.newInstance(shardingRule, sqlParser).parse();
        }
        if (sqlParser.equalAny(DefaultKeyword.UPDATE)) {
            return UpdateParserFactory.newInstance(sqlParser).parse();
        }
        if (sqlParser.equalAny(DefaultKeyword.DELETE)) {
            return DeleteParserFactory.newInstance(sqlParser).parse();
        }
        throw new SQLParsingUnsupportedException(sqlParser.getLexer().getCurrentToken().getType());
    }
复制代码
  1. 获取SQLParser,本文以MySQL为例,返回MySQLParser;
private SQLParser getSQLParser() {
        switch (dbType) {
            case H2:
            case MySQL:
                return new MySQLParser(sql, shardingRule);
            case Oracle:
                return new OracleParser(sql, shardingRule);
            case SQLServer:
                return new SQLServerParser(sql, shardingRule);
            case PostgreSQL:
                return new PostgreSQLParser(sql, shardingRule);
            default:
                throw new UnsupportedOperationException(dbType.name());
        }
    }
复制代码
  1. 第一个单词为 SELECT ,所以走SelectParserFactory.newInstance(sqlParser)
/**
     * 判断当前词法标记类型是否与其中一个传入值相等.
     *
     * @param tokenTypes 待判断的词法标记类型
     * @return 是否有相等的词法标记类型
     */
    public final boolean equalAny(final TokenType... tokenTypes) {
        for (TokenType each : tokenTypes) {
            if (each == lexer.getCurrentToken().getType()) {
                return true;
            }
        }
        return false;
    }
复制代码
  1. 入参为MySQLParser,此处返回MySQLSelectParser,最终调用MySQLSelectParser的parse()方法
/**
     * 创建Select语句解析器.
     * 
     * @param sqlParser SQL解析器
     * @return Select语句解析器
     */
    public static AbstractSelectParser newInstance(final SQLParser sqlParser) {
        if (sqlParser instanceof MySQLParser) {
            return new MySQLSelectParser(sqlParser);
        }
        if (sqlParser instanceof OracleParser) {
            return new OracleSelectParser(sqlParser);
        }
        if (sqlParser instanceof SQLServerParser) {
            return new SQLServerSelectParser(sqlParser);
        }
        if (sqlParser instanceof PostgreSQLParser) {
            return new PostgreSQLSelectParser(sqlParser);
        }
        throw new UnsupportedOperationException(String.format("Cannot support sqlParser class [%s].", sqlParser.getClass()));
    } 
复制代码

MySQLSelectParser#parse()方法

public final SelectStatement parse() {
       //解析表名称、查询条件
        query();
        //解析order by
        parseOrderBy();
        customizedSelect();
        appendDerivedColumns();
        appendDerivedOrderBy();
        return selectStatement;
    }
复制代码

query():

@Override
    public void query() {
        if (getSqlParser().equalAny(DefaultKeyword.SELECT)) {
            getSqlParser().getLexer().nextToken();
            //解析distinct
            parseDistinct();
            //跳过一些词
            getSqlParser().skipAll(MySQLKeyword.HIGH_PRIORITY, DefaultKeyword.STRAIGHT_JOIN, MySQLKeyword.SQL_SMALL_RESULT, MySQLKeyword.SQL_BIG_RESULT, MySQLKeyword.SQL_BUFFER_RESULT,
                    MySQLKeyword.SQL_CACHE, MySQLKeyword.SQL_NO_CACHE, MySQLKeyword.SQL_CALC_FOUND_ROWS);
            //解析select items
            parseSelectList();
            //跳到from
            skipToFrom();
        }
        //解析表
        parseFrom();
        //解析where条件
        parseWhere();
        //解析group by
        parseGroupBy();
        //解析 order by 
        parseOrderBy();
        //解析 limit
        parseLimit();
        if (getSqlParser().equalAny(DefaultKeyword.PROCEDURE)) {
            throw new SQLParsingUnsupportedException(getSqlParser().getLexer().getCurrentToken().getType());
        }
        queryRest();
    }
复制代码

由于篇幅有限,只分析parseSelectList、parseFrom、parseWhere、parseOrderBy 这几个跟文初的sql强相关的方法,其他的方法差不多的,相信自己分析会更有收获:blush:

  1. parseSelectList():
protected final void parseSelectList() {
        do {
            //解析SelectItem
            parseSelectItem();
            //循环条件:当前词法为“,”
        } while (sqlParser.skipIfEqual(Symbol.COMMA));
        //设置select最后结束的位置
        selectStatement.setSelectListLastPosition(sqlParser.getLexer().getCurrentToken().getEndPosition() - sqlParser.getLexer().getCurrentToken().getLiterals().length());
    }
复制代码
private void parseSelectItem() {
        //判断当前是否是“ROW_NUMBER”,false
        if (isRowNumberSelectItem()) {
            selectStatement.getItems().add(parseRowNumberSelectItem());
            return;
        }
        sqlParser.skipIfEqual(DefaultKeyword.CONNECT_BY_ROOT);
        String literals = sqlParser.getLexer().getCurrentToken().getLiterals();
        //是否是Select *  false
        if (isStarSelectItem(literals)) {
            selectStatement.getItems().add(parseStarSelectItem());
            return;
        }
        //是否是聚合函数(Max、Sum etc)  false
        if (isAggregationSelectItem()) {
            selectStatement.getItems().add(parseAggregationSelectItem(literals));
            return;
        }
        StringBuilder expression = new StringBuilder();
        Token lastToken = null;
        //循环条件:select后,from前的这部分每个单词
        while (!sqlParser.equalAny(DefaultKeyword.AS) && !sqlParser.equalAny(Symbol.COMMA) && !sqlParser.equalAny(DefaultKeyword.FROM) && !sqlParser.equalAny(Assist.END)) {
            String value = sqlParser.getLexer().getCurrentToken().getLiterals();
            int position = sqlParser.getLexer().getCurrentToken().getEndPosition() - value.length();
            expression.append(value);
            lastToken = sqlParser.getLexer().getCurrentToken();
            sqlParser.getLexer().nextToken();
            //若为“.”,则上一个单词为表的别名,添加表标记对象
            if (sqlParser.equalAny(Symbol.DOT)) {
                selectStatement.getSqlTokens().add(new TableToken(position, value));
            }
        }
        //是否有别名判断(o.xxx) false
        if (hasAlias(expression, lastToken)) {
            selectStatement.getItems().add(parseSelectItemWithAlias(expression, lastToken));
            return;
        }
        //add CommonSelectItem
        selectStatement.getItems().add(new CommonSelectItem(SQLUtil.getExactlyValue(expression.toString()), sqlParser.parseAlias()));
    }
复制代码

这个步骤完成后,SelectStatement的 List < SelectItem> itemssqlTokens 填充完毕.

Shading - jdbc 源码分析(三) - sql 解析之 Select
Shading - jdbc 源码分析(三) - sql 解析之 Select

2. parseFrom()

public final void parseFrom() {
        //跳过FROM
        if (sqlParser.skipIfEqual(DefaultKeyword.FROM)) {
            //解析Table
            parseTable();
        }
    }
复制代码
public void parseTable() {
        //如果含有“(”符号,说明有子查询 false
        if (sqlParser.skipIfEqual(Symbol.LEFT_PAREN)) {
            if (!selectStatement.getTables().isEmpty()) {
                throw new UnsupportedOperationException("Cannot support subquery for nested tables.");
            }
            selectStatement.setContainStar(false);
            //跳过无用的嵌套小括号
            sqlParser.skipUselessParentheses();
            //对子查询解析(子查询其实就是一个完整的Select语句)
            parse();
            sqlParser.skipUselessParentheses();
            if (!selectStatement.getTables().isEmpty()) {
                return;
            }
        }
        //解析表
        parseTableFactor();
        parseJoinTable();
    }
复制代码
protected final void parseTableFactor() {
        int beginPosition = sqlParser.getLexer().getCurrentToken().getEndPosition() - sqlParser.getLexer().getCurrentToken().getLiterals().length();
        //order
        String literals = sqlParser.getLexer().getCurrentToken().getLiterals();
        // o
        sqlParser.getLexer().nextToken();
        // TODO 包含Schema解析
        if (sqlParser.skipIfEqual(Symbol.DOT)) {
            sqlParser.getLexer().nextToken();
            sqlParser.parseAlias();
            return;
        }
        // FIXME 根据shardingRule过滤table
        //添加TableToken 标记
        selectStatement.getSqlTokens().add(new TableToken(beginPosition, literals));
        //添加Table
        selectStatement.getTables().add(new Table(SQLUtil.getExactlyValue(literals), sqlParser.parseAlias()));
    }
复制代码
/**
     * 解析别名. 此处返回o
     *
     * @return 别名
     */
    public Optional<String> parseAlias() {
        if (skipIfEqual(DefaultKeyword.AS)) {
            if (equalAny(Symbol.values())) {
                return Optional.absent();
            }
            String result = SQLUtil.getExactlyValue(getLexer().getCurrentToken().getLiterals());
            getLexer().nextToken();
            return Optional.of(result);
        }
        // o 被标记为IDENTIFIER
        // TODO 增加哪些数据库识别哪些关键字作为别名的配置
        if (equalAny(Literals.IDENTIFIER, Literals.CHARS, DefaultKeyword.USER, DefaultKeyword.END, DefaultKeyword.CASE, DefaultKeyword.KEY, DefaultKeyword.INTERVAL, DefaultKeyword.CONSTRAINT)) {
            // o
            String result = SQLUtil.getExactlyValue(getLexer().getCurrentToken().getLiterals());
            getLexer().nextToken();
            return Optional.of(result);
        }
        return Optional.absent();
    }
复制代码

执行完parseFrom()后,新添加了Table和TableToken标记

Shading - jdbc 源码分析(三) - sql 解析之 Select

3. parseWhere():

protected final void parseWhere() {
        if (selectStatement.getTables().isEmpty()) {
            return;
        }
        //解析查询条件
        sqlParser.parseWhere(selectStatement);
        parametersIndex = sqlParser.getParametersIndex();
    }
复制代码
/**
     * 解析查询条件.
     *
     * @param sqlStatement SQL语句对象
     */
    public final void parseWhere(final SQLStatement sqlStatement) {
        //解析别名
        parseAlias();
        //是否是where 标记
        if (skipIfEqual(DefaultKeyword.WHERE)) {
            parseConditions(sqlStatement);
        }
    }
复制代码
private void parseConditions(final SQLStatement sqlStatement) {
       //循环条件:每个词标记以and分隔(符合where条件的语义)
        do {
            parseComparisonCondition(sqlStatement);
        } while (skipIfEqual(DefaultKeyword.AND));
        if (equalAny(DefaultKeyword.OR)) {
            throw new SQLParsingUnsupportedException(getLexer().getCurrentToken().getType());
        }
    }
复制代码

3-1、解析SQL表达式,主要有下面几种:

Shading - jdbc 源码分析(三) - sql 解析之 Select
我们这里“=”左边的是 SQLPropertyExpression

3-2、判断条件,因为是“=” 所以走的是相等条件

public final void parseComparisonCondition(final SQLStatement sqlStatement) {
        skipIfEqual(Symbol.LEFT_PAREN);
        //1、SQL表达式
        SQLExpression left = parseExpression(sqlStatement);
        //2、判断条件
        if (equalAny(Symbol.EQ)) {
           //处理相等条件的condition
            parseEqualCondition(sqlStatement, left);
            skipIfEqual(Symbol.RIGHT_PAREN);
            return;
        }
        //in条件
        if (equalAny(DefaultKeyword.IN)) {
            parseInCondition(sqlStatement, left);
            skipIfEqual(Symbol.RIGHT_PAREN);
            return;
        }
        //between 条件
        if (equalAny(DefaultKeyword.BETWEEN)) {
            parseBetweenCondition(sqlStatement, left);
            skipIfEqual(Symbol.RIGHT_PAREN);
            return;
        }
        if (equalAny(Symbol.LT, Symbol.GT, Symbol.LT_EQ, Symbol.GT_EQ)) {
            if (left instanceof SQLIdentifierExpression && sqlStatement instanceof SelectStatement
                    && isRowNumberCondition((SelectStatement) sqlStatement, ((SQLIdentifierExpression) left).getName())) {
                parseRowNumberCondition((SelectStatement) sqlStatement);
            } else if (left instanceof SQLPropertyExpression && sqlStatement instanceof SelectStatement
                    && isRowNumberCondition((SelectStatement) sqlStatement, ((SQLPropertyExpression) left).getName())) {
                parseRowNumberCondition((SelectStatement) sqlStatement);
            } else {
                parseOtherCondition(sqlStatement);
            }
        } else if (equalAny(DefaultKeyword.LIKE)) {
            parseOtherCondition(sqlStatement);
        }
        skipIfEqual(Symbol.RIGHT_PAREN);
    }
复制代码
/**
     * 解析表达式.
     *
     * @param sqlStatement SQL语句对象
     * @return 表达式
     */
    public final SQLExpression parseExpression(final SQLStatement sqlStatement) {
        int beginPosition = getLexer().getCurrentToken().getEndPosition();
        SQLExpression result = parseExpression();
        if (result instanceof SQLPropertyExpression) {
            setTableToken(sqlStatement, beginPosition, (SQLPropertyExpression) result);
        }
        return result;
    }
复制代码
/**
     * 解析表达式.
     *
     * @return 表达式
     */
    // TODO 完善Expression解析的各种场景
    public final SQLExpression parseExpression() {
        String literals = getLexer().getCurrentToken().getLiterals();
        //“=”号左边 返回SQLIdentifierExpression
        final SQLExpression expression = getExpression(literals);
        // true
        if (skipIfEqual(Literals.IDENTIFIER)) {
            //true
            if (skipIfEqual(Symbol.DOT)) {
                //user_id
                String property = getLexer().getCurrentToken().getLiterals();
                getLexer().nextToken();
                //最终返回SQLPropertyExpression
                return skipIfCompositeExpression() ? new SQLIgnoreExpression() : new SQLPropertyExpression(new SQLIdentifierExpression(literals), property);
            }
            if (equalAny(Symbol.LEFT_PAREN)) {
                skipParentheses();
                skipRestCompositeExpression();
                return new SQLIgnoreExpression();
            }
            return skipIfCompositeExpression() ? new SQLIgnoreExpression() : expression;
        }
        getLexer().nextToken();
        return skipIfCompositeExpression() ? new SQLIgnoreExpression() : expression;
    }
复制代码
private SQLExpression getExpression(final String literals) {
        if (equalAny(Symbol.QUESTION)) {
            increaseParametersIndex();
            return new SQLPlaceholderExpression(getParametersIndex() - 1);
        }
        if (equalAny(Literals.CHARS)) {
            return new SQLTextExpression(literals);
        }
        if (equalAny(Literals.INT)) {
            return new SQLNumberExpression(NumberUtil.getExactlyNumber(literals, 10));
        }
        if (equalAny(Literals.FLOAT)) {
            return new SQLNumberExpression(Double.parseDouble(literals));
        }
        if (equalAny(Literals.HEX)) {
            return new SQLNumberExpression(NumberUtil.getExactlyNumber(literals, 16));
        }
        //true
        if (equalAny(Literals.IDENTIFIER)) {
            return new SQLIdentifierExpression(SQLUtil.getExactlyValue(literals));
        }
        return new SQLIgnoreExpression();
    }
复制代码

3-3、解析“=”右边的表达式,右边是* SQLNumberExpression

private void parseEqualCondition(final SQLStatement sqlStatement, final SQLExpression left) {
        getLexer().nextToken();
        //SQLNumberExpression
        SQLExpression right = parseExpression(sqlStatement);
        //true
        // TODO 如果有多表,且找不到column是哪个表的,则不加入condition,以后需要解析binding table
        if ((sqlStatement.getTables().isSingleTable() || left instanceof SQLPropertyExpression)
                && (right instanceof SQLNumberExpression || right instanceof SQLTextExpression || right instanceof SQLPlaceholderExpression)) {
            //处理column
            Optional<Column> column = find(sqlStatement.getTables(), left);
            //添加condition
            if (column.isPresent()) {
                sqlStatement.getConditions().add(new Condition(column.get(), right), shardingRule);
            }
        }
    }
复制代码

3-4、只有是分片列才能被添加到condition

/**
     * 添加条件对象.
     *
     * @param condition 条件对象
     * @param shardingRule 分库分表规则配置对象
     */
    // TODO 添加condition时进行判断, 比如:如果以存在 等于操作 的condition, 而已存在包含 =符号 的相同column的condition, 则不添加现有的condition, 而且删除原有condition
    public void add(final Condition condition, final ShardingRule shardingRule) {
        // TODO 自关联有问题,表名可考虑使用别名对应
        if (shardingRule.isShardingColumn(condition.getColumn())) {
            conditions.put(condition.getColumn(), condition);
        }
    }
复制代码

我们设置的分片column是user_id(这里模拟一下),执行完后,我们的SQL赋值结果:

"Column(name=user_id, tableName=order)" -> "Condition(column=Column(name=user_id, tableName=order), operator=EQUAL, positionValueMap={0=1000}, positionIndexMap={})"
复制代码

最终解析完成的 selectStatement 如下:

Shading - jdbc 源码分析(三) - sql 解析之 Select

总结:

SQL解析就是在填充 SQLStatement (SQL语句对象),把表名称、SQL标记、where 条件(分片列,条件是<、>还是=)解析出来,供路由的时候使用,下面再放一张 SQLStatement 的继承关系图:点我查看大图

Shading - jdbc 源码分析(三) - sql 解析之 Select

最后:

小尾巴走一波,欢迎关注我的公众号,不定期分享编程、投资、生活方面的感悟:)

Shading - jdbc 源码分析(三) - sql 解析之 Select
原文  https://juejin.im/post/5bce62f651882577c9322378
正文到此结束
Loading...