Druid学习笔记:Druid的AstNode类详解与其他产品测试体验

coverImg

解析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都可以直接调用这个方法求值
    @Override
    public 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、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 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 TABLE
  new_table AS
SELECT col1 as cc1, col2 as cc2 FROM table1
union
select
  col3,
  col4
from
  (
    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 AS
SELECT col1 as cc1, col2 as cc2 FROM table1
union
select

得到的一组目标写入字段为:

真实字段   别名字段    真实表名
  col1     cc1     new_table
  col2     cc2     new_table

步骤二:union右边的(带子查询、left join)

关注下面这部分:

union
select
  col3,
  col4
from
  (
    // 内部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 col4
from
  table4 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      t4
 table5      t5
# 2、借助select的字段去匹配from、left join的场景实现替换逻辑
# 由于t4与表别名字段t4匹配,则表字段记录的真实表名替换为相应的真实表名

真实字段  别名字段  真实表名
col1 			col3    table4
col2			col4    table5

在看外部2️⃣,如下:

select
  col3 as col5,
  col4 as col6
from
  (
      // 内部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    table4
col2			col6    table5

步骤三:union左边的

SELECT col1 as cc1, col2 as cc2 FROM table1
union

参考步骤二:

真实字段  别名字段  真实表名
col1 			cc1    table1
col2			cc2    table1

合并去看

目标表:

真实字段  别名字段      表名
  col1    cc1     new_table
  col2    cc2     new_table

union左:

真实字段  别名字段  真实表名
col1 			cc1    table1
col2			cc2    table1

union右:

真实字段  别名字段  真实表名
col1 			col5    table4
col2			col6    table5

血缘去匹配过程为:

union有几个就匹配几次,这里有两个就匹配两次 组成两组血缘

注意注意!!! 该过程与步骤2、3的追溯过程不一样

第一组:目标字段 & union左

# 目标字段
真实字段   别名字段   真实表名
 col1    cc1     new_table
 col2    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_table
 col2    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 TABLE
  new_table AS
SELECT col1 as cc1, col2 as cc2 FROM table1
union
select
  col3,
  col4
from
  (
    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.col1
CREATE TABLE
  new_table AS
SELECT col1 as cc1, col2 as cc2 FROM table1
union
select
  col3,
  col4
from
  (
    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 AS
SELECT col1, col2 FROM table1
UNION
SELECT col3, col4 FROM table2;

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


整理者:长路 时间:2025.3.21

评论区请在客户端页面查看