Druid学习笔记 03、Druid的AstNode类详解与其他产品测试体验
文章目录
- 前言
- 解析ast操作
- ast的结构(继承实现关系)
- SQL类型四种结构实现(SQLStatement)
- 可替换SQL组件类(SQLReplaceable接口实现)
- 表数据源类(SQLTableSource)
- 表达式实现类(SQLExpr)
- 提示信息接口(SQLHint)
- 查询实现接口(SQLSelectQuery)
- 深入解析Statement(组成结构)
- 顶层抽象类
- SelectStatement
- **SelectStatement实现类**
- SQLSelect
- SQLSelectQuery
- 针对解析到的node树进行处理模板操作
- 关于不同结构之间血缘如何计算(简单记录)
- 复杂sql结合例子
- 步骤一:确认目标表的字段
- 步骤二:union右边的(带子查询、left join)
- 步骤三:union左边的
- 合并去看
- datablau血缘问题记录
- 问题1:复杂sql加上on条件之后血缘解析有问题
- 问题2:union on问题
- 资料获取

前言
博主介绍:✌目前全网粉丝4W+,csdn博客专家、Java领域优质创作者,博客之星、阿里云平台优质作者、专注于Java后端技术领域。
涵盖技术内容:Java后端、大数据、算法、分布式微服务、中间件、前端、运维等。
博主所有博客文件目录索引:博客目录索引(持续更新)
CSDN搜索:长路
视频平台:b站-Coder长路
解析ast操作
解析语句相对简单,wiki上直接有示例,如:
String dbType = JdbcConstants.MYSQL;
List<SQLStatement> statementList = SQLUtils.parseStatements(sql, dbType);
SQLUtils的parseStatements方法会把你传入的SQL语句给解析成SQLStatement对象集合,每一个SQLStatement代表一条完整的SQL语句,如:
SELECT id FROM user WHERE status = 1
多个SQLStatement如:
SELECT id FROM user WHERE status = 1;
SELECT id FROM order WHERE create_time > '2018-01-01'
一般上我们只处理一条语句。
ast的结构(继承实现关系)
SQL类型四种结构实现(SQLStatement)
SQLStatement表示一条SQL语句,我们知道常见的SQL语句有CRUD四种操作,所以SQLStatement会有四种主要实现类,如:
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;
}
这里我们以SQLSelectStatement来说明,ast既然是SQL的语法结构表示。
我们先看一下ast和SQL 增删改查语法的主要对应结构如下:
可替换SQL组件类(SQLReplaceable接口实现)
**介绍:**SQLReplaceable 接口允许你把一个 SQL 表达式或者对象替换成另外一个,这在 SQL 解析、优化以及转换的过程中很有用。借助这个接口,你可以对 SQL 语句里的特定部分进行修改或者替换,从而实现自定义的 SQL 处理逻辑。
**实际应用:**例如 SQLLimit 是用于限制查询结果数量的组件,SQLOrderBy 是用于对查询结果进行排序的组件。
小组件节点SQLSelectItem、SQLSelectGroupByClause、SQLOrderBy、SQLLimit:
表数据源类(SQLTableSource)
**介绍:**在 SQL 查询中,SQLTableSource 实现类通常会定义数据的出处,可能是数据库中的物理表、视图,或者是经过特殊处理后的虚拟表等。例如,一个实现 SQLTableSource 用于从 MySQL 数据库表获取数据的类,可以命名为 MySQLSQLTableDataSource 。
**实际应用:**SQLTableSource这个节点,它有着常见的实现SQLExprTableSource(from的表)、SQLJoinTableSource(join的表)、SQLSubqueryTableSource(子查询的表):
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;
}
另外SQLExpr出现的地方也比较多,比如where语句,join条件,SQLSelectItem中等,因此,也需要细化了解一下,如
表达式实现类(SQLExpr)
**介绍:**SQLExpr 代表着 SQL 语句里的表达式,它是构成 SQL 查询的基础单元。这些表达式可以是常量、变量、函数调用、列引用等,能用来描述数据的筛选条件、计算逻辑等。
实际应用:实现 SQLExpr 接口的类就是用于表示和处理各种 SQL 表达式的类。SQLExpr出现的地方也比较多,比如where语句,join条件,SQLSelectItem中等,因此,也需要细化了解一下:
// 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都可以直接调用这个方法求值@Overridepublic Object getValue() {return this.number;}
}// 例如 NAME = 'jobs' 这里的'jobs'是一个SQLCharExpr
public class SQLCharExpr extends SQLTextLiteralExpr implements SQLValuableExpr{String text;
}// SQLMethodInvokeExpr 是 SQLExpr 接口的实现类,专门用来解析和表示 SQL 中的函数调用
public class SQLMethodInvokeExpr extends SQLExprImpl {private String methodName; // 方法名,如 "COUNT", "SUBSTR"private SQLExpr owner; // 方法所有者(用于类似 o.method() 的调用)private List<SQLExpr> parameters; // 参数列表private boolean distinct; // 是否包含 DISTINCT 关键字private boolean trim; // 是否 TRIM 函数特有属性// ... 其他属性和方法
}// 用于表示 SQL 聚合函数(Aggregate Function)。它继承自 SQLMethodInvokeExpr,专门处理聚合操作,如 COUNT(), SUM(), AVG(), MAX(), MIN() 等。
public class SQLAggregateExpr extends SQLMethodInvokeExpr implements Serializable, SQLReplaceable {
提示信息接口(SQLHint)
**介绍:**SQLHint 接口在 Druid 的 SQL 解析和处理体系中,代表着 SQL 提示信息。SQL 提示是一种给数据库查询优化器提供额外信息的方式,能够指导优化器以特定的方式执行查询,比如指定使用某个索引、调整查询的执行计划等。实现 SQLHint 接口的类就是用来表示和处理这些 SQL 提示信息的类。
查询实现接口(SQLSelectQuery)
**含义:**SQLSelectQuery 接口主要用于表示 SQL 中的 SELECT 查询语句。实现该接口的类承担着构建、解析和处理完整 SELECT 查询逻辑的任务。因此,称它们为 “SQL 查询语句类” 能突出其核心作用是代表完整的 SQL 查询语句。
**作用体现:**这些类可以包含查询的各个部分,如 SELECT 子句(指定要返回的列)、FROM 子句(指定数据源)、WHERE 子句(指定筛选条件)、GROUP BY 子句(分组)、ORDER BY 子句(排序)等。例如,一个实现类可能会将这些子句组合起来,形成一个完整的 SELECT 查询语句。
深入解析Statement(组成结构)
顶层抽象类
SelectStatement
SelectStatement实现类
所处的位置为:
class SQLSelectStatement implements SQLStatement {SQLSelect select;
}
SQLSelect
public class SQLSelect extends SQLObjectImpl implements SQLDbTypedObject {// 见上章节【可替换SQL组件类(SQLReplaceable接口实现)】protected SQLWithSubqueryClause withSubQuery;protected SQLOrderBy orderBy;protected SQLLimit limit;// 见章节【SQLSelectQuery】protected SQLSelectQuery query;protected List<SQLHint> hints;protected SQLObject restriction;protected boolean forBrowse;protected List<String> forXmlOptions;// 见章节 表达式实现类(SQLExpr)protected SQLExpr xmlPath;protected SQLExpr rowCount;protected SQLExpr offset;private SQLHint headHint;
SQLSelectQuery
SQLSelect包含一个SQLSelectQuery,都是组成的关系。
public class SQLSelect extends SQLObjectImpl implements SQLDbTypedObject {// sql中的子查询protected SQLWithSubqueryClause withSubQuery;// 主要select构成组成 【SQLSelectQueryBlock、SQLUnionQuery】protected SQLSelectQuery query;protected SQLOrderBy orderBy;protected SQLLimit limit;protected List<SQLHint> hints;protected SQLObject restriction;protected boolean forBrowse;protected List<String> forXmlOptions;protected SQLExpr xmlPath;protected SQLExpr rowCount;protected SQLExpr offset;private SQLHint headHint;
SQLSelectQuery有主要的两个派生类,分别是SQLSelectQueryBlock和SQLUnionQuery。
interface SQLSelectQuery extends SQLObject {}// 具体select查询条件其中组成
class SQLSelectQueryBlock implements SQLSelectQuery {// 字段内容List<SQLSelectItem> selectList;// 表来源 各种情况:SQLExprTableSource、SQLJoinTableSource、SQLSubqueryTableSourceSQLTableSource from;// where条件SQLExpr where;// group bySQLSelectGroupByClause groupBy;SQLOrderBy orderBy;SQLLimit limit;
}// 由SQLSelectQuery组成
class SQLUnionQuery implements SQLSelectQuery {SQLSelectQuery left;SQLSelectQuery right;SQLUnionOperator operator; // UNION/UNION_ALL/MINUS/INTERSECT
}
针对解析到的node树进行处理模板操作
关于不同结构之间血缘如何计算(简单记录)
// 单表 如:from table1 或者 from table1 as t1// 子查询表 如:from (select xxx, xxx from table 1)
CREATE TABLE new_table AS
select col3, col4 from (select col1 as col3, col2 as col4, col5 from table2
)as table3
// 场景3:join表 如:from table1 left|inner|right join table2 on table1.id = table2.id
CREATE TABLE new_table AS
SELECT col1 as cc1, col2 as cc2 FROM table1
union
-- left join
select col1, col2 from table4 as t4
left join table5 t4 on t1.col1 = t2.col1// 另一个例子
CREATE TABLE new_table AS
select col1, col2 from table4 as t4
left join table5 t4 on t1.col1 = t2.col1
复杂sql结合例子
复杂结合例子:
CREATE TABLEnew_table AS
SELECT col1 as cc1, col2 as cc2 FROM table1
union
selectcol3,col4
from(selectt4.col1 as col3,t5.col2 as col4fromtable4 as t4left join table5 t5 on t4.col3 = t5.col3) as tt
select类型:SqlSelectQueryBlock、SqlUnionQuery
tableSource类型:SQLSubqueryTableSource、SQLExprTableSource、SQLJoinTableSource
步骤一:确认目标表的字段
创建的表结构以第一组的sql为准:
CREATE TABLEnew_table AS
SELECT col1 as cc1, col2 as cc2 FROM table1
union
select
得到的一组目标写入字段为:
真实字段 别名字段 真实表名col1 cc1 new_tablecol2 cc2 new_table
步骤二:union右边的(带子查询、left join)
关注下面这部分:
union
selectcol3,col4
from(// 内部1️⃣selectt4.col1 as col3,t5.col2 as col4fromtable4 as t4left join table5 t5 on t4.col3 = t5.col3) as tt
以select开头作为一组,关注拿到的是该select 所拿到的selectItem字段:
如先看内部1️⃣:
selectt4.col1 as col3,t5.col2 as col4
fromtable4 as t4
left join table5 t5 on t4.col3 = t5.col3# 涉及到 join 关联
# 关于真实表名如何获取到:需要去单独去针对该selectsql匹配到表及别名预先获取到
# 1、首先为查询到select的字段:
# 关于select字段
真实字段 别名字段 真实表名
col1 col3 t4
col2 col4 t5
# 关于from来源表为这种SQLExprTableSource情况,直接进行匹配
真实表名 表别名字段table4 t4table5 t5
# 2、借助select的字段去匹配from、left join的场景实现替换逻辑
# 由于t4与表别名字段t4匹配,则表字段记录的真实表名替换为相应的真实表名真实字段 别名字段 真实表名
col1 col3 table4
col2 col4 table5
在看外部2️⃣,如下:
selectcol3 as col5,col4 as col6
from(// 内部1️⃣已替换真实字段 别名字段 真实表名col1 col3 table4col2 col4 table5 ) as tt
此时select的字段为col3、col4
逻辑应该是拿其col3匹配1️⃣中的中的字段(先别名、无别名再真实字段),匹配得到了则取相应table名字。
tt的col3匹配到1️⃣如下:
真实字段 别名字段 真实表名
col1 col3 table4// 中间过程
// 1、tt的真实字段col3尝试匹配内部1️⃣中的别名
// 1.1、匹配成功一致
// 1.1.1、判断tt自身这个字段是否有别名,有的话则将col5填充别名字段到表中
// 1.1.2、如果tt自身没有别名,则不动
// 1.2、匹配失败:则该字段舍弃,抛出异常记录(sql有问题了)
// 按照规则会走到1.1.1替换后如下得到一组字段记录真实字段 别名字段 真实表名
col1 col5 table4
tt的col4匹配1️⃣如下:
真实字段 别名字段 真实表名
col2 col4 table5 // 中间过程如上所述
// 同样按照规则走的是1.1.1 替换后如下真实字段 别名字段 真实表名
col2 col6 table5
最终外部2转换得到的一组字段为:
真实字段 别名字段 真实表名
col1 col5 table4
col2 col6 table5
步骤三:union左边的
SELECT col1 as cc1, col2 as cc2 FROM table1
union
参考步骤二:
真实字段 别名字段 真实表名
col1 cc1 table1
col2 cc2 table1
合并去看
目标表:
真实字段 别名字段 表名col1 cc1 new_tablecol2 cc2 new_table
union左:
真实字段 别名字段 真实表名
col1 cc1 table1
col2 cc2 table1
union右:
真实字段 别名字段 真实表名
col1 col5 table4
col2 col6 table5
血缘去匹配过程为:
union有几个就匹配几次,这里有两个就匹配两次 组成两组血缘
注意注意!!! 该过程与步骤2、3的追溯过程不一样
第一组:目标字段 & union左
# 目标字段
真实字段 别名字段 真实表名col1 cc1 new_tablecol2 cc2 new_table# union左sql字段
真实字段 别名字段 真实表名
col1 cc1 table1
col2 cc2 table1
匹配过程:
1、应该是一一对应的过程,而不是追溯寻找的过程。
2、例如【目标字段】第一行对应下面【union左sql字段】第一行,【目标字段】第二行对应下面【union左sql字段】第二行,各自只取真实的即可
来源表 来源字段 目标表 目标字段
table1 col1 new_table cc1
table1 col2 new_table cc2
3、关于select查询表有join连接逻辑(也要产生血缘)
# 目标字段
真实字段 别名字段 真实表名col1 cc1 new_tablecol2 cc2 new_table# 涉及到 join 关联
真实字段 真实表名(需要去匹配到) 表别名字段
col3 table4 t4
col3 table5 t5
产生4条血缘规则(每一个目标真实表及字段映射join关联的真实表及字段):
来源表 来源字段 目标表 目标字段
table4 col3 new_table cc1
table5 col3 new_table cc2
第二组:目标血缘 & union右
datablau血缘问题记录
问题1:复杂sql加上on条件之后血缘解析有问题
无问题:
CREATE TABLEnew_table AS
SELECT col1 as cc1, col2 as cc2 FROM table1
union
selectcol3,col4
from(selectt4.col1 as col3,t5.col2 as col4fromtable4 as t4left join table5 t5 on t4.col1 = t5.col1) as tt
有问题:修改on条件之后,影响new_table中的字段为col3、col3
// left join table5 t5 on t4.col1 = t5.col1
CREATE TABLEnew_table AS
SELECT col1 as cc1, col2 as cc2 FROM table1
union
selectcol3,col4
from(selectt4.col1 as col3,t5.col2 as col4fromtable4 as t4left join table5 t5 on t4.col3 = t5.col3) as tt
问题2:union on问题
CREATE TABLE new_table AS
SELECT col1, col2 FROM table1
UNION
SELECT col3, col4 FROM table2;
问题描述:错误的将目标表中的字段作为了col3、col4。
资料获取
大家点赞、收藏、关注、评论啦~
精彩专栏推荐订阅:在下方专栏👇🏻
- 长路-文章目录汇总(算法、后端Java、前端、运维技术导航):博主所有博客导航索引汇总
- 开源项目Studio-Vue—校园工作室管理系统(含前后台,SpringBoot+Vue):博主个人独立项目,包含详细部署上线视频,已开源
- 学习与生活-专栏:可以了解博主的学习历程
- 算法专栏:算法收录
更多博客与资料可查看👇🏻获取联系方式👇🏻,🍅文末获取开发资源及更多资源博客获取🍅