之前的项目中不多去思考SQL解析这个事情,即便在saas系统或者分库分表的时候有涉及到也会有专门的处理方案,这些方案也对使用者隐藏了实现细节。html
而最近的这个数据项目里面却频繁涉及到了对SQL的处理,原来只是简单地了解Druid的SqlParser模块就能够解决,慢慢地问题变得愈来愈复杂,直到某天改动本身写的SQL处理的代码很痛苦的时候,意识到彷佛有必要更加地了解一下相关的内容才行。git
在了解学习的过程当中,发现学习使用SqlParser仍是得先了解ast(抽象语法树)这个概念,一搜索相关内容要么是编译原理相关的知识,要么是JavaScript的示例,光看Druid提供的SqlParser相关的Wiki文档又似懂非懂,不知道从哪里下手。github
无论怎么样,看了很多碎片化的相关内容之后也收获了一些东西,这里记录下来。正则表达式
ast全称是abstract syntax tree,中文直译抽象语法树。sql
原先我以为要使用SqlParser就照着wiki上的代码步骤拷过来就行了呗,也确实如此,它快速解决了个人问题。但是正如上面所说,但愿你的相关代码写得更好一点,或者更理解它是在干嘛了解了ast会有很多的帮助。编程
SQL解析,本质上就是把SQL字符串给解析成ast,也就是说SqlParser的入参是SQL字符串,结果就是一个ast。你怎么使用这个ast结果又是另一回事,你能够修改ast,也能够添加点东西等等,但整个过程都是围绕着ast这个东西。数据结构
上面提了好几回ast,那ast又是个什么东西呢?maven
参照维基百科的说法,在计算机科学领域内,ast表示的是你写的编程语言源代码的抽象语法结构。如图:编程语言
左边是一个很是简单的编程语言源代码:1 + 2,作了一个加法计算,而当它被解析成ast之后如右边的图所示。咱们能够看到ast存在三个节点,顶部的 + 表示一个加法节点,这个表达式组合了一、2两个数值节点,由这三个组合在一块儿的节点就组成了1+2这样的语法结构。ide
咱们看到ast很清晰地用数据结构表示出了字符串源代码,ast的每个节点均表示源代码当中的一个语法结构。反过来思考一下,咱们能够知道源代码解析出来的ast是由不少这样简单的语法结构组合而成的,也就造成了一个复杂的语法树。下面咱们看一个稍微复杂一点的,来自维基百科的示例
源代码:
1 while b ≠ 0 2 if a > b 3 a = a − b 4 else 5 b = b − a 6 return a
语法树:
这个语法树也清晰地表示的源代码程序,主要由一个while语法和if/else语法以及一些变量之类的组成。
到这里,彷佛对源代码和ast有了一个简单的概念,可是仍是存在困惑,我为何要把好好的代码搞成这样?它有什么用?若是只是修改语法,我用正则表达式修改字符串不是简单吗?
确实,有的时候直接处理字符串会是更快速更好的解决方式,可是当源程序语法很是复杂的时候字符串处理的复杂度已经不是一个简单的事了。而ast则把这些字符串变成结构化的数据了,你能够精确地知道一段代码里面有哪些变量名,函数名,参数等,你能够很是精准地处理,相对于字符串处理来讲,遍历数据大大下降的处理难度。而ast也经常用在如IDE中错误提示、自动补全、编译器、语法翻译、重构、代码混淆压缩转换等。
了解ast能够参考文章:
https://mp.weixin.qq.com/s/UYzwVRPFas6hwe2U7R0eIg
http://www.javashuo.com/article/p-gqhtijbs-gc.html
http://www.javashuo.com/article/p-ehcxsygf-cx.html
https://www.jianshu.com/p/6a2f4ae4e099
https://en.wikipedia.org/wiki/Abstract_syntax_tree
https://en.wikipedia.org/wiki/Parse_tree#Constituency-based_parse_trees
咱们知道了ast是一种结构化的源代码表示,那针对SQL来讲ast就是把SQL语句用结构化的数据来表示了。而SqlParser也就是把SQL解析成ast,这个解析过程则被SqlParser作了隐藏,咱们不须要去实现这样一个字符串解析过程。
因而可知,咱们须要了解两方面内容:
一、怎么用SqlParser把SQL语句解析成ast;
二、SqlParser解析出来的ast是什么样的一个结构。
下面须要一点代码来讲明,因此先引入一下maven依赖
1 <dependency> 2 <groupId>com.alibaba</groupId> 3 <artifactId>druid</artifactId> 4 <version>1.1.12</version> 5 </dependency>
解析语句相对简单,wiki上直接有示例,如:
String dbType = JdbcConstants.MYSQL;
List<SQLStatement> statementList = SQLUtils.parseStatements(sql, dbType);
SQLUtils的parseStatements方法会把你传入的SQL语句给解析成SQLStatement对象集合,每个SQLStatement表明一条完整的SQL语句,如:
SELECT id FROM user WHERE status = 1
多个SQLStatement如:
SELECT id FROM user WHERE status = 1; SELECT id FROM order WHERE create_time > '2018-01-01'
通常上咱们只处理一条语句。
SQLStatement表示一条SQL语句,咱们知道常见的SQL语句有CRUD四种操做,因此SQLStatement会有四种主要实现类,如:
class SQLSelectStatement implements SQLStatement { SQLSelect select; } class SQLUpdateStatement implements SQLStatement { SQLExprTableSource tableSource; List<SQLUpdateSetItem> items; SQLExpr where; } class SQLDeleteStatement implements SQLStatement { SQLTableSource tableSource; SQLExpr where; } class SQLInsertStatement implements SQLStatement { SQLExprTableSource tableSource; List<SQLExpr> columns; SQLSelect query; }
这里咱们以SQLSelectStatement来讲明,ast既然是SQL的语法结构表示,咱们先看一下ast和SQL select语法的主要对应结构
SQLSelectStatement包含一个SQLSelect,SQLSelect包含一个SQLSelectQuery,都是组成的关系。SQLSelectQuery有主要的两个派生类,分别是SQLSelectQueryBlock和SQLUnionQuery。
class SQLSelect extends SQLObjectImpl { SQLWithSubqueryClause withSubQuery; SQLSelectQuery query; } interface SQLSelectQuery extends SQLObject {} class SQLSelectQueryBlock implements SQLSelectQuery { List<SQLSelectItem> selectList; SQLTableSource from; SQLExpr where; SQLSelectGroupByClause groupBy; SQLOrderBy orderBy; SQLLimit limit; } class SQLUnionQuery implements SQLSelectQuery { SQLSelectQuery left; SQLSelectQuery right; SQLUnionOperator operator; // UNION/UNION_ALL/MINUS/INTERSECT }
如下是SQLSelectQueryBlock中包含的主要节点
sql | ast |
字段 | SQLSelectItems |
表 | SQLTableSource |
where条件 | SQLExpr |
groupby | SQLSelectGroupByClause |
orderby | SQLOrderBy |
limit | SQLLimit |
表格中的这些ast节点都是SQL对应语法的一些表示,相信你们都很是熟悉,根据名字也轻易能了解具体是语法。
这里须要细化一下SQLTableSource这个节点,它有着常见的实现SQLExprTableSource(from的表)、SQLJoinTableSource(join的表)、SQLSubqueryTableSource(子查询的表)如:
class SQLTableSourceImpl extends SQLObjectImpl implements SQLTableSource { String alias; } // 例如 select * from emp where i = 3,这里的from emp是一个SQLExprTableSource // 其中expr是一个name=emp的SQLIdentifierExpr class SQLExprTableSource extends SQLTableSourceImpl { SQLExpr expr; } // 例如 select * from emp e inner join org o on e.org_id = o.id // 其中left 'emp e' 是一个SQLExprTableSource,right 'org o'也是一个SQLExprTableSource // condition 'e.org_id = o.id'是一个SQLBinaryOpExpr class SQLJoinTableSource extends SQLTableSourceImpl { SQLTableSource left; SQLTableSource right; JoinType joinType; // INNER_JOIN/CROSS_JOIN/LEFT_OUTER_JOIN/RIGHT_OUTER_JOIN/... SQLExpr condition; } // 例如 select * from (select * from temp) a,这里第一层from(...)是一个SQLSubqueryTableSource SQLSubqueryTableSource extends SQLTableSourceImpl { SQLSelect select; }
另外SQLExpr出现的地方也比较多,好比where语句,join条件,SQLSelectItem中等,所以,也须要细化了解一下,如
// SQLName是一种的SQLExpr的Expr,包括SQLIdentifierExpr、SQLPropertyExpr等 public interface SQLName extends SQLExpr {} // 例如 ID = 3 这里的ID是一个SQLIdentifierExpr class SQLIdentifierExpr implements SQLExpr, SQLName { String name; } // 例如 A.ID = 3 这里的A.ID是一个SQLPropertyExpr class SQLPropertyExpr implements SQLExpr, SQLName { SQLExpr owner; String name; } // 例如 ID = 3 这是一个SQLBinaryOpExpr // left是ID (SQLIdentifierExpr) // right是3 (SQLIntegerExpr) class SQLBinaryOpExpr implements SQLExpr { SQLExpr left; SQLExpr right; SQLBinaryOperator operator; } // 例如 select * from where id = ?,这里的?是一个SQLVariantRefExpr,name是'?' class SQLVariantRefExpr extends SQLExprImpl { String name; } // 例如 ID = 3 这里的3是一个SQLIntegerExpr public class SQLIntegerExpr extends SQLNumericLiteralExpr implements SQLValuableExpr { Number number; // 全部实现了SQLValuableExpr接口的SQLExpr均可以直接调用这个方法求值 @Override public Object getValue() { return this.number; } } // 例如 NAME = 'jobs' 这里的'jobs'是一个SQLCharExpr public class SQLCharExpr extends SQLTextLiteralExpr implements SQLValuableExpr{ String text; }
SqlParser定义了完整的ast各个节点对象,一条SQL语句被解析成这些对象的树形结构,而咱们要作的就是根据这样的一个树形结构去作相应的处理。以上代码片断摘取了部分wiki上的,并调整了一下顺序,完整的wiki能够到druid的github上查阅。
public void enhanceSql(String sql) { // 解析 List<SQLStatement> statements = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL); // 只考虑一条语句 SQLStatement statement = statements.get(0); // 只考虑查询语句 SQLSelectStatement sqlSelectStatement = (SQLSelectStatement) statement; SQLSelectQuery sqlSelectQuery = sqlSelectStatement.getSelect().getQuery(); // 非union的查询语句 if (sqlSelectQuery instanceof SQLSelectQueryBlock) { SQLSelectQueryBlock sqlSelectQueryBlock = (SQLSelectQueryBlock) sqlSelectQuery; // 获取字段列表 List<SQLSelectItem> selectItems = sqlSelectQueryBlock.getSelectList(); selectItems.forEach(x -> { // 处理--------------------- }); // 获取表 SQLTableSource table = sqlSelectQueryBlock.getFrom(); // 普通单表 if (table instanceof SQLExprTableSource) { // 处理--------------------- // join多表 } else if (table instanceof SQLJoinTableSource) { // 处理--------------------- // 子查询做为表 } else if (table instanceof SQLSubqueryTableSource) { // 处理--------------------- } // 获取where条件 SQLExpr where = sqlSelectQueryBlock.getWhere(); // 若是是二元表达式 if (where instanceof SQLBinaryOpExpr) { SQLBinaryOpExpr sqlBinaryOpExpr = (SQLBinaryOpExpr) where; SQLExpr left = sqlBinaryOpExpr.getLeft(); SQLBinaryOperator operator = sqlBinaryOpExpr.getOperator(); SQLExpr right = sqlBinaryOpExpr.getRight(); // 处理--------------------- // 若是是子查询 } else if (where instanceof SQLInSubQueryExpr) { SQLInSubQueryExpr sqlInSubQueryExpr = (SQLInSubQueryExpr) where; // 处理--------------------- } // 获取分组 SQLSelectGroupByClause groupBy = sqlSelectQueryBlock.getGroupBy(); // 处理--------------------- // 获取排序 SQLOrderBy orderBy = sqlSelectQueryBlock.getOrderBy(); // 处理--------------------- // 获取分页 SQLLimit limit = sqlSelectQueryBlock.getLimit(); // 处理--------------------- // union的查询语句 } else if (sqlSelectQuery instanceof SQLUnionQuery) { // 处理--------------------- } }
以上示例中只是简单判断了一下类型,实际项目中你可能须要对整个ast作递归之类的方式来处理节点。其实当SQL语句变成了ast结构之后,咱们只要知道这个ast结构存在什么样的节点,获取节点判断类型并作相应的操做便可,至于你是递归仍是访问者模式仍是别的什么方式去处理ast均可以。
SqlParser官方wiki
抽象语法树: https://github.com/alibaba/druid/wiki/Druid_SQL_AST#24-sqlselect--sqlselectquery
解析器:https://github.com/alibaba/druid/wiki/SQL-Parser
visitor访问者示例:https://github.com/alibaba/druid/wiki/SQL_Parser_Demo_visitor