基于Druid的HiveSQL血缘解析

目录

前言

一、Druid简介

二、Druid SQL Parser

Parser

AST

Visitor

三、血缘功能实现

1.建表语句

1.直接Create+字段定义

2. Create table... as select..

 2.插入

1.标准语法

2.高级语法(Multiple Inserts)

3.高级语法(Dynamic Partition Inserts)

点关注,防走丢,如有纰漏之处,请留言指教,非常感谢



前言

之前开发的基于Python语言的sqlparse库开发的SQL语言通用解析工具目前已经开源至github,大家如果有需要可以去看:https://github.com/Fanstuck/SQLblood-relationship。我说过做Python的SQL解析算是一个对AST解析树的深入理解。没想到的是基于sqlparse的工具做出sql解析是可行的,这涉及到较多的递归和判断,但是我写的程序应对的SQL语句应该是不多的1,很多条SQL语句都没有测试完还是有一定的风险的。如果大家有想要解析的SQL可以私信发我,将免费提供SQL解析,如果程序功能和兼容性足够完善的话,将再出一篇文章把所有的解析过程详解。

本篇文章主要讲述的是直接利用Druid的功能直接实现血缘解析,就不再过多的去解析其底层AST树的解析了,大致的做法都是相同的。Druid用于解析sql的工具是本身自带,其主要是数据库连接池实现。


一、Druid简介

Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。Druid连接池为监控而生,内置强大的监控功能,监控特性不影响性能。功能强大,能防SQL注入,内置Loging能诊断Hack应用行为。也正是因为有监控SQL注入因此必须要对上交的SQL任务进行解析,获取关键字段。

首先SQL本质上是一种数据处理的描述语言,是一种描述语言的规范。 如果我们用简单字符串处理,使用字符串查找或者正则表达式来提取SQL中的字段,对于简单的SQL可以这样实现,但SQL规范还有复杂的开闭括号以及嵌套查询,复杂SQL几乎不可能通过字符串匹配来实现。因此我们需要将SQL解析。Druid内置的SQL Parser, SQL Parser是Druid的一个重要组成部分,Druid内置使用SQL Parser来实现防御SQL注入(WallFilter)、合并统计没有参数化的SQL(StatFilter的mergeSql)、SQL格式化、分库分表。 而且官方强调:和Antlr生成的SQL有很大不同的是,Druid SQL Parser性能非常好,可以用于生产环境直接对SQL进行分析处理。

通过阅览源码会发现基本主流数据库的SQL语句都支持解析:

数据库DMLDDL
odps完全支持完全支持
mysql完全支持完全支持
postgresql完全支持完全支持
oracle支持大部分支持大部分
sql server支持常用的支持常用的ddl
db2支持常用的支持常用的ddl
hive支持常用的支持常用的ddl

每个数据库都有自己对应的AST树解析、parser语法解析和visitor模式。个别几个数据库的解析较为特殊,比如Hive、mysql等带额外带有其他的功能。

二、Druid SQL Parser

Druid SQL Parser源码中主要的构成框架包括:Parser、AST和Visitor。

Parser

根据之前的研究我们清楚语法分析器(Parser):将上一步得到的Token流转换为语法定义的树结构。对于HiveSQL的解析来讲,对于其定义的grammar语法文件来看,其各个不同的语法解析文件就是其SQL执行过程的支撑,自然需要先解析获取其对应的语法结构:

 From的解析文件可以说是通用的,因此在parser并没有看到关于Hive的From文件,都统一由全局SQLParser获取。

 

这些特定数据库的类都全部由通用parser继承而来,添加新方法。

AST

AST是abstract syntax tree的缩写,也就是抽象语法树。和所有的Parser一样,Druid Parser会生成一个抽象语法树。

之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现;而类似于if-condition-then这样的条件跳转语句,可以使用带有两个分支的节点来表示。

和抽象语法树相对的是具体语法树。一般的,在源代码的翻译和编译过程中,语法分析器创建出分析树。一旦AST被创建出来,在后续的处理过程中,比如语义分析阶段,会添加一些信息。


    
    
  1. String sql_format=formatMysql(sql_4);
  2. final DbType dbType = JdbcConstants.HIVE;
  3. // SQLStatement就是AST
  4. List<SQLStatement> stmtList = SQLUtils.parseStatements(sql_4, dbType);
  5. System.out.println(stmtList);

 

在Druid中,AST节点类型主要包括SQLObject、SQLExpr、SQLStatement三种抽象类型。

官方文档解释的更加清楚:Druid_SQL_AST


    
    
  1. package com.alibaba.druid.sql.ast.expr;
  2. // SQLName是一种的SQLExpr的Expr,包括SQLIdentifierExpr、SQLPropertyExpr等
  3. public interface SQLName extends SQLExpr {}
  4. // 例如 ID = 3 这里的ID是一个SQLIdentifierExpr
  5. class SQLIdentifierExpr implements SQLExpr, SQLName {
  6. String name;
  7. }
  8. // 例如 A.ID = 3 这里的A.ID是一个SQLPropertyExpr
  9. class SQLPropertyExpr implements SQLExpr, SQLName {
  10. SQLExpr owner;
  11. String name;
  12. }
  13. // 例如 ID = 3 这是一个SQLBinaryOpExpr
  14. // left是ID (SQLIdentifierExpr)
  15. // right是3 (SQLIntegerExpr)
  16. class SQLBinaryOpExpr implements SQLExpr {
  17. SQLExpr left;
  18. SQLExpr right;
  19. SQLBinaryOperator operator;
  20. }
  21. // 例如 select * from where id = ?,这里的?是一个SQLVariantRefExpr,name是'?'
  22. class SQLVariantRefExpr extends SQLExprImpl {
  23. String name;
  24. }
  25. // 例如 ID = 3 这里的3是一个SQLIntegerExpr
  26. public class SQLIntegerExpr extends SQLNumericLiteralExpr implements SQLValuableExpr {
  27. Number number;
  28. // 所有实现了SQLValuableExpr接口的SQLExpr都可以直接调用这个方法求值
  29. @Override
  30. public Object getValue () {
  31. return this.number;
  32. }
  33. }
  34. // 例如 NAME = 'jobs' 这里的'jobs'是一个SQLCharExpr
  35. public class SQLCharExpr extends SQLTextLiteralExpr implements SQLValuableExpr{
  36. String text;
  37. }

最常用的Statement当然是SELECT/UPDATE/DELETE/INSERT,他们分别是


    
    
  1. package com.alibaba.druid.sql.ast.statement;
  2. class SQLSelectStatement implements SQLStatement {
  3. SQLSelect select;
  4. }
  5. class SQLUpdateStatement implements SQLStatement {
  6. SQLExprTableSource tableSource;
  7. List<SQLUpdateSetItem> items;
  8. SQLExpr where;
  9. }
  10. class SQLDeleteStatement implements SQLStatement {
  11. SQLTableSource tableSource;
  12. SQLExpr where;
  13. }
  14. class SQLInsertStatement implements SQLStatement {
  15. SQLExprTableSource tableSource;
  16. List<SQLExpr> columns;
  17. SQLSelect query;
  18. }

Visitor

Visitor是遍历AST的手段,是处理AST最方便的模式,Visitor是一个接口。Druid内置提供了如下Visitor:

  • OutputVisitor用来把AST输出为字符串
  • WallVisitor 来分析SQL语意来防御SQL注入攻击
  • ParameterizedOutputVisitor用来合并未参数化的SQL进行统计
  • EvalVisitor 用来对SQL表达式求值
  • ExportParameterVisitor用来提取SQL中的变量参数
  • SchemaStatVisitor 用来统计SQL中使用的表、字段、过滤条件、排序表达式、分组表达式
  • SQL格式化 Druid内置了基于语义的SQL格式化功能
     

Druid提供了多种默认实现的Visitor,可以满足基本需求,如果默认提供的不满足需求,可自行实现自定义Visitor。也就是利用该功能我们能够快速获取表与字段。

更多详细功能参阅官方对于Visitor的文档:https://github.com/Fanstuck/SQLblood-relationship

三、血缘功能实现

1.建表语句

关于建表SQL语句一般包括一下两种常见方式,以Hive建表语句为例:

1.直接Create+字段定义


    
    
  1. CREATE EXTERNAL TABLE dwd_database.table_name(
  2. id BIGINT,
  3. user_id STRING,
  4. gmt_modified TIMESTAMP,
  5. gmt_create TIMESTAMP,
  6. pending_reward INT,
  7. description STRING
  8. )
  9. PARTITIONED BY (
  10. pt STRING
  11. )
  12. row format delimited fields terminated by '\t'
  13. STORED AS TEXTFILE
  14. location 'hdfs://nameservice1/user/hive/warehouse/dwd_database.db/table_name';

 解析结果为:

2. Create table... as select..

这个存在多重嵌套select,涉及到表和字段。如:


    
    
  1. create table table_name
  2. as
  3. select * from t_table_name where pt = '20210829';

 解析结果为:

 2.插入

1.标准语法


    
    
  1. INSERT OVERWRITE TABLE tablename [ PARTITION (partcol1 =val1, partcol2 =val2 ...)] select_statement1 FROM from_statement;
  2. INSERT INTO TABLE tablename [ PARTITION (partcol1 =val1, partcol2 =val2 ...)] select_statement1 FROM from_statement;
  3. INSERT INTO TABLE tablename [ PARTITION (partcol1 =val1, partcol2 =val2 ...)](z, y) select_statement1 FROM from_statement;

2.高级语法(Multiple Inserts)


    
    
  1. FROM from_statement
  2. INSERT OVERWRITE TABLE tablename [ PARTITION (partcol1 =val1, partcol2 =val2 ...)] select_statement1
  3. [ INSERT OVERWRITE TABLE tablename2 [ PARTITION ...] select_statement2]
  4. [ INSERT INTO TABLE tablename2 [ PARTITION ...] select_statement2];

3.高级语法(Dynamic Partition Inserts)


   
   
  1. INSERT OVERWRITE TABLE tablename PARTITION (partcol1[ =val1], partcol2[ =val2] ...) select_statement FROM from_statement;
  2. INSERT INTO TABLE tablename PARTITION (partcol1[ =val1], partcol2[ =val2] ...) select_statement FROM from_statement;

解析和Create差不多直接代入功能就好了:

 这里我没有写那么多可以自行添加。好了先写这么多,内容已经足够多了,下篇文章将继续完善基础功能。

点关注,防走丢,如有纰漏之处,请留言指教,非常感谢

以上就是本期全部内容。我是fanstuck ,有问题大家随时留言讨论 ,我们下期见

参考

基于Druid的HiveSQL血缘解析

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Druid是一款非常优秀的实时OLAP数据库。它具有高性能、高可用、高可靠的特点,广泛应用于海量数据实时查询和分析。在Druid中,可以使用Druid SQL来查询数据,而Druid SQL不仅可以查询数据,还可以分析SQL血缘关系,实现血缘关系计算。 血缘关系计算是指分析SQL中的每个字段,找出它们来源的表、字段、过滤条件等信息,然后根据这些信息,计算出每个字段的血缘关系。这样,就可以清楚地知道一个字段是从哪个表的哪个字段计算出来的,从而对数据的来源和计算过程有更加深入的理解。 在Druid中,可以使用Apache Calcite来解析SQL,然后根据解析结果,计算血缘关系。具体来说,可以通过以下步骤实现: 1. 首先,需要配置Druid SQL。在Druid的配置文件中,可以设置"druid.sql.enable"参数为"true",表示开启Druid SQL功能。 2. 然后,在应用程序中,可以使用Druid SQL的API来解析SQL。具体来说,可以使用"SqlParser"类来解析SQL,返回一个"SqlNode"对象,表示SQL语句的语法树。 3. 接着,可以使用Apache Calcite提供的"RelBuilder"类来构建SQL语句的逻辑计划。具体来说,可以将"SqlNode"对象转换为"RelNode"对象,然后使用"RelBuilder"类来构建逻辑计划。 4. 最后,可以使用Apache Calcite提供的"RelOptUtil"类,计算逻辑计划的血缘关系。具体来说,可以使用"RelOptUtil"类的"computeLineage"方法,计算每个字段的血缘关系。 通过以上步骤,就可以实现Druid SQL血缘关系计算。在实际应用中,可以将血缘关系保存到数据库中,供后续分析和查询使用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值