简介
- SQL Parser是Druid的一个重要组成部分,Druid内置使用SQL Parser来实现防御SQL注入(WallFilter)、合并统计没有参数化的SQL(StatFilter的mergeSql)、SQL格式化、分库分表
-
使用场景
- MySql SQL全量统计
- Hive/ODPS SQL执行安全审计
- 分库分表SQL解析引擎
- 数据库引擎的SQL Parser
-
各种语法的支持
-
性能
- Druid的SQL Parser是手工编写,性能非常好,目标就是在生产环境运行时使用的SQL Parser,性能比antlr、javacc之类工具生成的Parser快10倍甚至100倍以上。
-
SQL-92、SQL-99等都是标准SQL,mysql/oracle/pg/sqlserver/odps等都是方言,也就是dialect。parser/ast/visitor都需要针对不同的方言进行特别处理。
代码结构
-
Druid SQL Parser分三个模块:
- Parser
- AST
- Visitor
parser
-
parser是将输入文本转换为ast(抽象语法树)
-
parser有包括两个部分,
- Parser,Parser实现语法分析。
- Lexer,其中Lexer实现词法分析,
AST
-
AST是Abstract Syntax Tree的缩写,也就是抽象语法树。AST是parser输出的结果。下面是获得抽象语法树的一个例子
inifinal String dbType = JdbcConstants.MYSQL; // 可以是ORACLE、POSTGRESQL、SQLSERVER、ODPS等 String sql = "select * from t"; List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);
Visitor
-
Visitor是遍历AST的手段,是处理AST最方便的模式,Visitor是一个接口,有缺省什么都没做的实现VistorAdapter。
-
我们可以实现不同的Visitor来满足不同的需求,Druid内置提供了如下Visitor:
- OutputVisitor用来把AST输出为字符串
- WallVisitor 来分析SQL语意来防御SQL注入攻击
- ParameterizedOutputVisitor用来合并未参数化的SQL进行统计
- EvalVisitor 用来对SQL表达式求值
- ExportParameterVisitor用来提取SQL中的变量参数
- SchemaStatVisitor 用来统计SQL中使用的表、字段、过滤条件、排序表达式、分组表达式
- SQL格式化 Druid内置了基于语义的SQL格式化功能
-
自定义 Vistitor,每种方言的 Visitor 都有一个缺省的 VisitorAdapter,使得编写自定义的 Visitor 更方便。
AST
-
AST是abstract syntax tree的缩写,也就是抽象语法树。和所有的Parser一样,Druid Parser会生成一个抽象语法树
-
主要包括,三种抽象类型
-
SQLObject
-
SQLExpr
-
SLQStatement
-
SQLStatment
-
最常用的Statement当然是SELECT/UPDATE/DELETE/INSERT,他们分别是
- SQLSelectStatement:查询语句
- SQLUpdateStatement: 更新语句
- SQLDeleteStatement:删除语句
- SQLInsertStatement:新增语句
SQLSelectStatement
-
内部 结构
diffSQLSelectStatement --- SQLSelect ------- SQLSelectQuery -------- SQLSelectQueryBlock(单表Sql查询) -------- SQLUnionQuery(union表查询)
SQLExpr
- 主要的实现类
SQLTableSource
-
常见的 SQLTableSource 包括
-
SQLExprTableSource
-
SQLJoinTableSource
-
SQLSubqueryTableSource
-
SQLWithSubqueryCluase.Entry
-
SQL 语句解析实例
查询
解析 Where
-
存在两个类型
- SQLPropertyExpr:有别名的时候的类型
- SQLIdentifierExpr:没有别名的时候的类型
-
判断 where 要
-
注意是 SQLBinaryOpExpr(id= 1) or (u.id =1)需要注意是否使用了别名
-
注意如果只有一个产训添加 where 本身就是一个 SQLBinaryOpExpr,如果是多个就要用 where.getChildren()
- 如果有别名:SQLPropertyExpr(name=id,owerName=u)
- 如果没有别名:SQLIdentifierExpr(name=id)
-
值对象:SQLValuableExpr
-
解析 SQLSelectItem
-
存在两个类型
- SQLPropertyExpr:有别名的时候的类型
- SQLIdentifierExpr:没有别名的时候的类型
-
解析查询的列信息:解析查询字段,注意是否使用了别名.u.id as userId, u.name as userName, u.age as userAge,userId(sqlSelectItem.getAlias)
- 如果有别名: u.id( id = SQLPropertyExpr.getName,u = SQLPropertyExpr.getOwnernName)
- 如果没别名: id(id = SQLIdentifierExpr.name)
源码
java
public static void test_SQLBinaryOpExpr(){
String sql = "select a.id as uId,username from users a where a.id > 1 and name = 'tom' ";
List<SQLStatement> sqlStatements = SQLUtils.parseStatements(sql, DbType.mysql);
SQLStatement statement = sqlStatements.get(0);
SQLSelectStatement selectStatement = (SQLSelectStatement)statement;
SQLSelect sqlSelect = selectStatement.getSelect();
SQLSelectQueryBlock query = (SQLSelectQueryBlock)sqlSelect.getQuery();
//解析 where
parseWhere(query.getWhere());
System.out.println("----------------------");
//解析查询条件
parseSQLSelectItem(query.getSelectList());
}
public static void parseWhere(SQLExpr where) {
if (where instanceof SQLBinaryOpExpr) {
parseSQLBinaryOpExpr(cast(where, SQLBinaryOpExpr.class));
} else {
List<SQLObject> childrenList = where.getChildren();
for (SQLObject sqlObject : childrenList) {
// 包含了 left 和 right
SQLBinaryOpExpr conditionBinary = cast(sqlObject, SQLBinaryOpExpr.class);
parseSQLBinaryOpExpr(conditionBinary);
}
}
}
private static void parseSQLSelectItem(List<SQLSelectItem> selectColumnList){
for (SQLSelectItem sqlSelectItem : selectColumnList) {
// 情况一:u.id as userId(selectColumnAlias)
String selectColumnAlias = sqlSelectItem.getAlias();
// 情况二:u.id = SQLPropertyExpr
SQLExpr expr = sqlSelectItem.getExpr();
//有别名的情况
if (expr instanceof SQLPropertyExpr) {
SQLPropertyExpr selectColumnExpr = cast(expr, SQLPropertyExpr.class);
print("列名:%s,别名:%s,表别名:%s", selectColumnExpr.getName(), selectColumnAlias, selectColumnExpr.getOwnerName());
}
//没有别名的情况
if (expr instanceof SQLIdentifierExpr) {
SQLIdentifierExpr selectColumnExpr = cast(expr, SQLIdentifierExpr.class);
print("列名:%s,别名:%s", selectColumnExpr.getName(), selectColumnAlias);
}
}
}
public static void parseSQLBinaryOpExpr(SQLBinaryOpExpr conditionBinary) {
SQLExpr conditionExpr = conditionBinary.getLeft();
SQLExpr conditionValueExpr = conditionBinary.getRight();
/**
* 如果多个条件就会需要递归去获取数据了
*/
if(conditionExpr instanceof SQLBinaryOpExpr){
parseSQLBinaryOpExpr(cast(conditionExpr,SQLBinaryOpExpr.class));
System.out.println(conditionBinary.getOperator());
}
if(conditionValueExpr instanceof SQLBinaryOpExpr){
parseSQLBinaryOpExpr(cast(conditionValueExpr,SQLBinaryOpExpr.class));
System.out.println(conditionBinary.getOperator());
}
// 左边有别名所以是SQLPropertyExpr
if (conditionExpr instanceof SQLPropertyExpr) {
SQLPropertyExpr conditionColumnExpr = cast(conditionExpr, SQLPropertyExpr.class);
// 右边根据类型进行转换 id是SQLIntegerExpr name是SQLCharExpr
SQLValuableExpr conditionColumnValue = cast(conditionValueExpr, SQLValuableExpr.class);
print("条件列名:%s,条件别名:%s,条件值:%s", conditionColumnExpr.getName(), conditionColumnExpr.getOwnerName(), conditionColumnValue);
}
// 如果没有别名
if (conditionExpr instanceof SQLIdentifierExpr) {
SQLIdentifierExpr conditionColumnExpr = cast(conditionExpr, SQLIdentifierExpr.class);
SQLValuableExpr conditionColumnValue = cast(conditionValueExpr, SQLValuableExpr.class);
print("条件列名:%s,条件值:%s", conditionColumnExpr.getName(), conditionColumnValue);
}
}
public static <T> T cast(Object obj,Class<T> clazz){
return clazz.cast(obj);
}
public static void print(String msg,Object... params){
System.out.println(String.format(msg,params));
}
更新
-
涉及的类型
-
SQLUpdateStatement:更新的statement
-
SQLUpdateSetItem:更新的每个字段,肯定是key=value 或者 别名.key =value,可想而知,还有两种类型
- SQLPropertyExpr:存在别名
- SQLIdentifierExpr:不存在别名
javapublic static void main(String[] args) { test_Update(); } public static void test_Update(){ SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("update users u set u.name = 'tom',age = 18 where u.id = 1 "); SQLUpdateStatement updateStatement = cast(sqlStatement, SQLUpdateStatement.class); parseSet(updateStatement.getItems()); System.out.println("--------------------------------"); parseWhere(updateStatement.getWhere()); } private static void parseSet(List<SQLUpdateSetItem> setItems) { for (SQLUpdateSetItem setItem : setItems) { SQLExpr column = setItem.getColumn(); //情况一:有别名 if (column instanceof SQLPropertyExpr) { SQLPropertyExpr sqlPropertyExpr = cast(column, SQLPropertyExpr.class); SQLExpr value = setItem.getValue(); print("column:%s,列owner:%s,value:%s", sqlPropertyExpr.getName(), sqlPropertyExpr.getOwnerName(), value); } //情况二:没有别名 if (column instanceof SQLIdentifierExpr) { SQLExpr value = setItem.getValue(); print("column:%s,value:%s", column, value); } } } public static void parseWhere(SQLExpr where) { if (where instanceof SQLBinaryOpExpr) { parseSQLBinaryOpExpr(cast(where, SQLBinaryOpExpr.class)); } else { List<SQLObject> childrenList = where.getChildren(); for (SQLObject sqlObject : childrenList) { // 包含了 left 和 right SQLBinaryOpExpr conditionBinary = cast(sqlObject, SQLBinaryOpExpr.class); parseSQLBinaryOpExpr(conditionBinary); } } } public static void parseSQLBinaryOpExpr(SQLBinaryOpExpr conditionBinary) { SQLExpr conditionExpr = conditionBinary.getLeft(); SQLExpr conditionValueExpr = conditionBinary.getRight(); /** * 如果多个条件就会需要递归去获取数据了 */ if(conditionExpr instanceof SQLBinaryOpExpr){ parseSQLBinaryOpExpr(cast(conditionExpr,SQLBinaryOpExpr.class)); System.out.println(conditionBinary.getOperator()); } if(conditionValueExpr instanceof SQLBinaryOpExpr){ parseSQLBinaryOpExpr(cast(conditionValueExpr,SQLBinaryOpExpr.class)); System.out.println(conditionBinary.getOperator()); } // 左边有别名所以是SQLPropertyExpr if (conditionExpr instanceof SQLPropertyExpr) { SQLPropertyExpr conditionColumnExpr = cast(conditionExpr, SQLPropertyExpr.class); // 右边根据类型进行转换 id是SQLIntegerExpr name是SQLCharExpr SQLValuableExpr conditionColumnValue = cast(conditionValueExpr, SQLValuableExpr.class); print("条件列名:%s,条件别名:%s,条件值:%s", conditionColumnExpr.getName(), conditionColumnExpr.getOwnerName(), conditionColumnValue); } // 如果没有别名 if (conditionExpr instanceof SQLIdentifierExpr) { SQLIdentifierExpr conditionColumnExpr = cast(conditionExpr, SQLIdentifierExpr.class); SQLValuableExpr conditionColumnValue = cast(conditionValueExpr, SQLValuableExpr.class); print("条件列名:%s,条件值:%s", conditionColumnExpr.getName(), conditionColumnValue); } } public static <T> T cast(Object obj,Class<T> clazz){ return clazz.cast(obj); } public static void print(String msg,Object... params){ System.out.println(String.format(msg,params)); }
-
解析Limit
- 对于基本数字类型而言,如果没有Key的方式,只是有数字的时候,就是最简单的类型,下面只是列举了一些,还有Double这些的类型
-
使用到的类型
- SQLLimit
- SQLIntegerExpr:对于Limit而言是int类型,那么就是SQLIntegerExpr类型
javaprivate static void parseLimit(SQLLimit limit) { //偏移量 SQLExpr offset = limit.getOffset(); // 便宜数量 SQLExpr rowCount = limit.getRowCount(); print("偏移量:%s,偏移数量:%s", offset, rowCount); }
解析Group
-
涉及的类型
-
SQLSelectGroupByClause,因为Group by可以使用别名,所以,可想而已的两种类型就出来了
- SQLPropertyExpr:存在别名
- SQLIdentifierExpr:不存在别名
javapublic static void groupBy() { SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("select s.APPID,XGRQ from dev_cpjcpt.aps_app as s group by s.APPID,XGRQ;"); SQLSelectStatement selectStatement = cast(sqlStatement, SQLSelectStatement.class); SQLSelect select = selectStatement.getSelect(); SQLSelectQueryBlock query = cast(select.getQuery(), SQLSelectQueryBlock.class); //Clause:从句,子句 SQLSelectGroupByClause groupBy = query.getGroupBy(); List<SQLExpr> items = groupBy.getItems(); for (SQLExpr item : items) { // group by name // group by age //存在别名 if (item instanceof SQLPropertyExpr) { SQLPropertyExpr groupByColumn = cast(item, SQLPropertyExpr.class); print("group by %s", groupByColumn); } //没有别名的情况 if (item instanceof SQLIdentifierExpr) { SQLIdentifierExpr groupByColumn = cast(item, SQLIdentifierExpr.class); print("group by %s", groupByColumn); } } }
-
解析Having
-
基本类型
- 因为having的条件必然是group by所以,此处必须存在 SQLSelectGroupByClause,才能有having
-
因为having是个条件,所以,会涉及到 SQLBinaryOpExpr,那么必然存在一个和多个的问题
- 如果存在一个,那么left和right就是对应的 SQLIdentifierExpr 和 SQLValuableExpr
- 如果存在多个,那么left和right就是 SQLBinaryOpExpr
- 因为条件都是Key=value的方式,所以也会存在 SQLIdentifierExpr ,同样的,存在值,所以也就有SQLValuableExpr的类型了
-
通过上面的查看,可知 SQLBinaryOpExpr 是用于条件的时候,就是此类型,包括 where 和 having
java public static void having() { SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("select name,count(1) as count from users group by name,age having count > 2 and name is not null"); SQLSelectStatement selectStatement = cast(sqlStatement, SQLSelectStatement.class); SQLSelect select = selectStatement.getSelect(); SQLSelectQueryBlock query = cast(select.getQuery(), SQLSelectQueryBlock.class); SQLSelectGroupByClause groupBy = query.getGroupBy(); SQLExpr having = groupBy.getHaving(); // 因为只有一个条件,所以having就是SQLBinaryOpExpr SQLBinaryOpExpr havingExpr = cast(having, SQLBinaryOpExpr.class); parseHaving(havingExpr); } public static void parseHaving(SQLBinaryOpExpr havingExpr){ SQLExpr left = havingExpr.getLeft(); SQLExpr right = havingExpr.getRight(); if(left instanceof SQLBinaryOpExpr){ parseHaving(cast(havingExpr.getLeft(),SQLBinaryOpExpr.class)); } if(right instanceof SQLBinaryOpExpr){ parseHaving(cast(havingExpr.getRight(),SQLBinaryOpExpr.class)); } SQLIdentifierExpr leftExpr = null; // 没有使用别名,所以就是SQLIdentifierExpr if(left instanceof SQLIdentifierExpr) { leftExpr = cast(left, SQLIdentifierExpr.class); } // 数字类型就是 SQLValuableExpr rightValue = null; if(right instanceof SQLValuableExpr) { rightValue = cast(right, SQLValuableExpr.class); } SQLBinaryOperator operator = havingExpr.getOperator(); // left:count, operator:>,right:2 if(leftExpr !=null && rightValue!=null) { print("left:%s, operator:%s,right:%s", leftExpr.getName() , operator.name, rightValue.getValue()); } }
AST的修改
增加一个条件
java
public static void main(String[] args) {
SQLDeleteStatement();
}
public static void SQLDeleteStatement(){
SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("delete from users where id = 1");
SQLDeleteStatement sqlDeleteStatement = cast(sqlStatement, SQLDeleteStatement.class);
SQLExpr sqlExpr = SQLUtils.toSQLExpr("name = '孙悟空'");
sqlDeleteStatement.addCondition(sqlExpr);
// DELETE FROM users
// WHERE id = 1
// AND name = '孙悟空'
System.out.println(SQLUtils.toSQLString(sqlDeleteStatement));
}
修改一个条件值
java
public static void SQLDeleteStatement2(){
SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("delete from users where id = 1");
SQLDeleteStatement sqlDeleteStatement = cast(sqlStatement, SQLDeleteStatement.class);
SQLExpr where = sqlDeleteStatement.getWhere();
SQLBinaryOpExpr sqlBinaryOpExpr = cast(where, SQLBinaryOpExpr.class);
// DELETE FROM users
// WHERE id = 2
sqlBinaryOpExpr.setRight(SQLUtils.toSQLExpr("2"));
System.out.println(SQLUtils.toSQLString(sqlDeleteStatement));
}
Visitor模式
后续继续