SQL血缘解析
Druid
作为使用率特别高的的数据库连接池工具,在具备完善的连接池管理功能外,同时Druid 的 SQL解析功能
可以用来防止 SQL注入
等安全风险。通过对 SQL 语句进行解析和检查,Druid 可以识别并阻止潜在的恶意 SQL 语句执行,黑名单(阻止特定的 SQL 语句执行)、白名单(仅允许特定的 SQL 语句执行)等功能,从而提升数据库的安全性。
可以利用 Druid 的 SQL 解析结果,进行进一步的二次解析和组合,从而形成脚本的血缘关系和影响关系。
测试访问地址:http://43.139.19.48:8888/#/
Gitee:https://gitee.com/javalxy/sql-lineage-show
前置知识
AST抽象语法树
官方文档链接:Druid_SQL_AST · alibaba/druid Wiki · GitHub
抽象语法树(AST, Abstract Syntax Tree) 是编程语言解析和编译过程中产生的一种树状数据结构,它表示源代码的语法结构。AST 抽象出代码的语法特征,同时剔除不必要的语法细节,提供了程序结构的抽象表示。
SQLTableSource
常见的SQLTableSource包括SQLExprTableSource、SQLJoinTableSource、SQLSubqueryTableSource、SQLUnionQuery
主要为表的关联类型,针对与不同的SQL,分支处理。
if (tableSource instanceof SQLExprTableSource) {
SQLExpr sqlExpr = ((SQLExprTableSource) tableSource).getExpr();
processTableExpression(tableName, sqlExpr);
} else if (tableSource instanceof SQLSubqueryTableSource) {
List<SQLSelectItem> selectList = queryBlock.getSelectList();
selectList.forEach(item -> processTableExpression(tableName, item.getExpr()));
} else if (tableSource instanceof SQLUnionQuery) {
SQLUnionQuery sqlUnionQuery = (SQLUnionQuery) tableSource;
List<SQLSelectQuery> relations = sqlUnionQuery.getRelations();
for (SQLSelectQuery relation : relations) {
if (relation instanceof SQLSelectQueryBlock) {
SQLTableSource nestedTableSource = ((SQLSelectQueryBlock) relation).getFrom();
if (nestedTableSource instanceof SQLExprTableSource) {
SQLExpr sqlExpr = ((SQLExprTableSource) nestedTableSource).getExpr();
processTableExpression(tableName, sqlExpr);
}
}
}
}
SQLSelect & SQLSelectQuery
SQLSelectStatement
包含一个SQLSelect
,SQLSelect包含一个SQLSelectQuery
,都是组成的关系。SQLSelectQuery有主要的两个派生类,分别是SQLSelectQueryBlock
和SQLUnionQuery
。
此对象是一个查询主体,其主要的关联关系就是查询的关系。这种关系由查询字段衍生出来,涵盖了查询字段、关系字段、函数、组成等(也就是可解析的字段类型,后文会详细说明,即:SQLExpr
)。除此之外,还有表关系(如上文提到的 SQLTableSource
)、分组关系等。这些关系共同构成了 SQL 语句的结构和逻辑。此对象也可看做是解析的中流砥柱,很多向下的衍生关系也都通过此封装。
if (sqlSelectQuery instanceof SQLSelectQueryBlock) {
disposeSelectQuery((SQLSelectQueryBlock) sqlSelectQuery, sqlItems, fieldModels);
} else if (sqlSelectQuery instanceof SQLUnionQuery) {
disposeSQLUnionQuery((SQLUnionQuery) sqlSelectQuery, sqlItems, fieldModels);
}
SQLExpr
解析代码中大约 80% 的内容可能都围绕着 SQLExpr
进行。在所有的 SQLExpr
子对象中,通常存在多层次的继承关系。因此,无论表达式多么复杂,都可以通过递归层层向下,找到基本类型进行处理。这种处理方式相当繁琐,因为需要针对每种类型进行细致的拆解和处理。对于几种基本类型,可以直接进行处理;但对于聚合函数或二元操作这样的复杂类型,则需要逐层拆解,获取其下层类型后再判断并处理,如此反复,直至解析到基本类型为止,形成一个完整的闭环。
目前已处理类型如下:
SQLAggregateExpr、SQLBinaryOpExpr、SQLPropertyExpr、SQLMethodInvokeExpr、SQLAllColumnExpr、SQLIdentifierExpr、SQLCaseExpr、SQLInListExpr、SQLCastExpr、SQLBetweenExpr、SQLQueryExpr、SQLCharExpr、SQLNullExpr、SQLIntegerExpr、SQLNumberExpr
其中也存在无法处理的情况,如常量类型,All类型(*),需要其他逻辑支持。
if (sqlExpr instanceof SQLAggregateExpr) {
// 处理聚合函数表达式
dispose((SQLAggregateExpr) sqlExpr, itemFieldModels);
} else if (sqlExpr instanceof SQLBinaryOpExpr) {
// 处理二元操作表达式
dispose((SQLBinaryOpExpr) sqlExpr, itemFieldModels);
} else if (sqlExpr instanceof SQLPropertyExpr) {
// 处理属性表达式
dispose((SQLPropertyExpr) sqlExpr, itemFieldModels);
} else if (sqlExpr instanceof SQLMethodInvokeExpr) {
// 处理方法调用表达式
dispose((SQLMethodInvokeExpr) sqlExpr, itemFieldModels);
} else if (sqlExpr instanceof SQLAllColumnExpr) {
// 处理所有列(*)表达式
dispose((SQLAllColumnExpr) sqlExpr, itemFieldModels);
} else if (sqlExpr instanceof SQLIdentifierExpr) {
// 处理标识符表达式
dispose((SQLIdentifierExpr) sqlExpr, itemFieldModels);
}
// 略...
示例
此示例和druid解析无关系。
SELECT name, age FROM users WHERE age > 18;
对应的 AST
这个 SQL 查询的 AST(抽象语法树)可以表示如下:
SelectStatement
├── SelectClause
│ ├── Column (name)
│ └── Column (age)
├── FromClause
│ └── Table (users)
└── WhereClause
└── BinaryExpression (>)
├── Column (age)
└── Literal (18)
一条正确的 SQL 语句可以通过抽象语法树(AST)进行概括(通常使用 ANTLR
作为底层支持,排除 g4
模板解析失败的情况)。对于较复杂的语句,如关联查询、子查询和函数处理等,这些语句基本上是层层嵌套的。理论上,通过支持各种类型的解析后,可以使用递归方式遍历整个抽象语法树,从而完整地获取字段结构并进行二次处理。
二次处理Druid AST抽象语法树
使用Druid解析SQL
List