《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》

摘要: 原创出处 http://www.iocoder.cn/Sharding-JDBC/sql-parse-2/ 「芋道源码」欢迎转载,保留摘要,谢谢!

本文主要基于 Sharding-JDBC 1.5.0 正式版


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

1. 概述

上篇文章《词法解析》分享了词法解析器Lexer是如何解析 SQL 里的词法。本文分享SQL解析引擎是如何解析与理解 SQL的。因为本文建立在《词法解析》之上,你需要阅读它后在开始这段旅程。🙂如果对词法解析不完全理解,请给我的公众号**(芋道源码留言,我会逐条认真耐心**回复。

区别于 Lexer,Parser 理解SQL

  • 提炼分片上下文
  • 标记需要SQL改写的部分

Parser 有三个组件:

  • SQLParsingEngine :SQL 解析引擎
  • SQLParser :SQL 解析器
  • StatementParser :SQL语句解析器

SQLParsingEngine 调用 StatementParser 解析 SQL。
StatementParser 调用 SQLParser 解析 SQL 表达式。
SQLParser 调用 Lexer 解析 SQL 词法。

😜 是不是觉得 SQLParser 和 StatementParser 看起来很接近?下文为你揭开这个答案。

Sharding-JDBC 正在收集使用公司名单:传送门
🙂 你的登记,会让更多人参与和使用 Sharding-JDBC。传送门
Sharding-JDBC 也会因此,能够覆盖更多的业务场景。传送门
登记吧,骚年!传送门

2. SQLParsingEngine

SQLParsingEngine,SQL 解析引擎。其 #parse() 方法作为 SQL 解析入口,本身不带复杂逻辑,通过调用 SQL 对应的 StatementParser 进行 SQL 解析。

核心代码如下:

// SQLParsingEngine.java
public SQLStatement parse() {
// 获取 SQL解析器
SQLParser sqlParser = getSQLParser();
//
sqlParser.skipIfEqual(Symbol.SEMI); // 跳过 ";"
if (sqlParser.equalAny(DefaultKeyword.WITH)) { // WITH Syntax
skipWith(sqlParser);
}
// 获取对应 SQL语句解析器 解析SQL
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());
}

3. SQLParser SQL解析器

SQLParser,SQL 解析器。和词法解析器 Lexer 一样,不同数据库有不同的实现。

类图如下(包含所有属性和方法)(放大图片):

3.1 AbstractParser

AbstractParser,SQLParser 的抽象父类,对 Lexer 简单封装。例如:

  • #skipIfEqual():判断当前词法标记类型是否与其中一个传入值相等
  • #equalAny():判断当前词法标记类型是否与其中一个传入值相等

这里有一点我们需要注意,SQLParser 并不是等 Lexer 解析完词法( Token ),再根据词法去理解 SQL。而是,在理解 SQL 的过程中,调用 Lexer 进行分词。

// SQLParsingEngine.java#parse()片段
if (sqlParser.equalAny(DefaultKeyword.SELECT)) {
return SelectParserFactory.newInstance(sqlParser).parse();
}

// AbstractParser.java
public final boolean equalAny(final TokenType... tokenTypes) {
for (TokenType each : tokenTypes) {
if (each == lexer.getCurrentToken().getType()) {
return true;
}
}
return false;
}

  • ↑↑↑ 判断当前词法是否为 SELECT。实际 AbstractParser 只知道当前词法,并不知道后面还有哪些词法,也不知道之前有哪些词法。

我们来看 AbstractParser 里比较复杂的方法 #skipParentheses() 帮助大家再理解下。请认真看代码注释噢。

// AbstractParser.java
/**
* 跳过小括号内所有的词法标记.
*
* @return 小括号内所有的词法标记
*/
public final String skipParentheses() {
StringBuilder result = new StringBuilder("");
int count = 0;
if (Symbol.LEFT_PAREN == getLexer().getCurrentToken().getType()) {
final int beginPosition = getLexer().getCurrentToken().getEndPosition();
result.append(Symbol.LEFT_PAREN.getLiterals());
getLexer().nextToken();
while (true) {
if (equalAny(Symbol.QUESTION)) {
increaseParametersIndex();
}
// 到达结尾 或者 匹配合适数的)右括号
if (Assist.END == getLexer().getCurrentToken().getType() || (Symbol.RIGHT_PAREN == getLexer().getCurrentToken().getType() && 0 == count)) {
break;
}
// 处理里面有多个括号的情况,例如:SELECT COUNT(DISTINCT(order_id) FROM t_order
if (Symbol.LEFT_PAREN == getLexer().getCurrentToken().getType()) {
count++;
} else if (Symbol.RIGHT_PAREN == getLexer().getCurrentToken().getType()) {
count--;
}
// 下一个词法