前言
上一篇文章我們分析了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強相關的方法,其他的方法差不多的,相信自己分析會更有收穫?
- 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表示式,主要有下面幾種:
我們這裡“=”左邊的是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 如下:
總結:
SQL解析就是在填充SQLStatement(SQL語句物件),把表名稱、SQL標記、where 條件(分片列,條件是<、>還是=)解析出來,供路由的時候使用,下面再放一張 SQLStatement的繼承關係圖:點我檢視大圖
最後:
小尾巴走一波,歡迎關注我的公眾號,不定期分享程式設計、投資、生活方面的感悟:)