当前位置:网站首页>学习Apache ShardingSphere解析器源码(一)

学习Apache ShardingSphere解析器源码(一)

2022-08-10 23:44:00 InfoQ

1. 写作由来

Apache Shardingsphere是一款国产精品中间件。我对这款中间件关注了很久了,刚好最近也业务中需要用到Antlr4来对语言进行解析,作为学习资料,我仔细的研究了ShardingSphere的解析器部分源码。通过对其源码的阅读和仿写,我获得了丰富的Antlr4使用经验,同时,我也尝试着向官方贡献了自己写的一点解析代码,并被官方采纳,也是感到无比的高兴。

2. 以Oracle的DropTable功能为例分析

SharingSphere在其Github上提供了Oracle的Antlr4的g4文件,这个文件中支持了DDL的大部分功能,但是鉴于Oracle本身的功能复杂性,还是有一些功能没有提供的,但是也不影响学习。至于如何用g4文件生成代码,这不是本文的目的,有机会另开一篇来写。这里就默认生成了所有需要的AST和访问器代码。如下图:

null
Antlr4会自动生成这些代码,注意其中的OracleStatementBaseVisitor.java,我们所有的操作都要通过继承它来实现。换句话说,我们需要自己编写遍历AST的逻辑。首先来看看DropTable功能在g4文件中的定义:

dropTable
 : DROP TABLE tableName (CASCADE CONSTRAINTS)? (PURGE)?
 ;
 
tableName
 : (owner DOT_)? name
 ;
 
owner
 : identifier
 ;

name
 : identifier
 ; 
 
identifier
 : IDENTIFIER_ | unreservedWord
 ; 

再看测试看看这样一句SQL会被解析成一棵什么样形态的树:

-- 删除用户tom下的jerry表
drop table tom.jerry;

null
从上图中可以看出,我们只需要去访问tableName这个节点下的所有节点就可以拿到所有的有用信息。解析SQL第一步,我们需要对模型进行抽象。ShardingSphere的策略是所有的对象都实现自一个叫做ASTNode的接口:

public interface ASTNode {}

接下来继续观察上面的树,我们可以发现owner和name都是一个叫做identifier的东西,这个identifier实际上是一个字符串或者是一个用引号包裹起来的字符串。那么为了保存它,需要编写一个类来保存,为了简单,我们就不考虑保存引号了:

public class IdentifierValue implements ASTNode {
 private final String name;

 public IdentifierValue(String name) {
 this.name = name;
 }

 public String getName() {
 return name;
 }
}

这个基本的问题解决之后,就可以开始着手解决如何保存name和owner的问题了,还是观察上图,发现这两个token都是identifier,因此基本上可以不用做额外操作。接下来看看最关键的节点tableName,它包括了两个元素owner和name,其中owner是可选的元素,在知道了这一点之后,我们设计这样一个类:

public class TableName implements ASTNode {
 private IdentifierValue owner;

 private final IdentifierValue name;

 public TableName(IdentifierValue name) {
 this.name = name;
 }

 public void setOwner(IdentifierValue owner) {
 this.owner = owner;
 }

 public Optional<IdentifierValue> getOwner() {
 return Optional.ofNullable(owner);
 }

 public IdentifierValue getName() {
 return name;
 }
}

最后,还需要对语句本身进行抽象:

public class DropTableStatement implements ASTNode {
 private final TableName tableName;

 public DropTableStatement(TableName tableName) {
 this.tableName = tableName;
 }

 public TableName getTableName() {
 return tableName;
 }

 @Override
 public String toString() {
 return &quot;DropTableStatement{&quot; +
 &quot;tableName=&quot; + tableName +
 '}';
 }

}

这样,我们也就具备了将这个语句中所有的元素保存成模型的可能了(这里我忽略了(CASCADE CONSTRAINTS)? (PURGE)?后缀,因为这不过是一个简单的判断)。要将语句转换成模型,需要编写实现visitor逻辑,对这棵树的每一个节点进行遍历操作:

public class OracleDDLStatementVisitor extends OracleStatementBaseVisitor<ASTNode> {
 @Override
 public ASTNode visitDropTable(OracleStatementParser.DropTableContext ctx) {
 return new DropTableStatement((TableName) visit(ctx.tableName()));
 }

 @Override
 public ASTNode visitTableName(OracleStatementParser.TableNameContext ctx) {
 TableName result = new TableName((IdentifierValue) visit(ctx.name()));
 if (Objects.nonNull(ctx.owner())) {
 result.setOwner((IdentifierValue) visit(ctx.owner()));
 }
 return result;
 }

 @Override
 public ASTNode visitIdentifier(OracleStatementParser.IdentifierContext ctx) {
 return new IdentifierValue(ctx.IDENTIFIER_().getText());
 }

}

由于语句比较简单,只需要遍历三个结点就可以了,这就有了上面的逻辑。经过下面这段代码的测试,我的代码是没有问题的。

原网站

版权声明
本文为[InfoQ]所创,转载请带上原文链接,感谢
https://xie.infoq.cn/article/08188e376ee3a3e4c74476631