Druid学习笔记 01、快速了解Druid中SqlParser实现介绍Druid
Druid是数据库连接池,能够提供强大的监控和扩展功能。
SQL Parser是Druids的一个重要组成部分,Druid内置使用SQL Parser来实现防御SQL注入(WallFilter)、合并统计没有参数化的SQL(StatFilter的mergeSql)、SQL格式化、分库分表。
Druid SQL Parser分三个模块:Parser,AST,Visitor。
- • parser是将输入文本转换为ast(抽象语法树),parser有包括两个部分,Parser和Lexer,其中Lexer实现词法分析,Parser实现语法分析。
- • AST是Abstract Syntax Tree的缩写,也就是抽象语法树。
- • Visitor是遍历AST的手段,是处理AST最方便的模式。
生成语法树过程:
生成语法树一般过程:字符流->词法解析器->token流->语法解析器->语法树。对于字符流经过词法解析器后变成token流,如 SELECT A.ID,B.ID FROM A 将变成如下的token流。
接下来语法解析器根据上面的token流生成一棵语法树:

代码目录介绍

- • com.alibaba.druid.sql.ast.* 为通用 ast
- • com.alibaba.druid.sql.dialect.* 为方言 ast,如oracle,mysql

*.clause为子句
*.stmt为分析后的结果
*.parser为解析类,*StatementParser为主分析类
.visitor 为AST visitor类,注意其中OutputVisitor为输出,stmt.toString实际调用此类,注意分析后输出调用toString的输出不一定是原始sql
Lexer语义
说明:增加新的类型可以寻找类似语法来实现,一般会牵涉到StatementParser及OutputVisitor修改
模块一:Parser
parser是将输入文本转换为ast(抽象语法树),parser有包括两个部分,Parser和Lexer,其中Lexer实现词法分析,Parser实现语法分析。
模块二:Druid_SQL_AST
wiki学习:https://github.com/alibaba/druid/wiki/Druid_SQL_AST#2-%E5%9C%A8druid-sql-parser%E4%B8%AD%E6%9C%89%E5%93%AA%E4%BA%9Bast%E8%8A%82%E7%82%B9%E7%B1%BB%E5%9E%8B
在Druid SQL Parser中有哪些AST节点类型?
AST节点类型主要包括SQLObject、SQLExpr、SQLStatement三种抽象类型。
package com.alibaba.druid.sql.ast;interface SQLObject {}interface SQLExpr extends SQLObject {} // 条件表达式相关的抽象,例如 ID = 3 这里的ID是一个SQLIdentifierExprinterface SQLStatement extends SQLObject {} //最常用的Statement当然是SELECT/UPDATE/DELETE/INSERT,他们分别是SQLSelectStatement ,SQLUpdateStatement ,SQLDeleteStatement ,SQLInsertStatement interface SQLTableSource extends SQLObject {} //常见的SQLTableSource包括SQLExprTableSource、SQLJoinTableSource、SQLSubqueryTableSource、SQLWithSubqueryClause.Entryclass SQLSelect extends SQLObject {}class SQLSelectQueryBlock extends SQLObject {} //SQLSelectStatement包含一个SQLSelect,SQLSelect包含一个SQLSelectQuery,都是组成的关系。SQLSelectQuery有主要的两个派生类,分别是SQLSelectQueryBlock和SQLUnionQuery。
熟悉常用的AST节点组成
常用的SQLExpr有哪些?
package com.alibaba.druid.sql.ast.expr;// SQLName是一种的SQLExpr的Expr,包括SQLIdentifierExpr、SQLPropertyExpr等public interface SQLName extends SQLExpr {}// 例如 ID = 3 这里的ID是一个SQLIdentifierExprclass SQLIdentifierExpr implements SQLExpr, SQLName { String name;}// 例如 A.ID = 3 这里的A.ID是一个SQLPropertyExprclass 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是一个SQLIntegerExprpublic class SQLIntegerExpr extends SQLNumericLiteralExpr implements SQLValuableExpr { Number number; // 所有实现了SQLValuableExpr接口的SQLExpr都可以直接调用这个方法求值 @Override public Object getValue() { return this.number; }}// 例如 NAME = 'jobs' 这里的'jobs'是一个SQLCharExprpublic class SQLCharExpr extends SQLTextLiteralExpr implements SQLValuableExpr{ String text;}
常用的SQLStatemment?
最常用的Statement当然是SELECT/UPDATE/DELETE/INSERT,他们分别是
package com.alibaba.druid.sql.ast.statement;class SQLSelectStatement implements SQLStatement { SQLSelect select;}class SQLUpdateStatement implements SQLStatement { SQLExprTableSource tableSource; List<SQLUpdateSetItem> items; SQLExpr where;}class SQLDeleteStatement implements SQLStatement { SQLTableSource tableSource; SQLExpr where;}class SQLInsertStatement implements SQLStatement { SQLExprTableSource tableSource; List<SQLExpr> columns; SQLSelect query;}
SQLTableSource
常见的SQLTableSource包括SQLExprTableSource、SQLJoinTableSource、SQLSubqueryTableSource、SQLWithSubqueryClause.Entry
class SQLTableSourceImpl extends SQLObjectImpl implements SQLTableSource { String alias;}// 例如 select * from emp where i = 3,这里的from emp是一个SQLExprTableSource// 其中expr是一个name=emp的SQLIdentifierExprclass SQLExprTableSource extends SQLTableSourceImpl { SQLExpr expr;}// 例如 select * from emp e inner join org o on e.org_id = o.id// 其中left 'emp e' 是一个SQLExprTableSource,right 'org o'也是一个SQLExprTableSource// condition 'e.org_id = o.id'是一个SQLBinaryOpExprclass SQLJoinTableSource extends SQLTableSourceImpl { SQLTableSource left; SQLTableSource right; JoinType joinType; // INNER_JOIN/CROSS_JOIN/LEFT_OUTER_JOIN/RIGHT_OUTER_JOIN/... SQLExpr condition;}// 例如 select * from (select * from temp) a,这里第一层from(...)是一个SQLSubqueryTableSourceSQLSubqueryTableSource extends SQLTableSourceImpl { SQLSelect select;}/* 例如WITH RECURSIVE ancestors AS ( SELECT * FROM org UNION SELECT f.* FROM org f, ancestors a WHERE f.id = a.parent_id)SELECT *FROM ancestors;这里的ancestors AS (...) 是一个SQLWithSubqueryClause.Entry*/class SQLWithSubqueryClause { static class Entry extends SQLTableSourceImpl { SQLSelect subQuery; }}
SQLSelect & SQLSelectQuery
SQLSelectStatement包含一个SQLSelect,SQLSelect包含一个SQLSelectQuery,都是组成的关系。SQLSelectQuery有主要的两个派生类,分别是SQLSelectQueryBlock和SQLUnionQuery。
class SQLSelect extends SQLObjectImpl { SQLWithSubqueryClause withSubQuery; SQLSelectQuery query;}interface SQLSelectQuery extends SQLObject {}class SQLSelectQueryBlock implements SQLSelectQuery { List<SQLSelectItem> selectList; SQLTableSource from; SQLExprTableSource into; SQLExpr where; SQLSelectGroupByClause groupBy; SQLOrderBy orderBy; SQLLimit limit;}class SQLUnionQuery implements SQLSelectQuery { SQLSelectQuery left; SQLSelectQuery right; SQLUnionOperator operator; // UNION/UNION_ALL/MINUS/INTERSECT}
SQLCreateTableStatement
建表语句包含了一系列方法,用于方便各种操作
public class SQLCreateTableStatement extends SQLStatementImpl implements SQLDDLStatement, SQLCreateStatement { SQLExprTableSource tableSource; List<SQLTableElement> tableElementList; Select select; // 忽略大小写的查找SQLCreateTableStatement中的SQLColumnDefinition public SQLColumnDefinition findColumn(String columName) {} // 忽略大小写的查找SQLCreateTableStatement中的column关联的索引 public SQLTableElement findIndex(String columnName) {} // 是否外键依赖另外一个表 public boolean isReferenced(String tableName) {}}
怎样产生AST
通过SQLUtils产生List
import com.alibaba.druid.util.JdbcConstants;String dbType = JdbcConstants.MYSQL;List<SQLStatement> statementList = SQLUtils.parseStatements(sql, dbType);
通过SQLUtils产生SQLExpr
String dbType = JdbcConstants.MYSQL;SQLExpr expr = SQLUtils.toSQLExpr("id=3", dbType);
怎样打印AST节点?
通过SQLUtils工具类打印节点
package com.alibaba.druid.sql;public class SQLUtils { // 可以将SQLExpr/SQLStatement打印为String类型 static String toSQLString(SQLObject sqlObj, String dbType); // 可以将一个<SQLStatement>打印为String类型 static String toSQLString(List<SQLStatement> statementList, String dbType);}
如何自定义遍历AST节点?
所有的AST节点都支持Visitor模式,需要自定义遍历逻辑,可以实现相应的ASTVisitorAdapter派生类,比如 https://github.com/alibaba/druid/wiki/SQL_Parser_Demo_visitor
模块三:Visitor
Visitor是遍历AST的手段,是处理AST最方便的模式,Visitor是一个接口,有缺省什么都没做的实现VistorAdapter。
Druid内置提供了如下Visitor:
- • OutputVisitor用来把AST输出为字符串
- • WallVisitor 来分析SQL语意来防御SQL注入攻击
- • ParameterizedOutputVisitor用来合并未参数化的SQL进行统计
- • EvalVisitor 用来对SQL表达式求值
- • ExportParameterVisitor用来提取SQL中的变量参数
- • SchemaStatVisitor 用来统计SQL中使用的表、字段、过滤条件、排序表达式、分组表达式
- • SQL格式化 Druid内置了基于语义的SQL格式化功能
Druid提供了多种默认实现的Visitor,可以满足基本需求,如果默认提供的不满足需求,可自行实现自定义Visitor。
案例:统计SQL中涉及到的表、字段
比如我们要统计下一条SQL中涉及了哪些表 select name ,id ,select money from user from acct where id =10,如果我们不用visitor,自行遍历AST,能实现,但是很繁琐。
但是我们用默认自带的Visitor就可以很轻松的实现:MySqlSchemaStatVisitor
package com.changlu.visitor;import com.alibaba.druid.sql.ast.SQLStatement;import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;import com.alibaba.druid.sql.parser.SQLStatementParser;/** * 验证MySqlSchemaStatVisitor */public class TestSchemaVisitor { public static void main(String[] args) { SQLStatementParser parser = new MySqlStatementParser("select name ,id ,select money from user from acct where id =10"); SQLStatement sqlStatement = parser.parseStatement(); // 定义visitor MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor(); // 执行visitor访问操作 sqlStatement.accept(visitor); // 获取到最终的字段名、tables、条件分支、db类型 System.out.println(visitor.getColumns()); //[acct.name, acct.id, user.money] System.out.println(visitor.getTables()); //{acct=Select, user=Select} System.out.println(visitor.getConditions()); //[acct.id = 10] System.out.println(visitor.getDbType());//mysql }}

参考文章
[1]. 分析Druid 连接池中SQL语法树的基本原理:https://zhuanlan.zhihu.com/p/411029742
[2]. 使用Druid SQL Parser解析SQL:https://blog.csdn.net/cckevincyh/article/details/125317977
[3]. Ali Druid Parser AST扩展及修改记录:https://blog.csdn.net/weixin_40455124/article/details/91488294
[4]. SQL解析在美团的应用:https://tech.meituan.com/2018/05/20/sql-parser-used-in-mtdp.html