查看原文
其他

Sharding-JDBC 源码分析 —— SQL 解析(二)之SQL解析

2017-07-29 王文斌(芋艿) 芋道源码

🙂🙂🙂关注微信公众号:【芋艿的后端小屋】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表

  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址

  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢

  4. 新的源码解析文章实时收到通知。每周更新一篇左右

  5. 认真的源码交流微信群。


  • 1. 概述

  • 2. SQLParsingEngine

  • 3. SQLParser SQL解析器

    • 3.2.1 #parseExpression() 和 SQLExpression

    • 3.2.2 #parseAlias()

    • 3.2.3 #parseSingleTable()

    • 3.2.4 #skipJoin()

    • 3.2.5 #parseWhere()

    • 3.1 AbstractParser

    • 3.2 SQLParser

  • 4. StatementParser SQL语句解析器

    • 4.1 StatementParser

    • 4.2 Statement

  • 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 解析。

核心代码如下:

  1. // SQLParsingEngine.java

  2. public SQLStatement parse() {

  3.   // 获取 SQL解析器

  4.   SQLParser sqlParser = getSQLParser();

  5.   //

  6.   sqlParser.skipIfEqual(Symbol.SEMI); // 跳过 ";"

  7.   if (sqlParser.equalAny(DefaultKeyword.WITH)) { // WITH Syntax

  8.       skipWith(sqlParser);

  9.   }

  10.   // 获取对应 SQL语句解析器 解析SQL

  11.   if (sqlParser.equalAny(DefaultKeyword.SELECT)) {

  12.       return SelectParserFactory.newInstance(sqlParser).parse();

  13.   }

  14.   if (sqlParser.equalAny(DefaultKeyword.INSERT)) {

  15.       return InsertParserFactory.newInstance(shardingRule, sqlParser).parse();

  16.   }

  17.   if (sqlParser.equalAny(DefaultKeyword.UPDATE)) {

  18.       return UpdateParserFactory.newInstance(sqlParser).parse();

  19.   }

  20.   if (sqlParser.equalAny(DefaultKeyword.DELETE)) {

  21.       return DeleteParserFactory.newInstance(sqlParser).parse();

  22.   }

  23.   throw new SQLParsingUnsupportedException(sqlParser.getLexer().getCurrentToken().getType());

  24. }

3. SQLParser SQL解析器

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

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

3.1 AbstractParser

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

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

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

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

  1. // SQLParsingEngine.java#parse()片段

  2. if (sqlParser.equalAny(DefaultKeyword.SELECT)) {

  3.    return SelectParserFactory.newInstance(sqlParser).parse();

  4. }

  5. // AbstractParser.java

  6. public final boolean equalAny(final TokenType... tokenTypes) {

  7.   for (TokenType each : tokenTypes) {

  8.       if (each == lexer.getCurrentToken().getType()) {

  9.           return true;

  10.       }

  11.   }

  12.   return false;

  13. }

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

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

  1. // AbstractParser.java

  2. /**

  3. * 跳过小括号内所有的词法标记.

  4. *

  5. * @return 小括号内所有的词法标记

  6. */

  7. public final String skipParentheses() {

  8.   StringBuilder result = new StringBuilder("");

  9.   int count = 0;

  10.   if (Symbol.LEFT_PAREN == getLexer().getCurrentToken().getType()) {

  11.       final int beginPosition = getLexer().getCurrentToken().getEndPosition();

  12.       result.append(Symbol.LEFT_PAREN.getLiterals());

  13.       getLexer().nextToken();

  14.       while (true) {

  15.           if (equalAny(Symbol.QUESTION)) {

  16.               increaseParametersIndex();

  17.           }

  18.           // 到达结尾 或者 匹配合适数的)右括号

  19.           if (Assist.END == getLexer().getCurrentToken().getType() || (Symbol.RIGHT_PAREN == getLexer().getCurrentToken().getType() && 0 == count)) {

  20.               break;

  21.           }

  22.           // 处理里面有多个括号的情况,例如:SELECT COUNT(DISTINCT(order_id) FROM t_order

  23.           if (Symbol.LEFT_PAREN == getLexer().getCurrentToken().getType()) {

  24.               count++;

  25.           } else if (Symbol.RIGHT_PAREN == getLexer().getCurrentToken().getType()) {

  26.               count--;

  27.           }

  28.           // 下一个词法

  29.           getLexer().nextToken();

  30.       }

  31.       // 获得括号内的内容

  32.       result.append(getLexer().getInput().substring(beginPosition, getLexer().getCurrentToken().getEndPosition()));

  33.       // 下一个词法

  34.       getLexer().nextToken();

  35.   }

  36.   return result.toString();

  37. }

这个类其它方法很重要,逻辑相对简单,我们就不占用篇幅了。大家一定要看哟,后面调用非常非常多。AbstractParser.java 传送门。👼也可以关注我的公众号(芋艿的后端小屋)发送关键字【sjdbc】获取增加方法内注释的项目地址

3.2 SQLParser

SQLParser,SQL 解析器,主要提供只考虑 SQL 块的解析方法,不考虑 SQL 上下文。下文即将提到的 StatementParser 将 SQL 拆成对应的,调用 SQLParser 进行解析。🤓 这么说,可能会有些抽象,我们下面来一起看。

SQLParser 看起来方法特别多,合并下一共 5 种:

方法说明
#parseExpression()解析表达式
#parseAlias()解析别名
#parseSingleTable()解析单表
#skipJoin()跳过表关联词法
#parseWhere()解析查询条件

看了这 5 个方法是否有点理解了?SQLParser 不考虑 SQL 是 SELECT / INSERT / UPDATE / DELETE ,它考虑的是,给我的是 WHERE 处解析查询条件,或是 INSERT INTO 解析单表 等,提供 SELECT / INSERT / UPDATE / DELETE 需要的 SQL 块公用解析。

3.2.1 #parseExpression() 和 SQLExpression

SQLExpression,SQL表达式接口。目前 6 种实现:

说明对应Token
SQLIdentifierExpression标识表达式Literals.IDENTIFIER
SQLPropertyExpression属性表达式
SQLNumberExpression数字表达式Literals.INT, Literals.HEX
SQLPlaceholderExpression占位符表达式Symbol.QUESTION
SQLTextExpression字符表达式Literals.CHARS
SQLIgnoreExpression分片中无需关注的SQL表达式

  • SQLPropertyExpression 例如: SELECT*FROM t_order o ORDER BY o.order_id 中的 o.order_idSQLPropertyExpression 从 SQLIdentifierExpression 进一步判断解析而来。 

  • SQLIgnoreExpression 例如: SELECT*FROM t_order o ORDER BY o.order_id%2 中的 o.order_id%2复合表达式都会解析成 SQLIgnoreExpression。

解析 SQLExpression 核心代码如下:

  1. // SQLParser.java

  2. /**

  3. * 解析表达式.

  4. *

  5. * @return 表达式

  6. */

  7. // TODO 完善Expression解析的各种场景

  8. public final SQLExpression parseExpression() {

  9.   // 解析表达式

  10.   String literals = getLexer().getCurrentToken().getLiterals();

  11.   final SQLExpression expression = getExpression(literals);

  12.   // SQLIdentifierExpression 需要特殊处理。考虑自定义函数,表名.属性情况。

  13.   if (skipIfEqual(Literals.IDENTIFIER)) {

  14.       if (skipIfEqual(Symbol.DOT)) { // 例如,ORDER BY o.uid 中的 "o.uid"

  15.           String property = getLexer().getCurrentToken().getLiterals();

  16.           getLexer().nextToken();

  17.           return skipIfCompositeExpression() ? new SQLIgnoreExpression() : new SQLPropertyExpression(new SQLIdentifierExpression(literals), property);

  18.       }

  19.       if (equalAny(Symbol.LEFT_PAREN)) { // 例如,GROUP BY DATE(create_time) 中的 "DATE(create_time)"

  20.           skipParentheses();

  21.           skipRestCompositeExpression();

  22.           return new SQLIgnoreExpression();

  23.       }

  24.       return skipIfCompositeExpression() ? new SQLIgnoreExpression() : expression;

  25.   }

  26.   getLexer().nextToken();

  27.   return skipIfCompositeExpression() ? new SQLIgnoreExpression() : expression;

  28. }

  29. /**

  30. * 获得 词法Token 对应的 SQLExpression

  31. *

  32. * @param literals 词法字面量标记

  33. * @return SQLExpression

  34. */

  35. private SQLExpression getExpression(final String literals) {

  36.   if (equalAny(Symbol.QUESTION)) {

  37.       increaseParametersIndex();

  38.       return new SQLPlaceholderExpression(getParametersIndex() - 1);

  39.   }

  40.   if (equalAny(Literals.CHARS)) {

  41.       return new SQLTextExpression(literals);

  42.   }

  43.   // TODO 考虑long的情况

  44.   if (equalAny(Literals.INT)) {

  45.       return new SQLNumberExpression(Integer.parseInt(literals));

  46.   }

  47.   if (equalAny(Literals.FLOAT)) {

  48.       return new SQLNumberExpression(Double.parseDouble(literals));

  49.   }

  50.   // TODO 考虑long的情况

  51.   if (equalAny(Literals.HEX)) {

  52.       return new SQLNumberExpression(Integer.parseInt(literals, 16));

  53.   }

  54.   if (equalAny(Literals.IDENTIFIER)) {

  55.       return new SQLIdentifierExpression(SQLUtil.getExactlyValue(literals));

  56.   }

  57.   return new SQLIgnoreExpression();

  58. }

  59. /**

  60. * 如果是 复合表达式,跳过。

  61. *

  62. * @return 是否跳过

  63. */

  64. private boolean skipIfCompositeExpression() {

  65.   if (equalAny(Symbol.PLUS, Symbol.SUB, Symbol.STAR, Symbol.SLASH, Symbol.PERCENT, Symbol.AMP, Symbol.BAR, Symbol.DOUBLE_AMP, Symbol.DOUBLE_BAR, Symbol.CARET, Symbol.DOT, Symbol.LEFT_PAREN)) {

  66.       skipParentheses();

  67.       skipRestCompositeExpression();

  68.       return true;

  69.   }

  70.   return false;

  71. }

  72. /**

  73. * 跳过剩余复合表达式

  74. */

  75. private void skipRestCompositeExpression() {

  76.   while (skipIfEqual(Symbol.PLUS, Symbol.SUB, Symbol.STAR, Symbol.SLASH, Symbol.PERCENT, Symbol.AMP, Symbol.BAR, Symbol.DOUBLE_AMP, Symbol.DOUBLE_BAR, Symbol.CARET, Symbol.DOT)) {

  77.       if (equalAny(Symbol.QUESTION)) {

  78.           increaseParametersIndex();

  79.       }

  80.       getLexer().nextToken();

  81.       skipParentheses();

  82.   }

  83. }

解析了 SQLExpression 有什么用呢?我们会在《查询SQL解析》、《插入SQL解析》、《更新SQL解析》、《删除SQL解析》。留个悬念😈,关注我的公众号(芋艿的后端小屋)实时收到新文更新通知

3.2.2 #parseAlias()

  1. /**

  2. * 解析别名.不仅仅是字段的别名,也可以是表的别名。

  3. *

  4. * @return 别名

  5. */

  6. public Optional<String> parseAlias() {

  7.   // 解析带 AS 情况

  8.   if (skipIfEqual(DefaultKeyword.AS)) {

  9.       if (equalAny(Symbol.values())) {

  10.           return Optional.absent();

  11.       }

  12.       String result = SQLUtil.getExactlyValue(getLexer().getCurrentToken().getLiterals());

  13.       getLexer().nextToken();

  14.       return Optional.of(result);

  15.   }

  16.   // 解析别名

  17.   // TODO 增加哪些数据库识别哪些关键字作为别名的配置

  18.   if (equalAny(Literals.IDENTIFIER, Literals.CHARS, DefaultKeyword.USER, DefaultKeyword.END, DefaultKeyword.CASE, DefaultKeyword.KEY, DefaultKeyword.INTERVAL, DefaultKeyword.CONSTRAINT)) {

  19.       String result = SQLUtil.getExactlyValue(getLexer().getCurrentToken().getLiterals());

  20.       getLexer().nextToken();

  21.       return Optional.of(result);

  22.   }

  23.   return Optional.absent();

  24. }

3.2.3 #parseSingleTable()

  1. /**

  2. * 解析单表.

  3. *

  4. * @param sqlStatement SQL语句对象

  5. */

  6. public final void parseSingleTable(final SQLStatement sqlStatement) {

  7.   boolean hasParentheses = false;

  8.   if (skipIfEqual(Symbol.LEFT_PAREN)) {

  9.       if (equalAny(DefaultKeyword.SELECT)) { // multiple-update 或者 multiple-delete

  10.           throw new UnsupportedOperationException("Cannot support subquery");

  11.       }

  12.       hasParentheses = true;

  13.   }

  14.   Table table;

  15.   final int beginPosition = getLexer().getCurrentToken().getEndPosition() - getLexer().getCurrentToken().getLiterals().length();

  16.   String literals = getLexer().getCurrentToken().getLiterals();

  17.   getLexer().nextToken();

  18.   if (skipIfEqual(Symbol.DOT)) {

  19.       getLexer().nextToken();

  20.       if (hasParentheses) {

  21.           accept(Symbol.RIGHT_PAREN);

  22.       }

  23.       table = new Table(SQLUtil.getExactlyValue(literals), parseAlias());

  24.   } else {

  25.       if (hasParentheses) {

  26.           accept(Symbol.RIGHT_PAREN);

  27.       }

  28.       table = new Table(SQLUtil.getExactlyValue(literals), parseAlias());

  29.   }

  30.   if (skipJoin()) { // multiple-update 或者 multiple-delete

  31.       throw new UnsupportedOperationException("Cannot support Multiple-Table.");

  32.   }

  33.   sqlStatement.getSqlTokens().add(new TableToken(beginPosition, literals));

  34.   sqlStatement.getTables().add(table);

  35. }

3.2.4 #skipJoin()

跳过表关联词法,支持 SELECT*FROM t_user,t_order WHERE..., SELECT*FROM t_user JOIN t_order ON...。下篇《查询SQL解析》解析表会用到这个方法。

  1. // SQLParser.java

  2. /**

  3. * 跳过表关联词法.

  4. *

  5. * @return 是否表关联.

  6. */

  7. public final boolean skipJoin() {

  8.   if (skipIfEqual(DefaultKeyword.LEFT, DefaultKeyword.RIGHT, DefaultKeyword.FULL)) {

  9.       skipIfEqual(DefaultKeyword.OUTER);

  10.       accept(DefaultKeyword.JOIN);

  11.       return true;

  12.   } else if (skipIfEqual(DefaultKeyword.INNER)) {

  13.       accept(DefaultKeyword.JOIN);

  14.       return true;

  15.   } else if (skipIfEqual(DefaultKeyword.JOIN, Symbol.COMMA, DefaultKeyword.STRAIGHT_JOIN)) {

  16.       return true;

  17.   } else if (skipIfEqual(DefaultKeyword.CROSS)) {

  18.       if (skipIfEqual(DefaultKeyword.JOIN, DefaultKeyword.APPLY)) {

  19.           return true;

  20.       }

  21.   } else if (skipIfEqual(DefaultKeyword.OUTER)) {

  22.       if (skipIfEqual(DefaultKeyword.APPLY)) {

  23.           return true;

  24.       }

  25.   }

  26.   return false;

  27. }

3.2.5 #parseWhere()

解析 WHERE 查询条件。目前支持 AND 条件,不支持 OR 条件。近期 OR 条件支持的可能性比较低。另外条件这块对括号解析需要继续优化,实际使用请勿写冗余的括号。例如: SELECT*FROM tbl_name1 WHERE((val1=?)AND(val2=?))AND val3=?

根据不同的运算操作符,分成如下情况:

运算符附加条件方法
=
#parseEqualCondition()
IN
#parseInCondition()
BETWEEN
#parseBetweenCondition()
<, <=, >, >=Oracle 或 SQLServer 分页#parseRowNumberCondition()
<, <=, >, >=
#parseOtherCondition()
LIKE
parseOtherCondition

代码如下:

  1. // SQLParser.java

  2. /**

  3. * 解析所有查询条件。

  4. * 目前不支持 OR 条件。

  5. *

  6. * @param sqlStatement SQL

  7. */

  8. private void parseConditions(final SQLStatement sqlStatement) {

  9.   // AND 查询

  10.   do {

  11.       parseComparisonCondition(sqlStatement);

  12.   } while (skipIfEqual(DefaultKeyword.AND));

  13.   // 目前不支持 OR 条件

  14.   if (equalAny(DefaultKeyword.OR)) {

  15.       throw new SQLParsingUnsupportedException(getLexer().getCurrentToken().getType());

  16.   }

  17. }

  18. // TODO 解析组合expr

  19. /**

  20. * 解析单个查询条件

  21. *

  22. * @param sqlStatement SQL

  23. */

  24. public final void parseComparisonCondition(final SQLStatement sqlStatement) {

  25.   skipIfEqual(Symbol.LEFT_PAREN);

  26.   SQLExpression left = parseExpression(sqlStatement);

  27.   if (equalAny(Symbol.EQ)) {

  28.       parseEqualCondition(sqlStatement, left);

  29.       skipIfEqual(Symbol.RIGHT_PAREN);

  30.       return;

  31.   }

  32.   if (equalAny(DefaultKeyword.IN)) {

  33.       parseInCondition(sqlStatement, left);

  34.       skipIfEqual(Symbol.RIGHT_PAREN);

  35.       return;

  36.   }

  37.   if (equalAny(DefaultKeyword.BETWEEN)) {

  38.       parseBetweenCondition(sqlStatement, left);

  39.       skipIfEqual(Symbol.RIGHT_PAREN);

  40.       return;

  41.   }

  42.   if (equalAny(Symbol.LT, Symbol.GT, Symbol.LT_EQ, Symbol.GT_EQ)) {

  43.       if (left instanceof SQLIdentifierExpression && sqlStatement instanceof SelectStatement

  44.               && isRowNumberCondition((SelectStatement) sqlStatement, ((SQLIdentifierExpression) left).getName())) {

  45.           parseRowNumberCondition((SelectStatement) sqlStatement);

  46.       } else if (left instanceof SQLPropertyExpression && sqlStatement instanceof SelectStatement

  47.               && isRowNumberCondition((SelectStatement) sqlStatement, ((SQLPropertyExpression) left).getName())) {

  48.           parseRowNumberCondition((SelectStatement) sqlStatement);

  49.       } else {

  50.           parseOtherCondition(sqlStatement);

  51.       }

  52.   } else if (equalAny(DefaultKeyword.LIKE)) {

  53.       parseOtherCondition(sqlStatement);

  54.   }

  55.   skipIfEqual(Symbol.RIGHT_PAREN);

  56. }

#parseComparisonCondition() 解析到 SQL表达式(left) 和 运算符,调用相应方法进一步处理。我们选择 #parseEqualCondition() 看下,其他方法有兴趣跳转 SQLParser 查看。

  1. // SQLParser.java

  2. /**

  3. * 解析 = 条件

  4. *

  5. * @param sqlStatement SQL

  6. * @param left 左SQLExpression

  7. */

  8. private void parseEqualCondition(final SQLStatement sqlStatement, final SQLExpression left) {

  9.   getLexer().nextToken();

  10.   SQLExpression right = parseExpression(sqlStatement);

  11.   // 添加列

  12.   // TODO 如果有多表,且找不到column是哪个表的,则不加入condition,以后需要解析binding table

  13.   if ((sqlStatement.getTables().isSingleTable() || left instanceof SQLPropertyExpression)

  14.           // 只有对路由结果有影响的才会添加到 conditions。SQLPropertyExpression 和 SQLIdentifierExpression 无法判断,所以未加入 conditions

  15.           && (right instanceof SQLNumberExpression || right instanceof SQLTextExpression || right instanceof SQLPlaceholderExpression)) {

  16.       Optional<Column> column = find(sqlStatement.getTables(), left);

  17.       if (column.isPresent()) {

  18.           sqlStatement.getConditions().add(new Condition(column.get(), right), shardingRule);

  19.       }

  20.   }

  21. }

#parseEqualCondition() 解析到 SQL表达式(right),并判断 左右SQL表达式 与路由逻辑是否有影响,如果有,则加入到 Condition。这个就是 #parseWhere() 的目的:解析 WHERE 查询条件对路由有影响的条件。《路由》相关的逻辑,会单独开文章介绍。这里,我们先留有映像。

4. StatementParser SQL语句解析器

4.1 StatementParser

StatementParser,SQL语句解析器。每种 SQL,都有相应的 SQL语句解析器实现。不同数据库,继承这些 SQL语句解析器,实现各自 SQL 上的差异。大体结构如下:

SQLParsingEngine 根据不同 SQL 调用对应工厂创建 StatementParser。核心代码如下:

  1. public final class SelectParserFactory {

  2.    /**

  3.     * 创建Select语句解析器.

  4.     *

  5.     * @param sqlParser SQL解析器

  6.     * @return Select语句解析器

  7.     */

  8.    public static AbstractSelectParser newInstance(final SQLParser sqlParser) {

  9.        if (sqlParser instanceof MySQLParser) {

  10.            return new MySQLSelectParser(sqlParser);

  11.        }

  12.        if (sqlParser instanceof OracleParser) {

  13.            return new OracleSelectParser(sqlParser);

  14.        }

  15.        if (sqlParser instanceof SQLServerParser) {

  16.            return new SQLServerSelectParser(sqlParser);

  17.        }

  18.        if (sqlParser instanceof PostgreSQLParser) {

  19.            return new PostgreSQLSelectParser(sqlParser);

  20.        }

  21.        throw new UnsupportedOperationException(String.format("Cannot support sqlParser class [%s].", sqlParser.getClass()));

  22.    }

  23. }

调用 StatementParser#parse() 实现方法,对 SQL 进行解析。具体解析过程,另开文章分享。

4.2 Statement

不同 SQL 解析后,返回对应的 SQL 结果,即 Statement。大体结构如下:

Statement 包含两部分信息:

  • 分片上下文:用于 SQL 路由。


  • SQL 标记对象:用于 SQL 改写。


我们会在后文增删改查SQL解析的过程中分享到它们。

4.3 预告

ParserStatement分享文章
SelectStatementParserSelectStatement + AbstractSQLStatement《查询SQL解析》
InsertStatementParserInsertStatement《插入SQL解析》
UpdateStatementParserUpdateStatement《更新SQL解析》
DeleteStatementParserDeleteStatement《删除SQL解析》

5. 彩蛋

老铁,是不是有丢丢长?
如果有地方错误,烦请指出🙂。
如果有地方不是很理解,可以加我的公众号(芋艿的后端小屋)留言,我会逐条认真耐心回复。
如果觉得还凑合,劳驾分享朋友圈或者基佬。

《查询SQL解析》已经写了一半,预计很快...


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存