项目概况

Druid SQL解析

2026-01-297 min read后端技术
SQL解析

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

一、AST

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

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

java
1 2interface SQLObject {} 3interface SQLExpr extends SQLObject {} 4interface SQLStatement extends SQLObject {} 5 6interface SQLTableSource extends SQLObject {} 7class SQLSelect extends SQLObject {} 8class 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新增语句
java
1@Test 2public void statement() { 3 // 以下全部 true 4 System.out.println(SQLUtils.parseSingleMysqlStatement("select * from users") instanceof SQLSelectStatement); 5System.out.println(SQLUtils.parseSingleMysqlStatement("insert into users(id,name,age) values (1,'孙悟空',500)") instanceof SQLInsertStatement); 6System.out.println(SQLUtils.parseSingleMysqlStatement("update users set name = '唐僧' where id = 1 ") instanceof SQLUpdateStatement); 7System.out.println(SQLUtils.parseSingleMysqlStatement("delete from users where id = 1") instanceof SQLDeleteStatement); 8}

2.1.2 SQLSelect SQL查询

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

java
1 /** 2 * SQLSelectStatement包含一个SQLSelect,SQLSelect包含一个SQLSelectQuery。SQLSelectQuery有主要的两个派生类, 3 * 分别是SQLSelectQueryBlock(单表sql查询)和SQLUnionQuery(联合查询)。 4 */ 5 @Test 6 public void SQLSelectQuery() { 7 // true 8 System.out.println(parseSQLSelectQuery("select * from users") instanceof SQLSelectQueryBlock); 9// true 10 System.out.println(parseSQLSelectQuery("select name from users union select name from school") instanceof SQLUnionQuery); 11} 12 13 public SQLSelectQuery parseSQLSelectQuery(String sql) { 14 SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement(sql); 15SQLSelectStatement sqlSelectStatement = Utils.cast(sqlStatement, SQLSelectStatement.class); 16SQLSelect select = sqlSelectStatement.getSelect(); 17return select.getQuery(); 18}

2.1.3 SQLExpr

SQLExpr 是有几个实现类的。

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

2.1.3.1 SQLBinaryOpExpr

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

2.1.3.2 SQLVariantRefExpr

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

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()

java
1 /** 2 * 判断where要 3 * 1. 注意是SQLBinaryOpExpr(id = 1) or (u.id = 1) 需要注意是否使用了别名<br> 4 * 2. 注意如果只有一个查询添加 where本身就是一个SQLBinaryOpExpr,如果是多个就要用 where.getChildren()<br></> 5 * 如果有别名: SQLPropertyExpr(name = id , ownerName = u)<br> 6 * 如果没别名: SQLIdentifierExpr(name = id) <br></> 7 * 值对象: SQLValuableExpr 8 * 9 * @param where 条件对象 10 */ 11 public static void parseWhere(SQLExpr where) { 12 if (where instanceof SQLBinaryOpExpr) { 13 parseSQLBinaryOpExpr(cast(where, SQLBinaryOpExpr.class)); 14} else { 15 List<SQLObject> childrenList = where.getChildren(); 16for (SQLObject sqlObject : childrenList) { 17 // 包含了 left 和 right 18 SQLBinaryOpExpr conditionBinary = cast(sqlObject, SQLBinaryOpExpr.class); 19parseSQLBinaryOpExpr(conditionBinary); 20} 21 } 22 23 } 24 25 public static void parseSQLBinaryOpExpr(SQLBinaryOpExpr conditionBinary) { 26 SQLExpr conditionExpr = conditionBinary.getLeft(); 27SQLExpr conditionValueExpr = conditionBinary.getRight(); 28// 左边有别名所以是SQLPropertyExpr 29 if (conditionExpr instanceof SQLPropertyExpr) { 30 SQLPropertyExpr conditionColumnExpr = cast(conditionExpr, SQLPropertyExpr.class); 31// 右边根据类型进行转换 id是SQLIntegerExpr name是SQLCharExpr 32 SQLValuableExpr conditionColumnValue = cast(conditionValueExpr, SQLValuableExpr.class); 33print("条件列名:{},条件别名:{},条件值:{}", conditionColumnExpr.getName(), conditionColumnExpr.getOwnernName(), conditionColumnValue); 34} 35 // 如果没有别名 36 if (conditionExpr instanceof SQLIdentifierExpr) { 37 SQLIdentifierExpr conditionColumnExpr = cast(conditionExpr, SQLIdentifierExpr.class); 38SQLValuableExpr conditionColumnValue = cast(conditionValueExpr, SQLValuableExpr.class); 39print("条件列名:{},条件值:{}", conditionColumnExpr.getName(), conditionColumnValue); 40} 41 }

2.2.2 解析 SQLSelectItem

解析查询的列信息

java
1 /** 2 * 解析查询字段,注意是否使用了别名.u.id as userId, u.name as userName, u.age as userAge<br> 3 * userId(sqlSelectItem.getAlias)<br> 4 * 如果有别名: u.id( id = SQLPropertyExpr.getName,u = SQLPropertyExpr.getOwnernName)<br> 5 * 如果没别名: id(id = SQLIdentifierExpr.name) 6 * 7 * @param selectColumnList 查询字段 8 */ 9 private void parseSQLSelectItem(List<SQLSelectItem> selectColumnList) { 10 for (SQLSelectItem sqlSelectItem : selectColumnList) { 11 // u.id as userId(selectColumnAlias) 12 String selectColumnAlias = sqlSelectItem.getAlias(); 13// u.id = SQLPropertyExpr 14 SQLExpr expr = sqlSelectItem.getExpr(); 15if (expr instanceof SQLPropertyExpr) { 16 SQLPropertyExpr selectColumnExpr = cast(expr, SQLPropertyExpr.class); 17print("列名:{},别名:{},表别名:{}", selectColumnExpr.getName(), selectColumnAlias, selectColumnExpr.getOwnernName()); 18} 19 if (expr instanceof SQLIdentifierExpr) { 20 SQLIdentifierExpr selectColumnExpr = cast(expr, SQLIdentifierExpr.class); 21print("列名:{},别名:{}", selectColumnExpr.getName(), selectColumnAlias); 22} 23 } 24 }

2.2.3 解析 SQLUpdateSetItem

java
1 @Test 2 public void SQLUpdateStatement() { 3 SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("update users u set u.name = '唐僧',age = 18 where u.id = 1 "); 4SQLUpdateStatement sqlUpdateStatement = Utils.cast(sqlStatement, SQLUpdateStatement.class); 5List<SQLUpdateSetItem> setItems = sqlUpdateStatement.getItems(); 6for (SQLUpdateSetItem setItem : setItems) { 7 SQLExpr column = setItem.getColumn(); 8if (column instanceof SQLPropertyExpr) { 9 SQLPropertyExpr sqlPropertyExpr = Utils.cast(column, SQLPropertyExpr.class); 10SQLExpr value = setItem.getValue(); 11Utils.print("column:{},列owner:{},value:{}", sqlPropertyExpr.getName(), sqlPropertyExpr.getOwnernName(), value); 12} 13 if (column instanceof SQLIdentifierExpr) { 14 SQLExpr value = setItem.getValue(); 15Utils.print("column:{},value:{}", column, value); 16} 17 } 18 SQLExpr where = sqlUpdateStatement.getWhere(); 19Utils.startParse("解析where", Utils::parseWhere, where); 20}

2.2.4 解析 SQLLimit

java
1 /** 2 * 偏移量,只有2个值 3 * 4 * @param limit 限制 5 */ 6 private void parseLimit(SQLLimit limit) { 7 // 偏移量 8 SQLExpr offset = limit.getOffset(); 9// 便宜数量 10 SQLExpr rowCount = limit.getRowCount(); 11print("偏移量:{},偏移数量:{}", offset, rowCount); 12}

2.2.5 解析 SQLSelectGroupBy

java
1 @Test 2 public void groupBy() { 3 SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement("select name,count(1) as count from users group by name,age having count > 2"); 4SQLSelectStatement selectStatement = Utils.cast(sqlStatement, SQLSelectStatement.class); 5SQLSelect select = selectStatement.getSelect(); 6SQLSelectQueryBlock query = Utils.cast(select.getQuery(), SQLSelectQueryBlock.class); 7SQLSelectGroupByClause groupBy = query.getGroupBy(); 8List<SQLExpr> items = groupBy.getItems(); 9for (SQLExpr item : items) { 10 // group by name 11 // group by age 12 SQLIdentifierExpr groupByColumn = Utils.cast(item, SQLIdentifierExpr.class); 13Utils.print("group by {}", groupByColumn); 14} 15 } 16

2.2.6 解析 Having

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

三、语法树生成

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

3.1 修改语法树

3.1.1 增加一个条件

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

3.1.2 修改一个条件值

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

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

四、Visitor模式

访问者模式

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

java
1 public static class CustomerMySqlASTVisitorAdapter extends MySqlASTVisitorAdapter { 2 3 private final Map<String, SQLTableSource> ALIAS_MAP = new HashMap<String, SQLTableSource>(); 4 5private final Map<String, SQLExpr> ALIAS_COLUMN_MAP = new HashMap<String, SQLExpr>(); 6 7 8public boolean visit(SQLExprTableSource x) { 9 String alias = x.getAlias(); 10ALIAS_MAP.put(alias, x); 11return true; 12} 13 14 @Override 15 public boolean visit(MySqlSelectQueryBlock x) { 16 List<SQLSelectItem> selectList = x.getSelectList(); 17for (SQLSelectItem sqlSelectItem : selectList) { 18 String alias = sqlSelectItem.getAlias(); 19SQLExpr expr = sqlSelectItem.getExpr(); 20ALIAS_COLUMN_MAP.put(alias, expr); 21} 22 return true; 23} 24 25 public Map<String, SQLTableSource> getAliasMap() { 26 return ALIAS_MAP; 27} 28 29 public Map<String, SQLExpr> getAliasColumnMap() { 30 return ALIAS_COLUMN_MAP; 31} 32 } 33 34 @Test 35 public void AliasVisitor() { 36 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"; 37// 解析SQL 38 SQLStatement sqlStatement = SQLUtils.parseSingleMysqlStatement(sql); 39CustomerMySqlASTVisitorAdapter customerMySqlASTVisitorAdapter = new CustomerMySqlASTVisitorAdapter(); 40sqlStatement.accept(customerMySqlASTVisitorAdapter); 41// 表别名:{u=users} 42 System.out.println("表别名:" + customerMySqlASTVisitorAdapter.getAliasMap()); 43// 列别名{userName=u.name, userId=u.id, userAge=age} 44 System.out.println("列别名" + customerMySqlASTVisitorAdapter.getAliasColumnMap()); 45}