SQL Parser是Druid的一个重要组成部分,Druid内置使用SQL Parser来实现防御SQL注入(WallFilter)、合并统计没有参数化的SQL(StatFilter的mergeSql)、SQL格式化、分库分表。
Druid的sql parser是目前支持各种数据语法最完备的SQL Parser。目前对各种数据库的支持如下:
数据库 |
DML |
DDL |
odps |
完全支持 |
完全支持 |
mysql |
完全支持 |
完全支持 |
oracle |
大部分 |
支持大部分 |
postgresql |
完全支持 |
支持大部分 |
sql server |
支持常用的 |
支持常用的ddl |
db2 |
支持常用的 |
支持常用的ddl |
druid还缺省支持sql-92标准的语法,所以也部分支持其他数据库的sql语法。
Druid的SQL Parser是手工编写,性能是antlr、javacc之类工具生成的数倍甚至10倍以上。
SELECT ID, NAME, AGE FROM USER WHERE ID = ?
这样的SQL,druid parser处理大约是5us,也就是每秒中可以处理20万次。
测试代码看这里:
package com.alibaba.druid.benckmark.sql;
import java.util.List;
import junit.framework.TestCase;
import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser; import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlOutputVisitor; import com.alibaba.druid.sql.test.TestUtils; import com.alibaba.druid.util.Utils;
public class MySqlPerfTest extends TestCase {
private String sql;
protected void setUp() throws Exception { sql = "SELECT * FROM T"; sql = "SELECT ID, NAME, AGE FROM USER WHERE ID = ?";
// sql = Utils.readFromResource("benchmark/sql/ob_sql.txt"); }
public void test_pert() throws Exception { for (int i = 0; i < 10; ++i) { perfMySql(sql); } }
long perfMySql(String sql) { long startYGC = TestUtils.getYoungGC(); long startYGCTime = TestUtils.getYoungGCTime(); long startFGC = TestUtils.getFullGC();
long startMillis = System.currentTimeMillis(); for (int i = 0; i < 1000 * 1000; ++i) { execMySql(sql); } long millis = System.currentTimeMillis() - startMillis;
long ygc = TestUtils.getYoungGC() - startYGC; long ygct = TestUtils.getYoungGCTime() - startYGCTime; long fgc = TestUtils.getFullGC() - startFGC;
System.out.println("MySql\t" + millis + ", ygc " + ygc + ", ygct " + ygct + ", fgc " + fgc); return millis; }
private String execMySql(String sql) { StringBuilder out = new StringBuilder(); MySqlOutputVisitor visitor = new MySqlOutputVisitor(out); MySqlStatementParser parser = new MySqlStatementParser(sql); List // for (SQLStatement statement : statementList) { // statement.accept(visitor); // visitor.println(); // } return out.toString(); } } |
SQLStatement代表一条SQL,解析器会把一个字符串解析成一个SQL的列表,如果字符串中有多个SQL,每个SQL用分号分隔,会返回一个对应个SQLStatement对象的列表。
Visitor类可以定义遇到某个SQL元素后的处理方法,或者遇到某个SQL元素后的处理方法。 |
Druid SQL Parser分三个模块: * Parser * AST *Visitor
解析器通过语法解析和此法解析,把SQL转换成抽象语法树。用户可以使用Visitor模式遍历AST
parser是将输入文本转换为ast(抽象语法树),parser有包括两个部分,Parser和Lexer,其中Lexer实现词法分析,Parser实现语法分析。
1. Parser:语法分析器
2. Lexer:词法分析器
AST是Abstract Syntax Tree的缩写,也就是抽象语法树。AST是parser输出的结果。
Visitor是遍历AST的手段,是处理AST最方便的模式,Visitor是一个接口,有缺省什么都没做的实现VistorAdapter。
我们可以实现不同的Visitor来满足不同的需求,Druid内置提供了如下Visitor:
l OutputVisitor用来把AST输出为字符串
测试用例:com.alibaba.druid.sql.mysql.demo.Demo0
l WallVisitor来分析SQL语意来防御SQL注入攻击
可以使用工具类:WallVisitorUtils
WallVisitorUtils.getValue(SQLUtils.toSQLExpr("1 < 2")) |
l ParameterizedOutputVisitor用来合并未参数化的SQL进行统计
测试用例:MySqlParameterizedOutputVisitorTest
可以把一个SQL的参数转换成问号,例如:
SELECT appsheetserialno FROM app_trans WHERE nodeid = _gbk '619' AND alino = _gbk '2013110900031031001700thfund00163619' AND apserialno = _gbk '201405120002300002170013205458' |
SELECT appsheetserialno FROM app_trans WHERE nodeid = ? AND alino = ? AND apserialno = ? |
l EvalVisitor 用来对SQL表达式求值
有对应的一个工具:SQLEvalVisitorUtils
具体是计算SQL中使用到的各种表达式的值。例如:
SQLEvalVisitorUtils.evalExpr(JdbcConstants.MYSQL, "?>0 && ?>0", 1,0) SQLEvalVisitorUtils.evalExpr(JdbcConstants.MYSQL, "2>1 && 100>10") |
求出表达式的值。 |
l ExportParameterVisitor用来提取SQL中的变量参数
例如:
String sql = "select id, name from table where id = 100 and name='jluo';"; SQLStatementParser parser = new MySqlStatementParser(sql); List MySqlExportParameterVisitor pVisitor= new MySqlExportParameterVisitor(); for (SQLStatement stmt : stmtList) { stmt.accept(pVisitor); List for (Object obj : list) { System.out.println(obj); } } |
结果: 100 jluo |
l SchemaStatVisitor用来统计SQL中使用的表、字段、过滤条件、排序表达式、分组表达式
用来获取SQL中的各种元素,检查SQL中是否含有某些特征值。
有大量测试用例用于测试这个类,例如:MySqlSchemaStatVisitorTest1
例如:
select a.id, a.name FROM users a, money b where a.id=b.id and a.id in (select id from innertables) |
可以解析出下面信息: |
Tables : {users=Select, money=Select, innertables=Select} fields : [users.id, users.name, money.id, innertables.id] select id, name FROM users a Tables : {users=Select} fields : [users.id, users.name] select id, a.name FROM users a Tables : {users=Select} fields : [users.id, users.name] |
MySQL的Visitor:
$ find ./ -name MySql*Visitor.java ./src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySql2OracleOutputVisitor.java ./src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlASTVisitor.java ./src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlExportParameterVisitor.java ./src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlOutputVisitor.java ./src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlParameterizedOutputVisitor.java ./src/main/java/com/alibaba/druid/sql/dialect/mysql/visitor/MySqlSchemaStatVisitor.java ./src/main/java/com/alibaba/druid/wall/spi/MySqlWallVisitor.java |
SQL-92、SQL-99等都是标准SQL,mysql/oracle/pg/sqlserver/odps等都是方言,也就是dialect。parser/ast/visitor都需要针对不同的方言进行特别处理。