Durid的SQL解析器浅释

SQL Parser是Druid的一个重要组成部分,Druid内置使用SQL Parser来实现防御SQL注入(WallFilter)、合并统计没有参数化的SQL(StatFilter的mergeSql)、SQL格式化、分库分表。

1. 各种语法支持

Druid的sql parser是目前支持各种数据语法最完备的SQL Parser。目前对各种数据库的支持如下:

数据库

DML

DDL

odps

完全支持

完全支持

mysql

完全支持

完全支持

oracle

大部分

支持大部分

postgresql

完全支持

支持大部分

sql server

支持常用的

支持常用的ddl

db2

支持常用的

支持常用的ddl

druid还缺省支持sql-92标准的语法,所以也部分支持其他数据库的sql语法。

2. 性能

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 statementList = parser.parseStatementList();

        // for (SQLStatement statement : statementList) {

        // statement.accept(visitor);

        // visitor.println();

        // }

        return out.toString();

    }

}

SQLStatement代表一条SQL,解析器会把一个字符串解析成一个SQL的列表,如果字符串中有多个SQL,每个SQL用分号分隔,会返回一个对应个SQLStatement对象的列表。

 

Visitor类可以定义遇到某个SQL元素后的处理方法,或者遇到某个SQL元素后的处理方法。

 

3. Druid SQL Parser的代码结构

Druid SQL Parser分三个模块: * Parser * AST *Visitor

 

解析器通过语法解析和此法解析,把SQL转换成抽象语法树。用户可以使用Visitor模式遍历AST

3.1.  Parser

parser是将输入文本转换为ast(抽象语法树),parser有包括两个部分,Parser和Lexer,其中Lexer实现词法分析,Parser实现语法分析。

1.      Parser:语法分析器

2.      Lexer:词法分析器

3.2.  AST

AST是Abstract Syntax Tree的缩写,也就是抽象语法树。AST是parser输出的结果。

3.3.  Visitor

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 = ?

 

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 stmtList = parser.parseStatementList();

MySqlExportParameterVisitor pVisitor= new MySqlExportParameterVisitor();

for (SQLStatement stmt : stmtList) {

       stmt.accept(pVisitor);

       List list = pVisitor.getParameters();

       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

 

3.4.  方言

SQL-92、SQL-99等都是标准SQL,mysql/oracle/pg/sqlserver/odps等都是方言,也就是dialect。parser/ast/visitor都需要针对不同的方言进行特别处理。

 

你可能感兴趣的:(Durid)