我们知道 ShardingSphere 的 SQL 解析器换成了基于 antlr4 实现的 。

这篇文章,我们浅析 shardingsphere 如何定义 antlr4 描述文件,以及如何通过 visitor 模式遍历抽象语法树生成 statment 对象。

shardingsphere 的 antlr4 描述文件,笔者已经放到开源项目模块里,大家有兴趣的话,可以去查看下。

https://github.com/makemyownlife/shardingsphere-jdbc-demo

img

1. MySQL antlr4 描述文件

shardingsphere 的 解析引擎模块,支持解析不同数据库 ,比如 MySQL 、Oracle 、Postgresql 等,针对不同的数据,都定义不同的 antlr4 描述文件 。

img

上图是 MySQL 的 antlr4 描述文件,ShardingSphere 将词法语法文件进行了分离定义 ,我们做一个简单的介绍:

ANTLR约定词法解析规则以大写字母开头,语法解析规则以小写字母开头,关于antlr4的使用方法可参见**https://github.com/antlr/antlr4/blob/master/doc/index.md**

1、词法规则文件

  1. Alphabet.g4:定义了字母相关的规则。
  2. Comments.g4:定义了注释相关的规则。
  3. Keyword.g4:定义了通用的关键字规则。
  4. Literals.g4:定义了字面量(如字符串、数字等)相关的规则。
  5. MySQLKeyword.g4:定义了 MySQL 特有的关键字规则。
  6. Symbol.g4:定义了符号(如运算符、标点符号等)相关的规则。

比如下图是关键字规则 Keyword.g4 :

img

这段描述内容定义了 MySQL 的关键字的规则,每个规则匹配相应的 SQL 关键字 。比如 匹配关键字 MAX,其中 M, A, X 是从 Alphabet.g4 中导入的规则,匹配单个字母。

2、词法规则文件

我们知道 SQL 有不同的语句类型 ,我们经常使用的比如 DML、DDL 等:

  1. DML(Data Manipulation Language),数据操作类语句,包括 select 、insert、update、delete、selec for update、call
  2. DAL(Data Administration Language,数据管理类语句,包括use、show databases、show tables、show colums、show createtable
  3. DDL(Data Definition Language),数据定义类语句,包括create table、alter table、drop table、truncate table
  4. TCL(Transaction Control Language),事务控制类语句,包括set transaction、set autocimmit、begin、commit、rollback、saveponit
  5. DQL(Data Query Language),数据查询类语句,在ShardingSphere的antlr4文件中select属于DML,但部分类中如ShardingDQLResultMerger,将select又称为DQL。
  6. RL(Replication Language),复制类数据,包括change master to、start slave、stop slave。

因此,shardingsphere 在设计语法规则时,定义了如下的文件。

  1. BaseRule.g4:基础规则文件,定义了一些通用的语法规则。
  2. DALStatement.g4:定义了数据查询语言(Data Query Language,DAL)相关的语法规则。
  3. DCLStatement.g4:定义了数据控制语言(Data Control Language,DCL)相关的语法规则。
  4. DDLStatement.g4:定义了数据定义语言(Data Definition Language,DDL)相关的语法规则。
  5. DMLStatement.g4:定义了数据操作语言(Data Manipulation Language,DML)相关的语法规则。
  6. RLStatement.g4:定义了资源语言(Resource Language,RL)相关的语法规则。
  7. TCLStatement.g4:定义了事务控制语言(Transaction Control Language,TCL)相关的语法规则。

img

如上图 DML 描述文件片段,我们可以看到针对 SELECT 语句 、UPDATE 语句 都给出了相关语法描述。 每种语法规则像搭建积木一样,按照特定的规则搭建起来。

2. antlr4自动生成Java对象

为了精简 antlr4 自动生成的 Java 对象,shardingsphere 定义了一个 MySQLStatement.g4 文件。

img

该文件的内容是:

img

我们通过 IDEA antlr 插件将 MySQLStatement.g4 配置生成相关代码:

img

生成的代码如下:

img

这四个类分别是:

  1. 词法解析器 MySQLStatementLexer
  2. 语法解析器 MySQLStatementParser
  3. 语法访问接口 MySQLStatementVisitor
  4. 语法访问基础类 MySQLStatementBaseVisitor

我们知道使用 antlr4 的一般流程如下

  1. 编写 antlr4 的词法和文法规则;
  2. 使用 antlr4 的生成工具处理写好的规则,以生成指定语言的 Lexer 和 Parser 代码 ;
  3. 调用生成的 Lexer 和 Parser 类,书写相应的逻辑代码,将原始输入文本转化为一个抽象语法树;
  4. 使用 antlr4 的 visitor 来解析语法树,实现各种功能 。

因为接下来,我们需要分析 shardingsphere 如何处理两件事情:

  1. 调用生成的 Lexer 和 Parser 类,书写相应的逻辑代码,将 SQL 语句转化为一个抽象语法树;
  2. 使用 antlr4 的 visitor 来解析语法树,将语法树转换成用于分片的域模型 statment 。

3. 转化抽象语法树

Apache ShardingSphere 的 SQLParserEngine 是对 ANTLR4 解析的封装和抽象,它会通过 SPI 的方式来加载数据库方言的解析器,用户可以通过 SPI 扩展点对数据方言进行进一步扩展。内部还增加了缓存机制,用来提高性能。

img

上面的代码有三个要点:

1、 SPI 的方式来加载数据库方言的解析器

img

当我们指定数据库方言是 MySQL 时,实际上指向的是:MySQLParserFacade 。

img

我们看到 MySQLParserFacade 是外观模式,将 MySQL 的词法器和语法器都封装起来了。

2、通过反射机制,生成 ANTLR4 的解析器实例 MySQLParser

img

想要生成 MySQLParser 对象,有两个步骤 :

  1. 通过 SQL 和 词法解析器生成 tokenStream
  2. 过 tokenStream 和 语法解析器 构造 SQL 解析器

3、twoPhaseParse 是解析的核心

ANTLR4 官方提供的两种解析方式,首先进行快速解析,快速解析失败,会进行常规解析,大部分 SQL 都能够通过快速解析得到结果从而提高解析性能。解析过后,我们便得到了解析树。

Two-stage parsing 是 antlr4 的一个的标准写法https://github.com/antlr/antlr4/issues/374#issuecomment-30952357,关于Two-stage parse更详细的介绍可参见https://www.antlr.org/papers/allstar-techreport.pdf。 这种工程方式可以提升解析性能。

最后解析出来抽象语法树 ParseASTNode 是一个包装类,将 antlr 的对象 ParseTree 、CommonTokenStream 包装起来。

img

4. visitor模式构建域模型statement

对于分库分表来讲,比如 一个 Select 语句,我们要通过该语句通过解析器解析出每个查询字段对应的值,以及这些查询字段是否是分区字段,用于分片逻辑处理。

因此接下来,我们需要使用 antlr4 的 visitor 来解析语法树,将语法树转换成用于分片的域模型 statment。

img

首先根据数据库类型以及访问器类型来决定采用的访问器,内部也是通过反射的方式来实例化访问器。然后通过 visitor 遍历语法树,返回 ASTNode 并强转为 SQLStatement 。

这里有两个要点,我们一一拆解:

1、定义分库分表使用的域模型

img

SQLStatement 是一个基础接口,而实现类支持不同的数据库方言的 SELECT 、DELETE 等相关操作。

当我们执行如下 SQL 时 ,

1
String sql = "SELECT id, name FROM t_user WHERE status = 'ACTIVE' AND age > 18 AND a = 1";

生成的 MySQLSelectStatement 如下图:

img

2、定义 MySQL Visitor

img

上图,当我们指定数据库方言是 MySQL 时,实际上指向的是:MySQLStatementVisitorFacade 。

MySQLStatementVisitorFacade 是外观模式,将 MySQLStatement 的访问器都封装起来 。

img

举例子,当我们执行如下 SQL 时 ,

1
String sql = "SELECT id, name FROM t_user WHERE status = 'ACTIVE' AND age > 18 AND a = 1";

shardingsphere 会通过 MySQLDMLStatementVisitor 遍历抽象语法树的各个节点,比如下图:

img

当访问到表名节点时,将解析出来的表名信息封装到 SimpleTableSegment 对象里。

5. 总结

这篇文章,我们浅析 shardingsphere 使用 antlr4 的流程:

  1. 编写 antlr4 的词法和文法规则;
  2. 使用 antlr4 的生成工具处理写好的规则,以生成指定语言的 Lexer 和 Parser 代码 ;
  3. 调用生成的 Lexer 和 Parser 类,书写相应的逻辑代码,将原始输入文本转化为一个抽象语法树;
  4. 使用 antlr4 的 visitor 来解析语法树,将抽象语法树转换为域模型 SQLStatement 。

在第三步、第四步中涉及到了 SPI 机制(根据数据库方言动态加载解析器),同时为了让程序具备通用性,抽象了不同的域模型,尽量屏蔽了 antlr 的相关 API 。

在这个过程中涉及到了大量的编写 visitor 逻辑的工作,有非常多的细节,一般场景下,我们了解大概的原理即可。

当我们解析出域模型 statement ,那下一步就是根据分库分表配置,执行路由引擎相关策略了,那我们下篇文章见。


本站由 卡卡龙 使用 Stellar 1.27.0 主题创建

本站访问量 次. 本文阅读量 次.