Druid SQL 解析引擎:全面剖析 SQLTableSource 数据源抽象体系
SQLTableSource 架构概览
在 Alibaba Druid 的 SQL 解析引擎中,SQLTableSource 是抽象表示 FROM 子句中数据源的核心接口。无论是简单的物理表、多表关联、派生表(子查询)还是公共表表达式(CTE),在抽象语法树(AST)中均通过该接口的具体实现类来表达。其核心继承关系如下:
SQLTableSource (核心接口)
└── SQLTableSourceImpl (基础抽象实现)
├── SQLExprTableSource // 物理表或视图
├── SQLJoinTableSource // 表连接操作 (JOIN)
├── SQLSubqueryTableSource // 派生表 (子查询)
└── SQLWithSubqueryClause.Entry // CTE (WITH 子句定义的数据源)
核心子类深度解析与代码实战
1. SQLExprTableSource:基础物理表源
此类用于映射 FROM 关键字后直接跟随的物理表、视图及其别名,是最基础的数据源节点。
关键属性:
expr:表名标识(通常为SQLIdentifierExpr或SQLPropertyExpr)。alias:表别名(可选)。
SQL 场景:
SELECT order_id FROM sales_orders;
SELECT o.amount FROM sales_orders o;
SELECT * FROM inventory_db.stock_items;
Java 解析实现:
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.util.JdbcConstants;
public class PhysicalTableDemo {
public static void main(String[] args) {
String rawSql = "SELECT o.amount FROM sales_orders o";
SQLStatement parsedStmt = SQLUtils.parseSingleStatement(rawSql, JdbcConstants.MYSQL);
SQLSelectStatement selectStmt = (SQLSelectStatement) parsedStmt;
SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock) selectStmt.getSelect().getQuery();
SQLExprTableSource tableSrc = (SQLExprTableSource) queryBlock.getFrom();
System.out.println("Target Table: " + tableSrc.getExpr().toString());
System.out.println("Table Alias: " + tableSrc.getAlias());
}
}
2. SQLJoinTableSource:多表关联数据源
当 FROM 子句中包含 JOIN 操作时,Druid 会将其解析为 SQLJoinTableSource。它封装了左右数据源、连接类型以及关联条件。
关键属性:
left/right:左右两侧的数据源(均为SQLTableSource类型)。joinType:连接枚举(如INNER_JOIN,LEFT_OUTER_JOIN)。condition:ON 后面的条件表达式。
2.1 标准双表关联
SELECT u.username, d.dept_name
FROM users u
INNER JOIN departments d ON u.dept_id = d.id;
public class DualTableJoinDemo {
public static void main(String[] args) {
String rawSql = "SELECT u.username, d.dept_name FROM users u INNER JOIN departments d ON u.dept_id = d.id";
SQLStatement parsedStmt = SQLUtils.parseSingleStatement(rawSql, JdbcConstants.MYSQL);
SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock) ((SQLSelectStatement) parsedStmt).getSelect().getQuery();
SQLJoinTableSource joinSrc = (SQLJoinTableSource) queryBlock.getFrom();
SQLExprTableSource leftSrc = (SQLExprTableSource) joinSrc.getLeft();
System.out.println("Left: " + leftSrc.getExpr() + " AS " + leftSrc.getAlias());
SQLExprTableSource rightSrc = (SQLExprTableSource) joinSrc.getRight();
System.out.println("Right: " + rightSrc.getExpr() + " AS " + rightSrc.getAlias());
System.out.println("Join Type: " + joinSrc.getJoinType());
System.out.println("Condition: " + joinSrc.getCondition());
}
}
2.2 多表关联与左嵌套树结构
在 Druid 的 AST 设计中,当存在三个或更多表的连续 JOIN 时,解析器会自动构建左深树(Left-deep Tree)结构。例如 A JOIN B JOIN C 会被解析为 (A JOIN B) JOIN C。
SELECT *
FROM products p
JOIN categories c ON p.cat_id = c.id
JOIN brands b ON c.brand_id = b.id;
AST 嵌套模型:
SQLJoinTableSource (外层: (p JOIN c) JOIN b)
├── left = SQLJoinTableSource (内层: p JOIN c)
│ ├── left = products (SQLExprTableSource)
│ └── right = categories (SQLExprTableSource)
└── right = brands (SQLExprTableSource)
public class MultiTableJoinDemo {
public static void main(String[] args) {
String rawSql = "SELECT * FROM products p "
+ "JOIN categories c ON p.cat_id = c.id "
+ "JOIN brands b ON c.brand_id = b.id";
SQLStatement parsedStmt = SQLUtils.parseSingleStatement(rawSql, JdbcConstants.MYSQL);
SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock)
((SQLSelectStatement) parsedStmt).getSelect().getQuery();
SQLJoinTableSource outerJoin = (SQLJoinTableSource) queryBlock.getFrom();
System.out.println("--- Outer Join ---");
System.out.println("Condition: " + outerJoin.getCondition());
SQLJoinTableSource innerJoin = (SQLJoinTableSource) outerJoin.getLeft();
System.out.println("\n--- Inner Join ---");
System.out.println("Left Table: " + ((SQLExprTableSource) innerJoin.getLeft()).getExpr());
System.out.println("Right Table: " + ((SQLExprTableSource) innerJoin.getRight()).getExpr());
System.out.println("Condition: " + innerJoin.getCondition());
SQLExprTableSource outerRight = (SQLExprTableSource) outerJoin.getRight();
System.out.println("\n--- Outer Right Table ---");
System.out.println("Table: " + outerRight.getExpr());
}
}
3. SQLSubqueryTableSource:派生表(子查询)
用于表示 FROM 子句中由括号包裹的 SELECT 语句,即派生表。此类数据源必须指定别名。
关键属性:
select:内部的SQLSelect对象。alias:派生表的强制别名。
SELECT derived.total_amount
FROM (SELECT SUM(amount) AS total_amount FROM transactions) derived;
public class DerivedTableDemo {
public static void main(String[] args) {
String rawSql = "SELECT derived.total_amount FROM (SELECT SUM(amount) AS total_amount FROM transactions) derived";
SQLStatement parsedStmt = SQLUtils.parseSingleStatement(rawSql, JdbcConstants.MYSQL);
SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock) ((SQLSelectStatement) parsedStmt).getSelect().getQuery();
SQLSubqueryTableSource subSrc = (SQLSubqueryTableSource) queryBlock.getFrom();
System.out.println("Derived Table Alias: " + subSrc.getAlias());
System.out.println("Inner SQL: " + subSrc.getSelect());
}
}
4. SQLWithSubqueryClause.Entry:公共表表达式 (CTE)
映射 WITH ... AS (...) 语法定义的 CTE 节点。支持普通 CTE 和递归 CTE,是处理层次化数据或简化复杂查询的重要结构。
关键属性:
name:CTE 的标识名称。query:CTE 内部的查询逻辑。recursive:标识是否为递归 CTE。
-- 递归 CTE 示例:查询菜单树
WITH RECURSIVE menu_tree AS (
SELECT id, title, parent_id FROM menus WHERE parent_id IS NULL
UNION ALL
SELECT m.id, m.title, m.parent_id
FROM menus m
JOIN menu_tree mt ON m.parent_id = mt.id
)
SELECT * FROM menu_tree;
public class CteNodeDemo {
public static void main(String[] args) {
String rawSql = "WITH active_users AS (SELECT user_id FROM users WHERE status = 1) SELECT * FROM active_users";
SQLStatement parsedStmt = SQLUtils.parseSingleStatement(rawSql, JdbcConstants.MYSQL);
SQLSelectStatement selectStmt = (SQLSelectStatement) parsedStmt;
SQLSelect select = selectStmt.getSelect();
SQLWithSubqueryClause withClause = select.getWithSubqueryClause();
System.out.println("Is Recursive: " + withClause.isRecursive());
for (SQLWithSubqueryClause.Entry cteEntry : withClause.getEntries()) {
System.out.println("CTE Name: " + cteEntry.getName());
System.out.println("CTE Query: " + cteEntry.getQuery());
}
SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock) select.getQuery();
SQLExprTableSource fromSrc = (SQLExprTableSource) queryBlock.getFrom();
System.out.println("Main Query FROM: " + fromSrc.getExpr());
}
}
复杂嵌套场景的递归解析
在真实的业务系统中,SQL 语句往往混合了上述多种数据源类型。为了全面提取表元数据,我们需要编写递归算法来遍历 AST 树。
混合嵌套 SQL 示例:
WITH recent_orders AS (
SELECT id, user_id, amount FROM orders WHERE created_at > '2023-01-01'
)
SELECT r.amount, u.username
FROM recent_orders r
JOIN (SELECT id, username FROM users) u ON r.user_id = u.id;
AST 解析逻辑:
WITH块解析为SQLWithSubqueryClause.Entry。- 主查询的
FROM recent_orders r解析为SQLExprTableSource。 JOIN操作解析为SQLJoinTableSource,其右表为SQLSubqueryTableSource。
通用递归解析器实现:
public class AstTableSourceVisitor {
public static void traverse(SQLTableSource source) {
if (source instanceof SQLExprTableSource) {
SQLExprTableSource exprSrc = (SQLExprTableSource) source;
System.out.printf("[Physical Table] Name: %s, Alias: %s%n", exprSrc.getExpr(), exprSrc.getAlias());
}
else if (source instanceof SQLJoinTableSource) {
SQLJoinTableSource joinSrc = (SQLJoinTableSource) source;
System.out.printf("[Join Operation] Type: %s%n", joinSrc.getJoinType());
traverse(joinSrc.getLeft());
traverse(joinSrc.getRight());
}
else if (source instanceof SQLSubqueryTableSource) {
SQLSubqueryTableSource subSrc = (SQLSubqueryTableSource) source;
System.out.printf("[Derived Table] Alias: %s%n", subSrc.getAlias());
// 可在此处继续递归解析 subSrc.getSelect().getQuery()
}
}
public static void main(String[] args) {
String complexSql = "WITH recent_orders AS (SELECT id, user_id FROM orders) " +
"SELECT r.id, u.username FROM recent_orders r " +
"JOIN (SELECT id, username FROM users) u ON r.user_id = u.id";
SQLStatement parsedStmt = SQLUtils.parseSingleStatement(complexSql, JdbcConstants.MYSQL);
SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock) ((SQLSelectStatement) parsedStmt).getSelect().getQuery();
traverse(queryBlock.getFrom());
}
}
核心工程应用场景
- 数据血缘追踪:通过遍历
SQLTableSource提取所有物理表名及关联关系,构建企业级数据血缘图谱。 - SQL 动态改写与优化:在 AST 层面替换表名、注入租户 ID 条件,或调整多表 JOIN 的执行顺序以提升查询性能。
- 安全合规与防注入:拦截并校验
FROM子句中的数据源,防止恶意子查询或越权访问敏感物理表。 - 分库分表路由计算:精准提取目标表名,结合分片键(Sharding Key)计算路由规则,将请求分发至正确的底层数据库节点。