Druid学习笔记 01、快速了解Druid中SqlParser实现

文章目录

前言

博主介绍:✌目前全网粉丝4W+,csdn博客专家、Java领域优质创作者,博客之星、阿里云平台优质作者、专注于Java后端技术领域。

涵盖技术内容:Java后端、大数据、算法、分布式微服务、中间件、前端、运维等。

博主所有博客文件目录索引:博客目录索引(持续更新)

CSDN搜索:长路

视频平台:b站-Coder长路

介绍Druid

Druid是数据库连接池,能够提供强大的监控和扩展功能。

SQL Parser是Druid的一个重要组成部分,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流。

字符 类型
SELECT 关键字
A 变量
.
ID 变量
, 逗号
B 变量
. ID
FROM 关键字
A 变量

接下来语法解析器根据上面的token流生成一棵语法树:

代码目录介绍

  • com.alibaba.druid.sql.ast.* 为通用 ast
  • com.alibaba.druid.sql.dialect.* 为方言 ast,如oracle,mysql
  • *.expr为表达式

*.clause为子句

*.stmt为分析后的结果

*.parser为解析类,*StatementParser为主分析类

.visitor 为AST visitor类,注意其中OutputVisitor为输出,stmt.toString实际调用此类,注意分析后输出调用toString的输出不一定是原始sql

java 复制代码
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-在druid-sql-parser中有哪些ast节点类型

在Druid SQL Parser中有哪些AST节点类型?

AST节点类型主要包括SQLObject、SQLExpr、SQLStatement三种抽象类型。

java 复制代码
package com.alibaba.druid.sql.ast;

interface SQLObject {} 
interface SQLExpr extends SQLObject {} // 条件表达式相关的抽象,例如 ID = 3 这里的ID是一个SQLIdentifierExpr
interface SQLStatement extends SQLObject {} //最常用的Statement当然是SELECT/UPDATE/DELETE/INSERT,他们分别是SQLSelectStatement ,SQLUpdateStatement ,SQLDeleteStatement ,SQLInsertStatement 

interface SQLTableSource extends SQLObject {} //常见的SQLTableSource包括SQLExprTableSource、SQLJoinTableSource、SQLSubqueryTableSource、SQLWithSubqueryClause.Entry
class SQLSelect extends SQLObject {}
class SQLSelectQueryBlock extends SQLObject {} //SQLSelectStatement包含一个SQLSelect,SQLSelect包含一个SQLSelectQuery,都是组成的关系。SQLSelectQuery有主要的两个派生类,分别是SQLSelectQueryBlock和SQLUnionQuery。

熟悉常用的AST节点组成

常用的SQLExpr有哪些?

java 复制代码
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都可以直接调用这个方法求值
    @Override
    public Object getValue() {
        return this.number;
    }
}

// 例如 NAME = 'jobs' 这里的'jobs'是一个SQLCharExpr
public class SQLCharExpr extends SQLTextLiteralExpr implements SQLValuableExpr{
    String text;
}

常用的SQLStatemment?

最常用的Statement当然是SELECT/UPDATE/DELETE/INSERT,他们分别是

java 复制代码
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

java 复制代码
class SQLTableSourceImpl extends SQLObjectImpl implements SQLTableSource { 
    String alias;
}

// 例如 select * from emp where i = 3,这里的from emp是一个SQLExprTableSource
// 其中expr是一个name=emp的SQLIdentifierExpr
class 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'是一个SQLBinaryOpExpr
class 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(...)是一个SQLSubqueryTableSource
SQLSubqueryTableSource 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。

java 复制代码
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

建表语句包含了一系列方法,用于方便各种操作

java 复制代码
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

java 复制代码
import com.alibaba.druid.util.JdbcConstants;

String dbType = JdbcConstants.MYSQL;
List<SQLStatement> statementList = SQLUtils.parseStatements(sql, dbType);

通过SQLUtils产生SQLExpr

java 复制代码
String dbType = JdbcConstants.MYSQL;
SQLExpr expr = SQLUtils.toSQLExpr("id=3", dbType);

怎样打印AST节点?

通过SQLUtils工具类打印节点

java 复制代码
package com.alibaba.druid.sql;

public class SQLUtils {
    // 可以将SQLExpr/SQLStatement打印为String类型
    static String toSQLString(SQLObject sqlObj, String dbType);

    // 可以将一个&lt;SQLStatement&gt;打印为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

java 复制代码
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 ## 资料获取 大家点赞、收藏、关注、评论啦\~ 精彩专栏推荐订阅:在下方专栏👇🏻 * [长路-文章目录汇总(算法、后端Java、前端、运维技术导航)](https://blog.csdn.net/cl939974883/category_11568291.html?spm=1001.2014.3001.5482):博主所有博客导航索引汇总 * [开源项目Studio-Vue---校园工作室管理系统(含前后台,SpringBoot+Vue)](https://changlu.blog.csdn.net/article/details/125295334):博主个人独立项目,包含详细部署上线视频,已开源 * [学习与生活-专栏](https://blog.csdn.net/cl939974883/category_10700595.html):可以了解博主的学习历程 * [算法专栏](https://blog.csdn.net/cl939974883/category_11403550.html?spm=1001.2014.3001.5482):算法收录 更多博客与资料可查看👇🏻获取联系方式👇🏻,🍅文末获取开发资源及更多资源博客获取🍅

相关推荐
喜欢踢足球的老罗2 个月前
在Spring Boot 3.3中使用Druid数据源及其监控功能
java·spring boot·后端·druid
梦想画家2 个月前
Apache Druid 架构深度解析:构建高性能分布式数据存储系统
架构·druid·数据工程
虎鲸不是鱼6 个月前
Win10环境借助DockerDesktop部署大数据时序数据库Apache Druid
大数据·docker·apache·时序数据库·druid
全栈小酷Cool8 个月前
数据库连接池调优——可视化查询慢SQL利器
数据库·sql·druid
梦想画家9 个月前
精通rust宏系列教程-调试过程宏
rust·抽象语法树·过程宏·派生宏
zhangqiang08211 年前
抽象语法树AST
前端·ast·语法树·抽象语法树
洛*璃1 年前
Spring Cloud微服务项目集成MySQL
后端·mysql·spring cloud·微服务·lombok·druid
战神刘玉栋1 年前
《企业实战分享 · Druid 连接监控》
java·oracle·springcloud·druid
阳爱铭1 年前
深度分析Apache Druid:定义、对比、使用场景、选型指南及注意事项
java·大数据·数据库·后端·架构·apache·druid