如何做血缘解析?

blood

    • 一、AST抽象语法树
    • 二、血缘关系类
    • 三、递归迭代

血缘解析是数据治理中很关键的一环,本文着重讲解血缘解析的思路,如何把一段sql进行字段级别的解析,最终插入到数据库的数据表中,如下所示

target_tab target_col source_tab source_col source_flag is_valid calc_meth

一、AST抽象语法树

一段sql是如何被解析执行的呢?
无论是关系型数据库还是spark、hive等大数据引擎,第一步都是生成AST抽象语法树,流程如下

  • 词法分析:分析量化那些本来毫无意义的字符流,将它们翻译成离散的字符组(也就是一个个的token),包括关键字、标识符、符号和操作符供语法分析器使用,如sql语句中的select、as、from等等都是token
  • 语法分析:在分析字符流的时候,Lexer不关心所生成的单个token的语法意义及其上下文之间的关系,其一方面验证语法规则是否正确,另一方面使用预定义的语法规则来构建AST抽象语法树
  • AST构建:根据语法规则和词法单元序列,递归地构建AST,每个语法规则对应一个AST节点,而语法规则的非终结符对应AST的父节点,终结符对应AST节点的叶子节点

一般的AST抽象语法树如下所示

			SELECT
              |
           [Columns]
          /    |    \
   [Column] [Column] [Column]
      |        |        |
     id      name     age
      |
    FROM
      |
    [Table]
      |
    users
try{
    statements = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL);
}catch (Exception e){
    System.out.println("can't parser by druid mysql"+e);
}

上述代码是用druid sql来解析,常用的解析工具还有antlr4

二、血缘关系类

  • 新增对象进行血缘关系的存储,提供一些getter/setter方法
name desc
targetColumnName 目标字段
sourceDbName 字段来源Db
sourceTableName 字段来源表
sourceColumnName 字段来源列
expression 表达式,字段的加工过程
isEnd 是否结束标识

三、递归迭代

  • 通过递归迭代获取语法树结构,插入到血缘关系对象中,最终的叶子节点即为最终的血缘信息

比如说一段Sql传进来后,会先解析为AST抽象语法树
1、先传入Sql到解析入口,判断Sql是单独select语句还是包含union的select语句,若是包含union,通过 SQLUnionQuery.getLeft()和getRight()方法拆分union语句,再把拆分的语句放到解析的入口

columnLineageAnalyzer(((SQLUnionQuery) sqlSelectQuery).getLeft().toString(),node);
columnLineageAnalyzer(((SQLUnionQuery) sqlSelectQuery).getRight().toString(),node);

2、把单独的select语句或者union拆分的select语句传进来后,首先获取字段列表

List<SQLSelectItem> selectItems = sqlSelectQueryBlock.getSelectList();

然后遍历字段列表,通过getAlias获取字段的别名,若没有别名即为本身,别名就是targetColumnName,可以调用血缘关系对象的setTargetColumnName()方法
3、通过getExpr()获取字段的名称,也就是as之前的内容,若有加工过程也可获取到,然后调用setExpression()
4、解析表达式,上一步getExpr可能得到的字段是有多个字段加工而来,需要进行处理,解析表达式有如下种类,针对不同的类型有不同的处理方法,最终调用setSourceColumnName

  • 方法
  • 聚合
  • case
  • 比较
  • 表达式
  • 赋值表达式
  • 数字
  • 字符
  • 标量子查询
    private static void handlerExpr(SQLExpr sqlExpr,TreeNode<LineageColumn> itemNode) {
        //方法
        if (sqlExpr instanceof SQLMethodInvokeExpr){
            visitSQLMethodInvoke( (SQLMethodInvokeExpr) sqlExpr,itemNode);
        }
        //聚合
        else if (sqlExpr instanceof SQLAggregateExpr){
            visitSQLAggregateExpr((SQLAggregateExpr) sqlExpr,itemNode);
        }
        //case
        else if (sqlExpr instanceof SQLCaseExpr){
            visitSQLCaseExpr((SQLCaseExpr) sqlExpr,itemNode);
        }
        //比较
        else if (sqlExpr instanceof SQLBinaryOpExpr){
            visitSQLBinaryOpExpr((SQLBinaryOpExpr) sqlExpr,itemNode);
        }
        //表达式
        else if (sqlExpr instanceof SQLPropertyExpr){
            visitSQLPropertyExpr((SQLPropertyExpr) sqlExpr,itemNode);
        }
        //列
        else if (sqlExpr instanceof SQLIdentifierExpr){
            visitSQLIdentifierExpr((SQLIdentifierExpr) sqlExpr,itemNode);
        }
        //赋值表达式
        else if (sqlExpr instanceof SQLIntegerExpr){
            visitSQLIntegerExpr((SQLIntegerExpr) sqlExpr,itemNode);
        }
        //数字
        else if (sqlExpr instanceof SQLNumberExpr){
            visitSQLNumberExpr((SQLNumberExpr) sqlExpr,itemNode);
        }
        //字符
        else if (sqlExpr instanceof SQLCharExpr){
            visitSQLCharExpr((SQLCharExpr) sqlExpr,itemNode);
        //标量子查询
        } else if (sqlExpr instanceof SQLQueryExpr) {
            columnLineageAnalyzer(sqlExpr.toString(), itemNode);
        }
    }

5、字段处理完后,处理table,table有如下几种类型,每一种的处理方式也不一样

  • 单表:获取db、tableName,调用血缘关系的set方法
  • 子查询:子查询中间是一段Sql,再次传入血缘解析的入口
  • 表连接:通过getLeft()和getRight()获取关联的表,关联的表又有单表、子查询、表连接、union这几种类型,迭代即可
  • union:传入血缘解析入口即可
 if (isContinue.get()){
     // 获取表
     SQLTableSource table = sqlSelectQueryBlock.getFrom();
     // 普通单表
     if (table instanceof SQLExprTableSource) {
         // 处理最终表---------------------
         handlerSQLExprTableSource(node, (SQLExprTableSource) table);

     } else if (table instanceof SQLJoinTableSource) {
         //处理join
         handlerSQLJoinTableSource(node, (SQLJoinTableSource) table);

     } else if (table instanceof SQLSubqueryTableSource) {
         // 处理 subquery ---------------------
         handlerSQLSubqueryTableSource(node, table);

     }else if (table instanceof SQLUnionQueryTableSource) {
         // 处理 union ---------------------
         handlerSQLUnionQueryTableSource(node, (SQLUnionQueryTableSource) table);
     }
 }

你可能感兴趣的:(Java开发,sql,Java,血缘)