Java工具篇之Druid SQL语法解析树

本文涉及的产品
RDS AI 助手,专业版
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
简介: 本篇主要学习Druid 对Sql的语法解析。学习完之后,我们可以对任意sql进行解析,同时也可以基于AST语法树来生成sql语句。

本篇主要学习Druid 对Sql的语法解析。学习完之后,我们可以对任意sql进行解析,同时也可以基于AST语法树来生成sql语句。

一、AST

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

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


interface SQLObject {}
interface SQLExpr extends SQLObject {}
interface SQLStatement extends SQLObject {}

interface SQLTableSource extends SQLObject {}
class SQLSelect extends SQLObject {}
class SQLSelectQueryBlock extends SQLObject {}

二、语法树解析

2.1 核心类介绍

2.1.1 SQLStatemment DQL & DML顶级抽象

  • DQL 数据查询语言 select
  • DML 数据操纵语言 insert update delete

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

核心类 说明
SQLSelectStatement 查询语句
SQLUpdateStatement 更新语句
SQLDeleteStatement 删除语句
SQLInsertStatement 新增语句
@Test
public void statement() {
    // 以下全部 true
    System.out.println(SQLUtils.parseSingleMysqlStatement("select * from users") instanceof SQLSelectStatement);
    System.out.println(SQLUtils.parseSingleMysqlStatement("insert into users(id,name,age) values (1,'孙悟空',500)") instanceof SQLInsertStatement);
    System.out.println(SQLUtils.parseSingleMysqlStatement("update users set name = '唐僧' where id = 1 ") instanceof SQLUpdateStatement);
    System.out.println(SQLUtils.parseSingleMysqlStatement("delete from users where id = 1") instanceof SQLDeleteStatement);
}

2.1.2 SQLSelect SQL查询

SQLSelectStatement包含一个SQLSelect,SQLSelect包含一个SQLSelectQuery。SQLSelectQuery有主要的两个派生类,
分别是SQLSelectQueryBlock(单表sql查询)和SQLUnionQuery(union查询)。

    /**
     * SQLSelectStatement包含一个SQLSelect,SQLSelect包含一个SQLSelectQuery。SQLSelectQuery有主要的两个派生类,
     * 分别是SQLSelectQueryBlock(单表sql查询)和SQLUnionQuery(联合查询)。
     */
    @Test
    public void SQLSelectQuery() {
        // true
        System.out.println(parseSQLSelectQuery("select * from users") instanceof SQLSelectQueryBlock);
        // true
        System.out.println(parseSQLSelectQuery("select name from users union select name from school") instanceof SQLUnionQuery);
    }

    public SQLSelectQuery parseSQLSelectQuery(String sql) {
        SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement(sql);
        SQLSelectStatement sqlSelectStatement = Utils.cast(sqlStatement, SQLSelectStatement.class);
        SQLSelect select = sqlSelectStatement.getSelect();
        return select.getQuery();
    }

2.1.3 SQLExpr

SQLExpr 是有几个实现类的。

select id,name,age from users where id = 1 and name = '孙悟空';

select u.id, u.name from users as u where id = 1 and name = ?;
核心类 举例 说明 适用范围 快速记忆
SQLIdentifierExpr id,name,age SQLIdentifierExpr 查询字段或者where条件 唯一标记
SQLPropertyExpr u.id,u.name 区别于SQLIdentifierExpr,适用于有别名的场景; SQLPropertyExpr.name = id, SQLPropertyExpr.owner = SQLIdentifierExpr = u) 查询字段或者where条件 有别名就是它
SQLBinaryOpExpr id = 1, id > 5 SQLBinaryOpExpr(left = SQLIdentifierExpr = id ,right = SQLValuableExpr = 1) where条件 有操作符就是它
SQLVariantRefExpr id = ? 变量 where条件 有变量符就是它
SQLIntegerExpr id = 1 数字类型 值类型 -
SQLCharExpr name = '孙悟空' 字符类型 值类型 -

2.1.3.1 SQLBinaryOpExpr

    /**
     * 操作符相关: SQLBinaryOpExpr
     */
    @Test
    public void SQLBinaryOpExpr() {
        String sql = "select * from users where id > 1 and age = 18";
        SQLSelectQuery sqlSelectQuery = Utils.parseSQLSelectQuery(sql);
        SQLSelectQueryBlock selectQueryBlock = Utils.cast(sqlSelectQuery, SQLSelectQueryBlock.class);
        SQLExpr where = selectQueryBlock.getWhere();
        List<SQLObject> conditions = where.getChildren();
        // [id > 1 , age = 18] 出现了操作符所以是SQLBinaryOpExpr
        for (SQLObject condition : conditions) {
            SQLBinaryOpExpr conditionExpr = Utils.cast(condition, SQLBinaryOpExpr.class);
            SQLBinaryOperator operator = conditionExpr.getOperator();
            SQLIdentifierExpr conditionColumn = Utils.cast(conditionExpr.getLeft(), SQLIdentifierExpr.class);
            SQLValuableExpr conditionColumnValue = Utils.cast(conditionExpr.getRight(), SQLValuableExpr.class);
            Utils.print("条件字段:{},操作符号:{},条件值:{}", conditionColumn.getName(), operator.name, conditionColumnValue);
        }
    }

2.1.3.2 SQLVariantRefExpr

    @Test
    public void SQLVariantRefExpr() {
        String sql = "select * from users where id = ? and name = ?";
        SQLSelectQuery sqlSelectQuery = Utils.parseSQLSelectQuery(sql);
        SQLSelectQueryBlock selectQueryBlock = Utils.cast(sqlSelectQuery, SQLSelectQueryBlock.class);
        SQLExpr where = selectQueryBlock.getWhere();
        List<SQLObject> conditions = where.getChildren();
        // [id = ?] 出现了变量符,所以要用SQLVariantRefExpr
        for (SQLObject condition : conditions) {
            SQLBinaryOpExpr conditionExpr = Utils.cast(condition, SQLBinaryOpExpr.class);
            SQLBinaryOperator operator = conditionExpr.getOperator();
            SQLIdentifierExpr conditionColumn = Utils.cast(conditionExpr.getLeft(), SQLIdentifierExpr.class);
            SQLVariantRefExpr conditionColumnValue = Utils.cast(conditionExpr.getRight(), SQLVariantRefExpr.class);
            int index = conditionColumnValue.getIndex();
            Utils.print("条件字段:{},操作符号:{},索引位:{}", conditionColumn.getName(), operator.name, index);
        }
    }

2.1.4 SQLTableSource

常见的SQLTableSource包括SQLExprTableSource、SQLJoinTableSource、SQLSubqueryTableSource、SQLWithSubqueryClause.Entry

核心类 举例 说明 快速记忆
SQLExprTableSource select * from emp where i = 3 name = SQLIdentifierExpr = emp 单表查询
SQLJoinTableSource select * from emp e inner join org o on e.org_id = o.id left = SQLExprTableSource(emp e),right = SQLExprTableSource(org o), condition = SQLBinaryOpExpr(e.org_id = o.id) join 查询使用
SQLSubqueryTableSource select from (select from temp) a from(...)是一个SQLSubqueryTableSource 子查询语句
SQLWithSubqueryClause WITH RECURSIVE ancestors AS (SELECT FROM org UNION SELECT f. FROM org f, ancestors a WHERE f.id = a.parent_id ) SELECT * FROM ancestors; ancestors AS (...) 是一个SQLWithSubqueryClause.Entry with

2.2 SQL语句解析示例

2.2.1 解析 Where

注意如果条件语句中只有一个条件,那么where就是一个 SQLBinaryOpExpr
当条件大于2个,使用 where.getChildren()

    /**
     * 判断where要
     * 1. 注意是SQLBinaryOpExpr(id = 1) or (u.id = 1) 需要注意是否使用了别名<br>
     * 2. 注意如果只有一个查询添加 where本身就是一个SQLBinaryOpExpr,如果是多个就要用 where.getChildren()<br></>
     * 如果有别名: SQLPropertyExpr(name = id , ownerName = u)<br>
     * 如果没别名: SQLIdentifierExpr(name = id) <br></>
     * 值对象: SQLValuableExpr
     *
     * @param where 条件对象
     */
    public static void parseWhere(SQLExpr where) {
        if (where instanceof SQLBinaryOpExpr) {
            parseSQLBinaryOpExpr(cast(where, SQLBinaryOpExpr.class));
        } else {
            List<SQLObject> childrenList = where.getChildren();
            for (SQLObject sqlObject : childrenList) {
                // 包含了 left 和 right
                SQLBinaryOpExpr conditionBinary = cast(sqlObject, SQLBinaryOpExpr.class);
                parseSQLBinaryOpExpr(conditionBinary);
            }
        }

    }
    
     public static void parseSQLBinaryOpExpr(SQLBinaryOpExpr conditionBinary) {
        SQLExpr conditionExpr = conditionBinary.getLeft();
        SQLExpr conditionValueExpr = conditionBinary.getRight();
        // 左边有别名所以是SQLPropertyExpr
        if (conditionExpr instanceof SQLPropertyExpr) {
            SQLPropertyExpr conditionColumnExpr = cast(conditionExpr, SQLPropertyExpr.class);
            // 右边根据类型进行转换 id是SQLIntegerExpr name是SQLCharExpr
            SQLValuableExpr conditionColumnValue = cast(conditionValueExpr, SQLValuableExpr.class);
            print("条件列名:{},条件别名:{},条件值:{}", conditionColumnExpr.getName(), conditionColumnExpr.getOwnernName(), conditionColumnValue);
        }
        // 如果没有别名
        if (conditionExpr instanceof SQLIdentifierExpr) {
            SQLIdentifierExpr conditionColumnExpr = cast(conditionExpr, SQLIdentifierExpr.class);
            SQLValuableExpr conditionColumnValue = cast(conditionValueExpr, SQLValuableExpr.class);
            print("条件列名:{},条件值:{}", conditionColumnExpr.getName(), conditionColumnValue);
        }
    }

2.2.2 解析 SQLSelectItem

解析查询的列信息

    /**
     * 解析查询字段,注意是否使用了别名.u.id as userId, u.name as userName, u.age as userAge<br>
     * userId(sqlSelectItem.getAlias)<br>
     * 如果有别名: u.id( id = SQLPropertyExpr.getName,u = SQLPropertyExpr.getOwnernName)<br>
     * 如果没别名: id(id = SQLIdentifierExpr.name)
     *
     * @param selectColumnList 查询字段
     */
    private void parseSQLSelectItem(List<SQLSelectItem> selectColumnList) {
        for (SQLSelectItem sqlSelectItem : selectColumnList) {
            // u.id as userId(selectColumnAlias)
            String selectColumnAlias = sqlSelectItem.getAlias();
            // u.id = SQLPropertyExpr
            SQLExpr expr = sqlSelectItem.getExpr();
            if (expr instanceof SQLPropertyExpr) {
                SQLPropertyExpr selectColumnExpr = cast(expr, SQLPropertyExpr.class);
                print("列名:{},别名:{},表别名:{}", selectColumnExpr.getName(), selectColumnAlias, selectColumnExpr.getOwnernName());
            }
            if (expr instanceof SQLIdentifierExpr) {
                SQLIdentifierExpr selectColumnExpr = cast(expr, SQLIdentifierExpr.class);
                print("列名:{},别名:{}", selectColumnExpr.getName(), selectColumnAlias);
            }
        }
    }

2.2.3 解析 SQLUpdateSetItem

    @Test
    public void SQLUpdateStatement() {
        SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("update users u set u.name = '唐僧',age = 18 where u.id = 1 ");
        SQLUpdateStatement sqlUpdateStatement = Utils.cast(sqlStatement, SQLUpdateStatement.class);
        List<SQLUpdateSetItem> setItems = sqlUpdateStatement.getItems();
        for (SQLUpdateSetItem setItem : setItems) {
            SQLExpr column = setItem.getColumn();
            if (column instanceof SQLPropertyExpr) {
                SQLPropertyExpr sqlPropertyExpr = Utils.cast(column, SQLPropertyExpr.class);
                SQLExpr value = setItem.getValue();
                Utils.print("column:{},列owner:{},value:{}", sqlPropertyExpr.getName(), sqlPropertyExpr.getOwnernName(), value);
            }
            if (column instanceof SQLIdentifierExpr) {
                SQLExpr value = setItem.getValue();
                Utils.print("column:{},value:{}", column, value);
            }
        }
        SQLExpr where = sqlUpdateStatement.getWhere();
        Utils.startParse("解析where", Utils::parseWhere, where);
    }

2.2.4 解析 SQLLimit

    /**
     * 偏移量,只有2个值
     *
     * @param limit 限制
     */
    private void parseLimit(SQLLimit limit) {
        // 偏移量
        SQLExpr offset = limit.getOffset();
        // 便宜数量
        SQLExpr rowCount = limit.getRowCount();
        print("偏移量:{},偏移数量:{}", offset, rowCount);
    }

2.2.5 解析 SQLSelectGroupBy

    @Test
    public void groupBy() {
        SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("select name,count(1) as count from users group by name,age having count > 2");
        SQLSelectStatement selectStatement = Utils.cast(sqlStatement, SQLSelectStatement.class);
        SQLSelect select = selectStatement.getSelect();
        SQLSelectQueryBlock query = Utils.cast(select.getQuery(), SQLSelectQueryBlock.class);
        SQLSelectGroupByClause groupBy = query.getGroupBy();
        List<SQLExpr> items = groupBy.getItems();
        for (SQLExpr item : items) {
            // group by name
            // group by age
            SQLIdentifierExpr groupByColumn = Utils.cast(item, SQLIdentifierExpr.class);
            Utils.print("group by {}", groupByColumn);
        }
    }

2.2.6 解析 Having

    @Test
    public void having() {
        SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("select name,count(1) as count from users group by name,age having count > 2");
        SQLSelectStatement selectStatement = Utils.cast(sqlStatement, SQLSelectStatement.class);
        SQLSelect select = selectStatement.getSelect();
        SQLSelectQueryBlock query = Utils.cast(select.getQuery(), SQLSelectQueryBlock.class);
        SQLSelectGroupByClause groupBy = query.getGroupBy();
        SQLExpr having = groupBy.getHaving();
        // 因为只有一个条件,所以having就是SQLBinaryOpExpr
        SQLBinaryOpExpr havingExpr = Utils.cast(having, SQLBinaryOpExpr.class);
        // 没有使用别名,所以就是SQLIdentifierExpr
        SQLExpr left = havingExpr.getLeft();
        SQLIdentifierExpr leftExpr = Utils.cast(left, SQLIdentifierExpr.class);
        // 数字类型就是
        SQLExpr right = havingExpr.getRight();
        SQLValuableExpr rightValue = Utils.cast(right, SQLValuableExpr.class);
        SQLBinaryOperator operator = havingExpr.getOperator();
        // left:count, operator:>,right:2
        Utils.print("left:{}, operator:{},right:{}", leftExpr.getName(), operator.name, rightValue.getValue());
    }

三、语法树生成

前面的内容如果都搞清楚了,那么我们就能对sql进行解析,通知可以修改sql解析后的语法树,同时再将修改后的语法树,重新转换成sql

3.1 修改语法树

3.1.1 增加一个条件

    @Test
    public void SQLDeleteStatement(){
        SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("delete from users where id = 1");
        SQLDeleteStatement sqlDeleteStatement = Utils.cast(sqlStatement, SQLDeleteStatement.class);
        sqlDeleteStatement.addCondition(SQLUtils.toSQLExpr("name = '孙悟空'"));
//        DELETE FROM users
//        WHERE id = 1
//        AND name = '孙悟空'
        System.out.println(SQLUtils.toSQLString(sqlDeleteStatement));
    }

3.1.2 修改一个条件值

将条件id = 1 修改成 id = 2

    @Test
    public void SQLDeleteStatement2(){
        SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("delete from users where id = 1");
        SQLDeleteStatement sqlDeleteStatement = Utils.cast(sqlStatement, SQLDeleteStatement.class);
        SQLExpr where = sqlDeleteStatement.getWhere();
        SQLBinaryOpExpr sqlBinaryOpExpr = Utils.cast(where, SQLBinaryOpExpr.class);
//        DELETE FROM users
//        WHERE id = 2
        sqlBinaryOpExpr.setRight(SQLUtils.toSQLExpr("2"));
        System.out.println(SQLUtils.toSQLString(sqlDeleteStatement));
    }

四、Visitor模式

访问者模式

所有的AST节点都支持Visitor模式,需要自定义遍历逻辑,可以实现相应的ASTVisitorAdapter派生类

    public static class CustomerMySqlASTVisitorAdapter extends MySqlASTVisitorAdapter {

        private final Map<String, SQLTableSource> ALIAS_MAP = new HashMap<String, SQLTableSource>();

        private final Map<String, SQLExpr> ALIAS_COLUMN_MAP = new HashMap<String, SQLExpr>();


        public boolean visit(SQLExprTableSource x) {
            String alias = x.getAlias();
            ALIAS_MAP.put(alias, x);
            return true;
        }

        @Override
        public boolean visit(MySqlSelectQueryBlock x) {
            List<SQLSelectItem> selectList = x.getSelectList();
            for (SQLSelectItem sqlSelectItem : selectList) {
                String alias = sqlSelectItem.getAlias();
                SQLExpr expr = sqlSelectItem.getExpr();
                ALIAS_COLUMN_MAP.put(alias, expr);
            }
            return true;
        }

        public Map<String, SQLTableSource> getAliasMap() {
            return ALIAS_MAP;
        }

        public Map<String, SQLExpr> getAliasColumnMap() {
            return ALIAS_COLUMN_MAP;
        }
    }

    @Test
    public void AliasVisitor() {
        String sql = "select u.id as userId, u.name as userName, age as userAge from users as u where u.id = 1 and u.name = '孙悟空' limit 2,10";
        // 解析SQL
        SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement(sql);
        CustomerMySqlASTVisitorAdapter customerMySqlASTVisitorAdapter = new CustomerMySqlASTVisitorAdapter();
        sqlStatement.accept(customerMySqlASTVisitorAdapter);
        // 表别名:{u=users}
        System.out.println("表别名:" + customerMySqlASTVisitorAdapter.getAliasMap());
        // 列别名{userName=u.name, userId=u.id, userAge=age}
        System.out.println("列别名" + customerMySqlASTVisitorAdapter.getAliasColumnMap());
    }
相关文章
|
5月前
|
SQL 数据可视化 关系型数据库
MCP与PolarDB集成技术分析:降低SQL门槛与简化数据可视化流程的机制解析
阿里云PolarDB与MCP协议融合,打造“自然语言即分析”的新范式。通过云原生数据库与标准化AI接口协同,实现零代码、分钟级从数据到可视化洞察,打破技术壁垒,提升分析效率99%,推动企业数据能力普惠化。
473 3
|
10月前
|
SQL 安全 关系型数据库
SQL注入之万能密码:原理、实践与防御全解析
本文深入解析了“万能密码”攻击的运行机制及其危险性,通过实例展示了SQL注入的基本原理与变种形式。文章还提供了企业级防御方案,包括参数化查询、输入验证、权限控制及WAF规则配置等深度防御策略。同时,探讨了二阶注入和布尔盲注等新型攻击方式,并给出开发者自查清单。最后强调安全防护需持续改进,无绝对安全,建议使用成熟ORM框架并定期审计。技术内容仅供学习参考,严禁非法用途。
1623 0
|
9月前
|
SQL 存储 自然语言处理
SQL的解析和优化的原理:一条sql 执行过程是什么?
SQL的解析和优化的原理:一条sql 执行过程是什么?
SQL的解析和优化的原理:一条sql 执行过程是什么?
|
9月前
|
SQL Java 数据库连接
Java中实现SQL分页的方法
无论何种情况,选择适合自己的,理解了背后的工作原理,并能根据实际需求灵活变通的方式才是最重要的。
253 9
|
11月前
|
机器学习/深度学习 人工智能 JSON
Resume Matcher:增加面试机会!开源AI简历优化工具,一键解析简历和职位描述并优化
Resume Matcher 是一款开源AI简历优化工具,通过解析简历和职位描述,提取关键词并计算文本相似性,帮助求职者优化简历内容,提升通过自动化筛选系统(ATS)的概率,增加面试机会。
1426 18
Resume Matcher:增加面试机会!开源AI简历优化工具,一键解析简历和职位描述并优化
|
10月前
|
存储 人工智能 API
离线VS强制登录?Apipost与Apifox的API工具理念差异深度解析
在代码开发中,工具是助手还是枷锁?本文通过对比Apipost和Apifox在断网环境下的表现,探讨API工具的选择对开发自由度的影响。Apifox强制登录限制了离线使用,而Apipost支持游客模式与本地存储,尊重开发者数据主权。文章从登录策略、离线能力、协作模式等方面深入分析,揭示工具背后的设计理念与行业趋势,帮助开发者明智选择,掌握数据控制权并提升工作效率。
|
12月前
|
数据采集 存储 调度
BeautifulSoup VS Scrapy:如何选择适合的HTML解析工具?
在Python网页抓取领域,BeautifulSoup和Scrapy是两款备受推崇的工具。BeautifulSoup易于上手、灵活性高,适合初学者和简单任务;Scrapy则是一个高效的爬虫框架,内置请求调度、数据存储等功能,适合大规模数据抓取和复杂逻辑处理。两者结合使用可以发挥各自优势,例如用Scrapy进行请求调度,用BeautifulSoup解析HTML。示例代码展示了如何在Scrapy中设置代理IP、User-Agent和Cookies,并使用BeautifulSoup解析响应内容。选择工具应根据项目需求,简单任务选BeautifulSoup,复杂任务选Scrapy。
321 1
BeautifulSoup VS Scrapy:如何选择适合的HTML解析工具?
|
11月前
|
数据可视化 测试技术 API
前后端分离开发:如何高效调试API?有工具 vs 无工具全解析
在前后端分离开发中,API调试至关重要。本文探讨有无调试工具时如何高效调试API,重点分析Postman、Swagger等工具优势及无工具代码调试方法。通过实际场景如用户登录接口,对比两者特性。同时介绍Apipost-Hepler(IDEA插件),将可视化与代码调试结合,提供全局请求头配置、历史记录保存等功能,优化团队协作与开发效率,助力API调试进入全新阶段。
|
11月前
|
JSON 监控 物联网
WebSocket 调试全攻略:核心解析、工具选择与对比!
WebSocket 是一种全双工、实时交互的网络通信协议,适用于即时通信、实时数据流、多人协作、IoT 等场景。调试 WebSocket 时,工具应具备握手管理、实时消息收发、自定义 Header、消息大小告警、分组管理、多连接支持和断线重现等功能。主流调试工具如 Postman、ApiPost 和 ApiFox 各有优劣:Postman 界面友好适合基础调试;ApiPost 支持高级功能如消息分组和自动重连;ApiFox 则强化了多连接支持。选择工具时需根据具体需求和团队熟悉度决定。
|
11月前
|
数据可视化 测试技术 API
前后端分离开发:如何高效调试API?有工具 vs 无工具全解析
在前后端分离的开发模式中,API 调试的效率直接影响项目的质量和交付速度。通过本文的对比分析,我们可以看到无工具调试模式虽具备灵活性和代码复用能力,但在操作便利性和团队协作上稍显不足。而传统的外部调试工具带来了可视化、高效协作与扩展性,却可能存在工具切换带来的开发链路断层问题。Apipost-Hepler 融合了两者的优势,让开发者无需离开熟悉的 IDEA 环境,就能享受可视化调试工具的强大功能。
394 5

推荐镜像

更多
  • DNS