写点什么

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

作者:有财君
  • 2022 年 8 月 10 日
    陕西
  • 本文字数:2043 字

    阅读完需:约 7 分钟

1. 写作由来

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

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

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



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;
复制代码



从上图中可以看出,我们只需要去访问 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 "DropTableStatement{" + "tableName=" + 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()); }
}
复制代码


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


发布于: 刚刚阅读数: 2
用户头像

有财君

关注

还未添加个人签名 2019.02.21 加入

还未添加个人简介

评论

发布
暂无评论
学习Apache ShardingSphere解析器源码(一)_ANTLR_有财君_InfoQ写作社区