当前位置: 首页 > 科技观察

入门了解和使用DruidSqlParser

时间:2023-03-20 15:52:47 科技观察

之前的项目很少考虑SQL解析。即使是涉及到saas系统或者分库分表,也会有专门的解决方案。这些解决方案也有利于用户。实施细节被隐藏。但是最近的数据项目经常涉及到SQL的处理。原来,简单了解一下Druid的SqlParser模块就可以解决问题。慢慢的,问题越来越复杂,直到有一天我把自己写的SQL处理改了。代码痛苦的时候,似乎有必要多了解一下相关内容。在理解和学习的过程中,我发现学习使用SqlParser还是需要理解ast(抽象语法树)的概念。搜索相关内容时,要么是编译原理相关知识,要么是JavaScript实例。看看Druid提供的SqlParser就知道了。Wiki文档好像是半懂不懂,不知道从何入手。不管怎样,看了很多零散的相关内容,也收获了一些东西,记录在这里。为什么需要先了解ast?ast的全称是abstractsyntaxtree,是中文抽象语法树的直译。本来想着如果要用SqlParser,照着wiki上的代码步骤照着照搬就行了,果然如此。它很快解决了我的问题。但是上面说了,希望你的相关代码能写的更好,或者你能更好的理解它是干什么的。理解ast会有很大帮助。SQL解析本质上是将SQL字符串解析成AST,也就是说SqlParser的输入参数是SQL字符串,结果是一个AST。如何使用AST结果是另一回事。你可以修改AST,或者添加一些东西等等,但是整个过程都是围绕着AST进行的。什么是AST?上面多次提到了AST,那么什么是AST呢?参考维基百科,在计算机科学领域,ast代表你编写的编程语言源代码的抽象语法结构。如图:左边是一段很简单的编程语言源码:1+2,进行加法运算,解析成ast后,如右图所示。我们可以看到ast中有3个节点,最上面的+表示加法节点。这个表达式结合了两个数值节点1和2。1+2的语法结构就是通过这三个节点结合形成的。.我们看到ast把字符串源码用数据结构清晰的表达出来了,ast的每个节点代表了源码中的一个语法结构。反过来想一下,我们可以知道,从源码中解析出来的ast,是由很多这样简单的语法结构组成的,形成了一棵复杂的语法树。让我们看一个来自维基百科的稍微复杂的例子。源码:whileb≠0ifa>ba=a?belseb=b?areturna语法树:这个语法树也很清楚的表示了源码程序,主要由while语法和if/else语法以及一些变量组成这样的组成。至此,我似乎对源码和ast有了一个简单的概念,但还是有迷茫。我为什么要写这么好的代码?这是为了什么?如果只是修改语法,我用正则表达式修改字符串不简单吗?确实,有时候直接处理字符串是一种更快更好的解决方案,但是当源程序的语法非常复杂时,字符串处理的复杂性就不再是一件简单的事情了。AST将这些字符串转换为结构化数据。可以准确的知道一段代码中有哪些变量名、函数名、参数等,并且可以非常准确的进行处理。与字符串处理相比,遍历数据处理的难度大大降低。而ast也常用于IDE中的错误提示、自动补全、编译器、语法翻译、重构、代码混淆、压缩转换等。SqlParser我们知道ast是一种结构化的源代码表示,那么对于SQL来说,ast就是用结构化数据来表示SQL语句。而SqlParser就是将SQL解析成ast,这个解析过程被SqlParser隐藏了,我们不需要实现这样的字符串解析过程。可见,我们需要了解两个方面:如何使用SqlParser将SQL语句解析成ast;SqlParser解析出来的ast是什么结构。下面需要一点代码来说明,所以先引入maven依赖。com.alibabadruid1.1.12解析成AST解析语句比较简单,有例子直接上wiki,如:StringdbType=JdbcConstants.MYSQL;ListstatementList=SQLUtils.parseStatements(sql,dbType);SQLUtils的parseStatements方法会将你传入的SQL语句解析成一个SQLStatement对象集合,每个SQLStatement代表一个完整的SQL语句,如:SELECTidFROMuserWHEREstatus=1多个SQLStatement,如:SELECTidFROMuserWHERE状态=1;SELECTidFROMorderWHEREcreate_time>'2018-01-01'一般我们只处理一条语句。astSQLStatement的结构表示一个SQL语句。我们知道普通的SQL语句有四个CRUD操作,所以SQLStatement会有四个主要的实现类,比如:列出项;SQLExprwhere;}classSQLDeleteStatementimplementsSQLStatement{SQLTableSourcetableSource;SQLExpr其中;}类SQLInsertStatement实现SQLStatement{SQLExprTableSourcetableSource;列出列;SQL的语法结构表示我们先看ast和SQLselect语法的主要对应结构。SQLSelectStatement包含一个SQLSelect,SQLSelect包含一个SQLSelectQuery,都是组合关系。SQLSelectQuery有两个主要的派生类,分别是SQLSelectQueryBlock和SQLUnionQuery。SQLSelect类扩展SQLObjectImpl{SQLWithSubqueryClausewithSubQuery;SQLSelectQuery查询;}接口SQLSelectQuery扩展SQLObject{}类SQLSelectQueryBlock实现SQLSelectQuery{ListselectList;SQLTableSource来自;SQLExpr在哪里;SQLSelectGroupByClause组依据;SQLOrderByorderBy;SQLLimit限制;}类SQLUnionQuery实现SQLSelectQuery{SQLSelectQuery左;SQLSelectQuery对;SQLUnionOperator运算符;//UNION/UNION_ALL/MINUS/INTERSECT}下面是SQLSelectQueryBlock中包含的主要节点:表中的这些ast节点是SQL对应语法的一些表示,相信大家都很熟悉了,根据名字也很容易了解具体的语法。这里需要细化SQLTableSource节点,它有SQLExprTableSource(从表)、SQLJoinTableSource(连接表)、SQLSubqueryTableSource(子查询表)的常用实现,如:classSQLTableSourceImplextendsSQLObjectImplimplementsSQLTableSource{Stringalias;}//例如select*fromempwherei=3,wherefromempisaSQLExprTableSource//whereexprisaSQLIdentifierExprclasswithname=empSQLExprTableSourceextendsSQLTableSourceImpl{SQLExprexpr;}//例如select*fromempeinnerjoinorgoone.org_id=o.id//其中左边的'empe'是一个SQLExprTableSource,右边的'orgo'也是一个SQLExprTableSource//condition'e.org_id=o.id'是一个SQLBinaryOpExprclassSQLJoinTableSourceextendsSQLTableSourceImpl{SQLTableSourceleft;SQLTableSource对;加入类型//INNER_JOIN/CROSS_JOIN/LEFT_OUTER_JOIN/RIGHT_OUTER_JOIN/...SQLExprcondition;}//比如select*from(select*fromtemp)a,其中第一层from(...)是一个SQLSubqueryTableSourceSQLSubqueryTableSourceextendsSQLTableSourceImpl{SQLSelectselect;}另外SQLExpr出现的地方还有很多,比如where语句、连接条件、SQLSelectItem等,因此,也有必要详细了解一下,如://SQLName是SQLExpr的一种Expr,包括SQLIdentifierExpr、SQLPropertyExpr等publicinterfaceSQLNameextendsSQLExpr{}//例如,ID=3这里的ID是一个SQLIdentifierExpr类SQLIdentifierExprimplementsSQLExpr,SQLName{Stringname;}//例如A.ID=3其中A.ID是一个SQLPropertyExpr类SQLPropertyExprimplementsSQLExpr,SQLName{SQLExprowner;Stringname;}//例如ID=3ThisisaSQLBinaryOpExpr//leftisID(SQLIdentifierExpr)//rightis3(SQLIntegerExpr)classSQLBinaryOpExprimplementsSQLExpr{SQLExprleft;SQLExpr正确;SQLBinaryOperator运算符;}//例如select*fromwhereid=?,here?是名称为“?”的SQLVariantRefExprclassSQLVariantRefExprextendsSQLExprImpl{Stringname;}//例如,ID=3其中3是一个SQLIntegerExprpublicclassSQLIntegerExprextendsSQLNumericLiteralExprimplementsSQLValuableExpr{Numbernumber;//所有实现了SQLValuableExpr接口的SQLExpr都可用直接调用该方法求值@OverridepublicObjectgetValue(){returnn这个数字;}}//例如NAME='jobs'其中'jobs'是一个SQLCharExprpublicclassSQLCharExprextendsSQLTextLiteralExprimplementsSQLValuableExpr{Stringtext;}SqlParser定义了完整的ast每个节点对象,并将一条SQL语句解析成树状结构这些对象,而我们要做的就是根据这样的树状结构做相应的处理检查出来。使用示例:publicvoidenhanceSql(Stringsql){//parseListstatements=SQLUtils.parseStatements(sql,JdbcConstants.MYSQL);//只考虑一条语句SQLStatementstatement=statements.get(0);//只考虑查询语句SQLSelectStatementsqlSelectStatement=(SQLSelectStatement)statement;SQLSelectQuerysqlSelectQuery=sqlSelectStatement.getSelect().getQuery();//非联合查询语句selectItems.forEach(x->{//处理------------------});//获取表SQLTableSourcetable=sqlSelectQueryBlock.getFrom();//普通单表if(tableinstanceofSQLExprTableSource){//处理------------------//连接多个表}elseif(tableinstanceofSQLJoinTableSource){//处理--------------------//子查询作为表}elseif(tableinstanceofSQLSubqueryTableSource){//处理-------------------}//获取where条件SQLExprwhere=sqlSelectQueryBlock.getWhere();//如果是二进制表达式if(whereinstanceofSQLBinaryOpExpr){SQLBinaryOpExprsqlBinaryOpExpr=(SQLBinaryOpExpr)where;SQLExprleft=sqlBinaryOpExpr.getLeft();SQLBinaryOperator运算符=sqlBinaryOpExpr.getOperator();SQLExprright=sqlBinaryOpExpr.getRight();//处理------------------//如果是子查询}elseif(whereinstanceofSQLInSubQueryExpr){SQLInSubQueryExprsqlInSubQueryExpr=(SQLInSubQueryyExpr)其中;//处理------------------}//获取组SQLSelectGroupByClausegroupBy=sqlSelectQueryBlock.getGroupBy();//处理-------------------//获取排序SQLOrderByorderBy=sqlSelectQueryBlock.getOrderBy();//处理---------------------//获取分页SQLLimitlimit=sqlSelectQueryBlock.getLimit();//处理------------------//联合查询语句}elseif(sqlSelectQueryinstanceofSQLUnionQuery){//处理--------------------}}上面的例子中,只是简单的判断了类型。在实际项目中,可能需要勾选整个ast做递归到流程节点。实际上,当SQL语句变成ast结构时,我们只需要知道这个ast结构中存在什么样的节点,拿到节点判断类型,做相应的操作即可。至于你是recursiveoraccessormodeoranyotherwaytodealwithast就可以。