TOC
Open TOC
前言
数据血缘(Data Lineage)又叫做数据起源(Data Provenance)或者数据家谱(Data Pedigree)。用来描述数据的来源以及数据随时间移动的位置关系。 数据血缘是数据资产的重要组成部分,用于分析表和字段从数据源到当前表的血缘路径,以及血缘字段之间存在的关系是否满足,并关注数据一致性以及表设计的合理。它描述了数据从收集,生产到服务的全链路的变化和存在形式。
实现思路
通常要实现基于 SQL 的数据血缘分析,需要先获取到 SQL AST,然后针对获取对应数据模式下的来源表名和写入表名。在 starrocks 中官方提供的插件不能够完整获取 sql ast,但是额外的官方提供了 antlr4 语法文件。因此我们可以借助 antlr4 对原始 starrocks sql 做词法和语法分析,获取 ast。借助 antlr4 提供的两种 ast 遍历模式:Visitor 方式和 Listener 方式。我们就可以实现对应 SQL 的表名获取。
表血缘
在 starrocks 中做数据处理,离不开中间表。通常的 sql 样式为:目标表永远只有一个,来源表是 select 中所有 from 的真实表。
INSERT INTO ads_user_log_di
SELECT user_id, count(1) FROM view_log
WHERE date BETWEEN '2022-08-01' AND '2022-08-30'
GROUP BY user_id
针对这样的 sql,对应的 ast 为
我们期望获取其中 insert into 的表名和 from 的表名。这样就可以实现表维度的血缘关系分析。
Starrocks 语法解析
antlr4 语法文件:
StarRocks.g4 & StarRocksLex.g4 at main · StarRocks/starrocks
但是有两个问题需要解决:
- 语法文件中包含 java 语法,生成 Go 代码后不兼容
- 语法文件中部门函数名和 Go 接口名冲突
让我们先来解决第一个问题:
在StarRocksLex.g4
文件中存在下面两个 java 语法
@parser::members {public static long sqlMode;}
//....
LOGICAL_OR: '||' {setType((StarRocksParser.sqlMode & com.starrocks.qe.SqlModeHelper.MODE_PIPES_AS_CONCAT) == 0 ? LOGICAL_OR : StarRocksParser.CONCAT);};
可以移除或替换为go语法,例如
lexer grammar StarRocksLex;
// 移除java私有变量
// @parser::members {public static long sqlMode;}
// 或替换为go语法规则
// @parser::members {
// var sqlMode int
// }
tokens {
CONCAT
}
针对第二个问题: 可以参考下issue 3758
After looking over the generated files, and finding nothing obvious, I decided to check for the problem using automated means. I created a script (see below) that creates a new grammar with all parser rule symbols with a trailing underscore. It renames a symbol back to the original name without the underscore, and test how it builds.
The problem is “string”. A workaround is to rename it to something else, like “string_“. This needs to be added to the escaping code in the Antlr tool for Go.
所以解决办法也很简单,找到 StarRocks.g4 文件中如 string 等地方,替换成其他名称如 xstring string_ 等。
有个更简单的查询办法,先编译一版,运行时会提示不兼容。
这时根据对应的语法错误提示,就可以快速的找到对应的 token 名。然后替换下就可以了。 如:
literalExpression
: NULL #nullLiteral
| booleanValue #booleanLiteral
| number #numericLiteral
| (DATE | DATETIME) stringx #dateLiteral
| string #stringLiteral
| interval #intervalLiteral
;
其中第 6 行的 string 就可以替换成 xstring 来解决这个问题。
更进一步:字段血缘
对于字段血缘实现会麻烦一点,因为要将每个结果字段的层层关系找到并最后对应上真实表的字段,可能中间还会有多个字段计算为一个字段,一个字段于下层多个字段有血缘,还会有表别名,字段别名的干扰。
这边可以考虑将每个 select 剥离出来存成一个对象,其中包括来源表(来源子 select 则为 null),select 字段,父 select 的 Index(第一层则为 null)。
在解析完成后所有 select 的对象存为一个数组,然后逐个对最外层的字段进行溯源找到真实的来源表。