目录
模块分析
AST节点类型
SQL词法解析
举个例子
之前写的在第九章写的sql解析太简单了,SQL规范还有复杂的开闭括号以及嵌套查询,复杂SQL几乎不可能通过字符串匹配来实现。
本章以Druid SQL Parser解析SQL为例,进行分析。
模块分析
Druid SQL Parser分三个模块:Parser,AST,Visitor。
parser有包括两个部分,Parser和Lexer,其中Lexer实现词法分析,Parser实现语法分析。
Druid Parser会生成一个AST抽象语法树
Visitor是遍历AST的手段,最后返回json形式结果
在使用之前不要忘记了加入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
<scope>test</scope>
</dependency>
AST节点类型
在Druid中,AST节点类型主要包括SQLObject、SQLExpr、SQLStatement三种抽象类型
- interface SQLObject {}
- interface SQLExpr extends SQLObject {} // 条件表达式相关的抽象,例如select * from table where ID = 3 这里的ID是一个SQLIdentifierExpr
- interface SQLStatement extends SQLObject {} //最常用的Statement当然是SELECT/UPDATE/DELETE/INSERT
SQL词法解析
我这里主要关注在SQLExpr, 因为这个跟条件表达式相关的解析。
常用的SQLExpr有哪些?
package com.alibaba.druid.sql.ast.expr;
// SQLName是一种的SQLExpr的Expr,包括SQLIdentifierExpr、SQLPropertyExpr等
public interface SQLName extends SQLExpr {}
// 例如 ID = 3 这里的ID是一个SQLIdentifierExpr
class SQLIdentifierExpr implements SQLExpr, SQLName {
String name;
}
// 例如 A.ID = 3 这里的A.ID是一个SQLPropertyExpr
class SQLPropertyExpr implements SQLExpr, SQLName {
SQLExpr owner;
String name;
}
// 例如 ID = 3 这是一个SQLBinaryOpExpr
// left是ID (SQLIdentifierExpr)
// right是3 (SQLIntegerExpr)
class SQLBinaryOpExpr implements SQLExpr {
SQLExpr left;
SQLExpr right;
SQLBinaryOperator operator;
}
// 例如 select * from where id = ?,这里的?是一个SQLVariantRefExpr,name是'?'
class SQLVariantRefExpr extends SQLExprImpl {
String name;
}
// 例如 ID = 3 这里的3是一个SQLIntegerExpr
public class SQLIntegerExpr extends SQLNumericLiteralExpr implements SQLValuableExpr {
Number number;
// 所有实现了SQLValuableExpr接口的SQLExpr都可以直接调用这个方法求值
@Override
public Object getValue() {
return this.number;
}
}
// 例如 NAME = 'jobs' 这里的'jobs'是一个SQLCharExpr
public class SQLCharExpr extends SQLTextLiteralExpr implements SQLValuableExpr{
String text;
}
举个例子
对以下代码进行调试
package src;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.util.JdbcConstants;
import java.util.List;
public class main {
public static void main(String[] args) {
String sql = "select * from t where id=1 or name='test' and age=14";
List<SQLStatement> sqlStatements = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL);
System.out.println(sqlStatements);
}
}
可以得到
从最终的结果可以看出来,其实就是一个二叉树,父结点就是一个操作符,然后左右孩子结点就是表达式的左右两边的字段名和对应的值。
而且还可以通过SQLUtils.toSQLString
打印节点
SQLExpr sqlExpr = SQLUtils.toSQLExpr("(id=1 or name='test' and age=14)", JdbcConstants.MYSQL);
System.out.println(SQLUtils.toSQLString(sqlExpr, JdbcConstants.MYSQL));
//id = 1
//OR name = 'test'
//AND age = 14
看到这里我们是不是有一点点思路了,前面我们说SQLUtils产生SQLExpr本质上就是一个二叉树,所以我们可以通过遍历二叉树的方式去获取每个结点,判断结点的类型,然后在把它转成一个我们JSON的一个对象。
那要遍历二叉树,很显然我们这里需要用后序遍历的方式,因为我想从最下往上去遍历,最后遍历根结点,才能把左右两棵树通过操作符合并起来。
可以简单的把树画出来
从图上看出来我们遍历左子树,在遍历condition 1这部分的子树的时候,先遍历ID和1,然后再遍历到父节点的=,叶子节点我们可以不看,我们只要判断到节点是SQLBinaryOperator,我们就可以把他们的左右节点拿出来构成出一个condition 1对象,一样的我们会遍历右子树,遍历出condition 2和condition 3两个对象,然后我们在遍历他们的父节点OR,这个时候我们只需要把它左右子树的两个condition 2 和condition 3放到list中,然后在给他加上一个operator 为OR即可变成一个新的condition 4。然后最后遍历到根结点,就把condition 1 和 condition 4通过AND
连接变成一个condition 5,而这个condition 5就是我们最终的JSON结构了。