目录
模块分析
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形式结果
在使用之前不要忘记了加入依赖
com.alibaba druid 1.2.6 test
在Druid中,AST节点类型主要包括SQLObject、SQLExpr、SQLStatement三种抽象类型
我这里主要关注在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都可以直接调用这个方法求值@Overridepublic 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 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结构了。