Druid学习笔记 03、Druid的AstNode类详解与其他产品测试体验解析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的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;}
另外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是一个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;}// 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、SQLSubqueryTableSource SQLTableSource from; // where条件 SQLExpr where; // group by SQLSelectGroupByClause 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 ASselect 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.idCREATE TABLE new_table ASSELECT col1 as cc1, col2 as cc2 FROM table1union-- left joinselect col1, col2 from table4 as t4left join table5 t4 on t1.col1 = t2.col1// 另一个例子CREATE TABLE new_table ASselect col1, col2 from table4 as t4left join table5 t4 on t1.col1 = t2.col1


复杂sql结合例子
复杂结合例子:
CREATE TABLE new_table ASSELECT col1 as cc1, col2 as cc2 FROM table1unionselect col3, col4from ( select t4.col1 as col3, t5.col2 as col4 from table4 as t4 left join table5 t5 on t4.col3 = t5.col3 ) as tt

select类型:SqlSelectQueryBlock、SqlUnionQuery
tableSource类型:SQLSubqueryTableSource、SQLExprTableSource、SQLJoinTableSource
步骤一:确认目标表的字段
创建的表结构以第一组的sql为准:
CREATE TABLE new_table ASSELECT col1 as cc1, col2 as cc2 FROM table1unionselect
得到的一组目标写入字段为:
真实字段 别名字段 真实表名 col1 cc1 new_table col2 cc2 new_table
步骤二:union右边的(带子查询、left join)
关注下面这部分:
unionselect col3, col4from ( // 内部1️⃣ select t4.col1 as col3, t5.col2 as col4 from table4 as t4 left join table5 t5 on t4.col3 = t5.col3 ) as tt
以select开头作为一组,关注拿到的是该select 所拿到的selectItem字段:
如先看内部1️⃣:
select t4.col1 as col3, t5.col2 as col4from table4 as t4left join table5 t5 on t4.col3 = t5.col3# 涉及到 join 关联# 关于真实表名如何获取到:需要去单独去针对该selectsql匹配到表及别名预先获取到# 1、首先为查询到select的字段:# 关于select字段真实字段 别名字段 真实表名col1 col3 t4col2 col4 t5# 关于from来源表为这种SQLExprTableSource情况,直接进行匹配真实表名 表别名字段 table4 t4 table5 t5# 2、借助select的字段去匹配from、left join的场景实现替换逻辑# 由于t4与表别名字段t4匹配,则表字段记录的真实表名替换为相应的真实表名真实字段 别名字段 真实表名col1 col3 table4col2 col4 table5
在看外部2️⃣,如下:
select col3 as col5, col4 as col6from ( // 内部1️⃣已替换 真实字段 别名字段 真实表名 col1 col3 table4 col2 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 table4col2 col6 table5
步骤三:union左边的
SELECT col1 as cc1, col2 as cc2 FROM table1union
参考步骤二:
真实字段 别名字段 真实表名col1 cc1 table1col2 cc2 table1
合并去看
目标表:
真实字段 别名字段 表名 col1 cc1 new_table col2 cc2 new_table
union左:
真实字段 别名字段 真实表名col1 cc1 table1col2 cc2 table1
union右:
真实字段 别名字段 真实表名col1 col5 table4col2 col6 table5
血缘去匹配过程为:
union有几个就匹配几次,这里有两个就匹配两次 组成两组血缘
注意注意!!! 该过程与步骤2、3的追溯过程不一样
第一组:目标字段 & union左
# 目标字段真实字段 别名字段 真实表名 col1 cc1 new_table col2 cc2 new_table# union左sql字段真实字段 别名字段 真实表名col1 cc1 table1col2 cc2 table1
匹配过程:
1、应该是一一对应的过程,而不是追溯寻找的过程。
2、例如【目标字段】第一行对应下面【union左sql字段】第一行,【目标字段】第二行对应下面【union左sql字段】第二行,各自只取真实的即可

来源表 来源字段 目标表 目标字段table1 col1 new_table cc1table1 col2 new_table cc2
3、关于select查询表有join连接逻辑(也要产生血缘)
# 目标字段真实字段 别名字段 真实表名 col1 cc1 new_table col2 cc2 new_table# 涉及到 join 关联真实字段 真实表名(需要去匹配到) 表别名字段col3 table4 t4col3 table5 t5
产生4条血缘规则(每一个目标真实表及字段映射join关联的真实表及字段):
来源表 来源字段 目标表 目标字段table4 col3 new_table cc1table5 col3 new_table cc2
第二组:目标血缘 & union右
datablau血缘问题记录
问题1:复杂sql加上on条件之后血缘解析有问题
无问题:
CREATE TABLE new_table ASSELECT col1 as cc1, col2 as cc2 FROM table1unionselect col3, col4from ( select t4.col1 as col3, t5.col2 as col4 from table4 as t4 left join table5 t5 on t4.col1 = t5.col1 ) as tt

有问题:修改on条件之后,影响new_table中的字段为col3、col3
// left join table5 t5 on t4.col1 = t5.col1CREATE TABLE new_table ASSELECT col1 as cc1, col2 as cc2 FROM table1unionselect col3, col4from ( select t4.col1 as col3, t5.col2 as col4 from table4 as t4 left join table5 t5 on t4.col3 = t5.col3 ) as tt

问题2:union on问题
CREATE TABLE new_table ASSELECT col1, col2 FROM table1UNIONSELECT col3, col4 FROM table2;

问题描述:错误的将目标表中的字段作为了col3、col4。