基于Hive解析AST的模仿sqlFlow无中间表的字段级数据血缘的后端

0、前言

之前写了篇 基于jsPlumb.js的模仿sqlFlow数据血缘图的前端页面,是利用jsplumb.js模仿sqlflow的前端。
然后有些小伙伴想看后端代码,当时第一版代码写得太乱了,所以就没直接发。磨了1个月,虽然还有bug但是也算基本能跑了。
在这里插入图片描述
github代码地址:源代码丢失,待重新编写后开源

1、项目整体概述、思路

  1. 需求:完成类似sqlflow这样的,根据sql离线来生成粒度最小为字段级的数据血缘关系,并可视化。输入的sql暂时为HiveSQL。
  2. 思路:离线解析,所以hive自带的hook LineageInfo和LineageLogger这两个工具类就用不上了(利用hive自带的钩子去解析血缘也是网上最多的方案)。一些开源的解析器(druid等)不太适合hive,所以只能自己写,一条sql在hive中大概会经历以下变化:
ANTLR
语义分析
原始SQL
AST
逻辑计划
物理执行计划
  • 第一步:Hive用ANTLR语法定义的词法和文法文件来进行解析,生成抽象语法树。(语法分析也是在这一步做的)
  • 后面几步:得到AST后,发现后面几步在hive源码中依赖太重了,我想从源码中剥离出来很困难,所以只能基于AST来写数据血缘了。
  1. 项目结构介绍「参考下图」
    在这里插入图片描述
  • parser包:自己写的解析AST、生成数据血缘的核心包
  • ql包:从hive 3.5.2中剥离出来的包用于将sql解析AST
  • bean、service、controller、utils:不需要解释吧?
  • resources:前端。来自我的基于jsPlumb.js的模仿sqlFlow数据血缘图的前端页面文章,有改动。static的index包下的index.html就是前端了
  1. 如何部署:下载项目后,启动HiveSqlDataLineageApplication类。然后浏览器访问http://localhost:12284/index/index.html,在页面的textarea中输入待解析的一条查询SQL,点击旁边的按钮visualize即可。
  2. 适用范围及注意事项
    目前仅支持select……from…… 这样的查询语句。
  • 0.一切的基础是基于Hive解析模块生成的AST,若解析失败后面的血缘信息也就自然没有。 所以Hive不支持的函数(spark的left函数等)、中文需要自行处理。
  • 对于CTE查询语句请删除 with部分
  • 尽量给查询的表、查询的列都起别名
  • select * 尽量不要写,因为得连接元数据库。
  • 若查找的列名为中文,hive解析模块是解析不动的!本来想做将中文列名转换为拼音的的功能,但只写了工具类没做实现

2、项目细节

在数据血缘中,我们只在乎字段的流向,所以关于Where,Group by,Order by等等不影响结果的部分都可以无视掉,可以在AST中剪枝方便我们找出AST的规律。只把关注点集中在Select 、From、 Join(left、inner等)、 Union(union all)这几个关键词上。

数据血缘我们很容易想到是有向无环图(DAG),并且查询语句sql最终查询结果一般都是只有一个结果表。
所以对于无中间表的数据血缘我们只需要找出所有的起源表一张结果表起源表与结果表字段间的映射关系即可。

2.1、找出结果表

由于是无中间临时表的情况,观察AST发现。在查询语句中,AST一般都是这样,TOK_FROM和TOK_INSERT是兄弟节点。TOK_FROM就是from,是原表。TOK_INSERT就是 select选取的结果集。

TOK_QUERY 
  TOK_FROM
  TOK_INSERT

并且无论sql咋复杂嵌套,最外层的TOK_INSERT一般都是最终生成的结果集(除了union的情况后面会说)。所以我们可以先解析TOK_INSERT下TOK_INSERT。

TOK_INSERT分支上一般只有两个子分支。TOK_DESTINATION不用关注且无用,TOK_SELECT就是结果表的所有列名所在地,每一个列名由下每个TOK_SELEXPR。TOK_SELEXPR就是列名的所在地,也是我们解析的大头。

TOK_INSERT
  TOK_DESTINATION
  TOK_SELECT

TOK_SELEXPR一般有以下情况

  • 列名(带表名或者起别名排列组合四种情况)
    在这里插入图片描述

  • 被函数包裹(case、if也算)

  • 常量和一些可以视为常量的由函数生成的列(如current_timestamp函数)

列名的四种情况分情况讨论解析即可。
函数主要是解析出是什么函数,方便在该列没有起别名的情况下用 函数名(列名)做别名。一些特殊函数可能需要为其单独写分支处理。
常量单独解析即可因为AST结构和上面两类不一样。
具体参考代码的SelexprParser类。


结果表的特殊情况就是如果是 查询语句 unionall 查询语句,导致 AST结果改变失去顶级查询TOK_INSERT下的TOK_SELECT的TOK_SELEXPR。变成TOK_SETCOLREF,反正最后就打捞不到结果集。

TOK_QUERY=954
      TOK_FROM=840
        TOK_SUBQUERY=1026
          TOK_UNIONALL=1082
            TOK_QUERY=954
            TOK_UNIONALL=954
      TOK_INSERT=859
        TOK_DESTINATION=810
        TOK_SELECT=983
          TOK_SELEXPR=985
            TOK_SETCOLREF=990

我们得从TOK_FROM中打捞结果表了。这种情况下,由于union关键字(TOK_UNIONALL)可以嵌套。我们要从TOK_FROM递归去找最深处的TOK_UNIONALL下的TOK_QUERY节点。具体参考代码 CoreParser类的genRSTable()方法

2.2、找出起源表

起源表就有很多了。

分析ast可以发现,TOK_FROM是一系列子查询、join操作、union等操作的集合地。TOK_FROM通过嵌套大量的子查询、join、union来实现sql查询操作。在TOK_FROM中,还是以TOK_QUERY作为基本单元,以TOK_UNIONALL、TOK_SUBQUERY、TOK_LEFTOUTERJOIN等作为粘合剂,最终形式一个大的临时表即最终查询表供最终查询使用。

TOK_QUERY
	TOK_FROM
		TOK_JOIN|TOK_SUBQUERY|TOK_TABREF
	TOK_INSERT

观察AST后,我计划从最大的TOK_FROM下手,提取最外层TOK_FROM里的TOK_QUERY单元作为起源表。除此之外,最外层TOK_INSERT里的结果集若带表名,虽然往往带的是原表名的别名,但是可以在后面将别名映射到原表名。

关于表名的解析,带不带库名也分为四种情况,如下图按情况分类讨论即可。
在这里插入图片描述


下面是没整理过的关键字解析,有些乱想看就看看吧!

  • union从血缘的角度很简单,因为union本身限制,特别在hive中列名和列数都必须一样(hive官方文档说的),所以就更简单了。在无中间表的情况下我们直接忽视。

  • 子查询。子查询指示其第零个子节点是TOK_QUERY或TOK_UNIONALL。TOK_SUBQUERY出现在TOK_UNIONALL、TOK_JOIN、TOK_FRO的第零个子节点。在无中间表的情况下我们碰到子查询只需要按TOK_QUERY查询的方法即可(其实就是直接不管)。

  • join:
    对于血缘来说join是怎样的并无所谓,leftjoin、right join 、inner Join(TOK_JOIN)并无不同。join不会显示临时表,因为join的目的是基于过滤条件筛选后,将两张表的列名成一张新表。血缘不在乎过滤,所以可以视为从join的两张表中打捞源表。

    join的表类型有两种情况

  • 直接join一个基本表:left join db.tb c,这种情况应该直接添加生成源表,这种情况join的表都是在最终查询用的。我们

  • 要不在构建RS表的时候顺便构建origin表,

  • 要不再rs表构建完后再利用rsRelation带表名的特征构建,这个感觉麻烦

  • join的是一个子查询,这种情况毕竟多,而且子查询又可能嵌套。

    两种类型的表会有三种组合

  1. join两个表均是基本表,两边都是基本表的情况,似乎只需要将表名打捞出.来当起源表即可。基本表join一般是最终查询中用到,我们先把表名打捞出来并记录映射关系,先生成没有字段集合的表集合,之后从rs中找出字段形成新表

    TOK_LEFTOUTERJOIN=881
          TOK_TABREF=1062
          	TOK_TABNAME=1061
              aa_aar_safwew =24
           	zfvw=24
           TOK_TABREF=1062
               TOK_TABNAME=1061
                 aa_aa_info =24
               abcc=24
           and=36
           	 ……(on的条件)
    
  2. join两个表一个基本表一个子查询,这会导致parseVanillaTOK_QUERY无法解析

    TOK_QUERY
    	TOK_FROM
    		TOK_JOIN
    			TOK_TABREF
    			TOK_SUBQUERY
    		TOK_INSERT
    
  3. join两个表均是子查询,子查询最终还是可以vanillaTOK_QUERY解析出结果,不会被parseVanillaTOK_QUERY遗漏

    TOK_JOIN
    	TOK_SUBQUERY
    	TOK_SUBQUERY
    		TOK_QUERY
    			TOK_FROM
    				TOK_TABREF
    			TOK_INSERT
    
  4. 还有join生成的表再次被join,但伪血缘仅仅是需要打捞不需要考虑

总的来说遇到TOK_JOIN、TOK_LEFTOUTERJOIN,还是按照之前的想法,找到最底层的TOK_LEFTOUTERJOIN、TOK_JOIN,然后打捞源表

TOK_JOIN第零个子节点可以为TOK_TABREF、TOK_SUBQUERY,还可以嵌套其他TOK_JOIN

TOK_FROM|TOK_LEFTOUTERJOIN
    TOK_JOIN
          TOK_TABREF=1062
                TOK_TABNAME=1061
                     a_bcdd=24
                     aa_b_cccc_va_irgwev_amanasdfd=24                   a=24
          TOK_SUBQUERY|TOK_TABREF|TOK_LEFTOUTERJOIN
2.3、关系

n个起源表和一个结果表搞定后,就需要构建关系了。这边基于字段分了四步:

  1. 先找 originTable的某个set里的字段名称和RST中的字段名相同的字段去构建大部分关系
  2. 将原始的rsNameToAliasRelations 的表名替换成起源表名,构建关系
  3. 处理被遗弃的 rsAbandonedRSColsMap
  4. originColToAliasRelations 检查是否还有遗漏
    具体流程参考下图:
    在这里插入图片描述
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
要实现Hive SQL文件的数据血缘解析,您可以使用Python的第三方PyHive来连接Hive并执行SQL语句,然后使用Python的其他(例如sqlparse)来解析SQL语句和生成血缘关系图。 以下是一个基本的实现流程: 1. 连接到Hive并执行查询,获取SQL语句。 ```python from pyhive import hive # 连接到Hive conn = hive.Connection(host='localhost', port=10000, username='username') cursor = conn.cursor() # 执行查询 cursor.execute('SELECT * FROM mytable') sql = cursor.query_string ``` 2. 使用sqlparse解析SQL语句,获取名、字段名等信息。 ```python import sqlparse # 解析SQL语句 parsed = sqlparse.parse(sql)[0] # 获取名 table_name = None for token in parsed.tokens: if isinstance(token, sqlparse.sql.Identifier) and token.value.lower() == 'from': table_name = next(parsed.token_next(parsed.token_index(token))).value break # 获取字段名 field_names = [] for token in parsed.tokens: if isinstance(token, sqlparse.sql.IdentifierList): for identifier in token.get_identifiers(): field_names.append(identifier.value) ``` 3. 根据名和字段名,生成血缘关系图。 ```python # TODO: 根据名和字段名生成血缘关系图 ``` 4. 输出结果,包括源、目标、源字段、目标字段等信息。 ```python # TODO: 输出结果,包括源、目标、源字段、目标字段等信息 ``` 注意,这只是一个基本的实现流程,您需要根据实际需求进行修改和完善。例如,您可能需要处理更复杂的SQL语句、支持更多的SQL方言、生成更详细的血缘关系图等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值