SQL语句的解析方法
数据是程序处理的主要内容,它一般存储在关系型数据库中,要操作它们最终必须要通过SQL语句来完成,因此,解读分析和处理SQL语句成为程序员的基本工作内容之一,当然有时这项任务是比较乏味的,如果让计算机来完成一些基本的分析解读工作如找出SQL语句涉及了哪些表,字段和条件等,可以帮助程序员解放出部分精力,投入到更有挑战性和复杂性的任务中去,本文将就如何解析单句SQL语句提出自己的解决方案和大家探讨,希望大家不吝批评指正。
首先说明以下单句SQL的范畴,它是指不存在嵌套的SQL语句,包括Select,insert,delete,update四大类型(具体的解析子类还有一种insertselect类型),其中以select最为复杂,下面将以它为例。另关于嵌套SQL尤其是多重嵌套SQL的分析似乎比较复杂,我一时没想出好的解决方案,如果您知道请不吝赐教。
1.关于SQL语句的预处理。
在对sql语句进行分析之前,有必要对它进行一些预处理,这样能减轻不少后面编程的负担。
预处理的主要工作是消除SQL语句前后的空白,将其中的连续空白字符(包括空格,TAB和回车换行)替换成单个空格;将sql语句全变成小写形式(或大写形式);在SQL语句的尾后加上结束符号,至于为什么加,这里先买个关子。具体的语句如下:sql=sql.trim();sql=sql.toLowerCase();sql=sql.replaceAll("s+","");sql=""+sql+"ENDOFSQL";2.将SQL语句分离成片段。
经过第一步的工作,一个多行的,存在大小写混杂的SQL语句已经变成了单行的小写SQL语句,接下来我们需要把整句分离成更小的片段。以Select语句为例,其中可能存在有select子句部分,from子句部分,where子句部分,groupby子句和orderby子句等,如果能成功的把整句分离成这些子句,我们的分析工作又前进了一步。先让我们看看下面的SQL示例:
selectc1,c2,c3fromt1,t2wherecondi3=3orcondi4=5orderbyo1,o2
通过观察我们可以发现,select子句是selectc1,c2,c3from,它的起始标志是select,结束标志是from;from子句是fromt1,t2where,它的起始标志是from,结束标志是where;where子句是wherecondi3=3orcondi4=5,它的起始标志是where,结束标志是orderby;orderby子句是orderbyo1,o2其起始标志是orderby,刚才我们在整句SQL尾后加上了"ENDOFSQL"字样,因此,orderby子句的结束标志是"ENDOFSQL"。
这个分析给我们解析SQL语句提供了一个思路,如果我们能找到各个子句的前后标志,在正则表达式的帮助下我们就可以轻松的获得每一种子句,下面给出一个找到from子句的完整正则表达式:
"(from)(.+)(where|on|having|groups+by|orders+by|ENDOFSQL)"
这句正则表示式让程序到整句SQL中查找符合这样条件的文本单元:它以from开头,结束标志是where,on,having,groupby,orderby或语句结束中间的一个,开始标志和结束标志之间可以是任何字符。这样,from子句的各种情况就都囊括进这个正则表达式了,它能找到以下类型的各种form子句:
from....where
from....on
from....having
from....groupby
from....orderby
from....ENDOFSQL(这个ENDOFSQL是预处理时加上的,如果用$符号会给程序造成麻烦)
3.找到片段中的各个部分。
有了表示片段的正则表达式,找到片段后从中分离出片段起始标志start,片段主体body和片段结束标志end就很容易了,请见代码:Patternpattern=Pattern.compile(segmentRegExp,Pattern.CASE_INSENSITIVE);for(inti=0;i正则表达式比较贪婪,它会竭力向后寻找,比如说SQL语句是这样写的:
select....from....where....orderby....
那么用"(from)(.+)(where|on|having|groups+by|orders+by|ENDOFSQL)"进行查找得到from子句不是
from....where
而是
from....where....orderby
这当然不是我们想要的结果,因此采取了从SQL开头开始截取不断增长的SQL语句进行分析,找到了from....where部分就不用继续往下找了,当然这在效率上有降低,但一些效率的付出相对于正确的结果来说是值得的。
4.将片段主体部分劈分开来
还是拿from子句做例子,得到它以后我们希望继续进行分析,最终得到from子句的表,这部分工作比较简单,使用特定的标志对body进行查找劈分即可,from子句的劈分标志较多,用正则表达式写出来是"(,|s+lefts+joins+|s+rights+joins+|s+inners+joins+)",各种情况都要涉及到。其实大多数子句主体部分的劈分标志都是逗号,用正则表达式写出来都比from子句的简单。
劈分的代码如下,每个分隔符之间的小部分放在链表中:Listls=newArrayList();Patternp=Pattern.compile(bodySplitPattern,Pattern.CASE_INSENSITIVE);//bodySplitPattern就是劈分的正则表达式//先清除掉前后空格body=body.trim();Matcherm=p.matcher(body);StringBuffersb=newStringBuffer();booleanresult=m.find();while(result){m.appendReplacement(sb,m.group(0)+Crlf);result=m.find();}m.appendTail(sb);//再按空行断行String[]arr=sb.toString().split("[n]+");intarrLength=arr.length;for(inti=0;isegments;/***//***构造函数,传入原始Sql语句,进行劈分。*@paramoriginalSql*/publicBaseSingleSqlParser(StringoriginalSql){this.originalSql=originalSql;segments=newArrayList();initializeSegments();splitSql2Segment();}/***//***初始化segments,强制子类实现**/protectedabstractvoidinitializeSegments();/***//***将originalSql劈分成一个个片段**/protectedvoidsplitSql2Segment(){for(SqlSegmentsqlSegment:segments){sqlSegment.parse(originalSql);}}/***//***得到解析完毕的Sql语句*@return*/publicStringgetParsedSql(){StringBuffersb=newStringBuffer();for(SqlSegmentsqlSegment:segments){sb.append(sqlSegment.getParsedSqlSegment()+"n");}Stringretval=sb.toString().replaceAll("n+","n");returnretval;}}下面是BaseSingleSqlParser的五种子类:packagecom.sitinspring.common.sqlparser.single;importcom.sitinspring.common.sqlparser.SqlSegment;/***//****单句删除语句解析器*@author何杨([email protected])**@since2009年2月3日8:58:48*@version1.00*/publicclassDeleteSqlParserextendsBaseSingleSqlParser{publicDeleteSqlParser(StringoriginalSql){super(originalSql);}@OverrideprotectedvoidinitializeSegments(){segments.add(newSqlSegment("(deletefrom)(.+)(where|ENDOFSQL)","[,]"));segments.add(newSqlSegment("(where)(.+)(ENDOFSQL)","(and|or)"));}}packagecom.sitinspring.common.sqlparser.single;importcom.sitinspring.common.sqlparser.SqlSegment;/***//****单句查询插入语句解析器*@author何杨([email protected])**@since2009年2月3日9:41:23*@version1.00*/publicclassInsertSelectSqlParserextendsBaseSingleSqlParser{publicInsertSelectSqlParser(StringoriginalSql){super(originalSql);}@OverrideprotectedvoidinitializeSegments(){segments.add(newSqlSegment("(insertinto)(.+)(select)","[,]"));segments.add(newSqlSegment("(select)(.+)(from)","[,]"));segments.add(newSqlSegment("(from)(.+)(where|on|having|groups+by|orders+by|ENDOFSQL)","(,|s+lefts+joins+|s+rights+joins+|s+inners+joins+)"));segments.add(newSqlSegment("(where|on|having)(.+)(groups+by|orders+by|ENDOFSQL)","(and|or)"));segments.add(newSqlSegment("(groups+by)(.+)(orders+by|ENDOFSQL)","[,]"));segments.add(newSqlSegment("(orders+by)(.+)(ENDOFSQL)","[,]"));}}packagecom.sitinspring.common.sqlparser.single;importcom.sitinspring.common.sqlparser.SqlSegment;/***//****单句插入语句解析器*@author何杨([email protected])**@since2009年2月3日9:16:44*@version1.00*/publicclassInsertSqlParserextendsBaseSingleSqlParser{publicInsertSqlParser(StringoriginalSql){super(originalSql);}@OverrideprotectedvoidinitializeSegments(){segments.add(newSqlSegment("(insertinto)(.+)([(])","[,]"));segments.add(newSqlSegment("([(])(.+)([)]values)","[,]"));segments.add(newSqlSegment("([)]values[(])(.+)([)])","[,]"));}@OverridepublicStringgetParsedSql(){Stringretval=super.getParsedSql();retval=retval+")";returnretval;}}packagecom.sitinspring.common.sqlparser.single;importcom.sitinspring.common.sqlparser.SqlSegment;/***//****单句查询语句解析器*@author何杨([email protected])**@since2009-2-2下午03:30:54*@version1.00*/publicclassSelectSqlParserextendsBaseSingleSqlParser{publicSelectSqlParser(StringoriginalSql){super(originalSql);}@OverrideprotectedvoidinitializeSegments(){segments.add(newSqlSegment("(select)(.+)(from)","[,]"));segments.add(newSqlSegment("(from)(.+)(where|on|having|groups+by|orders+by|ENDOFSQL)","(,|s+lefts+joins+|s+rights+joins+|s+inners+joins+)"));segments.add(newSqlSegment("(where|on|having)(.+)(groups+by|orders+by|ENDOFSQL)","(and|or)"));segments.add(newSqlSegment("(groups+by)(.+)(orders+by|ENDOFSQL)","[,]"));segments.add(newSqlSegment("(orders+by)(.+)(ENDOFSQL)","[,]"));}}packagecom.sitinspring.common.sqlparser.single;importcom.sitinspring.common.sqlparser.SqlSegment;/***//****单句更新语句解析器*@author何杨([email protected])**@since2009年2月3日9:08:46*@version1.00*/publicclassUpdateSqlParserextendsBaseSingleSqlParser{publicUpdateSqlParser(StringoriginalSql){super(originalSql);}@OverrideprotectedvoidinitializeSegments(){segments.add(newSqlSegment("(update)(.+)(set)","[,]"));segments.add(newSqlSegment("(set)(.+)(where|ENDOFSQL)","[,]"));segments.add(newSqlSegment("(where)(.+)(ENDOFSQL)","(and|or)"));}}下面是用于找到具体子类分析器的工厂类:packagecom.sitinspring.common.sqlparser.single;importjava.util.regex.Matcher;importjava.util.regex.Pattern;importcom.sitinspring.common.sqlparser.exception.NoSqlParserException;/***//***单句Sql解析器制造工厂*@author何杨([email protected])**@since2009-2-3上午09:45:49*@version1.00*/publicclassSingleSqlParserFactory{publicstaticBaseSingleSqlParsergenerateParser(Stringsql){if(contains(sql,"(insertinto)(.+)(select)(.+)(from)(.+)")){returnnewInsertSelectSqlParser(sql);}elseif(contains(sql,"(select)(.+)(from)(.+)")){returnnewSelectSqlParser(sql);}elseif(contains(sql,"(deletefrom)(.+)")){returnnewDeleteSqlParser(sql);}elseif(contains(sql,"(update)(.+)(set)(.+)")){returnnewUpdateSqlParser(sql);}elseif(contains(sql,"(insertinto)(.+)(values)(.+)")){returnnewInsertSqlParser(sql);}//sql=sql.replaceAll("ENDSQL","");thrownewNoSqlParserException(sql.replaceAll("ENDOFSQL",""));}/***//***看word是否在lineText中存在,支持正则表达式*@paramsql:要解析的sql语句*@paramregExp:正则表达式*@return*/privatestaticbooleancontains(Stringsql,StringregExp){Patternpattern=Pattern.compile(regExp,Pattern.CASE_INSENSITIVE);Matchermatcher=pattern.matcher(sql);returnmatcher.find();}}最后是表示子句的SqlSegment类:packagecom.sitinspring.common.sqlparser;importjava.util.ArrayList;importjava.util.List;importjava.util.regex.Matcher;importjava.util.regex.Pattern;/***//***Sql语句片段**@author何杨([email protected])**@since2009-2-2下午03:10:29*@version1.00*/publicclassSqlSegment{privatestaticfinalStringCrlf="n";privatestaticfinalStringFourSpace="";/***//***Sql语句片段开头部分*/privateStringstart;/***//***Sql语句片段中间部分*/privateStringbody;/***//***Sql语句片段结束部分*/privateStringend;/***//***用于分割中间部分的正则表达式*/privateStringbodySplitPattern;/***//***表示片段的正则表达式*/privateStringsegmentRegExp;/***//***分割后的Body小片段*/privateListbodyPieces;/***//***构造函数*@paramsegmentRegExp表示这个Sql片段的正则表达式*@parambodySplitPattern用于分割body的正则表达式*/publicSqlSegment(StringsegmentRegExp,StringbodySplitPattern){start="";body="";end="";this.segmentRegExp=segmentRegExp;this.bodySplitPattern=bodySplitPattern;this.bodyPieces=newArrayList();}/***//***从sql中查找符合segmentRegExp的部分,并赋值到start,body,end等三个属性中*@paramsql*/publicvoidparse(Stringsql){Patternpattern=Pattern.compile(segmentRegExp,Pattern.CASE_INSENSITIVE);for(inti=0;ils=newArrayList();Patternp=Pattern.compile(bodySplitPattern,Pattern.CASE_INSENSITIVE);//先清除掉前后空格body=body.trim();Matcherm=p.matcher(body);StringBuffersb=newStringBuffer();booleanresult=m.find();while(result){m.appendReplacement(sb,m.group(0)+Crlf);result=m.find();}m.appendTail(sb);//再按空格断行String[]arr=sb.toString().split("[n]+");intarrLength=arr.length;for(inti=0;i<arrLength;i++){Stringtemp=FourSpace+arr[i];if(i!=arrLength-1){temp=temp+Crlf;}ls.add(temp);}bodyPieces=ls;}/***//***取得解析好的Sql片段*@return*/publicStringgetParsedSqlSegment(){StringBuffersb=newStringBuffer();sb.append(start+Crlf);for(Stringpiece:bodyPieces){sb.append(piece+Crlf);}returnsb.toString();}publicStringgetBody(){returnbody;}publicvoidsetBody(Stringbody){this.body=body;}publicStringgetEnd(){returnend;}publicvoidsetEnd(Stringend){this.end=end;}publicStringgetStart(){returnstart;}publicvoidsetStart(Stringstart){this.start=start;}}