python解析sql字段血缘_使用Druid的sql parser做一个表数据血缘分析工具

前言

大数据场景下,每天可能都要在离线集群,运行大量的任务来支持业务、运营的分析查询。任务越来越多的时候,就会有越来越多的依赖关系,每一个任务都需要等需要的input表生产出来后,再去生产自己的output表。最开始的时候,依赖关系自然是可以通过管理员来管理,随着任务量的加大,就需要一个分析工具来解析任务的inputs、outs,并且自行依赖上生产inputs表的那些任务。本文就介绍一个使用druid parser,来解析SQL的input、output的血缘分析工具。

建议对druid比较陌生的同学可以先看下druid的官方文档。

做一次sql的血缘分析的流程

解析sql,拿到抽象语法树

遍历抽象语法树,得到from、to

使用druid解析sql到语法树

druid提供了简单、快速的SQL解析工具,可以很简单拿到一段SQL的AST(抽象语法树)。而druid对语法树提供了多种的SQLStatement,使遍历语法树更加容易。

SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, JdbcConstants.HIVE);

SQLStatement stmt= parser.parseStatementList().get(0);

从语法树中取出from和to

拿到语法树之后,想办法把from、to从语法树中取出来就大功告成。

最初的写法

最开始,就是简单的遍历一下语法树的节点,取出from表和to表的表名。

/**

* 根据create或者insert的sql取出from、to

* @param sql

* @return

* @throws ParserException

*/

private static Map> getFromTo(String sql) throws ParserException {

SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, JdbcConstants.HIVE);

SQLStatement stmt= parser.parseStatementList().get(0);

Set from = new HashSet<>();

Set to = new HashSet<>();

if (stmt instanceof SQLInsertStatement) {

SQLInsertStatement istmt = (SQLInsertStatement) stmt;

to.add(istmt.getTableSource().toString().toUpperCase());

SQLTableSource sts = istmt.getQuery().getQueryBlock().getFrom();

from = getFromTableFromTableSource(sts);

} else if (stmt instanceof SQLCreateTableStatement) {

SQLCreateTableStatement cstmt = (SQLCreateTableStatement) stmt;

to.add(cstmt.getTableSource().toString().toUpperCase());

SQLTableSource sts = cstmt.getSelect().getQueryBlock().getFrom();

from = getFromTableFromTableSource(sts);

}

Map> fromTo = new HashMap<>(4);

fromTo.put("from", from);

fromTo.put("to", to);

return fromTo;

}

private static Set getFromTableFromTableSource (SQLTableSource sts) {

Set from = new HashSet<>();

if (sts instanceof SQLJoinTableSource) {

from = getFromTableFromJoinSource((SQLJoinTableSource)sts);

} else {

from.add(sts.toString().toUpperCase());

}

return from;

}

private static Set getFromTableFromJoinSource (SQLJoinTableSource sjts) {

Set result = new HashSet<>();

getFromTable(result, sjts);

return result;

}

// 递归获取join的表list

private static void getFromTable (Set fromList, SQLJoinTableSource sjts) {

SQLTableSource left = sjts.getLeft();

if (left instanceof SQLJoinTableSource) {

getFromTable(fromList, (SQLJoinTableSource)left);

} else {

fromList.add(left.toString().toUpperCase());

}

SQLTableSource right = sjts.getRight();

if (right instanceof SQLJoinTableSource) {

getFromTable(fromList, (SQLJoinTableSource)right);

} else {

fromList.add(right.toString().toUpperCase());

}

}

用druid更好的实现

因为是为了快速完成,所以写的取出from、to表的部分还是存在很大的问题的。只能支持一条sql,只能支持简单的sql语句,比如union all或者子查询就有些无力。于是又看了一下文档,其实druid是提供了visitor方法来遍历语法树的,而且提供了一个简单的SchemaStatVisitor,可以取出Sql中所有用到的表。于是就可以写成这种格式。

public static Map> getFromTo (String sql) throws ParserException {

List stmts = SQLUtils.parseStatements(sql, JdbcConstants.HIVE);

TreeSet fromSet = new TreeSet<>();

TreeSet toSet = new TreeSet<>();

if (stmts == null) {

return null;

}

String database="DEFAULT";

for (SQLStatement stmt : stmts) {

SchemaStatVisitor statVisitor = SQLUtils.createSchemaStatVisitor(JdbcConstants.HIVE);

if (stmt instanceof SQLUseStatement) {

database = ((SQLUseStatement) stmt).getDatabase().getSimpleName().toUpperCase();

}

stmt.accept(statVisitor);

Map tables = statVisitor.getTables();

if (tables != null) {

final String db = database;

tables.forEach((tableName, stat) -> {

if (stat.getCreateCount() > 0 || stat.getInsertCount() > 0) {

String to = tableName.getName().toUpperCase();

if (!to.contains("."))

to = db + "." + to;

toSet.add(to);

} else if (stat.getSelectCount() > 0) {

String from = tableName.getName().toUpperCase();

if (!from.contains("."))

from = db + "." + from;

fromSet.add(from);

}

});

}

}

你可能感兴趣的:(python解析sql字段血缘)