我们知道 ShardingSphere 的 SQL 解析器换成了基于 antlr4 实现的 。
这篇文章,我们浅析 shardingsphere 如何定义 antlr4 描述文件,以及如何通过 visitor 模式遍历抽象语法树生成 statment 对象。
shardingsphere 的 antlr4 描述文件,笔者已经放到开源项目模块里,大家有兴趣的话,可以去查看下。
https://github.com/makemyownlife/shardingsphere-jdbc-demo
1. MySQL antlr4 描述文件
shardingsphere 的 解析引擎模块,支持解析不同数据库 ,比如 MySQL 、Oracle 、Postgresql 等,针对不同的数据,都定义不同的 antlr4 描述文件 。
上图是 MySQL 的 antlr4 描述文件,ShardingSphere 将词法和语法文件进行了分离定义 ,我们做一个简单的介绍:
ANTLR约定词法解析规则以大写字母开头,语法解析规则以小写字母开头,关于antlr4的使用方法可参见**https://github.com/antlr/antlr4/blob/master/doc/index.md**
1、词法规则文件
- Alphabet.g4:定义了字母相关的规则。
- Comments.g4:定义了注释相关的规则。
- Keyword.g4:定义了通用的关键字规则。
- Literals.g4:定义了字面量(如字符串、数字等)相关的规则。
- MySQLKeyword.g4:定义了 MySQL 特有的关键字规则。
- Symbol.g4:定义了符号(如运算符、标点符号等)相关的规则。
比如下图是关键字规则 Keyword.g4 :
这段描述内容定义了 MySQL 的关键字的规则,每个规则匹配相应的 SQL 关键字 。比如 匹配关键字 MAX,其中 M, A, X 是从 Alphabet.g4 中导入的规则,匹配单个字母。
2、词法规则文件
我们知道 SQL 有不同的语句类型 ,我们经常使用的比如 DML、DDL 等:
- DML(Data Manipulation Language),数据操作类语句,包括 select 、insert、update、delete、selec for update、call
- DAL(Data Administration Language,数据管理类语句,包括use、show databases、show tables、show colums、show createtable
- DDL(Data Definition Language),数据定义类语句,包括create table、alter table、drop table、truncate table
- TCL(Transaction Control Language),事务控制类语句,包括set transaction、set autocimmit、begin、commit、rollback、saveponit
- DQL(Data Query Language),数据查询类语句,在ShardingSphere的antlr4文件中select属于DML,但部分类中如ShardingDQLResultMerger,将select又称为DQL。
- RL(Replication Language),复制类数据,包括change master to、start slave、stop slave。
因此,shardingsphere 在设计语法规则时,定义了如下的文件。
- BaseRule.g4:基础规则文件,定义了一些通用的语法规则。
- DALStatement.g4:定义了数据查询语言(Data Query Language,DAL)相关的语法规则。
- DCLStatement.g4:定义了数据控制语言(Data Control Language,DCL)相关的语法规则。
- DDLStatement.g4:定义了数据定义语言(Data Definition Language,DDL)相关的语法规则。
- DMLStatement.g4:定义了数据操作语言(Data Manipulation Language,DML)相关的语法规则。
- RLStatement.g4:定义了资源语言(Resource Language,RL)相关的语法规则。
- TCLStatement.g4:定义了事务控制语言(Transaction Control Language,TCL)相关的语法规则。
如上图 DML 描述文件片段,我们可以看到针对 SELECT 语句 、UPDATE 语句 都给出了相关语法描述。 每种语法规则像搭建积木一样,按照特定的规则搭建起来。
2. antlr4自动生成Java对象
为了精简 antlr4 自动生成的 Java 对象,shardingsphere 定义了一个 MySQLStatement.g4 文件。
该文件的内容是:
我们通过 IDEA antlr 插件将 MySQLStatement.g4 配置生成相关代码:
生成的代码如下:
这四个类分别是:
- 词法解析器 MySQLStatementLexer
- 语法解析器 MySQLStatementParser
- 语法访问接口 MySQLStatementVisitor
- 语法访问基础类 MySQLStatementBaseVisitor
我们知道使用 antlr4 的一般流程如下
- 编写 antlr4 的词法和文法规则;
- 使用 antlr4 的生成工具处理写好的规则,以生成指定语言的 Lexer 和 Parser 代码 ;
- 调用生成的 Lexer 和 Parser 类,书写相应的逻辑代码,将原始输入文本转化为一个抽象语法树;
- 使用 antlr4 的 visitor 来解析语法树,实现各种功能 。
因为接下来,我们需要分析 shardingsphere 如何处理两件事情:
- 调用生成的 Lexer 和 Parser 类,书写相应的逻辑代码,将 SQL 语句转化为一个抽象语法树;
- 使用 antlr4 的 visitor 来解析语法树,将语法树转换成用于分片的域模型 statment 。
3. 转化抽象语法树
Apache ShardingSphere 的 SQLParserEngine 是对 ANTLR4 解析的封装和抽象,它会通过 SPI 的方式来加载数据库方言的解析器,用户可以通过 SPI 扩展点对数据方言进行进一步扩展。内部还增加了缓存机制,用来提高性能。
上面的代码有三个要点:
1、 SPI 的方式来加载数据库方言的解析器
当我们指定数据库方言是 MySQL 时,实际上指向的是:MySQLParserFacade 。
我们看到 MySQLParserFacade 是外观模式,将 MySQL 的词法器和语法器都封装起来了。
2、通过反射机制,生成 ANTLR4 的解析器实例 MySQLParser
想要生成 MySQLParser 对象,有两个步骤 :
- 通过 SQL 和 词法解析器生成 tokenStream
- 过 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 包装起来。
4. visitor模式构建域模型statement
对于分库分表来讲,比如 一个 Select 语句,我们要通过该语句通过解析器解析出每个查询字段对应的值,以及这些查询字段是否是分区字段,用于分片逻辑处理。
因此接下来,我们需要使用 antlr4 的 visitor 来解析语法树,将语法树转换成用于分片的域模型 statment。
首先根据数据库类型以及访问器类型来决定采用的访问器,内部也是通过反射的方式来实例化访问器。然后通过 visitor 遍历语法树,返回 ASTNode 并强转为 SQLStatement 。
这里有两个要点,我们一一拆解:
1、定义分库分表使用的域模型
SQLStatement 是一个基础接口,而实现类支持不同的数据库方言的 SELECT 、DELETE 等相关操作。
当我们执行如下 SQL 时 ,
1 | String sql = "SELECT id, name FROM t_user WHERE status = 'ACTIVE' AND age > 18 AND a = 1"; |
生成的 MySQLSelectStatement 如下图:
2、定义 MySQL Visitor
上图,当我们指定数据库方言是 MySQL 时,实际上指向的是:MySQLStatementVisitorFacade 。
MySQLStatementVisitorFacade 是外观模式,将 MySQLStatement 的访问器都封装起来 。
举例子,当我们执行如下 SQL 时 ,
1 | String sql = "SELECT id, name FROM t_user WHERE status = 'ACTIVE' AND age > 18 AND a = 1"; |
shardingsphere 会通过 MySQLDMLStatementVisitor 遍历抽象语法树的各个节点,比如下图:
当访问到表名节点时,将解析出来的表名信息封装到 SimpleTableSegment 对象里。
5. 总结
这篇文章,我们浅析 shardingsphere 使用 antlr4 的流程:
- 编写 antlr4 的词法和文法规则;
- 使用 antlr4 的生成工具处理写好的规则,以生成指定语言的 Lexer 和 Parser 代码 ;
- 调用生成的 Lexer 和 Parser 类,书写相应的逻辑代码,将原始输入文本转化为一个抽象语法树;
- 使用 antlr4 的 visitor 来解析语法树,将抽象语法树转换为域模型 SQLStatement 。
在第三步、第四步中涉及到了 SPI 机制(根据数据库方言动态加载解析器),同时为了让程序具备通用性,抽象了不同的域模型,尽量屏蔽了 antlr 的相关 API 。
在这个过程中涉及到了大量的编写 visitor 逻辑的工作,有非常多的细节,一般场景下,我们了解大概的原理即可。
当我们解析出域模型 statement ,那下一步就是根据分库分表配置,执行路由引擎相关策略了,那我们下篇文章见。