Select 抽象语法树
打开 SQLSelectStatement 的代码,扫描它的子成员,便分析出这样的一棵语法树:
这意味着,在 Druid 眼里,它是这样看待一条 Select 语句的所有成员部分的。
Visitor
从 demo 代码中可以看到,有了 AST 语法树后,则需要一个 visitor 来访问它
// 使用visitor来访问AST
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
statement.accept(visitor);
System.out.println(visitor.getColumns());
System.out.println(visitor.getOrderByColumns());
statement 调用 accept 方法,以 visitor 作为参数,开始了访问之旅。在这里 statement 的实际类型是 SQLSelectStatement 。
在 Druid 中,一条 SQL 语句中的元素,无论是高层次还是低层次的元素,都是一个 SQLObject ,statement 是一种 SQLObject,表达式 expr 也是一种 SQLObject,函数、字段、条件等等,这些都是一种 SQLObject,SQLObject 是一个接口, accept 方法便是它定义的,目的是为了让访问者在访问 SQLObject 时,告知访问者一些事情,好让访问者在访问的过程中能够收集到关于该 SQLObject 的一些信息。
具体的 accept() 实现,在 SQLObjectImpl 这个类中,代码如下所示:
public final void accept(SQLASTVisitor visitor) {
if (visitor == null) {
throw new IllegalArgumentException();
}
visitor.preVisit(this);
accept0(visitor);
visitor.postVisit(this);
}
这是一个 final 方法,意味着所有的子类都要遵循这个模板,首先 accept 方法前和后,visitor 都会做一些工作。真正的访问流程定义在 accept0() 方法里,而它是一个 抽象方法 。
因此要知道 Druid 中是如何访问 AST 的,先拿 SQLSelectStatement 的 accept0() 方法来探探究竟。
protected void accept0(SQLASTVisitor visitor) {
if (visitor.visit(this)) {
acceptChild(visitor, this.select);
}
visitor.endVisit(this);
}
首先,使 visitor 访问自己,访问自己后,visitor 会决定是否还要访问自己的子元素。
打开 MySqlSchemaStateVisitor 的 visit 方法,可以看到,visitor 做了一些事,初始化了自己的 aliasMap,然后 return true,这意味着还要访问 SQLSelectStatement 的子节点。
public boolean visit(SQLSelectStatement x) {
setAliasMap();
return true;
}
接下来访问子元素
protected final void acceptChild(SQLASTVisitor visitor, SQLObject child) {
if (child == null) {
return;
}
child.accept(visitor);
}
由此可以看出,SQLObject 负责通知 visitor 要访问自己的哪些元素,而 visitor 则负责访问相应元素前,中,后三个过程的逻辑处理。