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()); } 複製程式碼
- 獲取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()); } } 複製程式碼
- 第一個單詞為 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; } 複製程式碼
- 入參為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:
- 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> items 、 sqlTokens 填充完畢.


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標記

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表示式,主要有下面幾種:

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 如下:

總結:
SQL解析就是在填充 SQLStatement (SQL語句物件),把表名稱、SQL標記、where 條件(分片列,條件是<、>還是=)解析出來,供路由的時候使用,下面再放一張 SQLStatement 的繼承關係圖:點我檢視大圖

最後:
小尾巴走一波,歡迎關注我的公眾號,不定期分享程式設計、投資、生活方面的感悟:)
