应用Druid解析SQL获取查询表字段、参数信息

业务需求:输入一条SQL,解析出对应的结果表字段列表与参数列表。

select t.id,t.name,date_format(update_time,'%Y-%m') as update_month
from t_user t
where t.account=#{account:varchar};

解析后需获得:

  • 结果字段信息:id,name,update_month
  • 涉及的表:t_user(别名>t)
  • 参数信息:account
  • 格式化处理后的SQL语句: select t.id,t.name,date_format(update_time,‘%Y-%m’) as update_month from t_user t where t.account=?;

因为输入的SQL复杂度不一,可能存在子查询和连表查询,所以想借用轮子,再自己定制需要的部分。调研了几个SQL Parser,结合当前项目,还是选择了开源的druid sql parser。

Druid是Java语言中最好的数据库连接池。提供强大的监控和扩展功能。其中,SQL Parser是Druid的一个重要组成部分,可用来实现防御SQL注入(WallFilter)、合并统计没有参数化的SQL(StatFilter的mergeSql)、SQL格式化、分库分表。
https://github.com/alibaba/druid
https://github.com/alibaba/druid/wiki/SQL-Parser

Druid的sql parser是目前支持各种数据语法最完备的SQL Parser。目前对各种数据库的支持如下:

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

对于本文中的业务场景,是支持。

Druid SQL Parser分三个模块:Parser、AST、Visitor。Parser将SQL文本解析为AST语法树,Visitor有遍历AST的能力。
在本文中因为输入的是相对规范的sql语法,Parser可以完全解析为需要的语法树。所以后续只需要自定义Visitor来获取需要的信息。
其中有提供SQLASTParameterizedVisitor(重点在:参数等)、SchemaStatVisitor(重点在:数据表关系、排序、分组等)等来获取AST的信息。因为没有整合开头所需的内容数据,所以本文自定义了一个。

注意:不要直接继承SQLASTParameterizedVisitor、SchemaStatVisitor这些已经实现SQLASTVisitor接口的类,因为会导致执行顺序偏差,反正在测试时,发现参数解析出现了丢失情况。

解析获得的结果类:

@Data
public class CustomSQLParser {
    /**
     * 格式化SQL语句
     */
    private String formatSql;
    /**
     * 预处理后SQL语句
     */
    private String prepareSql;
    /**
     * 涉及的表信息 tableName、tableAlias
     */
    private List<CustomTable> tables;
    /**
     * 查询获得的结果字段信息 column、columnAlias、dataType
     */
    private List<CustomColumn> columns;
    /**
     * 参数信息 paramName、paramType、expr、operation
     */
    private List<CustomParameter> parameters;

    /**
     * 参数名称
     */
    private List<String> parameterNames;
}

自定义Visitor

@Data
public class CustomSQLASTVisitor implements SQLASTVisitor {
    public static final Pattern PARAMETER_PATTERN = Pattern.compile("[#\\$]\\{(\\w+)(?::(\\w+))?\\}");
    protected List<SQLSelectItem> selectItems = new ArrayList<>();
    protected List<CustomTable> selectTables = new ArrayList<>();
    protected List<CustomColumn> selectColumns = new ArrayList<>();
    protected List<String> parameterNames = new ArrayList<>();
    protected List<CustomParameter> parameters = new ArrayList<>();
    protected List<String> columnNames = new ArrayList<>();
    protected Map<String, String> opera = new HashMap<>();


    @Override
    public void endVisit(SQLSelectQueryBlock x) {
        computeCustomColumnBeans();
    }

    @Override
    public boolean visit(SQLExprTableSource sqlExprTableSource) {
        selectTables.add(new CustomTable(sqlExprTableSource));
        return false;
    }

    @Override
    public boolean visit(SQLCharExpr sqlCharExpr ) {
        parserParameter(sqlCharExpr.toString());
        return false;
    }


    @Override
    public boolean visit(SQLSelectItem sqlSelectItem) {
        selectItems.add(sqlSelectItem );
        return false;
    }

    @Override
    public boolean visit(SQLVariantRefExpr sqlVariantRefExpr) {
        parserParameter(sqlVariantRefExpr.getName());
        return false;
    }


    @Override
    public void endVisit(SQLBinaryOpExpr sqlBinaryOpExpr) {
        parserWhereParam(sqlBinaryOpExpr);
        this.parameters.forEach(p->{
            if(opera.containsKey(p.getExpr())){
                p.setOperation(opera.get(p.getExpr()));
            }
        });
    }

    protected void parserParameter(String expr) {
        Matcher matcher = PARAMETER_PATTERN.matcher(expr);
        if (matcher.find()) {
            String field = matcher.group(1);
            String fieldType = matcher.group(2);
            parameterNames.add(field);
            parameters.add(new CustomParameter(field, fieldType, expr));

        }
    }

    protected void computeCustomColumnBeans() {
        selectItems.forEach(item -> {
            String alias = item.getAlias();
            CustomColumn curColumn = null;
            if (item.getExpr() instanceof SQLIdentifierExpr) {
                SQLIdentifierExpr expr = (SQLIdentifierExpr) item.getExpr();
                curColumn = new CustomColumn(selectTables.get(0).getTableName(), selectTables.get(0).getTableAlias(), expr.getName(), alias);
            } else if (item.getExpr() instanceof SQLAllColumnExpr) {
                SQLAllColumnExpr expr = (SQLAllColumnExpr) item.getExpr();
                curColumn = new CustomColumn(selectTables.get(0).getTableName(), selectTables.get(0).getTableAlias(), expr.toString(), alias);
            } else if (item.getExpr() instanceof SQLMethodInvokeExpr) {
                SQLMethodInvokeExpr expr = (SQLMethodInvokeExpr) item.getExpr();
                curColumn = new CustomColumn(selectTables.get(0).getTableName(), selectTables.get(0).getTableAlias(), expr.toString(), alias);
            } else if (item.getExpr() instanceof SQLPropertyExpr) {
                SQLPropertyExpr expr = (SQLPropertyExpr) item.getExpr();
                curColumn = new CustomColumn(getTableNameByAlias(expr.getOwnerName()), expr.getOwnerName(), expr.getName(), item.getAlias());

            }
            if (null != curColumn) {
                selectColumns.add(curColumn);
                columnNames.add(!StringUtils.isNullOrEmpty(curColumn.getColumnAlias()) ? curColumn.getColumnAlias() : curColumn.getColumn());
            }
        });
    }

    /**
     * 根据查询表别名获取查询表名
     *
     * @param alias 查询表别名
     * @return 查询表名
     */
    protected String getTableNameByAlias(String alias) {
        return getTableByAlias(alias).map(CustomTable::getTableName).orElse(null);
    }

    /**
     * 根据查询表别名获取查询表
     *
     * @param alias 查询表别名
     * @return 查询表
     */
    protected Optional<CustomTable> getTableByAlias(String alias) {
        return selectTables.stream().filter(table -> alias.equals(table.getTableAlias())).findFirst();
    }

    /**
     * 解析Where中的参数
     *
     * @param sqlBinaryOpExpr
     */
    protected void parserWhereParam(SQLBinaryOpExpr sqlBinaryOpExpr) {
        SQLExpr right = sqlBinaryOpExpr.getRight();
        SQLExpr left = sqlBinaryOpExpr.getLeft();
        if (left instanceof SQLIdentifierExpr || left instanceof SQLVariantRefExpr|| left instanceof SQLPropertyExpr) {
            Matcher matcher = PARAMETER_PATTERN.matcher(sqlBinaryOpExpr.toString());
            if (matcher.find()) {
                String expr = matcher.group();
                opera.put(expr, sqlBinaryOpExpr.getOperator().getName());
            }
        } else if(left instanceof SQLBinaryOpExpr){
            Matcher matcherRight = PARAMETER_PATTERN.matcher(right.toString());
            if (matcherRight.find()) {
                String expr = matcherRight.group();
                opera.put(expr, ((SQLBinaryOpExpr) right).getOperator().getName());
            }

            SQLExpr leftSubRight = ((SQLBinaryOpExpr) left).getRight();
            // 解析右边
            System.out.println("leftSubRight.toString():" + leftSubRight.toString());
            Matcher matcherLeftSubRight = PARAMETER_PATTERN.matcher(leftSubRight.toString());
            if (matcherLeftSubRight.find()) {
                String expr = matcherLeftSubRight.group();
                opera.put(expr, ((SQLBinaryOpExpr) leftSubRight).getOperator().getName());

            }
            SQLExpr leftSubLeft = ((SQLBinaryOpExpr) left).getLeft();
            if (leftSubLeft instanceof SQLBinaryOpExpr) {
                parserWhereParam((SQLBinaryOpExpr) leftSubLeft);
            }
            Matcher matcherLeftSubLeft = PARAMETER_PATTERN.matcher(leftSubLeft.toString());
            if (matcherLeftSubLeft.find()) {
                String expr = matcherLeftSubLeft.group();
                opera.put(expr, ((SQLBinaryOpExpr) leftSubLeft).getOperator().getName());
            }
        }
    }
}

使用

SQLStatement statement = SQLUtils.parseSingleStatement(sql, DbType.mysql);
CustomSQLASTVisitor visitor = new CustomSQLASTVisitor();
 statement.accept(visitor);

相关推荐

  1. 应用Druid解析SQL获取查询表字参数信息

    2024-03-30 00:42:01       37 阅读
  2. SQL SERVER 查询获取分组中时间最新的信息

    2024-03-30 00:42:01       54 阅读
  3. 【达梦数据库】查看pesg回滚信息的视图和SQL

    2024-03-30 00:42:01       53 阅读
  4. 写一防止sql注入的sql查询

    2024-03-30 00:42:01       51 阅读
  5. sql查看指定时间的数据

    2024-03-30 00:42:01       49 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-30 00:42:01       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-30 00:42:01       100 阅读
  3. 在Django里面运行非项目文件

    2024-03-30 00:42:01       82 阅读
  4. Python语言-面向对象

    2024-03-30 00:42:01       91 阅读

热门阅读

  1. 5.89 BCC工具之tcptop.py解读

    2024-03-30 00:42:01       34 阅读
  2. PTA 道路管制

    2024-03-30 00:42:01       38 阅读
  3. VUE3从i18n国际化组件动态获取字符串

    2024-03-30 00:42:01       38 阅读
  4. 星图金融价值跃迁:打造“一体两翼”正向循环

    2024-03-30 00:42:01       43 阅读
  5. 5、Cocos Creator 动作系统

    2024-03-30 00:42:01       40 阅读
  6. Composer常见错误以及常用解决办法指南

    2024-03-30 00:42:01       39 阅读
  7. Vue模板引用(ref),超详细

    2024-03-30 00:42:01       44 阅读
  8. Asp.net Core 中一键注入接口

    2024-03-30 00:42:01       41 阅读
  9. .NET Core教程:入门与实践实例

    2024-03-30 00:42:01       34 阅读