当执行任意一条SELECT SQL语句时,我们如何能够分析出所访问的字段信息,并进一步判断结果集中的每一列数据具体来自于哪些数据库、表以及表中的哪些字段呢?本文将会详细阐述针对此问题的技术解决方案。
应用场景
从 SQL 中解析访问的原始字段信息有一定难度,但是非常有用。一个典型的应用就是动态数据脱敏。动态数据脱敏是指数据以明文形式存储在介质中,在查询的时候根据用户权限动态地将敏感数据进行脱敏后展示。例如执行以下 SQL 时,将结果集中涉及员工邮箱和薪资信息的列数据,按照不同列对应的脱敏算法做了脱敏处理。
问题定义
输入:SQL
输出:结果集中的每一列所涉及的数据库、表和字段信息
技术方案
SQL 语法解析
语法解析的目的是将 SQL 文本转化为抽象语法树(Abstract Syntax Tree, AST),方便后续通过编程形式进行分析处理。以 OceanBase MySQL 兼容模式为例,使用 ANTLR4 作为解析器生成器,从 此处 获取 .g4 语法文件。编译生成对应的 Parser,然后就可以基于 Visitor 访问 AST 从 SQL 中提取想要的信息了。举个例子,对于以下 SQL:
SELECT tb.col FROM tb WHERE col1='abc';
解析成的 AST 如下:其中,每个节点对应语法文件中定义的一个 Token。例如,语法文件中对 SELECT 子句的起始 Token 定义为
select_expr_list
,则可以从 select_expr_list 节点的子节点中获取 SELECT 子句中使用的列信息。可以发现,直接访问 AST 来提取 SQL 中的关键信息是比较困难和低效的,因为需要对语法文件和 AST 的结构有清晰的了解。因此,推荐使用 ob-sql-parser。它是开源的基于 ANTLR4 构建的 SQL 解析器,可以将 SQL 文本翻译成 Pojo 类,让程序员可以像操作普通 Java 对象一样处理 SQL。对于上面的 SQL,其处理后的对象结构如下:
SQL Text
└──Statement: Select
└──selectBody: SelectBody
├──selectItems: List<Projection>
│ └──Projection
| └──column: Expression
| └──RelationReference: tb.col
├──froms: List<FromReference>
│ └──NameReference: tb
└──where: Expression
└──CompoundExpression: col1='abc'
SELECT 字段检测
一条完整的 SELECT 语句中有且仅有以下类型的子句:
- FROM 子句
SELECT * FROM table1;
- JOIN 子句
SELECT * FROM table1 JOIN table2;
- SELECT 子句
SELECT col1, col2+col3, func(col4, col5) FROM table1;