Shading – jdbc 原始碼分析(三) – sql 解析之 Select

selrain_公眾號也叫selrain發表於2019-02-25

前言

上一篇文章我們分析了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強相關的方法,其他的方法差不多的,相信自己分析會更有收穫?

  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

相關文章